1use ciborium::value::Integer;
2use coset::{CborSerializable, CoseSign1, iana::CoapContentFormat};
3use serde::{Serialize, de::DeserializeOwned};
4
5use super::{
6 SigningNamespace, content_type, message::SerializedMessage, namespace, signing_key::SigningKey,
7 verifying_key::VerifyingKey,
8};
9use crate::{
10 CoseSign1Bytes, CryptoError, KEY_ID_SIZE,
11 content_format::CoseSign1ContentFormat,
12 cose::{CoseSerializable, SIGNING_NAMESPACE},
13 error::{EncodingError, SignatureError},
14 keys::KeyId,
15};
16
17#[derive(Clone, Debug)]
22pub struct SignedObject(pub(crate) CoseSign1);
23
24impl From<CoseSign1> for SignedObject {
25 fn from(cose_sign1: CoseSign1) -> Self {
26 SignedObject(cose_sign1)
27 }
28}
29
30impl PartialEq for SignedObject {
31 fn eq(&self, other: &Self) -> bool {
32 self.to_cose() == other.to_cose()
33 }
34}
35
36impl SignedObject {
37 pub fn content_type(&self) -> Result<CoapContentFormat, CryptoError> {
40 content_type(&self.0.protected)
41 }
42
43 pub fn signed_by_id(&self) -> Result<KeyId, CryptoError> {
45 let key_id_bytes = &self.0.protected.header.key_id;
46 let key_id: [u8; KEY_ID_SIZE] = key_id_bytes
47 .clone()
48 .try_into()
49 .map_err(|_| SignatureError::InvalidSignature)?;
50 Ok(KeyId::from(key_id))
51 }
52
53 #[doc(hidden)]
57 pub fn dangerous_unverified_decode_do_not_use_except_for_debug_logs<
58 Message: DeserializeOwned,
59 >(
60 &self,
61 ) -> Option<Message> {
62 let payload = self.0.payload.as_deref()?;
63 let content_type = self.content_type().ok()?;
64 SerializedMessage::from_bytes(payload.to_vec(), content_type)
65 .decode()
66 .ok()
67 }
68
69 fn inner(&self) -> &CoseSign1 {
70 &self.0
71 }
72
73 fn namespace(&self) -> Result<SigningNamespace, CryptoError> {
74 namespace(&self.0.protected)
75 }
76
77 fn payload(&self) -> Result<Vec<u8>, CryptoError> {
78 self.0
79 .payload
80 .as_ref()
81 .ok_or(SignatureError::InvalidSignature.into())
82 .map(|payload| payload.to_vec())
83 }
84
85 pub fn verify_and_unwrap<Message: DeserializeOwned>(
88 &self,
89 verifying_key: &VerifyingKey,
90 namespace: &SigningNamespace,
91 ) -> Result<Message, CryptoError> {
92 SerializedMessage::from_bytes(
93 self.verify_and_unwrap_bytes(verifying_key, namespace)?,
94 self.content_type()?,
95 )
96 .decode()
97 .map_err(Into::into)
98 }
99
100 fn verify_and_unwrap_bytes(
103 &self,
104 verifying_key: &VerifyingKey,
105 namespace: &SigningNamespace,
106 ) -> Result<Vec<u8>, CryptoError> {
107 if self.inner().protected.header.alg.is_none() {
108 return Err(SignatureError::InvalidSignature.into());
109 }
110
111 if self.namespace()? != *namespace {
112 return Err(SignatureError::InvalidNamespace.into());
113 }
114
115 self.inner()
116 .verify_signature(&[], |sig, data| verifying_key.verify_raw(sig, data))?;
117 self.payload()
118 }
119}
120
121impl SigningKey {
122 fn sign_bytes(
126 &self,
127 serialized_message: &SerializedMessage,
128 namespace: &SigningNamespace,
129 ) -> Result<SignedObject, CryptoError> {
130 let cose_sign1 = coset::CoseSign1Builder::new()
131 .protected(
132 coset::HeaderBuilder::new()
133 .algorithm(self.cose_algorithm())
134 .key_id((&self.id).into())
135 .content_format(serialized_message.content_type())
136 .value(
137 SIGNING_NAMESPACE,
138 ciborium::Value::Integer(Integer::from(namespace.as_i64())),
139 )
140 .build(),
141 )
142 .payload(serialized_message.as_bytes().to_vec())
143 .create_signature(&[], |pt| self.sign_raw(pt))
144 .build();
145 Ok(SignedObject(cose_sign1))
146 }
147
148 pub fn sign<Message: Serialize>(
178 &self,
179 message: &Message,
180 namespace: &SigningNamespace,
181 ) -> Result<SignedObject, CryptoError> {
182 self.sign_bytes(&SerializedMessage::encode(message)?, namespace)
183 }
184}
185
186impl CoseSerializable<CoseSign1ContentFormat> for SignedObject {
187 fn from_cose(bytes: &CoseSign1Bytes) -> Result<Self, EncodingError> {
188 Ok(SignedObject(
189 CoseSign1::from_slice(bytes.as_ref())
190 .map_err(|_| EncodingError::InvalidCoseEncoding)?,
191 ))
192 }
193
194 fn to_cose(&self) -> CoseSign1Bytes {
195 self.0
196 .clone()
197 .to_vec()
198 .expect("SignedObject is always serializable")
199 .into()
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use serde::{Deserialize, Serialize};
206
207 use crate::{
208 CoseKeyBytes, CoseSerializable, CoseSign1Bytes, CryptoError, SignatureAlgorithm,
209 SignedObject, SigningKey, SigningNamespace, VerifyingKey,
210 };
211
212 const VERIFYING_KEY: &[u8] = &[
213 166, 1, 1, 2, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139, 94, 152, 45, 63, 13, 71,
214 3, 39, 4, 129, 2, 32, 6, 33, 88, 32, 93, 213, 35, 177, 81, 219, 226, 241, 147, 140, 238,
215 32, 34, 183, 213, 107, 227, 92, 75, 84, 208, 47, 198, 80, 18, 188, 172, 145, 184, 154, 26,
216 170,
217 ];
218 const SIGNED_OBJECT: &[u8] = &[
219 132, 88, 30, 164, 1, 39, 3, 24, 60, 4, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139,
220 94, 152, 45, 63, 13, 71, 58, 0, 1, 56, 127, 32, 160, 85, 161, 102, 102, 105, 101, 108, 100,
221 49, 108, 84, 101, 115, 116, 32, 109, 101, 115, 115, 97, 103, 101, 88, 64, 206, 83, 177,
222 184, 37, 103, 128, 39, 120, 174, 61, 4, 29, 184, 68, 46, 47, 203, 47, 246, 108, 160, 169,
223 114, 7, 165, 119, 198, 3, 209, 52, 249, 89, 31, 156, 255, 212, 75, 224, 78, 183, 37, 174,
224 63, 112, 70, 219, 246, 19, 213, 17, 121, 249, 244, 23, 182, 36, 193, 175, 55, 250, 65, 250,
225 6,
226 ];
227
228 #[derive(Deserialize, Debug, PartialEq, Serialize)]
229 struct TestMessage {
230 field1: String,
231 }
232
233 #[test]
234 fn test_roundtrip_cose() {
235 let signed_object =
236 SignedObject::from_cose(&<CoseSign1Bytes>::from(SIGNED_OBJECT)).unwrap();
237 assert_eq!(
238 signed_object.content_type().unwrap(),
239 coset::iana::CoapContentFormat::Cbor
240 );
241 let cose_bytes = signed_object.to_cose();
242 assert_eq!(cose_bytes, CoseSign1Bytes::from(SIGNED_OBJECT));
243 }
244
245 #[test]
246 fn test_verify_and_unwrap_testvector() {
247 let test_message = TestMessage {
248 field1: "Test message".to_string(),
249 };
250 let signed_object =
251 SignedObject::from_cose(&<CoseSign1Bytes>::from(SIGNED_OBJECT)).unwrap();
252 let verifying_key = VerifyingKey::from_cose(&<CoseKeyBytes>::from(VERIFYING_KEY)).unwrap();
253 let namespace = SigningNamespace::ExampleNamespace;
254 let payload: TestMessage = signed_object
255 .verify_and_unwrap(&verifying_key, &namespace)
256 .unwrap();
257 assert_eq!(payload, test_message);
258 }
259
260 #[test]
261 fn test_sign_verify_and_unwrap_roundtrip() {
262 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
263 let test_message = TestMessage {
264 field1: "Test message".to_string(),
265 };
266 let namespace = SigningNamespace::ExampleNamespace;
267 let signed_object = signing_key.sign(&test_message, &namespace).unwrap();
268 let verifying_key = signing_key.to_verifying_key();
269 let payload: TestMessage = signed_object
270 .verify_and_unwrap(&verifying_key, &namespace)
271 .unwrap();
272 assert_eq!(payload, test_message);
273 }
274
275 #[test]
276 fn test_fail_namespace_changed() {
277 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
278 let test_message = TestMessage {
279 field1: "Test message".to_string(),
280 };
281 let namespace = SigningNamespace::ExampleNamespace;
282 let signed_object = signing_key.sign(&test_message, &namespace).unwrap();
283 let verifying_key = signing_key.to_verifying_key();
284
285 let different_namespace = SigningNamespace::ExampleNamespace2;
286 let result: Result<TestMessage, CryptoError> =
287 signed_object.verify_and_unwrap(&verifying_key, &different_namespace);
288 assert!(result.is_err());
289 }
290}