Skip to main content

bitwarden_crypto/signing/
verifying_key.rs

1//! A verifying key is the public part of a signature key pair. It is used to verify signatures.
2//!
3//! This implements the lowest layer of the signature module, verifying signatures on raw byte
4//! arrays.
5
6use ciborium::{Value, value::Integer};
7use coset::{
8    CborSerializable, MlDsaVariant, RegisteredLabel, RegisteredLabelWithPrivate,
9    iana::{
10        AkpKeyParameter, Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyParameter, KeyType,
11        OkpKeyParameter,
12    },
13};
14use ml_dsa::{MlDsa44, signature::Verifier};
15
16use super::{SignatureAlgorithm, ed25519_verifying_key, key_id, mldsa44_verifying_key};
17use crate::{
18    CoseKeyBytes, CoseKeyThumbprint, CryptoError,
19    content_format::CoseKeyContentFormat,
20    cose::{CoseKeyThumbprintExt, CoseSerializable, thumbprint_from_required_params},
21    error::{EncodingError, SignatureError},
22    keys::KeyId,
23};
24
25/// A `VerifyingKey` without the key id. This enum contains a variant for each supported signature
26/// scheme.
27pub(super) enum RawVerifyingKey {
28    Ed25519(ed25519_dalek::VerifyingKey),
29    MlDsa44(Box<ml_dsa::VerifyingKey<MlDsa44>>),
30}
31
32/// A verifying key is a public key used for verifying signatures. It can be published to other
33/// users, who can use it to verify that messages were signed by the holder of the corresponding
34/// `SigningKey`.
35pub struct VerifyingKey {
36    pub(super) id: KeyId,
37    pub(super) inner: RawVerifyingKey,
38}
39
40impl std::fmt::Debug for VerifyingKey {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        let key_suffix = match &self.inner {
43            RawVerifyingKey::Ed25519(_) => "Ed25519",
44            RawVerifyingKey::MlDsa44(_) => "MlDsa44",
45        };
46        let mut debug_struct = f.debug_struct(format!("VerifyingKey::{}", key_suffix).as_str());
47        debug_struct.field("id", &self.id);
48        debug_struct.finish()
49    }
50}
51
52impl VerifyingKey {
53    /// Returns the signature scheme used by the verifying key.
54    pub fn algorithm(&self) -> SignatureAlgorithm {
55        match &self.inner {
56            RawVerifyingKey::Ed25519(_) => SignatureAlgorithm::Ed25519,
57            RawVerifyingKey::MlDsa44(_) => SignatureAlgorithm::MlDsa44,
58        }
59    }
60
61    /// Verifies the signature of the given data, for the given namespace.
62    /// This should never be used directly, but only through the `verify` method, to enforce
63    /// strong domain separation of the signatures.
64    pub(super) fn verify_raw(&self, signature: &[u8], data: &[u8]) -> Result<(), CryptoError> {
65        match &self.inner {
66            RawVerifyingKey::Ed25519(key) => {
67                let sig = ed25519_dalek::Signature::from_bytes(
68                    signature
69                        .try_into()
70                        .map_err(|_| SignatureError::InvalidSignature)?,
71                );
72                key.verify_strict(data, &sig)
73                    .map_err(|_| SignatureError::InvalidSignature.into())
74            }
75            RawVerifyingKey::MlDsa44(key) => {
76                let sig = ml_dsa::Signature::<MlDsa44>::try_from(signature)
77                    .map_err(|_| SignatureError::InvalidSignature)?;
78                key.verify(data, &sig)
79                    .map_err(|_| SignatureError::InvalidSignature.into())
80            }
81        }
82    }
83}
84
85impl CoseSerializable<CoseKeyContentFormat> for VerifyingKey {
86    fn to_cose(&self) -> CoseKeyBytes {
87        match &self.inner {
88            RawVerifyingKey::Ed25519(key) => coset::CoseKeyBuilder::new_okp_key()
89                .key_id((&self.id).into())
90                .algorithm(Algorithm::EdDSA)
91                .param(
92                    OkpKeyParameter::Crv.to_i64(), // Elliptic curve identifier
93                    Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())),
94                )
95                // Note: X does not refer to the X coordinate of the public key curve point, but
96                // to the verifying key (signature public key), as represented by the curve spec. In
97                // the case of Ed25519, this is the compressed Y coordinate. This
98                // was ill-defined in earlier drafts of the standard. https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair
99                .param(
100                    OkpKeyParameter::X.to_i64(), // Verifying key (digital signature public key)
101                    Value::Bytes(key.to_bytes().to_vec()),
102                )
103                .add_key_op(KeyOperation::Verify)
104                .build()
105                .to_vec()
106                .expect("Verifying key is always serializable")
107                .into(),
108            RawVerifyingKey::MlDsa44(key) => coset::CoseKeyBuilder::new_mldsa_pub_key(
109                MlDsaVariant::MlDsa44,
110                key.encode().to_vec(),
111            )
112            .key_id((&self.id).into())
113            .add_key_op(KeyOperation::Verify)
114            .build()
115            .to_vec()
116            .expect("Verifying key is always serializable")
117            .into(),
118        }
119    }
120
121    fn from_cose(bytes: &CoseKeyBytes) -> Result<Self, EncodingError>
122    where
123        Self: Sized,
124    {
125        let cose_key = coset::CoseKey::from_slice(bytes.as_ref())
126            .map_err(|_| EncodingError::InvalidCoseEncoding)?;
127
128        let algorithm = cose_key
129            .alg
130            .as_ref()
131            .ok_or(EncodingError::MissingValue("COSE key algorithm"))?;
132        match (&cose_key.kty, algorithm) {
133            (
134                RegisteredLabel::Assigned(KeyType::OKP),
135                RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA),
136            ) => Ok(VerifyingKey {
137                id: key_id(&cose_key)?,
138                inner: RawVerifyingKey::Ed25519(ed25519_verifying_key(&cose_key)?),
139            }),
140            (
141                RegisteredLabel::Assigned(KeyType::AKP),
142                RegisteredLabelWithPrivate::Assigned(Algorithm::ML_DSA_44),
143            ) => Ok(VerifyingKey {
144                id: key_id(&cose_key)?,
145                inner: RawVerifyingKey::MlDsa44(Box::new(mldsa44_verifying_key(&cose_key)?)),
146            }),
147            _ => Err(EncodingError::UnsupportedValue(
148                "COSE key type or algorithm",
149            )),
150        }
151    }
152}
153
154impl CoseKeyThumbprintExt for VerifyingKey {
155    fn thumbprint(&self) -> Result<CoseKeyThumbprint, CryptoError> {
156        let params = match &self.inner {
157            // https://datatracker.ietf.org/doc/rfc9679/
158            RawVerifyingKey::Ed25519(key) => vec![
159                (
160                    KeyParameter::Kty.to_i64(),
161                    Value::Integer(Integer::from(KeyType::OKP.to_i64())),
162                ),
163                (
164                    OkpKeyParameter::Crv.to_i64(),
165                    Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())),
166                ),
167                // `x` is the compressed Ed25519 public key (see the note in `to_cose`).
168                (
169                    OkpKeyParameter::X.to_i64(),
170                    Value::Bytes(key.to_bytes().to_vec()),
171                ),
172            ],
173            // https://datatracker.ietf.org/doc/rfc9964/
174            RawVerifyingKey::MlDsa44(key) => vec![
175                (
176                    KeyParameter::Kty.to_i64(),
177                    Value::Integer(Integer::from(KeyType::AKP.to_i64())),
178                ),
179                (
180                    KeyParameter::Alg.to_i64(),
181                    Value::Integer(Integer::from(Algorithm::ML_DSA_44.to_i64())),
182                ),
183                (
184                    AkpKeyParameter::Pub.to_i64(),
185                    Value::Bytes(key.encode().to_vec()),
186                ),
187            ],
188        };
189        Ok(thumbprint_from_required_params(params))
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
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 SIGNED_DATA_RAW: &[u8] = &[
204        247, 239, 74, 181, 75, 54, 137, 225, 2, 158, 14, 0, 61, 210, 254, 208, 255, 16, 8, 81, 173,
205        33, 59, 67, 204, 31, 45, 38, 147, 118, 228, 84, 235, 252, 104, 38, 194, 173, 62, 52, 9,
206        184, 1, 22, 113, 134, 154, 108, 24, 83, 78, 2, 23, 235, 80, 22, 57, 110, 100, 24, 151, 33,
207        186, 12,
208    ];
209    // Same key as `MLDSA44_VERIFYING_KEY` in `signing_key.rs`'s tests, kept in sync so both
210    // modules pin the same underlying key.
211    const MLDSA44_VERIFYING_KEY: &str = "a501070250e11a339366953128787c1b2c16b6945803382f048102205905205cc3620a551a0213972ad845e4930f4ecdfe1d10a81ffde8cdd1a0fffc38eefa3fd659028cad345c823074e870ffb9ed12e1d08a407db8f2431f2b75d3934e8a2662c033c8337aec1afdc1bb1babf185d709365a3057b41774dcf08e3877d4fdca2111b778ed53f6cf5b2d46d4a2427ee72c1c08a87e4e231794d8418513bae5e57d65428c41fa0b1031d6bc3b07a15f3349c4361a627c736d4e86fe3285e74277117f57df4bc53a98afcd55a77aee7e1b1465abb72e68ab2da897ceecfb8f7d4f0ced0dcf39506b31a46b795c8cee3ee6d789a1f8f7c35eb20eb17c6af5402954ec6eb41576eb2d65078b9755aca0e3af11f438df2a8abfb35d75b3fd151099735908f9b15a42f5211d4ea691014142adae9c9fdf0cf704bc4197d10f8f5f60be29abc805c6fa0f6192e4d12c8f7d50289e0ea796217f453730ca5af1ac3eb3c8ccc4bc161925806506160489a175a07bcf0f9323a4d05013117ba5f3c6d5bc6f6feadcfd093e40efbc19b8648cc8251d6d2faca5da7b506ec4b9e66b821c4176205b9ce0cd4c358b5e2a742c93b9ddb481feca10bb213925e1c661149c04e84a7809e440c190977cf015f12b6259581c368c70eec633985b78c1108f7ff42d1cb03d9ce957f705b871b57c3705ada6d6b386e647a3bacf846da4feb5c508bc4960c2d1f5378700b118fd2ea20db2790aafb7d39043bd2ab6f8902111a45fb181be8e83fa5a4c43e5d2d56797042f8d0c3e832857db0fccc61e7ea3616b70da0439eedf1a1a40196435c99b24e2ab07f8515272972faeafb2670ff66b237b00bf092da2de98a99ed4319498b4d8385aa68e36974b9c31d44b2dfebda78b3465b859296c63958cbcb587dd82e04c51986fdb9e0f3a2941ef1eac8d3b08c2fcd32ceb9dc434096c7f03e529c82132cd7e242a5668520baf00332a2de8a4f52420378350993bd35c573ba4f32e5ad89ddca1b7b3ddb0a21b0faa3674de2bdcb3519b86ed33a8939e6c7cbb9b9493279db28739b163916776bdcc40f7368000b5cefb3425b77dce84a957642319cb95226e30e119a4e455795b24e627b69881dc87084c3c1972fd170c87bcf4cc228beb4083a063e7a44c4fa8490f48457d12e4cf44b84ef41556e53599c5b4a030b4d07ee6594b6830ac0cb7823f908130fd6d2f3f9b5d65012f8e9abeeb74f4042a2a8c05fcb28f968f15f4230dfce034595141c770c200c1fd710e86d9b4d820ee0f015762526cb5a148e18874a385acef6f9efb77d43bc14936e8aa966dd53a1d2403a820f735fd60f5867570fa93ffae3214dc9ac93b2c191115a0045f71923249a10fdd8aa6ca35f6420fa1c80a867d7f54ef54b465f151de0b9a040c31d7373c81565d503cc1332dc6808533171ff2513a7139dad63b1848ba46f09255e81f7edab6208e62d17955aa43e27c74b5ed77b8d3c66ea50c478760e0db5c1b8970a22e74a29bd07bed0ba7e9c7a1866531889fa516ffd66c363d9558b2716d67809d85f16b4e18f456d5ec2343d08ea601da77676246c26432bfcd935c0d80a1e32fc41b380dff041a2629683540d5f2b9d9ef6101114242508c68987a44a0169bf8fbdabf1954bd54d745f1d66b7367848e43e0c94a6d88ccbdfd65333301d34d06d400e643e36c681c7f475b0026967d68e58af2fbf4800ca7aad6a43a3782b1d9452226ef881d50fcbb7b4df0c8c3b15ca80321cbb928fa8dce4048f01fc48121eaa1da0a954a6150c3d2e42add1b4f58910c2db0869a5481d469ef4bdccec3e7c0a21253fd19ce941b16808bf14fca6fec2b1ba7f771580235034e5ca95763721d6add9aa3914f476b37097ce67b000d5d";
212
213    #[test]
214    fn test_cose_roundtrip_encode_verifying() {
215        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
216        let cose = verifying_key.to_cose();
217        let parsed_key = VerifyingKey::from_cose(&cose).unwrap();
218
219        assert_eq!(verifying_key.to_cose(), parsed_key.to_cose());
220    }
221
222    #[test]
223    fn test_testvector() {
224        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
225        assert_eq!(verifying_key.algorithm(), SignatureAlgorithm::Ed25519);
226
227        verifying_key
228            .verify_raw(SIGNED_DATA_RAW, b"Test message")
229            .unwrap();
230    }
231
232    #[test]
233    fn test_invalid_testvector() {
234        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
235        assert_eq!(verifying_key.algorithm(), SignatureAlgorithm::Ed25519);
236
237        // This should fail, as the signed object is not valid for the given verifying key.
238        assert!(
239            verifying_key
240                .verify_raw(SIGNED_DATA_RAW, b"Invalid message")
241                .is_err()
242        );
243    }
244
245    #[test]
246    fn test_thumbprint_ed25519_vector() {
247        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
248        assert_eq!(
249            verifying_key.thumbprint().unwrap().to_hex(),
250            "ea38af8eb96812daa5217a342946814921f0ebc74edaa1e9832cbc4daf9ba803"
251        );
252    }
253
254    #[test]
255    fn test_thumbprint_mldsa44_vector() {
256        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(
257            hex::decode(MLDSA44_VERIFYING_KEY).unwrap(),
258        ))
259        .unwrap();
260        assert_eq!(
261            verifying_key.thumbprint().unwrap().to_hex(),
262            "fb932d165a6ff31caf0537ecb3c267dcbdac69773de6b739211c00e68969e233"
263        );
264    }
265}