Skip to main content

ssi_cose/
lib.rs

1//! CBOR Object Signing and Encryption ([COSE]) implementation based on
2//! [`coset`].
3//!
4//! [COSE]: <https://datatracker.ietf.org/doc/html/rfc8152>
5//! [`coset`]: <https://crates.io/crates/coset>
6//!
7//! # Usage
8//!
9//! ```
10//! # #[async_std::main]
11//! # async fn main() {
12//! # #[cfg(feature = "secp256r1")] {
13//! use std::borrow::Cow;
14//! use serde::{Serialize, Deserialize};
15//! use ssi_claims_core::{VerifiableClaims, ValidateClaims, VerificationParameters};
16//! use ssi_cose::{CosePayload, ValidateCoseHeader, CoseSignatureBytes, DecodedCoseSign1, CoseKey, key::CoseKeyGenerate};
17//!
18//! // Our custom payload type.
19//! #[derive(Serialize, Deserialize)]
20//! struct CustomPayload {
21//!   data: String
22//! }
23//!
24//! // Define how the payload is encoded in COSE.
25//! impl CosePayload for CustomPayload {
26//!   // Serialize the payload as JSON.
27//!   fn payload_bytes(&self) -> Cow<[u8]> {
28//!     Cow::Owned(serde_json::to_vec(self).unwrap())
29//!   }
30//! }
31//!
32//! // Define how to validate the COSE header (always valid by default).
33//! impl<P> ValidateCoseHeader<P> for CustomPayload {}
34//!
35//! // Define how to validate the payload (always valid by default).
36//! impl<P> ValidateClaims<P, CoseSignatureBytes> for CustomPayload {}
37//!
38//! // Create a payload.
39//! let payload = CustomPayload {
40//!   data: "Some Data".to_owned()
41//! };
42//!
43//! // Create a signature key.
44//! let key = CoseKey::generate_p256(); // requires the `secp256r1` feature.
45//!
46//! // Sign the payload!
47//! let bytes = payload.sign(
48//!   &key,
49//!   true // should the `COSE_Sign1` object be tagged or not.
50//! ).await.unwrap();
51//!
52//! // Decode the signed COSE object.
53//! let decoded: DecodedCoseSign1<CustomPayload> = bytes
54//!     .decode(true)
55//!     .unwrap()
56//!     .try_map(|_, bytes| serde_json::from_slice(bytes))
57//!     .unwrap();
58//!
59//! assert_eq!(decoded.signing_bytes.payload.data, "Some Data");
60//!
61//! // Verify the signature.
62//! let params = VerificationParameters::from_resolver(&key);
63//! decoded.verify(&params).await.unwrap();
64//! # } }
65//! ```
66use ssi_claims_core::SignatureError;
67use std::borrow::Cow;
68
69pub use coset;
70pub use coset::{ContentType, CoseError, CoseKey, CoseSign1, Header, Label, ProtectedHeader};
71
72pub use ciborium;
73pub use ciborium::Value as CborValue;
74
75pub mod key;
76
77mod signature;
78pub use signature::*;
79
80mod verification;
81pub use verification::*;
82
83pub mod algorithm;
84
85mod sign1;
86pub use sign1::*;
87
88/// COSE payload.
89///
90/// This trait defines how a custom type can be encoded and signed using COSE.
91///
92/// # Example
93///
94/// ```
95/// use std::borrow::Cow;
96/// use serde::{Serialize, Deserialize};
97/// use ssi_cose::{CosePayload, CosePayloadType, ContentType};
98///
99/// // Our custom payload type.
100/// #[derive(Serialize, Deserialize)]
101/// struct CustomPayload {
102///   data: String
103/// }
104///
105/// // Define how the payload is encoded in COSE.
106/// impl CosePayload for CustomPayload {
107///   fn typ(&self) -> Option<CosePayloadType> {
108///     Some(CosePayloadType::Text(
109///       "application/json+cose".to_owned(),
110///     ))
111///   }
112///
113///   fn content_type(&self) -> Option<ContentType> {
114///     Some(ContentType::Text("application/json".to_owned()))
115///   }
116///
117///   // Serialize the payload as JSON.
118///   fn payload_bytes(&self) -> Cow<[u8]> {
119///     Cow::Owned(serde_json::to_vec(self).unwrap())
120///   }
121/// }
122/// ```
123pub trait CosePayload {
124    /// `typ` header parameter.
125    ///
126    /// See: <https://www.rfc-editor.org/rfc/rfc9596#section-2>
127    fn typ(&self) -> Option<CosePayloadType> {
128        None
129    }
130
131    /// Content type header parameter.
132    fn content_type(&self) -> Option<ContentType> {
133        None
134    }
135
136    /// Payload bytes.
137    ///
138    /// Returns the payload bytes representing this value.
139    fn payload_bytes(&'_ self) -> Cow<'_, [u8]>;
140
141    /// Sign the payload to produce a serialized `COSE_Sign1` object.
142    ///
143    /// The `tagged` flag specifies if the COSE object should be tagged or
144    /// not.
145    #[allow(async_fn_in_trait)]
146    async fn sign(
147        &self,
148        signer: impl CoseSigner,
149        tagged: bool,
150    ) -> Result<CoseSign1BytesBuf, SignatureError> {
151        signer.sign(self, None, tagged).await
152    }
153}
154
155impl CosePayload for [u8] {
156    fn payload_bytes(&'_ self) -> Cow<'_, [u8]> {
157        Cow::Borrowed(self)
158    }
159}
160
161pub const TYP_LABEL: Label = Label::Int(16);
162
163/// COSE payload type.
164///
165/// Value of the `typ` header parameter.
166///
167/// See: <https://www.rfc-editor.org/rfc/rfc9596#section-2>
168pub enum CosePayloadType {
169    UInt(u64),
170    Text(String),
171}
172
173impl From<CosePayloadType> for CborValue {
174    fn from(ty: CosePayloadType) -> Self {
175        match ty {
176            CosePayloadType::UInt(i) => Self::Integer(i.into()),
177            CosePayloadType::Text(t) => Self::Text(t),
178        }
179    }
180}
181
182/// COSE signature bytes.
183pub struct CoseSignatureBytes(pub Vec<u8>);
184
185impl CoseSignatureBytes {
186    pub fn into_bytes(self) -> Vec<u8> {
187        self.0
188    }
189}