1use crate::crypto::traits::{Decryptor, KeyResolver, SignatureVerifier};
44use crate::error::{Claim169Error, Result};
45use crate::model::VerificationStatus;
46use crate::pipeline;
47use crate::pipeline::decompress::DetectedCompression;
48use crate::{DecodeResult, Warning, WarningCode};
49
50#[cfg(feature = "software-crypto")]
51use crate::crypto::software::{AesGcmDecryptor, EcdsaP256Verifier, Ed25519Verifier};
52
53use std::time::{SystemTime, UNIX_EPOCH};
54
55pub(crate) const DEFAULT_MAX_DECOMPRESSED_BYTES: usize = 65536;
57
58pub struct Decoder {
88 qr_text: String,
89 verifier: Option<Box<dyn SignatureVerifier + Send + Sync>>,
90 decryptor: Option<Box<dyn Decryptor + Send + Sync>>,
91 resolver: Option<Box<dyn KeyResolver + Send + Sync>>,
92 allow_unverified: bool,
93 skip_biometrics: bool,
94 validate_timestamps: bool,
95 clock_skew_tolerance_seconds: i64,
96 max_decompressed_bytes: usize,
97 strict_compression: bool,
98}
99
100impl Decoder {
101 pub fn new(qr_text: impl Into<String>) -> Self {
114 Self {
115 qr_text: qr_text.into(),
116 verifier: None,
117 decryptor: None,
118 resolver: None,
119 allow_unverified: false,
120 skip_biometrics: false,
121 validate_timestamps: true,
122 clock_skew_tolerance_seconds: 0,
123 max_decompressed_bytes: DEFAULT_MAX_DECOMPRESSED_BYTES,
124 strict_compression: false,
125 }
126 }
127
128 pub fn verify_with<V: SignatureVerifier + 'static>(mut self, verifier: V) -> Self {
143 self.verifier = Some(Box::new(verifier));
144 self
145 }
146
147 #[cfg(feature = "software-crypto")]
166 pub fn verify_with_ed25519(self, public_key: &[u8]) -> Result<Self> {
167 let verifier = Ed25519Verifier::from_bytes(public_key)
168 .map_err(|e| Claim169Error::Crypto(e.to_string()))?;
169
170 Ok(self.verify_with(verifier))
171 }
172
173 #[cfg(feature = "software-crypto")]
185 pub fn verify_with_ed25519_pem(self, pem: &str) -> Result<Self> {
186 let verifier =
187 Ed25519Verifier::from_pem(pem).map_err(|e| Claim169Error::Crypto(e.to_string()))?;
188
189 Ok(self.verify_with(verifier))
190 }
191
192 #[cfg(feature = "software-crypto")]
204 pub fn verify_with_ecdsa_p256(self, public_key: &[u8]) -> Result<Self> {
205 let verifier = EcdsaP256Verifier::from_sec1_bytes(public_key)
206 .map_err(|e| Claim169Error::Crypto(e.to_string()))?;
207
208 Ok(self.verify_with(verifier))
209 }
210
211 #[cfg(feature = "software-crypto")]
223 pub fn verify_with_ecdsa_p256_pem(self, pem: &str) -> Result<Self> {
224 let verifier =
225 EcdsaP256Verifier::from_pem(pem).map_err(|e| Claim169Error::Crypto(e.to_string()))?;
226
227 Ok(self.verify_with(verifier))
228 }
229
230 pub fn decrypt_with<D: Decryptor + 'static>(mut self, decryptor: D) -> Self {
238 self.decryptor = Some(Box::new(decryptor));
239 self
240 }
241
242 #[cfg(feature = "software-crypto")]
252 pub fn decrypt_with_aes256(self, key: &[u8]) -> Result<Self> {
253 let decryptor =
254 AesGcmDecryptor::aes256(key).map_err(|e| Claim169Error::Crypto(e.to_string()))?;
255
256 Ok(self.decrypt_with(decryptor))
257 }
258
259 #[cfg(feature = "software-crypto")]
269 pub fn decrypt_with_aes128(self, key: &[u8]) -> Result<Self> {
270 let decryptor =
271 AesGcmDecryptor::aes128(key).map_err(|e| Claim169Error::Crypto(e.to_string()))?;
272
273 Ok(self.decrypt_with(decryptor))
274 }
275
276 pub fn resolve_with<R: KeyResolver + 'static>(mut self, resolver: R) -> Self {
305 self.resolver = Some(Box::new(resolver));
306 self
307 }
308
309 pub fn allow_unverified(mut self) -> Self {
322 self.allow_unverified = true;
323 self
324 }
325
326 pub fn skip_biometrics(mut self) -> Self {
331 self.skip_biometrics = true;
332 self
333 }
334
335 pub fn without_timestamp_validation(mut self) -> Self {
343 self.validate_timestamps = false;
344 self
345 }
346
347 pub fn clock_skew_tolerance(mut self, seconds: i64) -> Self {
356 self.clock_skew_tolerance_seconds = seconds;
357 self
358 }
359
360 pub fn max_decompressed_bytes(mut self, bytes: usize) -> Self {
369 self.max_decompressed_bytes = bytes;
370 self
371 }
372
373 pub fn strict_compression(mut self) -> Self {
382 self.strict_compression = true;
383 self
384 }
385
386 pub fn decode(self) -> Result<DecodeResult> {
416 let mut warnings = Vec::new();
417
418 let verifier_ref: Option<&dyn SignatureVerifier> = self
420 .verifier
421 .as_ref()
422 .map(|v| v.as_ref() as &dyn SignatureVerifier);
423 let decryptor_ref: Option<&dyn Decryptor> = self
424 .decryptor
425 .as_ref()
426 .map(|d| d.as_ref() as &dyn Decryptor);
427
428 let compressed = pipeline::base45_decode(&self.qr_text)?;
430
431 let (cose_bytes, detected_compression) =
433 pipeline::decompress(&compressed, self.max_decompressed_bytes)?;
434
435 if self.strict_compression && detected_compression != DetectedCompression::Zlib {
437 return Err(Claim169Error::Decompress(format!(
438 "strict compression mode requires zlib, but detected: {}",
439 detected_compression
440 )));
441 }
442
443 if detected_compression != DetectedCompression::Zlib {
445 warnings.push(Warning {
446 code: WarningCode::NonStandardCompression,
447 message: format!(
448 "non-standard compression detected: {}",
449 detected_compression
450 ),
451 });
452 }
453
454 if self.resolver.is_some() && self.verifier.is_some() {
456 return Err(Claim169Error::DecodingConfig(
457 "cannot use both resolve_with() and verify_with() — \
458 resolve_with() provides its own verifier via key lookup"
459 .to_string(),
460 ));
461 }
462 if self.resolver.is_some() && self.decryptor.is_some() {
463 return Err(Claim169Error::DecodingConfig(
464 "cannot use both resolve_with() and decrypt_with() — \
465 resolve_with() provides its own decryptor via key lookup"
466 .to_string(),
467 ));
468 }
469
470 let cose_result = if let Some(ref resolver) = self.resolver {
472 pipeline::cose_parse_with_resolver(&cose_bytes, resolver.as_ref())?
474 } else {
475 pipeline::cose_parse(&cose_bytes, verifier_ref, decryptor_ref)?
476 };
477
478 if !self.allow_unverified && cose_result.verification_status == VerificationStatus::Skipped
480 {
481 return Err(Claim169Error::DecodingConfig(
482 "verification required but no verifier or resolver provided - \
483 use verify_with(), resolve_with(), or allow_unverified() to configure"
484 .to_string(),
485 ));
486 }
487
488 if cose_result.verification_status == VerificationStatus::Failed {
490 return Err(Claim169Error::SignatureInvalid(
491 "signature verification failed".to_string(),
492 ));
493 }
494
495 let cwt_result = pipeline::cwt_parse(&cose_result.payload)?;
497
498 if self.validate_timestamps {
500 let now = SystemTime::now()
501 .duration_since(UNIX_EPOCH)
502 .map(|d| d.as_secs() as i64)
503 .map_err(|_| {
504 Claim169Error::DecodingConfig("system clock is before Unix epoch".to_string())
505 })?;
506
507 let skew = self.clock_skew_tolerance_seconds;
508
509 if let Some(exp) = cwt_result.meta.expires_at {
510 if now > exp + skew {
511 return Err(Claim169Error::Expired(exp));
512 }
513 }
514
515 if let Some(nbf) = cwt_result.meta.not_before {
516 if now + skew < nbf {
517 return Err(Claim169Error::NotYetValid(nbf));
518 }
519 }
520 } else {
521 warnings.push(Warning {
522 code: WarningCode::TimestampValidationSkipped,
523 message: "Timestamp validation was disabled".to_string(),
524 });
525 }
526
527 let claim169 = pipeline::claim169_transform(cwt_result.claim_169, self.skip_biometrics)?;
529
530 if self.skip_biometrics {
531 warnings.push(Warning {
532 code: WarningCode::BiometricsSkipped,
533 message: "Biometric data was skipped".to_string(),
534 });
535 }
536
537 if !claim169.unknown_fields.is_empty() {
538 warnings.push(Warning {
539 code: WarningCode::UnknownFields,
540 message: format!(
541 "Found {} unknown fields (keys: {:?})",
542 claim169.unknown_fields.len(),
543 claim169.unknown_fields.keys().collect::<Vec<_>>()
544 ),
545 });
546 }
547
548 Ok(DecodeResult {
549 claim169,
550 cwt_meta: cwt_result.meta,
551 verification_status: cose_result.verification_status,
552 x509_headers: cose_result.x509_headers,
553 detected_compression,
554 warnings,
555 key_id: cose_result.key_id,
556 algorithm: cose_result.algorithm,
557 })
558 }
559}
560
561#[cfg(test)]
562mod tests {
563 use super::*;
564 use crate::model::{Claim169, CwtMeta, VerificationStatus};
565
566 fn create_test_qr() -> String {
568 use crate::Encoder;
569
570 let claim169 = Claim169 {
571 id: Some("test-123".to_string()),
572 full_name: Some("Test User".to_string()),
573 ..Default::default()
574 };
575
576 let cwt_meta = CwtMeta::new()
577 .with_issuer("https://test.issuer")
578 .with_expires_at(i64::MAX); Encoder::new(claim169, cwt_meta)
581 .allow_unsigned()
582 .encode()
583 .unwrap()
584 .qr_data
585 }
586
587 #[test]
588 fn test_decoder_requires_verifier_or_allow_unverified() {
589 let qr_text = create_test_qr();
590
591 let result = Decoder::new(&qr_text).decode();
592
593 assert!(result.is_err());
594 match result.unwrap_err() {
595 Claim169Error::DecodingConfig(msg) => {
596 assert!(msg.contains("allow_unverified"));
597 }
598 e => panic!("Expected DecodingConfig error, got: {:?}", e),
599 }
600 }
601
602 #[test]
603 fn test_decoder_allow_unverified() {
604 let qr_text = create_test_qr();
605
606 let result = Decoder::new(&qr_text).allow_unverified().decode();
607
608 assert!(result.is_ok());
609 let decoded = result.unwrap();
610 assert_eq!(decoded.claim169.id, Some("test-123".to_string()));
611 assert_eq!(decoded.claim169.full_name, Some("Test User".to_string()));
612 assert_eq!(decoded.verification_status, VerificationStatus::Skipped);
613 }
614
615 #[test]
616 fn test_decoder_accepts_string() {
617 let qr_text = create_test_qr();
618
619 let result = Decoder::new(qr_text.clone()).allow_unverified().decode();
621 assert!(result.is_ok());
622
623 let result = Decoder::new(&qr_text).allow_unverified().decode();
625 assert!(result.is_ok());
626 }
627
628 #[test]
629 fn test_decoder_skip_biometrics() {
630 let qr_text = create_test_qr();
631
632 let result = Decoder::new(&qr_text)
633 .allow_unverified()
634 .skip_biometrics()
635 .decode()
636 .unwrap();
637
638 assert!(result
640 .warnings
641 .iter()
642 .any(|w| w.code == WarningCode::BiometricsSkipped));
643 }
644
645 #[test]
646 fn test_decoder_without_timestamp_validation() {
647 let qr_text = create_test_qr();
648
649 let result = Decoder::new(&qr_text)
650 .allow_unverified()
651 .without_timestamp_validation()
652 .decode()
653 .unwrap();
654
655 assert!(result
657 .warnings
658 .iter()
659 .any(|w| w.code == WarningCode::TimestampValidationSkipped));
660 }
661
662 #[cfg(feature = "software-crypto")]
663 #[test]
664 fn test_decoder_roundtrip_ed25519() {
665 use crate::crypto::software::Ed25519Signer;
666 use crate::Encoder;
667 use coset::iana;
668
669 let claim169 = Claim169 {
670 id: Some("signed-test".to_string()),
671 full_name: Some("Signed User".to_string()),
672 email: Some("signed@example.com".to_string()),
673 ..Default::default()
674 };
675
676 let cwt_meta = CwtMeta::new()
677 .with_issuer("https://signed.test")
678 .with_expires_at(i64::MAX);
679
680 let signer = Ed25519Signer::generate();
682 let public_key = signer.public_key_bytes();
683
684 let encode_result = Encoder::new(claim169.clone(), cwt_meta)
686 .sign_with(signer, iana::Algorithm::EdDSA)
687 .encode()
688 .unwrap();
689
690 let result = Decoder::new(&encode_result.qr_data)
692 .verify_with_ed25519(&public_key)
693 .unwrap()
694 .decode()
695 .unwrap();
696
697 assert_eq!(result.verification_status, VerificationStatus::Verified);
698 assert_eq!(result.claim169.id, claim169.id);
699 assert_eq!(result.claim169.full_name, claim169.full_name);
700 assert_eq!(result.claim169.email, claim169.email);
701 assert_eq!(result.detected_compression, DetectedCompression::Zlib);
702 }
703
704 #[cfg(feature = "software-crypto")]
705 #[test]
706 fn test_decoder_roundtrip_encrypted() {
707 use crate::crypto::software::Ed25519Signer;
708 use crate::Encoder;
709 use coset::iana;
710
711 let claim169 = Claim169 {
712 id: Some("encrypted-test".to_string()),
713 full_name: Some("Encrypted User".to_string()),
714 ..Default::default()
715 };
716
717 let cwt_meta = CwtMeta::new()
718 .with_issuer("https://encrypted.test")
719 .with_expires_at(i64::MAX);
720
721 let signer = Ed25519Signer::generate();
723 let public_key = signer.public_key_bytes();
724 let aes_key = [42u8; 32];
725 let nonce = [7u8; 12];
726
727 let encode_result = Encoder::new(claim169.clone(), cwt_meta)
729 .sign_with(signer, iana::Algorithm::EdDSA)
730 .encrypt_with_aes256_nonce(&aes_key, &nonce)
731 .unwrap()
732 .encode()
733 .unwrap();
734
735 let result = Decoder::new(&encode_result.qr_data)
737 .decrypt_with_aes256(&aes_key)
738 .unwrap()
739 .verify_with_ed25519(&public_key)
740 .unwrap()
741 .decode()
742 .unwrap();
743
744 assert_eq!(result.verification_status, VerificationStatus::Verified);
745 assert_eq!(result.claim169.id, claim169.id);
746 assert_eq!(result.claim169.full_name, claim169.full_name);
747 }
748
749 #[cfg(feature = "software-crypto")]
750 #[test]
751 fn test_decoder_wrong_key_fails() {
752 use crate::crypto::software::Ed25519Signer;
753 use crate::Encoder;
754 use coset::iana;
755
756 let claim169 = Claim169::minimal("test", "Test");
757 let cwt_meta = CwtMeta::default();
758
759 let signer = Ed25519Signer::generate();
760 let wrong_signer = Ed25519Signer::generate();
761 let wrong_public_key = wrong_signer.public_key_bytes();
762
763 let qr_data = Encoder::new(claim169, cwt_meta)
764 .sign_with(signer, iana::Algorithm::EdDSA)
765 .encode()
766 .unwrap()
767 .qr_data;
768
769 let result = Decoder::new(&qr_data)
771 .verify_with_ed25519(&wrong_public_key)
772 .unwrap()
773 .decode();
774
775 assert!(result.is_err());
776 assert!(matches!(
777 result.unwrap_err(),
778 Claim169Error::SignatureInvalid(_)
779 ));
780 }
781
782 #[test]
783 fn test_decoder_strict_compression_accepts_zlib() {
784 let qr_text = create_test_qr();
785
786 let result = Decoder::new(&qr_text)
787 .allow_unverified()
788 .strict_compression()
789 .decode();
790
791 assert!(result.is_ok());
792 assert_eq!(
793 result.unwrap().detected_compression,
794 DetectedCompression::Zlib
795 );
796 }
797
798 #[test]
799 fn test_decoder_strict_compression_rejects_non_zlib() {
800 use crate::{Compression, Encoder};
801
802 let claim169 = Claim169::minimal("test", "Test");
803 let cwt_meta = CwtMeta::new().with_expires_at(i64::MAX);
804
805 let qr_data = Encoder::new(claim169, cwt_meta)
806 .allow_unsigned()
807 .compression(Compression::None)
808 .encode()
809 .unwrap()
810 .qr_data;
811
812 let result = Decoder::new(&qr_data)
813 .allow_unverified()
814 .strict_compression()
815 .decode();
816
817 assert!(result.is_err());
818 assert!(matches!(result.unwrap_err(), Claim169Error::Decompress(_)));
819 }
820
821 #[test]
822 fn test_decoder_non_standard_compression_warning() {
823 use crate::{Compression, Encoder};
824
825 let claim169 = Claim169::minimal("test", "Test");
826 let cwt_meta = CwtMeta::new().with_expires_at(i64::MAX);
827
828 let qr_data = Encoder::new(claim169, cwt_meta)
829 .allow_unsigned()
830 .compression(Compression::None)
831 .encode()
832 .unwrap()
833 .qr_data;
834
835 let result = Decoder::new(&qr_data).allow_unverified().decode().unwrap();
836
837 assert_eq!(result.detected_compression, DetectedCompression::None);
838 assert!(result
839 .warnings
840 .iter()
841 .any(|w| w.code == WarningCode::NonStandardCompression));
842 }
843
844 #[test]
845 fn test_decoder_invalid_base45() {
846 let result = Decoder::new("!!!invalid base45!!!")
847 .allow_unverified()
848 .decode();
849
850 assert!(result.is_err());
851 assert!(matches!(
852 result.unwrap_err(),
853 Claim169Error::Base45Decode(_)
854 ));
855 }
856
857 #[test]
858 fn test_decoder_max_decompressed_bytes() {
859 let qr_text = create_test_qr();
860
861 let result = Decoder::new(&qr_text)
863 .allow_unverified()
864 .max_decompressed_bytes(10)
865 .decode();
866
867 assert!(result.is_err());
868 assert!(matches!(
869 result.unwrap_err(),
870 Claim169Error::DecompressLimitExceeded { .. }
871 ));
872 }
873
874 #[cfg(feature = "software-crypto")]
875 #[test]
876 fn test_resolver_selects_verifier_by_kid() {
877 use crate::crypto::software::Ed25519Signer;
878 use crate::crypto::traits::{Decryptor as DecryptorTrait, KeyResolver};
879 use crate::error::{CryptoError, CryptoResult};
880 use coset::iana;
881 use std::collections::HashMap;
882
883 let claim169 = Claim169 {
884 id: Some("resolver-test".to_string()),
885 ..Default::default()
886 };
887 let cwt_meta = CwtMeta::new()
888 .with_issuer("https://issuer-a.test")
889 .with_expires_at(i64::MAX);
890
891 let mut signer_a = Ed25519Signer::generate();
893 signer_a.set_key_id(b"key-a".to_vec());
894 let pub_key_a = signer_a.public_key_bytes();
895
896 let mut signer_b = Ed25519Signer::generate();
897 signer_b.set_key_id(b"key-b".to_vec());
898 let pub_key_b = signer_b.public_key_bytes();
899
900 let qr_data = crate::Encoder::new(claim169, cwt_meta)
902 .sign_with(signer_a, iana::Algorithm::EdDSA)
903 .encode()
904 .unwrap()
905 .qr_data;
906
907 struct SimpleResolver {
909 keys: HashMap<Vec<u8>, Vec<u8>>,
910 }
911
912 impl KeyResolver for SimpleResolver {
913 fn resolve_verifier(
914 &self,
915 key_id: Option<&[u8]>,
916 _algorithm: iana::Algorithm,
917 ) -> CryptoResult<Box<dyn SignatureVerifier>> {
918 let kid = key_id.ok_or(CryptoError::KeyNotFound)?;
919 let public_key = self.keys.get(kid).ok_or(CryptoError::KeyNotFound)?;
920 let verifier = crate::crypto::software::Ed25519Verifier::from_bytes(public_key)
921 .map_err(|e| CryptoError::Other(e.to_string()))?;
922 Ok(Box::new(verifier))
923 }
924
925 fn resolve_decryptor(
926 &self,
927 _key_id: Option<&[u8]>,
928 _algorithm: iana::Algorithm,
929 ) -> CryptoResult<Box<dyn DecryptorTrait>> {
930 Err(CryptoError::KeyNotFound)
931 }
932 }
933
934 let mut keys = HashMap::new();
935 keys.insert(b"key-a".to_vec(), pub_key_a.to_vec());
936 keys.insert(b"key-b".to_vec(), pub_key_b.to_vec());
937
938 let resolver = SimpleResolver { keys };
939
940 let result = Decoder::new(&qr_data)
941 .resolve_with(resolver)
942 .decode()
943 .unwrap();
944
945 assert_eq!(result.claim169.id, Some("resolver-test".to_string()));
946 assert_eq!(result.verification_status, VerificationStatus::Verified);
947 assert_eq!(result.key_id, Some(b"key-a".to_vec()));
948 }
949
950 #[cfg(feature = "software-crypto")]
951 #[test]
952 fn test_resolver_unknown_kid_returns_error() {
953 use crate::crypto::traits::{Decryptor as DecryptorTrait, KeyResolver};
954 use crate::error::{CryptoError, CryptoResult};
955 use coset::iana;
956
957 let claim169 = Claim169 {
958 id: Some("unknown-kid-test".to_string()),
959 ..Default::default()
960 };
961 let cwt_meta = CwtMeta::new()
962 .with_issuer("https://issuer.test")
963 .with_expires_at(i64::MAX);
964
965 let mut signer = crate::crypto::software::Ed25519Signer::generate();
966 signer.set_key_id(b"unknown-key".to_vec());
967
968 let qr_data = crate::Encoder::new(claim169, cwt_meta)
969 .sign_with(signer, iana::Algorithm::EdDSA)
970 .encode()
971 .unwrap()
972 .qr_data;
973
974 struct EmptyResolver;
976 impl KeyResolver for EmptyResolver {
977 fn resolve_verifier(
978 &self,
979 _key_id: Option<&[u8]>,
980 _algorithm: iana::Algorithm,
981 ) -> CryptoResult<Box<dyn SignatureVerifier>> {
982 Err(CryptoError::KeyNotFound)
983 }
984 fn resolve_decryptor(
985 &self,
986 _key_id: Option<&[u8]>,
987 _algorithm: iana::Algorithm,
988 ) -> CryptoResult<Box<dyn DecryptorTrait>> {
989 Err(CryptoError::KeyNotFound)
990 }
991 }
992
993 let result = Decoder::new(&qr_data).resolve_with(EmptyResolver).decode();
994
995 assert!(result.is_err());
996 }
997
998 #[cfg(feature = "software-crypto")]
999 #[test]
1000 fn test_decode_result_exposes_key_id_and_algorithm() {
1001 use crate::crypto::software::Ed25519Signer;
1002 use crate::Encoder;
1003 use coset::iana;
1004
1005 let claim169 = Claim169 {
1006 id: Some("kid-decode-test".to_string()),
1007 ..Default::default()
1008 };
1009
1010 let cwt_meta = CwtMeta::new()
1011 .with_issuer("https://test.issuer")
1012 .with_expires_at(i64::MAX);
1013
1014 let mut signer = Ed25519Signer::generate();
1015 let kid = b"my-key-id-123";
1016 signer.set_key_id(kid.to_vec());
1017 let public_key = signer.public_key_bytes();
1018
1019 let qr_data = Encoder::new(claim169, cwt_meta)
1020 .sign_with(signer, iana::Algorithm::EdDSA)
1021 .encode()
1022 .unwrap()
1023 .qr_data;
1024
1025 let result = Decoder::new(&qr_data)
1026 .verify_with_ed25519(&public_key)
1027 .unwrap()
1028 .decode()
1029 .unwrap();
1030
1031 assert_eq!(result.key_id, Some(kid.to_vec()));
1032 assert_eq!(result.algorithm, Some(iana::Algorithm::EdDSA));
1033 assert_eq!(result.verification_status, VerificationStatus::Verified);
1034 }
1035
1036 #[test]
1037 fn test_decode_result_no_key_id_when_absent() {
1038 let qr_text = create_test_qr();
1039
1040 let result = Decoder::new(&qr_text).allow_unverified().decode().unwrap();
1041
1042 assert_eq!(result.key_id, None);
1043 }
1044
1045 #[cfg(feature = "software-crypto")]
1046 #[test]
1047 fn test_resolver_conflicts_with_verify_with() {
1048 use crate::crypto::traits::{Decryptor as DecryptorTrait, KeyResolver};
1049 use crate::error::{CryptoError, CryptoResult};
1050 use coset::iana;
1051
1052 struct DummyResolver;
1053 impl KeyResolver for DummyResolver {
1054 fn resolve_verifier(
1055 &self,
1056 _key_id: Option<&[u8]>,
1057 _algorithm: iana::Algorithm,
1058 ) -> CryptoResult<Box<dyn SignatureVerifier>> {
1059 Err(CryptoError::KeyNotFound)
1060 }
1061 fn resolve_decryptor(
1062 &self,
1063 _key_id: Option<&[u8]>,
1064 _algorithm: iana::Algorithm,
1065 ) -> CryptoResult<Box<dyn DecryptorTrait>> {
1066 Err(CryptoError::KeyNotFound)
1067 }
1068 }
1069
1070 let signer = crate::crypto::software::Ed25519Signer::generate();
1071 let public_key = signer.public_key_bytes();
1072
1073 let qr_text = create_test_qr();
1074
1075 let result = Decoder::new(&qr_text)
1076 .resolve_with(DummyResolver)
1077 .verify_with_ed25519(&public_key)
1078 .unwrap()
1079 .decode();
1080
1081 assert!(result.is_err());
1082 match result.unwrap_err() {
1083 Claim169Error::DecodingConfig(msg) => {
1084 assert!(msg.contains("resolve_with()"));
1085 assert!(msg.contains("verify_with()"));
1086 }
1087 e => panic!("Expected DecodingConfig error, got: {:?}", e),
1088 }
1089 }
1090
1091 #[cfg(feature = "software-crypto")]
1092 #[test]
1093 fn test_resolver_conflicts_with_decrypt_with() {
1094 use crate::crypto::traits::{Decryptor as DecryptorTrait, KeyResolver};
1095 use crate::error::{CryptoError, CryptoResult};
1096 use coset::iana;
1097
1098 struct DummyResolver;
1099 impl KeyResolver for DummyResolver {
1100 fn resolve_verifier(
1101 &self,
1102 _key_id: Option<&[u8]>,
1103 _algorithm: iana::Algorithm,
1104 ) -> CryptoResult<Box<dyn SignatureVerifier>> {
1105 Err(CryptoError::KeyNotFound)
1106 }
1107 fn resolve_decryptor(
1108 &self,
1109 _key_id: Option<&[u8]>,
1110 _algorithm: iana::Algorithm,
1111 ) -> CryptoResult<Box<dyn DecryptorTrait>> {
1112 Err(CryptoError::KeyNotFound)
1113 }
1114 }
1115
1116 let aes_key = [42u8; 32];
1117 let qr_text = create_test_qr();
1118
1119 let result = Decoder::new(&qr_text)
1120 .resolve_with(DummyResolver)
1121 .decrypt_with_aes256(&aes_key)
1122 .unwrap()
1123 .decode();
1124
1125 assert!(result.is_err());
1126 match result.unwrap_err() {
1127 Claim169Error::DecodingConfig(msg) => {
1128 assert!(msg.contains("resolve_with()"));
1129 assert!(msg.contains("decrypt_with()"));
1130 }
1131 e => panic!("Expected DecodingConfig error, got: {:?}", e),
1132 }
1133 }
1134
1135 #[cfg(feature = "software-crypto")]
1136 #[test]
1137 fn test_empty_key_id_roundtrips_as_none() {
1138 use crate::crypto::software::Ed25519Signer;
1142 use crate::Encoder;
1143 use coset::iana;
1144
1145 let claim169 = Claim169 {
1146 id: Some("empty-kid-test".to_string()),
1147 ..Default::default()
1148 };
1149 let cwt_meta = CwtMeta::new()
1150 .with_issuer("https://empty-kid.test")
1151 .with_expires_at(i64::MAX);
1152
1153 let mut signer = Ed25519Signer::generate();
1154 signer.set_key_id(vec![]); let public_key = signer.public_key_bytes();
1156
1157 let qr_data = Encoder::new(claim169, cwt_meta)
1158 .sign_with(signer, iana::Algorithm::EdDSA)
1159 .encode()
1160 .unwrap()
1161 .qr_data;
1162
1163 let result = Decoder::new(&qr_data)
1164 .verify_with_ed25519(&public_key)
1165 .unwrap()
1166 .decode()
1167 .unwrap();
1168
1169 assert_eq!(result.key_id, None);
1171 assert_eq!(result.verification_status, VerificationStatus::Verified);
1172 }
1173}