Skip to main content

bitwarden_crypto/signing/
signing_key.rs

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