ring/aead/
chacha.rs

1// Copyright 2016 Brian Smith.
2// Portions Copyright (c) 2016, Google Inc.
3//
4// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
11// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
13// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16use super::{quic::Sample, Nonce};
17
18#[cfg(any(
19    test,
20    not(any(
21        target_arch = "aarch64",
22        target_arch = "arm",
23        target_arch = "x86",
24        target_arch = "x86_64"
25    ))
26))]
27mod fallback;
28
29use crate::polyfill::ArraySplitMap;
30use core::ops::RangeFrom;
31
32#[derive(Clone)]
33pub struct Key {
34    words: [u32; KEY_LEN / 4],
35}
36
37impl Key {
38    pub(super) fn new(value: [u8; KEY_LEN]) -> Self {
39        Self {
40            words: value.array_split_map(u32::from_le_bytes),
41        }
42    }
43}
44
45impl Key {
46    #[inline]
47    pub fn encrypt_in_place(&self, counter: Counter, in_out: &mut [u8]) {
48        self.encrypt_less_safe(counter, in_out, 0..);
49    }
50
51    #[inline]
52    pub fn encrypt_iv_xor_in_place(&self, iv: Iv, in_out: &mut [u8; 32]) {
53        // It is safe to use `into_counter_for_single_block_less_safe()`
54        // because `in_out` is exactly one block long.
55        debug_assert!(in_out.len() <= BLOCK_LEN);
56        self.encrypt_less_safe(iv.into_counter_for_single_block_less_safe(), in_out, 0..);
57    }
58
59    #[inline]
60    pub fn new_mask(&self, sample: Sample) -> [u8; 5] {
61        let mut out: [u8; 5] = [0; 5];
62        let iv = Iv::assume_unique_for_key(sample);
63
64        debug_assert!(out.len() <= BLOCK_LEN);
65        self.encrypt_less_safe(iv.into_counter_for_single_block_less_safe(), &mut out, 0..);
66
67        out
68    }
69
70    /// Analogous to `slice::copy_within()`.
71    pub fn encrypt_within(&self, counter: Counter, in_out: &mut [u8], src: RangeFrom<usize>) {
72        // XXX: The x86 and at least one branch of the ARM assembly language
73        // code doesn't allow overlapping input and output unless they are
74        // exactly overlapping. TODO: Figure out which branch of the ARM code
75        // has this limitation and come up with a better solution.
76        //
77        // https://rt.openssl.org/Ticket/Display.html?id=4362
78        if cfg!(any(target_arch = "arm", target_arch = "x86")) && src.start != 0 {
79            let len = in_out.len() - src.start;
80            in_out.copy_within(src, 0);
81            self.encrypt_in_place(counter, &mut in_out[..len]);
82        } else {
83            self.encrypt_less_safe(counter, in_out, src);
84        }
85    }
86
87    /// This is "less safe" because it skips the important check that `encrypt_within` does.
88    /// Only call this with `src` equal to `0..` or from `encrypt_within`.
89    #[inline]
90    fn encrypt_less_safe(&self, counter: Counter, in_out: &mut [u8], src: RangeFrom<usize>) {
91        #[cfg(any(
92            target_arch = "aarch64",
93            target_arch = "arm",
94            target_arch = "x86",
95            target_arch = "x86_64"
96        ))]
97        #[inline(always)]
98        pub(super) fn ChaCha20_ctr32(
99            key: &Key,
100            counter: Counter,
101            in_out: &mut [u8],
102            src: RangeFrom<usize>,
103        ) {
104            let in_out_len = in_out.len().checked_sub(src.start).unwrap();
105
106            // There's no need to worry if `counter` is incremented because it is
107            // owned here and we drop immediately after the call.
108            prefixed_extern! {
109                fn ChaCha20_ctr32(
110                    out: *mut u8,
111                    in_: *const u8,
112                    in_len: crate::c::size_t,
113                    key: &[u32; KEY_LEN / 4],
114                    counter: &Counter,
115                );
116            }
117            unsafe {
118                ChaCha20_ctr32(
119                    in_out.as_mut_ptr(),
120                    in_out[src].as_ptr(),
121                    in_out_len,
122                    key.words_less_safe(),
123                    &counter,
124                )
125            }
126        }
127
128        #[cfg(not(any(
129            target_arch = "aarch64",
130            target_arch = "arm",
131            target_arch = "x86",
132            target_arch = "x86_64"
133        )))]
134        use fallback::ChaCha20_ctr32;
135
136        ChaCha20_ctr32(self, counter, in_out, src);
137    }
138
139    #[inline]
140    pub(super) fn words_less_safe(&self) -> &[u32; KEY_LEN / 4] {
141        &self.words
142    }
143}
144
145/// Counter || Nonce, all native endian.
146#[repr(transparent)]
147pub struct Counter([u32; 4]);
148
149impl Counter {
150    pub fn zero(nonce: Nonce) -> Self {
151        Self::from_nonce_and_ctr(nonce, 0)
152    }
153
154    fn from_nonce_and_ctr(nonce: Nonce, ctr: u32) -> Self {
155        let [n0, n1, n2] = nonce.as_ref().array_split_map(u32::from_le_bytes);
156        Self([ctr, n0, n1, n2])
157    }
158
159    pub fn increment(&mut self) -> Iv {
160        let iv = Iv(self.0);
161        self.0[0] += 1;
162        iv
163    }
164
165    /// This is "less safe" because it hands off management of the counter to
166    /// the caller.
167    #[cfg(any(
168        test,
169        not(any(
170            target_arch = "aarch64",
171            target_arch = "arm",
172            target_arch = "x86",
173            target_arch = "x86_64"
174        ))
175    ))]
176    fn into_words_less_safe(self) -> [u32; 4] {
177        self.0
178    }
179}
180
181/// The IV for a single block encryption.
182///
183/// Intentionally not `Clone` to ensure each is used only once.
184pub struct Iv([u32; 4]);
185
186impl Iv {
187    fn assume_unique_for_key(value: [u8; 16]) -> Self {
188        Self(value.array_split_map(u32::from_le_bytes))
189    }
190
191    fn into_counter_for_single_block_less_safe(self) -> Counter {
192        Counter(self.0)
193    }
194}
195
196pub const KEY_LEN: usize = 32;
197
198const BLOCK_LEN: usize = 64;
199
200#[cfg(test)]
201mod tests {
202    extern crate alloc;
203
204    use super::*;
205    use crate::test;
206    use alloc::vec;
207
208    const MAX_ALIGNMENT_AND_OFFSET: (usize, usize) = (15, 259);
209    const MAX_ALIGNMENT_AND_OFFSET_SUBSET: (usize, usize) =
210        if cfg!(any(debug_assertions = "false", feature = "slow_tests")) {
211            MAX_ALIGNMENT_AND_OFFSET
212        } else {
213            (0, 0)
214        };
215
216    #[test]
217    fn chacha20_test_default() {
218        // Always use `MAX_OFFSET` if we hav assembly code.
219        let max_offset = if cfg!(any(
220            target_arch = "aarch64",
221            target_arch = "arm",
222            target_arch = "x86",
223            target_arch = "x86_64"
224        )) {
225            MAX_ALIGNMENT_AND_OFFSET
226        } else {
227            MAX_ALIGNMENT_AND_OFFSET_SUBSET
228        };
229        chacha20_test(max_offset, Key::encrypt_within);
230    }
231
232    // Smoketest the fallback implementation.
233    #[test]
234    fn chacha20_test_fallback() {
235        chacha20_test(MAX_ALIGNMENT_AND_OFFSET_SUBSET, fallback::ChaCha20_ctr32);
236    }
237
238    // Verifies the encryption is successful when done on overlapping buffers.
239    //
240    // On some branches of the 32-bit x86 and ARM assembly code the in-place
241    // operation fails in some situations where the input/output buffers are
242    // not exactly overlapping. Such failures are dependent not only on the
243    // degree of overlapping but also the length of the data. `encrypt_within`
244    // works around that.
245    fn chacha20_test(
246        max_alignment_and_offset: (usize, usize),
247        f: impl for<'k, 'i> Fn(&'k Key, Counter, &'i mut [u8], RangeFrom<usize>),
248    ) {
249        // Reuse a buffer to avoid slowing down the tests with allocations.
250        let mut buf = vec![0u8; 1300];
251
252        test::run(test_file!("chacha_tests.txt"), move |section, test_case| {
253            assert_eq!(section, "");
254
255            let key = test_case.consume_bytes("Key");
256            let key: &[u8; KEY_LEN] = key.as_slice().try_into()?;
257            let key = Key::new(*key);
258
259            let ctr = test_case.consume_usize("Ctr");
260            let nonce = test_case.consume_bytes("Nonce");
261            let input = test_case.consume_bytes("Input");
262            let output = test_case.consume_bytes("Output");
263
264            // Run the test case over all prefixes of the input because the
265            // behavior of ChaCha20 implementation changes dependent on the
266            // length of the input.
267            for len in 0..=input.len() {
268                #[allow(clippy::cast_possible_truncation)]
269                chacha20_test_case_inner(
270                    &key,
271                    &nonce,
272                    ctr as u32,
273                    &input[..len],
274                    &output[..len],
275                    &mut buf,
276                    max_alignment_and_offset,
277                    &f,
278                );
279            }
280
281            Ok(())
282        });
283    }
284
285    fn chacha20_test_case_inner(
286        key: &Key,
287        nonce: &[u8],
288        ctr: u32,
289        input: &[u8],
290        expected: &[u8],
291        buf: &mut [u8],
292        (max_alignment, max_offset): (usize, usize),
293        f: &impl for<'k, 'i> Fn(&'k Key, Counter, &'i mut [u8], RangeFrom<usize>),
294    ) {
295        const ARBITRARY: u8 = 123;
296
297        for alignment in 0..=max_alignment {
298            buf[..alignment].fill(ARBITRARY);
299            let buf = &mut buf[alignment..];
300            for offset in 0..=max_offset {
301                let buf = &mut buf[..(offset + input.len())];
302                buf[..offset].fill(ARBITRARY);
303                let src = offset..;
304                buf[src.clone()].copy_from_slice(input);
305
306                let ctr = Counter::from_nonce_and_ctr(
307                    Nonce::try_assume_unique_for_key(nonce).unwrap(),
308                    ctr,
309                );
310                f(key, ctr, buf, src);
311                assert_eq!(&buf[..input.len()], expected)
312            }
313        }
314    }
315}