Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
16 changes: 15 additions & 1 deletion crates/bitwarden-core/src/auth/auth_client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[cfg(feature = "internal")]
use bitwarden_crypto::{
CryptoError, DeviceKey, EncString, Kdf, TrustDeviceResponse, UnsignedSharedKey,
safe::PasswordProtectedKeyEnvelope,
};
#[cfg(feature = "internal")]
use bitwarden_encoding::B64;
Expand All @@ -26,7 +27,7 @@ use crate::{
MasterPasswordPolicyOptions, password_strength, satisfies_policy, validate_password,
validate_password_user_key,
},
pin::validate_pin,
pin::{validate_pin, validate_pin_protected_user_key_envelope},
register::make_register_keys,
tde::{RegisterTdeKeyResponse, make_register_tde_keys},
},
Expand Down Expand Up @@ -160,6 +161,19 @@ impl AuthClient {
validate_pin(&self.client, pin, pin_protected_user_key)
}

/// Validates a PIN against a PIN-protected user key envelope.
///
/// Returns `false` if validation fails for any reason:
/// - The PIN is incorrect
/// - The envelope is corrupted or malformed
pub fn validate_pin_protected_user_key_envelope(
&self,
pin: String,
pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
) -> bool {
validate_pin_protected_user_key_envelope(&self.client, pin, pin_protected_user_key_envelope)
}

#[allow(missing_docs)]
pub fn new_auth_request(&self, email: &str) -> Result<AuthRequestResponse, CryptoError> {
new_auth_request(email)
Expand Down
78 changes: 77 additions & 1 deletion crates/bitwarden-core/src/auth/pin.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bitwarden_crypto::{EncString, PinKey};
use bitwarden_crypto::{EncString, PinKey, safe::PasswordProtectedKeyEnvelope};
use tracing::info;

use crate::{
Client, NotAuthenticatedError,
Expand Down Expand Up @@ -42,6 +43,23 @@ pub(crate) fn validate_pin(
}
}

/// Validates a PIN-protected user key envelope by attempting to unseal it with the provided PIN.
pub(crate) fn validate_pin_protected_user_key_envelope(
client: &Client,
pin: String,
pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
) -> bool {
let key_store = client.internal.get_key_store();
let mut ctx = key_store.context();

if let Err(e) = pin_protected_user_key_envelope.unseal(pin.as_str(), &mut ctx) {
info!("Validating PIN-protected user key envelope failed: {e:?}");
false
} else {
true
}
}

#[cfg(test)]
mod tests {
use std::num::NonZeroU32;
Expand Down Expand Up @@ -109,4 +127,62 @@ mod tests {
let client = init_client();
assert!(!validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap());
}

#[test]
fn test_validate_pin_protected_user_key_envelope_valid_pin() {
let pin = "1234";
let client = init_client();

// Create a PIN-protected envelope from the user key
let key_store = client.internal.get_key_store();
let ctx = key_store.context();
let envelope = PasswordProtectedKeyEnvelope::seal(SymmetricKeyId::User, pin, &ctx).unwrap();

// Validate with the correct PIN
let result = validate_pin_protected_user_key_envelope(&client, pin.to_string(), envelope);
assert!(result);
}

#[test]
fn test_validate_pin_protected_user_key_envelope_invalid_pin() {
let correct_pin = "1234";
let wrong_pin = "5678";
let client = init_client();

// Create a PIN-protected envelope with the correct PIN
let key_store = client.internal.get_key_store();
let ctx = key_store.context();
let envelope =
PasswordProtectedKeyEnvelope::seal(SymmetricKeyId::User, correct_pin, &ctx).unwrap();

// Validate with the wrong PIN
let result =
validate_pin_protected_user_key_envelope(&client, wrong_pin.to_string(), envelope);
assert!(!result);
}

#[test]
fn test_validate_pin_protected_user_key_malformed_envelope() {
let pin = "1234";

let client = init_client();

// Create a PIN-protected envelope with the correct PIN
let key_store = client.internal.get_key_store();
let ctx = key_store.context();
let envelope = PasswordProtectedKeyEnvelope::seal(SymmetricKeyId::User, pin, &ctx).unwrap();

let mut envelope_bytes: Vec<u8> = (&envelope).into();
// Corrupt some bytes
envelope_bytes[50] ^= 0xFF;

let envelope: PasswordProtectedKeyEnvelope =
PasswordProtectedKeyEnvelope::try_from(&envelope_bytes).unwrap();

let client = Client::new(None);

// Validate should fail because no user key is present in this client
let result = validate_pin_protected_user_key_envelope(&client, pin.to_string(), envelope);
assert!(!result);
}
}
23 changes: 22 additions & 1 deletion crates/bitwarden-uniffi/src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use bitwarden_core::auth::{
AuthRequestResponse, KeyConnectorResponse, RegisterKeyResponse, RegisterTdeKeyResponse,
password::MasterPasswordPolicyOptions,
};
use bitwarden_crypto::{EncString, HashPurpose, Kdf, TrustDeviceResponse, UnsignedSharedKey};
use bitwarden_crypto::{
EncString, HashPurpose, Kdf, TrustDeviceResponse, UnsignedSharedKey,
safe::PasswordProtectedKeyEnvelope,
};
use bitwarden_encoding::B64;

use crate::error::Result;
Expand Down Expand Up @@ -114,6 +117,24 @@ impl AuthClient {
Ok(self.0.auth().validate_pin(pin, pin_protected_user_key)?)
}

/// Validates a PIN against a PIN-protected user key envelope.
///
/// The `pin_protected_user_key_envelope` key is obtained when enabling PIN unlock on the
/// account with the [bitwarden_core::key_management::CryptoClient::enroll_pin] method.
///
/// Returns `false` if validation fails for any reason:
/// - The PIN is incorrect
/// - The envelope is corrupted or malformed
pub fn validate_pin_protected_user_key_envelope(
&self,
pin: String,
pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
) -> bool {
self.0
.auth()
.validate_pin_protected_user_key_envelope(pin, pin_protected_user_key_envelope)
}

/// Initialize a new auth request
pub fn new_auth_request(&self, email: String) -> Result<AuthRequestResponse> {
Ok(self.0.auth().new_auth_request(&email)?)
Expand Down
3 changes: 3 additions & 0 deletions crates/bitwarden-uniffi/src/uniffi_support.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use bitwarden_crypto::safe;
use uuid::Uuid;

// Forward the type definitions to the main bitwarden crate
type DateTime = chrono::DateTime<chrono::Utc>;
uniffi::use_remote_type!(bitwarden_core::DateTime);

uniffi::use_remote_type!(bitwarden_core::Uuid);

uniffi::use_remote_type!(bitwarden_crypto::safe::PasswordProtectedKeyEnvelope);
Loading