1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-13 23:13:36 +00:00

Merge branch 'main' into passkey-window-working

This commit is contained in:
Anders Åberg
2025-03-11 09:25:32 +01:00
2686 changed files with 159985 additions and 85589 deletions

View File

@@ -1,10 +1,10 @@
[package]
edition = "2021"
exclude = ["index.node"]
license = "GPL-3.0"
name = "desktop_napi"
version = "0.0.0"
publish = false
exclude = ["index.node"]
edition = { workspace = true }
license = { workspace = true }
version = { workspace = true }
publish = { workspace = true }
[lib]
crate-type = ["cdylib"]
@@ -16,18 +16,18 @@ manual_test = []
[dependencies]
base64 = "=0.22.1"
hex = "=0.4.3"
anyhow = "=1.0.94"
anyhow = { workspace = true }
desktop_core = { path = "../core" }
napi = { version = "=2.16.13", features = ["async"] }
napi = { version = "=2.16.15", features = ["async"] }
napi-derive = "=2.16.13"
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.127"
tokio = { version = "=1.41.1" }
tokio-util = "=0.7.12"
tokio-stream = "=0.1.15"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true }
tokio-util = { workspace = true }
tokio-stream = { workspace = true }
[target.'cfg(windows)'.dependencies]
windows-registry = "=0.3.0"
windows-registry = "=0.4.0"
[build-dependencies]
napi-build = "=2.1.3"
napi-build = "=2.1.4"

View File

