From b0dd64bab43747c864fead74abe1451b621dc9da Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 16 Feb 2024 10:55:51 +0100 Subject: [PATCH] [PM-4756] [PM-4755] Add BE and BS flags, and credProps (#7947) * [PM-4756] feat: set BE and BS flags * [PM-4755] feat: add support for credProps.rk * [PM-4755] feat: add extension support to page-script object mapping --- .../browser/src/vault/fido2/webauthn-utils.ts | 8 +++-- .../fido2/fido2-client.service.abstraction.ts | 12 ++++--- .../fido2/fido2-authenticator.service.spec.ts | 4 +-- .../fido2/fido2-authenticator.service.ts | 12 +++++++ .../fido2/fido2-client.service.spec.ts | 36 +++++++++++++++++++ .../services/fido2/fido2-client.service.ts | 8 +++++ 6 files changed, 72 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/vault/fido2/webauthn-utils.ts b/apps/browser/src/vault/fido2/webauthn-utils.ts index ec23303a6eb..59aa2154e3d 100644 --- a/apps/browser/src/vault/fido2/webauthn-utils.ts +++ b/apps/browser/src/vault/fido2/webauthn-utils.ts @@ -33,7 +33,9 @@ export class WebauthnUtils { transports: credential.transports, type: credential.type, })), - extensions: undefined, // extensions not currently supported + extensions: { + credProps: keyOptions.extensions?.credProps, + }, pubKeyCredParams: keyOptions.pubKeyCredParams.map((params) => ({ alg: params.alg, type: params.type, @@ -78,7 +80,9 @@ export class WebauthnUtils { return result.transports; }, } as AuthenticatorAttestationResponse, - getClientExtensionResults: () => ({}), + getClientExtensionResults: () => ({ + credProps: result.extensions.credProps, + }), } as PublicKeyCredential; // Modify prototype chains to fix `instanceof` calls. diff --git a/libs/common/src/vault/abstractions/fido2/fido2-client.service.abstraction.ts b/libs/common/src/vault/abstractions/fido2/fido2-client.service.abstraction.ts index c459c60d07c..8e2a1538308 100644 --- a/libs/common/src/vault/abstractions/fido2/fido2-client.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/fido2/fido2-client.service.abstraction.ts @@ -80,13 +80,12 @@ export interface CreateCredentialParams { }[]; /** * This member contains additional parameters requesting additional processing by the client and authenticator. - * Not currently supported. **/ extensions?: { - appid?: string; - appidExclude?: string; + appid?: string; // Not supported + appidExclude?: string; // Not supported + uvm?: boolean; // Not supported credProps?: boolean; - uvm?: boolean; }; /** * This member contains information about the desired properties of the credential to be created. @@ -125,6 +124,11 @@ export interface CreateCredentialResult { publicKey: string; publicKeyAlgorithm: number; transports: string[]; + extensions: { + credProps?: { + rk: boolean; + }; + }; } /** diff --git a/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts index 6daa1c54fd2..c5cf8275610 100644 --- a/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts @@ -362,7 +362,7 @@ describe("FidoAuthenticatorService", () => { 0xd0, 0x5c, 0x3d, 0xc3, ]), ); - expect(flags).toEqual(new Uint8Array([0b01000001])); // UP = true, AD = true + expect(flags).toEqual(new Uint8Array([0b01011001])); // UP = true, AT = true, BE = true, BS = true expect(counter).toEqual(new Uint8Array([0, 0, 0, 0])); // 0 because of new counter expect(aaguid).toEqual(AAGUID); expect(credentialIdLength).toEqual(new Uint8Array([0, 16])); // 16 bytes because we're using GUIDs @@ -697,7 +697,7 @@ describe("FidoAuthenticatorService", () => { 0xd0, 0x5c, 0x3d, 0xc3, ]), ); - expect(flags).toEqual(new Uint8Array([0b00000001])); // UP = true + expect(flags).toEqual(new Uint8Array([0b00011001])); // UP = true, BE = true, BS = true expect(counter).toEqual(new Uint8Array([0, 0, 0x23, 0x29])); // 9001 in hex // Verify signature diff --git a/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts b/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts index 4b4d93a9495..f3112eb3508 100644 --- a/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts @@ -444,6 +444,8 @@ async function generateAuthData(params: AuthDataParams) { const flags = authDataFlags({ extensionData: false, attestationData: params.keyPair != undefined, + backupEligibility: true, + backupState: true, // Credentials are always synced userVerification: params.userVerification, userPresence: params.userPresence, }); @@ -522,6 +524,8 @@ async function generateSignature(params: SignatureParams) { interface Flags { extensionData: boolean; attestationData: boolean; + backupEligibility: boolean; + backupState: boolean; userVerification: boolean; userPresence: boolean; } @@ -537,6 +541,14 @@ function authDataFlags(options: Flags): number { flags |= 0b01000000; } + if (options.backupEligibility) { + flags |= 0b00001000; + } + + if (options.backupState) { + flags |= 0b00010000; + } + if (options.userVerification) { flags |= 0b00000100; } diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts index ad812e309f4..46d5e2d9c80 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts @@ -207,6 +207,42 @@ describe("FidoAuthenticatorService", () => { ); }); + it("should return credProps.rk = true when creating a discoverable credential", async () => { + const params = createParams({ + authenticatorSelection: { residentKey: "required", userVerification: "required" }, + extensions: { credProps: true }, + }); + authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); + + const result = await client.createCredential(params, tab); + + expect(result.extensions.credProps?.rk).toBe(true); + }); + + it("should return credProps.rk = false when creating a non-discoverable credential", async () => { + const params = createParams({ + authenticatorSelection: { residentKey: "discouraged", userVerification: "required" }, + extensions: { credProps: true }, + }); + authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); + + const result = await client.createCredential(params, tab); + + expect(result.extensions.credProps?.rk).toBe(false); + }); + + it("should return credProps = undefiend when the extension is not requested", async () => { + const params = createParams({ + authenticatorSelection: { residentKey: "required", userVerification: "required" }, + extensions: {}, + }); + authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); + + const result = await client.createCredential(params, tab); + + expect(result.extensions.credProps).toBeUndefined(); + }); + // Spec: If any authenticator returns an error status equivalent to "InvalidStateError", Return a DOMException whose name is "InvalidStateError" and terminate this algorithm. it("should throw error if authenticator throws InvalidState", async () => { const params = createParams(); diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.ts b/libs/common/src/vault/services/fido2/fido2-client.service.ts index 05243f05231..764973b9562 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.ts @@ -192,6 +192,13 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { throw new DOMException("The operation either timed out or was not allowed.", "AbortError"); } + let credProps; + if (params.extensions?.credProps) { + credProps = { + rk: makeCredentialParams.requireResidentKey, + }; + } + clearTimeout(timeout); return { credentialId: Fido2Utils.bufferToString(makeCredentialResult.credentialId), @@ -201,6 +208,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { publicKey: Fido2Utils.bufferToString(makeCredentialResult.publicKey), publicKeyAlgorithm: makeCredentialResult.publicKeyAlgorithm, transports: params.rp.id === "google.com" ? ["internal", "usb"] : ["internal"], + extensions: { credProps }, }; }