mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
[EC-598] feat: implement missing credential checks
This commit is contained in:
@@ -537,14 +537,49 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("vault is missing non-discoverable credential", () => {
|
describe("vault is missing non-discoverable credential", () => {
|
||||||
|
let excludedId: string;
|
||||||
|
let params: Fido2AuthenticatorGetAssertionParams;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
excludedId = Utils.newGuid();
|
||||||
|
params = await createParams({
|
||||||
|
allowCredentialDescriptorList: [
|
||||||
|
{ id: Utils.guidToRawFormat(excludedId), type: "public-key" },
|
||||||
|
],
|
||||||
|
rpId: RpId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Spec: If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation. */
|
||||||
|
it("should throw error if no credential exists", async () => {
|
||||||
|
cipherService.getAllDecrypted.mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = async () => await authenticator.getAssertion(params);
|
||||||
|
|
||||||
|
await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotAllowed);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if credential exists but rpId does not match", async () => {
|
||||||
|
const cipher = await createCipher({ type: CipherType.Login }).decrypt();
|
||||||
|
cipher.fido2Key.nonDiscoverableId = excludedId;
|
||||||
|
cipher.fido2Key.rpId = "mismatch-rpid";
|
||||||
|
cipherService.getAllDecrypted.mockResolvedValue([cipher]);
|
||||||
|
|
||||||
|
const result = async () => await authenticator.getAssertion(params);
|
||||||
|
|
||||||
|
await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotAllowed);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("vault is missing discoverable credential", () => {
|
||||||
let params: Fido2AuthenticatorGetAssertionParams;
|
let params: Fido2AuthenticatorGetAssertionParams;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
params = await createParams({
|
params = await createParams({
|
||||||
allowCredentialDescriptorList: [
|
allowCredentialDescriptorList: [],
|
||||||
{ id: Utils.guidToRawFormat(Utils.newGuid()), type: "public-key" },
|
rpId: RpId,
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
cipherService.getAllDecrypted.mockResolvedValue([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Spec: If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation. */
|
/** Spec: If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation. */
|
||||||
|
|||||||
@@ -146,6 +146,24 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Constraint);
|
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let credentialOptions: Fido2KeyView[];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
if (params.allowCredentialDescriptorList?.length > 0) {
|
||||||
|
const ciphers = await this.findNonDiscoverableCredentials(
|
||||||
|
params.allowCredentialDescriptorList,
|
||||||
|
params.rpId
|
||||||
|
);
|
||||||
|
credentialOptions = ciphers.map((c) => c.fido2Key);
|
||||||
|
} else {
|
||||||
|
const ciphers = await this.findDiscoverableCredentials(params.rpId);
|
||||||
|
credentialOptions = ciphers.map((c) => c.fido2Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credentialOptions.length === 0) {
|
||||||
|
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +193,40 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async findNonDiscoverableCredentials(
|
||||||
|
credentials: PublicKeyCredentialDescriptor[],
|
||||||
|
rpId: string
|
||||||
|
): Promise<CipherView[]> {
|
||||||
|
const ids: string[] = [];
|
||||||
|
|
||||||
|
for (const credential of credentials) {
|
||||||
|
try {
|
||||||
|
ids.push(Utils.guidToStandardFormat(credential.id));
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphers = await this.cipherService.getAllDecrypted();
|
||||||
|
return ciphers.filter(
|
||||||
|
(cipher) =>
|
||||||
|
cipher.type === CipherType.Login &&
|
||||||
|
cipher.fido2Key != undefined &&
|
||||||
|
cipher.fido2Key.rpId === rpId &&
|
||||||
|
ids.includes(cipher.fido2Key.nonDiscoverableId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findDiscoverableCredentials(rpId: string): Promise<CipherView[]> {
|
||||||
|
const ciphers = await this.cipherService.getAllDecrypted();
|
||||||
|
return ciphers.filter(
|
||||||
|
(cipher) => cipher.type === CipherType.Fido2Key && cipher.fido2Key.rpId === rpId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async createKeyPair() {
|
private async createKeyPair() {
|
||||||
return await crypto.subtle.generateKey(
|
return await crypto.subtle.generateKey(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user