1use std::{cmp::max, pin::Pin};
2
3use base64::{engine::general_purpose::STANDARD, Engine};
4use coset::{iana::KeyOperation, CborSerializable, RegisteredLabelWithPrivate};
5use generic_array::GenericArray;
6use rand::Rng;
7#[cfg(test)]
8use rand::SeedableRng;
9#[cfg(test)]
10use rand_chacha::ChaChaRng;
11#[cfg(test)]
12use sha2::Digest;
13use subtle::{Choice, ConstantTimeEq};
14use typenum::U32;
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17use super::{
18 key_encryptable::CryptoKey,
19 key_id::{KeyId, KEY_ID_SIZE},
20};
21use crate::{cose, CryptoError};
22
23#[derive(ZeroizeOnDrop, Clone)]
27pub struct Aes256CbcKey {
28 pub(crate) enc_key: Pin<Box<GenericArray<u8, U32>>>,
30}
31
32impl ConstantTimeEq for Aes256CbcKey {
33 fn ct_eq(&self, other: &Self) -> Choice {
34 self.enc_key.ct_eq(&other.enc_key)
35 }
36}
37
38impl PartialEq for Aes256CbcKey {
39 fn eq(&self, other: &Self) -> bool {
40 self.ct_eq(other).into()
41 }
42}
43
44#[derive(ZeroizeOnDrop, Clone)]
47pub struct Aes256CbcHmacKey {
48 pub(crate) enc_key: Pin<Box<GenericArray<u8, U32>>>,
50 pub(crate) mac_key: Pin<Box<GenericArray<u8, U32>>>,
52}
53
54impl ConstantTimeEq for Aes256CbcHmacKey {
55 fn ct_eq(&self, other: &Self) -> Choice {
56 self.enc_key.ct_eq(&other.enc_key) & self.mac_key.ct_eq(&other.mac_key)
57 }
58}
59
60impl PartialEq for Aes256CbcHmacKey {
61 fn eq(&self, other: &Self) -> bool {
62 self.ct_eq(other).into()
63 }
64}
65
66#[derive(Zeroize, Clone)]
71pub struct XChaCha20Poly1305Key {
72 pub(crate) key_id: [u8; KEY_ID_SIZE],
73 pub(crate) enc_key: Pin<Box<GenericArray<u8, U32>>>,
74}
75
76impl ConstantTimeEq for XChaCha20Poly1305Key {
77 fn ct_eq(&self, other: &Self) -> Choice {
78 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
79 }
80}
81
82impl PartialEq for XChaCha20Poly1305Key {
83 fn eq(&self, other: &Self) -> bool {
84 self.ct_eq(other).into()
85 }
86}
87
88#[derive(ZeroizeOnDrop, Clone)]
90pub enum SymmetricCryptoKey {
91 #[allow(missing_docs)]
92 Aes256CbcKey(Aes256CbcKey),
93 #[allow(missing_docs)]
94 Aes256CbcHmacKey(Aes256CbcHmacKey),
95 XChaCha20Poly1305Key(XChaCha20Poly1305Key),
98}
99
100impl SymmetricCryptoKey {
101 const AES256_CBC_KEY_LEN: usize = 32;
103 const AES256_CBC_HMAC_KEY_LEN: usize = 64;
105
106 pub(crate) fn make_aes256_cbc_hmac_key_internal(
112 mut rng: impl rand::RngCore + rand::CryptoRng,
113 ) -> Self {
114 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
115 let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
116
117 rng.fill(enc_key.as_mut_slice());
118 rng.fill(mac_key.as_mut_slice());
119
120 Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
121 }
122
123 pub fn make_aes256_cbc_hmac_key() -> Self {
125 let rng = rand::thread_rng();
126 Self::make_aes256_cbc_hmac_key_internal(rng)
127 }
128
129 pub fn make_xchacha20_poly1305_key() -> Self {
131 let mut rng = rand::thread_rng();
132 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
133 rng.fill(enc_key.as_mut_slice());
134 Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
135 enc_key,
136 key_id: KeyId::make().into(),
137 })
138 }
139
140 pub fn to_encoded(&self) -> Vec<u8> {
149 let mut encoded_key = self.to_encoded_raw();
150 match self {
151 Self::Aes256CbcKey(_) | Self::Aes256CbcHmacKey(_) => encoded_key,
152 Self::XChaCha20Poly1305Key(_) => {
153 pad_key(&mut encoded_key, Self::AES256_CBC_HMAC_KEY_LEN + 1);
154 encoded_key
155 }
156 }
157 }
158
159 #[cfg(test)]
162 pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
163 let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into());
165 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
166 let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
167
168 seeded_rng.fill(enc_key.as_mut_slice());
169 seeded_rng.fill(mac_key.as_mut_slice());
170
171 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
172 }
173
174 pub(crate) fn to_encoded_raw(&self) -> Vec<u8> {
186 match self {
187 Self::Aes256CbcKey(key) => key.enc_key.to_vec(),
188 Self::Aes256CbcHmacKey(key) => {
189 let mut buf = Vec::with_capacity(64);
190 buf.extend_from_slice(&key.enc_key);
191 buf.extend_from_slice(&key.mac_key);
192 buf
193 }
194 Self::XChaCha20Poly1305Key(key) => {
195 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
196 let mut cose_key = builder
197 .key_id(key.key_id.to_vec())
198 .add_key_op(KeyOperation::Decrypt)
199 .add_key_op(KeyOperation::Encrypt)
200 .add_key_op(KeyOperation::WrapKey)
201 .add_key_op(KeyOperation::UnwrapKey)
202 .build();
203 cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(
204 cose::XCHACHA20_POLY1305,
205 ));
206 cose_key
207 .to_vec()
208 .expect("cose key serialization should not fail")
209 }
210 }
211 }
212
213 #[allow(missing_docs)]
214 pub fn to_base64(&self) -> String {
215 STANDARD.encode(self.to_encoded())
216 }
217}
218
219impl ConstantTimeEq for SymmetricCryptoKey {
220 fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
224 use SymmetricCryptoKey::*;
225 match (self, other) {
226 (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
227 (Aes256CbcKey(_), _) => Choice::from(0),
228
229 (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
230 (Aes256CbcHmacKey(_), _) => Choice::from(0),
231
232 (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
233 (XChaCha20Poly1305Key(_), _) => Choice::from(0),
234 }
235 }
236}
237
238impl PartialEq for SymmetricCryptoKey {
239 fn eq(&self, other: &Self) -> bool {
240 self.ct_eq(other).into()
241 }
242}
243
244impl TryFrom<String> for SymmetricCryptoKey {
245 type Error = CryptoError;
246
247 fn try_from(value: String) -> Result<Self, Self::Error> {
248 let b = STANDARD
249 .decode(value)
250 .map_err(|_| CryptoError::InvalidKey)?;
251 Self::try_from(b)
252 }
253}
254
255impl TryFrom<Vec<u8>> for SymmetricCryptoKey {
256 type Error = CryptoError;
257
258 fn try_from(mut value: Vec<u8>) -> Result<Self, Self::Error> {
259 Self::try_from(value.as_mut_slice())
260 }
261}
262
263impl TryFrom<&mut [u8]> for SymmetricCryptoKey {
264 type Error = CryptoError;
265
266 fn try_from(value: &mut [u8]) -> Result<Self, Self::Error> {
269 let result = if value.len() == Self::AES256_CBC_HMAC_KEY_LEN {
274 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
275 let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
276
277 enc_key.copy_from_slice(&value[..32]);
278 mac_key.copy_from_slice(&value[32..]);
279
280 Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
281 enc_key,
282 mac_key,
283 }))
284 } else if value.len() == Self::AES256_CBC_KEY_LEN {
285 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
286
287 enc_key.copy_from_slice(&value[..Self::AES256_CBC_KEY_LEN]);
288
289 Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
290 } else if value.len() > Self::AES256_CBC_HMAC_KEY_LEN {
291 let unpadded_value = unpad_key(value)?;
292 let cose_key =
293 coset::CoseKey::from_slice(unpadded_value).map_err(|_| CryptoError::InvalidKey)?;
294 SymmetricCryptoKey::try_from(&cose_key)
295 } else {
296 Err(CryptoError::InvalidKeyLen)
297 };
298
299 value.zeroize();
300 result
301 }
302}
303
304impl CryptoKey for SymmetricCryptoKey {}
305
306impl std::fmt::Debug for SymmetricCryptoKey {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 f.debug_struct("SymmetricCryptoKey")
310 .field(
311 "inner_type",
312 match self {
313 SymmetricCryptoKey::Aes256CbcKey(key) => key,
314 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
315 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key,
316 },
317 )
318 .finish()
319 }
320}
321
322impl std::fmt::Debug for Aes256CbcKey {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 f.debug_struct("Aes256CbcKey").finish()
325 }
326}
327
328impl std::fmt::Debug for Aes256CbcHmacKey {
329 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330 f.debug_struct("Aes256CbcHmacKey").finish()
331 }
332}
333
334impl std::fmt::Debug for XChaCha20Poly1305Key {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 f.debug_struct("XChaCha20Poly1305Key")
337 .field("key_id", &self.key_id)
338 .finish()
339 }
340}
341
342fn pad_key(key_bytes: &mut Vec<u8>, min_length: usize) {
353 let pad_bytes = min_length.saturating_sub(key_bytes.len()).max(1);
355 let padded_length = max(min_length, key_bytes.len() + 1);
356 key_bytes.resize(padded_length, pad_bytes as u8);
357}
358
359fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
370 let pad_len = *key_bytes.last().ok_or(CryptoError::InvalidKey)? as usize;
371 if pad_len >= key_bytes.len() {
372 return Err(CryptoError::InvalidKey);
373 }
374 Ok(key_bytes[..key_bytes.len() - pad_len].as_ref())
375}
376
377#[cfg(test)]
379pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
380 use zeroize::Zeroizing;
381
382 use crate::{derive_shareable_key, generate_random_bytes};
383
384 let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
385 derive_shareable_key(secret, name, None)
386}
387
388#[cfg(test)]
389mod tests {
390 use base64::{engine::general_purpose::STANDARD, Engine};
391 use generic_array::GenericArray;
392 use typenum::U32;
393
394 use super::{derive_symmetric_key, SymmetricCryptoKey};
395 use crate::{
396 keys::symmetric_crypto_key::{pad_key, unpad_key},
397 Aes256CbcHmacKey, Aes256CbcKey, XChaCha20Poly1305Key,
398 };
399
400 #[test]
401 fn test_symmetric_crypto_key() {
402 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
403 let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
404
405 assert_eq!(key, key2);
406
407 let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
408 let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
409 assert_eq!(key, key2.to_base64());
410 }
411
412 #[test]
413 fn test_encode_decode_old_symmetric_crypto_key() {
414 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
415 let encoded = key.to_encoded();
416 let decoded = SymmetricCryptoKey::try_from(encoded).unwrap();
417 assert_eq!(key, decoded);
418 }
419
420 #[test]
421 fn test_decode_new_symmetric_crypto_key() {
422 let key_b64 = STANDARD.decode("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").unwrap();
423 let key = SymmetricCryptoKey::try_from(key_b64).unwrap();
424 match key {
425 SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
426 _ => panic!("Invalid key type"),
427 }
428 }
429
430 #[test]
431 fn test_encode_xchacha20_poly1305_key() {
432 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
433 let encoded = key.to_encoded();
434 let decoded = SymmetricCryptoKey::try_from(encoded).unwrap();
435 assert_eq!(key, decoded);
436 }
437
438 #[test]
439 fn test_pad_unpad_key_63() {
440 let original_key = vec![1u8; 63];
441 let mut key_bytes = original_key.clone();
442 let mut encoded_bytes = vec![1u8; 65];
443 encoded_bytes[63] = 2;
444 encoded_bytes[64] = 2;
445 pad_key(&mut key_bytes, 65);
446 assert_eq!(encoded_bytes, key_bytes);
447 let unpadded_key = unpad_key(&key_bytes).unwrap();
448 assert_eq!(original_key, unpadded_key);
449 }
450
451 #[test]
452 fn test_pad_unpad_key_64() {
453 let original_key = vec![1u8; 64];
454 let mut key_bytes = original_key.clone();
455 let mut encoded_bytes = vec![1u8; 65];
456 encoded_bytes[64] = 1;
457 pad_key(&mut key_bytes, 65);
458 assert_eq!(encoded_bytes, key_bytes);
459 let unpadded_key = unpad_key(&key_bytes).unwrap();
460 assert_eq!(original_key, unpadded_key);
461 }
462
463 #[test]
464 fn test_pad_unpad_key_65() {
465 let original_key = vec![1u8; 65];
466 let mut key_bytes = original_key.clone();
467 let mut encoded_bytes = vec![1u8; 66];
468 encoded_bytes[65] = 1;
469 pad_key(&mut key_bytes, 65);
470 assert_eq!(encoded_bytes, key_bytes);
471 let unpadded_key = unpad_key(&key_bytes).unwrap();
472 assert_eq!(original_key, unpadded_key);
473 }
474
475 #[test]
476 fn test_eq_aes_cbc_hmac() {
477 let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
478 let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
479 assert_ne!(key1, key2);
480 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
481 assert_eq!(key1, key3);
482 }
483
484 #[test]
485 fn test_eq_aes_cbc() {
486 let key1 = SymmetricCryptoKey::try_from(vec![1u8; 32]).unwrap();
487 let key2 = SymmetricCryptoKey::try_from(vec![2u8; 32]).unwrap();
488 assert_ne!(key1, key2);
489 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
490 assert_eq!(key1, key3);
491 }
492
493 #[test]
494 fn test_eq_xchacha20_poly1305() {
495 let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
496 let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
497 assert_ne!(key1, key2);
498 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
499 assert_eq!(key1, key3);
500 }
501
502 #[test]
503 fn test_neq_different_key_types() {
504 let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
505 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
506 });
507 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
508 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
509 key_id: [0; 16],
510 });
511 assert_ne!(key1, key2);
512 }
513
514 #[test]
515 fn test_eq_variant_aes256_cbc() {
516 let key1 = Aes256CbcKey {
517 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
518 vec![1u8; 32].as_slice(),
519 )),
520 };
521 let key2 = Aes256CbcKey {
522 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
523 vec![1u8; 32].as_slice(),
524 )),
525 };
526 let key3 = Aes256CbcKey {
527 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
528 vec![2u8; 32].as_slice(),
529 )),
530 };
531 assert_eq!(key1, key2);
532 assert_ne!(key1, key3);
533 }
534
535 #[test]
536 fn test_eq_variant_aes256_cbc_hmac() {
537 let key1 = Aes256CbcHmacKey {
538 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
539 vec![1u8; 32].as_slice(),
540 )),
541 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
542 vec![2u8; 32].as_slice(),
543 )),
544 };
545 let key2 = Aes256CbcHmacKey {
546 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
547 vec![1u8; 32].as_slice(),
548 )),
549 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
550 vec![2u8; 32].as_slice(),
551 )),
552 };
553 let key3 = Aes256CbcHmacKey {
554 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
555 vec![3u8; 32].as_slice(),
556 )),
557 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
558 vec![4u8; 32].as_slice(),
559 )),
560 };
561 assert_eq!(key1, key2);
562 assert_ne!(key1, key3);
563 }
564
565 #[test]
566 fn test_eq_variant_xchacha20_poly1305() {
567 let key1 = XChaCha20Poly1305Key {
568 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
569 vec![1u8; 32].as_slice(),
570 )),
571 key_id: [0; 16],
572 };
573 let key2 = XChaCha20Poly1305Key {
574 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
575 vec![1u8; 32].as_slice(),
576 )),
577 key_id: [0; 16],
578 };
579 let key3 = XChaCha20Poly1305Key {
580 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
581 vec![2u8; 32].as_slice(),
582 )),
583 key_id: [1; 16],
584 };
585 assert_eq!(key1, key2);
586 assert_ne!(key1, key3);
587 }
588
589 #[test]
590 fn test_neq_different_key_id() {
591 let key1 = XChaCha20Poly1305Key {
592 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
593 key_id: [0; 16],
594 };
595 let key2 = XChaCha20Poly1305Key {
596 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
597 key_id: [1; 16],
598 };
599 assert_ne!(key1, key2);
600
601 let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
602 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
603 assert_ne!(key1, key2);
604 }
605}