Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add derive_pin_user_key #508

Merged
merged 6 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions crates/bitwarden-uniffi/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::sync::Arc;

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

use crate::{error::Result, Client};
Expand Down Expand Up @@ -53,4 +54,16 @@
pub async fn derive_pin_key(&self, pin: String) -> Result<DerivePinKeyResponse> {
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<EncString> {
Ok(self
.0
.0
.write()
.await
.crypto()
.derive_pin_user_key(encrypted_pin)
.await?)
}

Check warning on line 68 in crates/bitwarden-uniffi/src/crypto.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/crypto.rs#L59-L68

Added lines #L59 - L68 were not covered by tests
}
10 changes: 8 additions & 2 deletions crates/bitwarden/src/mobile/client_crypto.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::Client;
#[cfg(feature = "internal")]
use crate::{
crypto::EncString,
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,
},
};

Expand Down Expand Up @@ -32,6 +33,11 @@
pub async fn derive_pin_key(&mut self, pin: String) -> Result<DerivePinKeyResponse> {
derive_pin_key(self.client, pin)
}

#[cfg(feature = "internal")]
pub async fn derive_pin_user_key(&mut self, encrypted_pin: EncString) -> Result<EncString> {
derive_pin_user_key(self.client, encrypted_pin)
}

Check warning on line 40 in crates/bitwarden/src/mobile/client_crypto.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/mobile/client_crypto.rs#L38-L40

Added lines #L38 - L40 were not covered by tests
}

impl<'a> Client {
Expand Down
107 changes: 90 additions & 17 deletions crates/bitwarden/src/mobile/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
Client,
};

#[cfg(feature = "internal")]
use crate::{
client::{LoginMethod, UserLoginMethod},
crypto::{KeyEncryptable, MasterKey, SymmetricCryptoKey},
};

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand Down Expand Up @@ -50,8 +56,6 @@

#[cfg(feature = "internal")]
pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> {
use crate::crypto::SymmetricCryptoKey;

let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username {
client_id: "".to_string(),
email: req.email,
Expand Down Expand Up @@ -112,36 +116,67 @@
#[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 [UserKey]
Hinton marked this conversation as resolved.
Show resolved Hide resolved
encrypted_pin: EncString,
}

#[cfg(feature = "internal")]
pub fn derive_pin_key(client: &mut Client, pin: String) -> Result<DerivePinKeyResponse> {
use crate::{
client::{LoginMethod, UserLoginMethod},
crypto::{KeyEncryptable, MasterKey},
};

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<EncString> {
use crate::crypto::KeyDecryptable;

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<EncString> {
let derived_key = match login_method {
LoginMethod::User(
UserLoginMethod::Username { email, kdf, .. }
| UserLoginMethod::ApiKey { email, kdf, .. },

Check warning on line 172 in crates/bitwarden/src/mobile/crypto.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/mobile/crypto.rs#L172

Added line #L172 was not covered by tests
) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?,
_ => return Err(Error::NotAuthenticated),

Check warning on line 174 in crates/bitwarden/src/mobile/crypto.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/mobile/crypto.rs#L174

Added line #L174 was not covered by tests
};

derived_key.encrypt_user_key(user_key)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -172,8 +207,8 @@

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 {
Expand Down Expand Up @@ -205,5 +240,43 @@
.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: "[email protected]".into(),
private_key: priv_key.to_owned(),
method: InitUserCryptoMethod::Pin {
pin: "1234".into(),
pin_protected_user_key,
},
},
)
.await

Check warning on line 264 in crates/bitwarden/src/mobile/crypto.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/mobile/crypto.rs#L264

Added line #L264 was not covered by tests
.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()
);
}
}
35 changes: 31 additions & 4 deletions languages/kotlin/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,18 @@ initialize another client instance by using the PIN and the PIN key with

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

### `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<EncString,BitwardenError>

## ClientExporters

### `export_vault`
Expand Down Expand Up @@ -852,6 +864,16 @@ implementations.
</tr>
</table>

## `EncString`

<table>
<tr>
<th>Key</th>
<th>Type</th>
<th>Description</th>
</tr>
</table>

## `ExportFormat`

<table>
Expand Down Expand Up @@ -1428,13 +1450,18 @@ implementations.
</tr>
<tr>
<th>key</th>
<th></th>
<th></th>
<th>string,null</th>
<th>Base64 encoded key</th>
</tr>
<tr>
<th>password</th>
<th>newPassword</th>
<th>string,null</th>
<th></th>
<th>Replace 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 &#x60;[Create, Update]SendView&#x60; DTOs.</th>
</tr>
<tr>
<th>hasPassword</th>
<th>boolean</th>
<th>Denote if an existing send has a password. The SDK will ignore this value when creating or updating sends.</th>
</tr>
<tr>
<th>type</th>
Expand Down