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