pic_pca/
coset.rs

1/*
2 * Copyright Nitro Agility S.r.l.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! COSE_Sign1 signing for PIC payloads.
18//!
19//! Provides a generic `CoseSigned<T>` wrapper for signing and verifying
20//! any CBOR-serializable payload using COSE_Sign1 envelope (RFC 9052).
21//!
22//! PIC-specific extensions:
23//! - Challenge in protected header for freshness binding (PCC response)
24//! - kid used as key identifier (SPIFFE ID, DID, URL, etc.)
25
26use coset::{CborSerializable, CoseSign1, CoseSign1Builder, HeaderBuilder, Label, iana};
27use serde::{Serialize, de::DeserializeOwned};
28
29/// Custom COSE header label for PIC challenge.
30///
31/// Using -65537 which is in the private use range (values < -65536).
32/// This allows the challenge to be included in the protected header
33/// and covered by the COSE signature.
34pub const HEADER_CHALLENGE: i64 = -65537;
35
36/// Generic COSE_Sign1 signed envelope.
37///
38/// Wraps any serializable payload `T` with a COSE_Sign1 signature.
39#[derive(Debug, Clone)]
40pub struct CoseSigned<T> {
41    inner: CoseSign1,
42    _marker: std::marker::PhantomData<T>,
43}
44
45/// COSE signing and verification errors.
46#[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/// Supported COSE signing algorithms.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum SigningAlgorithm {
79    /// EdDSA with Ed25519
80    EdDSA,
81    /// ECDSA with P-256 and SHA-256
82    ES256,
83    /// ECDSA with P-384 and SHA-384
84    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    /// Returns the key identifier (kid) from the protected header.
112    ///
113    /// The kid can be a SPIFFE ID, DID, URL, or any resolvable identifier
114    /// that can be used to obtain the public key for verification.
115    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    /// Returns the issuer (kid) from the protected header.
125    ///
126    /// Alias for `kid()` for backward compatibility.
127    #[deprecated(since = "0.2.0", note = "Use kid() instead")]
128    pub fn issuer(&self) -> Option<String> {
129        self.kid()
130    }
131
132    /// Returns the signing algorithm from the protected header.
133    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    /// Returns the challenge from the protected header (if present).
149    ///
150    /// The challenge is used for freshness binding in PIC PoC.
151    /// It is included in the protected header and covered by the signature.
152    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    /// Serializes the signed envelope to CBOR bytes.
169    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    /// Deserializes a signed envelope from CBOR bytes.
177    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    /// Extracts the payload without verifying the signature.
187    ///
188    /// Use with caution: this bypasses signature verification.
189    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    /// Signs a payload using a custom signing function (crypto-agnostic).
201    ///
202    /// The closure receives the to-be-signed bytes and returns the signature.
203    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    /// Signs a payload with an optional challenge in the protected header.
216    ///
217    /// The challenge is included in the protected header and covered by the signature,
218    /// providing freshness binding for PIC PoC.
219    ///
220    /// # Arguments
221    /// * `payload` - The payload to sign
222    /// * `kid` - Key identifier (SPIFFE ID, DID, URL, etc.)
223    /// * `alg` - Signing algorithm
224    /// * `challenge` - Optional challenge bytes (PCC nonce)
225    /// * `sign_fn` - Signing function
226    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    /// Verifies the signature using a custom verification function (crypto-agnostic).
264    ///
265    /// The closure receives `(data, signature)` and returns `Ok(())` if valid.
266    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    /// Validates that the envelope's algorithm matches the expected one.
284    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        /// Signs payload with Ed25519. Algorithm is set to EdDSA automatically.
314        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        /// Signs payload with Ed25519 and an optional challenge.
323        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        /// Verifies Ed25519 signature and returns the payload.
336        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        /// Signs payload with P-256. Algorithm is set to ES256 automatically.
362        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        /// Signs payload with P-256 and an optional challenge.
371        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        /// Verifies P-256 signature and returns the payload.
384        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        /// Signs payload with P-384. Algorithm is set to ES384 automatically.
410        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        /// Signs payload with P-384 and an optional challenge.
419        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        /// Verifies P-384 signature and returns the payload.
432        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
449/// COSE-signed PCA payload.
450pub type SignedPca = CoseSigned<PcaPayload>;
451
452/// COSE-signed PoC payload.
453pub 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}