Skip to content

Commit 387b493

Browse files
committed
Add test and update enrollment function
1 parent ecb1528 commit 387b493

File tree

3 files changed

+146
-38
lines changed

3 files changed

+146
-38
lines changed

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

Lines changed: 126 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -579,42 +579,69 @@ pub(super) fn verify_asymmetric_keys(
579579
})
580580
}
581581

582-
/// A new signing key pair along with the signed public key
582+
///
583583
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
584584
#[serde(rename_all = "camelCase", deny_unknown_fields)]
585585
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
586586
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
587-
pub struct MakeUserSigningKeysResponse {
588-
/// Base64 encoded verifying key
589-
verifying_key: String,
590-
/// Signing key, encrypted with the user's symmetric key
591-
signing_key: EncString,
587+
pub struct EnrollUserCryptoV2Response {
588+
/// User key
589+
user_key: String,
590+
591+
/// Wrapped private key
592+
private_key: EncString,
593+
/// Public key
594+
public_key: String,
592595
/// The user's public key, signed by the signing key
593596
signed_public_key: SignedPublicKey,
597+
598+
/// Signing key, encrypted with the user's symmetric key
599+
signing_key: EncString,
600+
/// Base64 encoded verifying key
601+
verifying_key: String,
602+
594603
/// The user's signed security state
595604
signed_security_state: SignedSecurityState,
596605
}
597606

598-
/// Makes a new set of signing keys for a user, which should only be done during
599-
/// once. This also signs the public key with the signing key
600-
/// and returns the signed public key.
601-
pub fn make_user_signing_keys_for_enrollment(
607+
/// Initializes the user's cryptographic state for v2 users.
608+
/// If the client already contains a v1 user, then this user's private-key will be
609+
/// re-used.
610+
pub fn make_keys_for_user_crypto_v2(
602611
client: &Client,
603-
) -> Result<MakeUserSigningKeysResponse, CryptoError> {
612+
) -> Result<EnrollUserCryptoV2Response, CryptoError> {
604613
let key_store = client.internal.get_key_store();
605614
let mut ctx = key_store.context();
606615

607-
// Make new keypair and sign the public key with it
608-
let signature_keypair = SigningKey::make(SignatureAlgorithm::Ed25519);
609-
let temporary_signature_keypair_id = SigningKeyId::Local("temporary_key_for_rotation");
616+
let temporary_user_key_id = SymmetricKeyId::Local("temporary_user_key");
617+
let temporary_signing_key_id = SigningKeyId::Local("temporary_signing_key");
618+
let temporary_private_key_id = AsymmetricKeyId::Local("temporary_private_key");
619+
620+
// If the user already has a private key, use it. Otherwise, create a temporary one.
621+
let private_key_id = if ctx.has_asymmetric_key(AsymmetricKeyId::UserPrivateKey) {
622+
AsymmetricKeyId::UserPrivateKey
623+
} else {
624+
ctx.make_asymmetric_key(temporary_private_key_id)?
625+
};
626+
627+
// New user key
628+
let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
610629
#[allow(deprecated)]
611-
ctx.set_signing_key(temporary_signature_keypair_id, signature_keypair.clone())?;
612-
let signed_public_key = ctx.make_signed_public_key(
613-
AsymmetricKeyId::UserPrivateKey,
614-
temporary_signature_keypair_id,
615-
)?;
630+
ctx.set_symmetric_key(temporary_user_key_id, user_key.clone())?;
631+
632+
// New signing key
633+
let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
634+
#[allow(deprecated)]
635+
ctx.set_signing_key(temporary_signing_key_id, signing_key.clone())?;
636+
637+
// Sign existing public key
638+
let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
639+
#[allow(deprecated)]
640+
let public_key = ctx
641+
.dangerous_get_asymmetric_key(private_key_id)?
642+
.to_public_key();
616643

617-
// Make signed security state
644+
// Initialize security state for the user
618645
let security_state = bitwarden_crypto::security_state::SecurityState::initialize_for_user(
619646
client
620647
.internal
@@ -623,17 +650,28 @@ pub fn make_user_signing_keys_for_enrollment(
623650
);
624651
let signed_security_state = bitwarden_crypto::security_state::sign(
625652
&security_state,
626-
temporary_signature_keypair_id,
653+
temporary_signing_key_id,
627654
&mut ctx,
628655
)?;
629-
Ok(MakeUserSigningKeysResponse {
630-
verifying_key: STANDARD.encode(signature_keypair.to_verifying_key().to_cose()),
656+
657+
Ok(EnrollUserCryptoV2Response {
658+
user_key: user_key.to_base64(),
659+
660+
#[allow(deprecated)]
661+
private_key: ctx
662+
.dangerous_get_asymmetric_key(private_key_id)?
663+
.to_der()?
664+
.encrypt(&mut ctx, temporary_user_key_id)?,
665+
public_key: STANDARD.encode(public_key.to_der()?),
666+
signed_public_key,
667+
631668
// This needs to be changed to use the correct COSE content format before rolling out to
632669
// users: https://bitwarden.atlassian.net/browse/PM-22189
633-
signing_key: signature_keypair
670+
signing_key: signing_key
634671
.to_cose()
635-
.encrypt(&mut ctx, SymmetricKeyId::User)?,
636-
signed_public_key,
672+
.encrypt(&mut ctx, temporary_user_key_id)?,
673+
verifying_key: STANDARD.encode(signing_key.to_verifying_key().to_cose()),
674+
637675
signed_security_state,
638676
})
639677
}
@@ -866,6 +904,68 @@ mod tests {
866904
assert_eq!(client_key, client3_key);
867905
}
868906

907+
#[tokio::test]
908+
async fn test_user_crypto_v2() {
909+
let client = Client::new(None);
910+
911+
let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
912+
let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
913+
914+
initialize_user_crypto(
915+
&client,
916+
InitUserCryptoRequest {
917+
user_id: Some(uuid::Uuid::new_v4()),
918+
kdf_params: Kdf::PBKDF2 {
919+
iterations: 100_000.try_into().unwrap(),
920+
},
921+
email: "[email protected]".into(),
922+
private_key: priv_key,
923+
signing_key: None,
924+
security_state: None,
925+
method: InitUserCryptoMethod::Password {
926+
password: "asdfasdfasdf".into(),
927+
user_key: encrypted_userkey.clone(),
928+
},
929+
},
930+
)
931+
.await
932+
.unwrap();
933+
934+
let master_key = MasterKey::derive(
935+
"asdfasdfasdf",
936+
937+
&Kdf::PBKDF2 {
938+
iterations: NonZeroU32::new(100_000).unwrap(),
939+
},
940+
)
941+
.unwrap();
942+
let enrollment_response = make_keys_for_user_crypto_v2(&client).unwrap();
943+
let encrypted_userkey_v2 = master_key
944+
.encrypt_user_key(&SymmetricCryptoKey::try_from(enrollment_response.user_key).unwrap())
945+
.unwrap();
946+
947+
let client2 = Client::new(None);
948+
initialize_user_crypto(
949+
&client2,
950+
InitUserCryptoRequest {
951+
user_id: Some(uuid::Uuid::new_v4()),
952+
kdf_params: Kdf::PBKDF2 {
953+
iterations: 100_000.try_into().unwrap(),
954+
},
955+
email: "[email protected]".into(),
956+
private_key: enrollment_response.private_key,
957+
signing_key: Some(enrollment_response.signing_key),
958+
security_state: Some(enrollment_response.signed_security_state),
959+
method: InitUserCryptoMethod::Password {
960+
password: "asdfasdfasdf".into(),
961+
user_key: encrypted_userkey_v2,
962+
},
963+
},
964+
)
965+
.await
966+
.unwrap();
967+
}
968+
869969
#[test]
870970
fn test_enroll_admin_password_reset() {
871971
use base64::{engine::general_purpose::STANDARD, Engine};

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ use bitwarden_crypto::{EncString, UnsignedSharedKey};
55
use wasm_bindgen::prelude::*;
66

77
use super::crypto::{
8-
derive_key_connector, make_key_pair, make_user_signing_keys_for_enrollment,
9-
verify_asymmetric_keys, DeriveKeyConnectorError, DeriveKeyConnectorRequest,
10-
EnrollAdminPasswordResetError, MakeKeyPairResponse, MakeUserSigningKeysResponse,
11-
VerifyAsymmetricKeysRequest, VerifyAsymmetricKeysResponse,
8+
derive_key_connector, make_key_pair, verify_asymmetric_keys, DeriveKeyConnectorError,
9+
DeriveKeyConnectorRequest, EnrollAdminPasswordResetError, EnrollUserCryptoV2Response,
10+
MakeKeyPairResponse, VerifyAsymmetricKeysRequest, VerifyAsymmetricKeysResponse,
1211
};
1312
#[cfg(feature = "internal")]
1413
use crate::key_management::crypto::{
@@ -18,7 +17,9 @@ use crate::key_management::crypto::{
1817
};
1918
use crate::{
2019
client::encryption_settings::EncryptionSettingsError,
21-
key_management::crypto::{get_v2_rotated_account_keys, CryptoClientError},
20+
key_management::crypto::{
21+
get_v2_rotated_account_keys, make_keys_for_user_crypto_v2, CryptoClientError,
22+
},
2223
Client,
2324
};
2425

@@ -65,10 +66,8 @@ impl CryptoClient {
6566
}
6667

6768
/// Makes a new signing key pair and signs the public key for the user
68-
pub fn make_user_signing_keys_for_enrollment(
69-
&self,
70-
) -> Result<MakeUserSigningKeysResponse, CryptoError> {
71-
make_user_signing_keys_for_enrollment(&self.client)
69+
pub fn make_keys_for_user_crypto_v2(&self) -> Result<EnrollUserCryptoV2Response, CryptoError> {
70+
make_keys_for_user_crypto_v2(&self.client)
7271
}
7372

7473
/// Creates a rotated set of account keys for the current state

crates/bitwarden-crypto/src/store/context.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use zeroize::Zeroizing;
99
use super::KeyStoreInner;
1010
use crate::{
1111
derive_shareable_key, error::UnsupportedOperation, signing, store::backend::StoreBackend,
12-
AsymmetricCryptoKey, CryptoError, EncString, KeyId, KeyIds, Result, Signature,
13-
SignatureAlgorithm, SignedObject, SignedPublicKey, SignedPublicKeyMessage, SigningKey,
14-
SymmetricCryptoKey, UnsignedSharedKey,
12+
AsymmetricCryptoKey, CryptoError, EncString, KeyId, KeyIds, PublicKeyEncryptionAlgorithm,
13+
Result, Signature, SignatureAlgorithm, SignedObject, SignedPublicKey, SignedPublicKeyMessage,
14+
SigningKey, SymmetricCryptoKey, UnsignedSharedKey,
1515
};
1616

1717
/// The context of a crypto operation using [super::KeyStore]
@@ -265,6 +265,15 @@ impl<Ids: KeyIds> KeyStoreContext<'_, Ids> {
265265
Ok(key_id)
266266
}
267267

268+
/// Makes a new asymmetric encryption key using the current default algorithm, and stores it in the
269+
/// context
270+
pub fn make_asymmetric_key(&mut self, key_id: Ids::Asymmetric) -> Result<Ids::Asymmetric> {
271+
let key = AsymmetricCryptoKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
272+
#[allow(deprecated)]
273+
self.set_asymmetric_key(key_id, key)?;
274+
Ok(key_id)
275+
}
276+
268277
/// Generate a new signature key using the current default algorithm, and store it in the
269278
/// context
270279
pub fn make_signing_key(&mut self, key_id: Ids::Signing) -> Result<Ids::Signing> {

0 commit comments

Comments
 (0)