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 
15 use super::{
16     chacha::{self, Counter, Iv},
17     poly1305, Aad, Nonce, Tag,
18 };
19 use crate::{aead, cpu, endian::*, error, polyfill};
20 use core::ops::RangeFrom;
21 
22 /// ChaCha20-Poly1305 as described in [RFC 8439].
23 ///
24 /// The keys are 256 bits long and the nonces are 96 bits long.
25 ///
26 /// [RFC 8439]: https://tools.ietf.org/html/rfc8439
27 pub static CHACHA20_POLY1305: aead::Algorithm = aead::Algorithm {
28     key_len: chacha::KEY_LEN,
29     init: chacha20_poly1305_init,
30     seal: chacha20_poly1305_seal,
31     open: chacha20_poly1305_open,
32     id: aead::AlgorithmID::CHACHA20_POLY1305,
33     max_input_len: super::max_input_len(64, 1),
34 };
35 
36 /// Copies |key| into |ctx_buf|.
chacha20_poly1305_init( key: &[u8], cpu_features: cpu::Features, ) -> Result<aead::KeyInner, error::Unspecified>37 fn chacha20_poly1305_init(
38     key: &[u8],
39     cpu_features: cpu::Features,
40 ) -> Result<aead::KeyInner, error::Unspecified> {
41     let key: [u8; chacha::KEY_LEN] = key.try_into()?;
42     Ok(aead::KeyInner::ChaCha20Poly1305(chacha::Key::new(
43         key,
44         cpu_features,
45     )))
46 }
47 
chacha20_poly1305_seal( key: &aead::KeyInner, nonce: Nonce, aad: Aad<&[u8]>, in_out: &mut [u8], ) -> Tag48 fn chacha20_poly1305_seal(
49     key: &aead::KeyInner,
50     nonce: Nonce,
51     aad: Aad<&[u8]>,
52     in_out: &mut [u8],
53 ) -> Tag {
54     let chacha20_key = match key {
55         aead::KeyInner::ChaCha20Poly1305(key) => key,
56         _ => unreachable!(),
57     };
58 
59     #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
60     {
61         if cpu::intel::SSE41.available(chacha20_key.cpu_features())
62             || cpu::arm::NEON.available(chacha20_key.cpu_features())
63         {
64             // XXX: BoringSSL uses `alignas(16)` on `key` instead of on the
65             // structure, but Rust can't do that yet; see
66             // https://github.com/rust-lang/rust/issues/73557.
67             //
68             // Keep in sync with the anonymous struct of BoringSSL's
69             // `chacha20_poly1305_seal_data`.
70             #[repr(align(16), C)]
71             #[derive(Clone, Copy)]
72             struct seal_data_in {
73                 key: [u32; chacha::KEY_LEN / 4],
74                 counter: u32,
75                 nonce: [u8; super::NONCE_LEN],
76                 extra_ciphertext: *const u8,
77                 extra_ciphertext_len: usize,
78             }
79 
80             let mut data = InOut {
81                 input: seal_data_in {
82                     key: *chacha20_key.words_less_safe(),
83                     counter: 0,
84                     nonce: *nonce.as_ref(),
85                     extra_ciphertext: core::ptr::null(),
86                     extra_ciphertext_len: 0,
87                 },
88             };
89 
90             // Encrypts `plaintext_len` bytes from `plaintext` and writes them to `out_ciphertext`.
91             prefixed_extern! {
92                 fn chacha20_poly1305_seal(
93                     out_ciphertext: *mut u8,
94                     plaintext: *const u8,
95                     plaintext_len: usize,
96                     ad: *const u8,
97                     ad_len: usize,
98                     data: &mut InOut<seal_data_in>,
99                 );
100             }
101 
102             let out = unsafe {
103                 chacha20_poly1305_seal(
104                     in_out.as_mut_ptr(),
105                     in_out.as_ptr(),
106                     in_out.len(),
107                     aad.as_ref().as_ptr(),
108                     aad.as_ref().len(),
109                     &mut data,
110                 );
111                 &data.out
112             };
113 
114             return Tag(out.tag);
115         }
116     }
117 
118     let mut counter = Counter::zero(nonce);
119     let mut auth = {
120         let key = derive_poly1305_key(chacha20_key, counter.increment());
121         poly1305::Context::from_key(key)
122     };
123 
124     poly1305_update_padded_16(&mut auth, aad.as_ref());
125     chacha20_key.encrypt_in_place(counter, in_out);
126     poly1305_update_padded_16(&mut auth, in_out);
127     finish(auth, aad.as_ref().len(), in_out.len())
128 }
129 
chacha20_poly1305_open( key: &aead::KeyInner, nonce: Nonce, aad: Aad<&[u8]>, in_out: &mut [u8], src: RangeFrom<usize>, ) -> Tag130 fn chacha20_poly1305_open(
131     key: &aead::KeyInner,
132     nonce: Nonce,
133     aad: Aad<&[u8]>,
134     in_out: &mut [u8],
135     src: RangeFrom<usize>,
136 ) -> Tag {
137     let chacha20_key = match key {
138         aead::KeyInner::ChaCha20Poly1305(key) => key,
139         _ => unreachable!(),
140     };
141 
142     #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
143     {
144         if cpu::intel::SSE41.available(chacha20_key.cpu_features())
145             || cpu::arm::NEON.available(chacha20_key.cpu_features())
146         {
147             // XXX: BoringSSL uses `alignas(16)` on `key` instead of on the
148             // structure, but Rust can't do that yet; see
149             // https://github.com/rust-lang/rust/issues/73557.
150             //
151             // Keep in sync with the anonymous struct of BoringSSL's
152             // `chacha20_poly1305_open_data`.
153             #[derive(Copy, Clone)]
154             #[repr(align(16), C)]
155             struct open_data_in {
156                 key: [u32; chacha::KEY_LEN / 4],
157                 counter: u32,
158                 nonce: [u8; super::NONCE_LEN],
159             }
160 
161             let mut data = InOut {
162                 input: open_data_in {
163                     key: *chacha20_key.words_less_safe(),
164                     counter: 0,
165                     nonce: *nonce.as_ref(),
166                 },
167             };
168 
169             // Decrypts `plaintext_len` bytes from `ciphertext` and writes them to `out_plaintext`.
170             prefixed_extern! {
171                 fn chacha20_poly1305_open(
172                     out_plaintext: *mut u8,
173                     ciphertext: *const u8,
174                     plaintext_len: usize,
175                     ad: *const u8,
176                     ad_len: usize,
177                     data: &mut InOut<open_data_in>,
178                 );
179             }
180 
181             let out = unsafe {
182                 chacha20_poly1305_open(
183                     in_out.as_mut_ptr(),
184                     in_out.as_ptr().add(src.start),
185                     in_out.len() - src.start,
186                     aad.as_ref().as_ptr(),
187                     aad.as_ref().len(),
188                     &mut data,
189                 );
190                 &data.out
191             };
192 
193             return Tag(out.tag);
194         }
195     }
196 
197     let mut counter = Counter::zero(nonce);
198     let mut auth = {
199         let key = derive_poly1305_key(chacha20_key, counter.increment());
200         poly1305::Context::from_key(key)
201     };
202 
203     poly1305_update_padded_16(&mut auth, aad.as_ref());
204     poly1305_update_padded_16(&mut auth, &in_out[src.clone()]);
205     chacha20_key.encrypt_within(counter, in_out, src.clone());
206     finish(auth, aad.as_ref().len(), in_out[src].len())
207 }
208 
finish(mut auth: poly1305::Context, aad_len: usize, in_out_len: usize) -> Tag209 fn finish(mut auth: poly1305::Context, aad_len: usize, in_out_len: usize) -> Tag {
210     auth.update(
211         [
212             LittleEndian::from(polyfill::u64_from_usize(aad_len)),
213             LittleEndian::from(polyfill::u64_from_usize(in_out_len)),
214         ]
215         .as_byte_array(),
216     );
217     auth.finish()
218 }
219 
220 pub type Key = chacha::Key;
221 
222 // Keep in sync with BoringSSL's `chacha20_poly1305_open_data` and
223 // `chacha20_poly1305_seal_data`.
224 #[repr(C)]
225 #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
226 union InOut<T>
227 where
228     T: Copy,
229 {
230     input: T,
231     out: Out,
232 }
233 
234 // It isn't obvious whether the assembly code works for tags that aren't
235 // 16-byte aligned. In practice it will always be 16-byte aligned because it
236 // is embedded in a union where the other member of the union is 16-byte
237 // aligned.
238 #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
239 #[derive(Clone, Copy)]
240 #[repr(align(16), C)]
241 struct Out {
242     tag: [u8; super::TAG_LEN],
243 }
244 
245 #[inline]
poly1305_update_padded_16(ctx: &mut poly1305::Context, input: &[u8])246 fn poly1305_update_padded_16(ctx: &mut poly1305::Context, input: &[u8]) {
247     if !input.is_empty() {
248         ctx.update(input);
249         let remainder_len = input.len() % poly1305::BLOCK_LEN;
250         if remainder_len != 0 {
251             const ZEROES: [u8; poly1305::BLOCK_LEN] = [0; poly1305::BLOCK_LEN];
252             ctx.update(&ZEROES[..(poly1305::BLOCK_LEN - remainder_len)])
253         }
254     }
255 }
256 
257 // Also used by chacha20_poly1305_openssh.
derive_poly1305_key(chacha_key: &chacha::Key, iv: Iv) -> poly1305::Key258 pub(super) fn derive_poly1305_key(chacha_key: &chacha::Key, iv: Iv) -> poly1305::Key {
259     let mut key_bytes = [0u8; poly1305::KEY_LEN];
260     chacha_key.encrypt_iv_xor_in_place(iv, &mut key_bytes);
261     poly1305::Key::new(key_bytes, chacha_key.cpu_features())
262 }
263 
264 #[cfg(test)]
265 mod tests {
266     #[test]
max_input_len_test()267     fn max_input_len_test() {
268         // https://tools.ietf.org/html/rfc8439#section-2.8
269         assert_eq!(super::CHACHA20_POLY1305.max_input_len, 274_877_906_880u64);
270     }
271 }
272