From 791397d60da9cf4028a0c94913ec018c5d95ae76 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 17 Nov 2025 15:13:08 +0100 Subject: [PATCH] Fix sign in --- apps/desktop/src/auth/preload.ts | 4 +-- .../main-navigator-credentials.serivce.ts | 15 +++++++- .../renderer-navigator-credentials.service.ts | 7 ++-- .../webauthn/navigator-credentials.service.ts | 18 +++++++++- .../default-navigator-credentials.service.ts | 34 +++++++++++++++++-- ...bauthn-login-assertion-response.request.ts | 18 +++++----- .../webauthn-login-response.request.ts | 4 ++- .../webauthn-login/webauthn-login.service.ts | 10 +++--- 8 files changed, 86 insertions(+), 24 deletions(-) diff --git a/apps/desktop/src/auth/preload.ts b/apps/desktop/src/auth/preload.ts index bb6c624d49e..e5ce2e67348 100644 --- a/apps/desktop/src/auth/preload.ts +++ b/apps/desktop/src/auth/preload.ts @@ -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 => - ipcRenderer.invoke("navigatorCredentials.get", options), + ): Promise => ipcRenderer.invoke("navigatorCredentials.get", options), navigatorCredentialsAvailable: (): Promise => ipcRenderer.invoke("navigatorCredentials.available"), }; diff --git a/apps/desktop/src/auth/services/main-navigator-credentials.serivce.ts b/apps/desktop/src/auth/services/main-navigator-credentials.serivce.ts index 88f1def5673..7f9d378dc44 100644 --- a/apps/desktop/src/auth/services/main-navigator-credentials.serivce.ts +++ b/apps/desktop/src/auth/services/main-navigator-credentials.serivce.ts @@ -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) => { diff --git a/apps/desktop/src/auth/services/renderer-navigator-credentials.service.ts b/apps/desktop/src/auth/services/renderer-navigator-credentials.service.ts index 3b015c4f66a..c4a4e555ed8 100644 --- a/apps/desktop/src/auth/services/renderer-navigator-credentials.service.ts +++ b/apps/desktop/src/auth/services/renderer-navigator-credentials.service.ts @@ -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 { + async get(options: CredentialRequestOptions): Promise { return await ipc.auth.navigatorCredentialsGet({ challenge: arrayBufferSourceToUint8Array(options.publicKey.challenge), timeout: options.publicKey.timeout, diff --git a/libs/common/src/auth/abstractions/webauthn/navigator-credentials.service.ts b/libs/common/src/auth/abstractions/webauthn/navigator-credentials.service.ts index 815cbdb13b0..a7aa5999624 100644 --- a/libs/common/src/auth/abstractions/webauthn/navigator-credentials.service.ts +++ b/libs/common/src/auth/abstractions/webauthn/navigator-credentials.service.ts @@ -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; + abstract get(options: CredentialRequestOptions): Promise; abstract available(): Promise; } diff --git a/libs/common/src/auth/services/webauthn-login/default-navigator-credentials.service.ts b/libs/common/src/auth/services/webauthn-login/default-navigator-credentials.service.ts index 733e57c0d28..2c52e2d512d 100644 --- a/libs/common/src/auth/services/webauthn-login/default-navigator-credentials.service.ts +++ b/libs/common/src/auth/services/webauthn-login/default-navigator-credentials.service.ts @@ -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 { - return await this.navigatorCredentials.get(options); + async get(options: CredentialRequestOptions): Promise { + 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 { 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); + } +} diff --git a/libs/common/src/auth/services/webauthn-login/request/webauthn-login-assertion-response.request.ts b/libs/common/src/auth/services/webauthn-login/request/webauthn-login-assertion-response.request.ts index 746a0962e86..b54a7bc6493 100644 --- a/libs/common/src/auth/services/webauthn-login/request/webauthn-login-assertion-response.request.ts +++ b/libs/common/src/auth/services/webauthn-login/request/webauthn-login-assertion-response.request.ts @@ -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), }; } diff --git a/libs/common/src/auth/services/webauthn-login/request/webauthn-login-response.request.ts b/libs/common/src/auth/services/webauthn-login/request/webauthn-login-response.request.ts index 4b1eff4d471..301f32befff 100644 --- a/libs/common/src/auth/services/webauthn-login/request/webauthn-login-response.request.ts +++ b/libs/common/src/auth/services/webauthn-login/request/webauthn-login-response.request.ts @@ -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 diff --git a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts index 0ac383b13fe..2a357b32eab 100644 --- a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts +++ b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts @@ -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);