diff --git a/crates/claims/core/src/verification/claims.rs b/crates/claims/core/src/verification/claims.rs index 433c7b6cb..8e288e075 100644 --- a/crates/claims/core/src/verification/claims.rs +++ b/crates/claims/core/src/verification/claims.rs @@ -49,29 +49,15 @@ pub type ClaimsValidity = Result<(), InvalidClaims>; /// The `validate` function is also provided with the proof, as some claim type /// require information from the proof to be validated. pub trait ValidateClaims { - fn validate_claims(&self, environment: &E, proof: &P) -> ClaimsValidity; -} - -impl ValidateClaims for () { - fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity { + fn validate_claims(&self, _environment: &E, _proof: &P) -> ClaimsValidity { Ok(()) } } -impl ValidateClaims for [u8] { - fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity { - Ok(()) - } -} +impl ValidateClaims for () {} -impl ValidateClaims for Vec { - fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity { - Ok(()) - } -} +impl ValidateClaims for [u8] {} -impl<'a, E, P, T: ?Sized + ToOwned + ValidateClaims> ValidateClaims for Cow<'a, T> { - fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity { - Ok(()) - } -} +impl ValidateClaims for Vec {} + +impl<'a, E, P, T: ?Sized + ToOwned + ValidateClaims> ValidateClaims for Cow<'a, T> {} diff --git a/crates/claims/crates/cose/Cargo.toml b/crates/claims/crates/cose/Cargo.toml index 028a52c7a..f01f746d8 100644 --- a/crates/claims/crates/cose/Cargo.toml +++ b/crates/claims/crates/cose/Cargo.toml @@ -20,4 +20,10 @@ ssi-crypto.workspace = true ssi-claims-core.workspace = true thiserror.workspace = true ciborium.workspace = true -coset = { version = "0.3.8", features = ["std"] } \ No newline at end of file +coset = { version = "0.3.8", features = ["std"] } +serde = { workspace = true, features = ["derive"] } + +[dev-dependencies] +serde_json.workspace = true +hex.workspace = true +async-std = { workspace = true, features = ["attributes"] } \ No newline at end of file diff --git a/crates/claims/crates/cose/src/key.rs b/crates/claims/crates/cose/src/key.rs index 8106055ba..ea4b00eb1 100644 --- a/crates/claims/crates/cose/src/key.rs +++ b/crates/claims/crates/cose/src/key.rs @@ -102,13 +102,21 @@ impl From for ssi_claims_core::SignatureError { /// Decode COSE keys. pub trait CoseKeyDecode { + /// Reads a key parameter, if it exists. fn fetch_param(&self, label: &Label) -> Option<&ciborium::Value>; + /// Requires the given key parameter. + /// + /// Returns an error if the key parameter is not present in the key. fn require_param(&self, label: &Label) -> Result<&ciborium::Value, KeyDecodingError> { self.fetch_param(label) .ok_or_else(|| KeyDecodingError::MissingParam(label.clone())) } + /// Requires and parses the given key parameter. + /// + /// Returns an error if the key parameter is not present in the key, or + /// if the parsing function `f` returns `None`. fn parse_required_param<'a, T>( &'a self, label: &Label, @@ -203,7 +211,7 @@ impl CoseKeyDecode for CoseKey { })?; #[allow(unused_variables)] - let d = self.parse_required_param(&OKP_X, ciborium::Value::as_bytes)?; + let d = self.parse_required_param(&OKP_D, ciborium::Value::as_bytes)?; match iana::EllipticCurve::from_i64(crv) { #[cfg(feature = "ed25519")] @@ -264,14 +272,35 @@ pub enum KeyEncodingError { /// COSE key encoding pub trait CoseKeyEncode: Sized { - fn encode_public(key: &PublicKey) -> Result; + fn encode_public(key: &PublicKey) -> Result; - fn encode_secret(key: &SecretKey) -> Result; + fn encode_public_with_id(key: &PublicKey, id: Vec) -> Result { + let mut cose_key = Self::encode_public(key)?; + cose_key.key_id = id; + Ok(cose_key) + } + + fn encode_secret(key: &SecretKey) -> Result; + + fn encode_secret_with_id(key: &SecretKey, id: Vec) -> Result { + let mut cose_key = Self::encode_secret(key)?; + cose_key.key_id = id; + Ok(cose_key) + } } impl CoseKeyEncode for CoseKey { fn encode_public(key: &PublicKey) -> Result { match key { + #[cfg(feature = "ed25519")] + PublicKey::Ed25519(key) => Ok(Self { + kty: KeyType::Assigned(iana::KeyType::OKP), + params: vec![ + (OKP_CRV, iana::EllipticCurve::Ed25519.to_i64().into()), + (OKP_X, key.as_bytes().to_vec().into()), + ], + ..Default::default() + }), #[cfg(feature = "secp256k1")] PublicKey::Secp256k1(key) => { use ssi_crypto::k256::elliptic_curve::sec1::ToEncodedPoint; @@ -320,6 +349,19 @@ impl CoseKeyEncode for CoseKey { fn encode_secret(key: &SecretKey) -> Result { match key { + #[cfg(feature = "ed25519")] + SecretKey::Ed25519(key) => { + let public_key = key.verifying_key(); + Ok(Self { + kty: KeyType::Assigned(iana::KeyType::OKP), + params: vec![ + (OKP_CRV, iana::EllipticCurve::Ed25519.to_i64().into()), + (OKP_X, public_key.as_bytes().to_vec().into()), + (OKP_D, key.to_bytes().to_vec().into()), + ], + ..Default::default() + }) + } #[cfg(feature = "secp256k1")] SecretKey::Secp256k1(key) => { use ssi_crypto::k256::elliptic_curve::sec1::ToEncodedPoint; @@ -440,3 +482,251 @@ impl CoseKeyGenerate for CoseKey { Self::encode_secret(&ssi_crypto::SecretKey::generate_p384_from(rng)).unwrap() } } + +#[cfg(test)] +mod tests { + use super::{CoseKeyDecode, CoseKeyEncode}; + use coset::{CborSerializable, CoseKey}; + use ssi_crypto::{PublicKey, SecretKey}; + + /// Public secp256k1 key. + /// + /// ```cbor-diagnostic + /// { + /// 1: 1, + /// -1: 6, + /// -2: h'8816d41001dd1a9ddea1232381b2eede803161e88ebb19eaf573d393dec800a7' + /// } + /// ``` + #[cfg(feature = "ed25519")] + #[test] + fn public_ed25519_1() { + let input = hex::decode( + "a3010120062158208816d41001dd1a9ddea1232381b2eede803161e88ebb19eaf573d393dec800a7", + ) + .unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_public().unwrap(); + assert!(matches!(key, PublicKey::Ed25519(_))); + assert_eq!( + CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// Secret secp256k1 key. + /// + /// ```cbor-diagnostic + /// { + /// 1: 1, + /// -1: 6, + /// -2: h'8816d41001dd1a9ddea1232381b2eede803161e88ebb19eaf573d393dec800a7', + /// -4: h'e25df1249ab766fc5a8c9f98d5e311cd4f7d5fd1c6b6a2032adc973056c87dc3' + /// } + /// ``` + #[cfg(feature = "ed25519")] + #[test] + fn secret_ed25519_1() { + let input = hex::decode("a4010120062158208816d41001dd1a9ddea1232381b2eede803161e88ebb19eaf573d393dec800a7235820e25df1249ab766fc5a8c9f98d5e311cd4f7d5fd1c6b6a2032adc973056c87dc3").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_secret().unwrap(); + assert!(matches!(key, SecretKey::Ed25519(_))); + assert_eq!( + CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// Secret secp256k1 key. + /// + /// ```cbor-diagnostic + /// { + /// 1: 2, + /// -1: 8, + /// -2: h'394fd5a1e33b8a67d5fa9ddca42d261219dde202e65bbf07bf2f671e157ac41f', + /// -3: h'199d7db667e74905c8371168b815c267db76243fbfd387fa5f2d8a691099a89a' + /// } + /// ``` + #[cfg(feature = "secp256k1")] + #[test] + fn public_secp256k1_1() { + let input = hex::decode("a401022008215820394fd5a1e33b8a67d5fa9ddca42d261219dde202e65bbf07bf2f671e157ac41f225820199d7db667e74905c8371168b815c267db76243fbfd387fa5f2d8a691099a89a").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_public().unwrap(); + assert!(matches!(key, PublicKey::Secp256k1(_))); + assert_eq!( + CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// Secret secp256k1 key. + /// + /// ```cbor-diagnostic + /// { + /// 1: 2, + /// -1: 8, + /// -2: h'394fd5a1e33b8a67d5fa9ddca42d261219dde202e65bbf07bf2f671e157ac41f', + /// -3: h'199d7db667e74905c8371168b815c267db76243fbfd387fa5f2d8a691099a89a', + /// -4: h'3e0fada8be75e5e47ab4c1c91c3f8f9185d1e18a2a16b3400a1eb33c9cdf8b96' + /// } + /// ``` + #[cfg(feature = "secp256k1")] + #[test] + fn secret_secp256k1_1() { + let input = hex::decode("a501022008215820394fd5a1e33b8a67d5fa9ddca42d261219dde202e65bbf07bf2f671e157ac41f225820199d7db667e74905c8371168b815c267db76243fbfd387fa5f2d8a691099a89a2358203e0fada8be75e5e47ab4c1c91c3f8f9185d1e18a2a16b3400a1eb33c9cdf8b96").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_secret().unwrap(); + assert!(matches!(key, SecretKey::Secp256k1(_))); + assert_eq!( + CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// A public EC (P-256) key with a `kid` of + /// "meriadoc.brandybuck@buckland.example". + /// + /// See: + #[cfg(feature = "secp256r1")] + #[test] + fn public_p256_1() { + let input = hex::decode("a5200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c01020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_public().unwrap(); + assert!(matches!(key, PublicKey::P256(_))); + assert_eq!( + CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// A secret EC (P-256) key with a kid of + /// "meriadoc.brandybuck@buckland.example". + /// + /// See: + #[cfg(feature = "secp256r1")] + #[test] + fn secret_p256_1() { + let input = hex::decode("a601020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c235820aff907c99f9ad3aae6c4cdf21122bce2bd68b5283e6907154ad911840fa208cf").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_secret().unwrap(); + assert!(matches!(key, SecretKey::P256(_))); + assert_eq!( + CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// A public EC (P-256) key with a kid of "11". + /// + /// See: + #[cfg(feature = "secp256r1")] + #[test] + fn public_p256_2() { + let input = hex::decode("a52001215820bac5b11cad8f99f9c72b05cf4b9e26d244dc189f745228255a219a86d6a09eff22582020138bf82dc1b6d562be0fa54ab7804a3a64b6d72ccfed6b6fb6ed28bbfc117e010202423131").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_public().unwrap(); + assert!(matches!(key, PublicKey::P256(_))); + assert_eq!( + CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// A secret EC (P-256) key with a kid of "11". + /// + /// See: + #[cfg(feature = "secp256r1")] + #[test] + fn secret_p256_2() { + let input = hex::decode("a60102024231312001215820bac5b11cad8f99f9c72b05cf4b9e26d244dc189f745228255a219a86d6a09eff22582020138bf82dc1b6d562be0fa54ab7804a3a64b6d72ccfed6b6fb6ed28bbfc117e23582057c92077664146e876760c9520d054aa93c3afb04e306705db6090308507b4d3").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_secret().unwrap(); + assert!(matches!(key, SecretKey::P256(_))); + assert_eq!( + CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// A public EC (P-256) key with a kid of "peregrin.took@tuckborough.example". + /// + /// See: + #[cfg(feature = "secp256r1")] + #[test] + fn public_p256_3() { + let input = hex::decode("a5200121582098f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280225820f01400b089867804b8e9fc96c3932161f1934f4223069170d924b7e03bf822bb0102025821706572656772696e2e746f6f6b407475636b626f726f7567682e6578616d706c65").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_public().unwrap(); + assert!(matches!(key, PublicKey::P256(_))); + assert_eq!( + CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// A secret EC (P-256) key with a kid of + /// "peregrin.took@tuckborough.example". + /// + /// See: + #[cfg(feature = "secp256r1")] + #[test] + fn secret_256_3() { + let input = hex::decode("a601022001025821706572656772696e2e746f6f6b407475636b626f726f7567682e6578616d706c6521582098f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280225820f01400b089867804b8e9fc96c3932161f1934f4223069170d924b7e03bf822bb23582002d1f7e6f26c43d4868d87ceb2353161740aacf1f7163647984b522a848df1c3").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_secret().unwrap(); + assert!(matches!(key, SecretKey::P256(_))); + assert_eq!( + CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// A public EC (P-384) key. + /// + /// ```cbor-diagnostic + /// { + /// 1: 2, + /// -1: 2, + /// -2: h'fa1d31d39853d37fbfd145675635d52795f5feb3eacf11371ad8c6eb30c6f2493b0ec74d8c5b5a20ebf68ce3e0bd2c07', + /// -3: h'7c2b27b366e4fc73b79d28bac0b18ae2f2b0c4e7849656a71aac8987e60af5af57a9af3faf206afc798fa5fb06db15aa' + /// } + /// ``` + #[cfg(feature = "secp384r1")] + #[test] + fn public_p384_1() { + let input = hex::decode("a401022002215830fa1d31d39853d37fbfd145675635d52795f5feb3eacf11371ad8c6eb30c6f2493b0ec74d8c5b5a20ebf68ce3e0bd2c072258307c2b27b366e4fc73b79d28bac0b18ae2f2b0c4e7849656a71aac8987e60af5af57a9af3faf206afc798fa5fb06db15aa").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_public().unwrap(); + assert!(matches!(key, PublicKey::P384(_))); + assert_eq!( + CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } + + /// A secret EC (P-384) key. + /// + /// ```cbor-diagnostic + /// { + /// 1: 2, + /// -1: 2, + /// -2: h'fa1d31d39853d37fbfd145675635d52795f5feb3eacf11371ad8c6eb30c6f2493b0ec74d8c5b5a20ebf68ce3e0bd2c07', + /// -3: h'7c2b27b366e4fc73b79d28bac0b18ae2f2b0c4e7849656a71aac8987e60af5af57a9af3faf206afc798fa5fb06db15aa', + /// -4: h'21d8eb2250cdaa19bfb01f03211be11a70ef4739650ed954166531808aa254c1d6d968b36d16184d350600253fa672c0' + /// } + /// ``` + #[cfg(feature = "secp384r1")] + #[test] + fn secret_p384_1() { + let input = hex::decode("a501022002215830fa1d31d39853d37fbfd145675635d52795f5feb3eacf11371ad8c6eb30c6f2493b0ec74d8c5b5a20ebf68ce3e0bd2c072258307c2b27b366e4fc73b79d28bac0b18ae2f2b0c4e7849656a71aac8987e60af5af57a9af3faf206afc798fa5fb06db15aa23583021d8eb2250cdaa19bfb01f03211be11a70ef4739650ed954166531808aa254c1d6d968b36d16184d350600253fa672c0").unwrap(); + let cose_key = CoseKey::from_slice(&input).unwrap(); + let key = cose_key.decode_secret().unwrap(); + assert!(matches!(key, SecretKey::P384(_))); + assert_eq!( + CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(), + cose_key + ) + } +} diff --git a/crates/claims/crates/cose/src/lib.rs b/crates/claims/crates/cose/src/lib.rs index b81caf000..9e0105ea2 100644 --- a/crates/claims/crates/cose/src/lib.rs +++ b/crates/claims/crates/cose/src/lib.rs @@ -3,13 +3,69 @@ //! //! [COSE]: //! [`coset`]: +//! +//! # Usage +//! +//! ``` +//! # #[async_std::main] async fn main() { +//! use std::borrow::Cow; +//! use serde::{Serialize, Deserialize}; +//! use ssi_claims_core::{VerifiableClaims, ValidateClaims, VerificationParameters}; +//! use ssi_cose::{CosePayload, ValidateCoseHeader, CoseSignatureBytes, DecodedCoseSign1, CoseKey, key::CoseKeyGenerate}; +//! +//! // Our custom payload type. +//! #[derive(Serialize, Deserialize)] +//! struct CustomPayload { +//! data: String +//! } +//! +//! // Define how the payload is encoded in COSE. +//! impl CosePayload for CustomPayload { +//! // Serialize the payload as JSON. +//! fn payload_bytes(&self) -> Cow<[u8]> { +//! Cow::Owned(serde_json::to_vec(self).unwrap()) +//! } +//! } +//! +//! // Define how to validate the COSE header (always valid by default). +//! impl

