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