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::{ |
20 | aead, cpu, error, |
21 | polyfill::{u64_from_usize, usize_from_u64_saturated, ArrayFlatten}, |
22 | }; |
23 | use 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 |
30 | pub 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 | |
38 | const MAX_IN_OUT_LEN: usize = super::max_input_len(block_len:64, overhead_blocks_per_nonce:1); |
39 | // https://tools.ietf.org/html/rfc8439#section-2.8 |
40 | const _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|. |
44 | fn 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 | |
52 | fn 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 | |
139 | fn 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)] |
230 | fn 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 | |
242 | fn 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 | |
250 | pub 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" ))] |
256 | union InOut<T> |
257 | where |
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)] |
271 | struct Out { |
272 | tag: [u8; super::TAG_LEN], |
273 | } |
274 | |
275 | #[inline ] |
276 | fn poly1305_update_padded_16(ctx: &mut poly1305::Context, input: &[u8]) { |
277 | if !input.is_empty() { |
278 | ctx.update(input); |
279 | let remainder_len: usize = 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. |
288 | pub(super) fn derive_poly1305_key(chacha_key: &chacha::Key, iv: Iv) -> poly1305::Key { |
289 | let mut key_bytes: [u8; 32] = [0u8; poly1305::KEY_LEN]; |
290 | chacha_key.encrypt_iv_xor_in_place(iv, &mut key_bytes); |
291 | poly1305::Key::new(key_and_nonce:key_bytes) |
292 | } |
293 | |