bitwarden_crypto/signing/
signing_key.rs

1use std::pin::Pin;
2
3use ciborium::{value::Integer, Value};
4use coset::{
5    iana::{Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyType, OkpKeyParameter},
6    CborSerializable, CoseKey, RegisteredLabel, RegisteredLabelWithPrivate,
7};
8use ed25519_dalek::Signer;
9
10use super::{
11    ed25519_signing_key, key_id,
12    verifying_key::{RawVerifyingKey, VerifyingKey},
13    SignatureAlgorithm,
14};
15use crate::{
16    content_format::CoseKeyContentFormat,
17    cose::CoseSerializable,
18    error::{EncodingError, Result},
19    keys::KeyId,
20    CoseKeyBytes, CryptoKey,
21};
22
23/// A `SigningKey` without the key id. This enum contains a variant for each supported signature
24/// scheme.
25#[derive(Clone)]
26enum RawSigningKey {
27    Ed25519(Pin<Box<ed25519_dalek::SigningKey>>),
28}
29
30/// A signing key is a private key used for signing data. An associated `VerifyingKey` can be
31/// derived from it.
32#[derive(Clone)]
33pub struct SigningKey {
34    pub(super) id: KeyId,
35    inner: RawSigningKey,
36}
37
38// Note that `SigningKey` already implements ZeroizeOnDrop, so we don't need to do anything
39// We add this assertion to make sure that this is still true in the future
40// For any new keys, this needs to be checked
41const _: () = {
42    fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>() {}
43    fn assert_all() {
44        assert_zeroize_on_drop::<ed25519_dalek::SigningKey>();
45    }
46};
47impl zeroize::ZeroizeOnDrop for SigningKey {}
48impl CryptoKey for SigningKey {}
49
50impl SigningKey {
51    /// Makes a new signing key for the given signature scheme.
52    pub fn make(algorithm: SignatureAlgorithm) -> Self {
53        match algorithm {
54            SignatureAlgorithm::Ed25519 => SigningKey {
55                id: KeyId::make(),
56                inner: RawSigningKey::Ed25519(Box::pin(ed25519_dalek::SigningKey::generate(
57                    &mut rand::thread_rng(),
58                ))),
59            },
60        }
61    }
62
63    pub(super) fn cose_algorithm(&self) -> Algorithm {
64        match &self.inner {
65            RawSigningKey::Ed25519(_) => Algorithm::EdDSA,
66        }
67    }
68
69    /// Derives the verifying key from the signing key. The key id is the same for the signing and
70    /// verifying key, since they are a pair.
71    pub fn to_verifying_key(&self) -> VerifyingKey {
72        match &self.inner {
73            RawSigningKey::Ed25519(key) => VerifyingKey {
74                id: self.id.clone(),
75                inner: RawVerifyingKey::Ed25519(key.verifying_key()),
76            },
77        }
78    }
79
80    /// Signs the given byte array with the signing key.
81    /// This should not be used directly other than for generating namespace separated signatures or
82    /// signed objects.
83    pub(super) fn sign_raw(&self, data: &[u8]) -> Vec<u8> {
84        match &self.inner {
85            RawSigningKey::Ed25519(key) => key.sign(data).to_bytes().to_vec(),
86        }
87    }
88}
89
90impl CoseSerializable<CoseKeyContentFormat> for SigningKey {
91    /// Serializes the signing key to a COSE-formatted byte array.
92    fn to_cose(&self) -> CoseKeyBytes {
93        match &self.inner {
94            RawSigningKey::Ed25519(key) => {
95                coset::CoseKeyBuilder::new_okp_key()
96                    .key_id((&self.id).into())
97                    .algorithm(Algorithm::EdDSA)
98                    .param(
99                        OkpKeyParameter::D.to_i64(), // Signing key
100                        Value::Bytes(key.to_bytes().into()),
101                    )
102                    .param(
103                        OkpKeyParameter::Crv.to_i64(), // Elliptic curve identifier
104                        Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())),
105                    )
106                    .add_key_op(KeyOperation::Sign)
107                    .add_key_op(KeyOperation::Verify)
108                    .build()
109                    .to_vec()
110                    .expect("Signing key is always serializable")
111                    .into()
112            }
113        }
114    }
115
116    /// Deserializes a COSE-formatted byte array into a signing key.
117    fn from_cose(bytes: &CoseKeyBytes) -> Result<Self, EncodingError> {
118        let cose_key =
119            CoseKey::from_slice(bytes.as_ref()).map_err(|_| EncodingError::InvalidCoseEncoding)?;
120
121        match (&cose_key.alg, &cose_key.kty) {
122            (
123                Some(RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA)),
124                RegisteredLabel::Assigned(KeyType::OKP),
125            ) => Ok(SigningKey {
126                id: key_id(&cose_key)?,
127                inner: RawSigningKey::Ed25519(Box::pin(ed25519_signing_key(&cose_key)?)),
128            }),
129            _ => Err(EncodingError::UnsupportedValue(
130                "COSE key type or algorithm",
131            )),
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_cose_roundtrip_encode_signing() {
142        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
143        let cose = signing_key.to_cose();
144        let parsed_key = SigningKey::from_cose(&cose).unwrap();
145
146        assert_eq!(signing_key.to_cose(), parsed_key.to_cose());
147    }
148
149    #[test]
150    fn test_sign_rountrip() {
151        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
152        let signature = signing_key.sign_raw("Test message".as_bytes());
153        let verifying_key = signing_key.to_verifying_key();
154        assert!(verifying_key
155            .verify_raw(&signature, "Test message".as_bytes())
156            .is_ok());
157    }
158}