mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-27341] Chrome importer refactors (#16720)
Various refactors to the chrome importer
This commit is contained in:
54
apps/desktop/desktop_native/Cargo.lock
generated
54
apps/desktop/desktop_native/Cargo.lock
generated
@@ -440,33 +440,6 @@ dependencies = [
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitwarden_chromium_importer"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"cbc",
|
||||
"hex",
|
||||
"homedir",
|
||||
"napi",
|
||||
"napi-derive",
|
||||
"oo7",
|
||||
"pbkdf2",
|
||||
"rand 0.9.1",
|
||||
"rusqlite",
|
||||
"security-framework",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"tokio",
|
||||
"winapi",
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@@ -606,6 +579,31 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chromium_importer"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"cbc",
|
||||
"hex",
|
||||
"homedir",
|
||||
"oo7",
|
||||
"pbkdf2",
|
||||
"rand 0.9.1",
|
||||
"rusqlite",
|
||||
"security-framework",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"tokio",
|
||||
"winapi",
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@@ -968,7 +966,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"autotype",
|
||||
"base64",
|
||||
"bitwarden_chromium_importer",
|
||||
"chromium_importer",
|
||||
"desktop_core",
|
||||
"hex",
|
||||
"napi",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"autotype",
|
||||
"bitwarden_chromium_importer",
|
||||
"chromium_importer",
|
||||
"core",
|
||||
"macos_provider",
|
||||
"napi",
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
//! Cryptographic primitives used in the SDK
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use aes::cipher::{
|
||||
block_padding::Pkcs7, generic_array::GenericArray, typenum::U32, BlockDecryptMut, KeyIvInit,
|
||||
};
|
||||
|
||||
pub fn decrypt_aes256(iv: &[u8; 16], data: &[u8], key: GenericArray<u8, U32>) -> Result<Vec<u8>> {
|
||||
let iv = GenericArray::from_slice(iv);
|
||||
let mut data = data.to_vec();
|
||||
cbc::Decryptor::<aes::Aes256>::new(&key, iv)
|
||||
.decrypt_padded_mut::<Pkcs7>(&mut data)
|
||||
.map_err(|_| anyhow!("Failed to decrypt data"))?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use aes::cipher::{
|
||||
generic_array::{sequence::GenericSequence, GenericArray},
|
||||
ArrayLength,
|
||||
};
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
|
||||
pub fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
|
||||
(0..length).map(|i| offset + i as u8 * increment).collect()
|
||||
}
|
||||
pub fn generate_generic_array<N: ArrayLength<u8>>(
|
||||
offset: u8,
|
||||
increment: u8,
|
||||
) -> GenericArray<u8, N> {
|
||||
GenericArray::generate(|i| offset + i as u8 * increment)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_aes256() {
|
||||
let iv = generate_vec(16, 0, 1);
|
||||
let iv: &[u8; 16] = iv.as_slice().try_into().unwrap();
|
||||
let key = generate_generic_array(0, 1);
|
||||
let data: Vec<u8> = STANDARD.decode("ByUF8vhyX4ddU9gcooznwA==").unwrap();
|
||||
|
||||
let decrypted = super::decrypt_aes256(iv, &data, key).unwrap();
|
||||
|
||||
assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!\u{6}\u{6}\u{6}\u{6}\u{6}\u{6}");
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
pub mod chromium;
|
||||
pub mod metadata;
|
||||
pub mod util;
|
||||
|
||||
pub use crate::chromium::platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS;
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "bitwarden_chromium_importer"
|
||||
name = "chromium_importer"
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
version = { workspace = true }
|
||||
@@ -14,8 +14,6 @@ base64 = { workspace = true }
|
||||
cbc = { workspace = true, features = ["alloc"] }
|
||||
hex = { workspace = true }
|
||||
homedir = { workspace = true }
|
||||
napi = { workspace = true }
|
||||
napi-derive = { workspace = true }
|
||||
pbkdf2 = "=0.12.2"
|
||||
rand = { workspace = true }
|
||||
rusqlite = { version = "=0.37.0", features = ["bundled"] }
|
||||
@@ -36,4 +34,3 @@ oo7 = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
# Windows ABE Architecture
|
||||
# Chromium Direct Importer
|
||||
|
||||
## Overview
|
||||
A rust library that allows you to directly import credentials from Chromium-based browsers.
|
||||
|
||||
## Windows ABE Architecture
|
||||
|
||||
On Windows chrome has additional protection measurements which needs to be circumvented in order to
|
||||
get access to the passwords.
|
||||
|
||||
### Overview
|
||||
|
||||
The Windows Application Bound Encryption (ABE) consists of three main components that work together:
|
||||
|
||||
@@ -10,7 +17,7 @@ The Windows Application Bound Encryption (ABE) consists of three main components
|
||||
|
||||
_(The names of the binaries will be changed for the released product.)_
|
||||
|
||||
## The goal
|
||||
### The goal
|
||||
|
||||
The goal of this subsystem is to decrypt the master encryption key with which the login information
|
||||
is encrypted on the local system in Windows. This applies to the most recent versions of Chrome and
|
||||
@@ -24,7 +31,7 @@ Protection API at the system level on top of that. This triply encrypted key is
|
||||
|
||||
The next paragraphs describe what is done at each level to decrypt the key.
|
||||
|
||||
## 1. Client library
|
||||
### 1. Client library
|
||||
|
||||
This is a Rust module that is part of the Chromium importer. It only compiles and runs on Windows
|
||||
(see `abe.rs` and `abe_config.rs`). Its main task is to launch `admin.exe` with elevated privileges
|
||||
@@ -52,7 +59,7 @@ admin.exe --service-exe "c:\temp\service.exe" --encrypted "QVBQQgEAAADQjJ3fARXRE
|
||||
|
||||
**At this point, the user must permit the action to be performed on the UAC screen.**
|
||||
|
||||
## 2. Admin executable
|
||||
### 2. Admin executable
|
||||
|
||||
This executable receives the full path of `service.exe` and the data to be decrypted.
|
||||
|
||||
@@ -67,7 +74,7 @@ is sent to the named pipe server created by the user. The user responds with `ok
|
||||
|
||||
After that, the executable stops and uninstalls the service and then exits.
|
||||
|
||||
## 3. System service
|
||||
### 3. System service
|
||||
|
||||
The service starts and creates a named pipe server for communication between `admin.exe` and the
|
||||
system service. Please note that it is not possible to communicate between the user and the system
|
||||
@@ -83,7 +90,7 @@ removed from the system. Even though we send only one request, the service is de
|
||||
many clients with as many messages as needed and could be installed on the system permanently if
|
||||
necessary.
|
||||
|
||||
## 4. Back to client library
|
||||
### 4. Back to client library
|
||||
|
||||
The decrypted base64-encoded string comes back from the admin executable to the named pipe server at
|
||||
the user level. At this point, it has been decrypted only once at the system level.
|
||||
@@ -99,7 +106,7 @@ itself), it's either AES-256-GCM or ChaCha20Poly1305 encryption scheme. The deta
|
||||
After all of these steps, we have the master key which can be used to decrypt the password
|
||||
information stored in the local database.
|
||||
|
||||
## Summary
|
||||
### Summary
|
||||
|
||||
The Windows ABE decryption process involves a three-tier architecture with named pipe communication:
|
||||
|
||||
@@ -7,11 +7,9 @@ use hex::decode;
|
||||
use homedir::my_home;
|
||||
use rusqlite::{params, Connection};
|
||||
|
||||
// Platform-specific code
|
||||
#[cfg_attr(target_os = "linux", path = "linux.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
#[cfg_attr(target_os = "macos", path = "macos.rs")]
|
||||
pub mod platform;
|
||||
mod platform;
|
||||
|
||||
pub(crate) use platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS;
|
||||
|
||||
//
|
||||
// Public API
|
||||
@@ -22,10 +20,7 @@ pub struct ProfileInfo {
|
||||
pub name: String,
|
||||
pub folder: String,
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub account_name: Option<String>,
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub account_email: Option<String>,
|
||||
}
|
||||
|
||||
@@ -113,12 +108,12 @@ pub async fn import_logins(
|
||||
//
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BrowserConfig {
|
||||
pub(crate) struct BrowserConfig {
|
||||
pub name: &'static str,
|
||||
pub data_dir: &'static str,
|
||||
}
|
||||
|
||||
pub static SUPPORTED_BROWSER_MAP: LazyLock<
|
||||
pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock<
|
||||
std::collections::HashMap<&'static str, &'static BrowserConfig>,
|
||||
> = LazyLock::new(|| {
|
||||
platform::SUPPORTED_BROWSERS
|
||||
@@ -140,12 +135,12 @@ fn get_browser_data_dir(config: &BrowserConfig) -> Result<PathBuf> {
|
||||
//
|
||||
|
||||
#[async_trait]
|
||||
pub trait CryptoService: Send {
|
||||
pub(crate) trait CryptoService: Send {
|
||||
async fn decrypt_to_string(&mut self, encrypted: &[u8]) -> Result<String>;
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Clone)]
|
||||
pub struct LocalState {
|
||||
pub(crate) struct LocalState {
|
||||
profile: AllProfiles,
|
||||
#[allow(dead_code)]
|
||||
os_crypt: Option<OsCrypt>,
|
||||
@@ -198,16 +193,17 @@ fn load_local_state(browser_dir: &Path) -> Result<LocalState> {
|
||||
}
|
||||
|
||||
fn get_profile_info(local_state: &LocalState) -> Vec<ProfileInfo> {
|
||||
let mut profile_infos = Vec::new();
|
||||
for (name, info) in local_state.profile.info_cache.iter() {
|
||||
profile_infos.push(ProfileInfo {
|
||||
local_state
|
||||
.profile
|
||||
.info_cache
|
||||
.iter()
|
||||
.map(|(name, info)| ProfileInfo {
|
||||
name: info.name.clone(),
|
||||
folder: name.clone(),
|
||||
account_name: info.gaia_name.clone(),
|
||||
account_email: info.user_name.clone(),
|
||||
});
|
||||
}
|
||||
profile_infos
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct EncryptedLogin {
|
||||
@@ -264,17 +260,16 @@ fn hex_to_bytes(hex: &str) -> Vec<u8> {
|
||||
decode(hex).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn does_table_exist(conn: &Connection, table_name: &str) -> Result<bool, rusqlite::Error> {
|
||||
let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?1")?;
|
||||
let exists = stmt.exists(params![table_name])?;
|
||||
Ok(exists)
|
||||
fn table_exist(conn: &Connection, table_name: &str) -> Result<bool, rusqlite::Error> {
|
||||
conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?1")?
|
||||
.exists(params![table_name])
|
||||
}
|
||||
|
||||
fn query_logins(db_path: &str) -> Result<Vec<EncryptedLogin>, rusqlite::Error> {
|
||||
let conn = Connection::open(db_path)?;
|
||||
|
||||
let have_logins = does_table_exist(&conn, "logins")?;
|
||||
let have_password_notes = does_table_exist(&conn, "password_notes")?;
|
||||
let have_logins = table_exist(&conn, "logins")?;
|
||||
let have_password_notes = table_exist(&conn, "password_notes")?;
|
||||
if !have_logins || !have_password_notes {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
@@ -308,10 +303,7 @@ fn query_logins(db_path: &str) -> Result<Vec<EncryptedLogin>, rusqlite::Error> {
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut logins = Vec::new();
|
||||
for login in logins_iter {
|
||||
logins.push(login?);
|
||||
}
|
||||
let logins = logins_iter.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(logins)
|
||||
}
|
||||
@@ -13,7 +13,7 @@ use crate::util;
|
||||
//
|
||||
|
||||
// TODO: It's possible that there might be multiple possible data directories, depending on the installation method (e.g., snap, flatpak, etc.).
|
||||
pub const SUPPORTED_BROWSERS: [BrowserConfig; 4] = [
|
||||
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
|
||||
BrowserConfig {
|
||||
name: "Chrome",
|
||||
data_dir: ".config/google-chrome",
|
||||
@@ -32,7 +32,7 @@ pub const SUPPORTED_BROWSERS: [BrowserConfig; 4] = [
|
||||
},
|
||||
];
|
||||
|
||||
pub fn get_crypto_service(
|
||||
pub(crate) fn get_crypto_service(
|
||||
browser_name: &String,
|
||||
_local_state: &LocalState,
|
||||
) -> Result<Box<dyn CryptoService>> {
|
||||
@@ -10,7 +10,7 @@ use crate::util;
|
||||
// Public API
|
||||
//
|
||||
|
||||
pub const SUPPORTED_BROWSERS: [BrowserConfig; 7] = [
|
||||
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
|
||||
BrowserConfig {
|
||||
name: "Chrome",
|
||||
data_dir: "Library/Application Support/Google/Chrome",
|
||||
@@ -41,7 +41,7 @@ pub const SUPPORTED_BROWSERS: [BrowserConfig; 7] = [
|
||||
},
|
||||
];
|
||||
|
||||
pub fn get_crypto_service(
|
||||
pub(crate) fn get_crypto_service(
|
||||
browser_name: &String,
|
||||
_local_state: &LocalState,
|
||||
) -> Result<Box<dyn CryptoService>> {
|
||||
@@ -0,0 +1,7 @@
|
||||
// Platform-specific code
|
||||
#[cfg_attr(target_os = "linux", path = "linux.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
#[cfg_attr(target_os = "macos", path = "macos.rs")]
|
||||
mod native;
|
||||
|
||||
pub(crate) use native::*;
|
||||
@@ -15,8 +15,7 @@ use crate::util;
|
||||
// Public API
|
||||
//
|
||||
|
||||
// IMPORTANT adjust array size when enabling / disabling chromium importers here
|
||||
pub const SUPPORTED_BROWSERS: [BrowserConfig; 6] = [
|
||||
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
|
||||
BrowserConfig {
|
||||
name: "Brave",
|
||||
data_dir: "AppData/Local/BraveSoftware/Brave-Browser/User Data",
|
||||
@@ -43,7 +42,7 @@ pub const SUPPORTED_BROWSERS: [BrowserConfig; 6] = [
|
||||
},
|
||||
];
|
||||
|
||||
pub fn get_crypto_service(
|
||||
pub(crate) fn get_crypto_service(
|
||||
_browser_name: &str,
|
||||
local_state: &LocalState,
|
||||
) -> Result<Box<dyn CryptoService>> {
|
||||
5
apps/desktop/desktop_native/chromium_importer/src/lib.rs
Normal file
5
apps/desktop/desktop_native/chromium_importer/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod chromium;
|
||||
pub mod metadata;
|
||||
mod util;
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::{chromium::InstalledBrowserRetriever, PLATFORM_SUPPORTED_BROWSERS};
|
||||
use crate::chromium::{InstalledBrowserRetriever, PLATFORM_SUPPORTED_BROWSERS};
|
||||
|
||||
#[napi(object)]
|
||||
/// Mechanisms that load data into the importer
|
||||
pub struct NativeImporterMetadata {
|
||||
/// Identifies the importer
|
||||
@@ -24,7 +23,7 @@ pub fn get_supported_importers<T: InstalledBrowserRetriever>(
|
||||
// Check for installed browsers
|
||||
let installed_browsers = T::get_installed_browsers().unwrap_or_default();
|
||||
|
||||
const IMPORTERS: [(&str, &str); 6] = [
|
||||
const IMPORTERS: &[(&str, &str)] = &[
|
||||
("chromecsv", "Chrome"),
|
||||
("chromiumcsv", "Chromium"),
|
||||
("bravecsv", "Brave"),
|
||||
@@ -57,9 +56,7 @@ pub fn get_supported_importers<T: InstalledBrowserRetriever>(
|
||||
map
|
||||
}
|
||||
|
||||
/*
|
||||
Tests are cfg-gated based upon OS, and must be compiled/run on each OS for full coverage
|
||||
*/
|
||||
// Tests are cfg-gated based upon OS, and must be compiled/run on each OS for full coverage
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1,9 +1,6 @@
|
||||
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
|
||||
use anyhow::{anyhow, Result};
|
||||
use pbkdf2::{hmac::Hmac, pbkdf2};
|
||||
use sha1::Sha1;
|
||||
|
||||
pub fn split_encrypted_string(encrypted: &[u8]) -> Result<(&str, &[u8])> {
|
||||
fn split_encrypted_string(encrypted: &[u8]) -> Result<(&str, &[u8])> {
|
||||
if encrypted.len() < 3 {
|
||||
return Err(anyhow!(
|
||||
"Corrupted entry: invalid encrypted string length, expected at least 3 bytes, got {}",
|
||||
@@ -15,7 +12,14 @@ pub fn split_encrypted_string(encrypted: &[u8]) -> Result<(&str, &[u8])> {
|
||||
Ok((std::str::from_utf8(version)?, password))
|
||||
}
|
||||
|
||||
pub fn split_encrypted_string_and_validate<'a>(
|
||||
/// A Chromium password consists of three parts:
|
||||
/// - Version (3 bytes): "v10", "v11", etc.
|
||||
/// - Cipher text (chunks of 16 bytes)
|
||||
/// - Padding (1-15 bytes)
|
||||
///
|
||||
/// This function splits the encrypted byte slice into version and cipher text.
|
||||
/// Padding is included and handled by the underlying cryptographic library.
|
||||
pub(crate) fn split_encrypted_string_and_validate<'a>(
|
||||
encrypted: &'a [u8],
|
||||
supported_versions: &[&str],
|
||||
) -> Result<(&'a str, &'a [u8])> {
|
||||
@@ -27,15 +31,22 @@ pub fn split_encrypted_string_and_validate<'a>(
|
||||
Ok((version, password))
|
||||
}
|
||||
|
||||
pub fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
|
||||
let decryptor = cbc::Decryptor::<aes::Aes128>::new_from_slices(key, iv)?;
|
||||
let plaintext: Vec<u8> = decryptor
|
||||
/// Decrypt using AES-128 in CBC mode.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos", test))]
|
||||
pub(crate) fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
|
||||
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
|
||||
|
||||
cbc::Decryptor::<aes::Aes128>::new_from_slices(key, iv)?
|
||||
.decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
|
||||
.map_err(|e| anyhow!("Failed to decrypt: {}", e))?;
|
||||
Ok(plaintext)
|
||||
.map_err(|e| anyhow!("Failed to decrypt: {}", e))
|
||||
}
|
||||
|
||||
pub fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u8>> {
|
||||
/// 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};
|
||||
use sha1::Sha1;
|
||||
|
||||
let mut key = vec![0u8; 16];
|
||||
pbkdf2::<Hmac<Sha1>>(password, b"saltysalt", iterations, &mut key)
|
||||
.map_err(|e| anyhow!("Failed to derive master key: {}", e))?;
|
||||
@@ -44,16 +55,6 @@ pub fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u8>> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
|
||||
(0..length).map(|i| offset + i as u8 * increment).collect()
|
||||
}
|
||||
pub fn generate_generic_array<N: ArrayLength<u8>>(
|
||||
offset: u8,
|
||||
increment: u8,
|
||||
) -> GenericArray<u8, N> {
|
||||
GenericArray::generate(|i| offset + i as u8 * increment)
|
||||
}
|
||||
|
||||
use aes::cipher::{
|
||||
block_padding::Pkcs7,
|
||||
generic_array::{sequence::GenericSequence, GenericArray},
|
||||
@@ -64,6 +65,17 @@ mod tests {
|
||||
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,
|
||||
@@ -17,7 +17,7 @@ manual_test = []
|
||||
anyhow = { workspace = true }
|
||||
autotype = { path = "../autotype" }
|
||||
base64 = { workspace = true }
|
||||
bitwarden_chromium_importer = { path = "../bitwarden_chromium_importer" }
|
||||
chromium_importer = { path = "../chromium_importer" }
|
||||
desktop_core = { path = "../core" }
|
||||
hex = { workspace = true }
|
||||
napi = { workspace = true, features = ["async"] }
|
||||
|
||||
14
apps/desktop/desktop_native/napi/index.d.ts
vendored
14
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -3,15 +3,6 @@
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
/** Mechanisms that load data into the importer */
|
||||
export interface NativeImporterMetadata {
|
||||
/** Identifies the importer */
|
||||
id: string
|
||||
/** Describes the strategies used to obtain imported data */
|
||||
loaders: Array<string>
|
||||
/** Identifies the instructions for the importer */
|
||||
instructions: string
|
||||
}
|
||||
export declare namespace passwords {
|
||||
/** The error message returned when a password is not found during retrieval or deletion. */
|
||||
export const PASSWORD_NOT_FOUND: string
|
||||
@@ -249,6 +240,11 @@ export declare namespace chromium_importer {
|
||||
login?: Login
|
||||
failure?: LoginImportFailure
|
||||
}
|
||||
export interface NativeImporterMetadata {
|
||||
id: string
|
||||
loaders: Array<string>
|
||||
instructions: string
|
||||
}
|
||||
/** Returns OS aware metadata describing supported Chromium based importers as a JSON string. */
|
||||
export function getMetadata(): Record<string, NativeImporterMetadata>
|
||||
export function getInstalledBrowsers(): Array<string>
|
||||
|
||||
@@ -1064,11 +1064,13 @@ pub mod logging {
|
||||
|
||||
#[napi]
|
||||
pub mod chromium_importer {
|
||||
use bitwarden_chromium_importer::chromium::DefaultInstalledBrowserRetriever;
|
||||
use bitwarden_chromium_importer::chromium::InstalledBrowserRetriever;
|
||||
use bitwarden_chromium_importer::chromium::LoginImportResult as _LoginImportResult;
|
||||
use bitwarden_chromium_importer::chromium::ProfileInfo as _ProfileInfo;
|
||||
use bitwarden_chromium_importer::metadata::NativeImporterMetadata;
|
||||
use chromium_importer::{
|
||||
chromium::{
|
||||
DefaultInstalledBrowserRetriever, InstalledBrowserRetriever,
|
||||
LoginImportResult as _LoginImportResult, ProfileInfo as _ProfileInfo,
|
||||
},
|
||||
metadata::NativeImporterMetadata as _NativeImporterMetadata,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[napi(object)]
|
||||
@@ -1098,6 +1100,13 @@ pub mod chromium_importer {
|
||||
pub failure: Option<LoginImportFailure>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct NativeImporterMetadata {
|
||||
pub id: String,
|
||||
pub loaders: Vec<&'static str>,
|
||||
pub instructions: &'static str,
|
||||
}
|
||||
|
||||
impl From<_LoginImportResult> for LoginImportResult {
|
||||
fn from(l: _LoginImportResult) -> Self {
|
||||
match l {
|
||||
@@ -1131,23 +1140,34 @@ pub mod chromium_importer {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<_NativeImporterMetadata> for NativeImporterMetadata {
|
||||
fn from(m: _NativeImporterMetadata) -> Self {
|
||||
NativeImporterMetadata {
|
||||
id: m.id,
|
||||
loaders: m.loaders,
|
||||
instructions: m.instructions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
/// Returns OS aware metadata describing supported Chromium based importers as a JSON string.
|
||||
pub fn get_metadata() -> HashMap<String, NativeImporterMetadata> {
|
||||
bitwarden_chromium_importer::metadata::get_supported_importers::<
|
||||
DefaultInstalledBrowserRetriever,
|
||||
>()
|
||||
chromium_importer::metadata::get_supported_importers::<DefaultInstalledBrowserRetriever>()
|
||||
.into_iter()
|
||||
.map(|(browser, metadata)| (browser, NativeImporterMetadata::from(metadata)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_installed_browsers() -> napi::Result<Vec<String>> {
|
||||
bitwarden_chromium_importer::chromium::DefaultInstalledBrowserRetriever::get_installed_browsers()
|
||||
chromium_importer::chromium::DefaultInstalledBrowserRetriever::get_installed_browsers()
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_available_profiles(browser: String) -> napi::Result<Vec<ProfileInfo>> {
|
||||
bitwarden_chromium_importer::chromium::get_available_profiles(&browser)
|
||||
chromium_importer::chromium::get_available_profiles(&browser)
|
||||
.map(|profiles| profiles.into_iter().map(ProfileInfo::from).collect())
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
@@ -1157,7 +1177,7 @@ pub mod chromium_importer {
|
||||
browser: String,
|
||||
profile_id: String,
|
||||
) -> napi::Result<Vec<LoginImportResult>> {
|
||||
bitwarden_chromium_importer::chromium::import_logins(&browser, &profile_id)
|
||||
chromium_importer::chromium::import_logins(&browser, &profile_id)
|
||||
.await
|
||||
.map(|logins| logins.into_iter().map(LoginImportResult::from).collect())
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
|
||||
import type { NativeImporterMetadata } from "@bitwarden/desktop-napi";
|
||||
import type { chromium_importer } from "@bitwarden/desktop-napi";
|
||||
import {
|
||||
ImportType,
|
||||
DefaultImportMetadataService,
|
||||
@@ -25,7 +25,9 @@ export class DesktopImportMetadataService
|
||||
await super.init();
|
||||
}
|
||||
|
||||
private async parseNativeMetaData(raw: Record<string, NativeImporterMetadata>): Promise<void> {
|
||||
private async parseNativeMetaData(
|
||||
raw: Record<string, chromium_importer.NativeImporterMetadata>,
|
||||
): Promise<void> {
|
||||
const entries = Object.entries(raw).map(([id, meta]) => {
|
||||
const loaders = meta.loaders.map(this.mapLoader);
|
||||
const instructions = this.mapInstructions(meta.instructions);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
import type { NativeImporterMetadata } from "@bitwarden/desktop-napi";
|
||||
import type { chromium_importer } from "@bitwarden/desktop-napi";
|
||||
|
||||
const chromiumImporter = {
|
||||
getMetadata: (): Promise<Record<string, NativeImporterMetadata>> =>
|
||||
getMetadata: (): Promise<Record<string, chromium_importer.NativeImporterMetadata>> =>
|
||||
ipcRenderer.invoke("chromium_importer.getMetadata"),
|
||||
getInstalledBrowsers: (): Promise<string[]> =>
|
||||
ipcRenderer.invoke("chromium_importer.getInstalledBrowsers"),
|
||||
|
||||
Reference in New Issue
Block a user