mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 15:33:55 +00:00
Linux support
This commit is contained in:
100
apps/desktop/desktop_native/core/src/biometric_v2/linux.rs
Normal file
100
apps/desktop/desktop_native/core/src/biometric_v2/linux.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
//! This file implements Polkit based system unlock.
|
||||
//!
|
||||
//! # Security
|
||||
//! This section describes the assumed security model and security guarantees achieved. In the required security
|
||||
//! guarantee is that a locked vault - a running app - cannot be unlocked when the device (user-space)
|
||||
//! is compromised in this state.
|
||||
//!
|
||||
//! When first unlocking the app, the app sends the user-key to this module, which holds it in secure memory,
|
||||
//! protected by memfd_secret. This makes it inaccessible to other processes, even if they compromise root, a kernel compromise
|
||||
//! has circumventable best-effort protections. While the app is running this key is held in memory, even if locked.
|
||||
//! When unlocking, the app will prompt the user via `polkit` to get a yes/no decision on whether to release the key to the app.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use tokio::sync::Mutex;
|
||||
use zbus::Connection;
|
||||
use zbus_polkit::policykit1::{AuthorityProxy, CheckAuthorizationFlags, Subject};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
secure_memory::*
|
||||
};
|
||||
|
||||
pub struct BiometricLockSystem {
|
||||
// The userkeys that are held in memory MUST be protected from memory dumping attacks, to ensure
|
||||
// locked vaults cannot be unlocked
|
||||
secure_memory: Arc<Mutex<crate::secure_memory::memfd_secret::MemfdSecretKVStore>>
|
||||
}
|
||||
|
||||
impl BiometricLockSystem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
secure_memory: Arc::new(Mutex::new(crate::secure_memory::memfd_secret::MemfdSecretKVStore::new()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::BiometricV2Trait for BiometricLockSystem {
|
||||
async fn authenticate(&self, _hwnd: Vec<u8>, _message: String) -> Result<bool> {
|
||||
let connection = Connection::system().await?;
|
||||
let proxy = AuthorityProxy::new(&connection).await?;
|
||||
let subject = Subject::new_for_owner(std::process::id(), None, None)?;
|
||||
let details = std::collections::HashMap::new();
|
||||
let result = proxy
|
||||
.check_authorization(
|
||||
&subject,
|
||||
"com.bitwarden.Bitwarden.unlock",
|
||||
&details,
|
||||
CheckAuthorizationFlags::AllowUserInteraction.into(),
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(result) => Ok(result.is_authorized),
|
||||
Err(e) => {
|
||||
println!("polkit biometric error: {:?}", e);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate_available(&self) -> Result<bool> {
|
||||
let connection = Connection::system().await?;
|
||||
let proxy = AuthorityProxy::new(&connection).await?;
|
||||
let actions = proxy.enumerate_actions("en").await?;
|
||||
for action in actions {
|
||||
if action.action_id == "com.bitwarden.Bitwarden.unlock" {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
async fn enroll_persistent(&self, _user_id: &str, _key: &[u8]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn provide_key(&self, user_id: &str, key: &[u8]) {
|
||||
let mut secure_memory = self.secure_memory.lock().await;
|
||||
secure_memory.put(user_id.to_string(), key);
|
||||
}
|
||||
|
||||
async fn unlock(&self, user_id: &str, _hwnd: Vec<u8>) -> Result<Vec<u8>> {
|
||||
if self.authenticate(Vec::new(), "".to_string()).await? == false {
|
||||
return Err(anyhow!("Authentication failed"));
|
||||
}
|
||||
|
||||
let secure_memory = self.secure_memory.lock().await;
|
||||
secure_memory.get(user_id).ok_or(anyhow!("No key found"))
|
||||
}
|
||||
|
||||
async fn unlock_available(&self, user_id: &str) -> Result<bool> {
|
||||
let secure_memory = self.secure_memory.lock().await;
|
||||
return Ok(secure_memory.has(user_id));
|
||||
}
|
||||
|
||||
async fn has_persistent(&self, _user_id: &str) -> Result<bool> {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
use anyhow::{Result};
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
#[cfg_attr(target_os = "linux", path = "unimplemented.rs")]
|
||||
#[cfg_attr(target_os = "linux", path = "linux.rs")]
|
||||
#[cfg_attr(target_os = "macos", path = "unimplemented.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
mod biometric_v2;
|
||||
|
||||
#[cfg_attr(target_os = "windows", path = "windows_focus.rs")]
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows_focus;
|
||||
|
||||
pub use biometric_v2::BiometricLockSystem;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
//! Since the keychain can be accessed by all user-space processes, the challenge is known to all userspace processes.
|
||||
//! Therefore, to circumvent the security measure, the attacker would need to create a fake Windows-Hello prompt, and
|
||||
//! get the user to confirm it.
|
||||
|
||||
use std::{ffi::c_void, sync::{atomic::AtomicBool, Arc}};
|
||||
|
||||
use aes::cipher::KeyInit;
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
use std::{collections::HashMap, ptr::NonNull, sync::LazyLock};
|
||||
|
||||
use crate::secure_memory::SecureMemoryStore;
|
||||
|
||||
/// https://man.archlinux.org/man/memfd_secret.2.en
|
||||
/// The memfd_secret store protects the data using the `memfd_secret` syscall. The
|
||||
/// data is inaccessible to other user-mode processes, and even to root in most cases.
|
||||
/// If arbitrary data can be executed in the kernel, the data can still be retrieved:
|
||||
/// https://github.com/JonathonReinhart/nosecmem
|
||||
pub(crate) struct MemfdSecretKVStore {
|
||||
map: HashMap<String, std::ptr::NonNull<[u8]>>,
|
||||
}
|
||||
|
||||
impl MemfdSecretKVStore {
|
||||
pub(crate) fn new() -> Self {
|
||||
MemfdSecretKVStore {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SecureMemoryStore for MemfdSecretKVStore {
|
||||
fn put(&mut self, key: String, value: &[u8]) {
|
||||
let mut ptr: std::ptr::NonNull<[u8]> = unsafe {
|
||||
memsec::memfd_secret_sized(value.len()).expect("memfd_secret_sized should work")
|
||||
};
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(value.as_ptr(), ptr.as_mut().as_mut_ptr(), value.len());
|
||||
}
|
||||
self.map.insert(key, ptr);
|
||||
}
|
||||
|
||||
fn get(&self, key: &str) -> Option<Vec<u8>> {
|
||||
let ptr = self.map.get(key)?;
|
||||
let value = unsafe { ptr.as_ref() };
|
||||
Some(value.to_vec())
|
||||
}
|
||||
|
||||
fn has(&self, key: &str) -> bool {
|
||||
self.map.contains_key(key)
|
||||
}
|
||||
|
||||
fn remove(&mut self, key: &str) {
|
||||
if let Some(value) = self.map.remove(key) {
|
||||
unsafe {
|
||||
memsec::free_memfd_secret(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
for (_, value) in self.map.drain() {
|
||||
unsafe {
|
||||
memsec::free_memfd_secret(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MemfdSecretKVStore {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_supported() -> bool {
|
||||
// To test if memfd_secret is supported, we try to allocate a 1 byte and see if that
|
||||
// succeeds.
|
||||
static IS_SUPPORTED: LazyLock<bool> = LazyLock::new(|| {
|
||||
let Some(ptr): Option<NonNull<[u8]>> = (unsafe { memsec::memfd_secret_sized(1) }) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check that the pointer is readable and writable
|
||||
let result = unsafe {
|
||||
let ptr = ptr.as_ptr() as *mut u8;
|
||||
*ptr = 30;
|
||||
*ptr += 107;
|
||||
*ptr == 137
|
||||
};
|
||||
|
||||
unsafe { memsec::free_memfd_secret(ptr) };
|
||||
result
|
||||
});
|
||||
*IS_SUPPORTED
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_memfd_secret_kv_store() {
|
||||
let mut store = MemfdSecretKVStore::new();
|
||||
let key = "test_key".to_string();
|
||||
let value = vec![1, 2, 3, 4, 5];
|
||||
|
||||
store.put(key.clone(), &value);
|
||||
assert!(store.has(&key));
|
||||
assert_eq!(store.get(&key), Some(value.clone()));
|
||||
|
||||
store.remove(&key);
|
||||
assert!(!store.has(&key));
|
||||
assert_eq!(store.get(&key), None);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) mod dpapi;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod unimplemented;
|
||||
pub(crate) mod memfd_secret;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod unimplemented;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user