[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
This commit is contained in:
Andreas Coroiu 2024-02-16 10:55:51 +01:00 committed by GitHub
parent 111c102018
commit b0dd64bab4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 72 additions and 8 deletions

View File

@ -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.

View File

@ -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;
};
};
}
/**

View File

@ -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

View File

@ -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;
}

View File

@ -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();

View File

@ -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 },
};
}