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