mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-23627] Require publicKey for keyService getFingerprint (#15933)
* require public key on keyService getFingerprint * Update consumers and add error handling & logging
This commit is contained in:
@@ -318,14 +318,14 @@ export abstract class KeyService {
|
||||
): Observable<{ privateKey: UserPrivateKey; publicKey: UserPublicKey } | null>;
|
||||
|
||||
/**
|
||||
* Generates a fingerprint phrase for the user based on their public key
|
||||
* Generates a fingerprint phrase for the public key provided.
|
||||
*
|
||||
* @throws Error when publicKey is null and there is no active user, or the active user does not have a public key
|
||||
* @throws Error when publicKey is null or undefined.
|
||||
* @param fingerprintMaterial Fingerprint material
|
||||
* @param publicKey The user's public key
|
||||
* @returns The user's fingerprint phrase
|
||||
* @param publicKey The public key to generate the fingerprint phrase for.
|
||||
* @returns The fingerprint phrase
|
||||
*/
|
||||
abstract getFingerprint(fingerprintMaterial: string, publicKey?: Uint8Array): Promise<string[]>;
|
||||
abstract getFingerprint(fingerprintMaterial: string, publicKey: Uint8Array): Promise<string[]>;
|
||||
/**
|
||||
* Generates a new keypair
|
||||
* @param key A key to encrypt the private key with. If not provided,
|
||||
|
||||
@@ -1227,6 +1227,63 @@ describe("keyService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFingerprint", () => {
|
||||
const mockFingerprintMaterial = "test@example.com";
|
||||
const mockPublicKey = new Uint8Array(256);
|
||||
const mockKeyFingerprint = Utils.fromB64ToArray("nfG2jTrJilBEsSrg7ffe9exE9PlClem4P2bxlQ6rNbs=");
|
||||
const mockUserFingerprint = Utils.fromB64ToArray(
|
||||
"V5AQSk83YXd6kZqCncC6d9J72R7UZ60Xl1eIoDoWgTc=",
|
||||
);
|
||||
const expectedFingerprint = ["predefine", "hunting", "pastime", "enrich", "unhearing"];
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoFunctionService.hash.mockResolvedValue(mockKeyFingerprint);
|
||||
cryptoFunctionService.hkdfExpand.mockResolvedValue(mockUserFingerprint);
|
||||
});
|
||||
|
||||
test.each([null as unknown as Uint8Array, undefined as unknown as Uint8Array])(
|
||||
"throws when publicKey is %s",
|
||||
async (publicKey) => {
|
||||
await expect(keyService.getFingerprint(mockFingerprintMaterial, publicKey)).rejects.toThrow(
|
||||
"Public key is required to generate a fingerprint.",
|
||||
);
|
||||
expect(cryptoFunctionService.hash).not.toHaveBeenCalled();
|
||||
expect(cryptoFunctionService.hkdfExpand).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("generates fingerprint successfully", async () => {
|
||||
const result = await keyService.getFingerprint(mockFingerprintMaterial, mockPublicKey);
|
||||
|
||||
expect(result).toEqual(expectedFingerprint);
|
||||
expect(cryptoFunctionService.hash).toHaveBeenCalledWith(mockPublicKey, "sha256");
|
||||
expect(cryptoFunctionService.hkdfExpand).toHaveBeenCalledWith(
|
||||
mockKeyFingerprint,
|
||||
mockFingerprintMaterial,
|
||||
32,
|
||||
"sha256",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws when entropy of hash function is too small", async () => {
|
||||
const keyFingerprint = new Uint8Array(3);
|
||||
cryptoFunctionService.hash.mockResolvedValue(keyFingerprint);
|
||||
cryptoFunctionService.hkdfExpand.mockResolvedValue(new Uint8Array(3));
|
||||
|
||||
await expect(
|
||||
keyService.getFingerprint(mockFingerprintMaterial, mockPublicKey),
|
||||
).rejects.toThrow("Output entropy of hash function is too small");
|
||||
|
||||
expect(cryptoFunctionService.hash).toHaveBeenCalledWith(mockPublicKey, "sha256");
|
||||
expect(cryptoFunctionService.hkdfExpand).toHaveBeenCalledWith(
|
||||
keyFingerprint,
|
||||
mockFingerprintMaterial,
|
||||
32,
|
||||
"sha256",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("makeKeyPair", () => {
|
||||
test.each([null as unknown as SymmetricCryptoKey, undefined as unknown as SymmetricCryptoKey])(
|
||||
"throws when the provided key is %s",
|
||||
|
||||
@@ -473,19 +473,11 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
.update(() => encPrivateKey);
|
||||
}
|
||||
|
||||
// TODO: Make public key required
|
||||
async getFingerprint(fingerprintMaterial: string, publicKey?: Uint8Array): Promise<string[]> {
|
||||
async getFingerprint(fingerprintMaterial: string, publicKey: Uint8Array): Promise<string[]> {
|
||||
if (publicKey == null) {
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (activeUserId == null) {
|
||||
throw new Error("No active user found.");
|
||||
}
|
||||
publicKey = (await firstValueFrom(this.userPublicKey$(activeUserId))) as Uint8Array;
|
||||
throw new Error("Public key is required to generate a fingerprint.");
|
||||
}
|
||||
|
||||
if (publicKey === null) {
|
||||
throw new Error("No public key available.");
|
||||
}
|
||||
const keyFingerprint = await this.cryptoFunctionService.hash(publicKey, "sha256");
|
||||
const userFingerprint = await this.cryptoFunctionService.hkdfExpand(
|
||||
keyFingerprint,
|
||||
|
||||
Reference in New Issue
Block a user