Skip to content

Commit 5449a5a

Browse files
authored
[PM-21377] Add missing purecrypto functions (#267)
## 🎟️ Tracking https://bitwarden.atlassian.net/browse/PM-21377 ## 📔 Objective Add remaining missing functions needed to port encrypt service to sdk. Ref: https://github.com/bitwarden/clients/blob/87a1f4e8ac928edfb05a283372550d0792c71827/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts#L105-L188 Note: The way that this uses the key context is not ideal, but this should not be handled in this PR. This PR is to make the public interface available to unblock clients changes. Follow-up work (such as #221) can expose better apis from bitwarden-crypto and move PureCrypto to it. ## ⏰ Reminders before review - Contributor guidelines followed - All formatters and local linters executed and passed - Written new unit and / or integration tests where applicable - Protected functional changes with optionality (feature flags) - Used internationalization (i18n) for all UI strings - CI builds passed - Communicated to DevOps any deployment requirements - Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team ## 🦮 Reviewer guidelines <!-- Suggested interactions but feel free to use (or not) as you desire! --> - 👍 (`:+1:`) or similar for great changes - 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info - ❓ (`:question:`) for questions - 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion - 🎨 (`:art:`) for suggestions / improvements - ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention - 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt - ⛏ (`:pick:`) for minor or nitpick changes
1 parent fcecb90 commit 5449a5a

File tree

1 file changed

+225
-1
lines changed

1 file changed

+225
-1
lines changed

crates/bitwarden-wasm-internal/src/pure_crypto.rs

+225-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use std::str::FromStr;
22

3+
use bitwarden_core::key_management::{KeyIds, SymmetricKeyId};
34
use bitwarden_crypto::{
4-
CryptoError, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey, SymmetricCryptoKey,
5+
AsymmetricCryptoKey, AsymmetricPublicCryptoKey, CryptoError, Decryptable, EncString,
6+
Encryptable, Kdf, KeyDecryptable, KeyEncryptable, KeyStore, MasterKey, SymmetricCryptoKey,
7+
UnsignedSharedKey,
58
};
69
use wasm_bindgen::prelude::*;
710

@@ -107,6 +110,144 @@ impl PureCrypto {
107110
pub fn generate_user_key_xchacha20_poly1305() -> Vec<u8> {
108111
SymmetricCryptoKey::make_xchacha20_poly1305_key().to_encoded()
109112
}
113+
114+
// Key wrap
115+
pub fn wrap_symmetric_key(
116+
key_to_be_wrapped: Vec<u8>,
117+
wrapping_key: Vec<u8>,
118+
) -> Result<String, CryptoError> {
119+
let tmp_store: KeyStore<KeyIds> = KeyStore::default();
120+
let mut context = tmp_store.context();
121+
#[allow(deprecated)]
122+
context.set_symmetric_key(
123+
SymmetricKeyId::Local("wrapping_key"),
124+
SymmetricCryptoKey::try_from(wrapping_key)?,
125+
)?;
126+
#[allow(deprecated)]
127+
context.set_symmetric_key(
128+
SymmetricKeyId::Local("key_to_wrap"),
129+
SymmetricCryptoKey::try_from(key_to_be_wrapped)?,
130+
)?;
131+
// Note: The order of arguments is different here, and should probably be refactored
132+
Ok(context
133+
.wrap_symmetric_key(
134+
SymmetricKeyId::Local("wrapping_key"),
135+
SymmetricKeyId::Local("key_to_wrap"),
136+
)?
137+
.to_string())
138+
}
139+
140+
pub fn unwrap_symmetric_key(
141+
wrapped_key: String,
142+
wrapping_key: Vec<u8>,
143+
) -> Result<Vec<u8>, CryptoError> {
144+
let tmp_store: KeyStore<KeyIds> = KeyStore::default();
145+
let mut context = tmp_store.context();
146+
#[allow(deprecated)]
147+
context.set_symmetric_key(
148+
SymmetricKeyId::Local("wrapping_key"),
149+
SymmetricCryptoKey::try_from(wrapping_key)?,
150+
)?;
151+
// Note: The order of arguments is different here, and should probably be refactored
152+
context.unwrap_symmetric_key(
153+
SymmetricKeyId::Local("wrapping_key"),
154+
SymmetricKeyId::Local("wrapped_key"),
155+
&EncString::from_str(wrapped_key.as_str())?,
156+
)?;
157+
#[allow(deprecated)]
158+
let key = context.dangerous_get_symmetric_key(SymmetricKeyId::Local("wrapped_key"))?;
159+
Ok(key.to_encoded())
160+
}
161+
162+
pub fn wrap_encapsulation_key(
163+
encapsulation_key: Vec<u8>,
164+
wrapping_key: Vec<u8>,
165+
) -> Result<String, CryptoError> {
166+
let tmp_store: KeyStore<KeyIds> = KeyStore::default();
167+
let mut context = tmp_store.context();
168+
#[allow(deprecated)]
169+
context.set_symmetric_key(
170+
SymmetricKeyId::Local("wrapping_key"),
171+
SymmetricCryptoKey::try_from(wrapping_key)?,
172+
)?;
173+
// Note: The order of arguments is different here, and should probably be refactored
174+
Ok(encapsulation_key
175+
.encrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))?
176+
.to_string())
177+
}
178+
179+
pub fn unwrap_encapsulation_key(
180+
wrapped_key: String,
181+
wrapping_key: Vec<u8>,
182+
) -> Result<Vec<u8>, CryptoError> {
183+
let tmp_store: KeyStore<KeyIds> = KeyStore::default();
184+
let mut context = tmp_store.context();
185+
#[allow(deprecated)]
186+
context.set_symmetric_key(
187+
SymmetricKeyId::Local("wrapping_key"),
188+
SymmetricCryptoKey::try_from(wrapping_key)?,
189+
)?;
190+
// Note: The order of arguments is different here, and should probably be refactored
191+
EncString::from_str(wrapped_key.as_str())?
192+
.decrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))
193+
}
194+
195+
pub fn wrap_decapsulation_key(
196+
decapsulation_key: Vec<u8>,
197+
wrapping_key: Vec<u8>,
198+
) -> Result<String, CryptoError> {
199+
let tmp_store: KeyStore<KeyIds> = KeyStore::default();
200+
let mut context = tmp_store.context();
201+
#[allow(deprecated)]
202+
context.set_symmetric_key(
203+
SymmetricKeyId::Local("wrapping_key"),
204+
SymmetricCryptoKey::try_from(wrapping_key)?,
205+
)?;
206+
// Note: The order of arguments is different here, and should probably be refactored
207+
Ok(decapsulation_key
208+
.encrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))?
209+
.to_string())
210+
}
211+
212+
pub fn unwrap_decapsulation_key(
213+
wrapped_key: String,
214+
wrapping_key: Vec<u8>,
215+
) -> Result<Vec<u8>, CryptoError> {
216+
let tmp_store: KeyStore<KeyIds> = KeyStore::default();
217+
let mut context = tmp_store.context();
218+
#[allow(deprecated)]
219+
context.set_symmetric_key(
220+
SymmetricKeyId::Local("wrapping_key"),
221+
SymmetricCryptoKey::try_from(wrapping_key)?,
222+
)?;
223+
// Note: The order of arguments is different here, and should probably be refactored
224+
EncString::from_str(wrapped_key.as_str())?
225+
.decrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))
226+
}
227+
228+
// Key encapsulation
229+
pub fn encapsulate_key_unsigned(
230+
shared_key: Vec<u8>,
231+
encapsulation_key: Vec<u8>,
232+
) -> Result<String, CryptoError> {
233+
let encapsulation_key = AsymmetricPublicCryptoKey::from_der(encapsulation_key.as_slice())?;
234+
Ok(UnsignedSharedKey::encapsulate_key_unsigned(
235+
&SymmetricCryptoKey::try_from(shared_key)?,
236+
&encapsulation_key,
237+
)?
238+
.to_string())
239+
}
240+
241+
pub fn decapsulate_key_unsigned(
242+
encapsulated_key: String,
243+
decapsulation_key: Vec<u8>,
244+
) -> Result<Vec<u8>, CryptoError> {
245+
Ok(UnsignedSharedKey::from_str(encapsulated_key.as_str())?
246+
.decapsulate_key_unsigned(&AsymmetricCryptoKey::from_der(
247+
decapsulation_key.as_slice(),
248+
)?)?
249+
.to_encoded())
250+
}
110251
}
111252

