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(¶ms).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}