diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 259460734..df0aaa5d3 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -1,7 +1,10 @@ use std::str::FromStr; +use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; use bitwarden_crypto::{ - CryptoError, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey, SymmetricCryptoKey, + AsymmetricCryptoKey, AsymmetricPublicCryptoKey, CryptoError, Decryptable, EncString, + Encryptable, Kdf, KeyDecryptable, KeyEncryptable, KeyStore, MasterKey, SymmetricCryptoKey, + UnsignedSharedKey, }; use wasm_bindgen::prelude::*; @@ -107,6 +110,144 @@ impl PureCrypto { pub fn generate_user_key_xchacha20_poly1305() -> Vec { SymmetricCryptoKey::make_xchacha20_poly1305_key().to_encoded() } + + // Key wrap + pub fn wrap_symmetric_key( + key_to_be_wrapped: Vec, + wrapping_key: Vec, + ) -> Result { + let tmp_store: KeyStore = KeyStore::default(); + let mut context = tmp_store.context(); + #[allow(deprecated)] + context.set_symmetric_key( + SymmetricKeyId::Local("wrapping_key"), + SymmetricCryptoKey::try_from(wrapping_key)?, + )?; + #[allow(deprecated)] + context.set_symmetric_key( + SymmetricKeyId::Local("key_to_wrap"), + SymmetricCryptoKey::try_from(key_to_be_wrapped)?, + )?; + // Note: The order of arguments is different here, and should probably be refactored + Ok(context + .wrap_symmetric_key( + SymmetricKeyId::Local("wrapping_key"), + SymmetricKeyId::Local("key_to_wrap"), + )? + .to_string()) + } + + pub fn unwrap_symmetric_key( + wrapped_key: String, + wrapping_key: Vec, + ) -> Result, CryptoError> { + let tmp_store: KeyStore = KeyStore::default(); + let mut context = tmp_store.context(); + #[allow(deprecated)] + context.set_symmetric_key( + SymmetricKeyId::Local("wrapping_key"), + SymmetricCryptoKey::try_from(wrapping_key)?, + )?; + // Note: The order of arguments is different here, and should probably be refactored + context.unwrap_symmetric_key( + SymmetricKeyId::Local("wrapping_key"), + SymmetricKeyId::Local("wrapped_key"), + &EncString::from_str(wrapped_key.as_str())?, + )?; + #[allow(deprecated)] + let key = context.dangerous_get_symmetric_key(SymmetricKeyId::Local("wrapped_key"))?; + Ok(key.to_encoded()) + } + + pub fn wrap_encapsulation_key( + encapsulation_key: Vec, + wrapping_key: Vec, + ) -> Result { + let tmp_store: KeyStore = KeyStore::default(); + let mut context = tmp_store.context(); + #[allow(deprecated)] + context.set_symmetric_key( + SymmetricKeyId::Local("wrapping_key"), + SymmetricCryptoKey::try_from(wrapping_key)?, + )?; + // Note: The order of arguments is different here, and should probably be refactored + Ok(encapsulation_key + .encrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))? + .to_string()) + } + + pub fn unwrap_encapsulation_key( + wrapped_key: String, + wrapping_key: Vec, + ) -> Result, CryptoError> { + let tmp_store: KeyStore = KeyStore::default(); + let mut context = tmp_store.context(); + #[allow(deprecated)] + context.set_symmetric_key( + SymmetricKeyId::Local("wrapping_key"), + SymmetricCryptoKey::try_from(wrapping_key)?, + )?; + // Note: The order of arguments is different here, and should probably be refactored + EncString::from_str(wrapped_key.as_str())? + .decrypt(&mut context, SymmetricKeyId::Local("wrapping_key")) + } + + pub fn wrap_decapsulation_key( + decapsulation_key: Vec, + wrapping_key: Vec, + ) -> Result { + let tmp_store: KeyStore = KeyStore::default(); + let mut context = tmp_store.context(); + #[allow(deprecated)] + context.set_symmetric_key( + SymmetricKeyId::Local("wrapping_key"), + SymmetricCryptoKey::try_from(wrapping_key)?, + )?; + // Note: The order of arguments is different here, and should probably be refactored + Ok(decapsulation_key + .encrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))? + .to_string()) + } + + pub fn unwrap_decapsulation_key( + wrapped_key: String, + wrapping_key: Vec, + ) -> Result, CryptoError> { + let tmp_store: KeyStore = KeyStore::default(); + let mut context = tmp_store.context(); + #[allow(deprecated)] + context.set_symmetric_key( + SymmetricKeyId::Local("wrapping_key"), + SymmetricCryptoKey::try_from(wrapping_key)?, + )?; + // Note: The order of arguments is different here, and should probably be refactored + EncString::from_str(wrapped_key.as_str())? + .decrypt(&mut context, SymmetricKeyId::Local("wrapping_key")) + } + + // Key encapsulation + pub fn encapsulate_key_unsigned( + shared_key: Vec, + encapsulation_key: Vec, + ) -> Result { + let encapsulation_key = AsymmetricPublicCryptoKey::from_der(encapsulation_key.as_slice())?; + Ok(UnsignedSharedKey::encapsulate_key_unsigned( + &SymmetricCryptoKey::try_from(shared_key)?, + &encapsulation_key, + )? + .to_string()) + } + + pub fn decapsulate_key_unsigned( + encapsulated_key: String, + decapsulation_key: Vec, + ) -> Result, CryptoError> { + Ok(UnsignedSharedKey::from_str(encapsulated_key.as_str())? + .decapsulate_key_unsigned(&AsymmetricCryptoKey::from_der( + decapsulation_key.as_slice(), + )?)? + .to_encoded()) + } } #[cfg(test)] @@ -134,6 +275,35 @@ mod tests { 120, 104, 144, 4, 76, 3, ]; + const PEM_KEY: &str = "-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5 +qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc +afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm +qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv +b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw +P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam +rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi +szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx +0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+ +8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR +jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach +vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI +1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR +J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7 +l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ +TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9 +ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye +KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN +wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ +UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA +kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W +pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN +Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi +CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup +PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf +DnqOsltgPomWZ7xVfMkm9niL2OA= +-----END PRIVATE KEY-----"; + #[test] fn test_symmetric_decrypt() { let enc_string = EncString::from_str(ENCRYPTED).unwrap(); @@ -228,4 +398,58 @@ mod tests { .unwrap(); assert_eq!(user_key, decrypted_user_key); } + + #[test] + fn test_wrap_unwrap_symmetric_key() { + let key_to_be_wrapped = PureCrypto::generate_user_key_aes256_cbc_hmac(); + let wrapping_key = PureCrypto::generate_user_key_aes256_cbc_hmac(); + let wrapped_key = + PureCrypto::wrap_symmetric_key(key_to_be_wrapped.clone(), wrapping_key.clone()) + .unwrap(); + let unwrapped_key = PureCrypto::unwrap_symmetric_key(wrapped_key, wrapping_key).unwrap(); + assert_eq!(key_to_be_wrapped, unwrapped_key); + } + + #[test] + fn test_wrap_encapsulation_key() { + let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap(); + let encapsulation_key = decapsulation_key.to_public_der().unwrap(); + let wrapping_key = PureCrypto::generate_user_key_aes256_cbc_hmac(); + let wrapped_key = + PureCrypto::wrap_encapsulation_key(encapsulation_key.clone(), wrapping_key.clone()) + .unwrap(); + let unwrapped_key = + PureCrypto::unwrap_encapsulation_key(wrapped_key, wrapping_key).unwrap(); + assert_eq!(encapsulation_key, unwrapped_key); + } + + #[test] + fn test_wrap_decapsulation_key() { + let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap(); + let wrapping_key = PureCrypto::generate_user_key_aes256_cbc_hmac(); + let wrapped_key = PureCrypto::wrap_decapsulation_key( + decapsulation_key.to_der().unwrap(), + wrapping_key.clone(), + ) + .unwrap(); + let unwrapped_key = + PureCrypto::unwrap_decapsulation_key(wrapped_key, wrapping_key).unwrap(); + assert_eq!(decapsulation_key.to_der().unwrap(), unwrapped_key); + } + + #[test] + fn test_encapsulate_key_unsigned() { + let shared_key = PureCrypto::generate_user_key_aes256_cbc_hmac(); + let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap(); + let encapsulation_key = decapsulation_key.to_public_der().unwrap(); + let encapsulated_key = + PureCrypto::encapsulate_key_unsigned(shared_key.clone(), encapsulation_key.clone()) + .unwrap(); + let unwrapped_key = PureCrypto::decapsulate_key_unsigned( + encapsulated_key, + decapsulation_key.to_der().unwrap(), + ) + .unwrap(); + assert_eq!(shared_key, unwrapped_key); + } }