1mod types;
13
14pub use types::{
15 CoseEncrypt0, CoseSign1, CritHeader, EmptyHeader, EncProtectedHeader, EncStructure,
16 EncapKeyHeader, HEADER_TIMESTAMP, SigProtectedHeader, SigStructure,
17};
18
19use web_time::{SystemTime, UNIX_EPOCH};
22
23use crate::cbor::{self, Decode, Encode, Raw};
24use crate::{xdsa, xhpke};
25
26const DOMAIN_PREFIX: &[u8] = b"dark-bio-v1:";
33
34#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
36pub enum Error {
37 #[error("cbor: {0}")]
38 CborError(#[from] cbor::Error),
39 #[error("unexpected algorithm: have {0}, want {1}")]
40 UnexpectedAlgorithm(i64, i64),
41 #[error("unexpected signing key: have {0:x?}, want {1:x?}")]
42 UnexpectedSigningKey(xdsa::Fingerprint, xdsa::Fingerprint),
43 #[error("signature verification failed: {0}")]
44 InvalidSignature(String),
45 #[error("signature stale: time drift {0}s exceeds max {1}s")]
46 StaleSignature(u64, u64),
47 #[error("unexpected payload in detached signature")]
48 UnexpectedPayload,
49 #[error("missing payload in embedded signature")]
50 MissingPayload,
51 #[error("unexpected encryption key: have {0:x?}, want {1:x?}")]
52 UnexpectedEncryptionKey(xhpke::Fingerprint, xhpke::Fingerprint),
53 #[error("invalid encapsulated key size: {0}, expected {1}")]
54 InvalidEncapKeySize(usize, usize),
55 #[error("decryption failed: {0}")]
56 DecryptionFailed(String),
57}
58
59pub const ALGORITHM_ID_XDSA: i64 = -70000;
61
62pub const ALGORITHM_ID_XHPKE: i64 = -70001;
64
65pub fn sign_detached<A: Encode>(
77 msg_to_auth: A,
78 signer: &xdsa::SecretKey,
79 domain: &[u8],
80) -> Vec<u8> {
81 let timestamp = SystemTime::now()
82 .duration_since(UNIX_EPOCH)
83 .expect("system time before Unix epoch")
84 .as_secs() as i64;
85 sign_detached_at(msg_to_auth, signer, domain, timestamp)
86}
87
88pub fn sign<E: Encode, A: Encode>(
100 msg_to_embed: E,
101 msg_to_auth: A,
102 signer: &xdsa::SecretKey,
103 domain: &[u8],
104) -> Vec<u8> {
105 let timestamp = SystemTime::now()
106 .duration_since(UNIX_EPOCH)
107 .expect("system time before Unix epoch")
108 .as_secs() as i64;
109 sign_at(msg_to_embed, msg_to_auth, signer, domain, timestamp)
110}
111
112pub fn sign_detached_at<A: Encode>(
122 msg_to_auth: A,
123 signer: &xdsa::SecretKey,
124 domain: &[u8],
125 timestamp: i64,
126) -> Vec<u8> {
127 let info = [DOMAIN_PREFIX, domain].concat();
129 let aad = cbor::encode(&(&info, msg_to_auth));
130
131 let protected = cbor::encode(&SigProtectedHeader {
132 algorithm: ALGORITHM_ID_XDSA,
133 crit: CritHeader {
134 timestamp: HEADER_TIMESTAMP,
135 },
136 kid: signer.fingerprint(),
137 timestamp,
138 });
139 let signature = signer.sign(
141 &SigStructure {
142 context: "Signature1",
143 protected: &protected,
144 external_aad: &aad,
145 payload: &[],
146 }
147 .encode_cbor(),
148 );
149 cbor::encode(&CoseSign1 {
151 protected,
152 unprotected: EmptyHeader {},
153 payload: None,
154 signature,
155 })
156}
157
158pub fn sign_at<E: Encode, A: Encode>(
169 msg_to_embed: E,
170 msg_to_auth: A,
171 signer: &xdsa::SecretKey,
172 domain: &[u8],
173 timestamp: i64,
174) -> Vec<u8> {
175 let msg_to_embed = cbor::encode(msg_to_embed);
176
177 let info = [DOMAIN_PREFIX, domain].concat();
179 let aad = cbor::encode(&(&info, msg_to_auth));
180
181 let protected = cbor::encode(&SigProtectedHeader {
182 algorithm: ALGORITHM_ID_XDSA,
183 crit: CritHeader {
184 timestamp: HEADER_TIMESTAMP,
185 },
186 kid: signer.fingerprint(),
187 timestamp,
188 });
189 let signature = signer.sign(
191 &SigStructure {
192 context: "Signature1",
193 protected: &protected,
194 external_aad: &aad,
195 payload: &msg_to_embed,
196 }
197 .encode_cbor(),
198 );
199 cbor::encode(&CoseSign1 {
201 protected,
202 unprotected: EmptyHeader {},
203 payload: Some(msg_to_embed),
204 signature,
205 })
206}
207
208pub fn verify_detached<A: Encode>(
219 msg_to_check: &[u8],
220 msg_to_auth: A,
221 verifier: &xdsa::PublicKey,
222 domain: &[u8],
223 max_drift: Option<u64>,
224) -> Result<(), Error> {
225 let now = SystemTime::now()
226 .duration_since(UNIX_EPOCH)
227 .expect("system time before Unix epoch")
228 .as_secs() as i64;
229 verify_detached_at(msg_to_check, msg_to_auth, verifier, domain, max_drift, now)
230}
231
232pub fn verify_detached_at<A: Encode>(
242 msg_to_check: &[u8],
243 msg_to_auth: A,
244 verifier: &xdsa::PublicKey,
245 domain: &[u8],
246 max_drift: Option<u64>,
247 now: i64,
248) -> Result<(), Error> {
249 let info = [DOMAIN_PREFIX, domain].concat();
251 let aad = cbor::encode(&(&info, msg_to_auth));
252
253 let sign1: CoseSign1 = cbor::decode(msg_to_check)?;
255
256 if sign1.payload.is_some() {
258 return Err(Error::UnexpectedPayload);
259 }
260 let header = verify_sig_protected_header(&sign1.protected, ALGORITHM_ID_XDSA, verifier)?;
262
263 if let Some(max) = max_drift {
265 let drift = (now - header.timestamp).unsigned_abs();
266 if drift > max {
267 return Err(Error::StaleSignature(drift, max));
268 }
269 }
270 let blob = SigStructure {
272 context: "Signature1",
273 protected: &sign1.protected,
274 external_aad: &aad,
275 payload: &[],
276 }
277 .encode_cbor();
278
279 verifier
281 .verify(&blob, &sign1.signature)
282 .map_err(|e| Error::InvalidSignature(e.to_string()))?;
283
284 Ok(())
285}
286
287pub fn verify<E: Decode, A: Encode>(
300 msg_to_check: &[u8],
301 msg_to_auth: A,
302 verifier: &xdsa::PublicKey,
303 domain: &[u8],
304 max_drift: Option<u64>,
305) -> Result<E, Error> {
306 let now = SystemTime::now()
307 .duration_since(UNIX_EPOCH)
308 .expect("system time before Unix epoch")
309 .as_secs() as i64;
310 verify_at(msg_to_check, msg_to_auth, verifier, domain, max_drift, now)
311}
312
313pub fn verify_at<E: Decode, A: Encode>(
325 msg_to_check: &[u8],
326 msg_to_auth: A,
327 verifier: &xdsa::PublicKey,
328 domain: &[u8],
329 max_drift: Option<u64>,
330 now: i64,
331) -> Result<E, Error> {
332 let info = [DOMAIN_PREFIX, domain].concat();
334 let aad = cbor::encode(&(&info, msg_to_auth));
335
336 let sign1: CoseSign1 = cbor::decode(msg_to_check)?;
338
339 let payload = sign1.payload.ok_or(Error::MissingPayload)?;
341
342 let header = verify_sig_protected_header(&sign1.protected, ALGORITHM_ID_XDSA, verifier)?;
344
345 if let Some(max) = max_drift {
347 let drift = (now - header.timestamp).unsigned_abs();
348 if drift > max {
349 return Err(Error::StaleSignature(drift, max));
350 }
351 }
352 let blob = SigStructure {
354 context: "Signature1",
355 protected: &sign1.protected,
356 external_aad: &aad,
357 payload: &payload,
358 }
359 .encode_cbor();
360
361 verifier
363 .verify(&blob, &sign1.signature)
364 .map_err(|e| Error::InvalidSignature(e.to_string()))?;
365
366 Ok(cbor::decode(&payload)?)
367}
368
369pub fn signer(signature: &[u8]) -> Result<xdsa::Fingerprint, Error> {
379 let sign1: CoseSign1 = cbor::decode(signature)?;
380 let header: SigProtectedHeader = cbor::decode(&sign1.protected)?;
381 Ok(header.kid)
382}
383
384pub fn peek<E: Decode>(signature: &[u8]) -> Result<E, Error> {
395 let sign1: CoseSign1 = cbor::decode(signature)?;
396 let payload = sign1.payload.ok_or(Error::MissingPayload)?;
397 Ok(cbor::decode(&payload)?)
398}
399
400pub fn seal<E: Encode, A: Encode>(
413 msg_to_seal: E,
414 msg_to_auth: A,
415 signer: &xdsa::SecretKey,
416 recipient: &xhpke::PublicKey,
417 domain: &[u8],
418) -> Result<Vec<u8>, Error> {
419 let timestamp = SystemTime::now()
420 .duration_since(UNIX_EPOCH)
421 .expect("system time before Unix epoch")
422 .as_secs() as i64;
423 seal_at(
424 msg_to_seal,
425 msg_to_auth,
426 signer,
427 recipient,
428 domain,
429 timestamp,
430 )
431}
432
433pub fn seal_at<E: Encode, A: Encode>(
445 msg_to_seal: E,
446 msg_to_auth: A,
447 signer: &xdsa::SecretKey,
448 recipient: &xhpke::PublicKey,
449 domain: &[u8],
450 timestamp: i64,
451) -> Result<Vec<u8>, Error> {
452 let msg_to_seal = cbor::encode(msg_to_seal);
454 let msg_to_auth = cbor::encode(msg_to_auth);
455
456 let signed = sign_at(
458 Raw(msg_to_seal),
459 Raw(msg_to_auth.clone()),
460 signer,
461 domain,
462 timestamp,
463 );
464 encrypt(&signed, Raw(msg_to_auth), recipient, domain)
466}
467
468pub fn encrypt<A: Encode>(
481 sign1: &[u8],
482 msg_to_auth: A,
483 recipient: &xhpke::PublicKey,
484 domain: &[u8],
485) -> Result<Vec<u8>, Error> {
486 let msg_to_auth = cbor::encode(msg_to_auth);
488
489 let protected = cbor::encode(&EncProtectedHeader {
491 algorithm: ALGORITHM_ID_XHPKE,
492 kid: recipient.fingerprint(),
493 });
494 let info = [DOMAIN_PREFIX, domain].concat();
496
497 let (encap_key, ciphertext) = recipient
499 .seal(
500 sign1,
501 &EncStructure {
502 context: "Encrypt0",
503 protected: &protected,
504 external_aad: &msg_to_auth,
505 }
506 .encode_cbor(),
507 &info,
508 )
509 .map_err(|e| Error::DecryptionFailed(e.to_string()))?;
510
511 Ok(cbor::encode(&CoseEncrypt0 {
513 protected,
514 unprotected: EncapKeyHeader {
515 encap_key: encap_key.to_vec(),
516 },
517 ciphertext,
518 }))
519}
520
521pub fn open<E: Decode, A: Encode + Clone>(
535 msg_to_open: &[u8],
536 msg_to_auth: A,
537 recipient: &xhpke::SecretKey,
538 sender: &xdsa::PublicKey,
539 domain: &[u8],
540 max_drift: Option<u64>,
541) -> Result<E, Error> {
542 let now = SystemTime::now()
543 .duration_since(UNIX_EPOCH)
544 .expect("system time before Unix epoch")
545 .as_secs() as i64;
546 open_at(
547 msg_to_open,
548 msg_to_auth,
549 recipient,
550 sender,
551 domain,
552 max_drift,
553 now,
554 )
555}
556
557pub fn open_at<E: Decode, A: Encode + Clone>(
570 msg_to_open: &[u8],
571 msg_to_auth: A,
572 recipient: &xhpke::SecretKey,
573 sender: &xdsa::PublicKey,
574 domain: &[u8],
575 max_drift: Option<u64>,
576 now: i64,
577) -> Result<E, Error> {
578 let sign1 = decrypt(msg_to_open, msg_to_auth.clone(), recipient, domain)?;
580
581 let raw: Raw = verify_at::<Raw, _>(&sign1, &msg_to_auth, sender, domain, max_drift, now)?;
583 Ok(cbor::decode(&raw.0)?)
584}
585
586pub fn decrypt<A: Encode>(
598 msg_to_open: &[u8],
599 msg_to_auth: A,
600 recipient: &xhpke::SecretKey,
601 domain: &[u8],
602) -> Result<Vec<u8>, Error> {
603 let msg_to_auth = cbor::encode(msg_to_auth);
605
606 let info = [DOMAIN_PREFIX, domain].concat();
608
609 let encrypt0: CoseEncrypt0 = cbor::decode(msg_to_open)?;
611
612 verify_enc_protected_header(&encrypt0.protected, ALGORITHM_ID_XHPKE, recipient)?;
614
615 let encap_key: &[u8; xhpke::ENCAP_KEY_SIZE] = encrypt0
617 .unprotected
618 .encap_key
619 .as_slice()
620 .try_into()
621 .map_err(|_| {
622 Error::InvalidEncapKeySize(encrypt0.unprotected.encap_key.len(), xhpke::ENCAP_KEY_SIZE)
623 })?;
624
625 let decrypted = recipient
627 .open(
628 encap_key,
629 &encrypt0.ciphertext,
630 &EncStructure {
631 context: "Encrypt0",
632 protected: &encrypt0.protected,
633 external_aad: &msg_to_auth,
634 }
635 .encode_cbor(),
636 &info,
637 )
638 .map_err(|e| Error::DecryptionFailed(e.to_string()))?;
639
640 Ok(decrypted)
641}
642
643pub fn recipient(ciphertext: &[u8]) -> Result<xhpke::Fingerprint, Error> {
653 let encrypt0: CoseEncrypt0 = cbor::decode(ciphertext)?;
654 let header: EncProtectedHeader = cbor::decode(&encrypt0.protected)?;
655 Ok(header.kid)
656}
657
658fn verify_sig_protected_header(
661 bytes: &[u8],
662 exp_algo: i64,
663 verifier: &xdsa::PublicKey,
664) -> Result<SigProtectedHeader, Error> {
665 let header: SigProtectedHeader = cbor::decode(bytes)?;
666 if header.algorithm != exp_algo {
667 return Err(Error::UnexpectedAlgorithm(header.algorithm, exp_algo));
668 }
669 if header.crit.timestamp != HEADER_TIMESTAMP {
670 return Err(Error::UnexpectedAlgorithm(
671 header.crit.timestamp,
672 HEADER_TIMESTAMP,
673 ));
674 }
675 if header.kid != verifier.fingerprint() {
676 return Err(Error::UnexpectedSigningKey(
677 header.kid,
678 verifier.fingerprint(),
679 ));
680 }
681 Ok(header)
682}
683
684fn verify_enc_protected_header(
687 bytes: &[u8],
688 exp_algo: i64,
689 recipient: &xhpke::SecretKey,
690) -> Result<EncProtectedHeader, Error> {
691 let header: EncProtectedHeader = cbor::decode(bytes)?;
692 if header.algorithm != exp_algo {
693 return Err(Error::UnexpectedAlgorithm(header.algorithm, exp_algo));
694 }
695 if header.kid != recipient.fingerprint() {
696 return Err(Error::UnexpectedEncryptionKey(
697 header.kid,
698 recipient.fingerprint(),
699 ));
700 }
701 Ok(header)
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
710 fn test_sign_verify() {
711 struct TestCase {
712 msg_to_sign: &'static [u8],
713 msg_to_auth: &'static [u8],
714 verifier_msg_to_auth: &'static [u8],
715 domain: &'static [u8],
716 verifier_domain: &'static [u8],
717 timestamp: Option<i64>,
718 max_drift: Option<u64>,
719 wrong_key: bool,
720 want_ok: bool,
721 }
722 let now = SystemTime::now()
723 .duration_since(UNIX_EPOCH)
724 .unwrap()
725 .as_secs() as i64;
726
727 let tests = [
728 TestCase {
730 msg_to_sign: b"foo",
731 msg_to_auth: b"bar",
732 verifier_msg_to_auth: b"bar",
733 domain: b"baz",
734 verifier_domain: b"baz",
735 timestamp: None,
736 max_drift: None,
737 wrong_key: false,
738 want_ok: true,
739 },
740 TestCase {
742 msg_to_sign: b"foo",
743 msg_to_auth: b"",
744 verifier_msg_to_auth: b"",
745 domain: b"baz",
746 verifier_domain: b"baz",
747 timestamp: None,
748 max_drift: None,
749 wrong_key: false,
750 want_ok: true,
751 },
752 TestCase {
754 msg_to_sign: b"foo",
755 msg_to_auth: b"bar",
756 verifier_msg_to_auth: b"bar",
757 domain: b"baz",
758 verifier_domain: b"baz",
759 timestamp: Some(now),
760 max_drift: None,
761 wrong_key: false,
762 want_ok: true,
763 },
764 TestCase {
766 msg_to_sign: b"foo",
767 msg_to_auth: b"bar",
768 verifier_msg_to_auth: b"bar",
769 domain: b"baz",
770 verifier_domain: b"baz",
771 timestamp: Some(now - 30),
772 max_drift: Some(60),
773 wrong_key: false,
774 want_ok: true,
775 },
776 TestCase {
778 msg_to_sign: b"foo",
779 msg_to_auth: b"bar",
780 verifier_msg_to_auth: b"bar",
781 domain: b"baz",
782 verifier_domain: b"baz",
783 timestamp: Some(now - 120),
784 max_drift: Some(60),
785 wrong_key: false,
786 want_ok: false,
787 },
788 TestCase {
790 msg_to_sign: b"foo",
791 msg_to_auth: b"bar",
792 verifier_msg_to_auth: b"bar",
793 domain: b"baz",
794 verifier_domain: b"baz",
795 timestamp: Some(now + 120),
796 max_drift: Some(60),
797 wrong_key: false,
798 want_ok: false,
799 },
800 TestCase {
802 msg_to_sign: b"foo",
803 msg_to_auth: b"bar",
804 verifier_msg_to_auth: b"bar",
805 domain: b"baz",
806 verifier_domain: b"baz2",
807 timestamp: Some(now + 120),
808 max_drift: Some(60),
809 wrong_key: false,
810 want_ok: false,
811 },
812 TestCase {
814 msg_to_sign: b"foo",
815 msg_to_auth: b"bar",
816 verifier_msg_to_auth: b"bar2",
817 domain: b"baz",
818 verifier_domain: b"baz",
819 timestamp: None,
820 max_drift: None,
821 wrong_key: false,
822 want_ok: false,
823 },
824 TestCase {
826 msg_to_sign: b"foo",
827 msg_to_auth: b"",
828 verifier_msg_to_auth: b"",
829 domain: b"baz",
830 verifier_domain: b"baz",
831 timestamp: None,
832 max_drift: None,
833 wrong_key: true,
834 want_ok: false,
835 },
836 ];
837
838 for (i, test) in tests.iter().enumerate() {
839 let alice = xdsa::SecretKey::generate();
840 let bobby = xdsa::SecretKey::generate();
841
842 let signed = match test.timestamp {
843 Some(ts) => sign_at(
844 &test.msg_to_sign.to_vec(),
845 &test.msg_to_auth.to_vec(),
846 &alice,
847 test.domain,
848 ts,
849 ),
850 None => sign(
851 &test.msg_to_sign.to_vec(),
852 &test.msg_to_auth.to_vec(),
853 &alice,
854 test.domain,
855 ),
856 };
857 let verifier = if test.wrong_key {
858 bobby.public_key()
859 } else {
860 alice.public_key()
861 };
862 let result: Result<Vec<u8>, _> = verify(
863 &signed,
864 &test.verifier_msg_to_auth.to_vec(),
865 &verifier,
866 test.verifier_domain,
867 test.max_drift,
868 );
869
870 if test.want_ok {
871 let recovered = result.expect(&format!("test {}: expected success", i));
872 assert_eq!(recovered, test.msg_to_sign, "test {}: payload mismatch", i);
873 } else {
874 assert!(result.is_err(), "test {}: expected error", i);
875 }
876 }
877 }
878
879 #[test]
881 fn test_seal_open() {
882 struct TestCase {
883 msg_to_seal: &'static [u8],
884 msg_to_auth: &'static [u8],
885 opener_msg_to_auth: &'static [u8],
886 domain: &'static [u8],
887 opener_domain: &'static [u8],
888 timestamp: Option<i64>,
889 max_drift: Option<u64>,
890 wrong_signer: bool,
891 want_ok: bool,
892 }
893 let now = SystemTime::now()
895 .duration_since(UNIX_EPOCH)
896 .unwrap()
897 .as_secs() as i64;
898
899 let tests = [
900 TestCase {
902 msg_to_seal: b"foo",
903 msg_to_auth: b"bar",
904 opener_msg_to_auth: b"bar",
905 domain: b"baz",
906 opener_domain: b"baz",
907 timestamp: None,
908 max_drift: None,
909 wrong_signer: false,
910 want_ok: true,
911 },
912 TestCase {
914 msg_to_seal: b"foo",
915 msg_to_auth: b"",
916 opener_msg_to_auth: b"",
917 domain: b"baz",
918 opener_domain: b"baz",
919 timestamp: None,
920 max_drift: None,
921 wrong_signer: false,
922 want_ok: true,
923 },
924 TestCase {
926 msg_to_seal: b"foo",
927 msg_to_auth: b"bar",
928 opener_msg_to_auth: b"bar",
929 domain: b"baz",
930 opener_domain: b"baz",
931 timestamp: Some(now),
932 max_drift: None,
933 wrong_signer: false,
934 want_ok: true,
935 },
936 TestCase {
938 msg_to_seal: b"foo",
939 msg_to_auth: b"bar",
940 opener_msg_to_auth: b"bar",
941 domain: b"baz",
942 opener_domain: b"baz",
943 timestamp: Some(now - 30),
944 max_drift: Some(60),
945 wrong_signer: false,
946 want_ok: true,
947 },
948 TestCase {
950 msg_to_seal: b"foo",
951 msg_to_auth: b"",
952 opener_msg_to_auth: b"",
953 domain: b"baz",
954 opener_domain: b"baz2",
955 timestamp: None,
956 max_drift: None,
957 wrong_signer: false,
958 want_ok: false,
959 },
960 TestCase {
962 msg_to_seal: b"foo",
963 msg_to_auth: b"bar",
964 opener_msg_to_auth: b"bar2",
965 domain: b"baz",
966 opener_domain: b"baz",
967 timestamp: None,
968 max_drift: None,
969 wrong_signer: false,
970 want_ok: false,
971 },
972 TestCase {
974 msg_to_seal: b"foo",
975 msg_to_auth: b"",
976 opener_msg_to_auth: b"",
977 domain: b"baz",
978 opener_domain: b"baz",
979 timestamp: None,
980 max_drift: None,
981 wrong_signer: true,
982 want_ok: false,
983 },
984 TestCase {
986 msg_to_seal: b"foo",
987 msg_to_auth: b"bar",
988 opener_msg_to_auth: b"bar",
989 domain: b"baz",
990 opener_domain: b"baz",
991 timestamp: Some(now - 120),
992 max_drift: Some(60),
993 wrong_signer: false,
994 want_ok: false,
995 },
996 TestCase {
998 msg_to_seal: b"foo",
999 msg_to_auth: b"bar",
1000 opener_msg_to_auth: b"bar",
1001 domain: b"baz",
1002 opener_domain: b"baz",
1003 timestamp: Some(now + 120),
1004 max_drift: Some(60),
1005 wrong_signer: false,
1006 want_ok: false,
1007 },
1008 ];
1009
1010 for (i, test) in tests.iter().enumerate() {
1011 let alice = xdsa::SecretKey::generate();
1012 let bobby = xdsa::SecretKey::generate();
1013 let carol = xhpke::SecretKey::generate();
1014
1015 let sealed = match test.timestamp {
1016 Some(ts) => seal_at(
1017 &test.msg_to_seal.to_vec(),
1018 &test.msg_to_auth.to_vec(),
1019 &alice,
1020 &carol.public_key(),
1021 test.domain,
1022 ts,
1023 )
1024 .unwrap(),
1025 None => seal(
1026 &test.msg_to_seal.to_vec(),
1027 &test.msg_to_auth.to_vec(),
1028 &alice,
1029 &carol.public_key(),
1030 test.domain,
1031 )
1032 .unwrap(),
1033 };
1034
1035 let verifier = if test.wrong_signer {
1036 bobby.public_key()
1037 } else {
1038 alice.public_key()
1039 };
1040 let result: Result<Vec<u8>, _> = open(
1041 &sealed,
1042 &test.opener_msg_to_auth.to_vec(),
1043 &carol,
1044 &verifier,
1045 test.opener_domain,
1046 test.max_drift,
1047 );
1048
1049 if test.want_ok {
1050 let recovered = result.expect(&format!("test {}: expected success", i));
1051 assert_eq!(recovered, test.msg_to_seal, "test {}: payload mismatch", i);
1052 } else {
1053 assert!(result.is_err(), "test {}: expected error", i);
1054 }
1055 }
1056 }
1057
1058 #[test]
1060 fn test_sign_verify_typed() {
1061 let alice = xdsa::SecretKey::generate();
1062
1063 let payload = (42u64, "foo".to_string());
1064 let aad = ("bar".to_string(),);
1065
1066 let signed = sign(&payload, &aad, &alice, b"baz");
1067 let recovered: (u64, String) =
1068 verify(&signed, &aad, &alice.public_key(), b"baz", None).unwrap();
1069
1070 assert_eq!(recovered, payload);
1071 }
1072
1073 #[test]
1075 fn test_seal_open_typed() {
1076 let alice = xdsa::SecretKey::generate();
1077 let carol = xhpke::SecretKey::generate();
1078
1079 let payload = (123u64, "foo".to_string());
1080 let aad = ("bar".to_string(),);
1081
1082 let sealed = seal(&payload, &aad, &alice, &carol.public_key(), b"baz").unwrap();
1083 let recovered: (u64, String) =
1084 open(&sealed, &aad, &carol, &alice.public_key(), b"baz", None).unwrap();
1085
1086 assert_eq!(recovered, payload);
1087 }
1088}