bitwarden_wasm_internal/
pure_crypto.rs

1use std::str::FromStr;
2
3use bitwarden_core::key_management::{KeyIds, SymmetricKeyId};
4use bitwarden_crypto::{
5    AsymmetricCryptoKey, AsymmetricPublicCryptoKey, CryptoError, Decryptable, EncString,
6    Encryptable, Kdf, KeyDecryptable, KeyEncryptable, KeyStore, MasterKey, SymmetricCryptoKey,
7    UnsignedSharedKey,
8};
9use wasm_bindgen::prelude::*;
10
11/// This module represents a stopgap solution to provide access to primitive crypto functions for JS
12/// clients. It is not intended to be used outside of the JS clients and this pattern should not be
13/// proliferated. It is necessary because we want to use SDK crypto prior to the SDK being fully
14/// responsible for state and keys.
15#[wasm_bindgen]
16pub struct PureCrypto {}
17
18// Encryption
19#[wasm_bindgen]
20impl PureCrypto {
21    /// DEPRECATED: Use `symmetric_decrypt_string` instead.
22    /// Cleanup ticket: <https://bitwarden.atlassian.net/browse/PM-21247>
23    pub fn symmetric_decrypt(enc_string: String, key: Vec<u8>) -> Result<String, CryptoError> {
24        Self::symmetric_decrypt_string(enc_string, key)
25    }
26
27    pub fn symmetric_decrypt_string(
28        enc_string: String,
29        key: Vec<u8>,
30    ) -> Result<String, CryptoError> {
31        EncString::from_str(&enc_string)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
32    }
33
34    pub fn symmetric_decrypt_bytes(
35        enc_string: String,
36        key: Vec<u8>,
37    ) -> Result<Vec<u8>, CryptoError> {
38        EncString::from_str(&enc_string)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
39    }
40
41    /// DEPRECATED: Use `symmetric_decrypt_filedata` instead.
42    /// Cleanup ticket: <https://bitwarden.atlassian.net/browse/PM-21247>
43    pub fn symmetric_decrypt_array_buffer(
44        enc_bytes: Vec<u8>,
45        key: Vec<u8>,
46    ) -> Result<Vec<u8>, CryptoError> {
47        Self::symmetric_decrypt_filedata(enc_bytes, key)
48    }
49
50    pub fn symmetric_decrypt_filedata(
51        enc_bytes: Vec<u8>,
52        key: Vec<u8>,
53    ) -> Result<Vec<u8>, CryptoError> {
54        EncString::from_buffer(&enc_bytes)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
55    }
56
57    pub fn symmetric_encrypt_string(plain: String, key: Vec<u8>) -> Result<String, CryptoError> {
58        plain
59            .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
60            .map(|enc| enc.to_string())
61    }
62
63    pub fn symmetric_encrypt_bytes(plain: Vec<u8>, key: Vec<u8>) -> Result<String, CryptoError> {
64        plain
65            .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
66            .map(|enc| enc.to_string())
67    }
68
69    pub fn symmetric_encrypt_filedata(
70        plain: Vec<u8>,
71        key: Vec<u8>,
72    ) -> Result<Vec<u8>, CryptoError> {
73        plain
74            .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?)?
75            .to_buffer()
76    }
77
78    pub fn decrypt_user_key_with_master_password(
79        encrypted_user_key: String,
80        master_password: String,
81        email: String,
82        kdf: Kdf,
83    ) -> Result<Vec<u8>, CryptoError> {
84        let master_key = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?;
85        let encrypted_user_key = EncString::from_str(&encrypted_user_key)?;
86        let result = master_key
87            .decrypt_user_key(encrypted_user_key)
88            .map_err(|_| CryptoError::InvalidKey)?;
89        Ok(result.to_encoded())
90    }
91
92    pub fn encrypt_user_key_with_master_password(
93        user_key: Vec<u8>,
94        master_password: String,
95        email: String,
96        kdf: Kdf,
97    ) -> Result<String, CryptoError> {
98        let master_key = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?;
99        let user_key = SymmetricCryptoKey::try_from(user_key)?;
100        let result = master_key.encrypt_user_key(&user_key)?;
101        Ok(result.to_string())
102    }
103
104    pub fn make_user_key_aes256_cbc_hmac() -> Vec<u8> {
105        SymmetricCryptoKey::make_aes256_cbc_hmac_key().to_encoded()
106    }
107
108    pub fn make_user_key_xchacha20_poly1305() -> Vec<u8> {
109        SymmetricCryptoKey::make_xchacha20_poly1305_key().to_encoded()
110    }
111
112    /// Wraps (encrypts) a symmetric key using a symmetric wrapping key, returning the wrapped key
113    /// as an EncString.
114    pub fn wrap_symmetric_key(
115        key_to_be_wrapped: Vec<u8>,
116        wrapping_key: Vec<u8>,
117    ) -> Result<String, CryptoError> {
118        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
119        let mut context = tmp_store.context();
120        #[allow(deprecated)]
121        context.set_symmetric_key(
122            SymmetricKeyId::Local("wrapping_key"),
123            SymmetricCryptoKey::try_from(wrapping_key)?,
124        )?;
125        #[allow(deprecated)]
126        context.set_symmetric_key(
127            SymmetricKeyId::Local("key_to_wrap"),
128            SymmetricCryptoKey::try_from(key_to_be_wrapped)?,
129        )?;
130        // Note: The order of arguments is different here, and should probably be refactored
131        Ok(context
132            .wrap_symmetric_key(
133                SymmetricKeyId::Local("wrapping_key"),
134                SymmetricKeyId::Local("key_to_wrap"),
135            )?
136            .to_string())
137    }
138
139    /// Unwraps (decrypts) a wrapped symmetric key using a symmetric wrapping key, returning the
140    /// unwrapped key as a serialized byte array.
141    pub fn unwrap_symmetric_key(
142        wrapped_key: String,
143        wrapping_key: Vec<u8>,
144    ) -> Result<Vec<u8>, CryptoError> {
145        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
146        let mut context = tmp_store.context();
147        #[allow(deprecated)]
148        context.set_symmetric_key(
149            SymmetricKeyId::Local("wrapping_key"),
150            SymmetricCryptoKey::try_from(wrapping_key)?,
151        )?;
152        // Note: The order of arguments is different here, and should probably be refactored
153        context.unwrap_symmetric_key(
154            SymmetricKeyId::Local("wrapping_key"),
155            SymmetricKeyId::Local("wrapped_key"),
156            &EncString::from_str(wrapped_key.as_str())?,
157        )?;
158        #[allow(deprecated)]
159        let key = context.dangerous_get_symmetric_key(SymmetricKeyId::Local("wrapped_key"))?;
160        Ok(key.to_encoded())
161    }
162
163    /// Wraps (encrypts) an SPKI DER encoded encapsulation (public) key using a symmetric wrapping
164    /// key. Note: Usually, a public key is - by definition - public, so this should not be
165    /// used. The specific use-case for this function is to enable rotateable key sets, where
166    /// the "public key" is not public, with the intent of preventing the server from being able
167    /// to overwrite the user key unlocked by the rotateable keyset.
168    pub fn wrap_encapsulation_key(
169        encapsulation_key: Vec<u8>,
170        wrapping_key: Vec<u8>,
171    ) -> Result<String, CryptoError> {
172        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
173        let mut context = tmp_store.context();
174        #[allow(deprecated)]
175        context.set_symmetric_key(
176            SymmetricKeyId::Local("wrapping_key"),
177            SymmetricCryptoKey::try_from(wrapping_key)?,
178        )?;
179        // Note: The order of arguments is different here, and should probably be refactored
180        Ok(encapsulation_key
181            .encrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))?
182            .to_string())
183    }
184
185    /// Unwraps (decrypts) a wrapped SPKI DER encoded encapsulation (public) key using a symmetric
186    /// wrapping key.
187    pub fn unwrap_encapsulation_key(
188        wrapped_key: String,
189        wrapping_key: Vec<u8>,
190    ) -> Result<Vec<u8>, CryptoError> {
191        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
192        let mut context = tmp_store.context();
193        #[allow(deprecated)]
194        context.set_symmetric_key(
195            SymmetricKeyId::Local("wrapping_key"),
196            SymmetricCryptoKey::try_from(wrapping_key)?,
197        )?;
198        // Note: The order of arguments is different here, and should probably be refactored
199        EncString::from_str(wrapped_key.as_str())?
200            .decrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))
201    }
202
203    /// Wraps (encrypts) a PKCS8 DER encoded decapsulation (private) key using a symmetric wrapping
204    /// key,
205    pub fn wrap_decapsulation_key(
206        decapsulation_key: Vec<u8>,
207        wrapping_key: Vec<u8>,
208    ) -> Result<String, CryptoError> {
209        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
210        let mut context = tmp_store.context();
211        #[allow(deprecated)]
212        context.set_symmetric_key(
213            SymmetricKeyId::Local("wrapping_key"),
214            SymmetricCryptoKey::try_from(wrapping_key)?,
215        )?;
216        // Note: The order of arguments is different here, and should probably be refactored
217        Ok(decapsulation_key
218            .encrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))?
219            .to_string())
220    }
221
222    /// Unwraps (decrypts) a wrapped PKCS8 DER encoded decapsulation (private) key using a symmetric
223    /// wrapping key.
224    pub fn unwrap_decapsulation_key(
225        wrapped_key: String,
226        wrapping_key: Vec<u8>,
227    ) -> Result<Vec<u8>, CryptoError> {
228        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
229        let mut context = tmp_store.context();
230        #[allow(deprecated)]
231        context.set_symmetric_key(
232            SymmetricKeyId::Local("wrapping_key"),
233            SymmetricCryptoKey::try_from(wrapping_key)?,
234        )?;
235        // Note: The order of arguments is different here, and should probably be refactored
236        EncString::from_str(wrapped_key.as_str())?
237            .decrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))
238    }
239
240    /// Encapsulates (encrypts) a symmetric key using an asymmetric encapsulation key (public key)
241    /// in SPKI format, returning the encapsulated key as a string. Note: This is unsigned, so
242    /// the sender's authenticity cannot be verified by the recipient.
243    pub fn encapsulate_key_unsigned(
244        shared_key: Vec<u8>,
245        encapsulation_key: Vec<u8>,
246    ) -> Result<String, CryptoError> {
247        let encapsulation_key = AsymmetricPublicCryptoKey::from_der(encapsulation_key.as_slice())?;
248        Ok(UnsignedSharedKey::encapsulate_key_unsigned(
249            &SymmetricCryptoKey::try_from(shared_key)?,
250            &encapsulation_key,
251        )?
252        .to_string())
253    }
254
255    /// Decapsulates (decrypts) a symmetric key using an decapsulation key (private key) in PKCS8
256    /// DER format. Note: This is unsigned, so the sender's authenticity cannot be verified by the
257    /// recipient.
258    pub fn decapsulate_key_unsigned(
259        encapsulated_key: String,
260        decapsulation_key: Vec<u8>,
261    ) -> Result<Vec<u8>, CryptoError> {
262        Ok(UnsignedSharedKey::from_str(encapsulated_key.as_str())?
263            .decapsulate_key_unsigned(&AsymmetricCryptoKey::from_der(
264                decapsulation_key.as_slice(),
265            )?)?
266            .to_encoded())
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use std::{num::NonZero, str::FromStr};
273
274    use bitwarden_crypto::EncString;
275
276    use super::*;
277
278    const KEY: &[u8] = &[
279        81, 142, 1, 228, 222, 3, 3, 133, 34, 176, 35, 66, 150, 6, 109, 70, 190, 149, 47, 47, 89,
280        23, 144, 87, 92, 46, 220, 13, 148, 106, 162, 234, 202, 139, 136, 33, 16, 200, 8, 73, 176,
281        172, 185, 187, 224, 10, 65, 223, 228, 54, 92, 181, 8, 213, 162, 221, 117, 254, 245, 111,
282        55, 211, 77, 29,
283    ];
284
285    const ENCRYPTED: &str = "2.Dh7AFLXR+LXcxUaO5cRjpg==|uXyhubjAoNH8lTdy/zgJDQ==|cHEMboj0MYsU5yDRQ1rLCgxcjNbKRc1PWKuv8bpU5pM=";
286    const DECRYPTED: &str = "test";
287    const DECRYPTED_BYTES: &[u8] = b"test";
288    const ENCRYPTED_BYTES: &[u8] = &[
289        2, 209, 195, 115, 49, 205, 253, 128, 162, 169, 246, 175, 217, 144, 73, 108, 191, 27, 113,
290        69, 55, 94, 142, 62, 129, 204, 173, 130, 37, 42, 97, 209, 25, 192, 64, 126, 112, 139, 248,
291        2, 89, 112, 178, 83, 25, 77, 130, 187, 127, 85, 179, 211, 159, 186, 111, 44, 109, 211, 18,
292        120, 104, 144, 4, 76, 3,
293    ];
294
295    const PEM_KEY: &str = "-----BEGIN PRIVATE KEY-----
296MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5
297qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc
298afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm
299qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv
300b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw
301P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam
302rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi
303szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx
3040d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+
3058vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR
306jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach
307vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI
3081u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR
309J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7
310l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ
311TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9
312ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye
313KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN
314wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ
315UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA
316kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W
317pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN
318Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi
319CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup
320PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf
321DnqOsltgPomWZ7xVfMkm9niL2OA=
322-----END PRIVATE KEY-----";
323
324    #[test]
325    fn test_symmetric_decrypt() {
326        let enc_string = EncString::from_str(ENCRYPTED).unwrap();
327
328        let result = PureCrypto::symmetric_decrypt_string(enc_string.to_string(), KEY.to_vec());
329        assert!(result.is_ok());
330        assert_eq!(result.unwrap(), DECRYPTED);
331    }
332
333    #[test]
334    fn test_symmetric_encrypt() {
335        let result = PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec());
336        assert!(result.is_ok());
337        // Cannot test encrypted string content because IV is unique per encryption
338    }
339
340    #[test]
341    fn test_symmetric_string_round_trip() {
342        let encrypted =
343            PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec()).unwrap();
344        let decrypted =
345            PureCrypto::symmetric_decrypt_string(encrypted.clone(), KEY.to_vec()).unwrap();
346        assert_eq!(decrypted, DECRYPTED);
347    }
348
349    #[test]
350    fn test_symmetric_bytes_round_trip() {
351        let encrypted =
352            PureCrypto::symmetric_encrypt_bytes(DECRYPTED.as_bytes().to_vec(), KEY.to_vec())
353                .unwrap();
354        let decrypted =
355            PureCrypto::symmetric_decrypt_bytes(encrypted.clone(), KEY.to_vec()).unwrap();
356        assert_eq!(decrypted, DECRYPTED.as_bytes().to_vec());
357    }
358
359    #[test]
360    fn test_symmetric_decrypt_array_buffer() {
361        let result = PureCrypto::symmetric_decrypt_filedata(ENCRYPTED_BYTES.to_vec(), KEY.to_vec());
362        assert!(result.is_ok());
363        assert_eq!(result.unwrap(), DECRYPTED_BYTES);
364    }
365
366    #[test]
367    fn test_symmetric_encrypt_to_array_buffer() {
368        let result = PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec());
369        assert!(result.is_ok());
370        // Cannot test encrypted string content because IV is unique per encryption
371    }
372
373    #[test]
374    fn test_symmetric_filedata_round_trip() {
375        let encrypted =
376            PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()).unwrap();
377        let decrypted =
378            PureCrypto::symmetric_decrypt_filedata(encrypted.clone(), KEY.to_vec()).unwrap();
379        assert_eq!(decrypted, DECRYPTED_BYTES);
380    }
381
382    #[test]
383    fn test_make_aes256_cbc_hmac_key() {
384        let key = PureCrypto::make_user_key_aes256_cbc_hmac();
385        assert_eq!(key.len(), 64);
386    }
387
388    #[test]
389    fn test_make_xchacha20_poly1305_key() {
390        let key = PureCrypto::make_user_key_xchacha20_poly1305();
391        assert!(key.len() > 64);
392    }
393
394    #[test]
395    fn roundtrip_encrypt_user_key_with_master_password() {
396        let master_password = "test";
397        let email = "[email protected]";
398        let kdf = Kdf::PBKDF2 {
399            iterations: NonZero::try_from(600000).unwrap(),
400        };
401        let user_key = PureCrypto::make_user_key_aes256_cbc_hmac();
402        let encrypted_user_key = PureCrypto::encrypt_user_key_with_master_password(
403            user_key.clone(),
404            master_password.to_string(),
405            email.to_string(),
406            kdf.clone(),
407        )
408        .unwrap();
409        let decrypted_user_key = PureCrypto::decrypt_user_key_with_master_password(
410            encrypted_user_key,
411            master_password.to_string(),
412            email.to_string(),
413            kdf,
414        )
415        .unwrap();
416        assert_eq!(user_key, decrypted_user_key);
417    }
418
419    #[test]
420    fn test_wrap_unwrap_symmetric_key() {
421        let key_to_be_wrapped = PureCrypto::make_user_key_aes256_cbc_hmac();
422        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
423        let wrapped_key =
424            PureCrypto::wrap_symmetric_key(key_to_be_wrapped.clone(), wrapping_key.clone())
425                .unwrap();
426        let unwrapped_key = PureCrypto::unwrap_symmetric_key(wrapped_key, wrapping_key).unwrap();
427        assert_eq!(key_to_be_wrapped, unwrapped_key);
428    }
429
430    #[test]
431    fn test_wrap_encapsulation_key() {
432        let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap();
433        let encapsulation_key = decapsulation_key.to_public_der().unwrap();
434        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
435        let wrapped_key =
436            PureCrypto::wrap_encapsulation_key(encapsulation_key.clone(), wrapping_key.clone())
437                .unwrap();
438        let unwrapped_key =
439            PureCrypto::unwrap_encapsulation_key(wrapped_key, wrapping_key).unwrap();
440        assert_eq!(encapsulation_key, unwrapped_key);
441    }
442
443    #[test]
444    fn test_wrap_decapsulation_key() {
445        let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap();
446        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
447        let wrapped_key = PureCrypto::wrap_decapsulation_key(
448            decapsulation_key.to_der().unwrap(),
449            wrapping_key.clone(),
450        )
451        .unwrap();
452        let unwrapped_key =
453            PureCrypto::unwrap_decapsulation_key(wrapped_key, wrapping_key).unwrap();
454        assert_eq!(decapsulation_key.to_der().unwrap(), unwrapped_key);
455    }
456
457    #[test]
458    fn test_encapsulate_key_unsigned() {
459        let shared_key = PureCrypto::make_user_key_aes256_cbc_hmac();
460        let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap();
461        let encapsulation_key = decapsulation_key.to_public_der().unwrap();
462        let encapsulated_key =
463            PureCrypto::encapsulate_key_unsigned(shared_key.clone(), encapsulation_key.clone())
464                .unwrap();
465        let unwrapped_key = PureCrypto::decapsulate_key_unsigned(
466            encapsulated_key,
467            decapsulation_key.to_der().unwrap(),
468        )
469        .unwrap();
470        assert_eq!(shared_key, unwrapped_key);
471    }
472}