1use std::{fmt::Display, pin::Pin, str::FromStr};
2
3use bitwarden_encoding::{B64, FromStrVisitor};
4use ciborium::{Value, value::Integer};
5use coset::iana::{EnumI64, KeyParameter, KeyType, RsaKeyParameter};
6use rsa::{RsaPrivateKey, RsaPublicKey, pkcs8::DecodePublicKey, traits::PublicKeyParts};
7use serde::{Deserialize, Serialize};
8use serde_repr::{Deserialize_repr, Serialize_repr};
9#[cfg(feature = "wasm")]
10use wasm_bindgen::convert::FromWasmAbi;
11
12use super::key_encryptable::CryptoKey;
13use crate::{
14 CoseKeyThumbprint, Pkcs8PrivateKeyBytes, SpkiPublicKeyBytes,
15 cose::{CoseKeyThumbprintExt, thumbprint_from_required_params},
16 error::{CryptoError, Result},
17};
18
19#[cfg(feature = "wasm")]
20#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
21const TS_CUSTOM_TYPES: &'static str = r#"
22export type PublicKey = Tagged<string, "PublicKey">;
23"#;
24
25#[cfg(feature = "wasm")]
26impl wasm_bindgen::describe::WasmDescribe for PublicKey {
27 fn describe() {
28 <String as wasm_bindgen::describe::WasmDescribe>::describe();
29 }
30}
31
32#[cfg(feature = "wasm")]
33impl FromWasmAbi for PublicKey {
34 type Abi = <String as FromWasmAbi>::Abi;
35
36 unsafe fn from_abi(abi: Self::Abi) -> Self {
37 use wasm_bindgen::UnwrapThrowExt;
38
39 let s = unsafe { String::from_abi(abi) };
40 let bytes: Vec<u8> = s.parse::<bitwarden_encoding::B64>().unwrap_throw().into();
41 PublicKey::from_der(&SpkiPublicKeyBytes::from(bytes)).unwrap_throw()
42 }
43}
44
45#[derive(Debug, Serialize_repr, Deserialize_repr)]
47#[repr(u8)]
48pub enum PublicKeyEncryptionAlgorithm {
49 RsaOaepSha1 = 0,
51}
52
53#[derive(Clone, PartialEq)]
54pub(crate) enum RawPublicKey {
55 RsaOaepSha1(RsaPublicKey),
56}
57
58#[derive(Clone, PartialEq)]
61pub struct PublicKey {
62 inner: RawPublicKey,
63}
64
65impl std::fmt::Debug for PublicKey {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 let key_suffix = match &self.inner {
68 RawPublicKey::RsaOaepSha1(_) => "RsaOaepSha1",
69 };
70 let mut debug_struct = f.debug_struct(format!("PublicKey::{}", key_suffix).as_str());
71 match &self.inner {
72 RawPublicKey::RsaOaepSha1(_) => {
73 if let Ok(der) = self.to_der() {
74 debug_struct.field("key", &hex::encode(der.as_ref()));
75 }
76 }
77 }
78 debug_struct.finish()
79 }
80}
81
82impl PublicKey {
83 pub(crate) fn inner(&self) -> &RawPublicKey {
84 &self.inner
85 }
86
87 #[bitwarden_logging::instrument(err)]
89 pub fn from_der(der: &SpkiPublicKeyBytes) -> Result<Self> {
90 Ok(PublicKey {
91 inner: RawPublicKey::RsaOaepSha1(
92 RsaPublicKey::from_public_key_der(der.as_ref())
93 .map_err(|_| CryptoError::InvalidKey)?,
94 ),
95 })
96 }
97
98 #[bitwarden_logging::instrument(err)]
100 pub fn to_der(&self) -> Result<SpkiPublicKeyBytes> {
101 use rsa::pkcs8::EncodePublicKey;
102 match &self.inner {
103 RawPublicKey::RsaOaepSha1(public_key) => Ok(public_key
104 .to_public_key_der()
105 .map_err(|_| CryptoError::InvalidKey)?
106 .as_bytes()
107 .to_owned()
108 .into()),
109 }
110 }
111}
112
113impl CoseKeyThumbprintExt for PublicKey {
114 fn thumbprint(&self) -> Result<CoseKeyThumbprint> {
115 let params = match &self.inner {
116 RawPublicKey::RsaOaepSha1(key) => vec![
117 (
118 KeyParameter::Kty.to_i64(),
119 Value::Integer(Integer::from(KeyType::RSA.to_i64())),
120 ),
121 (
125 RsaKeyParameter::N.to_i64(),
126 Value::Bytes(key.n().to_be_bytes_trimmed_vartime().into_vec()),
127 ),
128 (
129 RsaKeyParameter::E.to_i64(),
130 Value::Bytes(key.e().to_be_bytes_trimmed_vartime().into_vec()),
131 ),
132 ],
133 };
134 Ok(thumbprint_from_required_params(params))
135 }
136}
137
138impl FromStr for PublicKey {
139 type Err = ();
140
141 fn from_str(s: &str) -> Result<Self, Self::Err> {
142 let bytes: Vec<u8> = s.parse::<B64>().map_err(|_| ())?.into();
143 Self::from_der(&SpkiPublicKeyBytes::from(bytes)).map_err(|_| ())
144 }
145}
146
147impl Display for PublicKey {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 match self.to_der() {
150 Ok(der) => write!(f, "{}", B64::from(der.as_ref())),
151 Err(_) => write!(f, "[INVALID PUBLIC KEY]"),
152 }
153 }
154}
155
156impl<'de> Deserialize<'de> for PublicKey {
157 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
158 where
159 D: serde::Deserializer<'de>,
160 {
161 deserializer.deserialize_str(FromStrVisitor::new())
162 }
163}
164
165impl Serialize for PublicKey {
166 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
167 where
168 S: serde::Serializer,
169 {
170 let der = self.to_der().map_err(serde::ser::Error::custom)?;
171 serializer.serialize_str(&B64::from(der.as_ref()).to_string())
172 }
173}
174
175#[derive(Clone)]
176pub(crate) enum RawPrivateKey {
177 RsaOaepSha1(Pin<Box<RsaPrivateKey>>),
182}
183
184#[derive(Clone)]
187pub struct PrivateKey {
188 inner: RawPrivateKey,
189}
190
191const _: fn() = || {
194 fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>() {}
195 assert_zeroize_on_drop::<RsaPrivateKey>();
196};
197impl zeroize::ZeroizeOnDrop for PrivateKey {}
198impl CryptoKey for PrivateKey {}
199
200impl PrivateKey {
201 pub fn make(algorithm: PublicKeyEncryptionAlgorithm) -> Self {
203 Self::make_internal(algorithm, &mut bitwarden_random::rng())
204 }
205
206 fn make_internal<R: rand::CryptoRng + rand::Rng>(
207 algorithm: PublicKeyEncryptionAlgorithm,
208 rng: &mut R,
209 ) -> Self {
210 match algorithm {
211 PublicKeyEncryptionAlgorithm::RsaOaepSha1 => Self {
212 inner: RawPrivateKey::RsaOaepSha1(Box::pin(
213 RsaPrivateKey::new(rng, 2048).expect("failed to generate a key"),
214 )),
215 },
216 }
217 }
218
219 #[allow(missing_docs)]
220 #[cfg_attr(
225 feature = "dangerous-crypto-debug",
226 allow(unknown_lints, tracing_instrument)
227 )]
228 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
229 #[cfg_attr(
230 not(feature = "dangerous-crypto-debug"),
231 bitwarden_logging::instrument(err)
232 )]
233 pub fn from_pem(pem: &str) -> Result<Self> {
234 use rsa::pkcs8::DecodePrivateKey;
235 Ok(Self {
236 inner: RawPrivateKey::RsaOaepSha1(Box::pin(
237 RsaPrivateKey::from_pkcs8_pem(pem).map_err(|_| CryptoError::InvalidKey)?,
238 )),
239 })
240 }
241
242 #[allow(missing_docs)]
243 #[cfg_attr(
244 feature = "dangerous-crypto-debug",
245 allow(unknown_lints, tracing_instrument)
246 )]
247 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
248 #[cfg_attr(
249 not(feature = "dangerous-crypto-debug"),
250 bitwarden_logging::instrument(err)
251 )]
252 pub fn from_der(der: &Pkcs8PrivateKeyBytes) -> Result<Self> {
253 use rsa::pkcs8::DecodePrivateKey;
254 Ok(Self {
255 inner: RawPrivateKey::RsaOaepSha1(Box::pin(
256 RsaPrivateKey::from_pkcs8_der(der.as_ref()).map_err(|_| CryptoError::InvalidKey)?,
257 )),
258 })
259 }
260
261 #[allow(missing_docs)]
262 #[cfg_attr(
263 feature = "dangerous-crypto-debug",
264 allow(unknown_lints, tracing_instrument)
265 )]
266 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
267 #[cfg_attr(
268 not(feature = "dangerous-crypto-debug"),
269 bitwarden_logging::instrument(err)
270 )]
271 pub fn to_der(&self) -> Result<Pkcs8PrivateKeyBytes> {
272 match &self.inner {
273 RawPrivateKey::RsaOaepSha1(private_key) => {
274 use rsa::pkcs8::EncodePrivateKey;
275 Ok(private_key
276 .to_pkcs8_der()
277 .map_err(|_| CryptoError::InvalidKey)?
278 .as_bytes()
279 .to_owned()
280 .into())
281 }
282 }
283 }
284
285 pub fn to_public_key(&self) -> PublicKey {
288 match &self.inner {
289 RawPrivateKey::RsaOaepSha1(private_key) => PublicKey {
290 inner: RawPublicKey::RsaOaepSha1(private_key.to_public_key()),
291 },
292 }
293 }
294
295 pub(crate) fn inner(&self) -> &RawPrivateKey {
296 &self.inner
297 }
298}
299
300impl CoseKeyThumbprintExt for PrivateKey {
301 fn thumbprint(&self) -> Result<CoseKeyThumbprint> {
302 self.to_public_key().thumbprint()
303 }
304}
305
306impl std::fmt::Debug for PrivateKey {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 let key_suffix = match &self.inner {
310 RawPrivateKey::RsaOaepSha1(_) => "RsaOaepSha1",
311 };
312 let mut debug_struct = f.debug_struct(format!("PrivateKey::{}", key_suffix).as_str());
313 #[cfg(feature = "dangerous-crypto-debug")]
314 match &self.inner {
315 RawPrivateKey::RsaOaepSha1(_) => {
316 if let Ok(der) = self.to_der() {
317 debug_struct.field("key", &hex::encode(der.as_ref()));
318 }
319 }
320 }
321 debug_struct.finish()
322 }
323}
324
325#[cfg(test)]
326mod tests {
327
328 use bitwarden_encoding::B64;
329
330 use crate::{
331 CoseKeyThumbprintExt, Pkcs8PrivateKeyBytes, PrivateKey, PublicKey,
332 PublicKeyEncryptionAlgorithm, SpkiPublicKeyBytes, SymmetricCryptoKey, UnsignedSharedKey,
333 content_format::{Bytes, Pkcs8PrivateKeyDerContentFormat},
334 };
335
336 #[test]
337 #[ignore = "Manual test to verify debug format"]
338 fn test_debug() {
339 let private_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
340 println!("{:?}", private_key);
341 let public_key = private_key.to_public_key();
342 println!("{:?}", public_key);
343 }
344
345 #[test]
346 fn test_asymmetric_crypto_key() {
347 let pem_key_str = "-----BEGIN PRIVATE KEY-----
348MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5
349qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc
350afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm
351qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv
352b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw
353P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam
354rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi
355szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx
3560d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+
3578vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR
358jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach
359vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI
3601u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR
361J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7
362l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ
363TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9
364ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye
365KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN
366wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ
367UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA
368kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W
369pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN
370Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi
371CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup
372PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf
373DnqOsltgPomWZ7xVfMkm9niL2OA=
374-----END PRIVATE KEY-----";
375
376 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();
377 let der_key_vec: Vec<u8> = der_key.into();
378
379 let pem_key = PrivateKey::from_pem(pem_key_str).unwrap();
381 let der_key = PrivateKey::from_der(&Bytes::<Pkcs8PrivateKeyDerContentFormat>::from(
382 der_key_vec.clone(),
383 ))
384 .unwrap();
385 assert_eq!(pem_key.to_der().unwrap(), der_key.to_der().unwrap());
386
387 assert_eq!(der_key.to_der().unwrap().to_vec(), der_key_vec.clone());
390 assert_eq!(pem_key.to_der().unwrap().to_vec(), der_key_vec);
391 }
392
393 #[test]
394 fn test_encrypt_public_decrypt_private() {
395 let private_key: B64 = concat!(
396 "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH",
397 "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY",
398 "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL",
399 "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK",
400 "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2",
401 "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO",
402 "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ",
403 "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj",
404 "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B",
405 "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL",
406 "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4",
407 "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk",
408 "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro",
409 "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv",
410 "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH",
411 "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j",
412 "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU",
413 "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM",
414 "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC",
415 "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G",
416 "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/",
417 "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J",
418 "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94",
419 "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs",
420 "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R",
421 "GDgRMUB6cL3IRVzcR0dC6cY=",
422 )
423 .parse()
424 .unwrap();
425
426 let public_key: B64 = concat!(
427 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
428 "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
429 "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
430 "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
431 "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
432 "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
433 "XQIDAQAB",
434 )
435 .parse()
436 .unwrap();
437
438 let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
439 let private_key = PrivateKey::from_der(&private_key).unwrap();
440 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&public_key)).unwrap();
441
442 let raw_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
443 #[expect(deprecated)]
444 let encrypted = UnsignedSharedKey::encapsulate_key_unsigned(&raw_key, &public_key).unwrap();
445 #[expect(deprecated)]
446 let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap();
447
448 assert_eq!(raw_key, decrypted);
449 }
450
451 #[test]
452 fn test_asymmetric_public_crypto_key_from_str() {
453 let public_key_b64 = concat!(
454 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
455 "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
456 "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
457 "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
458 "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
459 "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
460 "XQIDAQAB",
461 );
462
463 let parsed_key: PublicKey = public_key_b64.parse().expect("should parse");
465
466 let der = parsed_key.to_der().expect("should convert to DER");
468 let b64_str = B64::from(der.as_ref()).to_string();
469 assert_eq!(b64_str, public_key_b64);
470 }
471
472 #[test]
473 fn test_asymmetric_public_crypto_key_from_str_invalid() {
474 let result: Result<PublicKey, _> = "not-valid-base64!!!".parse();
476 assert!(result.is_err());
477
478 let result: Result<PublicKey, _> = "aGVsbG8gd29ybGQ=".parse();
480 assert!(result.is_err());
481 }
482
483 #[test]
484 fn test_asymmetric_public_crypto_key_serialize_deserialize() {
485 let public_key_b64 = concat!(
486 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
487 "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
488 "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
489 "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
490 "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
491 "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
492 "XQIDAQAB",
493 );
494
495 let key: PublicKey = public_key_b64.parse().expect("should parse");
497
498 let serialized = serde_json::to_string(&key).expect("should serialize");
500 assert_eq!(serialized, format!("\"{}\"", public_key_b64));
501
502 let deserialized: PublicKey =
504 serde_json::from_str(&serialized).expect("should deserialize");
505
506 assert_eq!(
508 key.to_der().expect("should convert to DER"),
509 deserialized.to_der().expect("should convert to DER")
510 );
511 }
512
513 #[test]
514 fn test_asymmetric_public_crypto_key_deserialize_invalid() {
515 let result: Result<PublicKey, _> = serde_json::from_str("\"not-valid-base64!!!\"");
517 assert!(result.is_err());
518
519 let result: Result<PublicKey, _> = serde_json::from_str("\"aGVsbG8gd29ybGQ=\"");
521 assert!(result.is_err());
522
523 let result: Result<PublicKey, _> = serde_json::from_str("123");
525 assert!(result.is_err());
526 }
527
528 const RSA_PRIVATE_KEY_B64: &str = concat!(
530 "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH",
531 "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY",
532 "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL",
533 "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK",
534 "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2",
535 "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO",
536 "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ",
537 "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj",
538 "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B",
539 "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL",
540 "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4",
541 "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk",
542 "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro",
543 "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv",
544 "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH",
545 "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j",
546 "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU",
547 "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM",
548 "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC",
549 "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G",
550 "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/",
551 "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J",
552 "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94",
553 "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs",
554 "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R",
555 "GDgRMUB6cL3IRVzcR0dC6cY=",
556 );
557 const RSA_PUBLIC_KEY_B64: &str = concat!(
558 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
559 "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
560 "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
561 "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
562 "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
563 "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
564 "XQIDAQAB",
565 );
566
567 #[test]
568 fn test_thumbprint_private_matches_public() {
569 let private_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
570 assert_eq!(
571 private_key.thumbprint().unwrap(),
572 private_key.to_public_key().thumbprint().unwrap()
573 );
574 }
575
576 #[test]
577 fn test_thumbprint_rsa_vector() {
578 let private_key_bytes: B64 = RSA_PRIVATE_KEY_B64.parse().unwrap();
579 let private_key =
580 PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(private_key_bytes.as_bytes()))
581 .unwrap();
582 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(
583 RSA_PUBLIC_KEY_B64.parse::<B64>().unwrap().as_bytes(),
584 ))
585 .unwrap();
586
587 assert_eq!(
588 private_key.thumbprint().unwrap(),
589 public_key.thumbprint().unwrap()
590 );
591 assert_eq!(
592 public_key.thumbprint().unwrap().to_hex(),
593 "04fbcfa50c5805171304cc5b4794c25d77f7359d8a201828a5d7ef89162463aa"
594 );
595 }
596}