From c2ec87a3f33403224d4e60a5f63c9a67ab1b524c Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 29 Mar 2023 10:37:02 +0200 Subject: [PATCH] [EC-598] feat: implement missing credential checks --- .../fido2-authenticator.service.spec.ts | 41 +++++++++++++-- .../services/fido2-authenticator.service.ts | 52 +++++++++++++++++++ 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts b/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts index f14668efe8b..20b032ec931 100644 --- a/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts +++ b/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts @@ -537,14 +537,49 @@ describe("FidoAuthenticatorService", () => { }); 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; beforeEach(async () => { params = await createParams({ - allowCredentialDescriptorList: [ - { id: Utils.guidToRawFormat(Utils.newGuid()), type: "public-key" }, - ], + allowCredentialDescriptorList: [], + rpId: RpId, }); + cipherService.getAllDecrypted.mockResolvedValue([]); }); /** Spec: If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation. */ diff --git a/libs/common/src/webauthn/services/fido2-authenticator.service.ts b/libs/common/src/webauthn/services/fido2-authenticator.service.ts index f1a72885237..1fbda09cd2a 100644 --- a/libs/common/src/webauthn/services/fido2-authenticator.service.ts +++ b/libs/common/src/webauthn/services/fido2-authenticator.service.ts @@ -146,6 +146,24 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr 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"); } @@ -175,6 +193,40 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr ); } + private async findNonDiscoverableCredentials( + credentials: PublicKeyCredentialDescriptor[], + rpId: string + ): Promise { + 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 { + const ciphers = await this.cipherService.getAllDecrypted(); + return ciphers.filter( + (cipher) => cipher.type === CipherType.Fido2Key && cipher.fido2Key.rpId === rpId + ); + } + private async createKeyPair() { return await crypto.subtle.generateKey( {