bitwarden_crypto/signing/
signed_object.rs

1use ciborium::value::Integer;
2use coset::{iana::CoapContentFormat, CborSerializable, CoseSign1};
3use serde::{de::DeserializeOwned, Serialize};
4
5use super::{
6    content_type, message::SerializedMessage, namespace, signing_key::SigningKey,
7    verifying_key::VerifyingKey, SigningNamespace,
8};
9use crate::{
10    cose::{CoseSerializable, SIGNING_NAMESPACE},
11    error::{EncodingError, SignatureError},
12    CryptoError,
13};
14
15/// A signed object is a message containing a payload and signature that attests the payload's
16/// integrity and authenticity for a specific namespace and signature key. In order to gain access
17/// to the payload, the caller must provide the correct namespace and verifying key, ensuring that
18/// the caller cannot forget to validate the signature before using the payload.
19#[derive(Clone, Debug)]
20pub struct SignedObject(pub(crate) CoseSign1);
21
22impl From<CoseSign1> for SignedObject {
23    fn from(cose_sign1: CoseSign1) -> Self {
24        SignedObject(cose_sign1)
25    }
26}
27
28impl SignedObject {
29    /// Parses the signature headers and returns the content type of the signed data. The content
30    /// type indicates how the serialized message that was signed was encoded.
31    pub fn content_type(&self) -> Result<CoapContentFormat, CryptoError> {
32        content_type(&self.0.protected)
33    }
34
35    fn inner(&self) -> &CoseSign1 {
36        &self.0
37    }
38
39    fn namespace(&self) -> Result<SigningNamespace, CryptoError> {
40        namespace(&self.0.protected)
41    }
42
43    fn payload(&self) -> Result<Vec<u8>, CryptoError> {
44        self.0
45            .payload
46            .as_ref()
47            .ok_or(SignatureError::InvalidSignature.into())
48            .map(|payload| payload.to_vec())
49    }
50
51    /// Verifies the signature of the signed object and returns the payload, if the signature is
52    /// valid.
53    pub fn verify_and_unwrap<Message: DeserializeOwned>(
54        &self,
55        verifying_key: &VerifyingKey,
56        namespace: &SigningNamespace,
57    ) -> Result<Message, CryptoError> {
58        SerializedMessage::from_bytes(
59            self.verify_and_unwrap_bytes(verifying_key, namespace)?,
60            self.content_type()?,
61        )
62        .decode()
63        .map_err(Into::into)
64    }
65
66    /// Verifies the signature of the signed object and returns the payload as raw bytes, if the
67    /// signature is valid.
68    fn verify_and_unwrap_bytes(
69        &self,
70        verifying_key: &VerifyingKey,
71        namespace: &SigningNamespace,
72    ) -> Result<Vec<u8>, CryptoError> {
73        if self.inner().protected.header.alg.is_none() {
74            return Err(SignatureError::InvalidSignature.into());
75        }
76
77        if self.namespace()? != *namespace {
78            return Err(SignatureError::InvalidNamespace.into());
79        }
80
81        self.inner()
82            .verify_signature(&[], |sig, data| verifying_key.verify_raw(sig, data))?;
83        self.payload()
84    }
85}
86
87impl SigningKey {
88    /// Signs the given payload with the signing key, under a given namespace.
89    /// This is is the underlying implementation of the `sign` method, and takes
90    /// a raw byte array as input.
91    fn sign_bytes(
92        &self,
93        serialized_message: &SerializedMessage,
94        namespace: &SigningNamespace,
95    ) -> Result<SignedObject, CryptoError> {
96        let cose_sign1 = coset::CoseSign1Builder::new()
97            .protected(
98                coset::HeaderBuilder::new()
99                    .algorithm(self.cose_algorithm())
100                    .key_id((&self.id).into())
101                    .content_format(serialized_message.content_type())
102                    .value(
103                        SIGNING_NAMESPACE,
104                        ciborium::Value::Integer(Integer::from(namespace.as_i64())),
105                    )
106                    .build(),
107            )
108            .payload(serialized_message.as_bytes().to_vec())
109            .create_signature(&[], |pt| self.sign_raw(pt))
110            .build();
111        Ok(SignedObject(cose_sign1))
112    }
113
114    /// Signs the given payload with the signing key, under a given namespace.
115    /// This returns a [`SignedObject`] object, that contains the payload.
116    /// The payload is included in the signature, and does not need to be provided when verifying
117    /// the signature.
118    ///
119    /// This should be used when only one signer is required, so that only one object needs to be
120    /// kept track of.
121    /// ```
122    /// use bitwarden_crypto::{SigningNamespace, SignatureAlgorithm, SigningKey};
123    /// use serde::{Serialize, Deserialize};
124    ///
125    /// const EXAMPLE_NAMESPACE: SigningNamespace = SigningNamespace::SignedPublicKey;
126    ///
127    /// #[derive(Serialize, Deserialize, Debug, PartialEq)]
128    /// struct TestMessage {
129    ///   field1: String,
130    /// }
131    ///
132    /// let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
133    /// let message = TestMessage {
134    ///   field1: "Test message".to_string(),
135    /// };
136    /// let namespace = EXAMPLE_NAMESPACE;
137    /// let signed_object = signing_key.sign(&message, &namespace).unwrap();
138    /// // The signed object can be verified using the verifying key:
139    /// let verifying_key = signing_key.to_verifying_key();
140    /// let payload: TestMessage = signed_object.verify_and_unwrap(&verifying_key, &namespace).unwrap();
141    /// assert_eq!(payload, message);
142    /// ```
143    pub fn sign<Message: Serialize>(
144        &self,
145        message: &Message,
146        namespace: &SigningNamespace,
147    ) -> Result<SignedObject, CryptoError> {
148        self.sign_bytes(&SerializedMessage::encode(message)?, namespace)
149    }
150}
151
152impl CoseSerializable for SignedObject {
153    fn from_cose(bytes: &[u8]) -> Result<Self, EncodingError> {
154        Ok(SignedObject(
155            CoseSign1::from_slice(bytes).map_err(|_| EncodingError::InvalidCoseEncoding)?,
156        ))
157    }
158
159    fn to_cose(&self) -> Vec<u8> {
160        self.0
161            .clone()
162            .to_vec()
163            .expect("SignedObject is always serializable")
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use serde::{Deserialize, Serialize};
170
171    use crate::{
172        CoseSerializable, CryptoError, SignatureAlgorithm, SignedObject, SigningKey,
173        SigningNamespace, VerifyingKey,
174    };
175
176    const VERIFYING_KEY: &[u8] = &[
177        166, 1, 1, 2, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139, 94, 152, 45, 63, 13, 71,
178        3, 39, 4, 129, 2, 32, 6, 33, 88, 32, 93, 213, 35, 177, 81, 219, 226, 241, 147, 140, 238,
179        32, 34, 183, 213, 107, 227, 92, 75, 84, 208, 47, 198, 80, 18, 188, 172, 145, 184, 154, 26,
180        170,
181    ];
182    const SIGNED_OBJECT: &[u8] = &[
183        132, 88, 30, 164, 1, 39, 3, 24, 60, 4, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139,
184        94, 152, 45, 63, 13, 71, 58, 0, 1, 56, 127, 32, 160, 85, 161, 102, 102, 105, 101, 108, 100,
185        49, 108, 84, 101, 115, 116, 32, 109, 101, 115, 115, 97, 103, 101, 88, 64, 206, 83, 177,
186        184, 37, 103, 128, 39, 120, 174, 61, 4, 29, 184, 68, 46, 47, 203, 47, 246, 108, 160, 169,
187        114, 7, 165, 119, 198, 3, 209, 52, 249, 89, 31, 156, 255, 212, 75, 224, 78, 183, 37, 174,
188        63, 112, 70, 219, 246, 19, 213, 17, 121, 249, 244, 23, 182, 36, 193, 175, 55, 250, 65, 250,
189        6,
190    ];
191
192    #[derive(Deserialize, Debug, PartialEq, Serialize)]
193    struct TestMessage {
194        field1: String,
195    }
196
197    #[test]
198    fn test_roundtrip_cose() {
199        let signed_object = SignedObject::from_cose(SIGNED_OBJECT).unwrap();
200        assert_eq!(
201            signed_object.content_type().unwrap(),
202            coset::iana::CoapContentFormat::Cbor
203        );
204        let cose_bytes = signed_object.to_cose();
205        assert_eq!(cose_bytes, SIGNED_OBJECT);
206    }
207
208    #[test]
209    fn test_verify_and_unwrap_testvector() {
210        let test_message = TestMessage {
211            field1: "Test message".to_string(),
212        };
213        let signed_object = SignedObject::from_cose(SIGNED_OBJECT).unwrap();
214        let verifying_key = VerifyingKey::from_cose(VERIFYING_KEY).unwrap();
215        let namespace = SigningNamespace::ExampleNamespace;
216        let payload: TestMessage = signed_object
217            .verify_and_unwrap(&verifying_key, &namespace)
218            .unwrap();
219        assert_eq!(payload, test_message);
220    }
221
222    #[test]
223    fn test_sign_verify_and_unwrap_roundtrip() {
224        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
225        let test_message = TestMessage {
226            field1: "Test message".to_string(),
227        };
228        let namespace = SigningNamespace::ExampleNamespace;
229        let signed_object = signing_key.sign(&test_message, &namespace).unwrap();
230        let verifying_key = signing_key.to_verifying_key();
231        let payload: TestMessage = signed_object
232            .verify_and_unwrap(&verifying_key, &namespace)
233            .unwrap();
234        assert_eq!(payload, test_message);
235    }
236
237    #[test]
238    fn test_fail_namespace_changed() {
239        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
240        let test_message = TestMessage {
241            field1: "Test message".to_string(),
242        };
243        let namespace = SigningNamespace::ExampleNamespace;
244        let signed_object = signing_key.sign(&test_message, &namespace).unwrap();
245        let verifying_key = signing_key.to_verifying_key();
246
247        let different_namespace = SigningNamespace::ExampleNamespace2;
248        let result: Result<TestMessage, CryptoError> =
249            signed_object.verify_and_unwrap(&verifying_key, &different_namespace);
250        assert!(result.is_err());
251    }
252}