Coverage Report

Created: 2025-07-23 07:04

/rust/registry/src/index.crates.io-6f17d22bba15001f/aws-lc-rs-1.13.0/src/cipher/chacha.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2016 Brian Smith.
2
// Portions Copyright (c) 2016, Google Inc.
3
// SPDX-License-Identifier: ISC
4
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
// SPDX-License-Identifier: Apache-2.0 OR ISC
6
7
use crate::aws_lc::CRYPTO_chacha_20;
8
use crate::cipher::block::{Block, BLOCK_LEN};
9
use zeroize::Zeroize;
10
11
use crate::error;
12
13
pub(crate) const KEY_LEN: usize = 32usize;
14
pub(crate) const NONCE_LEN: usize = 96 / 8;
15
16
pub(crate) struct ChaCha20Key(pub(super) [u8; KEY_LEN]);
17
18
impl From<[u8; KEY_LEN]> for ChaCha20Key {
19
0
    fn from(bytes: [u8; KEY_LEN]) -> Self {
20
0
        ChaCha20Key(bytes)
21
0
    }
22
}
23
24
impl Drop for ChaCha20Key {
25
0
    fn drop(&mut self) {
26
0
        self.0.zeroize();
27
0
    }
28
}
29
30
#[allow(clippy::needless_pass_by_value)]
31
impl ChaCha20Key {
32
    #[inline]
33
0
    pub(crate) fn encrypt_in_place(&self, nonce: &[u8; NONCE_LEN], in_out: &mut [u8], ctr: u32) {
34
0
        encrypt_in_place_chacha20(self, nonce, in_out, ctr);
35
0
    }
36
}
37
38
#[inline]
39
#[allow(clippy::needless_pass_by_value)]
40
0
pub(crate) fn encrypt_block_chacha20(
41
0
    key: &ChaCha20Key,
42
0
    block: Block,
43
0
    nonce: &[u8; NONCE_LEN],
44
0
    counter: u32,
45
0
) -> Result<Block, error::Unspecified> {
46
0
    let mut cipher_text = [0u8; BLOCK_LEN];
47
0
    encrypt_chacha20(
48
0
        key,
49
0
        block.as_ref().as_slice(),
50
0
        &mut cipher_text,
51
0
        nonce,
52
0
        counter,
53
0
    )?;
54
55
0
    crate::fips::set_fips_service_status_unapproved();
56
0
57
0
    Ok(Block::from(cipher_text))
58
0
}
59
60
#[inline]
61
0
pub(crate) fn encrypt_chacha20(
62
0
    key: &ChaCha20Key,
63
0
    plaintext: &[u8],
64
0
    ciphertext: &mut [u8],
65
0
    nonce: &[u8; NONCE_LEN],
66
0
    counter: u32,
67
0
) -> Result<(), error::Unspecified> {
68
0
    if ciphertext.len() < plaintext.len() {
69
0
        return Err(error::Unspecified);
70
0
    }
71
0
    let key_bytes = &key.0;
72
0
    unsafe {
73
0
        CRYPTO_chacha_20(
74
0
            ciphertext.as_mut_ptr(),
75
0
            plaintext.as_ptr(),
76
0
            plaintext.len(),
77
0
            key_bytes.as_ptr(),
78
0
            nonce.as_ptr(),
79
0
            counter,
80
0
        );
81
0
    };
82
0
    Ok(())
83
0
}
84
85
#[inline]
86
0
pub(crate) fn encrypt_in_place_chacha20(
87
0
    key: &ChaCha20Key,
88
0
    nonce: &[u8; NONCE_LEN],
89
0
    in_out: &mut [u8],
90
0
    counter: u32,
91
0
) {
92
0
    let key_bytes = &key.0;
93
0
    unsafe {
94
0
        CRYPTO_chacha_20(
95
0
            in_out.as_mut_ptr(),
96
0
            in_out.as_ptr(),
97
0
            in_out.len(),
98
0
            key_bytes.as_ptr(),
99
0
            nonce.as_ptr(),
100
0
            counter,
101
0
        );
102
0
    }
103
0
    crate::fips::set_fips_service_status_unapproved();
104
0
}
105
106
#[cfg(test)]
107
mod tests {
108
    use super::*;
109
    use crate::{test, test_file};
110
111
    const MAX_ALIGNMENT: usize = 15;
112
113
    // Verifies the encryption is successful when done on overlapping buffers.
114
    //
115
    // On some branches of the 32-bit x86 and ARM assembly code the in-place
116
    // operation fails in some situations where the input/output buffers are
117
    // not exactly overlapping. Such failures are dependent not only on the
118
    // degree of overlapping but also the length of the data. `encrypt_within`
119
    // works around that.
120
    #[test]
121
    fn chacha20_test() {
122
        // Reuse a buffer to avoid slowing down the tests with allocations.
123
        let mut buf = vec![0u8; 1300];
124
125
        test::run(
126
            test_file!("data/chacha_tests.txt"),
127
            move |section, test_case| {
128
                assert_eq!(section, "");
129
130
                let key = test_case.consume_bytes("Key");
131
                let key: &[u8; KEY_LEN] = key.as_slice().try_into()?;
132
                let key = ChaCha20Key::from(*key);
133
134
                #[allow(clippy::cast_possible_truncation)]
135
                let ctr = test_case.consume_usize("Ctr") as u32;
136
                let nonce: [u8; NONCE_LEN] = test_case.consume_bytes("Nonce").try_into().unwrap();
137
                let input = test_case.consume_bytes("Input");
138
                let output = test_case.consume_bytes("Output");
139
140
                // Run the test case over all prefixes of the input because the
141
                // behavior of ChaCha20 implementation changes dependent on the
142
                // length of the input.
143
                for len in 0..=input.len() {
144
                    chacha20_test_case_inner(
145
                        &key,
146
                        nonce,
147
                        ctr,
148
                        &input[..len],
149
                        &output[..len],
150
                        &mut buf,
151
                    );
152
                }
153
154
                Ok(())
155
            },
156
        );
157
    }
158
159
    fn chacha20_test_case_inner(
160
        key: &ChaCha20Key,
161
        nonce: [u8; NONCE_LEN],
162
        ctr: u32,
163
        input: &[u8],
164
        expected: &[u8],
165
        buf: &mut [u8],
166
    ) {
167
        // Straightforward encryption into disjoint buffers is computed
168
        // correctly.
169
        const ARBITRARY: u8 = 123;
170
171
        for alignment in 0..=MAX_ALIGNMENT {
172
            buf[..alignment].fill(ARBITRARY);
173
            let buf = &mut buf[..input.len()];
174
            buf.copy_from_slice(input);
175
            let nonce = &nonce;
176
177
            key.encrypt_in_place(nonce, buf, ctr);
178
            assert_eq!(
179
                &buf[..input.len()],
180
                expected,
181
                "Failed on alignment: {alignment}",
182
            );
183
        }
184
    }
185
}