Skip to main content

bitwarden_crypto/signing/
signed_object.rs

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/// A signed object is a message containing a payload and signature that attests the payload's
18/// integrity and authenticity for a specific namespace and signature key. In order to gain access
19/// to the payload, the caller must provide the correct namespace and verifying key, ensuring that
20/// the caller cannot forget to validate the signature before using the payload.
21#[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    /// Parses the signature headers and returns the content type of the signed data. The content
38    /// type indicates how the serialized message that was signed was encoded.
39    pub fn content_type(&self) -> Result<CoapContentFormat, CryptoError> {
40        content_type(&self.0.protected)
41    }
42
43    /// Returns the signing key ID from the COSE protected header.
44    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    /// Deserializes the payload without verifying the signature.
54    /// This is intended for debug logging only, to debug structs without having to explicitly
55    /// verify their signature first. WARNING: YOU MUST NOT USE THIS EXCEPT FOR DEBUG LOGGING.
56    #[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    /// Verifies the signature of the signed object and returns the payload, if the signature is
86    /// valid.
87    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    /// Verifies the signature of the signed object and returns the payload as raw bytes, if the
101    /// signature is valid.
102    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    /// Signs the given payload with the signing key, under a given namespace.
123    /// This is is the underlying implementation of the `sign` method, and takes
124    /// a raw byte array as input.
125    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    /// Signs the given payload with the signing key, under a given namespace.
149    /// This returns a [`SignedObject`] object, that contains the payload.
150    /// The payload is included in the signature, and does not need to be provided when verifying
151    /// the signature.
152    ///
153    /// This should be used when only one signer is required, so that only one object needs to be
154    /// kept track of.
155    /// ```
156    /// use bitwarden_crypto::{SigningNamespace, SignatureAlgorithm, SigningKey};
157    /// use serde::{Serialize, Deserialize};
158    ///
159    /// const EXAMPLE_NAMESPACE: SigningNamespace = SigningNamespace::SignedPublicKey;
160    ///
161    /// #[derive(Serialize, Deserialize, Debug, PartialEq)]
162    /// struct TestMessage {
163    ///   field1: String,
164    /// }
165    ///
166    /// let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
167    /// let message = TestMessage {
168    ///   field1: "Test message".to_string(),
169    /// };
170    /// let namespace = EXAMPLE_NAMESPACE;
171    /// let signed_object = signing_key.sign(&message, &namespace).unwrap();
172    /// // The signed object can be verified using the verifying key:
173    /// let verifying_key = signing_key.to_verifying_key();
174    /// let payload: TestMessage = signed_object.verify_and_unwrap(&verifying_key, &namespace).unwrap();
175    /// assert_eq!(payload, message);
176    /// ```
177    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}