1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-02 09:43:29 +00:00

Fix sign in

This commit is contained in:
Bernd Schoolmann
2025-11-17 15:13:08 +01:00
parent 452ef0d774
commit 791397d60d
8 changed files with 86 additions and 24 deletions

View File

@@ -1,5 +1,6 @@
import { ipcRenderer } from "electron";
import { PublicKeyCredential } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
import { navigator_credentials } from "@bitwarden/desktop-napi";
export default {
@@ -11,8 +12,7 @@ export default {
}),
navigatorCredentialsGet: (
options: navigator_credentials.PublicKeyCredentialRequestOptions,
): Promise<navigator_credentials.PublicKeyCredential | null> =>
ipcRenderer.invoke("navigatorCredentials.get", options),
): Promise<PublicKeyCredential | null> => ipcRenderer.invoke("navigatorCredentials.get", options),
navigatorCredentialsAvailable: (): Promise<boolean> =>
ipcRenderer.invoke("navigatorCredentials.available"),
};

View File

@@ -7,7 +7,20 @@ export class MainNavigatorCredentialsService {
ipcMain.handle(
"navigatorCredentials.get",
async (_event: any, message: navigator_credentials.PublicKeyCredentialRequestOptions) => {
return navigator_credentials.get(message);
const result = navigator_credentials.get(message);
return {
authenticatorAttachment: result.authenticatorAttachment,
id: result.id,
rawId: result.rawId,
response: {
clientDataJSON: result.response.clientDataJson,
authenticatorData: result.response.authenticatorData,
signature: result.response.signature,
userHandle: result.response.userHandle,
},
type: result.type,
prf: result.prf,
};
},
);
ipcMain.handle("navigatorCredentials.available", async (_event: any, _message: any) => {

View File

@@ -1,11 +1,14 @@
import { navigator_credentials } from "apps/desktop/desktop_native/napi";
import { NavigatorCredentialsService } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
import {
NavigatorCredentialsService,
PublicKeyCredential,
} from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
export class RendererNavigatorCredentialsService implements NavigatorCredentialsService {
constructor() {}
async get(options: CredentialRequestOptions): Promise<Credential | null> {
async get(options: CredentialRequestOptions): Promise<PublicKeyCredential | null> {
return await ipc.auth.navigatorCredentialsGet({
challenge: arrayBufferSourceToUint8Array(options.publicKey.challenge),
timeout: options.publicKey.timeout,

View File

@@ -1,4 +1,20 @@
export type AuthenticatorAssertionResponse = {
clientDataJSON: Uint8Array;
authenticatorData: Uint8Array;
signature: Uint8Array;
userHandle: Uint8Array | null;
};
export type PublicKeyCredential = {
authenticatorAttachment: string;
id: string;
rawId: Uint8Array;
response: AuthenticatorAssertionResponse;
type: string;
prf?: Uint8Array;
};
export abstract class NavigatorCredentialsService {
abstract get(options: CredentialRequestOptions): Promise<Credential | null>;
abstract get(options: CredentialRequestOptions): Promise<PublicKeyCredential | null>;
abstract available(): Promise<boolean>;
}

View File

@@ -1,7 +1,10 @@
import { ClientType } from "@bitwarden/client-type";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { NavigatorCredentialsService } from "../../abstractions/webauthn/navigator-credentials.service";
import {
NavigatorCredentialsService,
PublicKeyCredential as CustomPublicKeyCredential,
} from "../../abstractions/webauthn/navigator-credentials.service";
export class DefaultNavigatorCredentialsService implements NavigatorCredentialsService {
private navigatorCredentials: CredentialsContainer;
@@ -13,11 +16,36 @@ export class DefaultNavigatorCredentialsService implements NavigatorCredentialsS
this.navigatorCredentials = this.window.navigator.credentials;
}
async get(options: CredentialRequestOptions): Promise<Credential | null> {
return await this.navigatorCredentials.get(options);
async get(options: CredentialRequestOptions): Promise<CustomPublicKeyCredential | null> {
const result: PublicKeyCredential = (await this.navigatorCredentials.get(
options,
)) as PublicKeyCredential;
const response: AuthenticatorAssertionResponse =
result.response as AuthenticatorAssertionResponse;
return {
authenticatorAttachment: result.authenticatorAttachment,
id: result.id,
rawId: new Uint8Array(result.rawId),
response: {
clientDataJSON: new Uint8Array(response.clientDataJSON),
authenticatorData: new Uint8Array(response.authenticatorData),
signature: new Uint8Array(response.signature),
userHandle: response.userHandle ? new Uint8Array(response.userHandle) : null,
},
type: result.type,
prf: bufferSourceToUint8Array(result.getClientExtensionResults().prf?.results?.first),
};
}
async available(): Promise<boolean> {
return this.platformUtilsService.getClientType() === ClientType.Web;
}
}
function bufferSourceToUint8Array(source: BufferSource): Uint8Array {
if (source instanceof ArrayBuffer) {
return new Uint8Array(source);
} else {
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
}
}

View File

@@ -2,6 +2,8 @@
// @ts-strict-ignore
import { Jsonify } from "type-fest";
import { PublicKeyCredential } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
import { Utils } from "../../../../platform/misc/utils";
import { WebAuthnLoginResponseRequest } from "./webauthn-login-response.request";
@@ -20,15 +22,15 @@ export class WebAuthnLoginAssertionResponseRequest extends WebAuthnLoginResponse
constructor(credential: PublicKeyCredential) {
super(credential);
if (!(credential.response instanceof AuthenticatorAssertionResponse)) {
throw new Error("Invalid authenticator response");
}
this.response = {
authenticatorData: Utils.fromBufferToUrlB64(credential.response.authenticatorData),
signature: Utils.fromBufferToUrlB64(credential.response.signature),
clientDataJSON: Utils.fromBufferToUrlB64(credential.response.clientDataJSON),
userHandle: Utils.fromBufferToUrlB64(credential.response.userHandle),
authenticatorData: Utils.fromBufferToUrlB64(
credential.response.authenticatorData.buffer as ArrayBuffer,
),
signature: Utils.fromBufferToUrlB64(credential.response.signature.buffer as ArrayBuffer),
clientDataJSON: Utils.fromBufferToUrlB64(
credential.response.clientDataJSON.buffer as ArrayBuffer,
),
userHandle: Utils.fromBufferToUrlB64(credential.response.userHandle.buffer as ArrayBuffer),
};
}

View File

@@ -1,3 +1,5 @@
import { PublicKeyCredential } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
import { Utils } from "../../../../platform/misc/utils";
export abstract class WebAuthnLoginResponseRequest {
@@ -8,7 +10,7 @@ export abstract class WebAuthnLoginResponseRequest {
constructor(credential: PublicKeyCredential) {
this.id = credential.id;
this.rawId = Utils.fromBufferToUrlB64(credential.rawId);
this.rawId = Utils.fromBufferToUrlB64(credential.rawId.buffer as ArrayBuffer);
this.type = credential.type;
// WARNING: do not add PRF information here by mapping

View File

@@ -43,15 +43,13 @@ export class WebAuthnLoginService implements WebAuthnLoginServiceAbstraction {
try {
const response = await this.navigatorCredentialsService.get(nativeOptions);
if (!(response instanceof PublicKeyCredential)) {
return undefined;
}
// TODO: Remove `any` when typescript typings add support for PRF
const prfResult = (response.getClientExtensionResults() as any).prf?.results?.first;
const prfResult = response.prf;
let symmetricPrfKey: PrfKey | undefined;
if (prfResult != undefined) {
symmetricPrfKey =
await this.webAuthnLoginPrfKeyService.createSymmetricKeyFromPrf(prfResult);
symmetricPrfKey = await this.webAuthnLoginPrfKeyService.createSymmetricKeyFromPrf(
prfResult.buffer as ArrayBuffer,
);
}
const deviceResponse = new WebAuthnLoginAssertionResponseRequest(response);