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