bitwarden_crypto/hazmat/symmetric_encryption/
xaes_256_gcm.rs1use aes::cipher::common::Generate;
13use coset::{CoseEncrypt, CoseEncrypt0};
14use typenum::Unsigned;
15use xaes_256_gcm::{
16 Xaes256Gcm as Xaes256GcmAlg,
17 aead::{AeadCore, AeadInOut, KeyInit, Nonce},
18};
19
20use super::Aead;
21use crate::CryptoError;
22
23pub(crate) const KEY_SIZE: usize = 32;
24pub(crate) const NONCE_SIZE: usize = <Xaes256GcmAlg as AeadCore>::NonceSize::USIZE;
25
26pub(crate) struct XAes256Gcm;
30
31impl Aead for XAes256Gcm {
32 type Key = [u8; KEY_SIZE];
33 type Ciphertext = XAes256GcmCiphertext;
34 type Nonce = XAes256GcmNonce;
35
36 fn encrypt(
37 key: &Self::Key,
38 nonce: &Self::Nonce,
39 plaintext: &[u8],
40 associated_data: &[u8],
41 ) -> Self::Ciphertext {
42 let mut buffer = plaintext.to_vec();
43 Xaes256GcmAlg::new(key.into())
44 .encrypt_in_place(&nonce.0, associated_data, &mut buffer)
45 .expect("encryption failed");
46
47 XAes256GcmCiphertext {
48 encrypted_bytes: buffer,
49 }
50 }
51
52 fn decrypt(
53 key: &Self::Key,
54 nonce: &Self::Nonce,
55 ciphertext: &Self::Ciphertext,
56 associated_data: &[u8],
57 ) -> Result<Vec<u8>, CryptoError> {
58 let mut buffer = ciphertext.encrypted_bytes().to_vec();
59 Xaes256GcmAlg::new(key.into())
60 .decrypt_in_place(&nonce.0, associated_data, &mut buffer)
61 .map_err(|_| CryptoError::KeyDecrypt)?;
62 Ok(buffer)
63 }
64}
65
66pub(crate) struct XAes256GcmNonce(Nonce<Xaes256GcmAlg>);
68
69impl XAes256GcmNonce {
70 pub(crate) fn make() -> Self {
72 let mut rng = bitwarden_random::rng();
73 Self(Nonce::<Xaes256GcmAlg>::generate_from_rng(&mut rng))
74 }
75
76 pub(crate) fn as_bytes(&self) -> &[u8] {
78 self.0.as_slice()
79 }
80
81 fn from_cose_iv(iv: &[u8]) -> Result<Self, CryptoError> {
83 let nonce: [u8; NONCE_SIZE] = iv.try_into().map_err(|_| CryptoError::InvalidNonceLength)?;
84 Ok(Self(nonce.into()))
85 }
86}
87
88impl TryFrom<&CoseEncrypt> for XAes256GcmNonce {
90 type Error = CryptoError;
91
92 fn try_from(cose_encrypt: &CoseEncrypt) -> Result<Self, Self::Error> {
93 Self::from_cose_iv(cose_encrypt.unprotected.iv.as_slice())
94 }
95}
96
97impl TryFrom<&CoseEncrypt0> for XAes256GcmNonce {
99 type Error = CryptoError;
100
101 fn try_from(cose_encrypt0: &CoseEncrypt0) -> Result<Self, Self::Error> {
102 Self::from_cose_iv(cose_encrypt0.unprotected.iv.as_slice())
103 }
104}
105
106pub(crate) struct XAes256GcmCiphertext {
107 encrypted_bytes: Vec<u8>,
108}
109
110impl XAes256GcmCiphertext {
111 pub(crate) fn encrypted_bytes(&self) -> &[u8] {
112 &self.encrypted_bytes
113 }
114}
115
116impl From<Vec<u8>> for XAes256GcmCiphertext {
119 fn from(encrypted_bytes: Vec<u8>) -> Self {
120 Self { encrypted_bytes }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use coset::{CoseEncrypt0Builder, CoseEncryptBuilder, HeaderBuilder};
127
128 use super::*;
129
130 const NONCE: &[u8; NONCE_SIZE] = b"ABCDEFGHIJKLMNOPQRSTUVWX";
131 const PLAINTEXT: &[u8] = b"XAES-256-GCM";
132
133 fn nonce(bytes: &[u8]) -> XAes256GcmNonce {
134 XAes256GcmNonce::from_cose_iv(bytes).unwrap()
135 }
136
137 #[test]
138 fn test_c2sp_vectors() {
139 let vectors = [
141 (
142 [1u8; KEY_SIZE],
143 b"".as_slice(),
144 "ce546ef63c9cc60765923609b33a9a1974e96e52daf2fcf7075e2271",
145 ),
146 (
147 [3u8; KEY_SIZE],
148 b"c2sp.org/XAES-256-GCM".as_slice(),
149 "986ec1832593df5443a179437fd083bf3fdb41abd740a21f71eb769d",
150 ),
151 ];
152
153 for (key, associated_data, expected_ciphertext) in vectors {
154 let encrypted = XAes256Gcm::encrypt(&key, &nonce(NONCE), PLAINTEXT, associated_data);
155 assert_eq!(
156 encrypted.encrypted_bytes(),
157 hex::decode(expected_ciphertext).unwrap()
158 );
159 }
160 }
161
162 #[test]
163 fn test_make_nonce_has_correct_length() {
164 assert_eq!(XAes256GcmNonce::make().as_bytes().len(), 24);
165 }
166
167 #[test]
168 fn test_encrypt_decrypt_roundtrip() {
169 let key = [7u8; KEY_SIZE];
170 let nonce = XAes256GcmNonce::make();
171 let associated_data = b"authenticated data";
172 let encrypted = XAes256Gcm::encrypt(&key, &nonce, PLAINTEXT, associated_data);
173
174 let decrypted = XAes256Gcm::decrypt(&key, &nonce, &encrypted, associated_data).unwrap();
175 assert_eq!(decrypted, PLAINTEXT);
176 }
177
178 #[test]
179 fn test_authentication_failures() {
180 let key = [7u8; KEY_SIZE];
181 let original_nonce = nonce(NONCE);
182 let associated_data = b"authenticated data";
183 let encrypted = XAes256Gcm::encrypt(&key, &original_nonce, PLAINTEXT, associated_data);
184
185 assert!(matches!(
186 XAes256Gcm::decrypt(
187 &[8u8; KEY_SIZE],
188 &original_nonce,
189 &encrypted,
190 associated_data,
191 ),
192 Err(CryptoError::KeyDecrypt)
193 ));
194 assert!(matches!(
195 XAes256Gcm::decrypt(
196 &key,
197 &original_nonce,
198 &encrypted,
199 b"modified authenticated data",
200 ),
201 Err(CryptoError::KeyDecrypt)
202 ));
203
204 let mut modified_ciphertext = encrypted.encrypted_bytes().to_vec();
205 modified_ciphertext[0] ^= 1;
206 assert!(matches!(
207 XAes256Gcm::decrypt(
208 &key,
209 &original_nonce,
210 &XAes256GcmCiphertext::from(modified_ciphertext),
211 associated_data,
212 ),
213 Err(CryptoError::KeyDecrypt)
214 ));
215
216 let mut wrong_nonce = *NONCE;
217 wrong_nonce[0] ^= 1;
218 assert!(matches!(
219 XAes256Gcm::decrypt(&key, &nonce(&wrong_nonce), &encrypted, associated_data),
220 Err(CryptoError::KeyDecrypt)
221 ));
222 }
223
224 #[test]
225 fn test_rejects_malformed_cose_ivs() {
226 let malformed_iv = vec![0; NONCE_SIZE - 1];
227 let cose_encrypt = CoseEncryptBuilder::new()
228 .unprotected(HeaderBuilder::new().iv(malformed_iv.clone()).build())
229 .build();
230 let cose_encrypt0 = CoseEncrypt0Builder::new()
231 .unprotected(HeaderBuilder::new().iv(malformed_iv).build())
232 .build();
233
234 assert!(matches!(
235 XAes256GcmNonce::try_from(&cose_encrypt),
236 Err(CryptoError::InvalidNonceLength)
237 ));
238 assert!(matches!(
239 XAes256GcmNonce::try_from(&cose_encrypt0),
240 Err(CryptoError::InvalidNonceLength)
241 ));
242 }
243}