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