Skip to content

Commit

Permalink
Implement password update support (#566)
Browse files Browse the repository at this point in the history
## Type of change
```
- [ ] Bug fix
- [x] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
```

## Objective
Support re-encrypting the user key with the new password.
  • Loading branch information
dani-garcia authored Jan 31, 2024
1 parent 3064ac5 commit 878cd4a
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 2 deletions.
15 changes: 14 additions & 1 deletion crates/bitwarden-uniffi/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::sync::Arc;

use bitwarden::mobile::crypto::{
DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest,
DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse,
};
use bitwarden_crypto::EncString;

Expand Down Expand Up @@ -51,6 +51,19 @@ impl ClientCrypto {
.await?)
}

/// Update the user's password, which will re-encrypt the user's encryption key with the new
/// password. This returns the new encrypted user key and the new password hash.
pub async fn update_password(&self, new_password: String) -> Result<UpdatePasswordResponse> {
Ok(self
.0
.0
.write()
.await
.crypto()
.update_password(new_password)
.await?)
}

/// Generates a PIN protected user key from the provided PIN. The result can be stored and later
/// used to initialize another client instance by using the PIN and the PIN key with
/// `initialize_user_crypto`.
Expand Down
11 changes: 10 additions & 1 deletion crates/bitwarden/src/mobile/client_crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use crate::{
error::Result,
mobile::crypto::{
derive_pin_key, derive_pin_user_key, get_user_encryption_key, initialize_org_crypto,
initialize_user_crypto, DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest,
initialize_user_crypto, update_password, DerivePinKeyResponse, InitOrgCryptoRequest,
InitUserCryptoRequest, UpdatePasswordResponse,
},
};

Expand All @@ -31,6 +32,14 @@ impl<'a> ClientCrypto<'a> {
get_user_encryption_key(self.client).await
}

#[cfg(feature = "internal")]
pub async fn update_password(
&mut self,
new_password: String,
) -> Result<UpdatePasswordResponse> {
update_password(self.client, new_password)
}

#[cfg(feature = "internal")]
pub async fn derive_pin_key(&mut self, pin: String) -> Result<DerivePinKeyResponse> {
derive_pin_key(self.client, pin)
Expand Down
120 changes: 120 additions & 0 deletions crates/bitwarden/src/mobile/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,53 @@ pub async fn get_user_encryption_key(client: &mut Client) -> Result<String> {
Ok(user_key.to_base64())
}

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "mobile", derive(uniffi::Record))]
pub struct UpdatePasswordResponse {
/// Hash of the new password
password_hash: String,
/// User key, encrypted with the new password
new_key: EncString,
}

pub fn update_password(
client: &mut Client,
new_password: String,
) -> Result<UpdatePasswordResponse> {
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)?;

// Derive a new master key from password
let new_master_key = match login_method {
LoginMethod::User(
UserLoginMethod::Username { email, kdf, .. }
| UserLoginMethod::ApiKey { email, kdf, .. },
) => MasterKey::derive(new_password.as_bytes(), email.as_bytes(), kdf)?,
_ => return Err(Error::NotAuthenticated),
};

let new_key = new_master_key.encrypt_user_key(user_key)?;

let password_hash = new_master_key.derive_master_key_hash(
new_password.as_bytes(),
bitwarden_crypto::HashPurpose::ServerAuthorization,
)?;

Ok(UpdatePasswordResponse {
password_hash,
new_key,
})
}

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand Down Expand Up @@ -194,6 +241,79 @@ mod tests {
use super::*;
use crate::{client::Kdf, Client};

#[tokio::test]
async fn test_update_password() {
let mut client = Client::new(None);

let priv_key = "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=";

let kdf = Kdf::PBKDF2 {
iterations: 100_000.try_into().unwrap(),
};

initialize_user_crypto(
&mut client,
InitUserCryptoRequest {
kdf_params: kdf.clone(),
email: "[email protected]".into(),
private_key: priv_key.to_owned(),
method: InitUserCryptoMethod::Password {
password: "asdfasdfasdf".into(),
user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(),
},
},
)
.await
.unwrap();

let new_password_response = update_password(&mut client, "123412341234".into()).unwrap();

let mut client2 = Client::new(None);

initialize_user_crypto(
&mut client2,
InitUserCryptoRequest {
kdf_params: kdf.clone(),
email: "[email protected]".into(),
private_key: priv_key.to_owned(),
method: InitUserCryptoMethod::Password {
password: "123412341234".into(),
user_key: new_password_response.new_key.to_string(),
},
},
)
.await
.unwrap();

let new_hash = client2
.kdf()
.hash_password(
"[email protected]".into(),
"123412341234".into(),
kdf.clone(),
bitwarden_crypto::HashPurpose::ServerAuthorization,
)
.await
.unwrap();

assert_eq!(new_hash, new_password_response.password_hash);

assert_eq!(
client
.get_encryption_settings()
.unwrap()
.get_key(&None)
.unwrap()
.to_base64(),
client2
.get_encryption_settings()
.unwrap()
.get_key(&None)
.unwrap()
.to_base64()
);
}

#[tokio::test]
async fn test_initialize_user_crypto_pin() {
let mut client = Client::new(None);
Expand Down
12 changes: 12 additions & 0 deletions languages/kotlin/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,18 @@ as it can be used to decrypt all of the user&#x27;s data

**Output**: std::result::Result<String,BitwardenError>

### `update_password`

Update the user&#x27;s password, which will re-encrypt the user&#x27;s encryption key with the new
password. This returns the new encrypted user key and the new password hash.

**Arguments**:

- self:
- new_password: String

**Output**: std::result::Result<UpdatePasswordResponse,BitwardenError>

### `derive_pin_key`

Generates a PIN protected user key from the provided PIN. The result can be stored and later used to
Expand Down

0 comments on commit 878cd4a

Please sign in to comment.