1use coset::{CborSerializable, CoseSign1, CoseSign1Builder, HeaderBuilder, iana};
23use serde::{Serialize, de::DeserializeOwned};
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 =
145 CoseSign1::from_slice(bytes).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::{
290 Signature, SigningKey, VerifyingKey, signature::Signer, signature::Verifier,
291 };
292
293 impl<T> CoseSigned<T>
294 where
295 T: Serialize + DeserializeOwned,
296 {
297 pub fn sign_p256(
299 payload: &T,
300 issuer: &str,
301 signing_key: &SigningKey,
302 ) -> Result<Self, CoseError> {
303 Self::sign_with(payload, issuer, SigningAlgorithm::ES256, |data| {
304 let sig: Signature = signing_key.sign(data);
305 Ok(sig.to_bytes().to_vec())
306 })
307 }
308
309 pub fn verify_p256(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
311 self.check_algorithm(SigningAlgorithm::ES256)?;
312
313 self.verify_with(|data, sig| {
314 let signature =
315 Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
316
317 verifying_key
318 .verify(data, &signature)
319 .map_err(|_| CoseError::VerificationFailed)
320 })
321 }
322 }
323}
324
325#[cfg(feature = "p384")]
330mod p384_impl {
331 use super::*;
332 use p384::ecdsa::{
333 Signature, SigningKey, VerifyingKey, signature::Signer, signature::Verifier,
334 };
335
336 impl<T> CoseSigned<T>
337 where
338 T: Serialize + DeserializeOwned,
339 {
340 pub fn sign_p384(
342 payload: &T,
343 issuer: &str,
344 signing_key: &SigningKey,
345 ) -> Result<Self, CoseError> {
346 Self::sign_with(payload, issuer, SigningAlgorithm::ES384, |data| {
347 let sig: Signature = signing_key.sign(data);
348 Ok(sig.to_bytes().to_vec())
349 })
350 }
351
352 pub fn verify_p384(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
354 self.check_algorithm(SigningAlgorithm::ES384)?;
355
356 self.verify_with(|data, sig| {
357 let signature =
358 Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
359
360 verifying_key
361 .verify(data, &signature)
362 .map_err(|_| CoseError::VerificationFailed)
363 })
364 }
365 }
366}
367
368use crate::pca::PcaPayload;
373use crate::poc::PocPayload;
374
375pub type SignedPca = CoseSigned<PcaPayload>;
377
378pub type SignedPoc = CoseSigned<PocPayload>;
380
381#[cfg(test)]
386mod tests {
387 use super::*;
388 use crate::pca::{Executor, ExecutorBinding};
389
390 fn sample_pca() -> PcaPayload {
391 PcaPayload {
392 hop: "gateway".into(),
393 p_0: "https://idp.example.com/users/alice".into(),
394 ops: vec!["read:/user/*".into()],
395 executor: Executor {
396 binding: ExecutorBinding::new().with("org", "acme"),
397 },
398 provenance: None,
399 constraints: None,
400 }
401 }
402
403 #[test]
404 fn test_sign_with_and_verify_with() {
405 let pca = sample_pca();
406
407 let signed: SignedPca = CoseSigned::sign_with(
408 &pca,
409 "https://cat.example.com",
410 SigningAlgorithm::EdDSA,
411 |_data| Ok(vec![0xAB; 64]),
412 )
413 .unwrap();
414
415 assert_eq!(signed.issuer(), Some("https://cat.example.com".into()));
416 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::EdDSA));
417
418 let verified = signed.verify_with(|_data, _sig| Ok(())).unwrap();
419
420 assert_eq!(verified.hop, pca.hop);
421 assert_eq!(verified.p_0, pca.p_0);
422 }
423
424 #[test]
425 fn test_roundtrip_bytes() {
426 let pca = sample_pca();
427
428 let signed: SignedPca =
429 CoseSigned::sign_with(&pca, "issuer-1", SigningAlgorithm::ES256, |_| {
430 Ok(vec![0xCD; 64])
431 })
432 .unwrap();
433
434 let bytes = signed.to_bytes().unwrap();
435 let restored: SignedPca = CoseSigned::from_bytes(&bytes).unwrap();
436
437 assert_eq!(restored.issuer(), Some("issuer-1".into()));
438 }
439
440 #[test]
441 fn test_payload_unverified() {
442 let pca = sample_pca();
443
444 let signed: SignedPca =
445 CoseSigned::sign_with(&pca, "issuer", 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 =
502 CoseSigned::sign_with(&pca, "issuer", SigningAlgorithm::ES256, |_| {
503 Ok(vec![0x00; 64])
504 })
505 .unwrap();
506
507 let verifying_key = SigningKey::generate(&mut OsRng).verifying_key();
508 let result = signed.verify_ed25519(&verifying_key);
509
510 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
511 }
512
513 #[test]
514 #[cfg(feature = "p256")]
515 fn test_p256_sign_verify() {
516 use p256::ecdsa::SigningKey;
517 use rand::rngs::OsRng;
518
519 let pca = sample_pca();
520
521 let signing_key = SigningKey::random(&mut OsRng);
522 let verifying_key = signing_key.verifying_key();
523
524 let signed: SignedPca = CoseSigned::sign_p256(&pca, "p256-issuer", &signing_key).unwrap();
525
526 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::ES256));
527
528 let verified = signed.verify_p256(verifying_key).unwrap();
529
530 assert_eq!(verified.hop, pca.hop);
531 }
532
533 #[test]
534 #[cfg(feature = "p256")]
535 fn test_p256_wrong_key_fails() {
536 use p256::ecdsa::SigningKey;
537 use rand::rngs::OsRng;
538
539 let pca = sample_pca();
540
541 let signing_key = SigningKey::random(&mut OsRng);
542 let wrong_signing_key = SigningKey::random(&mut OsRng); let wrong_verifying_key = wrong_signing_key.verifying_key();
544
545 let signed: SignedPca = CoseSigned::sign_p256(&pca, "issuer", &signing_key).unwrap();
546
547 let result = signed.verify_p256(wrong_verifying_key);
548 assert!(result.is_err());
549 }
550
551 #[test]
552 #[cfg(feature = "p384")]
553 fn test_p384_sign_verify() {
554 use p384::ecdsa::SigningKey;
555 use rand::rngs::OsRng;
556
557 let pca = sample_pca();
558
559 let signing_key = SigningKey::random(&mut OsRng);
560 let verifying_key = signing_key.verifying_key();
561
562 let signed: SignedPca = CoseSigned::sign_p384(&pca, "p384-issuer", &signing_key).unwrap();
563
564 assert_eq!(signed.algorithm(), Some(SigningAlgorithm::ES384));
565
566 let verified = signed.verify_p384(verifying_key).unwrap();
567
568 assert_eq!(verified.hop, pca.hop);
569 }
570
571 #[test]
572 #[cfg(feature = "p384")]
573 fn test_p384_wrong_key_fails() {
574 use p384::ecdsa::SigningKey;
575 use rand::rngs::OsRng;
576
577 let pca = sample_pca();
578
579 let signing_key = SigningKey::random(&mut OsRng);
580 let wrong_signing_key = SigningKey::random(&mut OsRng); let wrong_verifying_key = wrong_signing_key.verifying_key();
582
583 let signed: SignedPca = CoseSigned::sign_p384(&pca, "issuer", &signing_key).unwrap();
584
585 let result = signed.verify_p384(wrong_verifying_key);
586 assert!(result.is_err());
587 }
588
589 #[test]
590 #[cfg(all(feature = "ed25519", feature = "p256"))]
591 fn test_cross_algorithm_ed25519_vs_p256() {
592 use ed25519_dalek::SigningKey as Ed25519SigningKey;
593 use p256::ecdsa::SigningKey as P256SigningKey;
594 use rand::rngs::OsRng;
595
596 let pca = sample_pca();
597
598 let ed_key = Ed25519SigningKey::generate(&mut OsRng);
599 let signed: SignedPca = CoseSigned::sign_ed25519(&pca, "issuer", &ed_key).unwrap();
600
601 let p256_key = P256SigningKey::random(&mut OsRng);
602 let result = signed.verify_p256(p256_key.verifying_key());
603
604 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
605 }
606
607 #[test]
608 #[cfg(all(feature = "ed25519", feature = "p384"))]
609 fn test_cross_algorithm_ed25519_vs_p384() {
610 use ed25519_dalek::SigningKey as Ed25519SigningKey;
611 use p384::ecdsa::SigningKey as P384SigningKey;
612 use rand::rngs::OsRng;
613
614 let pca = sample_pca();
615
616 let ed_key = Ed25519SigningKey::generate(&mut OsRng);
617 let signed: SignedPca = CoseSigned::sign_ed25519(&pca, "issuer", &ed_key).unwrap();
618
619 let p384_key = P384SigningKey::random(&mut OsRng);
620 let result = signed.verify_p384(p384_key.verifying_key());
621
622 assert!(matches!(result, Err(CoseError::AlgorithmMismatch { .. })));
623 }
624}