@@ -51,30 +51,19 @@ export declare namespace sshagent {
publicKey: string
keyFingerprint: string
}
export const enum SshKeyImportStatus {
/** ssh key was parsed correctly and will be returned in the result */
Success = 0,
/** ssh key was parsed correctly but is encrypted and requires a password */
PasswordRequired = 1,
/** ssh key was parsed correctly, and a password was provided when calling the import, but it was incorrect */
WrongPassword = 2,
/** ssh key could not be parsed, either due to an incorrect / unsupported format (pkcs#8) or key type (ecdsa), or because the input is not an ssh key */
ParsingError = 3,
/** ssh key type is not supported (e.g. ecdsa) */
UnsupportedKeyType = 4
export interface SshUiRequest {
cipherId?: string
isList: boolean
processName: string
isForwarding: boolean
namespace?: string
}
export interface SshKeyImportResult {
status: SshKeyImportStatus
sshKey?: SshKey
}
export function serve(callback: (err: Error | null, arg0: string | undefined | null, arg1: boolean, arg2: string) => any): Promise<SshAgentState>
export function serve(callback: (err: Error | null, arg: SshUiRequest) => any): Promise<SshAgentState>
export function stop(agentState: SshAgentState): void
export function isRunning(agentState: SshAgentState): boolean
export function setKeys(agentState: SshAgentState, newKeys: Array<PrivateKey>): void
export function lock(agentState: SshAgentState): void
export function importKey(encodedKey: string, password: string): SshKeyImportResult
export function clearKeys(agentState: SshAgentState): void
export function generateKeypair(keyAlgorithm: string): Promise<SshKey>
export class SshAgentState { }
}
export declare namespace processisolations {

View File

@@ -1,209 +1,132 @@
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { existsSync } = require("fs");
const { join } = require("path");
const { platform, arch } = process
const { platform, arch } = process;
let nativeBinding = null
let localFileExisted = false
let loadError = null
let nativeBinding = null;
let localFileExisted = false;
let loadError = null;
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
} catch (e) {
return true
function loadFirstAvailable(localFiles, nodeModule) {
for (const localFile of localFiles) {
if (existsSync(join(__dirname, localFile))) {
return require(`./${localFile}`);
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
require(nodeModule);
}
switch (platform) {
case 'android':
case "android":
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'desktop_napi.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.android-arm64.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'desktop_napi.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.android-arm-eabi.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
case "arm64":
nativeBinding = loadFirstAvailable(
["desktop_napi.android-arm64.node"],
"@bitwarden/desktop-napi-android-arm64",
);
break;
case "arm":
nativeBinding = loadFirstAvailable(
["desktop_napi.android-arm.node"],
"@bitwarden/desktop-napi-android-arm",
);
break;
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
throw new Error(`Unsupported architecture on Android ${arch}`);
}
break
case 'win32':
break;
case "win32":
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'desktop_napi.win32-x64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.win32-x64-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(
join(__dirname, 'desktop_napi.win32-ia32-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.win32-ia32-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'desktop_napi.win32-arm64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.win32-arm64-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
case "x64":
nativeBinding = loadFirstAvailable(
["desktop_napi.win32-x64-msvc.node"],
"@bitwarden/desktop-napi-win32-x64-msvc",
);
break;
case "ia32":
nativeBinding = loadFirstAvailable(
["desktop_napi.win32-ia32-msvc.node"],
"@bitwarden/desktop-napi-win32-ia32-msvc",
);
break;
case "arm64":
nativeBinding = loadFirstAvailable(
["desktop_napi.win32-arm64-msvc.node"],
"@bitwarden/desktop-napi-win32-arm64-msvc",
);
break;
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
throw new Error(`Unsupported architecture on Windows: ${arch}`);
}
break
case 'darwin':
break;
case "darwin":
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'desktop_napi.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.darwin-x64.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'desktop_napi.darwin-arm64.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.darwin-arm64.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
case "x64":
nativeBinding = loadFirstAvailable(
["desktop_napi.darwin-x64.node"],
"@bitwarden/desktop-napi-darwin-x64",
);
break;
case "arm64":
nativeBinding = loadFirstAvailable(
["desktop_napi.darwin-arm64.node"],
"@bitwarden/desktop-napi-darwin-arm64",
);
break;
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
throw new Error(`Unsupported architecture on macOS: ${arch}`);
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'desktop_napi.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.freebsd-x64.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
break;
case "freebsd":
nativeBinding = loadFirstAvailable(
["desktop_napi.freebsd-x64.node"],
"@bitwarden/desktop-napi-freebsd-x64",
);
break;
case "linux":
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'desktop_napi.linux-x64-musl.node')
)
case "x64":
nativeBinding = loadFirstAvailable(
["desktop_napi.linux-x64-musl.node", "desktop_napi.linux-x64-gnu.node"],
"@bitwarden/desktop-napi-linux-x64-musl",
);
break;
case "arm64":
nativeBinding = loadFirstAvailable(
["desktop_napi.linux-arm64-musl.node", "desktop_napi.linux-arm64-gnu.node"],
"@bitwarden/desktop-napi-linux-arm64-musl",
);
break;
case "arm":
nativeBinding = loadFirstAvailable(
["desktop_napi.linux-arm-musl.node", "desktop_napi.linux-arm-gnu.node"],
"@bitwarden/desktop-napi-linux-arm-musl",
);
localFileExisted = existsSync(join(__dirname, "desktop_napi.linux-arm-gnueabihf.node"));
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.linux-x64-musl.node')
nativeBinding = require("./desktop_napi.linux-arm-gnueabihf.node");
} else {
nativeBinding = require('@bitwarden/desktop-napi-linux-x64-musl')
nativeBinding = require("@bitwarden/desktop-napi-linux-arm-gnueabihf");
}
} catch (e) {
loadError = e
loadError = e;
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'desktop_napi.linux-arm64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.linux-arm64-musl.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(
join(__dirname, 'desktop_napi.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_napi.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@bitwarden/desktop-napi-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
break
break;
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
throw new Error(`Unsupported architecture on Linux: ${arch}`);
}
break
break;
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);
}
if (!nativeBinding) {
if (loadError) {
throw loadError
throw loadError;
}
throw new Error(`Failed to load native binding`)
throw new Error(`Failed to load native binding`);
}
module.exports = nativeBinding
module.exports = nativeBinding;

View File

