mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 08:43:33 +00:00
[EC-598] feat: fully working register and assert flow
This commit is contained in:
@@ -210,6 +210,8 @@ export default class RuntimeBackground {
|
|||||||
break;
|
break;
|
||||||
case "fido2RegisterCredentialRequest":
|
case "fido2RegisterCredentialRequest":
|
||||||
return await this.main.fido2Service.createCredential(msg.data);
|
return await this.main.fido2Service.createCredential(msg.data);
|
||||||
|
case "fido2GetCredentialRequest":
|
||||||
|
return await this.main.fido2Service.assertCredential(msg.data);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Fido2Utils } from "@bitwarden/common/abstractions/fido2/fido2-utils";
|
import { Fido2Utils } from "@bitwarden/common/abstractions/fido2/fido2-utils";
|
||||||
import {
|
import {
|
||||||
|
CredentialAssertParams,
|
||||||
|
CredentialAssertResult,
|
||||||
CredentialRegistrationParams,
|
CredentialRegistrationParams,
|
||||||
CredentialRegistrationResult,
|
CredentialRegistrationResult,
|
||||||
} from "@bitwarden/common/abstractions/fido2/fido2.service.abstraction";
|
} from "@bitwarden/common/abstractions/fido2/fido2.service.abstraction";
|
||||||
@@ -62,4 +64,38 @@ export class WebauthnUtils {
|
|||||||
getClientExtensionResults: () => ({}),
|
getClientExtensionResults: () => ({}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static mapCredentialRequestOptions(
|
||||||
|
options: CredentialRequestOptions,
|
||||||
|
origin: string
|
||||||
|
): CredentialAssertParams {
|
||||||
|
const keyOptions = options.publicKey;
|
||||||
|
|
||||||
|
if (keyOptions == undefined) {
|
||||||
|
throw new Error("Public-key options not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
origin,
|
||||||
|
allowedCredentialIds:
|
||||||
|
keyOptions.allowCredentials?.map((c) => Fido2Utils.bufferToString(c.id)) ?? [],
|
||||||
|
challenge: Fido2Utils.bufferToString(keyOptions.challenge),
|
||||||
|
rpId: keyOptions.rpId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static mapCredentialAssertResult(result: CredentialAssertResult): PublicKeyCredential {
|
||||||
|
return {
|
||||||
|
id: result.credentialId,
|
||||||
|
rawId: Fido2Utils.stringToBuffer(result.credentialId),
|
||||||
|
type: "public-key",
|
||||||
|
response: {
|
||||||
|
authenticatorData: Fido2Utils.stringToBuffer(result.authenticatorData),
|
||||||
|
clientDataJSON: Fido2Utils.stringToBuffer(result.clientDataJSON),
|
||||||
|
signature: Fido2Utils.stringToBuffer(result.signature),
|
||||||
|
userHandle: Fido2Utils.stringToBuffer(result.userHandle),
|
||||||
|
} as AuthenticatorAssertionResponse,
|
||||||
|
getClientExtensionResults: () => ({}),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,5 +29,22 @@ messenger.addHandler(async (message) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.type === MessageType.CredentialGetRequest) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
chrome.runtime.sendMessage(
|
||||||
|
{
|
||||||
|
command: "fido2GetCredentialRequest",
|
||||||
|
data: message.data,
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
resolve({
|
||||||
|
type: MessageType.CredentialGetResponse,
|
||||||
|
result: response,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
CredentialAssertParams,
|
||||||
|
CredentialAssertResult,
|
||||||
CredentialRegistrationParams,
|
CredentialRegistrationParams,
|
||||||
CredentialRegistrationResult,
|
CredentialRegistrationResult,
|
||||||
} from "@bitwarden/common/abstractions/fido2/fido2.service.abstraction";
|
} from "@bitwarden/common/abstractions/fido2/fido2.service.abstraction";
|
||||||
@@ -25,10 +27,12 @@ export type CredentialCreationResponse = {
|
|||||||
|
|
||||||
export type CredentialGetRequest = {
|
export type CredentialGetRequest = {
|
||||||
type: MessageType.CredentialGetRequest;
|
type: MessageType.CredentialGetRequest;
|
||||||
|
data: CredentialAssertParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CredentialGetResponse = {
|
export type CredentialGetResponse = {
|
||||||
type: MessageType.CredentialGetResponse;
|
type: MessageType.CredentialGetResponse;
|
||||||
|
result?: CredentialAssertResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AbortRequest = {
|
export type AbortRequest = {
|
||||||
|
|||||||
@@ -27,5 +27,14 @@ navigator.credentials.create = async (options?: CredentialCreationOptions): Prom
|
|||||||
};
|
};
|
||||||
|
|
||||||
navigator.credentials.get = async (options?: CredentialRequestOptions): Promise<Credential> => {
|
navigator.credentials.get = async (options?: CredentialRequestOptions): Promise<Credential> => {
|
||||||
return await browserCredentials.get(options);
|
const response = await messenger.request({
|
||||||
|
type: MessageType.CredentialGetRequest,
|
||||||
|
data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.type !== MessageType.CredentialGetResponse) {
|
||||||
|
return await browserCredentials.get(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebauthnUtils.mapCredentialAssertResult(response.result);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,22 @@ export interface CredentialRegistrationResult {
|
|||||||
attestationObject: string;
|
attestationObject: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CredentialAssertParams {
|
||||||
|
allowedCredentialIds: string[];
|
||||||
|
rpId: string;
|
||||||
|
origin: string;
|
||||||
|
challenge: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CredentialAssertResult {
|
||||||
|
credentialId: string;
|
||||||
|
clientDataJSON: string;
|
||||||
|
authenticatorData: string;
|
||||||
|
signature: string;
|
||||||
|
userHandle: string;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class Fido2Service {
|
export abstract class Fido2Service {
|
||||||
createCredential: (params: CredentialRegistrationParams) => Promise<CredentialRegistrationResult>;
|
createCredential: (params: CredentialRegistrationParams) => Promise<CredentialRegistrationResult>;
|
||||||
assertCredential: () => unknown;
|
assertCredential: (params: CredentialAssertParams) => Promise<CredentialAssertResult>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { CBOR } from "cbor-redux";
|
|||||||
import { Fido2UserInterfaceService } from "../../abstractions/fido2/fido2-user-interface.service.abstraction";
|
import { Fido2UserInterfaceService } from "../../abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||||
import { Fido2Utils } from "../../abstractions/fido2/fido2-utils";
|
import { Fido2Utils } from "../../abstractions/fido2/fido2-utils";
|
||||||
import {
|
import {
|
||||||
|
CredentialAssertParams,
|
||||||
|
CredentialAssertResult,
|
||||||
CredentialRegistrationParams,
|
CredentialRegistrationParams,
|
||||||
CredentialRegistrationResult,
|
CredentialRegistrationResult,
|
||||||
Fido2Service as Fido2ServiceAbstraction,
|
Fido2Service as Fido2ServiceAbstraction,
|
||||||
@@ -102,7 +104,74 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
assertCredential(): unknown {
|
async assertCredential(params: CredentialAssertParams): Promise<CredentialAssertResult> {
|
||||||
|
let credential: BitCredential | undefined;
|
||||||
|
|
||||||
|
if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
|
||||||
|
// We're looking for regular non-resident keys
|
||||||
|
credential = this.getCredential(params.allowedCredentialIds);
|
||||||
|
} else {
|
||||||
|
// We're looking for a resident key
|
||||||
|
credential = this.getCredentialByRp(params.rpId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credential === undefined) {
|
||||||
|
throw new Error("No valid credentials found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credential.origin !== params.origin) {
|
||||||
|
throw new Error("Not allowed: Origin mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const clientData = encoder.encode(
|
||||||
|
JSON.stringify({
|
||||||
|
type: "webauthn.get",
|
||||||
|
challenge: params.challenge,
|
||||||
|
origin: params.origin,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const authData = await generateAuthData({
|
||||||
|
credentialId: credential.credentialId,
|
||||||
|
rpId: params.rpId,
|
||||||
|
userPresence: true,
|
||||||
|
userVerification: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signature = await generateSignature({
|
||||||
|
authData,
|
||||||
|
clientData,
|
||||||
|
keyPair: credential.keyPair,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
credentialId: credential.credentialId.encoded,
|
||||||
|
clientDataJSON: Fido2Utils.bufferToString(clientData),
|
||||||
|
authenticatorData: Fido2Utils.bufferToString(authData),
|
||||||
|
signature: Fido2Utils.bufferToString(signature),
|
||||||
|
userHandle: Fido2Utils.bufferToString(credential.userHandle),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCredential(allowedCredentialIds: string[]): BitCredential | undefined {
|
||||||
|
let credential: BitCredential | undefined;
|
||||||
|
for (const allowedCredential of allowedCredentialIds) {
|
||||||
|
const id = new CredentialId(allowedCredential);
|
||||||
|
if (this.credentials.has(id.encoded)) {
|
||||||
|
credential = this.credentials.get(id.encoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCredentialByRp(rpId: string): BitCredential | undefined {
|
||||||
|
for (const credential of this.credentials.values()) {
|
||||||
|
if (credential.rpId === rpId) {
|
||||||
|
return credential;
|
||||||
|
}
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user