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:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user