@@ -89,11 +89,9 @@ pub mod biometrics {
account: String,
key_material: Option<KeyMaterial>,
) -> napi::Result<String> {
let result =
Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into()))
.await
.map_err(|e| napi::Error::from_reason(e.to_string()));
result
Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into()))
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Derives key material from biometric data. Returns a string encoded with a
@@ -184,70 +182,18 @@ pub mod sshagent {
pub key_fingerprint: String,
}
impl From<desktop_core::ssh_agent::importer::SshKey> for SshKey {
fn from(key: desktop_core::ssh_agent::importer::SshKey) -> Self {
SshKey {
private_key: key.private_key,
public_key: key.public_key,
key_fingerprint: key.key_fingerprint,
}
}
}
#[napi]
pub enum SshKeyImportStatus {
/// ssh key was parsed correctly and will be returned in the result
Success,
/// ssh key was parsed correctly but is encrypted and requires a password
PasswordRequired,
/// ssh key was parsed correctly, and a password was provided when calling the import, but it was incorrect
WrongPassword,
/// ssh key could not be parsed, either due to an incorrect / unsupported format (pkcs#8) or key type (ecdsa), or because the input is not an ssh key
ParsingError,
/// ssh key type is not supported (e.g. ecdsa)
UnsupportedKeyType,
}
impl From<desktop_core::ssh_agent::importer::SshKeyImportStatus> for SshKeyImportStatus {
fn from(status: desktop_core::ssh_agent::importer::SshKeyImportStatus) -> Self {
match status {
desktop_core::ssh_agent::importer::SshKeyImportStatus::Success => {
SshKeyImportStatus::Success
}
desktop_core::ssh_agent::importer::SshKeyImportStatus::PasswordRequired => {
SshKeyImportStatus::PasswordRequired
}
desktop_core::ssh_agent::importer::SshKeyImportStatus::WrongPassword => {
SshKeyImportStatus::WrongPassword
}
desktop_core::ssh_agent::importer::SshKeyImportStatus::ParsingError => {
SshKeyImportStatus::ParsingError
}
desktop_core::ssh_agent::importer::SshKeyImportStatus::UnsupportedKeyType => {
SshKeyImportStatus::UnsupportedKeyType
}
}
}
}
#[napi(object)]
pub struct SshKeyImportResult {
pub status: SshKeyImportStatus,
pub ssh_key: Option<SshKey>,
}
impl From<desktop_core::ssh_agent::importer::SshKeyImportResult> for SshKeyImportResult {
fn from(result: desktop_core::ssh_agent::importer::SshKeyImportResult) -> Self {
SshKeyImportResult {
status: result.status.into(),
ssh_key: result.ssh_key.map(|k| k.into()),
}
}
pub struct SshUIRequest {
pub cipher_id: Option<String>,
pub is_list: bool,
pub process_name: String,
pub is_forwarding: bool,
pub namespace: Option<String>,
}
#[napi]
pub async fn serve(
callback: ThreadsafeFunction<(Option<String>, bool, String), CalleeHandled>,
callback: ThreadsafeFunction<SshUIRequest, CalleeHandled>,
) -> napi::Result<SshAgentState> {
let (auth_request_tx, mut auth_request_rx) =
tokio::sync::mpsc::channel::<desktop_core::ssh_agent::SshAgentUIRequest>(32);
@@ -264,11 +210,13 @@ pub mod sshagent {
let auth_response_tx_arc = cloned_response_tx_arc;
let callback = cloned_callback;
let promise_result: Result<Promise<bool>, napi::Error> = callback
.call_async(Ok((
request.cipher_id,
request.is_list,
request.process_name,
)))
.call_async(Ok(SshUIRequest {
cipher_id: request.cipher_id,
is_list: request.is_list,
process_name: request.process_name,
is_forwarding: request.is_forwarding,
namespace: request.namespace,
}))
.await;
match promise_result {
Ok(promise_result) => match promise_result.await {
@@ -350,13 +298,6 @@ pub mod sshagent {
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
#[napi]
pub fn import_key(encoded_key: String, password: String) -> napi::Result<SshKeyImportResult> {
let result = desktop_core::ssh_agent::importer::import_key(encoded_key, password)
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
Ok(result.into())
}
#[napi]
pub fn clear_keys(agent_state: &mut SshAgentState) -> napi::Result<()> {
let bitwarden_agent_state = &mut agent_state.state;
@@ -364,14 +305,6 @@ pub mod sshagent {
.clear_keys()
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
#[napi]
pub async fn generate_keypair(key_algorithm: String) -> napi::Result<SshKey> {
desktop_core::ssh_agent::generator::generate_keypair(key_algorithm)
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
.map(|k| k.into())
}
}
#[napi]
@@ -409,8 +342,8 @@ pub mod powermonitors {
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
tokio::spawn(async move {
while let Some(message) = rx.recv().await {
callback.call(Ok(message.into()), ThreadsafeFunctionCallMode::NonBlocking);
while let Some(()) = rx.recv().await {
callback.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking);
}
});
Ok(())
@@ -860,6 +793,6 @@ pub mod crypto {
desktop_core::crypto::argon2(&secret, &salt, iterations, memory, parallelism)
.map_err(|e| napi::Error::from_reason(e.to_string()))
.map(|v| v.to_vec())
.map(|v| Buffer::from(v))
.map(Buffer::from)
}
}