1
0
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:
neuronull
2025-11-19 08:07:57 -07:00
committed by GitHub
parent 90ca6bf2cd
commit db16c201b8
59 changed files with 382 additions and 505 deletions

View File

@@ -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(),

View File

@@ -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 {}

View File

@@ -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]

View File

@@ -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");

View File

@@ -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>;
}

View File

@@ -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)
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>;

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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)?;

View File

@@ -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),

View File

@@ -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)]

View File

@@ -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;

View File

@@ -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();

View File

@@ -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>,

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 {}

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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;

View File

@@ -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>,
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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";

View File

@@ -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};