1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-06 18:43:25 +00:00

[PM-2094] Fix windows hello focusing behavior (#12255)

* Implement new windows focus behavior

* Fix formatting

* Fix clippy warning

* Fix clippy warning

* Fix build

* Fix build
This commit is contained in:
Bernd Schoolmann
2025-01-09 14:07:40 +01:00
committed by GitHub
parent 67a59b6072
commit a527aa9196
9 changed files with 97 additions and 93 deletions

View File

@@ -3,12 +3,16 @@ use anyhow::{anyhow, Result};
#[allow(clippy::module_inception)]
#[cfg_attr(target_os = "linux", path = "unix.rs")]
#[cfg_attr(target_os = "windows", path = "windows.rs")]
#[cfg_attr(target_os = "macos", path = "macos.rs")]
#[cfg_attr(target_os = "windows", path = "windows.rs")]
mod biometric;
use base64::{engine::general_purpose::STANDARD as base64_engine, Engine};
pub use biometric::Biometric;
#[cfg(target_os = "windows")]
pub mod windows_focus;
use base64::{engine::general_purpose::STANDARD as base64_engine, Engine};
use sha2::{Digest, Sha256};
use crate::crypto::{self, CipherString};

View File

@@ -1,12 +1,15 @@
use std::{ffi::c_void, str::FromStr};
use std::{
ffi::c_void,
str::FromStr,
sync::{atomic::AtomicBool, Arc},
};
use anyhow::{anyhow, Result};
use base64::{engine::general_purpose::STANDARD as base64_engine, Engine};
use rand::RngCore;
use retry::delay::Fixed;
use sha2::{Digest, Sha256};
use windows::{
core::{factory, h, s, HSTRING},
core::{factory, h, HSTRING},
Foundation::IAsyncOperation,
Security::{
Credentials::{
@@ -14,17 +17,7 @@ use windows::{
},
Cryptography::CryptographicBuffer,
},
Win32::{
Foundation::HWND,
System::WinRT::IUserConsentVerifierInterop,
UI::{
Input::KeyboardAndMouse::{
keybd_event, GetAsyncKeyState, SetFocus, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP,
VK_MENU,
},
WindowsAndMessaging::{FindWindowA, SetForegroundWindow},
},
},
Win32::{Foundation::HWND, System::WinRT::IUserConsentVerifierInterop},
};
use crate::{
@@ -32,7 +25,10 @@ use crate::{
crypto::CipherString,
};
use super::{decrypt, encrypt};
use super::{
decrypt, encrypt,
windows_focus::{focus_security_prompt, set_focus},
};
/// The Windows OS implementation of the biometric trait.
pub struct Biometric {}
@@ -103,8 +99,22 @@ impl super::BiometricTrait for Biometric {
let challenge_buffer = CryptographicBuffer::CreateFromByteArray(&challenge)?;
let async_operation = result.Credential()?.RequestSignAsync(&challenge_buffer)?;
focus_security_prompt()?;
let signature = async_operation.get()?;
focus_security_prompt();
let done = Arc::new(AtomicBool::new(false));
let done_clone = done.clone();
let _ = std::thread::spawn(move || loop {
if !done_clone.load(std::sync::atomic::Ordering::Relaxed) {
focus_security_prompt();
std::thread::sleep(std::time::Duration::from_millis(500));
} else {
break;
}
});
let signature = async_operation.get();
done.store(true, std::sync::atomic::Ordering::Relaxed);
let signature = signature?;
if signature.Status()? != KeyCredentialStatus::Success {
return Err(anyhow!("Failed to sign data"));
@@ -168,57 +178,6 @@ fn random_challenge() -> [u8; 16] {
challenge
}
/// Searches for a window that looks like a security prompt and set it as focused.
///
/// Gives up after 1.5 seconds with a delay of 500ms between each try.
fn focus_security_prompt() -> Result<()> {
unsafe fn try_find_and_set_focus(
class_name: windows::core::PCSTR,
) -> retry::OperationResult<(), ()> {
let hwnd = unsafe { FindWindowA(class_name, None) };
if let Ok(hwnd) = hwnd {
set_focus(hwnd);
return retry::OperationResult::Ok(());
}
retry::OperationResult::Retry(())
}
let class_name = s!("Credential Dialog Xaml Host");
retry::retry_with_index(Fixed::from_millis(500), |current_try| {
if current_try > 3 {
return retry::OperationResult::Err(());
}
unsafe { try_find_and_set_focus(class_name) }
})
.map_err(|_| anyhow!("Failed to find security prompt"))
}
fn set_focus(window: HWND) {
let mut pressed = false;
unsafe {
// Simulate holding down Alt key to bypass windows limitations
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate#return-value
// The most significant bit indicates if the key is currently being pressed. This means the
// value will be negative if the key is pressed.
if GetAsyncKeyState(VK_MENU.0 as i32) >= 0 {
pressed = true;
keybd_event(VK_MENU.0 as u8, 0, KEYEVENTF_EXTENDEDKEY, 0);
}
let _ = SetForegroundWindow(window);
let _ = SetFocus(window);
if pressed {
keybd_event(
VK_MENU.0 as u8,
0,
KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP,
0,
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -0,0 +1,28 @@
use windows::{
core::s,
Win32::{
Foundation::HWND,
UI::{
Input::KeyboardAndMouse::SetFocus,
WindowsAndMessaging::{FindWindowA, SetForegroundWindow},
},
},
};
/// Searches for a window that looks like a security prompt and set it as focused.
/// Only works when the process has permission to foreground, either by being in foreground
/// Or by being given foreground permission https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow#remarks
pub fn focus_security_prompt() {
let class_name = s!("Credential Dialog Xaml Host");
let hwnd = unsafe { FindWindowA(class_name, None) };
if let Ok(hwnd) = hwnd {
set_focus(hwnd);
}
}
pub(crate) fn set_focus(window: HWND) {
unsafe {
let _ = SetForegroundWindow(window);
let _ = SetFocus(window);
}
}

View File

@@ -1,16 +1,10 @@
pub mod autofill;
#[cfg(feature = "sys")]
pub mod biometric;
#[cfg(feature = "sys")]
pub mod clipboard;
pub mod crypto;
pub mod error;
pub mod ipc;
#[cfg(feature = "sys")]
pub mod password;
#[cfg(feature = "sys")]
pub mod powermonitor;
#[cfg(feature = "sys")]
pub mod process_isolation;
#[cfg(feature = "sys")]
pub mod ssh_agent;