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:
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user