112253
#[cfg(test)]
@@ -134,6 +275,35 @@ mod tests {
134275
120, 104, 144, 4, 76, 3,
135276
];
136277

278+
const PEM_KEY: &str = "-----BEGIN PRIVATE KEY-----
279+
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5
280+
qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc
281+
afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm
282+
qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv
283+
b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw
284+
P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam
285+
rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi
286+
szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx
287+
0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+
288+
8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR
289+
jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach
290+
vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI
291+
1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR
292+
J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7
293+
l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ
294+
TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9
295+
ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye
296+
KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN
297+
wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ
298+
UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA
299+
kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W
300+
pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN
301+
Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi
302+
CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup
303+
PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf
304+
DnqOsltgPomWZ7xVfMkm9niL2OA=
305+
-----END PRIVATE KEY-----";
306+
137307
#[test]
138308
fn test_symmetric_decrypt() {
139309
let enc_string = EncString::from_str(ENCRYPTED).unwrap();
@@ -228,4 +398,58 @@ mod tests {
228398
.unwrap();
229399
assert_eq!(user_key, decrypted_user_key);
230400
}
401+
402+
#[test]
403+
fn test_wrap_unwrap_symmetric_key() {
404+
let key_to_be_wrapped = PureCrypto::generate_user_key_aes256_cbc_hmac();
405+
let wrapping_key = PureCrypto::generate_user_key_aes256_cbc_hmac();
406+
let wrapped_key =
407+
PureCrypto::wrap_symmetric_key(key_to_be_wrapped.clone(), wrapping_key.clone())
408+
.unwrap();
409+
let unwrapped_key = PureCrypto::unwrap_symmetric_key(wrapped_key, wrapping_key).unwrap();
410+
assert_eq!(key_to_be_wrapped, unwrapped_key);
411+
}
412+
413+
#[test]
414+
fn test_wrap_encapsulation_key() {
415+
let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap();
416+
let encapsulation_key = decapsulation_key.to_public_der().unwrap();
417+
let wrapping_key = PureCrypto::generate_user_key_aes256_cbc_hmac();
418+
let wrapped_key =
419+
PureCrypto::wrap_encapsulation_key(encapsulation_key.clone(), wrapping_key.clone())
420+
.unwrap();
421+
let unwrapped_key =
422+
PureCrypto::unwrap_encapsulation_key(wrapped_key, wrapping_key).unwrap();
423+
assert_eq!(encapsulation_key, unwrapped_key);
424+
}
425+
426+
#[test]
427+
fn test_wrap_decapsulation_key() {
428+
let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap();
429+
let wrapping_key = PureCrypto::generate_user_key_aes256_cbc_hmac();
430+
let wrapped_key = PureCrypto::wrap_decapsulation_key(
431+
decapsulation_key.to_der().unwrap(),
432+
wrapping_key.clone(),
433+
)
434+
.unwrap();
435+
let unwrapped_key =
436+
PureCrypto::unwrap_decapsulation_key(wrapped_key, wrapping_key).unwrap();
437+
assert_eq!(decapsulation_key.to_der().unwrap(), unwrapped_key);
438+
}
439+
440+
#[test]
441+
fn test_encapsulate_key_unsigned() {
442+
let shared_key = PureCrypto::generate_user_key_aes256_cbc_hmac();
443+
let decapsulation_key = AsymmetricCryptoKey::from_pem(PEM_KEY).unwrap();
444+
let encapsulation_key = decapsulation_key.to_public_der().unwrap();
445+
let encapsulated_key =
446+
PureCrypto::encapsulate_key_unsigned(shared_key.clone(), encapsulation_key.clone())
447+
.unwrap();
448+
let unwrapped_key = PureCrypto::decapsulate_key_unsigned(
449+
encapsulated_key,
450+
decapsulation_key.to_der().unwrap(),
451+
)
452+
.unwrap();
453+
assert_eq!(shared_key, unwrapped_key);
454+
}
231455
}

0 commit comments

Comments
 (0)