1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

[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
6 changed files with 72 additions and 8 deletions

View File

@@ -33,7 +33,9 @@ export class WebauthnUtils {
transports: credential.transports, transports: credential.transports,
type: credential.type, type: credential.type,
})), })),
extensions: undefined, // extensions not currently supported extensions: {
credProps: keyOptions.extensions?.credProps,
},
pubKeyCredParams: keyOptions.pubKeyCredParams.map((params) => ({ pubKeyCredParams: keyOptions.pubKeyCredParams.map((params) => ({
alg: params.alg, alg: params.alg,
type: params.type, type: params.type,
@@ -78,7 +80,9 @@ export class WebauthnUtils {
return result.transports; return result.transports;
}, },
} as AuthenticatorAttestationResponse, } as AuthenticatorAttestationResponse,
getClientExtensionResults: () => ({}), getClientExtensionResults: () => ({
credProps: result.extensions.credProps,
}),
} as PublicKeyCredential; } as PublicKeyCredential;
// Modify prototype chains to fix `instanceof` calls. // 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. * This member contains additional parameters requesting additional processing by the client and authenticator.
* Not currently supported.
**/ **/
extensions?: { extensions?: {
appid?: string; appid?: string; // Not supported
appidExclude?: string; appidExclude?: string; // Not supported
uvm?: boolean; // Not supported
credProps?: boolean; credProps?: boolean;
uvm?: boolean;
}; };
/** /**
* This member contains information about the desired properties of the credential to be created. * This member contains information about the desired properties of the credential to be created.
@@ -125,6 +124,11 @@ export interface CreateCredentialResult {
publicKey: string; publicKey: string;
publicKeyAlgorithm: number; publicKeyAlgorithm: number;
transports: string[]; transports: string[];
extensions: {
credProps?: {
rk: boolean;
};
};
} }
/** /**

View File

@@ -362,7 +362,7 @@ describe("FidoAuthenticatorService", () => {
0xd0, 0x5c, 0x3d, 0xc3, 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(counter).toEqual(new Uint8Array([0, 0, 0, 0])); // 0 because of new counter
expect(aaguid).toEqual(AAGUID); expect(aaguid).toEqual(AAGUID);
expect(credentialIdLength).toEqual(new Uint8Array([0, 16])); // 16 bytes because we're using GUIDs expect(credentialIdLength).toEqual(new Uint8Array([0, 16])); // 16 bytes because we're using GUIDs
@@ -697,7 +697,7 @@ describe("FidoAuthenticatorService", () => {
0xd0, 0x5c, 0x3d, 0xc3, 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 expect(counter).toEqual(new Uint8Array([0, 0, 0x23, 0x29])); // 9001 in hex
// Verify signature // Verify signature

View File

@@ -444,6 +444,8 @@ async function generateAuthData(params: AuthDataParams) {
const flags = authDataFlags({ const flags = authDataFlags({
extensionData: false, extensionData: false,
attestationData: params.keyPair != undefined, attestationData: params.keyPair != undefined,
backupEligibility: true,
backupState: true, // Credentials are always synced
userVerification: params.userVerification, userVerification: params.userVerification,
userPresence: params.userPresence, userPresence: params.userPresence,
}); });
@@ -522,6 +524,8 @@ async function generateSignature(params: SignatureParams) {
interface Flags { interface Flags {
extensionData: boolean; extensionData: boolean;
attestationData: boolean; attestationData: boolean;
backupEligibility: boolean;
backupState: boolean;
userVerification: boolean; userVerification: boolean;
userPresence: boolean; userPresence: boolean;
} }
@@ -537,6 +541,14 @@ function authDataFlags(options: Flags): number {
flags |= 0b01000000; flags |= 0b01000000;
} }
if (options.backupEligibility) {
flags |= 0b00001000;
}
if (options.backupState) {
flags |= 0b00010000;
}
if (options.userVerification) { if (options.userVerification) {
flags |= 0b00000100; 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. // 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 () => { it("should throw error if authenticator throws InvalidState", async () => {
const params = createParams(); 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"); 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); clearTimeout(timeout);
return { return {
credentialId: Fido2Utils.bufferToString(makeCredentialResult.credentialId), credentialId: Fido2Utils.bufferToString(makeCredentialResult.credentialId),
@@ -201,6 +208,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
publicKey: Fido2Utils.bufferToString(makeCredentialResult.publicKey), publicKey: Fido2Utils.bufferToString(makeCredentialResult.publicKey),
publicKeyAlgorithm: makeCredentialResult.publicKeyAlgorithm, publicKeyAlgorithm: makeCredentialResult.publicKeyAlgorithm,
transports: params.rp.id === "google.com" ? ["internal", "usb"] : ["internal"], transports: params.rp.id === "google.com" ? ["internal", "usb"] : ["internal"],
extensions: { credProps },
}; };
} }