Skip to main content

bitwarden_crypto/keys/
public_key_encryption.rs

1use std::{fmt::Display, pin::Pin, str::FromStr};
2
3use bitwarden_encoding::{B64, FromStrVisitor};
4use rsa::{RsaPrivateKey, RsaPublicKey, pkcs8::DecodePublicKey};
5use serde::{Deserialize, Serialize};
6use serde_repr::{Deserialize_repr, Serialize_repr};
7use tracing::instrument;
8#[cfg(feature = "wasm")]
9use wasm_bindgen::convert::FromWasmAbi;
10
11use super::key_encryptable::CryptoKey;
12use crate::{
13    Pkcs8PrivateKeyBytes, SpkiPublicKeyBytes,
14    error::{CryptoError, Result},
15};
16
17#[cfg(feature = "wasm")]
18#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
19const TS_CUSTOM_TYPES: &'static str = r#"
20export type PublicKey = Tagged<string, "PublicKey">;
21"#;
22
23#[cfg(feature = "wasm")]
24impl wasm_bindgen::describe::WasmDescribe for PublicKey {
25    fn describe() {
26        <String as wasm_bindgen::describe::WasmDescribe>::describe();
27    }
28}
29
30#[cfg(feature = "wasm")]
31impl FromWasmAbi for PublicKey {
32    type Abi = <String as FromWasmAbi>::Abi;
33
34    unsafe fn from_abi(abi: Self::Abi) -> Self {
35        use wasm_bindgen::UnwrapThrowExt;
36
37        let s = unsafe { String::from_abi(abi) };
38        let bytes: Vec<u8> = s.parse::<bitwarden_encoding::B64>().unwrap_throw().into();
39        PublicKey::from_der(&SpkiPublicKeyBytes::from(bytes)).unwrap_throw()
40    }
41}
42
43/// Algorithm / public key encryption scheme used for encryption/decryption.
44#[derive(Debug, Serialize_repr, Deserialize_repr)]
45#[repr(u8)]
46pub enum PublicKeyEncryptionAlgorithm {
47    /// RSA with OAEP padding and SHA-1 hashing.
48    RsaOaepSha1 = 0,
49}
50
51#[derive(Clone, PartialEq)]
52pub(crate) enum RawPublicKey {
53    RsaOaepSha1(RsaPublicKey),
54}
55
56/// Public key of a key pair used in a public key encryption scheme. It is used for
57/// encrypting data.
58#[derive(Clone, PartialEq)]
59pub struct PublicKey {
60    inner: RawPublicKey,
61}
62
63impl std::fmt::Debug for PublicKey {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        let key_suffix = match &self.inner {
66            RawPublicKey::RsaOaepSha1(_) => "RsaOaepSha1",
67        };
68        let mut debug_struct = f.debug_struct(format!("PublicKey::{}", key_suffix).as_str());
69        match &self.inner {
70            RawPublicKey::RsaOaepSha1(_) => {
71                if let Ok(der) = self.to_der() {
72                    debug_struct.field("key", &hex::encode(der.as_ref()));
73                }
74            }
75        }
76        debug_struct.finish()
77    }
78}
79
80impl PublicKey {
81    pub(crate) fn inner(&self) -> &RawPublicKey {
82        &self.inner
83    }
84
85    /// Build a public key from the SubjectPublicKeyInfo DER.
86    #[instrument(skip_all, err)]
87    pub fn from_der(der: &SpkiPublicKeyBytes) -> Result<Self> {
88        Ok(PublicKey {
89            inner: RawPublicKey::RsaOaepSha1(
90                RsaPublicKey::from_public_key_der(der.as_ref())
91                    .map_err(|_| CryptoError::InvalidKey)?,
92            ),
93        })
94    }
95
96    /// Makes a SubjectPublicKeyInfo DER serialized version of the public key.
97    #[instrument(skip_all, err)]
98    pub fn to_der(&self) -> Result<SpkiPublicKeyBytes> {
99        use rsa::pkcs8::EncodePublicKey;
100        match &self.inner {
101            RawPublicKey::RsaOaepSha1(public_key) => Ok(public_key
102                .to_public_key_der()
103                .map_err(|_| CryptoError::InvalidKey)?
104                .as_bytes()
105                .to_owned()
106                .into()),
107        }
108    }
109}
110
111impl FromStr for PublicKey {
112    type Err = ();
113
114    fn from_str(s: &str) -> Result<Self, Self::Err> {
115        let bytes: Vec<u8> = s.parse::<B64>().map_err(|_| ())?.into();
116        Self::from_der(&SpkiPublicKeyBytes::from(bytes)).map_err(|_| ())
117    }
118}
119
120impl Display for PublicKey {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        match self.to_der() {
123            Ok(der) => write!(f, "{}", B64::from(der.as_ref())),
124            Err(_) => write!(f, "[INVALID PUBLIC KEY]"),
125        }
126    }
127}
128
129impl<'de> Deserialize<'de> for PublicKey {
130    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
131    where
132        D: serde::Deserializer<'de>,
133    {
134        deserializer.deserialize_str(FromStrVisitor::new())
135    }
136}
137
138impl Serialize for PublicKey {
139    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
140    where
141        S: serde::Serializer,
142    {
143        let der = self.to_der().map_err(serde::ser::Error::custom)?;
144        serializer.serialize_str(&B64::from(der.as_ref()).to_string())
145    }
146}
147
148#[derive(Clone)]
149pub(crate) enum RawPrivateKey {
150    // RsaPrivateKey is not a Copy type so this isn't completely necessary, but
151    // to keep the compiler from making stack copies when moving this struct around,
152    // we use a Box to keep the values on the heap. We also pin the box to make sure
153    // that the contents can't be pulled out of the box and moved
154    RsaOaepSha1(Pin<Box<RsaPrivateKey>>),
155}
156
157/// Private key of a key pair used in a public key encryption scheme. It is used for
158/// decrypting data that was encrypted with the corresponding public key.
159#[derive(Clone)]
160pub struct PrivateKey {
161    inner: RawPrivateKey,
162}
163
164// Note that RsaPrivateKey already implements ZeroizeOnDrop, so we don't need to do anything
165// We add this assertion to make sure that this is still true in the future
166const _: fn() = || {
167    fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>() {}
168    assert_zeroize_on_drop::<RsaPrivateKey>();
169};
170impl zeroize::ZeroizeOnDrop for PrivateKey {}
171impl CryptoKey for PrivateKey {}
172
173impl PrivateKey {
174    /// Generate a random PrivateKey (RSA-2048).
175    pub fn make(algorithm: PublicKeyEncryptionAlgorithm) -> Self {
176        Self::make_internal(algorithm, &mut rand::thread_rng())
177    }
178
179    fn make_internal<R: rand::CryptoRng + rand::RngCore>(
180        algorithm: PublicKeyEncryptionAlgorithm,
181        rng: &mut R,
182    ) -> Self {
183        match algorithm {
184            PublicKeyEncryptionAlgorithm::RsaOaepSha1 => Self {
185                inner: RawPrivateKey::RsaOaepSha1(Box::pin(
186                    RsaPrivateKey::new(rng, 2048).expect("failed to generate a key"),
187                )),
188            },
189        }
190    }
191
192    #[allow(missing_docs)]
193    #[cfg_attr(feature = "dangerous-crypto-debug", instrument(err))]
194    #[cfg_attr(not(feature = "dangerous-crypto-debug"), instrument(skip_all, err))]
195    pub fn from_pem(pem: &str) -> Result<Self> {
196        use rsa::pkcs8::DecodePrivateKey;
197        Ok(Self {
198            inner: RawPrivateKey::RsaOaepSha1(Box::pin(
199                RsaPrivateKey::from_pkcs8_pem(pem).map_err(|_| CryptoError::InvalidKey)?,
200            )),
201        })
202    }
203
204    #[allow(missing_docs)]
205    #[cfg_attr(feature = "dangerous-crypto-debug", instrument(err))]
206    #[cfg_attr(not(feature = "dangerous-crypto-debug"), instrument(skip_all, err))]
207    pub fn from_der(der: &Pkcs8PrivateKeyBytes) -> Result<Self> {
208        use rsa::pkcs8::DecodePrivateKey;
209        Ok(Self {
210            inner: RawPrivateKey::RsaOaepSha1(Box::pin(
211                RsaPrivateKey::from_pkcs8_der(der.as_ref()).map_err(|_| CryptoError::InvalidKey)?,
212            )),
213        })
214    }
215
216    #[allow(missing_docs)]
217    #[cfg_attr(feature = "dangerous-crypto-debug", instrument(err))]
218    #[cfg_attr(not(feature = "dangerous-crypto-debug"), instrument(skip_all, err))]
219    pub fn to_der(&self) -> Result<Pkcs8PrivateKeyBytes> {
220        match &self.inner {
221            RawPrivateKey::RsaOaepSha1(private_key) => {
222                use rsa::pkcs8::EncodePrivateKey;
223                Ok(private_key
224                    .to_pkcs8_der()
225                    .map_err(|_| CryptoError::InvalidKey)?
226                    .as_bytes()
227                    .to_owned()
228                    .into())
229            }
230        }
231    }
232
233    /// Derives the public key corresponding to this private key. This is deterministic
234    /// and always derives the same public key.
235    pub fn to_public_key(&self) -> PublicKey {
236        match &self.inner {
237            RawPrivateKey::RsaOaepSha1(private_key) => PublicKey {
238                inner: RawPublicKey::RsaOaepSha1(private_key.to_public_key()),
239            },
240        }
241    }
242
243    pub(crate) fn inner(&self) -> &RawPrivateKey {
244        &self.inner
245    }
246}
247
248// We manually implement these to make sure we don't print any sensitive data
249impl std::fmt::Debug for PrivateKey {
250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251        let key_suffix = match &self.inner {
252            RawPrivateKey::RsaOaepSha1(_) => "RsaOaepSha1",
253        };
254        let mut debug_struct = f.debug_struct(format!("PrivateKey::{}", key_suffix).as_str());
255        #[cfg(feature = "dangerous-crypto-debug")]
256        match &self.inner {
257            RawPrivateKey::RsaOaepSha1(_) => {
258                if let Ok(der) = self.to_der() {
259                    debug_struct.field("key", &hex::encode(der.as_ref()));
260                }
261            }
262        }
263        debug_struct.finish()
264    }
265}
266
267#[cfg(test)]
268mod tests {
269
270    use bitwarden_encoding::B64;
271
272    use crate::{
273        Pkcs8PrivateKeyBytes, PrivateKey, PublicKey, PublicKeyEncryptionAlgorithm,
274        SpkiPublicKeyBytes, SymmetricCryptoKey, UnsignedSharedKey,
275        content_format::{Bytes, Pkcs8PrivateKeyDerContentFormat},
276    };
277
278    #[test]
279    #[ignore = "Manual test to verify debug format"]
280    fn test_debug() {
281        let private_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
282        println!("{:?}", private_key);
283        let public_key = private_key.to_public_key();
284        println!("{:?}", public_key);
285    }
286
287    #[test]
288    fn test_asymmetric_crypto_key() {
289        let pem_key_str = "-----BEGIN PRIVATE KEY-----
290MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5
291qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc
292afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm
293qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv
294b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw
295P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam
296rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi
297szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx
2980d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+
2998vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR
300jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach
301vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI
3021u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR
303J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7
304l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ
305TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9
306ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye
307KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN
308wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ
309UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA
310kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W
311pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN
312Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi
313CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup
314PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf
315DnqOsltgPomWZ7xVfMkm9niL2OA=
316-----END PRIVATE KEY-----";
317
318        let der_key: B64 = "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYcafeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4CwmqqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyvb0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZwP7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2famrEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKiszJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVRjB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKachvGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KRJ30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQTjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9ByeKvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiNwEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZUZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEAkY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7Wpt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwNZy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLiCVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzupPFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnfDnqOsltgPomWZ7xVfMkm9niL2OA=".parse().unwrap();
319        let der_key_vec: Vec<u8> = der_key.into();
320
321        // Load the two different formats and check they are the same key
322        let pem_key = PrivateKey::from_pem(pem_key_str).unwrap();
323        let der_key = PrivateKey::from_der(&Bytes::<Pkcs8PrivateKeyDerContentFormat>::from(
324            der_key_vec.clone(),
325        ))
326        .unwrap();
327        assert_eq!(pem_key.to_der().unwrap(), der_key.to_der().unwrap());
328
329        // Check that the keys can be converted back to DER
330
331        assert_eq!(der_key.to_der().unwrap().to_vec(), der_key_vec.clone());
332        assert_eq!(pem_key.to_der().unwrap().to_vec(), der_key_vec);
333    }
334
335    #[test]
336    fn test_encrypt_public_decrypt_private() {
337        let private_key: B64 = concat!(
338            "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH",
339            "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY",
340            "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL",
341            "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK",
342            "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2",
343            "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO",
344            "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ",
345            "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj",
346            "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B",
347            "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL",
348            "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4",
349            "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk",
350            "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro",
351            "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv",
352            "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH",
353            "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j",
354            "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU",
355            "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM",
356            "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC",
357            "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G",
358            "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/",
359            "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J",
360            "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94",
361            "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs",
362            "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R",
363            "GDgRMUB6cL3IRVzcR0dC6cY=",
364        )
365        .parse()
366        .unwrap();
367
368        let public_key: B64 = concat!(
369            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
370            "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
371            "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
372            "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
373            "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
374            "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
375            "XQIDAQAB",
376        )
377        .parse()
378        .unwrap();
379
380        let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
381        let private_key = PrivateKey::from_der(&private_key).unwrap();
382        let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&public_key)).unwrap();
383
384        let raw_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
385        #[expect(deprecated)]
386        let encrypted = UnsignedSharedKey::encapsulate_key_unsigned(&raw_key, &public_key).unwrap();
387        #[expect(deprecated)]
388        let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap();
389
390        assert_eq!(raw_key, decrypted);
391    }
392
393    #[test]
394    fn test_asymmetric_public_crypto_key_from_str() {
395        let public_key_b64 = concat!(
396            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
397            "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
398            "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
399            "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
400            "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
401            "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
402            "XQIDAQAB",
403        );
404
405        // Test FromStr
406        let parsed_key: PublicKey = public_key_b64.parse().expect("should parse");
407
408        // Verify the key can be converted back to DER and then to B64
409        let der = parsed_key.to_der().expect("should convert to DER");
410        let b64_str = B64::from(der.as_ref()).to_string();
411        assert_eq!(b64_str, public_key_b64);
412    }
413
414    #[test]
415    fn test_asymmetric_public_crypto_key_from_str_invalid() {
416        // Invalid base64
417        let result: Result<PublicKey, _> = "not-valid-base64!!!".parse();
418        assert!(result.is_err());
419
420        // Valid base64 but invalid key data
421        let result: Result<PublicKey, _> = "aGVsbG8gd29ybGQ=".parse();
422        assert!(result.is_err());
423    }
424
425    #[test]
426    fn test_asymmetric_public_crypto_key_serialize_deserialize() {
427        let public_key_b64 = concat!(
428            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
429            "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
430            "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
431            "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
432            "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
433            "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
434            "XQIDAQAB",
435        );
436
437        // Parse the key
438        let key: PublicKey = public_key_b64.parse().expect("should parse");
439
440        // Serialize to JSON
441        let serialized = serde_json::to_string(&key).expect("should serialize");
442        assert_eq!(serialized, format!("\"{}\"", public_key_b64));
443
444        // Deserialize from JSON
445        let deserialized: PublicKey =
446            serde_json::from_str(&serialized).expect("should deserialize");
447
448        // Verify the keys are equal by comparing their DER representations
449        assert_eq!(
450            key.to_der().expect("should convert to DER"),
451            deserialized.to_der().expect("should convert to DER")
452        );
453    }
454
455    #[test]
456    fn test_asymmetric_public_crypto_key_deserialize_invalid() {
457        // Invalid base64
458        let result: Result<PublicKey, _> = serde_json::from_str("\"not-valid-base64!!!\"");
459        assert!(result.is_err());
460
461        // Valid base64 but invalid key data
462        let result: Result<PublicKey, _> = serde_json::from_str("\"aGVsbG8gd29ybGQ=\"");
463        assert!(result.is_err());
464
465        // Not a string
466        let result: Result<PublicKey, _> = serde_json::from_str("123");
467        assert!(result.is_err());
468    }
469}