1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-04-01 17:57:27 +02:00

[EC-598] feat: fully working register and assert flow

This commit is contained in:
Andreas Coroiu 2022-12-16 14:12:47 +01:00
parent 91daba5991
commit 98f11362ce
No known key found for this signature in database
GPG Key ID: E70B5FFC81DFEC1A
7 changed files with 155 additions and 3 deletions

View File

@ -210,6 +210,8 @@ export default class RuntimeBackground {
break;
case "fido2RegisterCredentialRequest":
return await this.main.fido2Service.createCredential(msg.data);
case "fido2GetCredentialRequest":
return await this.main.fido2Service.assertCredential(msg.data);
}
return undefined;
}

View File

@ -1,5 +1,7 @@
import { Fido2Utils } from "@bitwarden/common/abstractions/fido2/fido2-utils";
import {
CredentialAssertParams,
CredentialAssertResult,
CredentialRegistrationParams,
CredentialRegistrationResult,
} from "@bitwarden/common/abstractions/fido2/fido2.service.abstraction";
@ -62,4 +64,38 @@ export class WebauthnUtils {
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: () => ({}),
};
}
}

View File

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

View File

@ -1,4 +1,6 @@
import {
CredentialAssertParams,
CredentialAssertResult,
CredentialRegistrationParams,
CredentialRegistrationResult,
} from "@bitwarden/common/abstractions/fido2/fido2.service.abstraction";
@ -25,10 +27,12 @@ export type CredentialCreationResponse = {
export type CredentialGetRequest = {
type: MessageType.CredentialGetRequest;
data: CredentialAssertParams;
};
export type CredentialGetResponse = {
type: MessageType.CredentialGetResponse;
result?: CredentialAssertResult;
};
export type AbortRequest = {

View File

@ -27,5 +27,14 @@ navigator.credentials.create = async (options?: CredentialCreationOptions): Prom
};
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);
};

View File

@ -39,7 +39,22 @@ export interface CredentialRegistrationResult {
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 {
createCredential: (params: CredentialRegistrationParams) => Promise<CredentialRegistrationResult>;
assertCredential: () => unknown;
assertCredential: (params: CredentialAssertParams) => Promise<CredentialAssertResult>;
}

View File

@ -3,6 +3,8 @@ import { CBOR } from "cbor-redux";
import { Fido2UserInterfaceService } from "../../abstractions/fido2/fido2-user-interface.service.abstraction";
import { Fido2Utils } from "../../abstractions/fido2/fido2-utils";
import {
CredentialAssertParams,
CredentialAssertResult,
CredentialRegistrationParams,
CredentialRegistrationResult,
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;
}
}