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    cose::CoseSerializable,
17    error::{EncodingError, Result},
18    keys::KeyId,
19    CryptoKey,
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 _: () = {
41    fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>() {}
42    fn assert_all() {
43        assert_zeroize_on_drop::<ed25519_dalek::SigningKey>();
44    }
45};
46impl zeroize::ZeroizeOnDrop for SigningKey {}
47impl CryptoKey for SigningKey {}
48
49impl SigningKey {
50    /// Makes a new signing key for the given signature scheme.
51    pub fn make(algorithm: SignatureAlgorithm) -> Self {
52        match algorithm {
53            SignatureAlgorithm::Ed25519 => SigningKey {
54                id: KeyId::make(),
55                inner: RawSigningKey::Ed25519(Box::pin(ed25519_dalek::SigningKey::generate(
56                    &mut rand::thread_rng(),
57                ))),
58            },
59        }
60    }
61
62    pub(super) fn cose_algorithm(&self) -> Algorithm {
63        match &self.inner {
64            RawSigningKey::Ed25519(_) => Algorithm::EdDSA,
65        }
66    }
67
68    /// Derives the verifying key from the signing key. The key id is the same for the signing and
69    /// verifying key, since they are a pair.
70    pub fn to_verifying_key(&self) -> VerifyingKey {
71        match &self.inner {
72            RawSigningKey::Ed25519(key) => VerifyingKey {
73                id: self.id.clone(),
74                inner: RawVerifyingKey::Ed25519(key.verifying_key()),
75            },
76        }
77    }
78
79    /// Signs the given byte array with the signing key.
80    /// This should not be used directly other than for generating namespace separated signatures or
81    /// signed objects.
82    pub(super) fn sign_raw(&self, data: &[u8]) -> Vec<u8> {
83        match &self.inner {
84            RawSigningKey::Ed25519(key) => key.sign(data).to_bytes().to_vec(),
85        }
86    }
87}
88
89impl CoseSerializable for SigningKey {
90    /// Serializes the signing key to a COSE-formatted byte array.
91    fn to_cose(&self) -> Vec<u8> {
92        match &self.inner {
93            RawSigningKey::Ed25519(key) => {
94                coset::CoseKeyBuilder::new_okp_key()
95                    .key_id((&self.id).into())
96                    .algorithm(Algorithm::EdDSA)
97                    .param(
98                        OkpKeyParameter::D.to_i64(), // Signing key
99                        Value::Bytes(key.to_bytes().into()),
100                    )
101                    .param(
102                        OkpKeyParameter::Crv.to_i64(), // Elliptic curve identifier
103                        Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())),
104                    )
105                    .add_key_op(KeyOperation::Sign)
106                    .add_key_op(KeyOperation::Verify)
107                    .build()
108                    .to_vec()
109                    .expect("Signing key is always serializable")
110            }
111        }
112    }
113
114    /// Deserializes a COSE-formatted byte array into a signing key.
115    fn from_cose(bytes: &[u8]) -> Result<Self, EncodingError> {
116        let cose_key =
117            CoseKey::from_slice(bytes).map_err(|_| EncodingError::InvalidCoseEncoding)?;
118
119        match (&cose_key.alg, &cose_key.kty) {
120            (
121                Some(RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA)),
122                RegisteredLabel::Assigned(KeyType::OKP),
123            ) => Ok(SigningKey {
124                id: key_id(&cose_key)?,
125                inner: RawSigningKey::Ed25519(Box::pin(ed25519_signing_key(&cose_key)?)),
126            }),
127            _ => Err(EncodingError::UnsupportedValue(
128                "COSE key type or algorithm",
129            )),
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_cose_roundtrip_encode_signing() {
140        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
141        let cose = signing_key.to_cose();
142        let parsed_key = SigningKey::from_cose(&cose).unwrap();
143
144        assert_eq!(signing_key.to_cose(), parsed_key.to_cose());
145    }
146
147    #[test]
148    fn test_sign_rountrip() {
149        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
150        let signature = signing_key.sign_raw("Test message".as_bytes());
151        let verifying_key = signing_key.to_verifying_key();
152        assert!(verifying_key
153            .verify_raw(&signature, "Test message".as_bytes())
154            .is_ok());
155    }
156}