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