bitwarden_crypto/signing/
signature.rs

1use ciborium::value::Integer;
2use coset::{iana::CoapContentFormat, CborSerializable, CoseSign1};
3use serde::Serialize;
4
5use super::{
6    content_type, message::SerializedMessage, namespace, signing_key::SigningKey, SigningNamespace,
7    VerifyingKey,
8};
9use crate::{
10    content_format::CoseSign1ContentFormat,
11    cose::{CoseSerializable, SIGNING_NAMESPACE},
12    error::{EncodingError, SignatureError},
13    CoseSign1Bytes, CryptoError,
14};
15
16/// A signature cryptographically attests to a (namespace, data) pair. The namespace is included in
17/// the signature object, the data is not. One data object can be signed multiple times, with
18/// different namespaces / by different signers, depending on the application needs.
19pub struct Signature(CoseSign1);
20
21impl From<CoseSign1> for Signature {
22    fn from(cose_sign1: CoseSign1) -> Self {
23        Signature(cose_sign1)
24    }
25}
26
27impl Signature {
28    fn inner(&self) -> &CoseSign1 {
29        &self.0
30    }
31
32    fn namespace(&self) -> Result<SigningNamespace, CryptoError> {
33        namespace(&self.0.protected)
34    }
35
36    /// Parses the signature headers and returns the content type of the signed data. The content
37    /// type indicates how the serialized message that was signed was encoded.
38    pub fn content_type(&self) -> Result<CoapContentFormat, CryptoError> {
39        content_type(&self.0.protected)
40    }
41
42    /// Verifies the signature of the given serialized message bytes, created by
43    /// [`SigningKey::sign_detached`], for the given namespace. The namespace must match the one
44    /// used to create the signature.
45    ///
46    /// The first anticipated consumer will be signed org memberships / emergency access:
47    /// <https://bitwarden.atlassian.net/browse/PM-17458>
48    pub fn verify(
49        &self,
50        serialized_message_bytes: &[u8],
51        verifying_key: &VerifyingKey,
52        namespace: &SigningNamespace,
53    ) -> bool {
54        if self.inner().protected.header.alg.is_none() {
55            return false;
56        }
57
58        if self.namespace().ok().as_ref() != Some(namespace) {
59            return false;
60        }
61
62        self.inner()
63            .verify_detached_signature(serialized_message_bytes, &[], |sig, data| {
64                verifying_key.verify_raw(sig, data)
65            })
66            .is_ok()
67    }
68}
69
70impl SigningKey {
71    /// Signs the given payload with the signing key, under a given [`SigningNamespace`].
72    /// This returns a [`Signature`] object, that does not contain the payload.
73    /// The payload must be stored separately, and needs to be provided when verifying the
74    /// signature.
75    ///
76    /// This should be used when multiple signers are required, or when signatures need to be
77    /// replaceable without re-uploading the object, or if the signed object should be parseable
78    /// by the server side, without the use of COSE on the server.
79    /// ```
80    /// use bitwarden_crypto::{SigningNamespace, SignatureAlgorithm, SigningKey};
81    /// use serde::{Serialize, Deserialize};
82    ///
83    /// const EXAMPLE_NAMESPACE: SigningNamespace = SigningNamespace::SignedPublicKey;
84    ///
85    /// #[derive(Serialize, Deserialize, Debug, PartialEq)]
86    /// struct TestMessage {
87    ///  field1: String,
88    /// }
89    ///
90    /// let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
91    /// let message = TestMessage {
92    ///  field1: "Test message".to_string(),
93    /// };
94    /// let namespace = EXAMPLE_NAMESPACE;
95    /// let (signature, serialized_message) = signing_key.sign_detached(&message, &namespace).unwrap();
96    /// // Verification
97    /// let verifying_key = signing_key.to_verifying_key();
98    /// assert!(signature.verify(&serialized_message.as_bytes(), &verifying_key, &namespace));
99    /// ```
100    pub fn sign_detached<Message: Serialize>(
101        &self,
102        message: &Message,
103        namespace: &SigningNamespace,
104    ) -> Result<(Signature, SerializedMessage), CryptoError> {
105        let serialized_message = SerializedMessage::encode(message)?;
106        Ok((
107            self.sign_detached_bytes(&serialized_message, namespace),
108            serialized_message,
109        ))
110    }
111
112    /// Given a serialized message, signature, this counter-signs the message. That is, if multiple
113    /// parties want to sign the same message, one party creates the initial message, and the
114    /// other parties then counter-sign it, and submit their signatures. This can be done as
115    /// follows: ```
116    /// let alice_key = SigningKey::make(SignatureAlgorithm::Ed25519);
117    /// let bob_key = SigningKey::make(SignatureAlgorithm::Ed25519);
118    ///
119    /// let message = TestMessage {
120    ///    field1: "Test message".to_string(),
121    /// };
122    /// let namespace = SigningNamespace::ExampleNamespace;
123    /// let (signature, serialized_message) = alice_key.sign_detached(&message,
124    /// &namespace).unwrap();\ // Alice shares (signature, serialized_message) with Bob.
125    /// // Bob verifies the contents of serialized_message using application logic, then signs it:
126    /// let (bob_signature, serialized_message) = bob_key.counter_sign(&serialized_message,
127    /// &signature, &namespace).unwrap(); ```
128    pub fn counter_sign_detached(
129        &self,
130        serialized_message_bytes: Vec<u8>,
131        initial_signature: &Signature,
132        namespace: &SigningNamespace,
133    ) -> Result<Signature, CryptoError> {
134        // The namespace should be passed in to make sure the namespace the counter-signer is
135        // expecting to sign for is the same as the one that the signer used
136        if initial_signature.namespace()? != *namespace {
137            return Err(SignatureError::InvalidNamespace.into());
138        }
139
140        Ok(self.sign_detached_bytes(
141            &SerializedMessage::from_bytes(
142                serialized_message_bytes,
143                initial_signature.content_type()?,
144            ),
145            namespace,
146        ))
147    }
148
149    /// Signs the given payload with the signing key, under a given namespace.
150    /// This is is the underlying implementation of the `sign_detached` method, and takes
151    /// a raw byte array as input.
152    fn sign_detached_bytes(
153        &self,
154        message: &SerializedMessage,
155        namespace: &SigningNamespace,
156    ) -> Signature {
157        Signature::from(
158            coset::CoseSign1Builder::new()
159                .protected(
160                    coset::HeaderBuilder::new()
161                        .algorithm(self.cose_algorithm())
162                        .key_id((&self.id).into())
163                        .content_format(message.content_type())
164                        .value(
165                            SIGNING_NAMESPACE,
166                            ciborium::Value::Integer(Integer::from(namespace.as_i64())),
167                        )
168                        .build(),
169                )
170                .create_detached_signature(message.as_bytes(), &[], |pt| self.sign_raw(pt))
171                .build(),
172        )
173    }
174}
175
176impl CoseSerializable<CoseSign1ContentFormat> for Signature {
177    fn from_cose(bytes: &CoseSign1Bytes) -> Result<Self, EncodingError> {
178        let cose_sign1 = CoseSign1::from_slice(bytes.as_ref())
179            .map_err(|_| EncodingError::InvalidCoseEncoding)?;
180        Ok(Signature(cose_sign1))
181    }
182
183    fn to_cose(&self) -> CoseSign1Bytes {
184        self.0
185            .clone()
186            .to_vec()
187            .expect("Signature is always serializable")
188            .into()
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::{CoseKeyBytes, SignatureAlgorithm};
196
197    const VERIFYING_KEY: &[u8] = &[
198        166, 1, 1, 2, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139, 94, 152, 45, 63, 13, 71,
199        3, 39, 4, 129, 2, 32, 6, 33, 88, 32, 93, 213, 35, 177, 81, 219, 226, 241, 147, 140, 238,
200        32, 34, 183, 213, 107, 227, 92, 75, 84, 208, 47, 198, 80, 18, 188, 172, 145, 184, 154, 26,
201        170,
202    ];
203    const SIGNATURE: &[u8] = &[
204        132, 88, 30, 164, 1, 39, 3, 24, 60, 4, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139,
205        94, 152, 45, 63, 13, 71, 58, 0, 1, 56, 127, 32, 160, 246, 88, 64, 206, 83, 177, 184, 37,
206        103, 128, 39, 120, 174, 61, 4, 29, 184, 68, 46, 47, 203, 47, 246, 108, 160, 169, 114, 7,
207        165, 119, 198, 3, 209, 52, 249, 89, 31, 156, 255, 212, 75, 224, 78, 183, 37, 174, 63, 112,
208        70, 219, 246, 19, 213, 17, 121, 249, 244, 23, 182, 36, 193, 175, 55, 250, 65, 250, 6,
209    ];
210    const SERIALIZED_MESSAGE: &[u8] = &[
211        161, 102, 102, 105, 101, 108, 100, 49, 108, 84, 101, 115, 116, 32, 109, 101, 115, 115, 97,
212        103, 101,
213    ];
214
215    #[test]
216    fn test_cose_roundtrip_encode_signature() {
217        let signature = Signature::from_cose(&CoseSign1Bytes::from(SIGNATURE)).unwrap();
218        let cose_bytes = signature.to_cose();
219        let decoded_signature = Signature::from_cose(&cose_bytes).unwrap();
220        assert_eq!(signature.inner(), decoded_signature.inner());
221    }
222
223    #[test]
224    fn test_verify_testvector() {
225        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
226        let signature = Signature::from_cose(&CoseSign1Bytes::from(SIGNATURE)).unwrap();
227        let serialized_message =
228            SerializedMessage::from_bytes(SERIALIZED_MESSAGE.to_vec(), CoapContentFormat::Cbor);
229
230        let namespace = SigningNamespace::ExampleNamespace;
231
232        assert!(signature.verify(serialized_message.as_ref(), &verifying_key, &namespace));
233    }
234
235    #[test]
236    fn test_sign_detached_roundtrip() {
237        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
238        let message = "Test message";
239        let namespace = SigningNamespace::ExampleNamespace;
240
241        let (signature, serialized_message) =
242            signing_key.sign_detached(&message, &namespace).unwrap();
243
244        let verifying_key = signing_key.to_verifying_key();
245        assert!(signature.verify(serialized_message.as_ref(), &verifying_key, &namespace));
246    }
247
248    #[test]
249    fn test_countersign_detatched() {
250        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
251        let message = "Test message";
252        let namespace = SigningNamespace::ExampleNamespace;
253
254        let (signature, serialized_message) =
255            signing_key.sign_detached(&message, &namespace).unwrap();
256
257        let countersignature = signing_key
258            .counter_sign_detached(
259                serialized_message.as_bytes().to_vec(),
260                &signature,
261                &namespace,
262            )
263            .unwrap();
264
265        let verifying_key = signing_key.to_verifying_key();
266        assert!(countersignature.verify(serialized_message.as_ref(), &verifying_key, &namespace));
267    }
268
269    #[test]
270    fn test_fail_namespace_changed() {
271        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
272        let message = "Test message";
273        let namespace = SigningNamespace::ExampleNamespace;
274
275        let (signature, serialized_message) =
276            signing_key.sign_detached(&message, &namespace).unwrap();
277
278        let different_namespace = SigningNamespace::ExampleNamespace2;
279        let verifying_key = signing_key.to_verifying_key();
280
281        assert!(!signature.verify(
282            serialized_message.as_ref(),
283            &verifying_key,
284            &different_namespace
285        ));
286    }
287}