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(block_len:64, overhead_blocks_per_nonce: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: 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.
288pub(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