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 ca2d5d5be0..6652af6571 100644 --- a/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts +++ b/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts @@ -588,7 +588,7 @@ describe("FidoAuthenticatorService", () => { }); }); - describe("assertion of non-discoverable credential", () => { + describe("vault contains credential", () => { let credentialIds: string[]; let ciphers: CipherView[]; let params: Fido2AuthenticatorGetAssertionParams; @@ -612,10 +612,66 @@ describe("FidoAuthenticatorService", () => { /** Spec: Prompt the user to select a public key credential source selectedCredential from credentialOptions. */ it("should request confirmation from the user", async () => { + userInterface.pickCredential.mockResolvedValue(ciphers[0].id); + await authenticator.getAssertion(params); expect(userInterface.pickCredential).toHaveBeenCalledWith(ciphers.map((c) => c.id)); }); + + /** Spec: If the user does not consent, return an error code equivalent to "NotAllowedError" and terminate the operation. */ + it("should throw error", async () => { + userInterface.pickCredential.mockResolvedValue(undefined); + + const result = async () => await authenticator.getAssertion(params); + + await expect(result).rejects.toThrowError(Fido2AutenticatorErrorCode.NotAllowed); + }); + }); + + describe("assertion of non-discoverable credential", () => { + let credentialIds: string[]; + let ciphers: CipherView[]; + let params: Fido2AuthenticatorGetAssertionParams; + + beforeEach(async () => { + credentialIds = [Utils.newGuid(), Utils.newGuid()]; + ciphers = await Promise.all( + credentialIds.map((id) => + createCipherView( + { type: CipherType.Login }, + { nonDiscoverableId: id, rpId: RpId, counter: 9000 } + ) + ) + ); + params = await createParams({ + allowCredentialDescriptorList: credentialIds.map((credentialId) => ({ + id: Utils.guidToRawFormat(credentialId), + type: "public-key", + })), + rpId: RpId, + }); + cipherService.getAllDecrypted.mockResolvedValue(ciphers); + userInterface.pickCredential.mockResolvedValue(ciphers[0].id); + }); + + /** Spec: Increment the credential associated signature counter */ + it("should increment counter", async () => { + const encrypted = Symbol(); + cipherService.encrypt.mockResolvedValue(encrypted as any); + + await authenticator.getAssertion(params); + + expect(cipherService.encrypt).toHaveBeenCalledWith( + expect.objectContaining({ + id: ciphers[0].id, + fido2Key: expect.objectContaining({ + counter: 9001, + }), + }) + ); + expect(cipherService.updateWithServer).toHaveBeenCalledWith(encrypted); + }); }); async function createParams( @@ -651,10 +707,12 @@ function createCipherView( const cipher = new CipherView(); cipher.id = data.id ?? Utils.newGuid(); cipher.type = data.type ?? CipherType.Fido2Key; + cipher.localData = {}; cipher.login = data.type ?? data.type === CipherType.Login ? new LoginView() : null; cipher.fido2Key = new Fido2KeyView(); cipher.fido2Key.nonDiscoverableId = fido2Key.nonDiscoverableId; - cipher.fido2Key.rpId = fido2Key.rpId; + cipher.fido2Key.rpId = fido2Key.rpId ?? RpId; + cipher.fido2Key.counter = fido2Key.counter ?? 0; return cipher; } diff --git a/libs/common/src/webauthn/services/fido2-authenticator.service.ts b/libs/common/src/webauthn/services/fido2-authenticator.service.ts index e6786c4b28..637f503dff 100644 --- a/libs/common/src/webauthn/services/fido2-authenticator.service.ts +++ b/libs/common/src/webauthn/services/fido2-authenticator.service.ts @@ -162,12 +162,19 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const selectedCredential = await this.userInterface.pickCredential( + const selectedCredentialId = await this.userInterface.pickCredential( credentialOptions.map((cipher) => cipher.id) ); + const selectedCredential = credentialOptions.find((c) => c.id === selectedCredentialId); - return null; + if (selectedCredential === undefined) { + throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed); + } + + ++selectedCredential.fido2Key.counter; + selectedCredential.localData.lastUsedDate = new Date().getTime(); + const encrypted = await this.cipherService.encrypt(selectedCredential); + await this.cipherService.updateWithServer(encrypted); } private async vaultContainsCredentials(