1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-03 00:53:23 +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

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