1use coset::{iana, CborSerializable, CoseSign1, CoseSign1Builder, HeaderBuilder};
23use serde::{de::DeserializeOwned, Serialize};
24
25#[derive(Debug, Clone)]
33pub struct CoseSigned<T> {
34 inner: CoseSign1,
35 _marker: std::marker::PhantomData<T>,
36}
37
38#[derive(Debug, thiserror::Error)]
40pub enum CoseError {
41 #[error("CBOR serialization failed: {0}")]
42 CborSerialize(String),
43
44 #[error("CBOR deserialization failed: {0}")]
45 CborDeserialize(String),
46
47 #[error("COSE serialization failed: {0}")]
48 CoseSerialize(String),
49
50 #[error("COSE deserialization failed: {0}")]
51 CoseDeserialize(String),
52
53 #[error("Signature verification failed")]
54 VerificationFailed,
55
56 #[error("Missing payload")]
57 MissingPayload,
58
59 #[error("Invalid key: {0}")]
60 InvalidKey(String),
61
62 #[error("Invalid signature length")]
63 InvalidSignatureLength,
64
65 #[error("Algorithm mismatch: expected {expected}, got {got}")]
66 AlgorithmMismatch { expected: String, got: String },
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum SigningAlgorithm {
72 EdDSA,
74 ES256,
76 ES384,
78}
79
80impl std::fmt::Display for SigningAlgorithm {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 match self {
83 SigningAlgorithm::EdDSA => write!(f, "EdDSA"),
84 SigningAlgorithm::ES256 => write!(f, "ES256"),
85 SigningAlgorithm::ES384 => write!(f, "ES384"),
86 }
87 }
88}
89
90impl SigningAlgorithm {
91 fn to_iana(&self) -> iana::Algorithm {
92 match self {
93 SigningAlgorithm::EdDSA => iana::Algorithm::EdDSA,
94 SigningAlgorithm::ES256 => iana::Algorithm::ES256,
95 SigningAlgorithm::ES384 => iana::Algorithm::ES384,
96 }
97 }
98}
99
100impl<T> CoseSigned<T>
105where
106 T: Serialize + DeserializeOwned,
107{
108 pub fn issuer(&self) -> Option<String> {
110 let kid = &self.inner.protected.header.key_id;
111 if kid.is_empty() {
112 None
113 } else {
114 String::from_utf8(kid.clone()).ok()
115 }
116 }
117
118 pub fn algorithm(&self) -> Option<SigningAlgorithm> {
120 match self.inner.protected.header.alg {
121 Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::EdDSA)) => {
122 Some(SigningAlgorithm::EdDSA)
123 }
124 Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::ES256)) => {
125 Some(SigningAlgorithm::ES256)
126 }
127 Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::ES384)) => {
128 Some(SigningAlgorithm::ES384)
129 }
130 _ => None,
131 }
132 }
133
134 pub fn to_bytes(&self) -> Result<Vec<u8>, CoseError> {
136 self.inner
137 .clone()
138 .to_vec()
139 .map_err(|e| CoseError::CoseSerialize(e.to_string()))
140 }
141
142 pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoseError> {
144 let inner = CoseSign1::from_slice(bytes)
145 .map_err(|e| CoseError::CoseDeserialize(e.to_string()))?;
146 Ok(Self {
147 inner,
148 _marker: std::marker::PhantomData,
149 })
150 }
151
152 pub fn payload_unverified(&self) -> Result<T, CoseError> {
156 let payload = self
157 .inner
158 .payload
159 .as_ref()
160 .ok_or(CoseError::MissingPayload)?;
161
162 ciborium::from_reader(payload.as_slice())
163 .map_err(|e| CoseError::CborDeserialize(e.to_string()))
164 }
165
166 pub fn sign_with<F>(
170 payload: &T,
171 issuer: &str,
172 alg: SigningAlgorithm,
173 sign_fn: F,
174 ) -> Result<Self, CoseError>
175 where
176 F: FnOnce(&[u8]) -> Result<Vec<u8>, CoseError>,
177 {
178 let mut cbor_payload = Vec::new();
179 ciborium::into_writer(payload, &mut cbor_payload)
180 .map_err(|e| CoseError::CborSerialize(e.to_string()))?;
181
182 let protected = HeaderBuilder::new()
183 .algorithm(alg.to_iana())
184 .key_id(issuer.as_bytes().to_vec())
185 .build();
186
187 let sign1 = CoseSign1Builder::new()
188 .protected(protected)
189 .payload(cbor_payload)
190 .try_create_signature(&[], sign_fn)?
191 .build();
192
193 Ok(Self {
194 inner: sign1,
195 _marker: std::marker::PhantomData,
196 })
197 }
198
199 pub fn verify_with<F>(&self, verify_fn: F) -> Result<T, CoseError>
203 where
204 F: FnOnce(&[u8], &[u8]) -> Result<(), CoseError>,
205 {
206 self.inner
208 .verify_signature(&[], |sig, data| verify_fn(data, sig))?;
209
210 let payload = self
211 .inner
212 .payload
213 .as_ref()
214 .ok_or(CoseError::MissingPayload)?;
215
216 ciborium::from_reader(payload.as_slice())
217 .map_err(|e| CoseError::CborDeserialize(e.to_string()))
218 }
219
220 fn check_algorithm(&self, expected: SigningAlgorithm) -> Result<(), CoseError> {
222 let actual = self.algorithm();
223 if actual != Some(expected) {
224 return Err(CoseError::AlgorithmMismatch {
225 expected: expected.to_string(),
226 got: actual
227 .map(|a| a.to_string())
228 .unwrap_or_else(|| "None".to_string()),
229 });
230 }
231 Ok(())
232 }
233}
234
235impl From<coset::CoseError> for CoseError {
236 fn from(e: coset::CoseError) -> Self {
237 CoseError::CoseSerialize(format!("{:?}", e))
238 }
239}
240
241#[cfg(feature = "ed25519")]
246mod ed25519_impl {
247 use super::*;
248 use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
249
250 impl<T> CoseSigned<T>
251 where
252 T: Serialize + DeserializeOwned,
253 {
254 pub fn sign_ed25519(
256 payload: &T,
257 issuer: &str,
258 signing_key: &SigningKey,
259 ) -> Result<Self, CoseError> {
260 Self::sign_with(payload, issuer, SigningAlgorithm::EdDSA, |data| {
261 let sig = signing_key.sign(data);
262 Ok(sig.to_bytes().to_vec())
263 })
264 }
265
266 pub fn verify_ed25519(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
268 self.check_algorithm(SigningAlgorithm::EdDSA)?;
269
270 self.verify_with(|data, sig| {
271 let signature =
272 Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
273
274 verifying_key
275 .verify(data, &signature)
276 .map_err(|_| CoseError::VerificationFailed)
277 })
278 }
279 }
280}
281
282#[cfg(feature = "p256")]
287mod p256_impl {
288 use super::*;
289 use p256::ecdsa::{signature::Signer, signature::Verifier, Signature, SigningKey, VerifyingKey};
290
291 impl<T> CoseSigned<T>
292 where
293 T: Serialize + DeserializeOwned,
294 {
295 pub fn sign_p256(
297 payload: &T,
298 issuer: &str,
299 signing_key: &SigningKey,
300 ) -> Result<Self, CoseError> {
301 Self::sign_with(payload, issuer, SigningAlgorithm::ES256, |data| {
302 let sig: Signature = signing_key.sign(data);
303 Ok(sig.to_bytes().to_vec())
304 })
305 }
306
307 pub fn verify_p256(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
309 self.check_algorithm(SigningAlgorithm::ES256)?;
310
311 self.verify_with(|data, sig| {
312 let signature =
313 Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
314
315 verifying_key
316 .verify(data, &signature)
317 .map_err(|_| CoseError::VerificationFailed)
318 })
319 }
320 }
321}
322
323#[cfg(feature = "p384")]
328mod p384_impl {
329 use super::*;
330 use p384::ecdsa::{signature::Signer, signature::Verifier, Signature, SigningKey, VerifyingKey};
331
332 impl<T> CoseSigned<T>
333 where
334 T: Serialize + DeserializeOwned,
335 {
336 pub fn sign_p384(
338 payload: &T,
339 issuer: &str,
340 signing_key: &SigningKey,
341 ) -> Result<Self, CoseError> {
342 Self::sign_with(payload, issuer, SigningAlgorithm::ES384, |data| {
343 let sig: Signature = signing_key.sign(data);
344 Ok(sig.to_bytes().to_vec())
345 })
346 }
347
348 pub fn verify_p384(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
350 self.check_algorithm(SigningAlgorithm::ES384)?;
351
352 self.verify_with(|data, sig| {
353 let signature =
354 Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
355
356 verifying_key
357 .verify(data, &signature)
358 .map_err(|_| CoseError::VerificationFailed)
359 })
360 }
361 }
362}
363
364use crate::pca::PcaPayload;
369use crate::poc::PocPayload;
370
371pub type SignedPca = CoseSigned<PcaPayload>;
373
374pub type SignedPoc = CoseSigned<PocPayload>;
376
377#[cfg(test)]
382mod tests {
383 use super::*;
384 use crate::pca::{Executor, ExecutorBinding};
385
386 fn sample_pca() -> PcaPayload {
387 PcaPayload {
388 hop: "gateway".into(),
389 p_0: "https://idp.example.com/users/alice".into(),
390 ops: vec!["read:/user/*".into()],
391 executor: Executor {
392 binding: ExecutorBinding::new().with("org", "acme"),
393 },
394 provenance: None,
395 constraints: None,
396 }
397 }
398
399 #[test]
400 fn test_sign_with_and_verify_with() {
401 let pca = sample_pca();
402
403 let signed: SignedPca = CoseSigned::sign_with(
404 &pca,
405 "https://cat.example.com",
406 SigningAlgorithm::EdDSA,
407 |_data| Ok(vec![0xAB; 64]),
408 )
409 .unwrap();
410
411 assert_eq!(signed.issuer(), Some("https://cat.example.com".into()));
412 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::EdDSA));
413
414 let verified = signed.verify_with(|_data, _sig| Ok(())).unwrap();
415
416 assert_eq!(verified.hop, pca.hop);
417 assert_eq!(verified.p_0, pca.p_0);
418 }
419
420 #[test]
421 fn test_roundtrip_bytes() {
422 let pca = sample_pca();
423
424 let signed: SignedPca = CoseSigned::sign_with(
425 &pca,
426 "issuer-1",
427 SigningAlgorithm::ES256,
428 |_| Ok(vec![0xCD; 64]),
429 )
430 .unwrap();
431
432 let bytes = signed.to_bytes().unwrap();
433 let restored: SignedPca = CoseSigned::from_bytes(&bytes).unwrap();
434
435 assert_eq!(restored.issuer(), Some("issuer-1".into()));
436 }
437
438 #[test]
439 fn test_payload_unverified() {
440 let pca = sample_pca();
441
442 let signed: SignedPca = CoseSigned::sign_with(
443 &pca,
444 "issuer",
445 SigningAlgorithm::EdDSA,
446 |_| Ok(vec![0x00; 64]),
447 )
448 .unwrap();
449
450 let extracted = signed.payload_unverified().unwrap();
451 assert_eq!(extracted.hop, "gateway");
452 }
453
454 #[test]
455 #[cfg(feature = "ed25519")]
456 fn test_ed25519_sign_verify() {
457 use ed25519_dalek::SigningKey;
458 use rand::rngs::OsRng;
459
460 let pca = sample_pca();
461
462 let signing_key = SigningKey::generate(&mut OsRng);
463 let verifying_key = signing_key.verifying_key();
464
465 let signed: SignedPca =
466 CoseSigned::sign_ed25519(&pca, "ed25519-issuer", &signing_key).unwrap();
467
468 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::EdDSA));
469
470 let verified = signed.verify_ed25519(&verifying_key).unwrap();
471
472 assert_eq!(verified.hop, pca.hop);
473 assert_eq!(verified.p_0, pca.p_0);
474 }
475
476 #[test]
477 #[cfg(feature = "ed25519")]
478 fn test_ed25519_wrong_key_fails() {
479 use ed25519_dalek::SigningKey;
480 use rand::rngs::OsRng;
481
482 let pca = sample_pca();
483
484 let signing_key = SigningKey::generate(&mut OsRng);
485 let wrong_verifying_key = SigningKey::generate(&mut OsRng).verifying_key();
486
487 let signed: SignedPca = CoseSigned::sign_ed25519(&pca, "issuer", &signing_key).unwrap();
488
489 let result = signed.verify_ed25519(&wrong_verifying_key);
490 assert!(result.is_err());
491 }
492
493 #[test]
494 #[cfg(feature = "ed25519")]
495 fn test_algorithm_mismatch() {
496 use ed25519_dalek::SigningKey;
497 use rand::rngs::OsRng;
498
499 let pca = sample_pca();
500
501 let signed: SignedPca = CoseSigned::sign_with(
502 &pca,
503 "issuer",
504 SigningAlgorithm::ES256,
505 |_| Ok(vec![0x00; 64]),
506 )
507 .unwrap();
508
509 let verifying_key = SigningKey::generate(&mut OsRng).verifying_key();
510 let result = signed.verify_ed25519(&verifying_key);
511
512 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
513 }
514
515 #[test]
516 #[cfg(feature = "p256")]
517 fn test_p256_sign_verify() {
518 use p256::ecdsa::SigningKey;
519 use rand::rngs::OsRng;
520
521 let pca = sample_pca();
522
523 let signing_key = SigningKey::random(&mut OsRng);
524 let verifying_key = signing_key.verifying_key();
525
526 let signed: SignedPca =
527 CoseSigned::sign_p256(&pca, "p256-issuer", &signing_key).unwrap();
528
529 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::ES256));
530
531 let verified = signed.verify_p256(verifying_key).unwrap();
532
533 assert_eq!(verified.hop, pca.hop);
534 }
535
536 #[test]
537 #[cfg(feature = "p256")]
538 fn test_p256_wrong_key_fails() {
539 use p256::ecdsa::SigningKey;
540 use rand::rngs::OsRng;
541
542 let pca = sample_pca();
543
544 let signing_key = SigningKey::random(&mut OsRng);
545 let wrong_verifying_key = SigningKey::random(&mut OsRng).verifying_key();
546
547 let signed: SignedPca = CoseSigned::sign_p256(&pca, "issuer", &signing_key).unwrap();
548
549 let result = signed.verify_p256(wrong_verifying_key);
550 assert!(result.is_err());
551 }
552
553 #[test]
554 #[cfg(feature = "p384")]
555 fn test_p384_sign_verify() {
556 use p384::ecdsa::SigningKey;
557 use rand::rngs::OsRng;
558
559 let pca = sample_pca();
560
561 let signing_key = SigningKey::random(&mut OsRng);
562 let verifying_key = signing_key.verifying_key();
563
564 let signed: SignedPca =
565 CoseSigned::sign_p384(&pca, "p384-issuer", &signing_key).unwrap();
566
567 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::ES384));
568
569 let verified = signed.verify_p384(verifying_key).unwrap();
570
571 assert_eq!(verified.hop, pca.hop);
572 }
573
574 #[test]
575 #[cfg(feature = "p384")]
576 fn test_p384_wrong_key_fails() {
577 use p384::ecdsa::SigningKey;
578 use rand::rngs::OsRng;
579
580 let pca = sample_pca();
581
582 let signing_key = SigningKey::random(&mut OsRng);
583 let wrong_verifying_key = SigningKey::random(&mut OsRng).verifying_key();
584
585 let signed: SignedPca = CoseSigned::sign_p384(&pca, "issuer", &signing_key).unwrap();
586
587 let result = signed.verify_p384(wrong_verifying_key);
588 assert!(result.is_err());
589 }
590
591 #[test]
592 #[cfg(all(feature = "ed25519", feature = "p256"))]
593 fn test_cross_algorithm_ed25519_vs_p256() {
594 use ed25519_dalek::SigningKey as Ed25519SigningKey;
595 use p256::ecdsa::SigningKey as P256SigningKey;
596 use rand::rngs::OsRng;
597
598 let pca = sample_pca();
599
600 let ed_key = Ed25519SigningKey::generate(&mut OsRng);
601 let signed: SignedPca = CoseSigned::sign_ed25519(&pca, "issuer", &ed_key).unwrap();
602
603 let p256_key = P256SigningKey::random(&mut OsRng);
604 let result = signed.verify_p256(p256_key.verifying_key());
605
606 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
607 }
608
609 #[test]
610 #[cfg(all(feature = "ed25519", feature = "p384"))]
611 fn test_cross_algorithm_ed25519_vs_p384() {
612 use ed25519_dalek::SigningKey as Ed25519SigningKey;
613 use p384::ecdsa::SigningKey as P384SigningKey;
614 use rand::rngs::OsRng;
615
616 let pca = sample_pca();
617
618 let ed_key = Ed25519SigningKey::generate(&mut OsRng);
619 let signed: SignedPca = CoseSigned::sign_ed25519(&pca, "issuer", &ed_key).unwrap();
620
621 let p384_key = P384SigningKey::random(&mut OsRng);
622 let result = signed.verify_p384(p384_key.verifying_key());
623
624 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
625 }
626}