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, CryptoError,
15    PublicKeyEncryptionAlgorithm, RawPublicKey, SignedObject, SigningKey, SigningNamespace,
16    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()?),
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 Vec<u8> {
81    fn from(val: SignedPublicKey) -> Self {
82        val.0.to_cose()
83    }
84}
85
86impl TryFrom<Vec<u8>> for SignedPublicKey {
87    type Error = EncodingError;
88    fn try_from(bytes: Vec<u8>) -> 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: Vec<u8> = 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(&public_key_message.public_key.into_vec())
116                    .map_err(|_| EncodingError::InvalidValue("public key"))?,
117            ),
118        }
119    }
120}
121
122impl FromStr for SignedPublicKey {
123    type Err = EncodingError;
124
125    fn from_str(s: &str) -> Result<Self, Self::Err> {
126        let bytes = STANDARD
127            .decode(s)
128            .map_err(|_| EncodingError::InvalidCborSerialization)?;
129        Self::try_from(bytes)
130    }
131}
132
133impl<'de> Deserialize<'de> for SignedPublicKey {
134    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
135    where
136        D: serde::Deserializer<'de>,
137    {
138        deserializer.deserialize_str(FromStrVisitor::new())
139    }
140}
141
142impl serde::Serialize for SignedPublicKey {
143    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
144    where
145        S: serde::Serializer,
146    {
147        let b64_serialized_signed_public_key: String = self.clone().into();
148        serializer.serialize_str(&b64_serialized_signed_public_key)
149    }
150}
151
152impl schemars::JsonSchema for SignedPublicKey {
153    fn schema_name() -> String {
154        "SignedPublicKey".to_string()
155    }
156
157    fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
158        generator.subschema_for::<String>()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use crate::{AsymmetricCryptoKey, PublicKeyEncryptionAlgorithm, SignatureAlgorithm};
166
167    #[test]
168    fn test_signed_asymmetric_public_key() {
169        let public_key =
170            AsymmetricCryptoKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1).to_public_key();
171        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
172        let message = SignedPublicKeyMessage::from_public_key(&public_key).unwrap();
173        let signed_public_key = message.sign(&signing_key).unwrap();
174        let verifying_key = signing_key.to_verifying_key();
175        let verified_public_key = signed_public_key.verify_and_unwrap(&verifying_key).unwrap();
176        assert_eq!(
177            public_key.to_der().unwrap(),
178            verified_public_key.to_der().unwrap()
179        );
180    }
181}