diff --git a/apps/desktop/desktop_native/core/src/biometric/unix.rs b/apps/desktop/desktop_native/core/src/biometric/unix.rs index 60392adc9d7..d3bf8c50fe9 100644 --- a/apps/desktop/desktop_native/core/src/biometric/unix.rs +++ b/apps/desktop/desktop_native/core/src/biometric/unix.rs @@ -1,18 +1,9 @@ -use std::str::FromStr; - use anyhow::Result; -use base64::Engine; -use rand::RngCore; -use sha2::{Digest, Sha256}; -use crate::biometric::{base64_engine, KeyMaterial, OsDerivedKey}; +use crate::biometric::{KeyMaterial, OsDerivedKey}; use zbus::Connection; use zbus_polkit::policykit1::*; -use super::{decrypt, encrypt}; -use crate::crypto::CipherString; -use anyhow::anyhow; - /// The Unix implementation of the biometric trait. pub struct Biometric {} @@ -53,57 +44,25 @@ impl super::BiometricTrait for Biometric { Ok(false) } - fn derive_key_material(challenge_str: Option<&str>) -> Result { - let challenge: [u8; 16] = match challenge_str { - Some(challenge_str) => base64_engine - .decode(challenge_str)? - .try_into() - .map_err(|e: Vec<_>| anyhow!("Expect length {}, got {}", 16, e.len()))?, - None => random_challenge(), - }; - - // there is no windows hello like interactive bio protected secret at the moment on linux - // so we use a a key derived from the iv. this key is not intended to add any security - // but only a place-holder - let key = Sha256::digest(challenge); - let key_b64 = base64_engine.encode(key); - let iv_b64 = base64_engine.encode(challenge); - Ok(OsDerivedKey { key_b64, iv_b64 }) + fn derive_key_material(_challenge_str: Option<&str>) -> Result { + unimplemented!(); } async fn set_biometric_secret( - service: &str, - account: &str, - secret: &str, - key_material: Option, - iv_b64: &str, + _service: &str, + _account: &str, + _secret: &str, + _key_material: Option, + _iv_b64: &str, ) -> Result { - let key_material = key_material.ok_or(anyhow!( - "Key material is required for polkit protected keys" - ))?; - - let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; - crate::password::set_password(service, account, &encrypted_secret).await?; - Ok(encrypted_secret) + unimplemented!(); } async fn get_biometric_secret( - service: &str, - account: &str, - key_material: Option, + _service: &str, + _account: &str, + _key_material: Option, ) -> Result { - let key_material = key_material.ok_or(anyhow!( - "Key material is required for polkit protected keys" - ))?; - - let encrypted_secret = crate::password::get_password(service, account).await?; - let secret = CipherString::from_str(&encrypted_secret)?; - decrypt(&secret, &key_material) + unimplemented!(); } } - -fn random_challenge() -> [u8; 16] { - let mut challenge = [0u8; 16]; - rand::rng().fill_bytes(&mut challenge); - challenge -} diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts index 12388a78297..7753360350b 100644 --- a/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts @@ -1,13 +1,8 @@ import { spawn } from "child_process"; -import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; -import { biometrics, passwords } from "@bitwarden/desktop-napi"; -import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management"; +import { BiometricsStatus } from "@bitwarden/key-management"; import { isFlatpak, isLinux, isSnapStore } from "../../utils"; @@ -32,37 +27,15 @@ const polkitPolicy = ` const policyFileName = "com.bitwarden.Bitwarden.policy"; const policyPath = "/usr/share/polkit-1/actions/"; -const SERVICE = "Bitwarden_biometric"; -function getLookupKeyForUser(userId: UserId): string { - return `${userId}_user_biometric`; -} - export default class OsBiometricsServiceLinux implements OsBiometricService { - constructor( - private biometricStateService: BiometricStateService, - private encryptService: EncryptService, - private cryptoFunctionService: CryptoFunctionService, - ) {} - private _iv: string | null = null; - // Use getKeyMaterial helper instead of direct access - private _osKeyHalf: string | null = null; - private clientKeyHalves = new Map(); + constructor() {} + private inMemoryUserKeys = new Map(); async setBiometricKey(userId: UserId, key: SymmetricCryptoKey): Promise { - const clientKeyPartB64 = Utils.fromBufferToB64( - await this.getOrCreateBiometricEncryptionClientKeyHalf(userId, key), - ); - const storageDetails = await this.getStorageDetails({ clientKeyHalfB64: clientKeyPartB64 }); - await biometrics.setBiometricSecret( - SERVICE, - getLookupKeyForUser(userId), - key.toBase64(), - storageDetails.key_material, - storageDetails.ivB64, - ); + this.inMemoryUserKeys.set(userId.toString(), key); } async deleteBiometricKey(userId: UserId): Promise { - await passwords.deletePassword(SERVICE, getLookupKeyForUser(userId)); + this.inMemoryUserKeys.delete(userId.toString()); } async getBiometricKey(userId: UserId): Promise { @@ -72,23 +45,7 @@ export default class OsBiometricsServiceLinux implements OsBiometricService { throw new Error("Biometric authentication failed"); } - const value = await passwords.getPassword(SERVICE, getLookupKeyForUser(userId)); - - if (value == null || value == "") { - return null; - } else { - const clientKeyHalf = this.clientKeyHalves.get(userId.toString()); - const clientKeyPartB64 = Utils.fromBufferToB64(clientKeyHalf); - const encValue = new EncString(value); - this.setIv(encValue.iv); - const storageDetails = await this.getStorageDetails({ clientKeyHalfB64: clientKeyPartB64 }); - const storedValue = await biometrics.getBiometricSecret( - SERVICE, - getLookupKeyForUser(userId), - storageDetails.key_material, - ); - return SymmetricCryptoKey.fromString(storedValue); - } + return this.inMemoryUserKeys.get(userId.toString()) || null; } async authenticateBiometric(): Promise { @@ -100,11 +57,8 @@ export default class OsBiometricsServiceLinux implements OsBiometricService { async supportsBiometrics(): Promise { // We assume all linux distros have some polkit implementation // that either has bitwarden set up or not, which is reflected in osBiomtricsNeedsSetup. - // Snap does not have access at the moment to polkit // This could be dynamically detected on dbus in the future. - // We should check if a libsecret implementation is available on the system - // because otherwise we cannot offlod the protected userkey to secure storage. - return await passwords.isAvailable(); + return true; } async needsSetup(): Promise { @@ -143,77 +97,12 @@ export default class OsBiometricsServiceLinux implements OsBiometricService { }); } - // Nulls out key material in order to force a re-derive. This should only be used in getBiometricKey - // when we want to force a re-derive of the key material. - private setIv(iv?: string) { - this._iv = iv ?? null; - this._osKeyHalf = null; - } - - private async getStorageDetails({ - clientKeyHalfB64, - }: { - clientKeyHalfB64: string | undefined; - }): Promise<{ key_material: biometrics.KeyMaterial; ivB64: string }> { - if (this._osKeyHalf == null) { - const keyMaterial = await biometrics.deriveKeyMaterial(this._iv); - // osKeyHalf is based on the iv and in contrast to windows is not locked behind user verification! - this._osKeyHalf = keyMaterial.keyB64; - this._iv = keyMaterial.ivB64; - } - - if (this._iv == null) { - throw new Error("Initialization Vector is null"); - } - - return { - key_material: { - osKeyPartB64: this._osKeyHalf, - clientKeyPartB64: clientKeyHalfB64, - }, - ivB64: this._iv, - }; - } - - private async getOrCreateBiometricEncryptionClientKeyHalf( - userId: UserId, - key: SymmetricCryptoKey, - ): Promise { - const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId); - if (!requireClientKeyHalf) { - return null; - } - - if (this.clientKeyHalves.has(userId)) { - return this.clientKeyHalves.get(userId) || null; - } - - // Retrieve existing key half if it exists - let clientKeyHalf: Uint8Array | null = null; - const encryptedClientKeyHalf = - await this.biometricStateService.getEncryptedClientKeyHalf(userId); - if (encryptedClientKeyHalf != null) { - clientKeyHalf = await this.encryptService.decryptBytes(encryptedClientKeyHalf, key); - } - if (clientKeyHalf == null) { - // Set a key half if it doesn't exist - const keyBytes = await this.cryptoFunctionService.randomBytes(32); - const encKey = await this.encryptService.encryptBytes(keyBytes, key); - await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId); - } - - this.clientKeyHalves.set(userId.toString(), clientKeyHalf); - - return clientKeyHalf; - } - async getBiometricsFirstUnlockStatusForUser(userId: UserId): Promise { - const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId); - const clientKeyHalfB64 = this.clientKeyHalves.get(userId); - const clientKeyHalfSatisfied = !requireClientKeyHalf || !!clientKeyHalfB64; - if (!clientKeyHalfSatisfied) { + const hasUserKey = this.inMemoryUserKeys.has(userId.toString()); + if (hasUserKey) { + return BiometricsStatus.Available; + } else { return BiometricsStatus.UnlockNeeded; } - return BiometricsStatus.Available; } }