1use coset::{CborSerializable, CoseSign1, CoseSign1Builder, HeaderBuilder, Label, iana};
27use serde::{Serialize, de::DeserializeOwned};
28
29pub const HEADER_CHALLENGE: i64 = -65537;
35
36#[derive(Debug, Clone)]
40pub struct CoseSigned<T> {
41 inner: CoseSign1,
42 _marker: std::marker::PhantomData<T>,
43}
44
45#[derive(Debug, thiserror::Error)]
47pub enum CoseError {
48 #[error("CBOR serialization failed: {0}")]
49 CborSerialize(String),
50
51 #[error("CBOR deserialization failed: {0}")]
52 CborDeserialize(String),
53
54 #[error("COSE serialization failed: {0}")]
55 CoseSerialize(String),
56
57 #[error("COSE deserialization failed: {0}")]
58 CoseDeserialize(String),
59
60 #[error("Signature verification failed")]
61 VerificationFailed,
62
63 #[error("Missing payload")]
64 MissingPayload,
65
66 #[error("Invalid key: {0}")]
67 InvalidKey(String),
68
69 #[error("Invalid signature length")]
70 InvalidSignatureLength,
71
72 #[error("Algorithm mismatch: expected {expected}, got {got}")]
73 AlgorithmMismatch { expected: String, got: String },
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum SigningAlgorithm {
79 EdDSA,
81 ES256,
83 ES384,
85}
86
87impl std::fmt::Display for SigningAlgorithm {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 SigningAlgorithm::EdDSA => write!(f, "EdDSA"),
91 SigningAlgorithm::ES256 => write!(f, "ES256"),
92 SigningAlgorithm::ES384 => write!(f, "ES384"),
93 }
94 }
95}
96
97impl SigningAlgorithm {
98 fn to_iana(self) -> iana::Algorithm {
99 match self {
100 SigningAlgorithm::EdDSA => iana::Algorithm::EdDSA,
101 SigningAlgorithm::ES256 => iana::Algorithm::ES256,
102 SigningAlgorithm::ES384 => iana::Algorithm::ES384,
103 }
104 }
105}
106
107impl<T> CoseSigned<T>
108where
109 T: Serialize + DeserializeOwned,
110{
111 pub fn kid(&self) -> Option<String> {
116 let kid = &self.inner.protected.header.key_id;
117 if kid.is_empty() {
118 None
119 } else {
120 String::from_utf8(kid.clone()).ok()
121 }
122 }
123
124 #[deprecated(since = "0.2.0", note = "Use kid() instead")]
128 pub fn issuer(&self) -> Option<String> {
129 self.kid()
130 }
131
132 pub fn algorithm(&self) -> Option<SigningAlgorithm> {
134 match self.inner.protected.header.alg {
135 Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::EdDSA)) => {
136 Some(SigningAlgorithm::EdDSA)
137 }
138 Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::ES256)) => {
139 Some(SigningAlgorithm::ES256)
140 }
141 Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::ES384)) => {
142 Some(SigningAlgorithm::ES384)
143 }
144 _ => None,
145 }
146 }
147
148 pub fn challenge(&self) -> Option<Vec<u8>> {
153 self.inner
154 .protected
155 .header
156 .rest
157 .iter()
158 .find_map(|(label, value)| {
159 if let Label::Int(HEADER_CHALLENGE) = label
160 && let ciborium::Value::Bytes(bytes) = value
161 {
162 return Some(bytes.clone());
163 }
164 None
165 })
166 }
167
168 pub fn to_bytes(&self) -> Result<Vec<u8>, CoseError> {
170 self.inner
171 .clone()
172 .to_vec()
173 .map_err(|e| CoseError::CoseSerialize(e.to_string()))
174 }
175
176 pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoseError> {
178 let inner =
179 CoseSign1::from_slice(bytes).map_err(|e| CoseError::CoseDeserialize(e.to_string()))?;
180 Ok(Self {
181 inner,
182 _marker: std::marker::PhantomData,
183 })
184 }
185
186 pub fn payload_unverified(&self) -> Result<T, CoseError> {
190 let payload = self
191 .inner
192 .payload
193 .as_ref()
194 .ok_or(CoseError::MissingPayload)?;
195
196 ciborium::from_reader(payload.as_slice())
197 .map_err(|e| CoseError::CborDeserialize(e.to_string()))
198 }
199
200 pub fn sign_with<F>(
204 payload: &T,
205 kid: &str,
206 alg: SigningAlgorithm,
207 sign_fn: F,
208 ) -> Result<Self, CoseError>
209 where
210 F: FnOnce(&[u8]) -> Result<Vec<u8>, CoseError>,
211 {
212 Self::sign_with_challenge(payload, kid, alg, None, sign_fn)
213 }
214
215 pub fn sign_with_challenge<F>(
227 payload: &T,
228 kid: &str,
229 alg: SigningAlgorithm,
230 challenge: Option<&[u8]>,
231 sign_fn: F,
232 ) -> Result<Self, CoseError>
233 where
234 F: FnOnce(&[u8]) -> Result<Vec<u8>, CoseError>,
235 {
236 let mut cbor_payload = Vec::new();
237 ciborium::into_writer(payload, &mut cbor_payload)
238 .map_err(|e| CoseError::CborSerialize(e.to_string()))?;
239
240 let mut header_builder = HeaderBuilder::new()
241 .algorithm(alg.to_iana())
242 .key_id(kid.as_bytes().to_vec());
243
244 if let Some(ch) = challenge {
245 header_builder =
246 header_builder.value(HEADER_CHALLENGE, ciborium::Value::Bytes(ch.to_vec()));
247 }
248
249 let protected = header_builder.build();
250
251 let sign1 = CoseSign1Builder::new()
252 .protected(protected)
253 .payload(cbor_payload)
254 .try_create_signature(&[], sign_fn)?
255 .build();
256
257 Ok(Self {
258 inner: sign1,
259 _marker: std::marker::PhantomData,
260 })
261 }
262
263 pub fn verify_with<F>(&self, verify_fn: F) -> Result<T, CoseError>
267 where
268 F: FnOnce(&[u8], &[u8]) -> Result<(), CoseError>,
269 {
270 self.inner
271 .verify_signature(&[], |sig, data| verify_fn(data, sig))?;
272
273 let payload = self
274 .inner
275 .payload
276 .as_ref()
277 .ok_or(CoseError::MissingPayload)?;
278
279 ciborium::from_reader(payload.as_slice())
280 .map_err(|e| CoseError::CborDeserialize(e.to_string()))
281 }
282
283 fn check_algorithm(&self, expected: SigningAlgorithm) -> Result<(), CoseError> {
285 let actual = self.algorithm();
286 if actual != Some(expected) {
287 return Err(CoseError::AlgorithmMismatch {
288 expected: expected.to_string(),
289 got: actual
290 .map(|a| a.to_string())
291 .unwrap_or_else(|| "None".to_string()),
292 });
293 }
294 Ok(())
295 }
296}
297
298impl From<coset::CoseError> for CoseError {
299 fn from(e: coset::CoseError) -> Self {
300 CoseError::CoseSerialize(format!("{:?}", e))
301 }
302}
303
304#[cfg(feature = "ed25519")]
305mod ed25519_impl {
306 use super::*;
307 use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
308
309 impl<T> CoseSigned<T>
310 where
311 T: Serialize + DeserializeOwned,
312 {
313 pub fn sign_ed25519(
315 payload: &T,
316 kid: &str,
317 signing_key: &SigningKey,
318 ) -> Result<Self, CoseError> {
319 Self::sign_ed25519_with_challenge(payload, kid, None, signing_key)
320 }
321
322 pub fn sign_ed25519_with_challenge(
324 payload: &T,
325 kid: &str,
326 challenge: Option<&[u8]>,
327 signing_key: &SigningKey,
328 ) -> Result<Self, CoseError> {
329 Self::sign_with_challenge(payload, kid, SigningAlgorithm::EdDSA, challenge, |data| {
330 let sig = signing_key.sign(data);
331 Ok(sig.to_bytes().to_vec())
332 })
333 }
334
335 pub fn verify_ed25519(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
337 self.check_algorithm(SigningAlgorithm::EdDSA)?;
338
339 self.verify_with(|data, sig| {
340 let signature =
341 Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
342 verifying_key
343 .verify(data, &signature)
344 .map_err(|_| CoseError::VerificationFailed)
345 })
346 }
347 }
348}
349
350#[cfg(feature = "p256")]
351mod p256_impl {
352 use super::*;
353 use p256::ecdsa::{
354 Signature, SigningKey, VerifyingKey, signature::Signer, signature::Verifier,
355 };
356
357 impl<T> CoseSigned<T>
358 where
359 T: Serialize + DeserializeOwned,
360 {
361 pub fn sign_p256(
363 payload: &T,
364 kid: &str,
365 signing_key: &SigningKey,
366 ) -> Result<Self, CoseError> {
367 Self::sign_p256_with_challenge(payload, kid, None, signing_key)
368 }
369
370 pub fn sign_p256_with_challenge(
372 payload: &T,
373 kid: &str,
374 challenge: Option<&[u8]>,
375 signing_key: &SigningKey,
376 ) -> Result<Self, CoseError> {
377 Self::sign_with_challenge(payload, kid, SigningAlgorithm::ES256, challenge, |data| {
378 let sig: Signature = signing_key.sign(data);
379 Ok(sig.to_bytes().to_vec())
380 })
381 }
382
383 pub fn verify_p256(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
385 self.check_algorithm(SigningAlgorithm::ES256)?;
386
387 self.verify_with(|data, sig| {
388 let signature =
389 Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
390 verifying_key
391 .verify(data, &signature)
392 .map_err(|_| CoseError::VerificationFailed)
393 })
394 }
395 }
396}
397
398#[cfg(feature = "p384")]
399mod p384_impl {
400 use super::*;
401 use p384::ecdsa::{
402 Signature, SigningKey, VerifyingKey, signature::Signer, signature::Verifier,
403 };
404
405 impl<T> CoseSigned<T>
406 where
407 T: Serialize + DeserializeOwned,
408 {
409 pub fn sign_p384(
411 payload: &T,
412 kid: &str,
413 signing_key: &SigningKey,
414 ) -> Result<Self, CoseError> {
415 Self::sign_p384_with_challenge(payload, kid, None, signing_key)
416 }
417
418 pub fn sign_p384_with_challenge(
420 payload: &T,
421 kid: &str,
422 challenge: Option<&[u8]>,
423 signing_key: &SigningKey,
424 ) -> Result<Self, CoseError> {
425 Self::sign_with_challenge(payload, kid, SigningAlgorithm::ES384, challenge, |data| {
426 let sig: Signature = signing_key.sign(data);
427 Ok(sig.to_bytes().to_vec())
428 })
429 }
430
431 pub fn verify_p384(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
433 self.check_algorithm(SigningAlgorithm::ES384)?;
434
435 self.verify_with(|data, sig| {
436 let signature =
437 Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
438 verifying_key
439 .verify(data, &signature)
440 .map_err(|_| CoseError::VerificationFailed)
441 })
442 }
443 }
444}
445
446use crate::pca::PcaPayload;
447use crate::poc::PocPayload;
448
449pub type SignedPca = CoseSigned<PcaPayload>;
451
452pub type SignedPoc = CoseSigned<PocPayload>;
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::pca::{Executor, ExecutorBinding};
459
460 fn sample_pca() -> PcaPayload {
461 PcaPayload {
462 hop: 0,
463 p_0: "https://idp.example.com/users/alice".into(),
464 ops: vec!["read:/user/*".into()],
465 executor: Executor {
466 binding: ExecutorBinding::new().with("org", "acme"),
467 },
468 provenance: None,
469 constraints: None,
470 }
471 }
472
473 #[test]
474 fn test_sign_with_and_verify_with() {
475 let pca = sample_pca();
476
477 let signed: SignedPca = CoseSigned::sign_with(
478 &pca,
479 "https://cat.example.com",
480 SigningAlgorithm::EdDSA,
481 |_data| Ok(vec![0xAB; 64]),
482 )
483 .unwrap();
484
485 assert_eq!(signed.kid(), Some("https://cat.example.com".into()));
486 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::EdDSA));
487 assert!(signed.challenge().is_none());
488
489 let verified = signed.verify_with(|_data, _sig| Ok(())).unwrap();
490
491 assert_eq!(verified.hop, pca.hop);
492 assert_eq!(verified.p_0, pca.p_0);
493 }
494
495 #[test]
496 fn test_sign_with_challenge() {
497 let pca = sample_pca();
498 let challenge = b"nonce12345";
499
500 let signed: SignedPca = CoseSigned::sign_with_challenge(
501 &pca,
502 "spiffe://example.com/service",
503 SigningAlgorithm::EdDSA,
504 Some(challenge),
505 |_data| Ok(vec![0xAB; 64]),
506 )
507 .unwrap();
508
509 assert_eq!(signed.kid(), Some("spiffe://example.com/service".into()));
510 assert_eq!(signed.challenge(), Some(challenge.to_vec()));
511
512 let verified = signed.verify_with(|_data, _sig| Ok(())).unwrap();
513 assert_eq!(verified.hop, pca.hop);
514 }
515
516 #[test]
517 fn test_challenge_none_when_not_provided() {
518 let pca = sample_pca();
519
520 let signed: SignedPca =
521 CoseSigned::sign_with(&pca, "issuer", SigningAlgorithm::EdDSA, |_| {
522 Ok(vec![0xAB; 64])
523 })
524 .unwrap();
525
526 assert!(signed.challenge().is_none());
527 }
528
529 #[test]
530 fn test_roundtrip_bytes_with_challenge() {
531 let pca = sample_pca();
532 let challenge = b"freshness-nonce";
533
534 let signed: SignedPca = CoseSigned::sign_with_challenge(
535 &pca,
536 "did:web:example.com",
537 SigningAlgorithm::ES256,
538 Some(challenge),
539 |_| Ok(vec![0xCD; 64]),
540 )
541 .unwrap();
542
543 let bytes = signed.to_bytes().unwrap();
544 let restored: SignedPca = CoseSigned::from_bytes(&bytes).unwrap();
545
546 assert_eq!(restored.kid(), Some("did:web:example.com".into()));
547 assert_eq!(restored.challenge(), Some(challenge.to_vec()));
548 }
549
550 #[test]
551 fn test_payload_unverified() {
552 let pca = sample_pca();
553
554 let signed: SignedPca =
555 CoseSigned::sign_with(&pca, "issuer", SigningAlgorithm::EdDSA, |_| {
556 Ok(vec![0x00; 64])
557 })
558 .unwrap();
559
560 let extracted = signed.payload_unverified().unwrap();
561 assert_eq!(extracted.hop, 0);
562 }
563
564 #[test]
565 #[cfg(feature = "ed25519")]
566 fn test_ed25519_sign_verify() {
567 use ed25519_dalek::SigningKey;
568 use rand::rngs::OsRng;
569
570 let pca = sample_pca();
571
572 let signing_key = SigningKey::generate(&mut OsRng);
573 let verifying_key = signing_key.verifying_key();
574
575 let signed: SignedPca =
576 CoseSigned::sign_ed25519(&pca, "ed25519-issuer", &signing_key).unwrap();
577
578 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::EdDSA));
579
580 let verified = signed.verify_ed25519(&verifying_key).unwrap();
581
582 assert_eq!(verified.hop, pca.hop);
583 assert_eq!(verified.p_0, pca.p_0);
584 }
585
586 #[test]
587 #[cfg(feature = "ed25519")]
588 fn test_ed25519_with_challenge() {
589 use ed25519_dalek::SigningKey;
590 use rand::rngs::OsRng;
591
592 let pca = sample_pca();
593 let challenge = b"pcc-nonce-abc123";
594
595 let signing_key = SigningKey::generate(&mut OsRng);
596 let verifying_key = signing_key.verifying_key();
597
598 let signed: SignedPca = CoseSigned::sign_ed25519_with_challenge(
599 &pca,
600 "spiffe://trust.example.com/ns/prod/sa/service-a",
601 Some(challenge),
602 &signing_key,
603 )
604 .unwrap();
605
606 assert_eq!(signed.challenge(), Some(challenge.to_vec()));
607
608 let verified = signed.verify_ed25519(&verifying_key).unwrap();
609 assert_eq!(verified.hop, pca.hop);
610 }
611
612 #[test]
613 #[cfg(feature = "ed25519")]
614 fn test_ed25519_wrong_key_fails() {
615 use ed25519_dalek::SigningKey;
616 use rand::rngs::OsRng;
617
618 let pca = sample_pca();
619
620 let signing_key = SigningKey::generate(&mut OsRng);
621 let wrong_verifying_key = SigningKey::generate(&mut OsRng).verifying_key();
622
623 let signed: SignedPca = CoseSigned::sign_ed25519(&pca, "issuer", &signing_key).unwrap();
624
625 let result = signed.verify_ed25519(&wrong_verifying_key);
626 assert!(result.is_err());
627 }
628
629 #[test]
630 #[cfg(feature = "ed25519")]
631 fn test_algorithm_mismatch() {
632 use ed25519_dalek::SigningKey;
633 use rand::rngs::OsRng;
634
635 let pca = sample_pca();
636
637 let signed: SignedPca =
638 CoseSigned::sign_with(&pca, "issuer", SigningAlgorithm::ES256, |_| {
639 Ok(vec![0x00; 64])
640 })
641 .unwrap();
642
643 let verifying_key = SigningKey::generate(&mut OsRng).verifying_key();
644 let result = signed.verify_ed25519(&verifying_key);
645
646 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
647 }
648
649 #[test]
650 #[cfg(feature = "p256")]
651 fn test_p256_sign_verify() {
652 use p256::ecdsa::SigningKey;
653 use rand::rngs::OsRng;
654
655 let pca = sample_pca();
656
657 let signing_key = SigningKey::random(&mut OsRng);
658 let verifying_key = signing_key.verifying_key();
659
660 let signed: SignedPca = CoseSigned::sign_p256(&pca, "p256-issuer", &signing_key).unwrap();
661
662 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::ES256));
663
664 let verified = signed.verify_p256(verifying_key).unwrap();
665
666 assert_eq!(verified.hop, pca.hop);
667 }
668
669 #[test]
670 #[cfg(feature = "p256")]
671 fn test_p256_with_challenge() {
672 use p256::ecdsa::SigningKey;
673 use rand::rngs::OsRng;
674
675 let pca = sample_pca();
676 let challenge = b"p256-challenge";
677
678 let signing_key = SigningKey::random(&mut OsRng);
679 let verifying_key = signing_key.verifying_key();
680
681 let signed: SignedPca = CoseSigned::sign_p256_with_challenge(
682 &pca,
683 "p256-issuer",
684 Some(challenge),
685 &signing_key,
686 )
687 .unwrap();
688
689 assert_eq!(signed.challenge(), Some(challenge.to_vec()));
690
691 let verified = signed.verify_p256(verifying_key).unwrap();
692 assert_eq!(verified.hop, pca.hop);
693 }
694
695 #[test]
696 #[cfg(feature = "p256")]
697 fn test_p256_wrong_key_fails() {
698 use p256::ecdsa::SigningKey;
699 use rand::rngs::OsRng;
700
701 let pca = sample_pca();
702
703 let signing_key = SigningKey::random(&mut OsRng);
704 let wrong_signing_key = SigningKey::random(&mut OsRng);
705 let wrong_verifying_key = wrong_signing_key.verifying_key();
706
707 let signed: SignedPca = CoseSigned::sign_p256(&pca, "issuer", &signing_key).unwrap();
708
709 let result = signed.verify_p256(wrong_verifying_key);
710 assert!(result.is_err());
711 }
712
713 #[test]
714 #[cfg(feature = "p384")]
715 fn test_p384_sign_verify() {
716 use p384::ecdsa::SigningKey;
717 use rand::rngs::OsRng;
718
719 let pca = sample_pca();
720
721 let signing_key = SigningKey::random(&mut OsRng);
722 let verifying_key = signing_key.verifying_key();
723
724 let signed: SignedPca = CoseSigned::sign_p384(&pca, "p384-issuer", &signing_key).unwrap();
725
726 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::ES384));
727
728 let verified = signed.verify_p384(verifying_key).unwrap();
729
730 assert_eq!(verified.hop, pca.hop);
731 }
732
733 #[test]
734 #[cfg(feature = "p384")]
735 fn test_p384_wrong_key_fails() {
736 use p384::ecdsa::SigningKey;
737 use rand::rngs::OsRng;
738
739 let pca = sample_pca();
740
741 let signing_key = SigningKey::random(&mut OsRng);
742 let wrong_signing_key = SigningKey::random(&mut OsRng);
743 let wrong_verifying_key = wrong_signing_key.verifying_key();
744
745 let signed: SignedPca = CoseSigned::sign_p384(&pca, "issuer", &signing_key).unwrap();
746
747 let result = signed.verify_p384(wrong_verifying_key);
748 assert!(result.is_err());
749 }
750
751 #[test]
752 #[cfg(all(feature = "ed25519", feature = "p256"))]
753 fn test_cross_algorithm_ed25519_vs_p256() {
754 use ed25519_dalek::SigningKey as Ed25519SigningKey;
755 use p256::ecdsa::SigningKey as P256SigningKey;
756 use rand::rngs::OsRng;
757
758 let pca = sample_pca();
759
760 let ed_key = Ed25519SigningKey::generate(&mut OsRng);
761 let signed: SignedPca = CoseSigned::sign_ed25519(&pca, "issuer", &ed_key).unwrap();
762
763 let p256_key = P256SigningKey::random(&mut OsRng);
764 let result = signed.verify_p256(p256_key.verifying_key());
765
766 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
767 }
768
769 #[test]
770 #[cfg(all(feature = "ed25519", feature = "p384"))]
771 fn test_cross_algorithm_ed25519_vs_p384() {
772 use ed25519_dalek::SigningKey as Ed25519SigningKey;
773 use p384::ecdsa::SigningKey as P384SigningKey;
774 use rand::rngs::OsRng;
775
776 let pca = sample_pca();
777
778 let ed_key = Ed25519SigningKey::generate(&mut OsRng);
779 let signed: SignedPca = CoseSigned::sign_ed25519(&pca, "issuer", &ed_key).unwrap();
780
781 let p384_key = P384SigningKey::random(&mut OsRng);
782 let result = signed.verify_p384(p384_key.verifying_key());
783
784 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
785 }
786}