diff --git a/crates/bitwarden-core/src/auth/auth_client.rs b/crates/bitwarden-core/src/auth/auth_client.rs index b1d4909fa..028aca414 100644 --- a/crates/bitwarden-core/src/auth/auth_client.rs +++ b/crates/bitwarden-core/src/auth/auth_client.rs @@ -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; @@ -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}, }, @@ -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 { new_auth_request(email) diff --git a/crates/bitwarden-core/src/auth/pin.rs b/crates/bitwarden-core/src/auth/pin.rs index 8983fca71..aba2c704a 100644 --- a/crates/bitwarden-core/src/auth/pin.rs +++ b/crates/bitwarden-core/src/auth/pin.rs @@ -1,4 +1,5 @@ -use bitwarden_crypto::{EncString, PinKey}; +use bitwarden_crypto::{EncString, PinKey, safe::PasswordProtectedKeyEnvelope}; +use tracing::info; use crate::{ Client, NotAuthenticatedError, @@ -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; @@ -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 = (&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); + } } diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index d320f120e..ea9af1f8d 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -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; @@ -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 { Ok(self.0.auth().new_auth_request(&email)?) diff --git a/crates/bitwarden-uniffi/src/uniffi_support.rs b/crates/bitwarden-uniffi/src/uniffi_support.rs index d0bd6e393..0b31f833a 100644 --- a/crates/bitwarden-uniffi/src/uniffi_support.rs +++ b/crates/bitwarden-uniffi/src/uniffi_support.rs @@ -1,3 +1,4 @@ +use bitwarden_crypto::safe; use uuid::Uuid; // Forward the type definitions to the main bitwarden crate @@ -5,3 +6,5 @@ type DateTime = chrono::DateTime; uniffi::use_remote_type!(bitwarden_core::DateTime); uniffi::use_remote_type!(bitwarden_core::Uuid); + +uniffi::use_remote_type!(bitwarden_crypto::safe::PasswordProtectedKeyEnvelope);