1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-25 17:13:24 +00:00

Use WebAuthn client window for silent assertions

This commit is contained in:
Isaiah Inuwa
2025-11-25 09:55:15 -06:00
parent 89585f0b67
commit 93833f743d
9 changed files with 29 additions and 8 deletions

View File

@@ -165,6 +165,7 @@ export declare namespace autofill {
supportedAlgorithms: Array<number>
windowXy: Position
excludedCredentials: Array<Array<number>>
clientWindowHandle?: Array<number>
context?: string
}
export interface PasskeyRegistrationResponse {
@@ -179,6 +180,7 @@ export declare namespace autofill {
userVerification: UserVerification
allowedCredentials: Array<Array<number>>
windowXy: Position
clientWindowHandle?: Array<number>
context?: string
}
export interface PasskeyAssertionWithoutUserInterfaceRequest {
@@ -190,6 +192,7 @@ export declare namespace autofill {
clientDataHash: Array<number>
userVerification: UserVerification
windowXy: Position
clientWindowHandle?: Array<number>
context?: string
}
export interface NativeStatus {

View File

@@ -695,6 +695,7 @@ pub mod autofill {
pub supported_algorithms: Vec<i32>,
pub window_xy: Position,
pub excluded_credentials: Vec<Vec<u8>>,
pub client_window_handle: Option<Vec<u8>>,
pub context: Option<String>,
}
@@ -717,6 +718,7 @@ pub mod autofill {
pub user_verification: UserVerification,
pub allowed_credentials: Vec<Vec<u8>>,
pub window_xy: Position,
pub client_window_handle: Option<Vec<u8>>,
pub context: Option<String>,
//extension_input: Vec<u8>, TODO: Implement support for extensions
}
@@ -733,6 +735,7 @@ pub mod autofill {
pub client_data_hash: Vec<u8>,
pub user_verification: UserVerification,
pub window_xy: Position,
pub client_window_handle: Option<Vec<u8>>,
pub context: Option<String>,
}

View File

@@ -44,6 +44,7 @@ pub fn get_assertion(
.map(|id| id.to_vec())
.collect();
let client_window_handle = request.window_handle.0.addr().to_le_bytes().to_vec();
let client_pos = request
.window_handle
.center_position()
@@ -62,6 +63,7 @@ pub fn get_assertion(
client_data_hash,
allowed_credentials: allowed_credential_ids,
user_verification,
client_window_handle,
window_xy: Position {
x: client_pos.0,
y: client_pos.1,
@@ -109,6 +111,7 @@ fn send_assertion_request(
credential_id: request.allowed_credentials[0].clone(),
client_data_hash: request.client_data_hash,
user_verification: request.user_verification,
client_window_handle: request.client_window_handle,
window_xy: request.window_xy,
context: request.context,
};

View File

@@ -12,6 +12,7 @@ pub struct PasskeyAssertionRequest {
pub user_verification: UserVerification,
pub allowed_credentials: Vec<Vec<u8>>,
pub window_xy: Position,
pub client_window_handle: Vec<u8>,
pub context: String,
// pub extension_input: Vec<u8>, TODO: Implement support for extensions
}
@@ -24,6 +25,7 @@ pub struct PasskeyAssertionWithoutUserInterfaceRequest {
pub client_data_hash: Vec<u8>,
pub user_verification: UserVerification,
pub window_xy: Position,
pub client_window_handle: Vec<u8>,
pub context: String,
}

View File

@@ -14,6 +14,7 @@ pub struct PasskeyRegistrationRequest {
pub user_verification: UserVerification,
pub supported_algorithms: Vec<i32>,
pub window_xy: Position,
pub client_window_handle: Vec<u8>,
pub excluded_credentials: Vec<Vec<u8>>,
pub context: String,
}

View File

@@ -87,6 +87,7 @@ pub fn make_credential(
);
}
let client_window_handle = request.window_handle.0.addr().to_le_bytes().to_vec();
let client_pos = request
.window_handle
.center_position()
@@ -104,6 +105,7 @@ pub fn make_credential(
excluded_credentials,
user_verification: user_verification,
supported_algorithms,
client_window_handle,
window_xy: Position {
x: client_pos.0,
y: client_pos.1,

View File

@@ -213,7 +213,7 @@ export class Fido2CreateComponent implements OnInit, OnDestroy {
let cred = cipher.login.fido2Credentials[0];
const username = cred.userName ?? cred.userDisplayName
return this.session.promptForUserVerification(username, "Verify it's you to update a new credential")
return this.session.promptForUserVerification(username, "Verify it's you to create a new credential")
}
private async showErrorDialog(config: SimpleDialogOptions): Promise<void> {

View File

@@ -215,10 +215,11 @@ export class DesktopAutofillService implements OnDestroy {
this.inFlightRequests[request.context] = controller;
}
const clientHandle = request.clientWindowHandle ? new Uint8Array(request.clientWindowHandle) : null;
try {
const response = await this.fido2AuthenticatorService.makeCredential(
this.convertRegistrationRequest(request),
{ windowXy: request.windowXy },
{ windowXy: request.windowXy, handle: clientHandle },
controller,
request.context,
);
@@ -293,9 +294,10 @@ export class DesktopAutofillService implements OnDestroy {
);
}
const clientHandle = request.clientWindowHandle ? new Uint8Array(request.clientWindowHandle) : null;
const response = await this.fido2AuthenticatorService.getAssertion(
this.convertAssertionRequest(request, true),
{ windowXy: request.windowXy },
{ windowXy: request.windowXy, handle: clientHandle },
controller,
request.context
);
@@ -329,10 +331,11 @@ export class DesktopAutofillService implements OnDestroy {
this.inFlightRequests[request.context] = controller;
}
const clientHandle = request.clientWindowHandle ? new Uint8Array(request.clientWindowHandle) : null;
try {
const response = await this.fido2AuthenticatorService.getAssertion(
this.convertAssertionRequest(request),
{ windowXy: request.windowXy },
{ windowXy: request.windowXy, handle: clientHandle },
controller,
request.context,
);

View File

@@ -43,6 +43,7 @@ export type NativeWindowObject = {
* The position of the window, first entry is the x position, second is the y position
*/
windowXy?: { x: number; y: number };
handle?: Uint8Array;
};
export class DesktopFido2UserInterfaceService
@@ -153,7 +154,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
const username = cred.userName ?? cred.userDisplayName
// TODO: internationalization
try {
const isConfirmed = await this.promptForUserVerification(username, "Verify it's you to log in with Bitwarden.");
const isConfirmed = await this.promptForUserVerification(username, "Verify it's you to log in with Bitwarden.", this.windowObject.handle);
return { cipherId: cipherIds[0], userVerified: isConfirmed };
}
catch (e) {
@@ -161,7 +162,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
}
}
else {
this.logService.debug(
this.logService.warning(
"shortcut - Assuming user presence and returning cipherId",
cipherIds[0],
);
@@ -371,9 +372,12 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
}
/** Called by the UI to prompt the user for verification. May be fulfilled by the OS. */
async promptForUserVerification(username: string, displayHint: string): Promise<boolean> {
async promptForUserVerification(username: string, displayHint: string, clientWindowHandle?: Uint8Array): Promise<boolean> {
this.logService.info("DesktopFido2UserInterfaceSession] Prompting for user verification")
let windowHandle = await ipc.platform.getNativeWindowHandle();
// Use the client window handle if we're not showing UI; otherwise use our modal window.
// For Windows, if the selected window handle is not in the foreground, then the Windows
// Hello dialog will also be in the background.
let windowHandle = clientWindowHandle ?? await ipc.platform.getNativeWindowHandle();
const uvResult = await ipc.autofill.runCommand<NativeAutofillUserVerificationCommand>({
namespace: "autofill",