From a1e57c14ad200da613c21bd323be316f5e511700 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 15 Jan 2024 16:43:20 +0100 Subject: [PATCH] Add derive_pin_user_key (#508) Adds support for using pin when require master password is enabled. --- crates/bitwarden-uniffi/src/crypto.rs | 13 +++ crates/bitwarden/src/mobile/client_crypto.rs | 12 ++- crates/bitwarden/src/mobile/crypto.rs | 102 ++++++++++++++++--- languages/kotlin/doc.md | 35 ++++++- 4 files changed, 140 insertions(+), 22 deletions(-) diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 25d577ba5..e54aae435 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use bitwarden::mobile::crypto::{ DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, }; +use bitwarden_crypto::EncString; use crate::{error::Result, Client}; @@ -53,4 +54,16 @@ impl ClientCrypto { pub async fn derive_pin_key(&self, pin: String) -> Result { Ok(self.0 .0.write().await.crypto().derive_pin_key(pin).await?) } + + /// Derives the pin protected user key from encrypted pin. Used when pin requires master password on first unlock. + pub async fn derive_pin_user_key(&self, encrypted_pin: EncString) -> Result { + Ok(self + .0 + .0 + .write() + .await + .crypto() + .derive_pin_user_key(encrypted_pin) + .await?) + } } diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index cddb7c140..6f8887002 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -1,10 +1,13 @@ +#[cfg(feature = "internal")] +use bitwarden_crypto::EncString; + use crate::Client; #[cfg(feature = "internal")] use crate::{ error::Result, mobile::crypto::{ - derive_pin_key, get_user_encryption_key, initialize_org_crypto, initialize_user_crypto, - DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, + derive_pin_key, derive_pin_user_key, get_user_encryption_key, initialize_org_crypto, + initialize_user_crypto, DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, }, }; @@ -32,6 +35,11 @@ impl<'a> ClientCrypto<'a> { pub async fn derive_pin_key(&mut self, pin: String) -> Result { derive_pin_key(self.client, pin) } + + #[cfg(feature = "internal")] + pub async fn derive_pin_user_key(&mut self, encrypted_pin: EncString) -> Result { + derive_pin_user_key(self.client, encrypted_pin) + } } impl<'a> Client { diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index 8894b19db..a96d85ec6 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -1,9 +1,13 @@ use std::collections::HashMap; use bitwarden_crypto::{AsymmEncString, EncString}; +#[cfg(feature = "internal")] +use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, MasterKey, SymmetricCryptoKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[cfg(feature = "internal")] +use crate::client::{LoginMethod, UserLoginMethod}; use crate::{ client::Kdf, error::{Error, Result}, @@ -50,8 +54,6 @@ pub enum InitUserCryptoMethod { #[cfg(feature = "internal")] pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { - use bitwarden_crypto::SymmetricCryptoKey; - let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { client_id: "".to_string(), email: req.email, @@ -112,35 +114,65 @@ pub async fn get_user_encryption_key(client: &mut Client) -> Result { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct DerivePinKeyResponse { + /// [UserKey] protected by PIN pin_protected_user_key: EncString, + /// PIN protected by [UserKey] encrypted_pin: EncString, } #[cfg(feature = "internal")] pub fn derive_pin_key(client: &mut Client, pin: String) -> Result { - use bitwarden_crypto::{KeyEncryptable, MasterKey}; - - use crate::client::{LoginMethod, UserLoginMethod}; - - let derived_key = match &client.login_method { - Some(LoginMethod::User( - UserLoginMethod::Username { email, kdf, .. } - | UserLoginMethod::ApiKey { email, kdf, .. }, - )) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, - _ => return Err(Error::NotAuthenticated), - }; - let user_key = client .get_encryption_settings()? .get_key(&None) .ok_or(Error::VaultLocked)?; + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + let pin_protected_user_key = derive_pin_protected_user_key(&pin, login_method, user_key)?; + Ok(DerivePinKeyResponse { - pin_protected_user_key: derived_key.encrypt_user_key(user_key)?, + pin_protected_user_key, encrypted_pin: pin.encrypt_with_key(user_key)?, }) } +#[cfg(feature = "internal")] +pub fn derive_pin_user_key(client: &mut Client, encrypted_pin: EncString) -> Result { + let user_key = client + .get_encryption_settings()? + .get_key(&None) + .ok_or(Error::VaultLocked)?; + + let pin: String = encrypted_pin.decrypt_with_key(user_key)?; + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + derive_pin_protected_user_key(&pin, login_method, user_key) +} + +#[cfg(feature = "internal")] +fn derive_pin_protected_user_key( + pin: &str, + login_method: &LoginMethod, + user_key: &SymmetricCryptoKey, +) -> Result { + let derived_key = match login_method { + LoginMethod::User( + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. }, + ) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, + _ => return Err(Error::NotAuthenticated), + }; + + Ok(derived_key.encrypt_user_key(user_key)?) +} + #[cfg(test)] mod tests { use super::*; @@ -171,8 +203,8 @@ mod tests { let pin_key = derive_pin_key(&mut client, "1234".into()).unwrap(); + // Verify we can unlock with the pin let mut client2 = Client::new(None); - initialize_user_crypto( &mut client2, InitUserCryptoRequest { @@ -204,5 +236,43 @@ mod tests { .unwrap() .to_base64() ); + + // Verify we can derive the pin protected user key from the encrypted pin + let pin_protected_user_key = + derive_pin_user_key(&mut client, pin_key.encrypted_pin).unwrap(); + + let mut client3 = Client::new(None); + + initialize_user_crypto( + &mut client3, + InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }, + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Pin { + pin: "1234".into(), + pin_protected_user_key, + }, + }, + ) + .await + .unwrap(); + + assert_eq!( + client + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64(), + client3 + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64() + ); } } diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index 27b376051..896cf7a76 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -246,6 +246,18 @@ initialize another client instance by using the PIN and the PIN key with **Output**: std::result::Result +### `derive_pin_user_key` + +Derives the pin protected user key from encrypted pin. Used when pin requires master password on +first unlock. + +**Arguments**: + +- self: +- encrypted_pin: [EncString](#encstring) + +**Output**: std::result::Result + ## ClientExporters ### `export_vault` @@ -852,6 +864,16 @@ implementations. +## `EncString` + + + + + + + +
KeyTypeDescription
+ ## `ExportFormat` @@ -1428,13 +1450,18 @@ implementations. - - + + - + - + + + + + +
keystring,nullBase64 encoded key
passwordnewPassword string,nullReplace or add a password to an existing send. The SDK will always return None when decrypting a [Send] TODO: We should revisit this, one variant is to have `[Create, Update]SendView` DTOs.
hasPasswordbooleanDenote if an existing send has a password. The SDK will ignore this value when creating or updating sends.
type