ring/aead/
chacha20_poly1305.rs

1// Copyright 2015-2016 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15use super::{
16    chacha::{self, Counter, Iv},
17    poly1305, Aad, Nonce, Tag,
18};
19use crate::{
20    aead, cpu, error,
21    polyfill::{u64_from_usize, usize_from_u64_saturated, ArrayFlatten},
22};
23use core::ops::RangeFrom;
24
25/// ChaCha20-Poly1305 as described in [RFC 8439].
26///
27/// The keys are 256 bits long and the nonces are 96 bits long.
28///
29/// [RFC 8439]: https://tools.ietf.org/html/rfc8439
30pub static CHACHA20_POLY1305: aead::Algorithm = aead::Algorithm {
31    key_len: chacha::KEY_LEN,
32    init: chacha20_poly1305_init,
33    seal: chacha20_poly1305_seal,
34    open: chacha20_poly1305_open,
35    id: aead::AlgorithmID::CHACHA20_POLY1305,
36};
37
38const MAX_IN_OUT_LEN: usize = super::max_input_len(64, 1);
39// https://tools.ietf.org/html/rfc8439#section-2.8
40const _MAX_IN_OUT_LEN_BOUNDED_BY_RFC: () =
41    assert!(MAX_IN_OUT_LEN == usize_from_u64_saturated(274_877_906_880u64));
42
43/// Copies |key| into |ctx_buf|.
44fn chacha20_poly1305_init(
45    key: &[u8],
46    _cpu_features: cpu::Features,
47) -> Result<aead::KeyInner, error::Unspecified> {
48    let key: [u8; chacha::KEY_LEN] = key.try_into()?;
49    Ok(aead::KeyInner::ChaCha20Poly1305(chacha::Key::new(key)))
50}
51
52fn chacha20_poly1305_seal(
53    key: &aead::KeyInner,
54    nonce: Nonce,
55    aad: Aad<&[u8]>,
56    in_out: &mut [u8],
57    cpu_features: cpu::Features,
58) -> Result<Tag, error::Unspecified> {
59    let chacha20_key = match key {
60        aead::KeyInner::ChaCha20Poly1305(key) => key,
61        _ => unreachable!(),
62    };
63
64    if in_out.len() > MAX_IN_OUT_LEN {
65        return Err(error::Unspecified);
66    }
67    /// RFC 8439 Section 2.8 says the maximum AAD length is 2**64 - 1, which is
68    /// never larger than usize::MAX, so we don't need an explicit length
69    /// check.
70    const _USIZE_BOUNDED_BY_U64: u64 = u64_from_usize(usize::MAX);
71
72    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
73    if has_integrated(cpu_features) {
74        // XXX: BoringSSL uses `alignas(16)` on `key` instead of on the
75        // structure, but Rust can't do that yet; see
76        // https://github.com/rust-lang/rust/issues/73557.
77        //
78        // Keep in sync with the anonymous struct of BoringSSL's
79        // `chacha20_poly1305_seal_data`.
80        #[repr(align(16), C)]
81        #[derive(Clone, Copy)]
82        struct seal_data_in {
83            key: [u32; chacha::KEY_LEN / 4],
84            counter: u32,
85            nonce: [u8; super::NONCE_LEN],
86            extra_ciphertext: *const u8,
87            extra_ciphertext_len: usize,
88        }
89
90        let mut data = InOut {
91            input: seal_data_in {
92                key: *chacha20_key.words_less_safe(),
93                counter: 0,
94                nonce: *nonce.as_ref(),
95                extra_ciphertext: core::ptr::null(),
96                extra_ciphertext_len: 0,
97            },
98        };
99
100        // Encrypts `plaintext_len` bytes from `plaintext` and writes them to `out_ciphertext`.
101        prefixed_extern! {
102            fn chacha20_poly1305_seal(
103                out_ciphertext: *mut u8,
104                plaintext: *const u8,
105                plaintext_len: usize,
106                ad: *const u8,
107                ad_len: usize,
108                data: &mut InOut<seal_data_in>,
109            );
110        }
111
112        let out = unsafe {
113            chacha20_poly1305_seal(
114                in_out.as_mut_ptr(),
115                in_out.as_ptr(),
116                in_out.len(),
117                aad.as_ref().as_ptr(),
118                aad.as_ref().len(),
119                &mut data,
120            );
121            &data.out
122        };
123
124        return Ok(Tag(out.tag));
125    }
126
127    let mut counter = Counter::zero(nonce);
128    let mut auth = {
129        let key = derive_poly1305_key(chacha20_key, counter.increment());
130        poly1305::Context::from_key(key, cpu_features)
131    };
132
133    poly1305_update_padded_16(&mut auth, aad.as_ref());
134    chacha20_key.encrypt_in_place(counter, in_out);
135    poly1305_update_padded_16(&mut auth, in_out);
136    Ok(finish(auth, aad.as_ref().len(), in_out.len()))
137}
138
139fn chacha20_poly1305_open(
140    key: &aead::KeyInner,
141    nonce: Nonce,
142    aad: Aad<&[u8]>,
143    in_out: &mut [u8],
144    src: RangeFrom<usize>,
145    cpu_features: cpu::Features,
146) -> Result<Tag, error::Unspecified> {
147    let chacha20_key = match key {
148        aead::KeyInner::ChaCha20Poly1305(key) => key,
149        _ => unreachable!(),
150    };
151
152    let unprefixed_len = in_out
153        .len()
154        .checked_sub(src.start)
155        .ok_or(error::Unspecified)?;
156    if unprefixed_len > MAX_IN_OUT_LEN {
157        return Err(error::Unspecified);
158    }
159    // RFC 8439 Section 2.8 says the maximum AAD length is 2**64 - 1, which is
160    // never larger than usize::MAX, so we don't need an explicit length
161    // check.
162    const _USIZE_BOUNDED_BY_U64: u64 = u64_from_usize(usize::MAX);
163
164    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
165    if has_integrated(cpu_features) {
166        // XXX: BoringSSL uses `alignas(16)` on `key` instead of on the
167        // structure, but Rust can't do that yet; see
168        // https://github.com/rust-lang/rust/issues/73557.
169        //
170        // Keep in sync with the anonymous struct of BoringSSL's
171        // `chacha20_poly1305_open_data`.
172        #[derive(Copy, Clone)]
173        #[repr(align(16), C)]
174        struct open_data_in {
175            key: [u32; chacha::KEY_LEN / 4],
176            counter: u32,
177            nonce: [u8; super::NONCE_LEN],
178        }
179
180        let mut data = InOut {
181            input: open_data_in {
182                key: *chacha20_key.words_less_safe(),
183                counter: 0,
184                nonce: *nonce.as_ref(),
185            },
186        };
187
188        // Decrypts `plaintext_len` bytes from `ciphertext` and writes them to `out_plaintext`.
189        prefixed_extern! {
190            fn chacha20_poly1305_open(
191                out_plaintext: *mut u8,
192                ciphertext: *const u8,
193                plaintext_len: usize,
194                ad: *const u8,
195                ad_len: usize,
196                data: &mut InOut<open_data_in>,
197            );
198        }
199
200        let out = unsafe {
201            chacha20_poly1305_open(
202                in_out.as_mut_ptr(),
203                in_out.as_ptr().add(src.start),
204                unprefixed_len,
205                aad.as_ref().as_ptr(),
206                aad.as_ref().len(),
207                &mut data,
208            );
209            &data.out
210        };
211
212        return Ok(Tag(out.tag));
213    }
214
215    let mut counter = Counter::zero(nonce);
216    let mut auth = {
217        let key = derive_poly1305_key(chacha20_key, counter.increment());
218        poly1305::Context::from_key(key, cpu_features)
219    };
220
221    poly1305_update_padded_16(&mut auth, aad.as_ref());
222    poly1305_update_padded_16(&mut auth, &in_out[src.clone()]);
223    chacha20_key.encrypt_within(counter, in_out, src.clone());
224    Ok(finish(auth, aad.as_ref().len(), unprefixed_len))
225}
226
227#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
228#[allow(clippy::needless_return)]
229#[inline(always)]
230fn has_integrated(cpu_features: cpu::Features) -> bool {
231    #[cfg(target_arch = "aarch64")]
232    {
233        return cpu::arm::NEON.available(cpu_features);
234    }
235
236    #[cfg(target_arch = "x86_64")]
237    {
238        return cpu::intel::SSE41.available(cpu_features);
239    }
240}
241
242fn finish(mut auth: poly1305::Context, aad_len: usize, in_out_len: usize) -> Tag {
243    let block: [[u8; 8]; 2] = [aad_len, in_out_len]
244        .map(u64_from_usize)
245        .map(u64::to_le_bytes);
246    auth.update(&block.array_flatten());
247    auth.finish()
248}
249
250pub type Key = chacha::Key;
251
252// Keep in sync with BoringSSL's `chacha20_poly1305_open_data` and
253// `chacha20_poly1305_seal_data`.
254#[repr(C)]
255#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
256union InOut<T>
257where
258    T: Copy,
259{
260    input: T,
261    out: Out,
262}
263
264// It isn't obvious whether the assembly code works for tags that aren't
265// 16-byte aligned. In practice it will always be 16-byte aligned because it
266// is embedded in a union where the other member of the union is 16-byte
267// aligned.
268#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
269#[derive(Clone, Copy)]
270#[repr(align(16), C)]
271struct Out {
272    tag: [u8; super::TAG_LEN],
273}
274
275#[inline]
276fn poly1305_update_padded_16(ctx: &mut poly1305::Context, input: &[u8]) {
277    if !input.is_empty() {
278        ctx.update(input);
279        let remainder_len = input.len() % poly1305::BLOCK_LEN;
280        if remainder_len != 0 {
281            const ZEROES: [u8; poly1305::BLOCK_LEN] = [0; poly1305::BLOCK_LEN];
282            ctx.update(&ZEROES[..(poly1305::BLOCK_LEN - remainder_len)])
283        }
284    }
285}
286
287// Also used by chacha20_poly1305_openssh.
288pub(super) fn derive_poly1305_key(chacha_key: &chacha::Key, iv: Iv) -> poly1305::Key {
289    let mut key_bytes = [0u8; poly1305::KEY_LEN];
290    chacha_key.encrypt_iv_xor_in_place(iv, &mut key_bytes);
291    poly1305::Key::new(key_bytes)
292}