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 5 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
13 changes: 13 additions & 0 deletions crates/bitwarden-uniffi/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::sync::Arc;
use bitwarden::mobile::crypto::{
DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest,
};
use bitwarden_crypto::EncString;

use crate::{error::Result, Client};

Expand Down Expand Up @@ -53,4 +54,16 @@ impl ClientCrypto {
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?)
}
}
12 changes: 10 additions & 2 deletions crates/bitwarden/src/mobile/client_crypto.rs
Original file line number Diff line number Diff line change
@@ -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,
},
};

Expand Down Expand Up @@ -32,6 +35,11 @@ impl<'a> ClientCrypto<'a> {
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)
}
}

impl<'a> Client {
Expand Down
102 changes: 86 additions & 16 deletions crates/bitwarden/src/mobile/crypto.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -112,35 +114,65 @@ pub async fn get_user_encryption_key(client: &mut Client) -> Result<String> {
#[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 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<EncString> {
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, .. },
) => 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::*;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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: "[email protected]".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()
);
}
}
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