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