bitwarden_crypto/enc_string/
asymmetric.rs1use std::{borrow::Cow, fmt::Display, str::FromStr};
2
3use bitwarden_encoding::{B64, FromStrVisitor};
4pub use internal::UnsignedSharedKey;
5use rsa::Oaep;
6use serde::Deserialize;
7
8use super::{from_b64_vec, split_enc_string};
9use crate::{
10 BitwardenLegacyKeyBytes, KeyIds, KeyStoreContext, PrivateKey, PublicKey, RawPrivateKey,
11 RawPublicKey, SymmetricCryptoKey,
12 error::{CryptoError, EncStringParseError, Result},
13 rsa::encrypt_rsa2048_oaep_sha1,
14};
15#[allow(deprecated)]
18mod internal {
19 #[cfg(feature = "wasm")]
20 #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
21 const TS_CUSTOM_TYPES: &'static str = r#"
22 export type UnsignedSharedKey = Tagged<string, "UnsignedSharedKey">;
23 "#;
24
25 #[allow(missing_docs)]
59 #[derive(Clone, zeroize::ZeroizeOnDrop)]
60 #[allow(unused, non_camel_case_types)]
61 pub enum UnsignedSharedKey {
62 Rsa2048_OaepSha256_B64 { data: Vec<u8> },
64 Rsa2048_OaepSha1_B64 { data: Vec<u8> },
66 #[deprecated]
68 Rsa2048_OaepSha256_HmacSha256_B64 { data: Vec<u8>, mac: Vec<u8> },
69 #[deprecated]
71 Rsa2048_OaepSha1_HmacSha256_B64 { data: Vec<u8>, mac: Vec<u8> },
72 }
73}
74
75impl std::fmt::Debug for UnsignedSharedKey {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 f.debug_struct("UnsignedSharedKey").finish()
80 }
81}
82
83impl FromStr for UnsignedSharedKey {
85 type Err = CryptoError;
86
87 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 let (enc_type, parts) = split_enc_string(s);
89 match (enc_type, parts.len()) {
90 ("3", 1) => {
91 let data = from_b64_vec(parts[0])?;
92 Ok(UnsignedSharedKey::Rsa2048_OaepSha256_B64 { data })
93 }
94 ("4", 1) => {
95 let data = from_b64_vec(parts[0])?;
96 Ok(UnsignedSharedKey::Rsa2048_OaepSha1_B64 { data })
97 }
98 #[allow(deprecated)]
99 ("5", 2) => {
100 let data = from_b64_vec(parts[0])?;
101 let mac: Vec<u8> = from_b64_vec(parts[1])?;
102 Ok(UnsignedSharedKey::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac })
103 }
104 #[allow(deprecated)]
105 ("6", 2) => {
106 let data = from_b64_vec(parts[0])?;
107 let mac: Vec<u8> = from_b64_vec(parts[1])?;
108 Ok(UnsignedSharedKey::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac })
109 }
110
111 (enc_type, parts) => Err(EncStringParseError::InvalidTypeAsymm {
112 enc_type: enc_type.to_string(),
113 parts,
114 }
115 .into()),
116 }
117 }
118}
119
120impl Display for UnsignedSharedKey {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 let parts: Vec<&[u8]> = match self {
123 UnsignedSharedKey::Rsa2048_OaepSha256_B64 { data } => vec![data],
124 UnsignedSharedKey::Rsa2048_OaepSha1_B64 { data } => vec![data],
125 #[allow(deprecated)]
126 UnsignedSharedKey::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac } => {
127 vec![data, mac]
128 }
129 #[allow(deprecated)]
130 UnsignedSharedKey::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac } => {
131 vec![data, mac]
132 }
133 };
134
135 let encoded_parts: Vec<String> = parts
136 .iter()
137 .map(|part| B64::from(*part).to_string())
138 .collect();
139
140 write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?;
141
142 Ok(())
143 }
144}
145
146impl<'de> Deserialize<'de> for UnsignedSharedKey {
147 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148 where
149 D: serde::Deserializer<'de>,
150 {
151 deserializer.deserialize_str(FromStrVisitor::new())
152 }
153}
154
155impl serde::Serialize for UnsignedSharedKey {
156 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157 where
158 S: serde::Serializer,
159 {
160 serializer.serialize_str(&self.to_string())
161 }
162}
163
164impl UnsignedSharedKey {
165 #[deprecated(note = "Use encapsulate() instead")]
169 pub fn encapsulate_key_unsigned(
170 encapsulated_key: &SymmetricCryptoKey,
171 encapsulation_key: &PublicKey,
172 ) -> Result<UnsignedSharedKey> {
173 match encapsulation_key.inner() {
174 RawPublicKey::RsaOaepSha1(rsa_public_key) => {
175 Ok(UnsignedSharedKey::Rsa2048_OaepSha1_B64 {
176 data: encrypt_rsa2048_oaep_sha1(
177 rsa_public_key,
178 encapsulated_key.to_encoded().as_ref(),
179 )?,
180 })
181 }
182 }
183 }
184
185 pub fn encapsulate<Ids: KeyIds>(
189 key_to_encapsulate: Ids::Symmetric,
190 encapsulation_key: &PublicKey,
191 ctx: &KeyStoreContext<Ids>,
192 ) -> Result<UnsignedSharedKey> {
193 #[expect(deprecated)]
195 let encapsulated_key = ctx.dangerous_get_symmetric_key(key_to_encapsulate)?;
196 #[expect(deprecated)]
198 Self::encapsulate_key_unsigned(encapsulated_key, encapsulation_key)
199 }
200
201 const fn enc_type(&self) -> u8 {
203 match self {
204 UnsignedSharedKey::Rsa2048_OaepSha256_B64 { .. } => 3,
205 UnsignedSharedKey::Rsa2048_OaepSha1_B64 { .. } => 4,
206 #[allow(deprecated)]
207 UnsignedSharedKey::Rsa2048_OaepSha256_HmacSha256_B64 { .. } => 5,
208 #[allow(deprecated)]
209 UnsignedSharedKey::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6,
210 }
211 }
212}
213
214impl UnsignedSharedKey {
215 pub fn decapsulate<Ids: KeyIds>(
218 &self,
219 decapsulation_key: Ids::Private,
220 ctx: &mut KeyStoreContext<Ids>,
221 ) -> Result<Ids::Symmetric> {
222 #[expect(deprecated)]
224 let private_key = ctx.dangerous_get_private_key(decapsulation_key)?;
225 #[expect(deprecated)]
226 let key = Self::decapsulate_key_unsigned(self, private_key)
227 .map_err(|_| CryptoError::KeyDecrypt)?;
228 Ok(ctx.add_local_symmetric_key(key))
229 }
230
231 #[deprecated(note = "Use decapsulate() instead")]
235 pub fn decapsulate_key_unsigned(
236 &self,
237 decapsulation_key: &PrivateKey,
238 ) -> Result<SymmetricCryptoKey> {
239 match decapsulation_key.inner() {
240 RawPrivateKey::RsaOaepSha1(rsa_private_key) => {
241 use UnsignedSharedKey::*;
242 let key_data = match self {
243 Rsa2048_OaepSha256_B64 { data } => {
244 rsa_private_key.decrypt(Oaep::new::<sha2::Sha256>(), data)
245 }
246 Rsa2048_OaepSha1_B64 { data } => {
247 rsa_private_key.decrypt(Oaep::new::<sha1::Sha1>(), data)
248 }
249 #[allow(deprecated)]
250 Rsa2048_OaepSha256_HmacSha256_B64 { data, .. } => {
251 rsa_private_key.decrypt(Oaep::new::<sha2::Sha256>(), data)
252 }
253 #[allow(deprecated)]
254 Rsa2048_OaepSha1_HmacSha256_B64 { data, .. } => {
255 rsa_private_key.decrypt(Oaep::new::<sha1::Sha1>(), data)
256 }
257 }
258 .map_err(|_| CryptoError::KeyDecrypt)?;
259 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(key_data))
260 }
261 }
262 }
263}
264
265impl schemars::JsonSchema for UnsignedSharedKey {
269 fn schema_name() -> Cow<'static, str> {
270 "UnsignedSharedKey".into()
271 }
272
273 fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
274 generator.subschema_for::<String>()
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use schemars::schema_for;
281
282 use super::UnsignedSharedKey;
283 use crate::{PrivateKey, SymmetricCryptoKey};
284
285 const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY-----
286MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS
2878HzYUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2
288e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86LnhD56A9FDUfuI0dVnPcrwNv0YJIo9
2894LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfa
290F4/YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6A
291QOajdZijfEvepgnOe7cQ7aeatiOJFrjTApKPGxOVRzEMX4XS4xbyhH0QxQeB6l16
292l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq
29392qBuwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tP
294dr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapjWpxEF+11x7r+wM+0xRZQ8sNFYG46a
295PfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLX
296UIh5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTR
297buDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2
298hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxuc
299vOUBeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjA
300hCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIfTFKC/hDk6FKZlgwvupWYJyU9Rkyfs
301tPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQY
302UcUq4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vs
303zv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVvq1UTXIeQcQnoY5lGHJl3K8mbS3TnX
304E6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEP
305jNX5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBez
306MRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1eLLGd7YV0H+J3fgNc7gGWK51hOrF9
307JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXg
308AoEZ18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGp
309Is3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8+tPVgppLcG0+tMdLjigFQiDUQk2y3
310WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEz
311XKZBokBGnjFnTnKcs7nv/O8=
312-----END PRIVATE KEY-----";
313
314 #[test]
315 fn test_enc_string_rsa2048_oaep_sha256_b64() {
316 let key_pair = PrivateKey::from_pem(RSA_PRIVATE_KEY).unwrap();
317 let enc_str: &str = "3.SUx5gWrgmAKs/S1BoQrqOmx2Hl5fPVBVHokW17Flvm4TpBnJJRkfoitp7Jc4dfazPYjWGlckJz6X+qe+/AWilS1mxtzS0PmDy7tS5xP0GRlB39dstCd5jDw1wPmTbXiLcQ5VTvzpRAfRMEYVveTsEvVTByvEYAGSn4TnCsUDykyhRbD0YcJ4r1KHLs1b3BCBy2M1Gl5nmwckH08CAXaf8VfuBFStAGRKueovqp4euneQla+4G4fXdVvb8qKPnu0iVuALIE6nUNmeOiA3xN3d+akMxbbGxrQ1Ca4TYWjHVdj9C6abngQHkjKNYQwGUXrYo160hP4LIHn/huK6bZe5dQ==";
318 let enc_string: UnsignedSharedKey = enc_str.parse().unwrap();
319
320 let test_key = SymmetricCryptoKey::generate_seeded_for_unit_tests("test");
321 assert_eq!(enc_string.enc_type(), 3);
322
323 #[expect(deprecated)]
324 let res = enc_string.decapsulate_key_unsigned(&key_pair).unwrap();
325 assert_eq!(res, test_key);
326 }
327
328 #[test]
329 fn test_enc_string_rsa2048_oaep_sha1_b64() {
330 let private_key = PrivateKey::from_pem(RSA_PRIVATE_KEY).unwrap();
331 let enc_str: &str = "4.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww==";
332 let enc_string: UnsignedSharedKey = enc_str.parse().unwrap();
333
334 let test_key = SymmetricCryptoKey::generate_seeded_for_unit_tests("test");
335 assert_eq!(enc_string.enc_type(), 4);
336
337 #[expect(deprecated)]
338 let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap();
339 assert_eq!(res, test_key);
340 }
341
342 #[test]
343 fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() {
344 let private_key = PrivateKey::from_pem(RSA_PRIVATE_KEY).unwrap();
345 let enc_str: &str = "6.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww==|AA==";
346 let enc_string: UnsignedSharedKey = enc_str.parse().unwrap();
347
348 let test_key: SymmetricCryptoKey =
349 SymmetricCryptoKey::generate_seeded_for_unit_tests("test");
350 assert_eq!(enc_string.enc_type(), 6);
351
352 #[expect(deprecated)]
353 let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap();
354 assert_eq!(res.to_base64(), test_key.to_base64());
355 }
356
357 #[test]
358 fn test_enc_string_serialization() {
359 #[derive(serde::Serialize, serde::Deserialize)]
360 struct Test {
361 key: UnsignedSharedKey,
362 }
363
364 let cipher = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ=";
365 let serialized = format!("{{\"key\":\"{cipher}\"}}");
366
367 let t = serde_json::from_str::<Test>(&serialized).unwrap();
368 assert_eq!(t.key.enc_type(), 6);
369 assert_eq!(t.key.to_string(), cipher);
370 assert_eq!(serde_json::to_string(&t).unwrap(), serialized);
371 }
372
373 #[test]
374 fn test_from_str_invalid() {
375 let enc_str = "7.ABC";
376 let enc_string: Result<UnsignedSharedKey, _> = enc_str.parse();
377
378 let err = enc_string.unwrap_err();
379 assert_eq!(
380 err.to_string(),
381 "EncString error, Invalid asymmetric type, got type 7 with 1 parts"
382 );
383 }
384
385 #[test]
386 fn test_debug_format() {
387 let enc_str: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw==";
388 let enc_string: UnsignedSharedKey = enc_str.parse().unwrap();
389
390 let debug_string = format!("{enc_string:?}");
391 assert_eq!(debug_string, "UnsignedSharedKey");
392 }
393
394 #[test]
395 fn test_json_schema() {
396 let schema = schema_for!(UnsignedSharedKey);
397
398 assert_eq!(
399 serde_json::to_string(&schema).unwrap(),
400 r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"UnsignedSharedKey","type":"string"}"#
401 );
402 }
403}