ValidateCoseHeader

for CustomPayload {} +//! +//! // Define how to validate the payload (always valid by default). +//! impl

ValidateClaims for CustomPayload {} +//! +//! // Create a payload. +//! let payload = CustomPayload { +//! data: "Some Data".to_owned() +//! }; +//! +//! // Create a signature key. +//! let key = CoseKey::generate_p256(); // requires the `secp256r1` feature. +//! +//! // Sign the payload! +//! let bytes = payload.sign( +//! &key, +//! true // should the `COSE_Sign1` object be tagged or not. +//! ).await.unwrap(); +//! +//! // Decode the signed COSE object. +//! let decoded: DecodedCoseSign1 = bytes +//! .decode(true) +//! .unwrap() +//! .try_map(|_, bytes| serde_json::from_slice(bytes)) +//! .unwrap(); +//! +//! assert_eq!(decoded.signing_bytes.payload.data, "Some Data"); +//! +//! // Verify the signature. +//! let params = VerificationParameters::from_resolver(&key); +//! decoded.verify(¶ms).await.unwrap(); +//! # } +//! ``` use ssi_claims_core::SignatureError; use std::borrow::Cow; pub use coset; -pub use coset::{CoseError, CoseKey, CoseSign1, Header, Label, ProtectedHeader}; - -pub type ContentType = coset::RegisteredLabel; +pub use coset::{ContentType, CoseError, CoseKey, CoseSign1, Header, Label, ProtectedHeader}; pub use ciborium; pub use ciborium::Value as CborValue; @@ -28,6 +84,40 @@ mod sign1; pub use sign1::*; /// COSE payload. +/// +/// This trait defines how a custom type can be encoded and signed using COSE. +/// +/// # Example +/// +/// ``` +/// use std::borrow::Cow; +/// use serde::{Serialize, Deserialize}; +/// use ssi_cose::{CosePayload, CosePayloadType, ContentType}; +/// +/// // Our custom payload type. +/// #[derive(Serialize, Deserialize)] +/// struct CustomPayload { +/// data: String +/// } +/// +/// // Define how the payload is encoded in COSE. +/// impl CosePayload for CustomPayload { +/// fn typ(&self) -> Option { +/// Some(CosePayloadType::Text( +/// "application/json+cose".to_owned(), +/// )) +/// } +/// +/// fn content_type(&self) -> Option { +/// Some(ContentType::Text("application/json".to_owned())) +/// } +/// +/// // Serialize the payload as JSON. +/// fn payload_bytes(&self) -> Cow<[u8]> { +/// Cow::Owned(serde_json::to_vec(self).unwrap()) +/// } +/// } +/// ``` pub trait CosePayload { /// `typ` header parameter. /// @@ -36,13 +126,20 @@ pub trait CosePayload { None } + /// Content type header parameter. fn content_type(&self) -> Option { None } + /// Payload bytes. + /// + /// Returns the payload bytes representing this value. fn payload_bytes(&self) -> Cow<[u8]>; - /// Sign the payload to produce a `COSE_Sign1` object. + /// Sign the payload to produce a serialized `COSE_Sign1` object. + /// + /// The `tagged` flag specifies if the COSE object should be tagged or + /// not. #[allow(async_fn_in_trait)] async fn sign( &self, diff --git a/crates/claims/crates/cose/src/sign1.rs b/crates/claims/crates/cose/src/sign1.rs index ca970585a..067b0c421 100644 --- a/crates/claims/crates/cose/src/sign1.rs +++ b/crates/claims/crates/cose/src/sign1.rs @@ -3,13 +3,19 @@ use coset::{ sig_structure_data, CborSerializable, CoseError, CoseSign1, Header, ProtectedHeader, TaggedCborSerializable, }; +use serde::{Deserialize, Serialize}; use std::{borrow::Borrow, ops::Deref}; /// CBOR-encoded `COSE_Sign1` object. /// +/// This represents the raw CBOR bytes encoding a [`CoseSign1`] object. The +/// [`Self::decode`] method can be used to decode into a [`DecodedCoseSign1`] +/// (similar to `CoseSign1` but with extra information about the payload). +/// /// This is the borrowed equivalent of [`CompactCoseSign1Buf`]. -#[derive(Debug)] +#[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] +#[serde(transparent)] pub struct CompactCoseSign1([u8]); impl CompactCoseSign1 { @@ -17,11 +23,12 @@ impl CompactCoseSign1 { /// /// The bytes are not actually checked. If the bytes are not describing /// a CBOR-encoded `COSE_Sign1` object it will be detected when the - /// `decode` method is called. + /// [`Self::decode`] method is called. pub fn new(bytes: &[u8]) -> &Self { unsafe { std::mem::transmute(bytes) } } + /// Decodes the CBOR bytes into a [`DecodedCoseSign1`]. pub fn decode(&self, tagged: bool) -> Result { let cose = if tagged { CoseSign1::from_tagged_slice(&self.0)? @@ -32,6 +39,7 @@ impl CompactCoseSign1 { Ok(cose.into()) } + /// Returns the raw CBOR bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } @@ -53,14 +61,30 @@ impl ToOwned for CompactCoseSign1 { /// CBOR-encoded `COSE_Sign1` object buffer. /// +/// This represents the raw CBOR bytes encoding a [`CoseSign1`] object. The +/// [`CompactCoseSign1::decode`] method can be used to decode into a +/// [`DecodedCoseSign1`] (similar to `CoseSign1` but with extra information +/// about the payload). +/// /// This is the owned equivalent of [`CompactCoseSign1`]. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(transparent)] pub struct CompactCoseSign1Buf(Vec); impl CompactCoseSign1Buf { + /// Creates a new CBOR-encoded `COSE_Sign1` object from a byte buffer. + /// + /// The bytes are not actually checked. If the bytes are not describing + /// a CBOR-encoded `COSE_Sign1` object it will be detected when the + /// [`CompactCoseSign1::decode`] method is called. pub fn new(bytes: Vec) -> Self { Self(bytes) } + /// Creates a new CBOR-encoded `COSE_Sign1` object by encoding the give + /// [`CoseSign1`] value. + /// + /// If `tagged` is set to `true`, the CBOR value will be tagged. pub fn encode(object: impl Into, tagged: bool) -> Self { if tagged { Self(TaggedCborSerializable::to_tagged_vec(object.into()).unwrap()) @@ -69,6 +93,7 @@ impl CompactCoseSign1Buf { } } + /// Borrows the value as a [`CompactCoseSign1`]. pub fn as_compact(&self) -> &CompactCoseSign1 { CompactCoseSign1::new(self.0.as_slice()) } @@ -118,6 +143,10 @@ pub struct DecodedCoseSign1 { } impl DecodedCoseSign1 { + /// Maps the payload interpretation. + /// + /// This function can be used to decode the raw payload bytes into a + /// proper typed value the application can work with. pub fn map(self, f: impl FnOnce(T, &[u8]) -> U) -> DecodedCoseSign1 { DecodedCoseSign1 { signing_bytes: self.signing_bytes.map(f), @@ -125,6 +154,10 @@ impl DecodedCoseSign1 { } } + /// Tries to map the payload interpretation. + /// + /// This function can be used to decode the raw payload bytes into a + /// proper typed value the application can work with. pub fn try_map( self, f: impl FnOnce(T, &[u8]) -> Result, @@ -164,19 +197,31 @@ impl From> for CoseSign1 { /// /// Stores the payload value as interpreted by the application (type `T`) and /// the original payload bytes. +/// +/// The original payload bytes are always preserved since they can not always +/// be deterministically (or cheaply) reconstructed from the typed payload +/// value. #[derive(Clone, PartialEq)] pub struct PayloadBytes { + /// Original payload bytes. bytes: Vec, + + /// Interpretation of the payload bytes. value: T, } impl PayloadBytes { + /// Creates a new `PayloadBytes` from the bytes. + /// + /// The interpretation of the bytes will be unit `()`. pub fn from_bytes(bytes: Vec) -> Self { Self { bytes, value: () } } } impl PayloadBytes { + /// Creates a new `PayloadBytes` from the payload, using + /// [`CosePayload::payload_bytes`] to reconstruct the payload bytes. pub fn new(value: T) -> Self { Self { bytes: value.payload_bytes().into_owned(), @@ -186,10 +231,15 @@ impl PayloadBytes { } impl PayloadBytes { + /// Returns the bytes as a slice. pub fn as_bytes(&self) -> &[u8] { &self.bytes } + /// Maps the payload interpretation. + /// + /// This function can be used to decode the raw payload bytes into a + /// proper typed value the application can work with. pub fn map(self, f: impl FnOnce(T, &[u8]) -> U) -> PayloadBytes { let value = f(self.value, &self.bytes); PayloadBytes { @@ -198,6 +248,10 @@ impl PayloadBytes { } } + /// Tries to map the payload interpretation. + /// + /// This function can be used to decode the raw payload bytes into a + /// proper typed value the application can work with. pub fn try_map( self, f: impl FnOnce(T, &[u8]) -> Result, @@ -209,6 +263,7 @@ impl PayloadBytes { }) } + /// Forgets about the payload interpretation and returns the raw bytes. pub fn into_bytes(self) -> Vec { self.bytes } @@ -253,6 +308,7 @@ impl UnsignedCoseSign1 { ) } + /// Maps the payload interpretation. pub fn map(self, f: impl FnOnce(T, &[u8]) -> U) -> UnsignedCoseSign1 { UnsignedCoseSign1 { protected: self.protected, @@ -261,6 +317,7 @@ impl UnsignedCoseSign1 { } } + /// Tries to map the payload interpretation. pub fn try_map( self, f: impl FnOnce(T, &[u8]) -> Result, diff --git a/crates/claims/crates/cose/src/signature.rs b/crates/claims/crates/cose/src/signature.rs index eda7d8ae1..9fc01d400 100644 --- a/crates/claims/crates/cose/src/signature.rs +++ b/crates/claims/crates/cose/src/signature.rs @@ -15,13 +15,23 @@ pub struct CoseSignerInfo { } /// COSE signer. +/// +/// Any type with the ability to sign a COSE payload. pub trait CoseSigner { + /// Fetches the information about the signing key. + /// + /// This information will be included in the COSE header. #[allow(async_fn_in_trait)] async fn fetch_info(&self) -> Result; + /// Signs the given bytes. #[allow(async_fn_in_trait)] async fn sign_bytes(&self, signing_bytes: &[u8]) -> Result, SignatureError>; + /// Signs the given payload. + /// + /// Returns a serialized `COSE_Sign1` object, tagged or not according to + /// `tagged`. #[allow(async_fn_in_trait)] async fn sign( &self, diff --git a/crates/claims/crates/cose/src/verification.rs b/crates/claims/crates/cose/src/verification.rs index c6e897278..95eaf6c16 100644 --- a/crates/claims/crates/cose/src/verification.rs +++ b/crates/claims/crates/cose/src/verification.rs @@ -38,10 +38,12 @@ impl VerifiableClaims for DecodedCoseSign1 { pub trait ValidateCoseHeader

{ fn validate_cose_headers( &self, - params: &P, - protected: &ProtectedHeader, - unprotected: &Header, - ) -> ClaimsValidity; + _params: &P, + _protected: &ProtectedHeader, + _unprotected: &Header, + ) -> ClaimsValidity { + Ok(()) + } } impl ValidateClaims for UnsignedCoseSign1