aws_lc_rs/cipher/
chacha.rs

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
7use crate::aws_lc::CRYPTO_chacha_20;
8use crate::cipher::block::{Block, BLOCK_LEN};
9use zeroize::Zeroize;
10
11use crate::error;
12
13pub(crate) const KEY_LEN: usize = 32usize;
14pub(crate) const NONCE_LEN: usize = 96 / 8;
15
16pub(crate) struct ChaCha20Key(pub(super) [u8; KEY_LEN]);
17
18impl From<[u8; KEY_LEN]> for ChaCha20Key {
19    fn from(bytes: [u8; KEY_LEN]) -> Self {
20        ChaCha20Key(bytes)
21    }
22}
23
24impl Drop for ChaCha20Key {
25    fn drop(&mut self) {
26        self.0.zeroize();
27    }
28}
29
30#[allow(clippy::needless_pass_by_value)]
31impl ChaCha20Key {
32    #[inline]
33    pub(crate) fn encrypt_in_place(&self, nonce: &[u8; NONCE_LEN], in_out: &mut [u8], ctr: u32) {
34        encrypt_in_place_chacha20(self, nonce, in_out, ctr);
35    }
36}
37
38#[inline]
39#[allow(clippy::needless_pass_by_value)]
40pub(crate) fn encrypt_block_chacha20(
41    key: &ChaCha20Key,
42    block: Block,
43    nonce: &[u8; NONCE_LEN],
44    counter: u32,
45) -> Result<Block, error::Unspecified> {
46    let mut cipher_text = [0u8; BLOCK_LEN];
47    encrypt_chacha20(
48        key,
49        block.as_ref().as_slice(),
50        &mut cipher_text,
51        nonce,
52        counter,
53    )?;
54
55    crate::fips::set_fips_service_status_unapproved();
56
57    Ok(Block::from(cipher_text))
58}
59
60#[inline]
61pub(crate) fn encrypt_chacha20(
62    key: &ChaCha20Key,
63    plaintext: &[u8],
64    ciphertext: &mut [u8],
65    nonce: &[u8; NONCE_LEN],
66    counter: u32,
67) -> Result<(), error::Unspecified> {
68    if ciphertext.len() < plaintext.len() {
69        return Err(error::Unspecified);
70    }
71    let key_bytes = &key.0;
72    unsafe {
73        CRYPTO_chacha_20(
74            ciphertext.as_mut_ptr(),
75            plaintext.as_ptr(),
76            plaintext.len(),
77            key_bytes.as_ptr(),
78            nonce.as_ptr(),
79            counter,
80        );
81    };
82    Ok(())
83}
84
85#[inline]
86pub(crate) fn encrypt_in_place_chacha20(
87    key: &ChaCha20Key,
88    nonce: &[u8; NONCE_LEN],
89    in_out: &mut [u8],
90    counter: u32,
91) {
92    let key_bytes = &key.0;
93    unsafe {
94        CRYPTO_chacha_20(
95            in_out.as_mut_ptr(),
96            in_out.as_ptr(),
97            in_out.len(),
98            key_bytes.as_ptr(),
99            nonce.as_ptr(),
100            counter,
101        );
102    }
103    crate::fips::set_fips_service_status_unapproved();
104}
105
106#[cfg(test)]
107mod 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}