1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 22:33:35 +00:00

[PM-22271] Switch to SDK argon2 implementation, and drop other impls (#15401)

* Switch to SDK argon2 implementation

* Cleanup and update to the latest sdk

* Update package lock

* Remove copy patch

* Fix builds

* Fix test build

* Remove error

* Fix tests

* Fix build

* Run prettier

* Remove argon2 references

* Regenerate index.d.ts for desktop_native napi

* Replace mocked crypto function service type
This commit is contained in:
Bernd Schoolmann
2025-07-15 11:53:58 +02:00
committed by GitHub
parent 1315e7c37c
commit 8250e40c6c
27 changed files with 44 additions and 697 deletions

View File

@@ -92,11 +92,6 @@ const moduleRules = [
test: /\.[jt]sx?$/,
loader: "@ngtools/webpack",
},
{
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
];
const requiredPlugins = [
@@ -290,7 +285,6 @@ const mainConfig = {
clean: true,
},
module: {
noParse: /argon2(-simd)?\.wasm$/,
rules: moduleRules,
},
experiments: {
@@ -365,13 +359,7 @@ if (manifestVersion == 2) {
test: /\.tsx?$/,
loader: "ts-loader",
},
{
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
],
noParse: /argon2(-simd)?\.wasm$/,
},
cache:
ENV !== "development"

View File

@@ -59,14 +59,12 @@
},
"pkg": {
"assets": [
"./build/**/*",
"../../node_modules/argon2/**/*"
"./build/**/*"
]
},
"dependencies": {
"@koa/multer": "4.0.0",
"@koa/router": "13.1.0",
"argon2": "0.41.1",
"big-integer": "1.6.52",
"browser-hrtime": "1.1.8",
"chalk": "4.1.2",

View File

@@ -135,19 +135,6 @@ dependencies = [
"x11rb",
]
[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"password-hash",
"zeroize",
]
[[package]]
name = "ashpd"
version = "0.11.0"
@@ -452,15 +439,6 @@ dependencies = [
"tokio-util",
]
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -887,7 +865,6 @@ dependencies = [
"aes",
"anyhow",
"arboard",
"argon2",
"ashpd",
"base64",
"bitwarden-russh",
@@ -2264,17 +2241,6 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "paste"
version = "1.0.15"

View File

@@ -12,7 +12,6 @@ publish = false
aes = "=0.8.4"
anyhow = "=1.0.94"
arboard = { version = "=3.6.0", default-features = false }
argon2 = "=0.5.3"
ashpd = "=0.11.0"
base64 = "=0.22.1"
bindgen = "=0.72.0"

View File

@@ -23,7 +23,6 @@ anyhow = { workspace = true }
arboard = { workspace = true, features = [
"wayland-data-control",
] }
argon2 = { workspace = true, features = ["zeroize"] }
base64 = { workspace = true }
byteorder = { workspace = true }
cbc = { workspace = true, features = ["alloc"] }

View File

@@ -5,7 +5,7 @@ use aes::cipher::{
BlockEncryptMut, KeyIvInit,
};
use crate::error::{CryptoError, KdfParamError, Result};
use crate::error::{CryptoError, Result};
use super::CipherString;
@@ -33,53 +33,3 @@ pub fn encrypt_aes256(
Ok(CipherString::AesCbc256_B64 { iv, data })
}
pub fn argon2(
secret: &[u8],
salt: &[u8],
iterations: u32,
memory: u32,
parallelism: u32,
) -> Result<[u8; 32]> {
use argon2::*;
let params = Params::new(memory, iterations, parallelism, Some(32)).map_err(|e| {
KdfParamError::InvalidParams(format!("Argon2 parameters are invalid: {e}",))
})?;
let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut hash = [0u8; 32];
argon
.hash_password_into(secret, salt, &mut hash)
.map_err(|e| KdfParamError::InvalidParams(format!("Argon2 hashing failed: {e}",)))?;
// Argon2 is using some stack memory that is not zeroed. Eventually some function will
// overwrite the stack, but we use this trick to force the used stack to be zeroed.
#[inline(never)]
fn clear_stack() {
std::hint::black_box([0u8; 4096]);
}
clear_stack();
Ok(hash)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_argon2() {
let test_hash: [u8; 32] = [
112, 200, 85, 209, 100, 4, 246, 146, 117, 180, 152, 44, 103, 198, 75, 14, 166, 77, 201,
22, 62, 178, 87, 224, 95, 209, 253, 68, 166, 209, 47, 218,
];
let secret = b"supersecurepassword";
let salt = b"mail@example.com";
let iterations = 3;
let memory = 1024 * 64;
let parallelism = 4;
let hash = argon2(secret, salt, iterations, memory, parallelism).unwrap();
assert_eq!(hash, test_hash,);
}
}

View File

@@ -9,8 +9,6 @@ pub enum Error {
#[error("Cryptography Error, {0}")]
Crypto(#[from] CryptoError),
#[error("KDF Parameter Error, {0}")]
KdfParam(#[from] KdfParamError),
}
#[derive(Debug, Error)]

View File

@@ -195,9 +195,6 @@ export declare namespace autofill {
completeError(clientId: number, sequenceNumber: number, error: string): number
}
}
export declare namespace crypto {
export function argon2(secret: Buffer, salt: Buffer, iterations: number, memory: number, parallelism: number): Promise<Buffer>
}
export declare namespace passkey_authenticator {
export function register(): void
}

View File

@@ -798,25 +798,6 @@ pub mod autofill {
}
}
#[napi]
pub mod crypto {
use napi::bindgen_prelude::Buffer;
#[napi]
pub async fn argon2(
secret: Buffer,
salt: Buffer,
iterations: u32,
memory: u32,
parallelism: u32,
) -> napi::Result<Buffer> {
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(Buffer::from)
}
}
#[napi]
pub mod passkey_authenticator {
#[napi]

View File

@@ -1,31 +0,0 @@
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypto/services/web-crypto-function.service";
export class RendererCryptoFunctionService
extends WebCryptoFunctionService
implements CryptoFunctionService
{
constructor(win: Window | typeof global) {
super(win);
}
// We can't use the `argon2-browser` implementation because it loads WASM and the Content Security Policy doesn't allow it.
// Rather than trying to weaken the policy, we'll just use the Node.js implementation though the IPC channel.
// Note that the rest of the functions on this service will be inherited from the WebCryptoFunctionService, as those work just fine.
async argon2(
password: string | Uint8Array,
salt: string | Uint8Array,
iterations: number,
memory: number,
parallelism: number,
): Promise<Uint8Array> {
if (typeof password === "string") {
password = new TextEncoder().encode(password);
}
if (typeof salt === "string") {
salt = new TextEncoder().encode(salt);
}
return await ipc.platform.crypto.argon2(password, salt, iterations, memory, parallelism);
}
}

View File

@@ -55,6 +55,7 @@ import { ClientType } from "@bitwarden/common/enums";
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypto/services/web-crypto-function.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service";
import {
@@ -140,7 +141,6 @@ import { DesktopFileDownloadService } from "./desktop-file-download.service";
import { DesktopSetPasswordJitService } from "./desktop-set-password-jit.service";
import { InitService } from "./init.service";
import { NativeMessagingManifestService } from "./native-messaging-manifest.service";
import { RendererCryptoFunctionService } from "./renderer-crypto-function.service";
import { DesktopSetInitialPasswordService } from "./set-initial-password/desktop-set-initial-password.service";
const RELOAD_CALLBACK = new SafeInjectionToken<() => any>("RELOAD_CALLBACK");
@@ -296,7 +296,7 @@ const safeProviders: SafeProvider[] = [
}),
safeProvider({
provide: CryptoFunctionServiceAbstraction,
useClass: RendererCryptoFunctionService,
useClass: WebCryptoFunctionService,
deps: [WINDOW],
}),
safeProvider({

View File

@@ -1,5 +1,6 @@
import { mock, MockProxy } from "jest-mock-extended";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -13,7 +14,6 @@ import {
} from "@bitwarden/key-management";
import { WindowMain } from "../../main/window.main";
import { MainCryptoFunctionService } from "../../platform/main/main-crypto-function.service";
import { MainBiometricsService } from "./main-biometrics.service";
import OsBiometricsServiceLinux from "./os-biometrics-linux.service";
@@ -35,7 +35,7 @@ describe("MainBiometricsService", function () {
const windowMain = mock<WindowMain>();
const logService = mock<LogService>();
const biometricStateService = mock<BiometricStateService>();
const cryptoFunctionService = mock<MainCryptoFunctionService>();
const cryptoFunctionService = mock<CryptoFunctionService>();
const encryptService = mock<EncryptService>();
it("Should call the platformspecific methods", async () => {

View File

@@ -28,8 +28,9 @@ import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
import { DefaultBiometricStateService } from "@bitwarden/key-management";
/* eslint-enable import/no-restricted-paths */
import { DefaultBiometricStateService } from "@bitwarden/key-management";
import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service";
import { MainSshAgentService } from "./autofill/main/main-ssh-agent.service";
import { DesktopAutofillSettingsService } from "./autofill/services/desktop-autofill-settings.service";
@@ -46,7 +47,6 @@ import { WindowMain } from "./main/window.main";
import { NativeAutofillMain } from "./platform/main/autofill/native-autofill.main";
import { ClipboardMain } from "./platform/main/clipboard.main";
import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener";
import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service";
import { VersionMain } from "./platform/main/version.main";
import { DesktopSettingsService } from "./platform/services/desktop-settings.service";
import { ElectronLogMainService } from "./platform/services/electron-log.main.service";
@@ -68,7 +68,7 @@ export class Main {
desktopCredentialStorageListener: DesktopCredentialStorageListener;
mainBiometricsIpcListener: MainBiometricsIPCListener;
desktopSettingsService: DesktopSettingsService;
mainCryptoFunctionService: MainCryptoFunctionService;
mainCryptoFunctionService: NodeCryptoFunctionService;
migrationRunner: MigrationRunner;
ssoUrlService: SsoUrlService;
@@ -140,8 +140,7 @@ export class Main {
this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);
this.mainCryptoFunctionService = new MainCryptoFunctionService();
this.mainCryptoFunctionService.init();
this.mainCryptoFunctionService = new NodeCryptoFunctionService();
const stateEventRegistrarService = new StateEventRegistrarService(
globalStateProvider,

View File

@@ -1,34 +0,0 @@
import { ipcMain } from "electron";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { crypto } from "@bitwarden/desktop-napi";
import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service";
export class MainCryptoFunctionService
extends NodeCryptoFunctionService
implements CryptoFunctionService
{
init() {
ipcMain.handle(
"crypto.argon2",
async (
event,
opts: {
password: Uint8Array;
salt: Uint8Array;
iterations: number;
memory: number;
parallelism: number;
},
) => {
return await crypto.argon2(
Buffer.from(opts.password),
Buffer.from(opts.salt),
opts.iterations,
opts.memory,
opts.parallelism,
);
},
);
}
}

View File

@@ -98,17 +98,6 @@ const nativeMessaging = {
},
};
const crypto = {
argon2: (
password: Uint8Array,
salt: Uint8Array,
iterations: number,
memory: number,
parallelism: number,
): Promise<Uint8Array> =>
ipcRenderer.invoke("crypto.argon2", { password, salt, iterations, memory, parallelism }),
};
const ephemeralStore = {
setEphemeralValue: (key: string, value: string): Promise<void> =>
ipcRenderer.invoke("setEphemeralValue", { key, value }),

View File

@@ -41,11 +41,6 @@ const common = {
},
type: "asset/resource",
},
{
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
],
},
plugins: [],
@@ -154,13 +149,7 @@ const renderer = {
test: /[\/\\]@angular[\/\\].+\.js$/,
parser: { system: true },
},
{
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
],
noParse: /argon2(-simd)?\.wasm$/,
},
experiments: {
asyncWebAssembly: true,

View File

@@ -90,11 +90,6 @@ const moduleRules = [
test: /\.[jt]sx?$/,
loader: "@ngtools/webpack",
},
{
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
];
const plugins = [
@@ -404,7 +399,6 @@ const webpackConfig = {
clean: true,
},
module: {
noParse: /argon2(-simd)?\.wasm$/,
rules: moduleRules,
},
experiments: {