Skip to content

Commit d27f58b

Browse files
authored
[PM-19603] Asymmetric encrypt/encapsulate interface (#202)
## 🎟️ Tracking https://bitwarden.atlassian.net/browse/PM-19603 ## 📔 Objective The goal of this PR is to provide a consistent, clear interface for asymmetric encryption. For asymmetric encryption we should never encrypt data, only share keys - "encapsulate". If data access is to be shared to another account, then this should be done via creating a new symmetric key, and sharing access to it. Further, currently our asymmetric data sharing makes no proofs about authenticity of the sender, that is, the data is unsigned. This makes that clear on the publicly exposed interface, and leaves the room open to add an interface that explicitly does provide sender authenticity via signing. This PR renames the interfaces to reflect that. Further, we drop the AsymmetricKey impl of the KeyEncryptable trait, specifically since it should *not* be able to encrypt arbitrary data, but also because a new interface that does provide sender authenticity should be the primarily used interface, which does not line up with the encryptable trait. ## ⏰ 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 fe76be2 commit d27f58b

24 files changed

+237
-324
lines changed

crates/bitwarden-core/src/auth/auth_client.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[cfg(feature = "internal")]
22
use bitwarden_crypto::{
3-
AsymmetricEncString, CryptoError, DeviceKey, EncString, Kdf, TrustDeviceResponse,
3+
CryptoError, DeviceKey, EncString, Kdf, TrustDeviceResponse, UnsignedSharedKey,
44
};
55

66
#[cfg(feature = "secrets")]
@@ -152,7 +152,7 @@ impl AuthClient {
152152
pub fn approve_auth_request(
153153
&self,
154154
public_key: String,
155-
) -> Result<AsymmetricEncString, ApproveAuthRequestError> {
155+
) -> Result<UnsignedSharedKey, ApproveAuthRequestError> {
156156
approve_auth_request(&self.client, public_key)
157157
}
158158

crates/bitwarden-core/src/auth/auth_request.rs

+19-16
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use base64::{engine::general_purpose::STANDARD, Engine};
22
use bitwarden_crypto::{
3-
fingerprint, generate_random_alphanumeric, AsymmetricCryptoKey, AsymmetricEncString,
4-
AsymmetricPublicCryptoKey, CryptoError,
3+
fingerprint, generate_random_alphanumeric, AsymmetricCryptoKey, AsymmetricPublicCryptoKey,
4+
CryptoError, UnsignedSharedKey,
55
};
66
#[cfg(feature = "internal")]
7-
use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey};
7+
use bitwarden_crypto::{EncString, SymmetricCryptoKey};
88
use thiserror::Error;
99

1010
#[cfg(feature = "internal")]
@@ -52,26 +52,25 @@ pub(crate) fn new_auth_request(email: &str) -> Result<AuthRequestResponse, Crypt
5252
#[cfg(feature = "internal")]
5353
pub(crate) fn auth_request_decrypt_user_key(
5454
private_key: String,
55-
user_key: AsymmetricEncString,
55+
user_key: UnsignedSharedKey,
5656
) -> Result<SymmetricCryptoKey, EncryptionSettingsError> {
5757
let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?;
58-
let mut key: Vec<u8> = user_key.decrypt_with_key(&key)?;
59-
60-
Ok(SymmetricCryptoKey::try_from(key.as_mut_slice())?)
58+
let key: SymmetricCryptoKey = user_key.decapsulate_key_unsigned(&key)?;
59+
Ok(key)
6160
}
6261

6362
/// Decrypt the user key using the private key generated previously.
6463
#[cfg(feature = "internal")]
6564
pub(crate) fn auth_request_decrypt_master_key(
6665
private_key: String,
67-
master_key: AsymmetricEncString,
66+
master_key: UnsignedSharedKey,
6867
user_key: EncString,
6968
) -> Result<SymmetricCryptoKey, EncryptionSettingsError> {
7069
use bitwarden_crypto::MasterKey;
7170

7271
let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?;
73-
let mut master_key: Vec<u8> = master_key.decrypt_with_key(&key)?;
74-
let master_key = MasterKey::try_from(master_key.as_mut_slice())?;
72+
let master_key: SymmetricCryptoKey = master_key.decapsulate_key_unsigned(&key)?;
73+
let master_key = MasterKey::try_from(&master_key)?;
7574

7675
Ok(master_key.decrypt_user_key(user_key)?)
7776
}
@@ -93,7 +92,7 @@ pub enum ApproveAuthRequestError {
9392
pub(crate) fn approve_auth_request(
9493
client: &Client,
9594
public_key: String,
96-
) -> Result<AsymmetricEncString, ApproveAuthRequestError> {
95+
) -> Result<UnsignedSharedKey, ApproveAuthRequestError> {
9796
let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?;
9897

9998
let key_store = client.internal.get_key_store();
@@ -103,8 +102,8 @@ pub(crate) fn approve_auth_request(
103102
#[allow(deprecated)]
104103
let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
105104

106-
Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1(
107-
&key.to_vec(),
105+
Ok(UnsignedSharedKey::encapsulate_key_unsigned(
106+
key,
108107
&public_key,
109108
)?)
110109
}
@@ -113,7 +112,7 @@ pub(crate) fn approve_auth_request(
113112
fn test_auth_request() {
114113
let request = new_auth_request("[email protected]").unwrap();
115114

116-
let secret: &[u8] = &[
115+
let secret = vec![
117116
111, 32, 97, 169, 4, 241, 174, 74, 239, 206, 113, 86, 174, 68, 216, 238, 52, 85, 156, 27,
118117
134, 149, 54, 55, 91, 147, 45, 130, 131, 237, 51, 31, 191, 106, 155, 14, 160, 82, 47, 40,
119118
96, 31, 114, 127, 212, 187, 167, 110, 205, 116, 198, 243, 218, 72, 137, 53, 248, 43, 255,
@@ -123,11 +122,15 @@ fn test_auth_request() {
123122
let private_key =
124123
AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap();
125124

126-
let encrypted = AsymmetricEncString::encrypt_rsa2048_oaep_sha1(secret, &private_key).unwrap();
125+
let encrypted = UnsignedSharedKey::encapsulate_key_unsigned(
126+
&SymmetricCryptoKey::try_from(secret.clone()).unwrap(),
127+
&private_key,
128+
)
129+
.unwrap();
127130

128131
let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap();
129132

130-
assert_eq!(&decrypted.to_vec(), secret);
133+
assert_eq!(decrypted.to_vec(), secret);
131134
}
132135

133136
#[cfg(test)]

crates/bitwarden-core/src/auth/tde.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use base64::{engine::general_purpose::STANDARD, Engine};
22
use bitwarden_crypto::{
3-
AsymmetricEncString, AsymmetricPublicCryptoKey, DeviceKey, EncString, Kdf, SymmetricCryptoKey,
4-
TrustDeviceResponse, UserKey,
3+
AsymmetricPublicCryptoKey, DeviceKey, EncString, Kdf, SymmetricCryptoKey, TrustDeviceResponse,
4+
UnsignedSharedKey, UserKey,
55
};
66

77
use crate::{client::encryption_settings::EncryptionSettingsError, Client};
@@ -22,8 +22,7 @@ pub(super) fn make_register_tde_keys(
2222
let user_key = UserKey::new(SymmetricCryptoKey::generate(&mut rng));
2323
let key_pair = user_key.make_key_pair()?;
2424

25-
let admin_reset =
26-
AsymmetricEncString::encrypt_rsa2048_oaep_sha1(&user_key.0.to_vec(), &public_key)?;
25+
let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(&user_key.0, &public_key)?;
2726

2827
let device_key = if remember_device {
2928
Some(DeviceKey::trust_device(&user_key.0)?)
@@ -58,6 +57,6 @@ pub struct RegisterTdeKeyResponse {
5857
pub private_key: EncString,
5958
pub public_key: String,
6059

61-
pub admin_reset: AsymmetricEncString,
60+
pub admin_reset: UnsignedSharedKey,
6261
pub device_key: Option<TrustDeviceResponse>,
6362
}

crates/bitwarden-core/src/client/encryption_settings.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bitwarden_crypto::{AsymmetricCryptoKey, KeyStore, SymmetricCryptoKey};
22
#[cfg(feature = "internal")]
3-
use bitwarden_crypto::{AsymmetricEncString, EncString};
3+
use bitwarden_crypto::{EncString, UnsignedSharedKey};
44
use bitwarden_error::bitwarden_error;
55
use thiserror::Error;
66
use uuid::Uuid;
@@ -91,7 +91,7 @@ impl EncryptionSettings {
9191

9292
#[cfg(feature = "internal")]
9393
pub(crate) fn set_org_keys(
94-
org_enc_keys: Vec<(Uuid, AsymmetricEncString)>,
94+
org_enc_keys: Vec<(Uuid, UnsignedSharedKey)>,
9595
store: &KeyStore<KeyIds>,
9696
) -> Result<(), EncryptionSettingsError> {
9797
let mut ctx = store.context_mut();
@@ -111,7 +111,7 @@ impl EncryptionSettings {
111111

112112
// Decrypt the org keys with the private key
113113
for (org_id, org_enc_key) in org_enc_keys {
114-
ctx.decrypt_symmetric_key_with_asymmetric_key(
114+
ctx.decapsulate_key_unsigned(
115115
AsymmetricKeyId::UserPrivateKey,
116116
SymmetricKeyId::Organization(org_id),
117117
&org_enc_key,

crates/bitwarden-core/src/client/internal.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bitwarden_crypto::KeyStore;
44
#[cfg(any(feature = "internal", feature = "secrets"))]
55
use bitwarden_crypto::SymmetricCryptoKey;
66
#[cfg(feature = "internal")]
7-
use bitwarden_crypto::{AsymmetricEncString, EncString, Kdf, MasterKey, PinKey};
7+
use bitwarden_crypto::{EncString, Kdf, MasterKey, PinKey, UnsignedSharedKey};
88
use chrono::Utc;
99
use uuid::Uuid;
1010

@@ -215,7 +215,7 @@ impl InternalClient {
215215
#[cfg(feature = "internal")]
216216
pub fn initialize_org_crypto(
217217
&self,
218-
org_keys: Vec<(Uuid, AsymmetricEncString)>,
218+
org_keys: Vec<(Uuid, UnsignedSharedKey)>,
219219
) -> Result<(), EncryptionSettingsError> {
220220
EncryptionSettings::set_org_keys(org_keys, &self.key_store)
221221
}

crates/bitwarden-core/src/mobile/crypto.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use std::collections::HashMap;
88

99
use base64::{engine::general_purpose::STANDARD, Engine};
1010
use bitwarden_crypto::{
11-
AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, Kdf, KeyDecryptable,
12-
KeyEncryptable, MasterKey, SymmetricCryptoKey, UserKey,
11+
AsymmetricCryptoKey, CryptoError, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey,
12+
SymmetricCryptoKey, UnsignedSharedKey, UserKey,
1313
};
1414
use schemars::JsonSchema;
1515
use serde::{Deserialize, Serialize};
@@ -90,7 +90,7 @@ pub enum InitUserCryptoMethod {
9090
/// The Device Private Key
9191
protected_device_private_key: EncString,
9292
/// The user's symmetric crypto key, encrypted with the Device Key.
93-
device_protected_user_key: AsymmetricEncString,
93+
device_protected_user_key: UnsignedSharedKey,
9494
},
9595
/// Key connector
9696
KeyConnector {
@@ -110,12 +110,12 @@ pub enum AuthRequestMethod {
110110
/// User Key
111111
UserKey {
112112
/// User Key protected by the private key provided in `AuthRequestResponse`.
113-
protected_user_key: AsymmetricEncString,
113+
protected_user_key: UnsignedSharedKey,
114114
},
115115
/// Master Key
116116
MasterKey {
117117
/// Master Key protected by the private key provided in `AuthRequestResponse`.
118-
protected_master_key: AsymmetricEncString,
118+
protected_master_key: UnsignedSharedKey,
119119
/// User Key protected by the MasterKey, provided by the auth response.
120120
auth_request_key: EncString,
121121
},
@@ -228,7 +228,7 @@ pub async fn initialize_user_crypto(
228228
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
229229
pub struct InitOrgCryptoRequest {
230230
/// The encryption keys for all the organizations the user is a part of
231-
pub organization_keys: HashMap<uuid::Uuid, AsymmetricEncString>,
231+
pub organization_keys: HashMap<uuid::Uuid, UnsignedSharedKey>,
232232
}
233233

234234
/// Initialize the user's organizational cryptographic state.
@@ -386,7 +386,7 @@ pub enum EnrollAdminPasswordResetError {
386386
pub(super) fn enroll_admin_password_reset(
387387
client: &Client,
388388
public_key: String,
389-
) -> Result<AsymmetricEncString, EnrollAdminPasswordResetError> {
389+
) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
390390
use base64::{engine::general_purpose::STANDARD, Engine};
391391
use bitwarden_crypto::AsymmetricPublicCryptoKey;
392392

@@ -397,8 +397,8 @@ pub(super) fn enroll_admin_password_reset(
397397
#[allow(deprecated)]
398398
let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
399399

400-
Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1(
401-
&key.to_vec(),
400+
Ok(UnsignedSharedKey::encapsulate_key_unsigned(
401+
key,
402402
&public_key,
403403
)?)
404404
}
@@ -767,7 +767,8 @@ mod tests {
767767
let private_key = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLtEUdxfcLxDj84yaGFsVF5hZ8Hjlb08NMQDy1RnBma06I3ZESshLYzVz4r/gegMn9OOltfV/Yxlyvida8oW6qdlfJ7AVz6Oa8pV7BiL40C7b76+oqraQpyYw2HChANB1AhXL9SqWngKmLZwjA7qiCrmcc0kZHeOb4KnKtp9iVvPVs+8veFvKgYO4ba2AAOHKFdR0W55/agXfAy+fWUAkC8mc9ikyJdQWaPV6OZvC2XFkOseBQm9Rynudh3BQpoWiL6w620efe7t5k+02/EyOFJL9f/XEEjM/+Yo0t3LAfkuhHGeKiRST59Xc9hTEmyJTeVXROtz+0fjqOp3xkaObAgMBAAECggEACs4xhnO0HaZhh1/iH7zORMIRXKeyxP2LQiTR8xwN5JJ9wRWmGAR9VasS7EZFTDidIGVME2u/h4s5EqXnhxfO+0gGksVvgNXJ/qw87E8K2216g6ZNo6vSGA7H1GH2voWwejJ4/k/cJug6dz2S402rRAKh2Wong1arYHSkVlQp3diiMa5FHAOSE+Cy09O2ZsaF9IXQYUtlW6AVXFrBEPYH2kvkaPXchh8VETMijo6tbvoKLnUHe+wTaDMls7hy8exjtVyI59r3DNzjy1lNGaGb5QSnFMXR+eHhPZc844Wv02MxC15zKABADrl58gpJyjTl6XpDdHCYGsmGpVGH3X9TQQKBgQDz/9beFjzq59ve6rGwn+EtnQfSsyYT+jr7GN8lNEXb3YOFXBgPhfFIcHRh2R00Vm9w2ApfAx2cd8xm2I6HuvQ1Os7g26LWazvuWY0Qzb+KaCLQTEGH1RnTq6CCG+BTRq/a3J8M4t38GV5TWlzv8wr9U4dl6FR4efjb65HXs1GQ4QKBgQC7/uHfrOTEHrLeIeqEuSl0vWNqEotFKdKLV6xpOvNuxDGbgW4/r/zaxDqt0YBOXmRbQYSEhmO3oy9J6XfE1SUln0gbavZeW0HESCAmUIC88bDnspUwS9RxauqT5aF8ODKN/bNCWCnBM1xyonPOs1oT1nyparJVdQoG//Y7vkB3+wKBgBqLqPq8fKAp3XfhHLfUjREDVoiLyQa/YI9U42IOz9LdxKNLo6p8rgVthpvmnRDGnpUuS+KOWjhdqDVANjF6G3t3DG7WNl8Rh5Gk2H4NhFswfSkgQrjebFLlBy9gjQVCWXt8KSmjvPbiY6q52Aaa8IUjA0YJAregvXxfopxO+/7BAoGARicvEtDp7WWnSc1OPoj6N14VIxgYcI7SyrzE0d/1x3ffKzB5e7qomNpxKzvqrVP8DzG7ydh8jaKPmv1MfF8tpYRy3AhmN3/GYwCnPqT75YYrhcrWcVdax5gmQVqHkFtIQkRSCIftzPLlpMGKha/YBV8c1fvC4LD0NPh/Ynv0gtECgYEAyOZg95/kte0jpgUEgwuMrzkhY/AaUJULFuR5MkyvReEbtSBQwV5tx60+T95PHNiFooWWVXiLMsAgyI2IbkxVR1Pzdri3gWK5CTfqb7kLuaj/B7SGvBa2Sxo478KS5K8tBBBWkITqo+wLC0mn3uZi1dyMWO1zopTA+KtEGF2dtGQ=";
768768
let private_key =
769769
AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key).unwrap()).unwrap();
770-
let decrypted: Vec<u8> = encrypted.decrypt_with_key(&private_key).unwrap();
770+
let decrypted: SymmetricCryptoKey =
771+
encrypted.decapsulate_key_unsigned(&private_key).unwrap();
771772

772773
let key_store = client.internal.get_key_store();
773774
let ctx = key_store.context();
@@ -776,7 +777,7 @@ mod tests {
776777
.dangerous_get_symmetric_key(SymmetricKeyId::User)
777778
.unwrap();
778779

779-
assert_eq!(&decrypted, &expected.to_vec());
780+
assert_eq!(&decrypted.to_vec(), &expected.to_vec());
780781
}
781782

782783
#[test]

crates/bitwarden-core/src/mobile/crypto_client.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bitwarden_crypto::CryptoError;
22
#[cfg(feature = "internal")]
3-
use bitwarden_crypto::{AsymmetricEncString, EncString};
3+
use bitwarden_crypto::{EncString, UnsignedSharedKey};
44

55
use super::crypto::{
66
derive_key_connector, make_key_pair, verify_asymmetric_keys, DeriveKeyConnectorError,
@@ -75,7 +75,7 @@ impl CryptoClient {
7575
pub fn enroll_admin_password_reset(
7676
&self,
7777
public_key: String,
78-
) -> Result<AsymmetricEncString, EnrollAdminPasswordResetError> {
78+
) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
7979
enroll_admin_password_reset(&self.client, public_key)
8080
}
8181

crates/bitwarden-core/src/uniffi_support.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
33
use std::num::NonZeroU32;
44

5-
use bitwarden_crypto::{AsymmetricEncString, EncString};
5+
use bitwarden_crypto::{EncString, UnsignedSharedKey};
66
use uuid::Uuid;
77

88
use crate::UniffiCustomTypeConverter;
99

1010
uniffi::ffi_converter_forward!(NonZeroU32, bitwarden_crypto::UniFfiTag, crate::UniFfiTag);
1111
uniffi::ffi_converter_forward!(EncString, bitwarden_crypto::UniFfiTag, crate::UniFfiTag);
1212
uniffi::ffi_converter_forward!(
13-
AsymmetricEncString,
13+
UnsignedSharedKey,
1414
bitwarden_crypto::UniFfiTag,
1515
crate::UniFfiTag
1616
);

crates/bitwarden-crypto/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ num-bigint = ">=0.4, <0.5"
3838
num-traits = ">=0.2.15, <0.3"
3939
pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false }
4040
rand = ">=0.8.5, <0.9"
41+
rand_chacha = ">=0.3.1, <0.4.0"
4142
rayon = ">=1.8.1, <2.0"
4243
rsa = ">=0.9.2, <0.10"
4344
schemars = { workspace = true }

0 commit comments

Comments
 (0)