ring/aead/
chacha20_poly1305.rs

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},
17    iv::Iv,
18    poly1305, Aad, Block, Direction, Nonce, Tag, BLOCK_LEN,
19};
20use crate::{aead, cpu, endian::*, error, polyfill};
21use 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
28pub 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|.
38fn 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
46fn 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
118fn 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
194pub 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")]
200union InOut<T>
201where
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)]
215struct Out {
216    tag: [u8; super::TAG_LEN],
217}
218
219#[inline(always)] // Statically eliminate branches on `direction`.
220fn 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]
260fn 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.
274pub(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)]
285mod tests {
286    #[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}