Coverage Report

Created: 2025-07-12 06:15

/rust/registry/src/index.crates.io-6f17d22bba15001f/ring-0.17.14/src/aead/chacha.rs
Line
Count
Source (jump to first uncovered line)
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 AUTHOR DISCLAIMS ALL WARRANTIES
9
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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
16
use super::{overlapping, quic::Sample, Nonce};
17
use crate::cpu;
18
use cfg_if::cfg_if;
19
20
cfg_if! {
21
    if #[cfg(any(
22
                all(target_arch = "aarch64", target_endian = "little"),
23
                all(target_arch = "arm", target_endian = "little"),
24
                target_arch = "x86",
25
                target_arch = "x86_64"
26
            ))] {
27
        #[macro_use]
28
        mod ffi;
29
        #[cfg(any(target_arch = "x86", test))]
30
        mod fallback;
31
    } else {
32
        mod fallback;
33
    }
34
}
35
36
use crate::polyfill::ArraySplitMap;
37
38
pub type Overlapping<'o> = overlapping::Overlapping<'o, u8>;
39
40
#[derive(Clone)]
41
pub struct Key {
42
    words: [u32; KEY_LEN / 4],
43
}
44
45
impl Key {
46
0
    pub(super) fn new(value: [u8; KEY_LEN]) -> Self {
47
0
        Self {
48
0
            words: value.array_split_map(u32::from_le_bytes),
49
0
        }
50
0
    }
51
}
52
53
impl Key {
54
    // Encrypts `in_out` with the counter 0 and returns counter 1,
55
    // where the counter is derived from the nonce `nonce`.
56
    #[inline]
57
0
    pub(super) fn encrypt_single_block_with_ctr_0<const N: usize>(
58
0
        &self,
59
0
        nonce: Nonce,
60
0
        in_out: &mut [u8; N],
61
0
        cpu: cpu::Features,
62
0
    ) -> Counter {
63
0
        assert!(N <= BLOCK_LEN);
64
0
        let (zero, one) = Counter::zero_one_less_safe(nonce);
65
0
        self.encrypt(zero, in_out.as_mut().into(), cpu);
66
0
        one
67
0
    }
Unexecuted instantiation: <ring::aead::chacha::Key>::encrypt_single_block_with_ctr_0::<32>
Unexecuted instantiation: <ring::aead::chacha::Key>::encrypt_single_block_with_ctr_0::<4>
68
69
    #[inline]
70
0
    pub fn new_mask(&self, sample: Sample) -> [u8; 5] {
71
0
        let cpu = cpu::features(); // TODO: Remove this.
72
0
        let (ctr, nonce) = sample.split_at(4);
73
0
        let ctr = u32::from_le_bytes(ctr.try_into().unwrap());
74
0
        let nonce = Nonce::assume_unique_for_key(nonce.try_into().unwrap());
75
0
        let ctr = Counter::from_nonce_and_ctr(nonce, ctr);
76
0
77
0
        let mut out: [u8; 5] = [0; 5];
78
0
        self.encrypt(ctr, out.as_mut().into(), cpu);
79
0
        out
80
0
    }
81
82
    #[inline(always)]
83
0
    pub(super) fn encrypt(&self, counter: Counter, in_out: Overlapping<'_>, cpu: cpu::Features) {
84
        cfg_if! {
85
            if #[cfg(all(target_arch = "aarch64", target_endian = "little"))] {
86
                use cpu::{GetFeature as _, arm::Neon};
87
                const NEON_MIN_LEN: usize = 192 + 1;
88
                if in_out.len() >= NEON_MIN_LEN {
89
                    if let Some(cpu) = cpu.get_feature() {
90
                        return chacha20_ctr32_ffi!(
91
                            unsafe { (NEON_MIN_LEN, Neon, Overlapping<'_>) => ChaCha20_ctr32_neon },
92
                            self, counter, in_out, cpu);
93
                    }
94
                }
95
                if in_out.len() >= 1 {
96
                    chacha20_ctr32_ffi!(
97
                        unsafe { (1, (), Overlapping<'_>) => ChaCha20_ctr32_nohw },
98
                        self, counter, in_out, ())
99
                }
100
            } else if #[cfg(all(target_arch = "arm", target_endian = "little"))] {
101
                use cpu::{GetFeature as _, arm::Neon};
102
                const NEON_MIN_LEN: usize = 192 + 1;
103
                if in_out.len() >= NEON_MIN_LEN {
104
                    if let Some(cpu) = cpu.get_feature() {
105
                        return chacha20_ctr32_ffi!(
106
                            unsafe { (NEON_MIN_LEN, Neon, &mut [u8]) => ChaCha20_ctr32_neon },
107
                            self, counter, in_out.copy_within(), cpu);
108
                    }
109
                }
110
                if in_out.len() >= 1 {
111
                    chacha20_ctr32_ffi!(
112
                        unsafe { (1, (), &mut [u8]) => ChaCha20_ctr32_nohw },
113
                        self, counter, in_out.copy_within(), ())
114
                }
115
            } else if #[cfg(target_arch = "x86")] {
116
                use cpu::{GetFeature as _, intel::Ssse3};
117
                if in_out.len() >= 1 {
118
                    if let Some(cpu) = cpu.get_feature() {
119
                        chacha20_ctr32_ffi!(
120
                            unsafe { (1, Ssse3, &mut [u8]) => ChaCha20_ctr32_ssse3 },
121
                            self, counter, in_out.copy_within(), cpu)
122
                    } else {
123
                        let _: cpu::Features = cpu;
124
                        fallback::ChaCha20_ctr32(self, counter, in_out)
125
                    }
126
                }
127
            } else if #[cfg(target_arch = "x86_64")] {
128
                use cpu::{GetFeature, intel::{Avx2, Ssse3}};
129
                const SSE_MIN_LEN: usize = 128 + 1; // Also AVX2, SSSE3_4X, SSSE3
130
0
                if in_out.len() >= SSE_MIN_LEN {
131
0
                    let values = cpu.values();
132
0
                    if let Some(cpu) = values.get_feature() {
133
0
                        return chacha20_ctr32_ffi!(
134
0
                            unsafe { (SSE_MIN_LEN, Avx2, Overlapping<'_>) => ChaCha20_ctr32_avx2 },
135
0
                            self, counter, in_out, cpu);
136
0
                    }
137
0
                    if let Some(cpu) = values.get_feature() {
138
0
                        return chacha20_ctr32_ffi!(
139
0
                            unsafe { (SSE_MIN_LEN, Ssse3, Overlapping<'_>) =>
140
0
                                ChaCha20_ctr32_ssse3_4x },
141
0
                            self, counter, in_out, cpu);
142
0
                    }
143
0
                }
144
0
                if in_out.len() >= 1 {
145
0
                    chacha20_ctr32_ffi!(
146
0
                        unsafe { (1, (), Overlapping<'_>) => ChaCha20_ctr32_nohw },
147
0
                        self, counter, in_out, ())
148
0
                }
149
            } else {
150
                let _: cpu::Features = cpu;
151
                fallback::ChaCha20_ctr32(self, counter, in_out)
152
            }
153
        }
154
0
    }
155
156
    #[inline]
157
0
    pub(super) fn words_less_safe(&self) -> &[u32; KEY_LEN / 4] {
158
0
        &self.words
159
0
    }
160
}
161
162
/// Counter || Nonce, all native endian.
163
#[repr(transparent)]
164
pub struct Counter([u32; 4]);
165
166
impl Counter {
167
    // Nonce-reuse: the caller must only use the first counter (0) for at most
168
    // a single block.
169
0
    fn zero_one_less_safe(nonce: Nonce) -> (Self, Self) {
170
0
        let ctr0 @ Self([_, n0, n1, n2]) = Self::from_nonce_and_ctr(nonce, 0);
171
0
        let ctr1 = Self([1, n0, n1, n2]);
172
0
        (ctr0, ctr1)
173
0
    }
174
175
0
    fn from_nonce_and_ctr(nonce: Nonce, ctr: u32) -> Self {
176
0
        let [n0, n1, n2] = nonce.as_ref().array_split_map(u32::from_le_bytes);
177
0
        Self([ctr, n0, n1, n2])
178
0
    }
179
180
    /// This is "less safe" because it hands off management of the counter to
181
    /// the caller.
182
    #[cfg(any(
183
        test,
184
        not(any(
185
            all(target_arch = "aarch64", target_endian = "little"),
186
            all(target_arch = "arm", target_endian = "little"),
187
            target_arch = "x86_64"
188
        ))
189
    ))]
190
    fn into_words_less_safe(self) -> [u32; 4] {
191
        self.0
192
    }
193
}
194
195
pub const KEY_LEN: usize = 32;
196
197
const BLOCK_LEN: usize = 64;
198
199
#[cfg(test)]
200
mod tests {
201
    extern crate alloc;
202
203
    use super::{super::overlapping::IndexError, *};
204
    use crate::error;
205
    use crate::testutil as 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(not(debug_assertions), 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
            all(target_arch = "aarch64", target_endian = "little"),
221
            all(target_arch = "arm", target_endian = "little"),
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);
230
    }
231
232
    // Smoketest the fallback implementation.
233
    #[test]
234
    fn chacha20_test_fallback() {
235
        chacha20_test(MAX_ALIGNMENT_AND_OFFSET_SUBSET, |key, ctr, in_out, _cpu| {
236
            fallback::ChaCha20_ctr32(key, ctr, in_out)
237
        });
238
    }
239
240
    // Verifies the encryption is successful when done on overlapping buffers.
241
    //
242
    // On some branches of the 32-bit x86 and ARM assembly code the in-place
243
    // operation fails in some situations where the input/output buffers are
244
    // not exactly overlapping. Such failures are dependent not only on the
245
    // degree of overlapping but also the length of the data. `encrypt_within`
246
    // works around that.
247
    fn chacha20_test(
248
        max_alignment_and_offset: (usize, usize),
249
        f: impl for<'k, 'o> Fn(&'k Key, Counter, Overlapping<'o>, cpu::Features),
250
    ) {
251
        let cpu = cpu::features();
252
253
        // Reuse a buffer to avoid slowing down the tests with allocations.
254
        let mut buf = vec![0u8; 1300];
255
256
        test::run(
257
            test_vector_file!("chacha_tests.txt"),
258
            move |section, test_case| {
259
                assert_eq!(section, "");
260
261
                let key = test_case.consume_bytes("Key");
262
                let key: &[u8; KEY_LEN] = key.as_slice().try_into()?;
263
                let key = Key::new(*key);
264
265
                let ctr = test_case.consume_usize("Ctr");
266
                let nonce = test_case.consume_bytes("Nonce");
267
                let input = test_case.consume_bytes("Input");
268
                let output = test_case.consume_bytes("Output");
269
270
                // Run the test case over all prefixes of the input because the
271
                // behavior of ChaCha20 implementation changes dependent on the
272
                // length of the input.
273
                for len in 0..=input.len() {
274
                    #[allow(clippy::cast_possible_truncation)]
275
                    chacha20_test_case_inner(
276
                        &key,
277
                        &nonce,
278
                        ctr as u32,
279
                        &input[..len],
280
                        &output[..len],
281
                        &mut buf,
282
                        max_alignment_and_offset,
283
                        cpu,
284
                        &f,
285
                    );
286
                }
287
288
                Ok(())
289
            },
290
        );
291
    }
292
293
    fn chacha20_test_case_inner(
294
        key: &Key,
295
        nonce: &[u8],
296
        ctr: u32,
297
        input: &[u8],
298
        expected: &[u8],
299
        buf: &mut [u8],
300
        (max_alignment, max_offset): (usize, usize),
301
        cpu: cpu::Features,
302
        f: &impl for<'k, 'o> Fn(&'k Key, Counter, Overlapping<'o>, cpu::Features),
303
    ) {
304
        const ARBITRARY: u8 = 123;
305
306
        for alignment in 0..=max_alignment {
307
            buf[..alignment].fill(ARBITRARY);
308
            let buf = &mut buf[alignment..];
309
            for offset in 0..=max_offset {
310
                let buf = &mut buf[..(offset + input.len())];
311
                buf[..offset].fill(ARBITRARY);
312
                let src = offset..;
313
                buf[src.clone()].copy_from_slice(input);
314
315
                let ctr = Counter::from_nonce_and_ctr(
316
                    Nonce::try_assume_unique_for_key(nonce).unwrap(),
317
                    ctr,
318
                );
319
                let in_out = Overlapping::new(buf, src)
320
                    .map_err(error::erase::<IndexError>)
321
                    .unwrap();
322
                f(key, ctr, in_out, cpu);
323
                assert_eq!(&buf[..input.len()], expected)
324
            }
325
        }
326
    }
327
}