1mod error;
4#[cfg(feature = "metrics")]
5mod metrics_decryptor;
6mod multi;
7mod refreshable;
8mod retrying;
9mod scheduled;
10
11use std::{borrow::Cow, sync::Arc};
12
13use bon::Builder;
14pub use error::{DecryptError, UnsealError};
15#[cfg(feature = "metrics")]
16pub use metrics_decryptor::MetricsAeadDecryptor;
17pub use multi::{MultiKeyCipher, MultiKeyDecryptor};
18pub use refreshable::RefreshableCipher;
19pub use retrying::RetryingDecryptor;
20pub use scheduled::ScheduledRefreshCipher;
21
22use crate::{
23 crypto::KeyMatchStrength,
24 error::{Error, ErrorKind},
25 platform::{MaybeSendBoxFuture, MaybeSendSync},
26};
27
28pub struct AeadOutput {
30 pub nonce: Vec<u8>,
32 pub ciphertext: Vec<u8>,
34 pub tag: Vec<u8>,
36}
37
38#[non_exhaustive]
44#[derive(Debug, Clone, Copy, Builder)]
45pub struct CipherMatch<'a> {
46 pub enc: Option<&'a str>,
49 pub kid: Option<&'a str>,
51}
52
53impl CipherMatch<'_> {
54 #[must_use]
65 pub fn strength_for(
66 &self,
67 enc_algorithm: &str,
68 registered_kid: Option<&str>,
69 ) -> Option<KeyMatchStrength> {
70 if let Some(enc) = self.enc
71 && enc != enc_algorithm
72 {
73 return None;
74 }
75
76 crate::crypto::kid_match_strength(self.kid, registered_kid)
77 }
78}
79
80pub trait AeadEncryptor: std::fmt::Debug + MaybeSendSync {
86 fn enc_algorithm(&self) -> Cow<'_, str>;
88
89 fn key_id(&self) -> Option<Cow<'_, str>>;
91
92 fn encrypt<'a>(
103 &'a self,
104 plaintext: &'a [u8],
105 aad: &'a [u8],
106 ) -> MaybeSendBoxFuture<'a, Result<AeadOutput, Error>>;
107}
108
109pub trait AeadDecryptor: std::fmt::Debug + MaybeSendSync {
116 fn cipher_match(&self, m: &CipherMatch<'_>) -> Option<KeyMatchStrength>;
131
132 fn decrypt<'a>(
147 &'a self,
148 cipher_match: Option<&'a CipherMatch<'a>>,
149 nonce: &'a [u8],
150 ciphertext: &'a [u8],
151 tag: &'a [u8],
152 aad: &'a [u8],
153 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>>;
154
155 fn try_refresh(&self) -> MaybeSendBoxFuture<'_, bool> {
169 Box::pin(async { false })
170 }
171}
172
173pub trait AeadCipher: AeadEncryptor + AeadDecryptor {}
179
180impl<T: AeadEncryptor + AeadDecryptor + ?Sized> AeadCipher for T {}
181
182pub trait AeadCipherSelector: std::fmt::Debug + MaybeSendSync {
194 fn select_cipher(&self) -> MaybeSendBoxFuture<'_, Arc<dyn AeadEncryptor>>;
197}
198
199pub trait AeadSealer: AeadEncryptor {
207 fn seal<'a>(
210 &'a self,
211 plaintext: &'a [u8],
212 aad: &'a [u8],
213 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, Error>>;
214}
215
216pub trait AeadUnsealer: AeadDecryptor {
218 fn unseal<'a>(
230 &'a self,
231 cipher_match: Option<&'a CipherMatch<'a>>,
232 bundle: &'a [u8],
233 aad: &'a [u8],
234 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>>;
235}
236
237pub trait SealedAeadCipher: AeadSealer + AeadUnsealer {}
244
245impl<T: AeadSealer + AeadUnsealer + ?Sized> SealedAeadCipher for T {}
246
247pub trait AeadSealerSelector: std::fmt::Debug + MaybeSendSync {
265 fn select_sealer(&self) -> MaybeSendBoxFuture<'_, Arc<dyn AeadSealer>>;
268}
269
270pub trait SealedAeadCipherSelector: AeadSealerSelector + AeadUnsealer {}
284
285impl<T: AeadSealerSelector + AeadUnsealer + ?Sized> SealedAeadCipherSelector for T {}
286
287macro_rules! forward_aead_encryptor {
288 ($wrapper:ty) => {
289 impl<T: AeadEncryptor + ?Sized> AeadEncryptor for $wrapper {
290 fn enc_algorithm(&self) -> Cow<'_, str> {
291 (**self).enc_algorithm()
292 }
293
294 fn key_id(&self) -> Option<Cow<'_, str>> {
295 (**self).key_id()
296 }
297
298 fn encrypt<'a>(
299 &'a self,
300 plaintext: &'a [u8],
301 aad: &'a [u8],
302 ) -> MaybeSendBoxFuture<'a, Result<AeadOutput, Error>> {
303 (**self).encrypt(plaintext, aad)
304 }
305 }
306 };
307}
308
309forward_aead_encryptor!(&T);
310forward_aead_encryptor!(Box<T>);
311forward_aead_encryptor!(Arc<T>);
312
313macro_rules! forward_aead_decryptor {
314 ($wrapper:ty) => {
315 impl<T: AeadDecryptor + ?Sized> AeadDecryptor for $wrapper {
316 fn cipher_match(&self, m: &CipherMatch<'_>) -> Option<KeyMatchStrength> {
317 (**self).cipher_match(m)
318 }
319
320 fn decrypt<'a>(
321 &'a self,
322 cipher_match: Option<&'a CipherMatch<'a>>,
323 nonce: &'a [u8],
324 ciphertext: &'a [u8],
325 tag: &'a [u8],
326 aad: &'a [u8],
327 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>> {
328 (**self).decrypt(cipher_match, nonce, ciphertext, tag, aad)
329 }
330
331 fn try_refresh(&self) -> MaybeSendBoxFuture<'_, bool> {
332 (**self).try_refresh()
333 }
334 }
335 };
336}
337
338forward_aead_decryptor!(&T);
339forward_aead_decryptor!(Box<T>);
340forward_aead_decryptor!(Arc<T>);
341
342macro_rules! forward_aead_cipher_selector {
343 ($wrapper:ty) => {
344 impl<T: AeadCipherSelector + ?Sized> AeadCipherSelector for $wrapper {
345 fn select_cipher(&self) -> MaybeSendBoxFuture<'_, Arc<dyn AeadEncryptor>> {
346 (**self).select_cipher()
347 }
348 }
349 };
350}
351
352forward_aead_cipher_selector!(&T);
353forward_aead_cipher_selector!(Box<T>);
354forward_aead_cipher_selector!(Arc<T>);
355
356macro_rules! forward_aead_sealer {
357 ($wrapper:ty) => {
358 impl<T: AeadSealer + ?Sized> AeadSealer for $wrapper {
359 fn seal<'a>(
360 &'a self,
361 plaintext: &'a [u8],
362 aad: &'a [u8],
363 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, Error>> {
364 (**self).seal(plaintext, aad)
365 }
366 }
367 };
368}
369
370forward_aead_sealer!(&T);
371forward_aead_sealer!(Box<T>);
372forward_aead_sealer!(Arc<T>);
373
374macro_rules! forward_aead_unsealer {
375 ($wrapper:ty) => {
376 impl<T: AeadUnsealer + ?Sized> AeadUnsealer for $wrapper {
377 fn unseal<'a>(
378 &'a self,
379 cipher_match: Option<&'a CipherMatch<'a>>,
380 bundle: &'a [u8],
381 aad: &'a [u8],
382 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>> {
383 (**self).unseal(cipher_match, bundle, aad)
384 }
385 }
386 };
387}
388
389forward_aead_unsealer!(&T);
390forward_aead_unsealer!(Box<T>);
391forward_aead_unsealer!(Arc<T>);
392
393macro_rules! forward_aead_sealer_selector {
394 ($wrapper:ty) => {
395 impl<T: AeadSealerSelector + ?Sized> AeadSealerSelector for $wrapper {
396 fn select_sealer(&self) -> MaybeSendBoxFuture<'_, Arc<dyn AeadSealer>> {
397 (**self).select_sealer()
398 }
399 }
400 };
401}
402
403forward_aead_sealer_selector!(&T);
404forward_aead_sealer_selector!(Box<T>);
405forward_aead_sealer_selector!(Arc<T>);
406
407#[derive(Debug)]
463pub struct AeadV1Cipher<C>(C);
464
465impl<C> AeadV1Cipher<C> {
466 pub fn new(cipher: C) -> Self {
468 Self(cipher)
469 }
470}
471
472impl<C: AeadEncryptor> AeadEncryptor for AeadV1Cipher<C> {
473 fn enc_algorithm(&self) -> Cow<'_, str> {
474 self.0.enc_algorithm()
475 }
476
477 fn key_id(&self) -> Option<Cow<'_, str>> {
478 self.0.key_id()
479 }
480
481 fn encrypt<'a>(
482 &'a self,
483 plaintext: &'a [u8],
484 aad: &'a [u8],
485 ) -> MaybeSendBoxFuture<'a, Result<AeadOutput, Error>> {
486 self.0.encrypt(plaintext, aad)
487 }
488}
489
490impl<C: AeadEncryptor> AeadSealer for AeadV1Cipher<C> {
491 fn seal<'a>(
492 &'a self,
493 plaintext: &'a [u8],
494 aad: &'a [u8],
495 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, Error>> {
496 Box::pin(async move {
497 let output = self.encrypt(plaintext, aad).await?;
498
499 let nonce_len: u8 = output.nonce.len().try_into().map_err(|_| {
500 Error::new(crate::ErrorKind::Crypto, "nonce length exceeds u8::MAX")
501 })?;
502 let tag_len: u8 =
503 output.tag.len().try_into().map_err(|_| {
504 Error::new(crate::ErrorKind::Crypto, "tag length exceeds u8::MAX")
505 })?;
506
507 let mut bundle = Vec::with_capacity(
508 3 + output.nonce.len() + output.ciphertext.len() + output.tag.len(),
509 );
510 bundle.push(0x01);
511 bundle.push(nonce_len);
512 bundle.push(tag_len);
513 bundle.extend_from_slice(&output.nonce);
514 bundle.extend_from_slice(&output.ciphertext);
515 bundle.extend_from_slice(&output.tag);
516
517 Ok(bundle)
518 })
519 }
520}
521
522fn invalid_bundle() -> Error {
523 Error::new(ErrorKind::Crypto, UnsealError::InvalidBundle)
524}
525
526impl<C: AeadDecryptor> AeadDecryptor for AeadV1Cipher<C> {
527 fn cipher_match(&self, m: &CipherMatch<'_>) -> Option<KeyMatchStrength> {
528 self.0.cipher_match(m)
529 }
530
531 fn decrypt<'a>(
532 &'a self,
533 cipher_match: Option<&'a CipherMatch<'a>>,
534 nonce: &'a [u8],
535 ciphertext: &'a [u8],
536 tag: &'a [u8],
537 aad: &'a [u8],
538 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>> {
539 self.0.decrypt(cipher_match, nonce, ciphertext, tag, aad)
540 }
541
542 fn try_refresh(&self) -> MaybeSendBoxFuture<'_, bool> {
543 self.0.try_refresh()
544 }
545}
546
547impl<C: AeadDecryptor> AeadUnsealer for AeadV1Cipher<C> {
548 fn unseal<'a>(
549 &'a self,
550 cipher_match: Option<&'a CipherMatch<'a>>,
551 bundle: &'a [u8],
552 aad: &'a [u8],
553 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>> {
554 Box::pin(async move {
555 if bundle.len() < 3 || bundle[0] != 0x01 {
556 return Err(invalid_bundle().into());
557 }
558
559 let nonce_len = bundle[1] as usize;
560 let tag_len = bundle[2] as usize;
561
562 if bundle.len() < 3 + nonce_len + tag_len {
563 return Err(invalid_bundle().into());
564 }
565
566 let nonce = &bundle[3..3 + nonce_len];
567 let tag = &bundle[bundle.len() - tag_len..];
568 let ciphertext = &bundle[3 + nonce_len..bundle.len() - tag_len];
569
570 self.decrypt(cipher_match, nonce, ciphertext, tag, aad)
571 .await
572 })
573 }
574}
575
576#[derive(Debug)]
593pub struct StaticAeadCipher<C> {
594 cipher: Arc<C>,
595}
596
597impl<C> StaticAeadCipher<C> {
598 pub fn new(cipher: C) -> Self {
600 Self {
601 cipher: Arc::new(cipher),
602 }
603 }
604}
605
606impl<C: AeadEncryptor + 'static> AeadCipherSelector for StaticAeadCipher<C> {
607 fn select_cipher(&self) -> MaybeSendBoxFuture<'_, Arc<dyn AeadEncryptor>> {
608 let encryptor: Arc<dyn AeadEncryptor> = self.cipher.clone();
609 Box::pin(async move { encryptor })
610 }
611}
612
613impl<C: AeadEncryptor + 'static> AeadSealerSelector for StaticAeadCipher<C> {
614 fn select_sealer(&self) -> MaybeSendBoxFuture<'_, Arc<dyn AeadSealer>> {
615 let sealer: Arc<dyn AeadSealer> = Arc::new(AeadV1Cipher::new(Arc::clone(&self.cipher)));
616 Box::pin(async move { sealer })
617 }
618}
619
620impl<C: AeadDecryptor + 'static> AeadDecryptor for StaticAeadCipher<C> {
621 fn cipher_match(&self, m: &CipherMatch<'_>) -> Option<KeyMatchStrength> {
622 self.cipher.cipher_match(m)
623 }
624
625 fn decrypt<'a>(
626 &'a self,
627 cipher_match: Option<&'a CipherMatch<'a>>,
628 nonce: &'a [u8],
629 ciphertext: &'a [u8],
630 tag: &'a [u8],
631 aad: &'a [u8],
632 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>> {
633 self.cipher
634 .decrypt(cipher_match, nonce, ciphertext, tag, aad)
635 }
636
637 fn try_refresh(&self) -> MaybeSendBoxFuture<'_, bool> {
638 self.cipher.try_refresh()
639 }
640}
641
642impl<C: AeadDecryptor + 'static> AeadUnsealer for StaticAeadCipher<C> {
643 fn unseal<'a>(
644 &'a self,
645 cipher_match: Option<&'a CipherMatch<'a>>,
646 bundle: &'a [u8],
647 aad: &'a [u8],
648 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>> {
649 Box::pin(async move {
650 AeadV1Cipher::new(Arc::clone(&self.cipher))
651 .unseal(cipher_match, bundle, aad)
652 .await
653 })
654 }
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660
661 #[derive(Debug)]
662 struct MockEncryptor;
663
664 impl AeadEncryptor for MockEncryptor {
665 fn enc_algorithm(&self) -> Cow<'_, str> {
666 "mock".into()
667 }
668
669 fn key_id(&self) -> Option<Cow<'_, str>> {
670 None
671 }
672
673 fn encrypt<'a>(
674 &'a self,
675 plaintext: &'a [u8],
676 _aad: &'a [u8],
677 ) -> MaybeSendBoxFuture<'a, Result<AeadOutput, Error>> {
678 Box::pin(async move {
679 Ok(AeadOutput {
680 nonce: vec![1, 2, 3],
681 ciphertext: plaintext.iter().map(|b| b ^ 0xFF).collect(),
682 tag: vec![4, 5, 6, 7],
683 })
684 })
685 }
686 }
687
688 #[derive(Debug)]
689 struct MockDecryptor;
690
691 impl AeadDecryptor for MockDecryptor {
692 fn cipher_match(&self, _m: &CipherMatch<'_>) -> Option<KeyMatchStrength> {
693 Some(KeyMatchStrength::ByAlgorithm)
694 }
695
696 fn decrypt<'a>(
697 &'a self,
698 _cipher_match: Option<&'a CipherMatch<'a>>,
699 _nonce: &'a [u8],
700 ciphertext: &'a [u8],
701 _tag: &'a [u8],
702 _aad: &'a [u8],
703 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>> {
704 Box::pin(async move { Ok(ciphertext.iter().map(|b| b ^ 0xFF).collect()) })
706 }
707 }
708
709 #[derive(Debug)]
711 struct MockCipher;
712
713 impl AeadEncryptor for MockCipher {
714 fn enc_algorithm(&self) -> Cow<'_, str> {
715 MockEncryptor.enc_algorithm()
716 }
717
718 fn key_id(&self) -> Option<Cow<'_, str>> {
719 MockEncryptor.key_id()
720 }
721
722 fn encrypt<'a>(
723 &'a self,
724 plaintext: &'a [u8],
725 aad: &'a [u8],
726 ) -> MaybeSendBoxFuture<'a, Result<AeadOutput, Error>> {
727 Box::pin(async move { MockEncryptor.encrypt(plaintext, aad).await })
728 }
729 }
730
731 impl AeadDecryptor for MockCipher {
732 fn cipher_match(&self, m: &CipherMatch<'_>) -> Option<KeyMatchStrength> {
733 MockDecryptor.cipher_match(m)
734 }
735
736 fn decrypt<'a>(
737 &'a self,
738 cipher_match: Option<&'a CipherMatch<'a>>,
739 nonce: &'a [u8],
740 ciphertext: &'a [u8],
741 tag: &'a [u8],
742 aad: &'a [u8],
743 ) -> MaybeSendBoxFuture<'a, Result<Vec<u8>, DecryptError>> {
744 Box::pin(async move {
745 MockDecryptor
746 .decrypt(cipher_match, nonce, ciphertext, tag, aad)
747 .await
748 })
749 }
750 }
751
752 fn assert_invalid_bundle(err: &DecryptError) {
753 let DecryptError::Other { source } = err else {
754 unreachable!("expected DecryptError::Other, got {err:?}");
755 };
756 assert_eq!(source.kind(), ErrorKind::Crypto);
757 assert_eq!(source.to_string(), "cryptographic operation failed");
758 assert_eq!(
759 std::error::Error::source(source)
760 .expect("source")
761 .to_string(),
762 "invalid bundle"
763 );
764 }
765
766 #[tokio::test]
767 async fn seal_roundtrip() {
768 let plaintext = b"hello world";
769 let aad = b"associated";
770
771 let sealer = AeadV1Cipher::new(MockEncryptor);
772 let bundle = sealer.seal(plaintext, aad).await.unwrap();
773
774 let unsealer = AeadV1Cipher::new(MockDecryptor);
775 let recovered = unsealer.unseal(None, &bundle, aad).await.unwrap();
776 assert_eq!(recovered, plaintext);
777 }
778
779 #[tokio::test]
780 async fn erased_cipher_roundtrip() {
781 let sealer: Arc<dyn AeadSealer> = Arc::new(AeadV1Cipher::new(MockEncryptor));
782 let bundle = sealer.seal(b"hello", b"").await.unwrap();
783
784 let unsealer: Arc<dyn AeadUnsealer> = Arc::new(AeadV1Cipher::new(MockDecryptor));
785 let recovered = unsealer.unseal(None, &bundle, b"").await.unwrap();
786 assert_eq!(recovered, b"hello");
787 }
788
789 #[tokio::test]
793 async fn one_object_sealed_cipher_roundtrip() {
794 async fn roundtrip(cipher: impl AeadSealer + AeadUnsealer) {
795 let bundle = cipher.seal(b"hello", b"aad").await.unwrap();
796 let recovered = cipher.unseal(None, &bundle, b"aad").await.unwrap();
797 assert_eq!(recovered, b"hello");
798 }
799
800 roundtrip(AeadV1Cipher::new(MockCipher)).await;
801
802 let erased: Arc<dyn SealedAeadCipher> = Arc::new(AeadV1Cipher::new(MockCipher));
803 roundtrip(erased).await;
804 }
805
806 #[tokio::test]
807 async fn bundle_format() {
808 let plaintext = b"AB";
809 let sealer = AeadV1Cipher::new(MockEncryptor);
810 let bundle = sealer.seal(plaintext, b"").await.unwrap();
811
812 assert_eq!(bundle[0], 0x01);
814 assert_eq!(bundle[1], 3); assert_eq!(bundle[2], 4); assert_eq!(&bundle[3..6], &[1, 2, 3]); assert_eq!(&bundle[6..8], &[0x41 ^ 0xFF, 0x42 ^ 0xFF]); assert_eq!(&bundle[8..12], &[4, 5, 6, 7]); }
820
821 #[tokio::test]
822 async fn unseal_wrong_version() {
823 let unsealer = AeadV1Cipher::new(MockDecryptor);
824 let err = unsealer.unseal(None, &[0x02, 0, 0], b"").await.unwrap_err();
825 assert_invalid_bundle(&err);
826 }
827
828 #[tokio::test]
829 async fn unseal_too_short() {
830 let unsealer = AeadV1Cipher::new(MockDecryptor);
831 let err = unsealer.unseal(None, &[0x01], b"").await.unwrap_err();
832 assert_invalid_bundle(&err);
833 }
834
835 #[tokio::test]
836 async fn unseal_truncated() {
837 let unsealer = AeadV1Cipher::new(MockDecryptor);
838 let err = unsealer
840 .unseal(None, &[0x01, 10, 10, 0x00], b"")
841 .await
842 .unwrap_err();
843 assert_invalid_bundle(&err);
844 }
845
846 #[tokio::test]
847 async fn unseal_empty() {
848 let unsealer = AeadV1Cipher::new(MockDecryptor);
849 let err = unsealer.unseal(None, &[], b"").await.unwrap_err();
850 assert_invalid_bundle(&err);
851 }
852
853 fn cipher_match<'a>(enc: Option<&'a str>, kid: Option<&'a str>) -> CipherMatch<'a> {
854 CipherMatch { enc, kid }
855 }
856
857 #[test]
858 fn strength_for_enc_mismatch() {
859 let m = cipher_match(Some("A128GCM"), Some("kid-1"));
860 assert_eq!(m.strength_for("A256GCM", Some("kid-1")), None);
861 }
862
863 #[test]
864 fn strength_for_no_enc_is_compatible() {
865 let m = cipher_match(None, None);
866 assert_eq!(
867 m.strength_for("A256GCM", None),
868 Some(KeyMatchStrength::ByAlgorithm)
869 );
870 }
871
872 #[test]
873 fn strength_for_matching_kid() {
874 let m = cipher_match(Some("A256GCM"), Some("kid-1"));
875 assert_eq!(
876 m.strength_for("A256GCM", Some("kid-1")),
877 Some(KeyMatchStrength::ByKeyId)
878 );
879 }
880
881 #[test]
882 fn strength_for_kid_mismatch_is_none_not_by_algorithm() {
883 let m = cipher_match(None, Some("kid-1"));
884 assert_eq!(m.strength_for("A256GCM", Some("kid-2")), None);
885 }
886
887 #[test]
888 fn strength_for_missing_kid_falls_back_to_algorithm() {
889 let m = cipher_match(Some("A256GCM"), None);
890 assert_eq!(
891 m.strength_for("A256GCM", Some("kid-1")),
892 Some(KeyMatchStrength::ByAlgorithm)
893 );
894 let m = cipher_match(Some("A256GCM"), Some("kid-1"));
895 assert_eq!(
896 m.strength_for("A256GCM", None),
897 Some(KeyMatchStrength::ByAlgorithm)
898 );
899 }
900}