1use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::Pkcs7};
7use hmac::{KeyInit, Mac};
8use rand::RngExt;
9use subtle::ConstantTimeEq;
10
11use super::Aead;
12use crate::CryptoError;
13
14type HmacSha256 = hmac::Hmac<sha2::Sha256>;
15
16pub(crate) const NONCE_SIZE: usize = 16;
17pub(crate) const ENC_KEY_SIZE: usize = 32;
18pub(crate) const MAC_KEY_SIZE: usize = 32;
19pub(crate) const KEY_SIZE: usize = ENC_KEY_SIZE + MAC_KEY_SIZE;
20pub(crate) const MAC_SIZE: usize = 32;
21
22const MAC_DOMAIN_SEPARATOR: &[u8] = b"bitwarden.aes256-cbc-hmac-sha256-aead.v1";
27
28pub(crate) struct Aes256CbcHmacSha256Aead;
30
31impl Aead for Aes256CbcHmacSha256Aead {
32 type Key = [u8; KEY_SIZE];
33 type Ciphertext = Aes256CbcHmacSha256AeadCiphertext;
34 type Nonce = Aes256CbcHmacSha256AeadNonce;
35
36 fn encrypt(
37 key: &Self::Key,
38 nonce: &Self::Nonce,
39 plaintext: &[u8],
40 associated_data: &[u8],
41 ) -> Self::Ciphertext {
42 let (enc_key, mac_key) = split_to_subkeys(key);
43
44 let mut encrypted_bytes =
45 cbc::Encryptor::<aes::Aes256>::new(enc_key.into(), nonce.as_array().into())
46 .encrypt_padded_vec::<Pkcs7>(plaintext);
47 let mac =
48 calculate_mac_with_aad(nonce.as_array(), &encrypted_bytes, associated_data, mac_key);
49 encrypted_bytes.extend_from_slice(&mac);
50
51 Aes256CbcHmacSha256AeadCiphertext { encrypted_bytes }
52 }
53
54 fn decrypt(
55 key: &Self::Key,
56 nonce: &Self::Nonce,
57 ciphertext: &Self::Ciphertext,
58 associated_data: &[u8],
59 ) -> Result<Vec<u8>, CryptoError> {
60 let (enc_key, mac_key) = split_to_subkeys(key);
61
62 let encrypted_bytes = ciphertext.encrypted_bytes();
63 if encrypted_bytes.len() < MAC_SIZE {
64 return Err(CryptoError::KeyDecrypt);
65 }
66 let (data, received_mac) = encrypted_bytes.split_at(encrypted_bytes.len() - MAC_SIZE);
67
68 let expected_mac = calculate_mac_with_aad(nonce.as_array(), data, associated_data, mac_key);
69 if expected_mac.ct_ne(received_mac).into() {
70 return Err(CryptoError::KeyDecrypt);
71 }
72
73 let mut buf = data.to_vec();
74 cbc::Decryptor::<aes::Aes256>::new(enc_key.into(), nonce.as_array().into())
75 .decrypt_padded::<Pkcs7>(&mut buf)
76 .map(|slice| slice.to_vec())
77 .map_err(|_| CryptoError::KeyDecrypt)
78 }
79}
80
81fn split_to_subkeys(key: &[u8; KEY_SIZE]) -> (&[u8; ENC_KEY_SIZE], &[u8; MAC_KEY_SIZE]) {
83 let (enc_key, mac_key) = key.split_at(ENC_KEY_SIZE);
84 let enc_key = enc_key
85 .try_into()
86 .expect("first half of a 64-byte key is always 32 bytes");
87 let mac_key = mac_key
88 .try_into()
89 .expect("second half of a 64-byte key is always 32 bytes");
90 (enc_key, mac_key)
91}
92
93fn calculate_mac_with_aad(
96 iv: &[u8; NONCE_SIZE],
97 data: &[u8],
98 associated_data: &[u8],
99 mac_key: &[u8; MAC_KEY_SIZE],
100) -> [u8; MAC_SIZE] {
101 let mut hmac =
102 HmacSha256::new_from_slice(mac_key).expect("hmac new_from_slice should not fail");
103 hmac.update(MAC_DOMAIN_SEPARATOR);
104 hmac.update(iv);
105 hmac.update(&(data.len() as u64).to_be_bytes());
106 hmac.update(data);
107 hmac.update(&(associated_data.len() as u64).to_be_bytes());
108 hmac.update(associated_data);
109 (*hmac.finalize().into_bytes())
110 .try_into()
111 .expect("HMAC output size to be correct")
113}
114
115pub(crate) struct Aes256CbcHmacSha256AeadNonce([u8; NONCE_SIZE]);
120
121impl Aes256CbcHmacSha256AeadNonce {
122 pub(crate) fn make() -> Self {
124 let mut rng = bitwarden_random::rng();
125 let mut iv = [0u8; NONCE_SIZE];
126 rng.fill(&mut iv);
127 Aes256CbcHmacSha256AeadNonce(iv)
128 }
129
130 pub(crate) fn as_bytes(&self) -> &[u8] {
132 &self.0
133 }
134
135 fn as_array(&self) -> &[u8; NONCE_SIZE] {
137 &self.0
138 }
139
140 fn from_cose_iv(iv: &[u8]) -> Result<Self, CryptoError> {
142 let iv: [u8; NONCE_SIZE] = iv.try_into().map_err(|_| CryptoError::InvalidNonceLength)?;
143 Ok(Aes256CbcHmacSha256AeadNonce(iv))
144 }
145}
146
147impl TryFrom<&coset::CoseEncrypt> for Aes256CbcHmacSha256AeadNonce {
149 type Error = CryptoError;
150
151 fn try_from(cose_encrypt: &coset::CoseEncrypt) -> Result<Self, Self::Error> {
152 Self::from_cose_iv(cose_encrypt.unprotected.iv.as_slice())
153 }
154}
155
156impl TryFrom<&coset::CoseEncrypt0> for Aes256CbcHmacSha256AeadNonce {
158 type Error = CryptoError;
159
160 fn try_from(cose_encrypt0: &coset::CoseEncrypt0) -> Result<Self, Self::Error> {
161 Self::from_cose_iv(cose_encrypt0.unprotected.iv.as_slice())
162 }
163}
164
165pub(crate) struct Aes256CbcHmacSha256AeadCiphertext {
166 encrypted_bytes: Vec<u8>,
167}
168
169impl Aes256CbcHmacSha256AeadCiphertext {
170 pub(crate) fn encrypted_bytes(&self) -> &[u8] {
171 &self.encrypted_bytes
172 }
173}
174
175impl From<Vec<u8>> for Aes256CbcHmacSha256AeadCiphertext {
178 fn from(encrypted_bytes: Vec<u8>) -> Self {
179 Aes256CbcHmacSha256AeadCiphertext { encrypted_bytes }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 const TESTVECTOR_PLAINTEXT: &[u8] = b"Bitwarden SDK test vector";
188 const TESTVECTOR_ASSOCIATED_DATA: &[u8] = b"Bitwarden SDK test vector associated data";
189 const TESTVECTOR_ENCRYPTED: &[u8] = &[
190 111, 16, 33, 143, 207, 253, 106, 89, 58, 52, 145, 240, 140, 213, 149, 83, 168, 188, 122,
191 57, 130, 28, 35, 182, 114, 227, 215, 250, 175, 182, 169, 102, 33, 159, 62, 224, 104, 214,
192 248, 153, 248, 63, 219, 206, 228, 17, 93, 110, 14, 150, 81, 139, 74, 235, 115, 206, 113,
193 115, 220, 197, 18, 206, 252, 198,
194 ];
195
196 const TESTVECTOR_KEY: [u8; KEY_SIZE] = [
197 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
198 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
199 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
200 ];
201 const TESTVECTOR_NONCE: Aes256CbcHmacSha256AeadNonce =
202 Aes256CbcHmacSha256AeadNonce([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
203
204 #[test]
205 #[ignore = "Generates test vectors; run manually"]
206 fn generate_test_vectors() {
207 let encrypted = Aes256CbcHmacSha256Aead::encrypt(
208 &TESTVECTOR_KEY,
209 &TESTVECTOR_NONCE,
210 TESTVECTOR_PLAINTEXT,
211 TESTVECTOR_ASSOCIATED_DATA,
212 );
213 let decrypted = Aes256CbcHmacSha256Aead::decrypt(
214 &TESTVECTOR_KEY,
215 &TESTVECTOR_NONCE,
216 &encrypted,
217 TESTVECTOR_ASSOCIATED_DATA,
218 )
219 .unwrap();
220 assert_eq!(decrypted, TESTVECTOR_PLAINTEXT);
221
222 println!(
223 "const TESTVECTOR_ENCRYPTED: &[u8] = &{:?};",
224 encrypted.encrypted_bytes()
225 );
226 }
227
228 #[test]
232 fn test_decrypt_testvector() {
233 let encrypted = Aes256CbcHmacSha256AeadCiphertext::from(TESTVECTOR_ENCRYPTED.to_vec());
234
235 let decrypted = Aes256CbcHmacSha256Aead::decrypt(
236 &TESTVECTOR_KEY,
237 &TESTVECTOR_NONCE,
238 &encrypted,
239 TESTVECTOR_ASSOCIATED_DATA,
240 )
241 .unwrap();
242
243 assert_eq!(decrypted, TESTVECTOR_PLAINTEXT);
244 }
245
246 #[test]
247 fn test_encrypt_decrypt_aes256_cbc_hmac() {
248 let key = TESTVECTOR_KEY;
249 let nonce = Aes256CbcHmacSha256AeadNonce::make();
250 let plaintext_secret_data = b"My secret data";
251 let authenticated_data = b"My authenticated data";
252 let encrypted = Aes256CbcHmacSha256Aead::encrypt(
253 &key,
254 &nonce,
255 plaintext_secret_data,
256 authenticated_data,
257 );
258 let decrypted =
259 Aes256CbcHmacSha256Aead::decrypt(&key, &nonce, &encrypted, authenticated_data).unwrap();
260 assert_eq!(plaintext_secret_data, decrypted.as_slice());
261 }
262
263 #[test]
264 fn test_make_nonce_has_correct_length() {
265 let nonce = Aes256CbcHmacSha256AeadNonce::make();
266 assert_eq!(nonce.as_bytes().len(), NONCE_SIZE);
267 }
268
269 #[test]
270 fn test_fails_when_ciphertext_changed() {
271 let key = TESTVECTOR_KEY;
272 let nonce = Aes256CbcHmacSha256AeadNonce::make();
273 let plaintext_secret_data = b"My secret data";
274 let authenticated_data = b"My authenticated data";
275
276 let mut encrypted = Aes256CbcHmacSha256Aead::encrypt(
277 &key,
278 &nonce,
279 plaintext_secret_data,
280 authenticated_data,
281 );
282 encrypted.encrypted_bytes[0] = encrypted.encrypted_bytes[0].wrapping_add(1);
283 let result = Aes256CbcHmacSha256Aead::decrypt(&key, &nonce, &encrypted, authenticated_data);
284 assert!(result.is_err());
285 }
286
287 #[test]
288 fn test_fails_when_associated_data_changed() {
289 let key = TESTVECTOR_KEY;
290 let nonce = Aes256CbcHmacSha256AeadNonce::make();
291 let plaintext_secret_data = b"My secret data";
292 let mut authenticated_data = b"My authenticated data".to_vec();
293
294 let encrypted = Aes256CbcHmacSha256Aead::encrypt(
295 &key,
296 &nonce,
297 plaintext_secret_data,
298 authenticated_data.as_slice(),
299 );
300 authenticated_data[0] = authenticated_data[0].wrapping_add(1);
301 let result = Aes256CbcHmacSha256Aead::decrypt(
302 &key,
303 &nonce,
304 &encrypted,
305 authenticated_data.as_slice(),
306 );
307 assert!(result.is_err());
308 }
309
310 #[test]
311 fn test_fails_when_nonce_changed() {
312 let key = TESTVECTOR_KEY;
313 let nonce = Aes256CbcHmacSha256AeadNonce::make();
314 let plaintext_secret_data = b"My secret data";
315 let authenticated_data = b"My authenticated data";
316
317 let encrypted = Aes256CbcHmacSha256Aead::encrypt(
318 &key,
319 &nonce,
320 plaintext_secret_data,
321 authenticated_data,
322 );
323 let other_nonce = Aes256CbcHmacSha256AeadNonce::make();
325 let result =
326 Aes256CbcHmacSha256Aead::decrypt(&key, &other_nonce, &encrypted, authenticated_data);
327 assert!(result.is_err());
328 }
329
330 #[test]
331 fn test_fails_when_ciphertext_too_short() {
332 let key = TESTVECTOR_KEY;
333 let nonce = Aes256CbcHmacSha256AeadNonce::make();
334 let truncated = Aes256CbcHmacSha256AeadCiphertext {
335 encrypted_bytes: vec![0u8; MAC_SIZE - 1],
336 };
337 let result = Aes256CbcHmacSha256Aead::decrypt(&key, &nonce, &truncated, b"");
338 assert!(result.is_err());
339 }
340
341 #[test]
342 fn test_length_prefixing_prevents_data_ad_boundary_confusion() {
343 let mac_key = [0u8; MAC_KEY_SIZE];
346 let iv = [0u8; NONCE_SIZE];
347 let mac1 = calculate_mac_with_aad(&iv, b"ab", b"cd", &mac_key);
348 let mac2 = calculate_mac_with_aad(&iv, b"a", b"bcd", &mac_key);
349 assert_ne!(mac1, mac2);
350 }
351}