1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +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

@@ -684,17 +684,6 @@ dependencies = [
"error-code",
]
[[package]]
name = "codespan-reporting"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
dependencies = [
"serde",
"termcolor",
"unicode-width",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
@@ -841,65 +830,6 @@ dependencies = [
"syn",
]
[[package]]
name = "cxx"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a71ea7f29c73f7ffa64c50b83c9fe4d3a6d4be89a86b009eb80d5a6d3429d741"
dependencies = [
"cc",
"cxxbridge-cmd",
"cxxbridge-flags",
"cxxbridge-macro",
"foldhash",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36a8232661d66dcf713394726157d3cfe0a89bfc85f52d6e9f9bbc2306797fe7"
dependencies = [
"cc",
"codespan-reporting",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-cmd"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f44296c8693e9ea226a48f6a122727f77aa9e9e338380cb021accaeeb7ee279"
dependencies = [
"clap",
"codespan-reporting",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f69c181c176981ae44ba9876e2ea41ce8e574c296b38d06925ce9214fb8e4"
[[package]]
name = "cxxbridge-macro"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8faff5d4467e0709448187df29ccbf3b0982cc426ee444a193f87b11afb565a8"
dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "der"
version = "0.7.10"
@@ -921,27 +851,21 @@ dependencies = [
"ashpd",
"base64",
"bitwarden-russh",
"byteorder",
"bytes",
"cbc",
"chacha20poly1305",
"core-foundation",
"desktop_objc",
"dirs",
"ed25519",
"futures",
"homedir",
"interprocess",
"keytar",
"libc",
"linux-keyutils",
"memsec",
"oo7",
"pin-project",
"pkcs8",
"rand 0.9.1",
"rsa",
"russh-cryptovec",
"scopeguard",
"secmem-proc",
"security-framework",
@@ -949,12 +873,10 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"ssh-encoding",
"ssh-key",
"sysinfo",
"thiserror 2.0.12",
"tokio",
"tokio-stream",
"tokio-util",
"tracing",
"typenum",
@@ -972,18 +894,14 @@ version = "0.0.0"
dependencies = [
"anyhow",
"autotype",
"base64",
"chromium_importer",
"desktop_core",
"hex",
"napi",
"napi-build",
"napi-derive",
"serde",
"serde_json",
"tokio",
"tokio-stream",
"tokio-util",
"tracing",
"tracing-subscriber",
"windows-registry",
@@ -996,9 +914,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"cc",
"core-foundation",
"glob",
"thiserror 2.0.12",
"tokio",
"tracing",
]
@@ -1007,7 +923,6 @@ dependencies = [
name = "desktop_proxy"
version = "0.0.0"
dependencies = [
"anyhow",
"desktop_core",
"embed_plist",
"futures",
@@ -1740,27 +1655,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "keytar"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d361c55fba09829ac620b040f5425bf239b1030c3d6820a84acac8da867dca4d"
dependencies = [
"keytar-sys",
]
[[package]]
name = "keytar-sys"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe908c6896705a1cb516cd6a5d956c63f08d95ace81b93253a98cd93e1e6a65a"
dependencies = [
"cc",
"cxx",
"cxx-build",
"pkg-config",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -1813,15 +1707,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "link-cplusplus"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212"
dependencies = [
"cc",
]
[[package]]
name = "linux-keyutils"
version = "0.2.4"
@@ -1875,7 +1760,6 @@ dependencies = [
"serde",
"serde_json",
"tokio",
"tokio-util",
"tracing",
"tracing-oslog",
"tracing-subscriber",
@@ -2521,21 +2405,6 @@ dependencies = [
"spki",
]
[[package]]
name = "pkcs5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
dependencies = [
"aes",
"cbc",
"der",
"pbkdf2",
"scrypt",
"sha2",
"spki",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
@@ -2543,8 +2412,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"pkcs5",
"rand_core 0.6.4",
"spki",
]
@@ -2923,27 +2790,12 @@ dependencies = [
"rustix 1.0.7",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]]
name = "scc"
version = "2.4.0"
@@ -2959,12 +2811,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scratch"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52"
[[package]]
name = "scroll"
version = "0.12.0"
@@ -2985,17 +2831,6 @@ dependencies = [
"syn",
]
[[package]]
name = "scrypt"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
dependencies = [
"pbkdf2",
"salsa20",
"sha2",
]
[[package]]
name = "sdd"
version = "3.0.10"
@@ -3370,15 +3205,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "termtree"
version = "0.5.1"
@@ -3483,17 +3309,6 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-stream"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.13"
@@ -3693,12 +3508,6 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "uniffi"
version = "0.28.3"
@@ -4029,15 +3838,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View File

@@ -39,7 +39,6 @@ futures = "=0.3.31"
hex = "=0.4.3"
homedir = "=0.3.4"
interprocess = "=2.2.1"
keytar = "=0.1.6"
libc = "=0.2.172"
linux-keyutils = "=0.2.4"
memsec = "=0.7.0"
@@ -64,7 +63,6 @@ ssh-key = { version = "=0.6.7", default-features = false }
sysinfo = "=0.35.0"
thiserror = "=2.0.12"
tokio = "=1.45.0"
tokio-stream = "=0.1.15"
tokio-util = "=0.7.13"
tracing = "=0.1.41"
tracing-subscriber = { version = "=0.3.20", features = [

View File

@@ -33,7 +33,8 @@ impl InputOperations for Win32InputOperations {
/// Attempts to type the input text wherever the user's cursor is.
///
/// `input` must be a vector of utf-16 encoded characters to insert.
/// `keyboard_shortcut` must be a vector of Strings, where valid shortcut keys: Control, Alt, Super, Shift, letters a - Z
/// `keyboard_shortcut` must be a vector of Strings, where valid shortcut keys: Control, Alt, Super,
/// Shift, letters a - Z
///
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
pub(super) fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<()> {
@@ -234,16 +235,16 @@ where
#[cfg(test)]
mod tests {
//! For the mocking of the traits that are static methods, we need to use the `serial_test` crate
//! in order to mock those, since the mock expectations set have to be global in absence of a `self`.
//! More info: https://docs.rs/mockall/latest/mockall/#static-methods
//! For the mocking of the traits that are static methods, we need to use the `serial_test`
//! crate in order to mock those, since the mock expectations set have to be global in
//! absence of a `self`. More info: https://docs.rs/mockall/latest/mockall/#static-methods
use super::*;
use crate::windowing::MockErrorOperations;
use serial_test::serial;
use windows::Win32::Foundation::WIN32_ERROR;
use super::*;
use crate::windowing::MockErrorOperations;
#[test]
fn get_alphabetic_hot_key_succeeds() {
for c in ('a'..='z').chain('A'..='Z') {

View File

@@ -127,8 +127,8 @@ where
///
/// # Errors
///
/// - If the actual window title length (what the win32 API declares was written into the
/// buffer), is length zero and GetLastError() != 0 , return the GetLastError() message.
/// - If the actual window title length (what the win32 API declares was written into the buffer),
/// is length zero and GetLastError() != 0 , return the GetLastError() message.
fn get_window_title<H, E>(window_handle: &H, expected_title_length: usize) -> Result<String>
where
H: WindowHandleOperations,
@@ -169,17 +169,17 @@ where
#[cfg(test)]
mod tests {
//! For the mocking of the traits that are static methods, we need to use the `serial_test` crate
//! in order to mock those, since the mock expectations set have to be global in absence of a `self`.
//! More info: https://docs.rs/mockall/latest/mockall/#static-methods
//! For the mocking of the traits that are static methods, we need to use the `serial_test`
//! crate in order to mock those, since the mock expectations set have to be global in
//! absence of a `self`. More info: https://docs.rs/mockall/latest/mockall/#static-methods
use super::*;
use crate::windowing::MockErrorOperations;
use mockall::predicate;
use serial_test::serial;
use windows::Win32::Foundation::WIN32_ERROR;
use super::*;
use crate::windowing::MockErrorOperations;
#[test]
#[serial]
fn get_window_title_length_can_be_zero() {

View File

@@ -95,7 +95,8 @@ pub(crate) fn decode_abe_key_blob(blob_data: &[u8]) -> Result<Vec<u8>> {
let content_offset = content_len_offset + 4;
let content = get_safe(blob_data, content_offset, content_len)?;
// When the size is exactly 32 bytes, it's a plain key. It's used in unbranded Chromium builds, Brave, possibly Edge
// When the size is exactly 32 bytes, it's a plain key. It's used in unbranded Chromium builds,
// Brave, possibly Edge
if content_len == 32 {
return Ok(content.to_vec());
}

View File

@@ -30,7 +30,8 @@ pub(crate) fn start_impersonating() -> Result<HANDLE> {
// Need to enable SE_DEBUG_PRIVILEGE to enumerate and open SYSTEM processes
enable_debug_privilege()?;
// Find a SYSTEM process and get its token. Not every SYSTEM process allows token duplication, so try several.
// Find a SYSTEM process and get its token. Not every SYSTEM process allows token duplication,
// so try several.
let (token, pid, name) = find_system_process_with_token(get_system_pid_list())?;
// Impersonate the SYSTEM process

View File

@@ -1,13 +1,13 @@
use chromium_importer::config::{ENABLE_DEVELOPER_LOGGING, LOG_FILENAME};
use tracing::{error, level_filters::LevelFilter};
use tracing_subscriber::{
fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter, Layer as _,
};
use chromium_importer::config::{ENABLE_DEVELOPER_LOGGING, LOG_FILENAME};
pub(crate) fn init_logging() {
if ENABLE_DEVELOPER_LOGGING {
// We only log to a file. It's impossible to see stdout/stderr when this exe is launched from ShellExecuteW.
// We only log to a file. It's impossible to see stdout/stderr when this exe is launched
// from ShellExecuteW.
match std::fs::File::create(LOG_FILENAME) {
Ok(file) => {
let file_filter = EnvFilter::builder()

View File

@@ -1,12 +1,14 @@
use anyhow::{anyhow, Result};
use clap::Parser;
use scopeguard::defer;
use std::{
ffi::OsString,
os::windows::{ffi::OsStringExt as _, io::AsRawHandle},
path::PathBuf,
time::Duration,
};
use anyhow::{anyhow, Result};
use chromium_importer::chromium::{verify_signature, ADMIN_TO_USER_PIPE_NAME};
use clap::Parser;
use scopeguard::defer;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::windows::named_pipe::{ClientOptions, NamedPipeClient},
@@ -25,8 +27,6 @@ use windows::Win32::{
UI::Shell::IsUserAnAdmin,
};
use chromium_importer::chromium::{verify_signature, ADMIN_TO_USER_PIPE_NAME};
use super::{
crypto::{
decode_abe_key_blob, decode_base64, decrypt_with_dpapi_as_system,

View File

@@ -7,35 +7,38 @@ publish = { workspace = true }
[dependencies]
aes = { workspace = true }
aes-gcm = { workspace = true }
anyhow = { workspace = true }
async-trait = "=0.1.88"
base64 = { workspace = true }
cbc = { workspace = true, features = ["alloc"] }
dirs = { workspace = true }
hex = { workspace = true }
pbkdf2 = "=0.12.2"
rand = { workspace = true }
rusqlite = { version = "=0.37.0", features = ["bundled"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha1 = "=0.10.6"
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
cbc = { workspace = true, features = ["alloc"] }
pbkdf2 = "=0.12.2"
security-framework = { workspace = true }
sha1 = "=0.10.6"
[target.'cfg(target_os = "windows")'.dependencies]
aes-gcm = { workspace = true }
base64 = { workspace = true }
windows = { workspace = true, features = [
"Win32_Security_Cryptography",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging",
] }
verifysign = "=0.2.4"
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
[target.'cfg(target_os = "linux")'.dependencies]
cbc = { workspace = true, features = ["alloc"] }
oo7 = { workspace = true }
pbkdf2 = "=0.12.2"
sha1 = "=0.10.6"
[lints]
workspace = true

View File

@@ -1,6 +1,8 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::LazyLock,
};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
@@ -10,11 +12,10 @@ use rusqlite::{params, Connection};
mod platform;
pub(crate) use platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS;
#[cfg(target_os = "windows")]
pub use platform::*;
pub(crate) use platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS;
//
// Public API
//
@@ -87,14 +88,15 @@ pub async fn import_logins(
let local_logins = get_logins(&data_dir, profile_id, "Login Data")
.map_err(|e| anyhow!("Failed to query logins: {}", e))?;
// This is not available in all browsers, but there's no harm in trying. If the file doesn't exist we just get an empty vector.
// This is not available in all browsers, but there's no harm in trying. If the file doesn't
// exist we just get an empty vector.
let account_logins = get_logins(&data_dir, profile_id, "Login Data For Account")
.map_err(|e| anyhow!("Failed to query logins: {}", e))?;
// TODO: Do we need a better merge strategy? Maybe ignore duplicates at least?
// TODO: Should we also ignore an error from one of the two imports? If one is successful and the other fails,
// should we still return the successful ones? At the moment it doesn't fail for a missing file, only when
// something goes really wrong.
// TODO: Should we also ignore an error from one of the two imports? If one is successful and
// the other fails, should we still return the successful ones? At the moment it
// doesn't fail for a missing file, only when something goes really wrong.
let all_logins = local_logins
.into_iter()
.chain(account_logins.into_iter())

View File

@@ -4,15 +4,17 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait;
use oo7::XDG_SCHEMA_ATTRIBUTE;
use crate::chromium::{BrowserConfig, CryptoService, LocalState};
use crate::util;
use crate::{
chromium::{BrowserConfig, CryptoService, LocalState},
util,
};
//
// Public API
//
// TODO: It's possible that there might be multiple possible data directories, depending on the installation method (e.g., snap, flatpak, etc.).
// TODO: It's possible that there might be multiple possible data directories, depending on the
// installation method (e.g., snap, flatpak, etc.).
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
BrowserConfig {
name: "Chrome",

View File

@@ -2,9 +2,10 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait;
use security_framework::passwords::get_generic_password;
use crate::chromium::{BrowserConfig, CryptoService, LocalState};
use crate::util;
use crate::{
chromium::{BrowserConfig, CryptoService, LocalState},
util,
};
//
// Public API

View File

@@ -1,6 +1,6 @@
use super::abe_config;
use anyhow::{anyhow, Result};
use std::{ffi::OsStr, os::windows::ffi::OsStrExt};
use anyhow::{anyhow, Result};
use tokio::{
io::{self, AsyncReadExt, AsyncWriteExt},
net::windows::named_pipe::{NamedPipeServer, ServerOptions},
@@ -14,6 +14,8 @@ use windows::{
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_HIDE},
};
use super::abe_config;
const WAIT_FOR_ADMIN_MESSAGE_TIMEOUT_SECS: u64 = 30;
fn start_tokio_named_pipe_server<F>(

View File

@@ -1,11 +1,14 @@
use std::path::{Path, PathBuf};
use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit, Nonce};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
use std::path::{Path, PathBuf};
use crate::chromium::{BrowserConfig, CryptoService, LocalState};
use crate::util;
use crate::{
chromium::{BrowserConfig, CryptoService, LocalState},
util,
};
mod abe;
mod abe_config;
mod crypto;
@@ -95,7 +98,8 @@ impl CryptoService for WindowsCryptoService {
let (version, no_prefix) =
util::split_encrypted_string_and_validate(encrypted, &["v10", "v20"])?;
// v10 is already stripped; Windows Chrome uses AES-GCM: [12 bytes IV][ciphertext][16 bytes auth tag]
// v10 is already stripped; Windows Chrome uses AES-GCM: [12 bytes IV][ciphertext][16 bytes
// auth tag]
const IV_SIZE: usize = 12;
const TAG_SIZE: usize = 16;
const MIN_LENGTH: usize = IV_SIZE + TAG_SIZE;
@@ -242,8 +246,8 @@ fn get_dist_admin_exe_path(current_exe_full_path: &Path) -> Result<PathBuf> {
Ok(admin_exe)
}
// Try to find bitwarden_chromium_import_helper.exe in debug build folders. This might not cover all the cases.
// Tested on `npm run electron` from apps/desktop and apps/desktop/desktop_native.
// Try to find bitwarden_chromium_import_helper.exe in debug build folders. This might not cover all
// the cases. Tested on `npm run electron` from apps/desktop and apps/desktop/desktop_native.
fn get_debug_admin_exe_path() -> Result<PathBuf> {
let current_dir = std::env::current_dir()?;
let folder_name = current_dir

View File

@@ -1,5 +1,6 @@
use anyhow::{anyhow, Result};
use std::path::Path;
use anyhow::{anyhow, Result};
use tracing::{debug, info};
use verifysign::CodeSignVerifier;

View File

@@ -59,9 +59,9 @@ pub fn get_supported_importers<T: InstalledBrowserRetriever>(
// Tests are cfg-gated based upon OS, and must be compiled/run on each OS for full coverage
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use super::*;
use crate::chromium::{InstalledBrowserRetriever, SUPPORTED_BROWSER_MAP};
pub struct MockInstalledBrowserRetriever {}

View File

@@ -32,7 +32,7 @@ pub(crate) fn split_encrypted_string_and_validate<'a>(
}
/// Decrypt using AES-128 in CBC mode.
#[cfg(any(target_os = "linux", target_os = "macos", test))]
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(crate) fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
@@ -41,7 +41,8 @@ pub(crate) fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> R
.map_err(|e| anyhow!("Failed to decrypt: {}", e))
}
/// Derives a PBKDF2 key from the static "saltysalt" salt with the given password and iteration count.
/// Derives a PBKDF2 key from the static "saltysalt" salt with the given password and iteration
/// count.
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(crate) fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u8>> {
use pbkdf2::{hmac::Hmac, pbkdf2};
@@ -55,27 +56,9 @@ pub(crate) fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u
#[cfg(test)]
mod tests {
use aes::cipher::{
block_padding::Pkcs7,
generic_array::{sequence::GenericSequence, GenericArray},
ArrayLength, BlockEncryptMut, KeyIvInit,
};
const LENGTH16: usize = 16;
const LENGTH10: usize = 10;
const LENGTH0: usize = 0;
fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
(0..length).map(|i| offset + i as u8 * increment).collect()
}
fn generate_generic_array<N: ArrayLength<u8>>(
offset: u8,
increment: u8,
) -> GenericArray<u8, N> {
GenericArray::generate(|i| offset + i as u8 * increment)
}
fn run_split_encrypted_string_test<'a, const N: usize>(
successfully_split: bool,
plaintext_to_encrypt: &'a str,
@@ -144,8 +127,28 @@ mod tests {
run_split_encrypted_string_and_validate_test(false, "v10EncryptMe!", &[]);
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn test_decrypt_aes_128_cbc() {
use aes::cipher::{
block_padding::Pkcs7,
generic_array::{sequence::GenericSequence, GenericArray},
ArrayLength, BlockEncryptMut, KeyIvInit,
};
const LENGTH16: usize = 16;
fn generate_generic_array<N: ArrayLength<u8>>(
offset: u8,
increment: u8,
) -> GenericArray<u8, N> {
GenericArray::generate(|i| offset + i as u8 * increment)
}
fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
(0..length).map(|i| offset + i as u8 * increment).collect()
}
let offset = 0;
let increment = 1;

View File

@@ -23,27 +23,15 @@ anyhow = { workspace = true }
arboard = { workspace = true, features = ["wayland-data-control"] }
base64 = { workspace = true }
bitwarden-russh = { workspace = true }
byteorder = { workspace = true }
bytes = { workspace = true }
cbc = { workspace = true, features = ["alloc"] }
chacha20poly1305 = { workspace = true }
dirs = { workspace = true }
ed25519 = { workspace = true, features = ["pkcs8"] }
futures = { workspace = true }
homedir = { workspace = true }
interprocess = { workspace = true, features = ["tokio"] }
memsec = { workspace = true, features = ["alloc_ext"] }
pin-project = { workspace = true }
pkcs8 = { workspace = true, features = ["alloc", "encryption", "pem"] }
rand = { workspace = true }
rsa = { workspace = true }
russh-cryptovec = { workspace = true }
scopeguard = { workspace = true }
secmem-proc = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = { workspace = true }
ssh-encoding = { workspace = true }
ssh-key = { workspace = true, features = [
"encryption",
"ed25519",
@@ -53,13 +41,17 @@ ssh-key = { workspace = true, features = [
sysinfo = { workspace = true, features = ["windows"] }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["io-util", "sync", "macros", "net"] }
tokio-stream = { workspace = true, features = ["net"] }
tokio-util = { workspace = true, features = ["codec"] }
tracing = { workspace = true }
typenum = { workspace = true }
zeroizing-alloc = { workspace = true }
[target.'cfg(windows)'.dependencies]
pin-project = { workspace = true }
scopeguard = { workspace = true }
secmem-proc = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
widestring = { workspace = true, optional = true }
windows = { workspace = true, features = [
"Foundation",
@@ -76,21 +68,20 @@ windows = { workspace = true, features = [
], optional = true }
windows-future = { workspace = true }
[target.'cfg(windows)'.dev-dependencies]
keytar = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = { workspace = true, optional = true }
homedir = { workspace = true }
secmem-proc = { workspace = true }
security-framework = { workspace = true, optional = true }
security-framework-sys = { workspace = true, optional = true }
desktop_objc = { path = "../objc" }
[target.'cfg(target_os = "linux")'.dependencies]
oo7 = { workspace = true }
ashpd = { workspace = true }
homedir = { workspace = true }
libc = { workspace = true }
linux-keyutils = { workspace = true }
ashpd = { workspace = true }
oo7 = { workspace = true }
zbus = { workspace = true, optional = true }
zbus_polkit = { workspace = true, optional = true }

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

View File

@@ -14,17 +14,16 @@ crate-type = ["staticlib", "cdylib"]
bench = false
[dependencies]
uniffi = { workspace = true, features = ["cli"] }
[target.'cfg(target_os = "macos")'.dependencies]
desktop_core = { path = "../core" }
futures = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
tokio-util = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
uniffi = { workspace = true, features = ["cli"] }
[target.'cfg(target_os = "macos")'.dependencies]
tracing-oslog = "0.3.0"
[build-dependencies]

View File

@@ -16,17 +16,13 @@ manual_test = []
[dependencies]
anyhow = { workspace = true }
autotype = { path = "../autotype" }
base64 = { workspace = true }
chromium_importer = { path = "../chromium_importer" }
desktop_core = { path = "../core" }
hex = { workspace = true }
napi = { workspace = true, features = ["async"] }
napi-derive = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tokio-util = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }

View File

@@ -11,7 +11,10 @@ export declare namespace passwords {
* Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist.
*/
export function getPassword(service: string, account: string): Promise<string>
/** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */
/**
* Save the password to the keychain. Adds an entry if none exists otherwise updates the
* existing entry.
*/
export function setPassword(service: string, account: string, password: string): Promise<void>
/**
* Delete the stored password from the keychain.
@@ -35,7 +38,8 @@ export declare namespace biometrics {
* base64 encoded key and the base64 encoded challenge used to create it
* separated by a `|` character.
*
* If the iv is provided, it will be used as the challenge. Otherwise a random challenge will be generated.
* If the iv is provided, it will be used as the challenge. Otherwise a random challenge will
* be generated.
*
* `format!("<key_base64>|<iv_base64>")`
*/
@@ -119,8 +123,9 @@ export declare namespace ipc {
/**
* Create and start the IPC server without blocking.
*
* @param 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.
* @param callback This function will be called whenever a message is received from a client.
* @param 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. @param callback
* This function will be called whenever a message is received from a client.
*/
static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise<IpcServer>
/** Return the path to the IPC server. */
@@ -130,8 +135,9 @@ export declare namespace ipc {
/**
* Send a message over the IPC server to all the connected clients
*
* @return The number of clients that the message was sent to. Note that the number of messages
* actually received may be less, as some clients could disconnect before receiving the message.
* @return The number of clients that the message was sent to. Note that the number of
* messages actually received may be less, as some clients could disconnect before
* receiving the message.
*/
send(message: string): number
}
@@ -194,8 +200,9 @@ export declare namespace autofill {
/**
* Create and start the IPC server without blocking.
*
* @param 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.
* @param callback This function will be called whenever a message is received from a client.
* @param 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. @param callback
* This function will be called whenever a message is received from a client.
*/
static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void, assertionWithoutUserInterfaceCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void): Promise<IpcServer>
/** Return the path to the IPC server. */

View File

@@ -19,7 +19,8 @@ pub mod passwords {
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry.
/// Save the password to the keychain. Adds an entry if none exists otherwise updates the
/// existing entry.
#[napi]
pub async fn set_password(
service: String,
@@ -107,7 +108,8 @@ pub mod biometrics {
/// base64 encoded key and the base64 encoded challenge used to create it
/// separated by a `|` character.
///
/// If the iv is provided, it will be used as the challenge. Otherwise a random challenge will be generated.
/// If the iv is provided, it will be used as the challenge. Otherwise a random challenge will
/// be generated.
///
/// `format!("<key_base64>|<iv_base64>")`
#[allow(clippy::unused_async)] // FIXME: Remove unused async!
@@ -556,8 +558,9 @@ pub mod ipc {
impl IpcServer {
/// Create and start the IPC server without blocking.
///
/// @param 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.
/// @param callback This function will be called whenever a message is received from a client.
/// @param 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. @param callback
/// This function will be called whenever a message is received from a client.
#[allow(clippy::unused_async)] // FIXME: Remove unused async!
#[napi(factory)]
pub async fn listen(
@@ -598,8 +601,9 @@ pub mod ipc {
/// Send a message over the IPC server to all the connected clients
///
/// @return The number of clients that the message was sent to. Note that the number of messages
/// actually received may be less, as some clients could disconnect before receiving the message.
/// @return The number of clients that the message was sent to. Note that the number of
/// messages actually received may be less, as some clients could disconnect before
/// receiving the message.
#[napi]
pub fn send(&self, message: String) -> napi::Result<u32> {
self.server
@@ -743,8 +747,9 @@ pub mod autofill {
impl IpcServer {
/// Create and start the IPC server without blocking.
///
/// @param 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.
/// @param callback This function will be called whenever a message is received from a client.
/// @param 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. @param callback
/// This function will be called whenever a message is received from a client.
#[allow(clippy::unused_async)] // FIXME: Remove unused async!
#[napi(factory)]
pub async fn listen(
@@ -946,18 +951,21 @@ pub mod logging {
//!
//! # Example
//!
//! [Elec] 14:34:03.517 [NAPI] [INFO] desktop_core::ssh_agent::platform_ssh_agent: Starting SSH Agent server {socket=/Users/foo/.bitwarden-ssh-agent.sock}
//! [Elec] 14:34:03.517 [NAPI] [INFO] desktop_core::ssh_agent::platform_ssh_agent: Starting
//! SSH Agent server {socket=/Users/foo/.bitwarden-ssh-agent.sock}
use std::fmt::Write;
use std::sync::OnceLock;
use std::{fmt::Write, sync::OnceLock};
use napi::threadsafe_function::{
ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode,
};
use tracing::Level;
use tracing_subscriber::fmt::format::{DefaultVisitor, Writer};
use tracing_subscriber::{
filter::EnvFilter, layer::SubscriberExt, util::SubscriberInitExt, Layer,
filter::EnvFilter,
fmt::format::{DefaultVisitor, Writer},
layer::SubscriberExt,
util::SubscriberInitExt,
Layer,
};
struct JsLogger(OnceLock<ThreadsafeFunction<(LogLevel, String), CalleeHandled>>);
@@ -1069,6 +1077,8 @@ pub mod logging {
#[napi]
pub mod chromium_importer {
use std::collections::HashMap;
use chromium_importer::{
chromium::{
DefaultInstalledBrowserRetriever, LoginImportResult as _LoginImportResult,
@@ -1076,7 +1086,6 @@ pub mod chromium_importer {
},
metadata::NativeImporterMetadata as _NativeImporterMetadata,
};
use std::collections::HashMap;
#[napi(object)]
pub struct ProfileInfo {

View File

@@ -8,16 +8,12 @@ publish = { workspace = true }
[features]
default = []
[dependencies]
[target.'cfg(target_os = "macos")'.dependencies]
anyhow = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "=0.10.1"
[build-dependencies]
[target.'cfg(target_os = "macos")'.build-dependencies]
cc = "=1.2.4"
glob = "=0.3.2"

View File

@@ -8,7 +8,7 @@ publish = { workspace = true }
[lib]
crate-type = ["cdylib"]
[dependencies]
[target.'cfg(target_os = "linux")'.dependencies]
ctor = { workspace = true }
desktop_core = { path = "../core" }
libc = { workspace = true }

View File

@@ -5,8 +5,9 @@
//! On Linux, this is PR_SET_DUMPABLE to prevent debuggers from attaching, the env
//! from being read and the memory from being stolen.
use desktop_core::process_isolation;
use std::{ffi::c_char, sync::LazyLock};
use desktop_core::process_isolation;
use tracing::info;
static ORIGINAL_UNSETENV: LazyLock<unsafe extern "C" fn(*const c_char) -> i32> =

View File

@@ -6,7 +6,6 @@ version = { workspace = true }
publish = { workspace = true }
[dependencies]
anyhow = { workspace = true }
desktop_core = { path = "../core" }
futures = { workspace = true }
tokio = { workspace = true, features = ["io-std", "io-util", "macros", "rt"] }

View File

@@ -60,7 +60,6 @@ fn init_logging(log_path: &Path, console_level: LevelFilter, file_level: LevelFi
/// a stable communication channel between the proxy and the running desktop application.
///
/// Browser extension <-[native messaging]-> proxy <-[ipc]-> desktop
///
// FIXME: Remove unwraps! They panic and terminate the whole application.
#[allow(clippy::unwrap_used)]
#[tokio::main(flavor = "current_thread")]
@@ -83,8 +82,10 @@ async fn main() {
// Different browsers send different arguments when the app starts:
//
// Firefox:
// - The complete path to the app manifest. (in the form `/Users/<user>/Library/.../Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json`)
// - (in Firefox 55+) the ID (as given in the manifest.json) of the add-on that started it (in the form `{[UUID]}`).
// - The complete path to the app manifest. (in the form
// `/Users/<user>/Library/.../Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json`)
// - (in Firefox 55+) the ID (as given in the manifest.json) of the add-on that started it (in
// the form `{[UUID]}`).
//
// Chrome on Windows:
// - Origin of the extension that started it (in the form `chrome-extension://[ID]`).
@@ -96,7 +97,8 @@ async fn main() {
let args: Vec<_> = std::env::args().skip(1).collect();
info!(?args, "Process args");
// Setup two channels, one for sending messages to the desktop application (`out`) and one for receiving messages from the desktop application (`in`)
// Setup two channels, one for sending messages to the desktop application (`out`) and one for
// receiving messages from the desktop application (`in`)
let (in_send, in_recv) = tokio::sync::mpsc::channel(MESSAGE_CHANNEL_BUFFER);
let (out_send, mut out_recv) = tokio::sync::mpsc::channel(MESSAGE_CHANNEL_BUFFER);

View File

@@ -0,0 +1,7 @@
# Wrap comments and increase the width of comments to 100
comment_width = 100
wrap_comments = true
# Sort and group imports
group_imports = "StdExternalCrate"
imports_granularity = "Crate"

View File

@@ -2,11 +2,12 @@
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
use std::ffi::c_uchar;
use std::ptr;
use windows::Win32::Foundation::*;
use windows::Win32::System::Com::*;
use windows::Win32::System::LibraryLoader::*;
use std::{ffi::c_uchar, ptr};
use windows::Win32::{
Foundation::*,
System::{Com::*, LibraryLoader::*},
};
use windows_core::*;
mod pluginauthenticator;