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

@@ -12,13 +12,6 @@ export abstract class CryptoFunctionService {
algorithm: "sha256" | "sha512",
iterations: number,
): Promise<Uint8Array>;
abstract argon2(
password: string | Uint8Array,
salt: string | Uint8Array,
iterations: number,
memory: number,
parallelism: number,
): Promise<Uint8Array>;
abstract hkdf(
ikm: Uint8Array,
salt: string | Uint8Array,

View File

@@ -1,4 +1,3 @@
import * as argon2 from "argon2-browser";
import * as forge from "node-forge";
import { EncryptionType } from "../../../platform/enums";
@@ -14,7 +13,6 @@ import { CryptoFunctionService } from "../abstractions/crypto-function.service";
export class WebCryptoFunctionService implements CryptoFunctionService {
private crypto: Crypto;
private subtle: SubtleCrypto;
private wasmSupported: boolean;
constructor(globalContext: { crypto: Crypto }) {
if (globalContext?.crypto?.subtle == null) {
@@ -24,7 +22,6 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
}
this.crypto = globalContext.crypto;
this.subtle = this.crypto.subtle;
this.wasmSupported = this.checkIfWasmSupported();
}
async pbkdf2(
@@ -55,33 +52,6 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return new Uint8Array(buffer);
}
async argon2(
password: string | Uint8Array,
salt: string | Uint8Array,
iterations: number,
memory: number,
parallelism: number,
): Promise<Uint8Array> {
if (!this.wasmSupported) {
throw "Webassembly support is required for the Argon2 KDF feature.";
}
const passwordArr = new Uint8Array(this.toBuf(password));
const saltArr = new Uint8Array(this.toBuf(salt));
const result = await argon2.hash({
pass: passwordArr,
salt: saltArr,
time: iterations,
mem: memory,
parallelism: parallelism,
hashLen: 32,
type: argon2.ArgonType.Argon2id,
});
argon2.unloadRuntime();
return result.hash;
}
async hkdf(
ikm: Uint8Array,
salt: string | Uint8Array,
@@ -442,21 +412,4 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
private toWebCryptoAesMode(mode: "cbc" | "ecb"): string {
return mode === "cbc" ? "AES-CBC" : "AES-ECB";
}
// ref: https://stackoverflow.com/a/47880734/1090359
private checkIfWasmSupported(): boolean {
try {
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00),
);
if (module instanceof WebAssembly.Module) {
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}
}
} catch {
return false;
}
return false;
}
}

View File

@@ -6,6 +6,7 @@ import { PBKDF2KdfConfig, Argon2KdfConfig } from "@bitwarden/key-management";
import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service";
import { CsprngArray } from "../../types/csprng";
import { SdkLoadService } from "../abstractions/sdk/sdk-load.service";
import { EncryptionType } from "../enums";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
@@ -77,27 +78,30 @@ describe("KeyGenerationService", () => {
describe("deriveKeyFromPassword", () => {
it("should derive a 32 byte key from a password using pbkdf2", async () => {
const password = "password";
const salt = "salt";
const password = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
const salt = new Uint8Array([9, 10, 11, 12]);
const kdfConfig = new PBKDF2KdfConfig(600_000);
cryptoFunctionService.pbkdf2.mockResolvedValue(new Uint8Array(32));
Object.defineProperty(SdkLoadService, "Ready", {
value: Promise.resolve(),
configurable: true,
});
const key = await sut.deriveKeyFromPassword(password, salt, kdfConfig);
expect(key.inner().type).toEqual(EncryptionType.AesCbc256_B64);
});
it("should derive a 32 byte key from a password using argon2id", async () => {
const password = "password";
const salt = "salt";
const password = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
const salt = new Uint8Array([9, 10, 11, 12]);
const kdfConfig = new Argon2KdfConfig(3, 16, 4);
cryptoFunctionService.hash.mockResolvedValue(new Uint8Array(32));
cryptoFunctionService.argon2.mockResolvedValue(new Uint8Array(32));
Object.defineProperty(SdkLoadService, "Ready", {
value: Promise.resolve(),
configurable: true,
});
const key = await sut.deriveKeyFromPassword(password, salt, kdfConfig);
expect(key.inner().type).toEqual(EncryptionType.AesCbc256_B64);
});
});

View File

@@ -2,11 +2,13 @@
// @ts-strict-ignore
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { KdfConfig, PBKDF2KdfConfig, Argon2KdfConfig, KdfType } from "@bitwarden/key-management";
import { KdfConfig } from "@bitwarden/key-management";
import { PureCrypto } from "@bitwarden/sdk-internal";
import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service";
import { CsprngArray } from "../../types/csprng";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "../abstractions/key-generation.service";
import { SdkLoadService } from "../abstractions/sdk/sdk-load.service";
import { EncryptionType } from "../enums";
import { Utils } from "../misc/utils";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
@@ -47,38 +49,17 @@ export class KeyGenerationService implements KeyGenerationServiceAbstraction {
salt: string | Uint8Array,
kdfConfig: KdfConfig,
): Promise<SymmetricCryptoKey> {
let key: Uint8Array = null;
if (kdfConfig.kdfType == null || kdfConfig.kdfType === KdfType.PBKDF2_SHA256) {
if (kdfConfig.iterations == null) {
kdfConfig.iterations = PBKDF2KdfConfig.ITERATIONS.defaultValue;
}
key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfConfig.iterations);
} else if (kdfConfig.kdfType == KdfType.Argon2id) {
if (kdfConfig.iterations == null) {
kdfConfig.iterations = Argon2KdfConfig.ITERATIONS.defaultValue;
}
if (kdfConfig.memory == null) {
kdfConfig.memory = Argon2KdfConfig.MEMORY.defaultValue;
}
if (kdfConfig.parallelism == null) {
kdfConfig.parallelism = Argon2KdfConfig.PARALLELISM.defaultValue;
}
const saltHash = await this.cryptoFunctionService.hash(salt, "sha256");
key = await this.cryptoFunctionService.argon2(
password,
saltHash,
kdfConfig.iterations,
kdfConfig.memory * 1024, // convert to KiB from MiB
kdfConfig.parallelism,
);
} else {
throw new Error("Unknown Kdf.");
if (typeof password === "string") {
password = new TextEncoder().encode(password);
}
return new SymmetricCryptoKey(key);
if (typeof salt === "string") {
salt = new TextEncoder().encode(salt);
}
await SdkLoadService.Ready;
return new SymmetricCryptoKey(
PureCrypto.derive_kdf_material(password, salt, kdfConfig.toSdkConfig()),
);
}
async stretchKey(key: SymmetricCryptoKey): Promise<SymmetricCryptoKey> {