bitwarden_crypto/hazmat/symmetric_encryption/
xchacha20.rs1use aes::cipher::common::Generate;
21use chacha20poly1305::{
22 AeadCore, AeadInOut, KeyInit, XChaCha20Poly1305 as XChaCha20Poly1305Alg, aead::Nonce,
23};
24use coset::{CoseEncrypt, CoseEncrypt0};
25use typenum::Unsigned;
26
27use super::Aead;
28use crate::CryptoError;
29
30pub(crate) const NONCE_SIZE: usize = <XChaCha20Poly1305Alg as AeadCore>::NonceSize::USIZE;
31pub(crate) const KEY_SIZE: usize = 32;
32
33pub(crate) struct XChaCha20Poly1305;
38
39impl Aead for XChaCha20Poly1305 {
40 type Key = [u8; KEY_SIZE];
41 type Ciphertext = XChaCha20Poly1305Ciphertext;
42 type Nonce = XChaCha20Poly1305Nonce;
43
44 fn encrypt(
45 key: &Self::Key,
46 nonce: &Self::Nonce,
47 plaintext: &[u8],
48 associated_data: &[u8],
49 ) -> Self::Ciphertext {
50 let mut buffer = plaintext.to_vec();
52 XChaCha20Poly1305Alg::new(key.into())
53 .encrypt_in_place(&nonce.0, associated_data, &mut buffer)
54 .expect("encryption failed");
55
56 XChaCha20Poly1305Ciphertext {
57 encrypted_bytes: buffer,
58 }
59 }
60
61 fn decrypt(
62 key: &Self::Key,
63 nonce: &Self::Nonce,
64 ciphertext: &Self::Ciphertext,
65 associated_data: &[u8],
66 ) -> Result<Vec<u8>, CryptoError> {
67 let mut buffer = ciphertext.encrypted_bytes().to_vec();
68 XChaCha20Poly1305Alg::new(key.into())
69 .decrypt_in_place(&nonce.0, associated_data, &mut buffer)
70 .map_err(|_| CryptoError::KeyDecrypt)?;
71 Ok(buffer)
72 }
73}
74
75pub(crate) struct XChaCha20Poly1305Nonce(Nonce<XChaCha20Poly1305Alg>);
81
82impl XChaCha20Poly1305Nonce {
83 pub(crate) fn make() -> Self {
85 let mut rng = rand::rng();
86 XChaCha20Poly1305Nonce(Nonce::<XChaCha20Poly1305Alg>::generate_from_rng(&mut rng))
87 }
88
89 pub(crate) fn as_bytes(&self) -> &[u8] {
91 self.0.as_slice()
92 }
93
94 fn from_cose_iv(iv: &[u8]) -> Result<Self, CryptoError> {
96 let nonce: [u8; NONCE_SIZE] = iv.try_into().map_err(|_| CryptoError::InvalidNonceLength)?;
97 Ok(XChaCha20Poly1305Nonce(nonce.into()))
98 }
99}
100
101impl TryFrom<&CoseEncrypt> for XChaCha20Poly1305Nonce {
103 type Error = CryptoError;
104
105 fn try_from(cose_encrypt: &CoseEncrypt) -> Result<Self, Self::Error> {
106 Self::from_cose_iv(cose_encrypt.unprotected.iv.as_slice())
107 }
108}
109
110impl TryFrom<&CoseEncrypt0> for XChaCha20Poly1305Nonce {
112 type Error = CryptoError;
113
114 fn try_from(cose_encrypt0: &CoseEncrypt0) -> Result<Self, Self::Error> {
115 Self::from_cose_iv(cose_encrypt0.unprotected.iv.as_slice())
116 }
117}
118
119pub(crate) struct XChaCha20Poly1305Ciphertext {
120 encrypted_bytes: Vec<u8>,
121}
122
123impl XChaCha20Poly1305Ciphertext {
124 pub(crate) fn encrypted_bytes(&self) -> &[u8] {
125 &self.encrypted_bytes
126 }
127}
128
129impl From<Vec<u8>> for XChaCha20Poly1305Ciphertext {
132 fn from(encrypted_bytes: Vec<u8>) -> Self {
133 XChaCha20Poly1305Ciphertext { encrypted_bytes }
134 }
135}
136
137mod tests {
138 #[cfg(test)]
139 use super::*;
140
141 #[test]
142 fn test_encrypt_decrypt_xchacha20() {
143 let key = [0u8; KEY_SIZE];
144 let nonce = XChaCha20Poly1305Nonce::make();
145 let plaintext_secret_data = b"My secret data";
146 let authenticated_data = b"My authenticated data";
147 let encrypted =
148 XChaCha20Poly1305::encrypt(&key, &nonce, plaintext_secret_data, authenticated_data);
149 let decrypted =
150 XChaCha20Poly1305::decrypt(&key, &nonce, &encrypted, authenticated_data).unwrap();
151 assert_eq!(plaintext_secret_data, decrypted.as_slice());
152 }
153
154 #[test]
155 fn test_make_nonce_has_correct_length() {
156 let nonce = XChaCha20Poly1305Nonce::make();
157 assert_eq!(nonce.as_bytes().len(), NONCE_SIZE);
158 }
159
160 #[test]
161 fn test_fails_when_ciphertext_changed() {
162 let key = [0u8; KEY_SIZE];
163 let nonce = XChaCha20Poly1305Nonce::make();
164 let plaintext_secret_data = b"My secret data";
165 let authenticated_data = b"My authenticated data";
166
167 let mut encrypted =
168 XChaCha20Poly1305::encrypt(&key, &nonce, plaintext_secret_data, authenticated_data);
169 encrypted.encrypted_bytes[0] = encrypted.encrypted_bytes[0].wrapping_add(1);
170 let result = XChaCha20Poly1305::decrypt(&key, &nonce, &encrypted, authenticated_data);
171 assert!(result.is_err());
172 }
173
174 #[test]
175 fn test_fails_when_associated_data_changed() {
176 let key = [0u8; KEY_SIZE];
177 let nonce = XChaCha20Poly1305Nonce::make();
178 let plaintext_secret_data = b"My secret data";
179 let mut authenticated_data = b"My authenticated data".to_vec();
180
181 let encrypted = XChaCha20Poly1305::encrypt(
182 &key,
183 &nonce,
184 plaintext_secret_data,
185 authenticated_data.as_slice(),
186 );
187 authenticated_data[0] = authenticated_data[0].wrapping_add(1);
188 let result =
189 XChaCha20Poly1305::decrypt(&key, &nonce, &encrypted, authenticated_data.as_slice());
190 assert!(result.is_err());
191 }
192
193 #[test]
194 fn test_fails_when_nonce_changed() {
195 let key = [0u8; KEY_SIZE];
196 let nonce = XChaCha20Poly1305Nonce::make();
197 let plaintext_secret_data = b"My secret data";
198 let authenticated_data = b"My authenticated data";
199
200 let encrypted =
201 XChaCha20Poly1305::encrypt(&key, &nonce, plaintext_secret_data, authenticated_data);
202 let other_nonce = XChaCha20Poly1305Nonce::make();
204 let result = XChaCha20Poly1305::decrypt(&key, &other_nonce, &encrypted, authenticated_data);
205 assert!(result.is_err());
206 }
207}