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