mirror of
https://github.com/bitwarden/browser
synced 2026-01-03 17:13:47 +00:00
Align Desktop Native's Rust CI checks with SDK (#17261)
* clean crate deps * update lint workflow * add rustfmt.toml * apply rust fmt * missed one * fix lint of lint lol * more deps platform fixes * fix macos_provider * some more deps clean * more cleanup * add --all-targets * remove another unused dep * generate index.d.ts * fix whitespace * fix split comment in biometric * formatting comment in biometric_v2 * apply fmt
This commit is contained in:
@@ -86,11 +86,15 @@ impl KeyMaterial {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::biometric::{decrypt, encrypt, KeyMaterial};
|
||||
use crate::crypto::CipherString;
|
||||
use base64::{engine::general_purpose::STANDARD as base64_engine, Engine};
|
||||
use std::str::FromStr;
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD as base64_engine, Engine};
|
||||
|
||||
use crate::{
|
||||
biometric::{decrypt, encrypt, KeyMaterial},
|
||||
crypto::CipherString,
|
||||
};
|
||||
|
||||
fn key_material() -> KeyMaterial {
|
||||
KeyMaterial {
|
||||
os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(),
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use base64::Engine;
|
||||
use rand::RngCore;
|
||||
use sha2::{Digest, Sha256};
|
||||
use tracing::error;
|
||||
|
||||
use crate::biometric::{base64_engine, KeyMaterial, OsDerivedKey};
|
||||
use zbus::Connection;
|
||||
use zbus_polkit::policykit1::*;
|
||||
|
||||
use super::{decrypt, encrypt};
|
||||
use crate::crypto::CipherString;
|
||||
use anyhow::anyhow;
|
||||
use crate::{
|
||||
biometric::{base64_engine, KeyMaterial, OsDerivedKey},
|
||||
crypto::CipherString,
|
||||
};
|
||||
|
||||
/// The Unix implementation of the biometric trait.
|
||||
pub struct Biometric {}
|
||||
|
||||
@@ -16,13 +16,12 @@ use windows::{
|
||||
};
|
||||
use windows_future::IAsyncOperation;
|
||||
|
||||
use super::{decrypt, encrypt, windows_focus::set_focus};
|
||||
use crate::{
|
||||
biometric::{KeyMaterial, OsDerivedKey},
|
||||
crypto::CipherString,
|
||||
};
|
||||
|
||||
use super::{decrypt, encrypt, windows_focus::set_focus};
|
||||
|
||||
/// The Windows OS implementation of the biometric trait.
|
||||
pub struct Biometric {}
|
||||
|
||||
@@ -61,7 +60,8 @@ impl super::BiometricTrait for Biometric {
|
||||
|
||||
match ucv_available {
|
||||
UserConsentVerifierAvailability::Available => Ok(true),
|
||||
UserConsentVerifierAvailability::DeviceBusy => Ok(true), // TODO: Look into removing this and making the check more ad-hoc
|
||||
// TODO: look into removing this and making the check more ad-hoc
|
||||
UserConsentVerifierAvailability::DeviceBusy => Ok(true),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,6 @@ fn random_challenge() -> [u8; 16] {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::biometric::BiometricTrait;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
//! 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.
|
||||
//! 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.
|
||||
//! 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 std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, warn};
|
||||
use zbus::Connection;
|
||||
@@ -20,8 +22,8 @@ use zbus_polkit::policykit1::{AuthorityProxy, CheckAuthorizationFlags, Subject};
|
||||
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
|
||||
// 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::encrypted_memory_store::EncryptedMemoryStore>>,
|
||||
}
|
||||
|
||||
@@ -88,8 +90,9 @@ impl super::BiometricTrait for BiometricLockSystem {
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a polkit authorization against the bitwarden unlock policy. Note: This relies on no custom
|
||||
/// rules in the system skipping the authorization check, in which case this counts as UV / authentication.
|
||||
/// Perform a polkit authorization against the bitwarden unlock policy. Note: This relies on no
|
||||
/// custom rules in the system skipping the authorization check, in which case this counts as UV /
|
||||
/// authentication.
|
||||
async fn polkit_authenticate_bitwarden_policy() -> Result<bool> {
|
||||
debug!("[Polkit] Authenticating / performing UV");
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ pub trait BiometricTrait: Send + Sync {
|
||||
async fn authenticate(&self, hwnd: Vec<u8>, message: String) -> Result<bool>;
|
||||
/// Check if biometric authentication is available
|
||||
async fn authenticate_available(&self) -> Result<bool>;
|
||||
/// Enroll a key for persistent unlock. If the implementation does not support persistent enrollment,
|
||||
/// this function should do nothing.
|
||||
/// Enroll a key for persistent unlock. If the implementation does not support persistent
|
||||
/// enrollment, this function should do nothing.
|
||||
async fn enroll_persistent(&self, user_id: &str, key: &[u8]) -> Result<()>;
|
||||
/// Clear the persistent and ephemeral keys
|
||||
async fn unenroll(&self, user_id: &str) -> Result<()>;
|
||||
@@ -28,6 +28,7 @@ pub trait BiometricTrait: Send + Sync {
|
||||
async fn provide_key(&self, user_id: &str, key: &[u8]);
|
||||
/// Perform biometric unlock and return the key
|
||||
async fn unlock(&self, user_id: &str, hwnd: Vec<u8>) -> Result<Vec<u8>>;
|
||||
/// Check if biometric unlock is available based on whether a key is present and whether authentication is possible
|
||||
/// Check if biometric unlock is available based on whether a key is present and whether
|
||||
/// authentication is possible
|
||||
async fn unlock_available(&self, user_id: &str) -> Result<bool>;
|
||||
}
|
||||
|
||||
@@ -2,38 +2,40 @@
|
||||
//!
|
||||
//! There are two paths implemented here.
|
||||
//! The former via UV + ephemerally (but protected) keys. This only works after first unlock.
|
||||
//! The latter via a signing API, that deterministically signs a challenge, from which a windows hello key is derived. This key
|
||||
//! is used to encrypt the protected key.
|
||||
//! The latter via a signing API, that deterministically signs a challenge, from which a windows
|
||||
//! hello key is derived. This key is used to encrypt the protected key.
|
||||
//!
|
||||
//! # Security
|
||||
//! The security goal is that a locked vault - a running app - cannot be unlocked when the device (user-space)
|
||||
//! is compromised in this state.
|
||||
//! The security goal is that a locked vault - a running app - cannot be unlocked when the device
|
||||
//! (user-space) is compromised in this state.
|
||||
//!
|
||||
//! ## UV path
|
||||
//! When first unlocking the app, the app sends the user-key to this module, which holds it in secure memory,
|
||||
//! protected by DPAPI. This makes it inaccessible to other processes, unless they compromise the system administrator, or kernel.
|
||||
//! While the app is running this key is held in memory, even if locked. When unlocking, the app will prompt the user via
|
||||
//! When first unlocking the app, the app sends the user-key to this module, which holds it in
|
||||
//! secure memory, protected by DPAPI. This makes it inaccessible to other processes, unless they
|
||||
//! compromise the system administrator, or kernel. While the app is running this key is held in
|
||||
//! memory, even if locked. When unlocking, the app will prompt the user via
|
||||
//! `windows_hello_authenticate` to get a yes/no decision on whether to release the key to the app.
|
||||
//! Note: Further process isolation is needed here so that code cannot be injected into the running process, which may
|
||||
//! circumvent DPAPI.
|
||||
//! Note: Further process isolation is needed here so that code cannot be injected into the running
|
||||
//! process, which may circumvent DPAPI.
|
||||
//!
|
||||
//! ## Sign path
|
||||
//! In this scenario, when enrolling, the app sends the user-key to this module, which derives the windows hello key
|
||||
//! with the Windows Hello prompt. This is done by signing a per-user challenge, which produces a deterministic
|
||||
//! signature which is hashed to obtain a key. This key is used to encrypt and persist the vault unlock key (user key).
|
||||
//! In this scenario, when enrolling, the app sends the user-key to this module, which derives the
|
||||
//! windows hello key with the Windows Hello prompt. This is done by signing a per-user challenge,
|
||||
//! which produces a deterministic signature which is hashed to obtain a key. This key is used to
|
||||
//! encrypt and persist the vault unlock key (user key).
|
||||
//!
|
||||
//! 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.
|
||||
//! 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::sync::{atomic::AtomicBool, Arc};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use aes::cipher::KeyInit;
|
||||
use anyhow::{anyhow, Result};
|
||||
use chacha20poly1305::{aead::Aead, XChaCha20Poly1305, XNonce};
|
||||
use sha2::{Digest, Sha256};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, warn};
|
||||
use windows::{
|
||||
core::{factory, h, Interface, HSTRING},
|
||||
Security::{
|
||||
@@ -74,8 +76,8 @@ struct WindowsHelloKeychainEntry {
|
||||
|
||||
/// The Windows OS implementation of the biometric trait.
|
||||
pub struct BiometricLockSystem {
|
||||
// The userkeys that are held in memory MUST be protected from memory dumping attacks, to ensure
|
||||
// locked vaults cannot be unlocked
|
||||
// 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::dpapi::DpapiSecretKVStore>>,
|
||||
}
|
||||
|
||||
@@ -114,12 +116,14 @@ impl super::BiometricTrait for BiometricLockSystem {
|
||||
}
|
||||
|
||||
async fn enroll_persistent(&self, user_id: &str, key: &[u8]) -> Result<()> {
|
||||
// Enrollment works by first generating a random challenge unique to the user / enrollment. Then,
|
||||
// with the challenge and a Windows-Hello prompt, the "windows hello key" is derived. The windows
|
||||
// hello key is used to encrypt the key to store with XChaCha20Poly1305. The bundle of nonce,
|
||||
// challenge and wrapped-key are stored to the keychain
|
||||
// Enrollment works by first generating a random challenge unique to the user / enrollment.
|
||||
// Then, with the challenge and a Windows-Hello prompt, the "windows hello key" is
|
||||
// derived. The windows hello key is used to encrypt the key to store with
|
||||
// XChaCha20Poly1305. The bundle of nonce, challenge and wrapped-key are stored to
|
||||
// the keychain
|
||||
|
||||
// Each enrollment (per user) has a unique challenge, so that the windows-hello key is unique
|
||||
// Each enrollment (per user) has a unique challenge, so that the windows-hello key is
|
||||
// unique
|
||||
let challenge: [u8; CHALLENGE_LENGTH] = rand::random();
|
||||
|
||||
// This key is unique to the challenge
|
||||
@@ -155,8 +159,8 @@ impl super::BiometricTrait for BiometricLockSystem {
|
||||
});
|
||||
|
||||
let mut secure_memory = self.secure_memory.lock().await;
|
||||
// If the key is held ephemerally, always use UV API. Only use signing API if the key is not held
|
||||
// ephemerally but the keychain holds it persistently.
|
||||
// If the key is held ephemerally, always use UV API. Only use signing API if the key is not
|
||||
// held ephemerally but the keychain holds it persistently.
|
||||
if secure_memory.has(user_id) {
|
||||
if windows_hello_authenticate("Unlock your vault".to_string()).await? {
|
||||
secure_memory
|
||||
@@ -175,7 +179,8 @@ impl super::BiometricTrait for BiometricLockSystem {
|
||||
&keychain_entry.wrapped_key,
|
||||
&keychain_entry.nonce,
|
||||
)?;
|
||||
// The first unlock already sets the key for subsequent unlocks. The key may again be set externally after unlock finishes.
|
||||
// The first unlock already sets the key for subsequent unlocks. The key may again be
|
||||
// set externally after unlock finishes.
|
||||
secure_memory.put(user_id.to_string(), &decrypted_key.clone());
|
||||
Ok(decrypted_key)
|
||||
}
|
||||
@@ -231,8 +236,8 @@ async fn windows_hello_authenticate_with_crypto(
|
||||
) -> Result<[u8; XCHACHA20POLY1305_KEY_LENGTH]> {
|
||||
debug!("[Windows Hello] Authenticating to sign challenge");
|
||||
|
||||
// Ugly hack: We need to focus the window via window focusing APIs until Microsoft releases a new API.
|
||||
// This is unreliable, and if it does not work, the operation may fail
|
||||
// Ugly hack: We need to focus the window via window focusing APIs until Microsoft releases a
|
||||
// new API. This is unreliable, and if it does not work, the operation may fail
|
||||
let stop_focusing = Arc::new(AtomicBool::new(false));
|
||||
let stop_focusing_clone = stop_focusing.clone();
|
||||
let _ = std::thread::spawn(move || loop {
|
||||
@@ -243,8 +248,8 @@ async fn windows_hello_authenticate_with_crypto(
|
||||
break;
|
||||
}
|
||||
});
|
||||
// Only stop focusing once this function exits. The focus MUST run both during the initial creation
|
||||
// with RequestCreateAsync, and also with the subsequent use with RequestSignAsync.
|
||||
// Only stop focusing once this function exits. The focus MUST run both during the initial
|
||||
// creation with RequestCreateAsync, and also with the subsequent use with RequestSignAsync.
|
||||
let _guard = scopeguard::guard((), |_| {
|
||||
stop_focusing.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
});
|
||||
@@ -283,8 +288,8 @@ async fn windows_hello_authenticate_with_crypto(
|
||||
let signature_buffer = signature.Result()?;
|
||||
let signature_value = unsafe { as_mut_bytes(&signature_buffer)? };
|
||||
|
||||
// The signature is deterministic based on the challenge and keychain key. Thus, it can be hashed to a key.
|
||||
// It is unclear what entropy this key provides.
|
||||
// The signature is deterministic based on the challenge and keychain key. Thus, it can be
|
||||
// hashed to a key. It is unclear what entropy this key provides.
|
||||
let windows_hello_key = Sha256::digest(signature_value).into();
|
||||
Ok(windows_hello_key)
|
||||
}
|
||||
|
||||
@@ -34,23 +34,25 @@ pub fn focus_security_prompt() {
|
||||
/// Sets focus to a window using a few unstable methods
|
||||
fn set_focus(hwnd: HWND) {
|
||||
unsafe {
|
||||
// Windows REALLY does not like apps stealing focus, even if it is for fixing Windows-Hello bugs.
|
||||
// The windows hello signing prompt NEEDS to be focused instantly, or it will error, but it does
|
||||
// not focus itself.
|
||||
// Windows REALLY does not like apps stealing focus, even if it is for fixing Windows-Hello
|
||||
// bugs. The windows hello signing prompt NEEDS to be focused instantly, or it will
|
||||
// error, but it does not focus itself.
|
||||
|
||||
// This function implements forced focusing of windows using a few hacks.
|
||||
// The conditions to successfully foreground a window are:
|
||||
// All of the following conditions are true:
|
||||
// The calling process belongs to a desktop application, not a UWP app or a Windows Store app designed for Windows 8 or 8.1.
|
||||
// The foreground process has not disabled calls to SetForegroundWindow by a previous call to the LockSetForegroundWindow function.
|
||||
// The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
|
||||
// No menus are active.
|
||||
// - The calling process belongs to a desktop application, not a UWP app or a Windows
|
||||
// Store app designed for Windows 8 or 8.1.
|
||||
// - The foreground process has not disabled calls to SetForegroundWindow by a previous
|
||||
// call to the LockSetForegroundWindow function.
|
||||
// - The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in
|
||||
// SystemParametersInfo). No menus are active.
|
||||
// Additionally, at least one of the following conditions is true:
|
||||
// The calling process is the foreground process.
|
||||
// The calling process was started by the foreground process.
|
||||
// There is currently no foreground window, and thus no foreground process.
|
||||
// The calling process received the last input event.
|
||||
// Either the foreground process or the calling process is being debugged.
|
||||
// - The calling process is the foreground process.
|
||||
// - The calling process was started by the foreground process.
|
||||
// - There is currently no foreground window, and thus no foreground process.
|
||||
// - The calling process received the last input event.
|
||||
// - Either the foreground process or the calling process is being debugged.
|
||||
|
||||
// Update the foreground lock timeout temporarily
|
||||
let mut old_timeout = 0;
|
||||
@@ -75,7 +77,8 @@ fn set_focus(hwnd: HWND) {
|
||||
);
|
||||
});
|
||||
|
||||
// Attach to the foreground thread once attached, we can foreground, even if in the background
|
||||
// Attach to the foreground thread once attached, we can foreground, even if in the
|
||||
// background
|
||||
let dw_current_thread = GetCurrentThreadId();
|
||||
let dw_fg_thread = GetWindowThreadProcessId(GetForegroundWindow(), None);
|
||||
|
||||
@@ -91,7 +94,8 @@ fn set_focus(hwnd: HWND) {
|
||||
}
|
||||
}
|
||||
|
||||
/// When restoring focus to the application window, we need a less aggressive method so the electron window doesn't get frozen.
|
||||
/// When restoring focus to the application window, we need a less aggressive method so the electron
|
||||
/// window doesn't get frozen.
|
||||
pub(crate) fn restore_focus(hwnd: HWND) {
|
||||
unsafe {
|
||||
let _ = SetForegroundWindow(hwnd);
|
||||
|
||||
@@ -5,9 +5,8 @@ use aes::cipher::{
|
||||
BlockEncryptMut, KeyIvInit,
|
||||
};
|
||||
|
||||
use crate::error::{CryptoError, Result};
|
||||
|
||||
use super::CipherString;
|
||||
use crate::error::{CryptoError, Result};
|
||||
|
||||
pub fn decrypt_aes256(iv: &[u8; 16], data: &[u8], key: GenericArray<u8, U32>) -> Result<Vec<u8>> {
|
||||
let iv = GenericArray::from_slice(iv);
|
||||
@@ -16,7 +15,8 @@ pub fn decrypt_aes256(iv: &[u8; 16], data: &[u8], key: GenericArray<u8, U32>) ->
|
||||
.decrypt_padded_mut::<Pkcs7>(&mut data)
|
||||
.map_err(|_| CryptoError::KeyDecrypt)?;
|
||||
|
||||
// Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, we truncate to the subslice length
|
||||
// Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it,
|
||||
// we truncate to the subslice length
|
||||
let decrypted_len = decrypted_key_slice.len();
|
||||
data.truncate(decrypted_len);
|
||||
|
||||
|
||||
@@ -35,15 +35,4 @@ pub enum KdfParamError {
|
||||
InvalidParams(String),
|
||||
}
|
||||
|
||||
// Ensure that the error messages implement Send and Sync
|
||||
#[cfg(test)]
|
||||
const _: () = {
|
||||
fn assert_send<T: Send>() {}
|
||||
fn assert_sync<T: Sync>() {}
|
||||
fn assert_all() {
|
||||
assert_send::<Error>();
|
||||
assert_sync::<Error>();
|
||||
}
|
||||
};
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
@@ -49,7 +49,8 @@ pub fn path(name: &str) -> std::path::PathBuf {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// When running in an unsandboxed environment, path is: /Users/<user>/
|
||||
// While running sandboxed, it's different: /Users/<user>/Library/Containers/com.bitwarden.desktop/Data
|
||||
// While running sandboxed, it's different:
|
||||
// /Users/<user>/Library/Containers/com.bitwarden.desktop/Data
|
||||
let mut home = dirs::home_dir().unwrap();
|
||||
|
||||
// Check if the app is sandboxed by looking for the Containers directory
|
||||
@@ -59,8 +60,9 @@ pub fn path(name: &str) -> std::path::PathBuf {
|
||||
|
||||
// If the app is sanboxed, we need to use the App Group directory
|
||||
if let Some(position) = containers_position {
|
||||
// We want to use App Groups in /Users/<user>/Library/Group Containers/LTZ2PFU5D6.com.bitwarden.desktop,
|
||||
// so we need to remove all the components after the user. We can use the previous position to do this.
|
||||
// We want to use App Groups in /Users/<user>/Library/Group
|
||||
// Containers/LTZ2PFU5D6.com.bitwarden.desktop, so we need to remove all the
|
||||
// components after the user. We can use the previous position to do this.
|
||||
while home.components().count() > position - 1 {
|
||||
home.pop();
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use futures::{SinkExt, StreamExt, TryFutureExt};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::{SinkExt, StreamExt, TryFutureExt};
|
||||
use interprocess::local_socket::{tokio::prelude::*, GenericFilePath, ListenerOptions};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
@@ -42,14 +41,17 @@ impl Server {
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `name`: The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client.
|
||||
/// - `client_to_server_send`: This [`mpsc::Sender<Message>`] will receive all the [`Message`]'s that the clients send to this server.
|
||||
/// - `name`: The endpoint name to listen on. This name uniquely identifies the IPC connection
|
||||
/// and must be the same for both the server and client.
|
||||
/// - `client_to_server_send`: This [`mpsc::Sender<Message>`] will receive all the [`Message`]'s
|
||||
/// that the clients send to this server.
|
||||
pub fn start(
|
||||
path: &Path,
|
||||
client_to_server_send: mpsc::Sender<Message>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
// If the unix socket file already exists, we get an error when trying to bind to it. So we remove it first.
|
||||
// Any processes that were using the old socket should remain connected to it but any new connections will use the new socket.
|
||||
// If the unix socket file already exists, we get an error when trying to bind to it. So we
|
||||
// remove it first. Any processes that were using the old socket should remain
|
||||
// connected to it but any new connections will use the new socket.
|
||||
if !cfg!(windows) {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
@@ -58,8 +60,9 @@ impl Server {
|
||||
let opts = ListenerOptions::new().name(name);
|
||||
let listener = opts.create_tokio()?;
|
||||
|
||||
// This broadcast channel is used for sending messages to all connected clients, and so the sender
|
||||
// will be stored in the server while the receiver will be cloned and passed to each client handler.
|
||||
// This broadcast channel is used for sending messages to all connected clients, and so the
|
||||
// sender will be stored in the server while the receiver will be cloned and passed
|
||||
// to each client handler.
|
||||
let (server_to_clients_send, server_to_clients_recv) =
|
||||
broadcast::channel::<String>(MESSAGE_CHANNEL_BUFFER);
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
use anyhow::Result;
|
||||
use security_framework::passwords::{
|
||||
delete_generic_password, get_generic_password, set_generic_password,
|
||||
};
|
||||
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn get_password(service: &str, account: &str) -> Result<String> {
|
||||
let password = get_generic_password(service, account).map_err(convert_error)?;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use oo7::dbus::{self};
|
||||
use std::collections::HashMap;
|
||||
use tracing::info;
|
||||
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
|
||||
pub async fn get_password(service: &str, account: &str) -> Result<String> {
|
||||
match get_password_new(service, account).await {
|
||||
Ok(res) => Ok(res),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
use anyhow::{anyhow, Result};
|
||||
use widestring::{U16CString, U16String};
|
||||
use windows::{
|
||||
@@ -12,6 +11,8 @@ use windows::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
|
||||
const CRED_FLAGS_NONE: u32 = 0;
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
|
||||
@@ -4,15 +4,15 @@ use libc::c_uint;
|
||||
use libc::{self, c_int};
|
||||
use tracing::info;
|
||||
|
||||
// RLIMIT_CORE is the maximum size of a core dump file. Setting both to 0 disables core dumps, on crashes
|
||||
// https://github.com/torvalds/linux/blob/1613e604df0cd359cf2a7fbd9be7a0bcfacfabd0/include/uapi/asm-generic/resource.h#L20
|
||||
// RLIMIT_CORE is the maximum size of a core dump file. Setting both to 0 disables core dumps, on
|
||||
// crashes https://github.com/torvalds/linux/blob/1613e604df0cd359cf2a7fbd9be7a0bcfacfabd0/include/uapi/asm-generic/resource.h#L20
|
||||
#[cfg(target_env = "musl")]
|
||||
const RLIMIT_CORE: c_int = 4;
|
||||
#[cfg(target_env = "gnu")]
|
||||
const RLIMIT_CORE: c_uint = 4;
|
||||
|
||||
// PR_SET_DUMPABLE makes it so no other running process (root or same user) can dump the memory of this process
|
||||
// or attach a debugger to it.
|
||||
// PR_SET_DUMPABLE makes it so no other running process (root or same user) can dump the memory of
|
||||
// this process or attach a debugger to it.
|
||||
// https://github.com/torvalds/linux/blob/a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6/include/uapi/linux/prctl.h#L14
|
||||
const PR_SET_DUMPABLE: c_int = 4;
|
||||
|
||||
|
||||
@@ -29,8 +29,9 @@ impl SecureMemoryStore for DpapiSecretKVStore {
|
||||
fn put(&mut self, key: String, value: &[u8]) {
|
||||
let length_header_len = std::mem::size_of::<usize>();
|
||||
|
||||
// The allocated data has to be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE, so we pad it and write the length in front
|
||||
// We are storing LENGTH|DATA|00..00, where LENGTH is the length of DATA, the total length is a multiple
|
||||
// The allocated data has to be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE, so we pad it
|
||||
// and write the length in front We are storing LENGTH|DATA|00..00, where LENGTH is
|
||||
// the length of DATA, the total length is a multiple
|
||||
// of CRYPTPROTECTMEMORY_BLOCK_SIZE, and the padding is filled with zeros.
|
||||
|
||||
let data_len = value.len();
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::secure_memory::{
|
||||
/// allows circumventing length and amount limitations on platform specific secure memory APIs since
|
||||
/// only a single short item needs to be protected.
|
||||
///
|
||||
/// The key is briefly in process memory during encryption and decryption, in memory that is protected
|
||||
/// from swapping to disk via mlock, and then zeroed out immediately after use.
|
||||
/// The key is briefly in process memory during encryption and decryption, in memory that is
|
||||
/// protected from swapping to disk via mlock, and then zeroed out immediately after use.
|
||||
#[allow(unused)]
|
||||
pub(crate) struct EncryptedMemoryStore {
|
||||
map: std::collections::HashMap<String, EncryptedMemory>,
|
||||
|
||||
@@ -6,9 +6,9 @@ use rand::{rng, Rng};
|
||||
pub(super) const KEY_SIZE: usize = 32;
|
||||
pub(super) const NONCE_SIZE: usize = 24;
|
||||
|
||||
/// The encryption performed here is xchacha-poly1305. Any tampering with the key or the ciphertexts will result
|
||||
/// in a decryption failure and panic. The key's memory contents are protected from being swapped to disk
|
||||
/// via mlock.
|
||||
/// The encryption performed here is xchacha-poly1305. Any tampering with the key or the ciphertexts
|
||||
/// will result in a decryption failure and panic. The key's memory contents are protected from
|
||||
/// being swapped to disk via mlock.
|
||||
pub(super) struct MemoryEncryptionKey(NonNull<[u8]>);
|
||||
|
||||
/// An encrypted memory blob that must be decrypted using the same key that it was encrypted with.
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use super::crypto::{MemoryEncryptionKey, KEY_SIZE};
|
||||
use super::SecureKeyContainer;
|
||||
use windows::Win32::Security::Cryptography::{
|
||||
CryptProtectMemory, CryptUnprotectMemory, CRYPTPROTECTMEMORY_BLOCK_SIZE,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
};
|
||||
|
||||
use super::{
|
||||
crypto::{MemoryEncryptionKey, KEY_SIZE},
|
||||
SecureKeyContainer,
|
||||
};
|
||||
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
|
||||
/// The DPAPI store encrypts data using the Windows Data Protection API (DPAPI). The key is bound
|
||||
/// to the current process, and cannot be decrypted by other user-mode processes.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::secure_memory::secure_key::crypto::MemoryEncryptionKey;
|
||||
|
||||
use super::crypto::KEY_SIZE;
|
||||
use super::SecureKeyContainer;
|
||||
use linux_keyutils::{KeyRing, KeyRingIdentifier};
|
||||
|
||||
use super::{crypto::KEY_SIZE, SecureKeyContainer};
|
||||
use crate::secure_memory::secure_key::crypto::MemoryEncryptionKey;
|
||||
|
||||
/// The keys are bound to the process keyring.
|
||||
const KEY_RING_IDENTIFIER: KeyRingIdentifier = KeyRingIdentifier::Process;
|
||||
/// This is an atomic global counter used to help generate unique key IDs
|
||||
@@ -26,9 +25,9 @@ pub(super) struct KeyctlSecureKeyContainer {
|
||||
id: String,
|
||||
}
|
||||
|
||||
// SAFETY: The key id is fully owned by this struct and not exposed or cloned, and cleaned up on drop.
|
||||
// Further, since we use `KeyRingIdentifier::Process` and not `KeyRingIdentifier::Thread`, the key
|
||||
// is accessible across threads within the same process bound.
|
||||
// SAFETY: The key id is fully owned by this struct and not exposed or cloned, and cleaned up on
|
||||
// drop. Further, since we use `KeyRingIdentifier::Process` and not `KeyRingIdentifier::Thread`, the
|
||||
// key is accessible across threads within the same process bound.
|
||||
unsafe impl Send for KeyctlSecureKeyContainer {}
|
||||
// SAFETY: The container is non-mutable and thus safe to share between threads.
|
||||
unsafe impl Sync for KeyctlSecureKeyContainer {}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::{ptr::NonNull, sync::LazyLock};
|
||||
|
||||
use super::crypto::MemoryEncryptionKey;
|
||||
use super::crypto::KEY_SIZE;
|
||||
use super::SecureKeyContainer;
|
||||
use super::{
|
||||
crypto::{MemoryEncryptionKey, KEY_SIZE},
|
||||
SecureKeyContainer,
|
||||
};
|
||||
|
||||
/// https://man.archlinux.org/man/memfd_secret.2.en
|
||||
/// The memfd_secret store protects the data using the `memfd_secret` syscall. The
|
||||
@@ -15,8 +16,8 @@ pub(super) struct MemfdSecretSecureKeyContainer {
|
||||
// SAFETY: The pointers in this struct are allocated by `memfd_secret`, and we have full ownership.
|
||||
// They are never exposed outside or cloned, and are cleaned up by drop.
|
||||
unsafe impl Send for MemfdSecretSecureKeyContainer {}
|
||||
// SAFETY: The container is non-mutable and thus safe to share between threads. Further, memfd-secret
|
||||
// is accessible across threads within the same process bound.
|
||||
// SAFETY: The container is non-mutable and thus safe to share between threads. Further,
|
||||
// memfd-secret is accessible across threads within the same process bound.
|
||||
unsafe impl Sync for MemfdSecretSecureKeyContainer {}
|
||||
|
||||
impl SecureKeyContainer for MemfdSecretSecureKeyContainer {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use super::crypto::MemoryEncryptionKey;
|
||||
use super::crypto::KEY_SIZE;
|
||||
use super::SecureKeyContainer;
|
||||
use super::{
|
||||
crypto::{MemoryEncryptionKey, KEY_SIZE},
|
||||
SecureKeyContainer,
|
||||
};
|
||||
|
||||
/// A SecureKeyContainer that uses mlock to prevent the memory from being swapped to disk.
|
||||
/// This does not provide as strong protections as other methods, but is always supported.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
//! This module provides hardened storage for single cryptographic keys. These are meant for encrypting large amounts of memory.
|
||||
//! Some platforms restrict how many keys can be protected by their APIs, which necessitates this layer of indirection. This significantly
|
||||
//! reduces the complexity of each platform specific implementation, since all that's needed is implementing protecting a single fixed sized key
|
||||
//! instead of protecting many arbitrarily sized secrets. This significantly lowers the effort to maintain each implementation.
|
||||
//! This module provides hardened storage for single cryptographic keys. These are meant for
|
||||
//! encrypting large amounts of memory. Some platforms restrict how many keys can be protected by
|
||||
//! their APIs, which necessitates this layer of indirection. This significantly reduces the
|
||||
//! complexity of each platform specific implementation, since all that's needed is implementing
|
||||
//! protecting a single fixed sized key instead of protecting many arbitrarily sized secrets. This
|
||||
//! significantly lowers the effort to maintain each implementation.
|
||||
//!
|
||||
//! The implementations include DPAPI on Windows, `keyctl` on Linux, and `memfd_secret` on Linux, and a fallback implementation using mlock.
|
||||
//! The implementations include DPAPI on Windows, `keyctl` on Linux, and `memfd_secret` on Linux,
|
||||
//! and a fallback implementation using mlock.
|
||||
|
||||
use tracing::info;
|
||||
|
||||
@@ -20,12 +23,13 @@ pub use crypto::EncryptedMemory;
|
||||
|
||||
use crate::secure_memory::secure_key::crypto::DecryptionError;
|
||||
|
||||
/// An ephemeral key that is protected using a platform mechanism. It is generated on construction freshly, and can be used
|
||||
/// to encrypt and decrypt segments of memory. Since the key is ephemeral, persistent data cannot be encrypted with this key.
|
||||
/// On Linux and Windows, in most cases the protection mechanisms prevent memory dumps/debuggers from reading the key.
|
||||
/// An ephemeral key that is protected using a platform mechanism. It is generated on construction
|
||||
/// freshly, and can be used to encrypt and decrypt segments of memory. Since the key is ephemeral,
|
||||
/// persistent data cannot be encrypted with this key. On Linux and Windows, in most cases the
|
||||
/// protection mechanisms prevent memory dumps/debuggers from reading the key.
|
||||
///
|
||||
/// Note: This can be circumvented if code can be injected into the process and is only effective in combination with the
|
||||
/// memory isolation provided in `process_isolation`.
|
||||
/// Note: This can be circumvented if code can be injected into the process and is only effective in
|
||||
/// combination with the memory isolation provided in `process_isolation`.
|
||||
/// - https://github.com/zer1t0/keydump
|
||||
#[allow(unused)]
|
||||
pub(crate) struct SecureMemoryEncryptionKey(CrossPlatformSecureKeyContainer);
|
||||
@@ -55,7 +59,8 @@ impl SecureMemoryEncryptionKey {
|
||||
/// from memory attacks.
|
||||
#[allow(unused)]
|
||||
trait SecureKeyContainer: Sync + Send {
|
||||
/// Returns the key as a byte slice. This slice does not have additional memory protections applied.
|
||||
/// Returns the key as a byte slice. This slice does not have additional memory protections
|
||||
/// applied.
|
||||
fn as_key(&self) -> crypto::MemoryEncryptionKey;
|
||||
/// Creates a new SecureKeyContainer from the provided key.
|
||||
fn from_key(key: crypto::MemoryEncryptionKey) -> Self;
|
||||
|
||||
@@ -7,13 +7,12 @@ use std::{
|
||||
};
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use bitwarden_russh::{
|
||||
session_bind::SessionBindResult,
|
||||
ssh_agent::{self, SshKey},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info};
|
||||
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
@@ -34,7 +33,8 @@ pub struct BitwardenDesktopAgent {
|
||||
show_ui_request_tx: tokio::sync::mpsc::Sender<SshAgentUIRequest>,
|
||||
get_ui_response_rx: Arc<Mutex<tokio::sync::broadcast::Receiver<(u32, bool)>>>,
|
||||
request_id: Arc<AtomicU32>,
|
||||
/// before first unlock, or after account switching, listing keys should require an unlock to get a list of public keys
|
||||
/// before first unlock, or after account switching, listing keys should require an unlock to
|
||||
/// get a list of public keys
|
||||
needs_unlock: Arc<AtomicBool>,
|
||||
is_running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use futures::Stream;
|
||||
use std::os::windows::prelude::AsRawHandle as _;
|
||||
use std::{
|
||||
io,
|
||||
os::windows::prelude::AsRawHandle as _,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -9,6 +8,8 @@ use std::{
|
||||
},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::Stream;
|
||||
use tokio::{
|
||||
net::windows::named_pipe::{NamedPipeServer, ServerOptions},
|
||||
select,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::{
|
||||
io,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::Stream;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
|
||||
use super::peerinfo;
|
||||
use super::peerinfo::models::PeerInfo;
|
||||
use super::{peerinfo, peerinfo::models::PeerInfo};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PeercredUnixListenerStream {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::sync::{atomic::AtomicBool, Arc, Mutex};
|
||||
|
||||
/**
|
||||
* Peerinfo represents the information of a peer process connecting over a socket.
|
||||
* This can be later extended to include more information (icon, app name) for the corresponding application.
|
||||
*/
|
||||
* Peerinfo represents the information of a peer process connecting over a socket.
|
||||
* This can be later extended to include more information (icon, app name) for the corresponding
|
||||
* application.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PeerInfo {
|
||||
uid: u32,
|
||||
|
||||
@@ -6,9 +6,8 @@ use homedir::my_home;
|
||||
use tokio::{net::UnixListener, sync::Mutex};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream;
|
||||
|
||||
use super::{BitwardenDesktopAgent, SshAgentUIRequest};
|
||||
use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream;
|
||||
|
||||
/// User can override the default socket path with this env var
|
||||
const ENV_BITWARDEN_SSH_AUTH_SOCK: &str = "BITWARDEN_SSH_AUTH_SOCK";
|
||||
|
||||
@@ -2,6 +2,7 @@ use bitwarden_russh::ssh_agent;
|
||||
pub mod named_pipe_listener_stream;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::{BitwardenDesktopAgent, SshAgentUIRequest};
|
||||
|
||||
Reference in New Issue
Block a user