Skip to main content

wacore_libsignal/crypto/
aes_cbc.rs

1//
2// Copyright 2023 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6use std::result::Result;
7
8use crate::crypto::provider::provider;
9
10#[derive(Debug, thiserror::Error)]
11pub enum EncryptionError {
12    #[error("The key or IV is the wrong length.")]
13    BadKeyOrIv,
14    #[error("Padding error during encryption.")]
15    BadPadding,
16}
17
18#[derive(Debug, thiserror::Error)]
19pub enum DecryptionError {
20    #[error("The key or IV is the wrong length.")]
21    BadKeyOrIv,
22    #[error(
23        "These cases should not be distinguished; message corruption can cause either problem."
24    )]
25    BadCiphertext(&'static str),
26}
27
28pub fn aes_256_cbc_encrypt_into(
29    ptext: &[u8],
30    key: &[u8],
31    iv: &[u8],
32    output: &mut Vec<u8>,
33) -> Result<(), EncryptionError> {
34    let key: &[u8; 32] = key.try_into().map_err(|_| EncryptionError::BadKeyOrIv)?;
35    let iv: &[u8; 16] = iv.try_into().map_err(|_| EncryptionError::BadKeyOrIv)?;
36    provider()
37        .aes_256_cbc_encrypt(key, iv, ptext, output)
38        .map_err(|_| EncryptionError::BadPadding)
39}
40
41/// The output buffer is cleared and filled with the decrypted plaintext.
42pub fn aes_256_cbc_decrypt_into(
43    ctext: &[u8],
44    key: &[u8],
45    iv: &[u8],
46    output: &mut Vec<u8>,
47) -> Result<(), DecryptionError> {
48    let key: &[u8; 32] = key.try_into().map_err(|_| DecryptionError::BadKeyOrIv)?;
49    let iv: &[u8; 16] = iv.try_into().map_err(|_| DecryptionError::BadKeyOrIv)?;
50    provider()
51        .aes_256_cbc_decrypt(key, iv, ctext, output)
52        .map_err(|_| DecryptionError::BadCiphertext("failed to decrypt"))
53}
54
55/// Decrypt and unpad a caller-owned ciphertext buffer in place.
56pub fn aes_256_cbc_decrypt_in_place(
57    buffer: &mut Vec<u8>,
58    key: &[u8],
59    iv: &[u8],
60) -> Result<(), DecryptionError> {
61    let key: &[u8; 32] = key.try_into().map_err(|_| DecryptionError::BadKeyOrIv)?;
62    let iv: &[u8; 16] = iv.try_into().map_err(|_| DecryptionError::BadKeyOrIv)?;
63    provider()
64        .aes_256_cbc_decrypt_in_place(key, iv, buffer)
65        .map_err(|_| DecryptionError::BadCiphertext("failed to decrypt"))
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_encrypt_into_appends_to_existing_buffer() {
74        let plaintext = b"test message";
75        let key = [3u8; 32];
76        let iv = [4u8; 16];
77
78        let mut buffer = vec![1, 2, 3, 4]; // Pre-existing data
79        let initial_len = buffer.len();
80
81        aes_256_cbc_encrypt_into(plaintext, &key, &iv, &mut buffer).expect("Encryption failed");
82
83        // Check that original data is preserved
84        assert_eq!(&buffer[..initial_len], &[1, 2, 3, 4]);
85
86        // Check that encrypted data was appended
87        let encrypted_part = &buffer[initial_len..];
88        let mut decrypted = Vec::new();
89        aes_256_cbc_decrypt_into(encrypted_part, &key, &iv, &mut decrypted)
90            .expect("Decryption failed");
91        assert_eq!(decrypted, plaintext);
92    }
93
94    /// Basic encrypt/decrypt roundtrip
95    #[test]
96    fn test_aes_cbc_roundtrip() {
97        let key = [0x42u8; 32];
98        let iv = [0x11u8; 16];
99        let plaintext = b"hello world, this is a roundtrip test!";
100
101        let mut ciphertext = Vec::new();
102        aes_256_cbc_encrypt_into(plaintext, &key, &iv, &mut ciphertext).unwrap();
103
104        // Ciphertext must differ from plaintext
105        assert_ne!(&ciphertext[..], &plaintext[..]);
106
107        let mut decrypted = Vec::new();
108        aes_256_cbc_decrypt_into(&ciphertext, &key, &iv, &mut decrypted).unwrap();
109        assert_eq!(decrypted, plaintext);
110    }
111
112    #[test]
113    fn test_aes_cbc_in_place_roundtrip_reuses_allocation() {
114        let key = [0x42u8; 32];
115        let iv = [0x11u8; 16];
116        let plaintext = b"caller-owned ciphertext allocation";
117
118        let mut buffer = Vec::new();
119        aes_256_cbc_encrypt_into(plaintext, &key, &iv, &mut buffer).unwrap();
120        let allocation = buffer.as_ptr();
121
122        aes_256_cbc_decrypt_in_place(&mut buffer, &key, &iv).unwrap();
123
124        assert_eq!(buffer, plaintext);
125        assert_eq!(buffer.as_ptr(), allocation);
126    }
127
128    /// NIST AES-256-CBC known-answer vector (from NIST SP 800-38A, Section F.2.5/F.2.6)
129    /// Key: 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4
130    /// IV:  000102030405060708090a0b0c0d0e0f
131    /// Plaintext block 1: 6bc1bee22e409f96e93d7e117393172a
132    /// Ciphertext block 1: f58c4c04d6e5f1ba779eabfb5f7bfbd6
133    ///
134    /// We test a single block here. Because our API uses PKCS7 padding and the NIST
135    /// vector does not include padding, we verify only the first ciphertext block.
136    #[test]
137    fn test_aes_256_cbc_nist_vector() {
138        let key: [u8; 32] = [
139            0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d,
140            0x77, 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3,
141            0x09, 0x14, 0xdf, 0xf4,
142        ];
143        let iv: [u8; 16] = [
144            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
145            0x0e, 0x0f,
146        ];
147        let plaintext: [u8; 16] = [
148            0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93,
149            0x17, 0x2a,
150        ];
151        let expected_ct_block: [u8; 16] = [
152            0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba, 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b,
153            0xfb, 0xd6,
154        ];
155
156        let mut ciphertext = Vec::new();
157        aes_256_cbc_encrypt_into(&plaintext, &key, &iv, &mut ciphertext).unwrap();
158
159        // Our output has PKCS7 padding (2 blocks), but the first block must match NIST
160        assert_eq!(ciphertext.len(), 32, "16-byte input + PKCS7 = 32 bytes");
161        assert_eq!(
162            &ciphertext[..16],
163            &expected_ct_block,
164            "First ciphertext block must match NIST vector"
165        );
166
167        // Verify full roundtrip
168        let mut decrypted = Vec::new();
169        aes_256_cbc_decrypt_into(&ciphertext, &key, &iv, &mut decrypted).unwrap();
170        assert_eq!(decrypted, plaintext);
171    }
172
173    /// Empty plaintext produces exactly one padding block (16 bytes)
174    #[test]
175    fn test_aes_cbc_empty_plaintext() {
176        let key = [0x42u8; 32];
177        let iv = [0x11u8; 16];
178
179        let mut ciphertext = Vec::new();
180        aes_256_cbc_encrypt_into(b"", &key, &iv, &mut ciphertext).unwrap();
181
182        // PKCS7 on empty input adds a full 16-byte padding block
183        assert_eq!(ciphertext.len(), 16);
184
185        let mut decrypted = Vec::new();
186        aes_256_cbc_decrypt_into(&ciphertext, &key, &iv, &mut decrypted).unwrap();
187        assert!(decrypted.is_empty());
188    }
189
190    /// Exact block-boundary plaintext (16 bytes) must add a full padding block
191    #[test]
192    fn test_aes_cbc_exact_block_boundary() {
193        let key = [0x42u8; 32];
194        let iv = [0x11u8; 16];
195        let plaintext = [0xAA; 16]; // exactly one block
196
197        let mut ciphertext = Vec::new();
198        aes_256_cbc_encrypt_into(&plaintext, &key, &iv, &mut ciphertext).unwrap();
199
200        // 16 bytes plaintext + 16 bytes PKCS7 padding = 32 bytes
201        assert_eq!(ciphertext.len(), 32);
202
203        let mut decrypted = Vec::new();
204        aes_256_cbc_decrypt_into(&ciphertext, &key, &iv, &mut decrypted).unwrap();
205        assert_eq!(decrypted, plaintext);
206    }
207
208    /// Decrypt with wrong key returns error
209    #[test]
210    fn test_aes_cbc_decrypt_wrong_key() {
211        let key = [0x42u8; 32];
212        let wrong_key = [0x43u8; 32];
213        let iv = [0x11u8; 16];
214        let plaintext = b"secret message";
215
216        let mut ciphertext = Vec::new();
217        aes_256_cbc_encrypt_into(plaintext, &key, &iv, &mut ciphertext).unwrap();
218
219        let mut decrypted = Vec::new();
220        let result = aes_256_cbc_decrypt_into(&ciphertext, &wrong_key, &iv, &mut decrypted);
221        assert!(result.is_err());
222    }
223
224    /// Decrypt with bad ciphertext length returns error
225    #[test]
226    fn test_aes_cbc_decrypt_bad_ciphertext_length() {
227        let key = [0x42u8; 32];
228        let iv = [0x11u8; 16];
229
230        // Empty ciphertext
231        let mut output = Vec::new();
232        let result = aes_256_cbc_decrypt_into(&[], &key, &iv, &mut output);
233        assert!(result.is_err());
234
235        // Non-multiple-of-16 ciphertext
236        let bad_ct = vec![0xAA; 15];
237        let result = aes_256_cbc_decrypt_into(&bad_ct, &key, &iv, &mut output);
238        assert!(result.is_err());
239
240        // Another non-multiple-of-16
241        let bad_ct = vec![0xBB; 17];
242        let result = aes_256_cbc_decrypt_into(&bad_ct, &key, &iv, &mut output);
243        assert!(result.is_err());
244    }
245}