bitwarden_crypto/keys/
signed_public_key.rs

1//! A public encryption key alone is not authenticated. It needs to be tied to a cryptographic
2//! identity, which is provided by a signature keypair. This is done by signing the public key, and
3//! requiring consumers to verify the public key before consumption by using unwrap_and_verify.
4
5use std::str::FromStr;
6
7use base64::{engine::general_purpose::STANDARD, Engine};
8use serde::{Deserialize, Serialize};
9use serde_bytes::ByteBuf;
10use serde_repr::{Deserialize_repr, Serialize_repr};
11
12use super::AsymmetricPublicCryptoKey;
13use crate::{
14    cose::CoseSerializable, error::EncodingError, util::FromStrVisitor, CoseSign1Bytes,
15    CryptoError, PublicKeyEncryptionAlgorithm, RawPublicKey, SignedObject, SigningKey,
16    SigningNamespace, SpkiPublicKeyBytes, VerifyingKey,
17};
18
19#[cfg(feature = "wasm")]
20#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
21const TS_CUSTOM_TYPES: &'static str = r#"
22export type SignedPublicKey = string;
23"#;
24
25/// `PublicKeyFormat` defines the format of the public key in a `SignedAsymmetricPublicKeyMessage`.
26/// Currently, only ASN.1 Subject Public Key Info (SPKI) is used, but CoseKey may become another
27/// option in the future.
28#[derive(Serialize_repr, Deserialize_repr)]
29#[repr(u8)]
30enum PublicKeyFormat {
31    Spki = 0,
32}
33
34/// `SignedAsymmetricPublicKeyMessage` is a message that once signed, makes a claim towards owning a
35/// public encryption key.
36#[derive(Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct SignedPublicKeyMessage {
39    /// The algorithm/crypto system used with this public key.
40    algorithm: PublicKeyEncryptionAlgorithm,
41    /// The format of the public key.
42    content_format: PublicKeyFormat,
43    /// The public key, serialized and formatted in the content format specified in
44    /// `content_format`.
45    ///
46    /// Note: [ByteBuf] is used here to ensure efficient serialization. Using [`Vec<u8>`] would
47    /// lead to an incompatible encoding of individual bytes, instead of a contiguous byte
48    /// buffer.
49    public_key: ByteBuf,
50}
51
52impl SignedPublicKeyMessage {
53    /// Creates a new `SignedPublicKeyMessage` from an `AsymmetricPublicCryptoKey`. This message
54    /// can then be signed using a `SigningKey` to create a `SignedPublicKey`.
55    pub fn from_public_key(public_key: &AsymmetricPublicCryptoKey) -> Result<Self, CryptoError> {
56        match public_key.inner() {
57            RawPublicKey::RsaOaepSha1(_) => Ok(SignedPublicKeyMessage {
58                algorithm: PublicKeyEncryptionAlgorithm::RsaOaepSha1,
59                content_format: PublicKeyFormat::Spki,
60                public_key: ByteBuf::from(public_key.to_der()?.as_ref()),
61            }),
62        }
63    }
64
65    /// Signs the `SignedPublicKeyMessage` using the provided `SigningKey`, and returns a
66    /// `SignedPublicKey`.
67    pub fn sign(&self, signing_key: &SigningKey) -> Result<SignedPublicKey, CryptoError> {
68        Ok(SignedPublicKey(
69            signing_key.sign(self, &SigningNamespace::SignedPublicKey)?,
70        ))
71    }
72}
73
74/// `SignedAsymmetricPublicKey` is a public encryption key, signed by the owner of the encryption
75/// keypair. This wrapping ensures that the consumer of the public key MUST verify the identity of
76/// the Signer before they can use the public key for encryption.
77#[derive(Clone, Debug)]
78pub struct SignedPublicKey(pub(crate) SignedObject);
79
80impl From<SignedPublicKey> for CoseSign1Bytes {
81    fn from(val: SignedPublicKey) -> Self {
82        val.0.to_cose()
83    }
84}
85
86impl TryFrom<CoseSign1Bytes> for SignedPublicKey {
87    type Error = EncodingError;
88    fn try_from(bytes: CoseSign1Bytes) -> Result<Self, EncodingError> {
89        Ok(SignedPublicKey(SignedObject::from_cose(&bytes)?))
90    }
91}
92
93impl From<SignedPublicKey> for String {
94    fn from(val: SignedPublicKey) -> Self {
95        let bytes: CoseSign1Bytes = val.into();
96        STANDARD.encode(&bytes)
97    }
98}
99
100impl SignedPublicKey {
101    /// Verifies the signature of the public key against the provided `VerifyingKey`, and returns
102    /// the `AsymmetricPublicCryptoKey` if the verification is successful.
103    pub fn verify_and_unwrap(
104        self,
105        verifying_key: &VerifyingKey,
106    ) -> Result<AsymmetricPublicCryptoKey, CryptoError> {
107        let public_key_message: SignedPublicKeyMessage = self
108            .0
109            .verify_and_unwrap(verifying_key, &SigningNamespace::SignedPublicKey)?;
110        match (
111            public_key_message.algorithm,
112            public_key_message.content_format,
113        ) {
114            (PublicKeyEncryptionAlgorithm::RsaOaepSha1, PublicKeyFormat::Spki) => Ok(
115                AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(
116                    public_key_message.public_key.into_vec(),
117                ))
118                .map_err(|_| EncodingError::InvalidValue("public key"))?,
119            ),
120        }
121    }
122}
123
124impl FromStr for SignedPublicKey {
125    type Err = EncodingError;
126
127    fn from_str(s: &str) -> Result<Self, Self::Err> {
128        let bytes = STANDARD
129            .decode(s)
130            .map_err(|_| EncodingError::InvalidCborSerialization)?;
131        Self::try_from(CoseSign1Bytes::from(bytes))
132    }
133}
134
135impl<'de> Deserialize<'de> for SignedPublicKey {
136    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
137    where
138        D: serde::Deserializer<'de>,
139    {
140        deserializer.deserialize_str(FromStrVisitor::new())
141    }
142}
143
144impl serde::Serialize for SignedPublicKey {
145    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
146    where
147        S: serde::Serializer,
148    {
149        let b64_serialized_signed_public_key: String = self.clone().into();
150        serializer.serialize_str(&b64_serialized_signed_public_key)
151    }
152}
153
154impl schemars::JsonSchema for SignedPublicKey {
155    fn schema_name() -> String {
156        "SignedPublicKey".to_string()
157    }
158
159    fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
160        generator.subschema_for::<String>()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use crate::{AsymmetricCryptoKey, PublicKeyEncryptionAlgorithm, SignatureAlgorithm};
168
169    #[test]
170    fn test_signed_asymmetric_public_key() {
171        let public_key =
172            AsymmetricCryptoKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1).to_public_key();
173        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
174        let message = SignedPublicKeyMessage::from_public_key(&public_key).unwrap();
175        let signed_public_key = message.sign(&signing_key).unwrap();
176        let verifying_key = signing_key.to_verifying_key();
177        let verified_public_key = signed_public_key.verify_and_unwrap(&verifying_key).unwrap();
178        assert_eq!(
179            public_key.to_der().unwrap(),
180            verified_public_key.to_der().unwrap()
181        );
182    }
183}