From b2017ed9ee6d1a2e9193066b9f001436d4c5955d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Mon, 23 Jun 2025 11:18:35 +0200 Subject: [PATCH] draft draft --- apps/desktop/com.bitwarden.pfx | Bin 0 -> 2694 bytes apps/desktop/custom-appx-manifest.xml | 8 +- apps/desktop/desktop_native/Cargo.lock | 47 + apps/desktop/desktop_native/napi/index.d.ts | 48 + apps/desktop/desktop_native/napi/src/lib.rs | 201 ++ .../passkey_authenticator_internal/dummy.rs | 17 + .../passkey_authenticator_internal/windows.rs | 75 + .../windows_plugin_authenticator/Cargo.toml | 4 + .../src/PluginAuthenticatorImpl.cpp.sample | 977 ++++++++++ .../src/assert.rs | 249 +++ .../src/com_buffer.rs | 84 + .../src/com_provider.rs | 417 ++++ .../src/com_registration.rs | 212 ++ .../windows_plugin_authenticator/src/ipc.rs | 65 + .../windows_plugin_authenticator/src/lib.rs | 570 +----- .../src/make_credential.rs | 253 +++ .../src/pluginauthenticator.h.sample | 239 +++ .../src/pluginauthenticator.rs | 126 -- .../windows_plugin_authenticator/src/sync.rs | 207 ++ .../windows_plugin_authenticator/src/types.rs | 92 + .../windows_plugin_authenticator/src/util.rs | 66 - .../windows_plugin_authenticator/src/utils.rs | 109 ++ .../src/webauthn.h.sample | 1727 +++++++++++++++++ .../src/webauthn.rs | 380 +++- apps/desktop/electron-builder.json | 12 +- apps/desktop/package.json | 5 +- apps/desktop/sign.ps1 | Bin 0 -> 166 bytes apps/desktop/src/autofill/preload.ts | 4 + .../services/desktop-autofill.service.ts | 56 +- apps/desktop/src/entry.ts | 4 - apps/desktop/src/package.json | 2 +- .../main/autofill/native-autofill.main.ts | 360 +++- 32 files changed, 5835 insertions(+), 781 deletions(-) create mode 100644 apps/desktop/com.bitwarden.pfx create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/PluginAuthenticatorImpl.cpp.sample create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/com_buffer.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/com_registration.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.h.sample delete mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/sync.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs delete mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/util.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/utils.rs create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.h.sample create mode 100644 apps/desktop/sign.ps1 diff --git a/apps/desktop/com.bitwarden.pfx b/apps/desktop/com.bitwarden.pfx new file mode 100644 index 0000000000000000000000000000000000000000..ed82d494b20d3de2562a053abe7f04b7d4e00e8f GIT binary patch literal 2694 zcmZXUc{r5)9>$-U#>j+>ZDi-QHMSY+2!jaWZ5vB8_K9qR8CfbzlPy#tORq9x*T`0u zA!JF^K{6Ftk}Yf5!=TR8^`7@!=Q@A{2@ku;8_UqrE-389%(T-&##)Qzw@UMQ{mkdCV8P zNOxkm5Ab?2x)=gNpo}0p+JTG6)v05u_k9(@w8Mc9rgR&$4@U`%00oMd|o~> ziX1(H=gl&{`~BOeOzAHakp7bSlO+a2MKMjO9-B3BC(e`bg;1EgH`**ZFsFFKAiObE zmJF#1P?=rrSuV_kzZ1!=oHaVOAd=%?65%6fA@fKyw|&><~# zI72}B;(AlL*vI59uhWW&F=spa=bsPDy3k*%93!fjMNs(#;GK`}PpuM*@8++T5hR_k zt&KjLr}s<_zc4yeu)-@jGNh(-$NEC0&Q4CT+Lh74{X83M-&iKEzt+x$CI3|>s{O%L zpVU>w7bj%5>arCscLhmn(*WszQD;wfjCttI(R?vw(?4a8$8nUs6V0NLq6 z_MbViq8DfBL1ROY-s~;Oy96gOSv>fhXtT~3V7Fe{$MMhg`KcLQ62f&kD>x6HFO04!D=k9Khw%jfV0e3Z+z9JTJ z?HqBa5!Naa_sXn2zJXDeKW;A#H+{n4gU!t`N@6ZEUR;U?j=_$}lc&e_W zlDWmgEnYlH#fTY)`wu+tmC~JgVchA{6A-XzTS5LB{zFw6IoA_#;@lRbccsJi0?#ST zys?B;dY$1B$SYRiS9-B~iwNS#O%?P#Llj5A27?(Uh4`yNp@1@`LRYd33KDiBb_v+$ zPUFCF9RP8)ziP$Mr^Mly= z1IqeK#+d*YJfvePk@xjU>SryU)Kva=vwDGtJN@n$YnBG7l*b^qX&eili1n!*D39u8 z3c5yVJ2CNMf(9((gNi}Shoq>N-RoN&@K2|zevm^Y$Ite-hKD?>Z}{%SG}?DGPS=e0 zJ@+;tSD;6Cc+^voGTE(a7~fDS5!Mm7foonOWqc+2^79}gmNcw9p57h_=3s`ER!f!L z{G&P6Ppe*F4}qw2M_*^61pcW@E+I4&0s#R4fO7sX6hxnqJOzXR7XeiO4QK%bz!T5} zG}zOfO$dPAZ>Gwgnt&I8VKWS%%ieX_8;X7Fu$5mvP4@db>=ZQM`72%-3W9(rx^U3% z_&)(3b|47|1jy{;#r~W$fctw3V;f0qqcmX6o)N#55Vl5QbMN2$%x`BXJA=S}Cb2tR z>YxATgtBX%kBzv&Ab@>w{8s_}fAQazC}HTkg@ybo{us)EAybx=Fj1|Q+HWc{_=qt) zPWrgn9vvAjzC0HnymbwAx!(wA&+wtgmW^C>>Cy{#71*E6uP!C74c!TH>g;y{jdQ7F z0Hb@Kxz9RMiY8SKym!9LJ4+K_x*CRH-gsBuzs{MzVY*y_EG#NM8AX3}q|eRaoTlMb zPbSZKu`eSE52dYEhk9z1jT(DlvQC;w-c{K2JWTBIM&EAjznr*cG#zx~TLXVu7;B4B zH9|b%;vUH?XvylXKqE!*`lOnDN1T|q`)<5XD*CEp1?O9uD&C`{y|{n{UKB2F*2AFr zYq&cH;_K7f=y4jwxc0-JoDrBo*hlY&;C?#8$@Qncl(n!+Q&)e@Piw1BM$c!w@yZu+ zrrJ&PCou2N7^<^Ky+;<$`)Avty9>o>NRg-*n^T)r6~+vmEY>2@$%G^!-r}m`48eVD z9w3M6!rz_Al`Cj-uStQ;w+RW_9IK=`e8uYoC8)af_%&S0eI%Vo}`!ZKa$e|1hzI;Bi zsx^_m6myMK`*gPUg!|#81l>}YAk?FNJ9M2Kh5&xmag|s zxNEV=Dj(nf;Jz2kxsxa4Qpb8(%%$Kg=R;;30M!!auqUN2d5eiZdbF1YW{}q@^RPa_ zR;@EUVU?Mz)hBtg0jaH_G48RgwGP1+U*p2ZI9jhOdXJ@a!z5J?Kc2WvMELTb(n^ji zw#ln&_-r6^fEB#Tq2DLx=X*jM|8xf{zM?*A^f^0XV9iNcV_4|*a8hB#Tv5sZzQ})C zcdB_I%f<<8U&5Wf5|AfAA`?zFXV%=X+aa_PykkS{1HDRY<7LzNtQpR?eG|pI - + Bitwarden Bitwarden Inc diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index f7081f056ae..674c0e40a3b 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -581,6 +581,33 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -723,6 +750,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "crypto-common" version = "0.1.6" @@ -1401,6 +1434,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -4547,9 +4590,13 @@ checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" name = "windows_plugin_authenticator" version = "0.0.0" dependencies = [ + "ciborium", "hex", "reqwest", + "serde", "serde_json", + "sha2", + "tokio", "windows 0.61.1 (git+https://github.com/microsoft/windows-rs.git?rev=d09b4681de02560cf05bd3e57d7ea56b73f3b2f8)", "windows-core 0.61.2 (git+https://github.com/microsoft/windows-rs.git?rev=d09b4681de02560cf05bd3e57d7ea56b73f3b2f8)", ] diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index b3c6f715e98..b973c19d3b1 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -3,6 +3,10 @@ /* auto-generated by NAPI-RS */ +export interface PasskeyRequestEvent { + requestType: string + requestJson: string +} export declare namespace passwords { /** Fetch the stored password from the keychain. */ export function getPassword(service: string, account: string): Promise @@ -186,7 +190,51 @@ export declare namespace crypto { export function argon2(secret: Buffer, salt: Buffer, iterations: number, memory: number, parallelism: number): Promise } export declare namespace passkey_authenticator { + export interface SyncedCredential { + credentialId: string + rpId: string + userName: string + userId: string + } + export interface PasskeyAssertionRequest { + rpId: string + transactionId: string + clientDataHash: Array + allowedCredentials: Array> + userVerification: boolean + } + export interface PasskeyRegistrationRequest { + rpId: string + transactionId: string + userId: Array + userName: string + clientDataHash: Array + userVerification: boolean + supportedAlgorithms: Array + } + export interface PasskeySyncRequest { + rpId: string + } + export interface PasskeyAssertionResponse { + credentialId: Array + authenticatorData: Array + signature: Array + userHandle: Array + } + export interface PasskeyRegistrationResponse { + credentialId: Array + attestationObject: Array + } + export interface PasskeySyncResponse { + credentials: Array + } + export interface PasskeyErrorResponse { + message: string + } export function register(): void + export function onRequest(callback: (error: null | Error, event: PasskeyRequestEvent) => Promise): Promise + export function syncCredentialsToWindows(credentials: Array): void + export function getCredentialsFromWindows(): Array } export declare namespace logging { export const enum LogLevel { diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 079872a3b03..95ec030fbc3 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -810,12 +810,213 @@ pub mod crypto { #[napi] pub mod passkey_authenticator { + use napi::threadsafe_function::{ + ErrorStrategy::CalleeHandled, ThreadsafeFunction, + }; + use serde_json; + + // Re-export the platform-specific types + pub use crate::passkey_authenticator_internal::PasskeyRequestEvent; + + #[napi(object)] + #[derive(serde::Serialize, serde::Deserialize)] + pub struct SyncedCredential { + pub credential_id: String, // base64url encoded + pub rp_id: String, + pub user_name: String, + pub user_id: String, // base64url encoded + } + + impl From for SyncedCredential { + fn from(cred: windows_plugin_authenticator::SyncedCredential) -> Self { + use base64::Engine; + Self { + credential_id: base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cred.credential_id), + rp_id: cred.rp_id, + user_name: cred.user_name, + user_id: base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cred.user_id), + } + } + } + + impl From for windows_plugin_authenticator::SyncedCredential { + fn from(cred: SyncedCredential) -> Self { + use base64::Engine; + Self { + credential_id: base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(&cred.credential_id) + .unwrap_or_default(), + rp_id: cred.rp_id, + user_name: cred.user_name, + user_id: base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(&cred.user_id) + .unwrap_or_default(), + } + } + } + + #[napi(object)] + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyAssertionRequest { + pub rp_id: String, + pub transaction_id: String, + pub client_data_hash: Vec, + pub allowed_credentials: Vec>, + pub user_verification: bool, + } + + #[napi(object)] + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "camelCase")] + + pub struct PasskeyRegistrationRequest { + pub rp_id: String, + pub transaction_id: String, + pub user_id: Vec, + pub user_name: String, + pub client_data_hash: Vec, + pub user_verification: bool, + pub supported_algorithms: Vec, + } + + #[napi(object)] + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeySyncRequest { + pub rp_id: String, + } + + #[napi(object)] + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "camelCase")] + + pub struct PasskeyAssertionResponse { + pub credential_id: Vec, + pub authenticator_data: Vec, + pub signature: Vec, + pub user_handle: Vec, + } + + #[napi(object)] + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "camelCase")] + + pub struct PasskeyRegistrationResponse { + pub credential_id: Vec, + pub attestation_object: Vec, + } + + #[napi(object)] + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "camelCase")] + + pub struct PasskeySyncResponse { + pub credentials: Vec, + } + + #[napi(object)] + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "camelCase")] + + pub struct PasskeyErrorResponse { + pub message: String, + } + + #[napi] pub fn register() -> napi::Result<()> { crate::passkey_authenticator_internal::register().map_err(|e| { napi::Error::from_reason(format!("Passkey registration failed - Error: {e} - {e:?}")) }) } + + #[napi] + pub async fn on_request( + #[napi(ts_arg_type = "(error: null | Error, event: PasskeyRequestEvent) => Promise")] + callback: ThreadsafeFunction, + ) -> napi::Result { + crate::passkey_authenticator_internal::on_request(callback).await + } + + #[napi] + pub fn sync_credentials_to_windows(credentials: Vec) -> napi::Result<()> { + const PLUGIN_CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062"; + + log::info!("[NAPI] sync_credentials_to_windows called with {} credentials", credentials.len()); + + // Log each credential being synced (with truncated IDs for security) + for (i, cred) in credentials.iter().enumerate() { + let truncated_cred_id = if cred.credential_id.len() > 16 { + format!("{}...", &cred.credential_id[..16]) + } else { + cred.credential_id.clone() + }; + let truncated_user_id = if cred.user_id.len() > 16 { + format!("{}...", &cred.user_id[..16]) + } else { + cred.user_id.clone() + }; + log::info!("[NAPI] Credential {}: RP={}, User={}, CredID={}, UserID={}", + i + 1, cred.rp_id, cred.user_name, truncated_cred_id, truncated_user_id); + } + + // Convert NAPI types to internal types using From trait + let internal_credentials: Vec = credentials + .into_iter() + .map(|cred| cred.into()) + .collect(); + + log::info!("[NAPI] Calling Windows Plugin Authenticator sync with CLSID: {}", PLUGIN_CLSID); + let result = windows_plugin_authenticator::sync_credentials_to_windows(internal_credentials, PLUGIN_CLSID); + + match &result { + Ok(()) => log::info!("[NAPI] sync_credentials_to_windows completed successfully"), + Err(e) => log::error!("[NAPI] sync_credentials_to_windows failed: {}", e), + } + + result.map_err(|e| napi::Error::from_reason(format!("Sync credentials failed: {}", e))) + } + + #[napi] + pub fn get_credentials_from_windows() -> napi::Result> { + const PLUGIN_CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062"; + + log::info!("[NAPI] get_credentials_from_windows called with CLSID: {}", PLUGIN_CLSID); + + let result = windows_plugin_authenticator::get_credentials_from_windows(PLUGIN_CLSID); + + let internal_credentials = match &result { + Ok(creds) => { + log::info!("[NAPI] Retrieved {} credentials from Windows", creds.len()); + result.map_err(|e| napi::Error::from_reason(format!("Get credentials failed: {}", e)))? + } + Err(e) => { + log::error!("[NAPI] get_credentials_from_windows failed: {}", e); + return Err(napi::Error::from_reason(format!("Get credentials failed: {}", e))); + } + }; + + // Convert internal types to NAPI types using From trait + let napi_credentials: Vec = internal_credentials + .into_iter() + .enumerate() + .map(|(i, cred)| { + let result_cred: SyncedCredential = cred.into(); + let truncated_cred_id = if result_cred.credential_id.len() > 16 { + format!("{}...", &result_cred.credential_id[..16]) + } else { + result_cred.credential_id.clone() + }; + log::info!("[NAPI] Retrieved credential {}: RP={}, User={}, CredID={}", + i + 1, result_cred.rp_id, result_cred.user_name, truncated_cred_id); + result_cred + }) + .collect(); + + log::info!("[NAPI] get_credentials_from_windows completed successfully, returning {} credentials", napi_credentials.len()); + Ok(napi_credentials) + } } #[napi] diff --git a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs index bcd929c16b4..9cd748ba185 100644 --- a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs +++ b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs @@ -1,5 +1,22 @@ use anyhow::{bail, Result}; +use napi::threadsafe_function::{ + ErrorStrategy::CalleeHandled, ThreadsafeFunction, +}; + +#[napi(object)] +#[derive(Debug)] +pub struct PasskeyRequestEvent { + pub operation: String, + pub rpid: String, + pub transaction_id: String, +} pub fn register() -> Result<()> { bail!("Not implemented") } + +pub async fn on_request( + _callback: ThreadsafeFunction, +) -> napi::Result { + Err(napi::Error::from_reason("Passkey authenticator is not supported on this platform".to_string())) +} diff --git a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs index 4ff51f5bce4..13bc8f71b32 100644 --- a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs +++ b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs @@ -1,7 +1,82 @@ use anyhow::{anyhow, Result}; +use napi::{bindgen_prelude::Promise, threadsafe_function::{ + ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, +}, JsObject}; +use tokio::sync::mpsc; +use serde_json; +use windows_plugin_authenticator::util; + +// Simple wrapper for passing JSON strings to TypeScript +#[napi(object)] +#[derive(Debug)] +pub struct PasskeyRequestEvent { + pub request_type: String, + pub request_json: String, +} pub fn register() -> Result<()> { windows_plugin_authenticator::register().map_err(|e| anyhow!(e))?; Ok(()) } + +pub async fn on_request( + callback: ThreadsafeFunction, +) -> napi::Result { + let (tx, mut rx) = mpsc::unbounded_channel(); + + // Set the sender in the Windows plugin authenticator + windows_plugin_authenticator::set_request_sender(tx); + + // Spawn task to handle incoming events + tokio::spawn(async move { + while let Some(event) = rx.recv().await { + // The request is already serialized as JSON in the event + let request_json = event.request_json; + + // Get the request type as a string + let request_type = match event.request_type { + windows_plugin_authenticator::RequestType::Assertion => "assertion".to_string(), + windows_plugin_authenticator::RequestType::Registration => "registration".to_string(), + windows_plugin_authenticator::RequestType::Sync => "sync".to_string(), + }; + + let napi_event = PasskeyRequestEvent { request_type, request_json }; + + // Call the callback asynchronously and capture the return value + let promise_result: Result, napi::Error> = callback.call_async(Ok(napi_event)).await; + // awai promse + + match promise_result { + Ok(promise_result) => match promise_result.await { + Ok(result) => { + util::message(&format!("CALLBACK COMPLETED WITH RESPONSE: {}", result)); + // Parse the JSON response directly back to Rust enum + let response: windows_plugin_authenticator::PasskeyResponse = match serde_json::from_str(&result) { + Ok(resp) => resp, + Err(e) => windows_plugin_authenticator::PasskeyResponse::Error { + message: format!("JSON parse error: {}", e), + } + }; + let _ = event.response_sender.send(response); + } + Err(e) => { + eprintln!("Error calling passkey callback inner: {}", e); + let _ = event.response_sender.send(windows_plugin_authenticator::PasskeyResponse::Error { + message: format!("Inner Callback error: {}", e), + }); + + } + } + Err(e) => { + eprintln!("Error calling passkey callback: {}", e); + let _ = event.response_sender.send(windows_plugin_authenticator::PasskeyResponse::Error { + message: format!("Callback error: {}", e), + }); + } + } + } + }); + + Ok("Event listener registered successfully".to_string()) +} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml b/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml index 1c6dee6abfb..1631d430188 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml @@ -11,3 +11,7 @@ windows-core = { workspace = true } hex = { workspace = true } reqwest = { version = "0.12", features = ["json", "blocking"] } serde_json = { workspace = true } +serde = { workspace = true, features = ["derive"] } +ciborium = "0.2" +sha2 = "0.10" +tokio = { workspace = true } diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/PluginAuthenticatorImpl.cpp.sample b/apps/desktop/desktop_native/windows_plugin_authenticator/src/PluginAuthenticatorImpl.cpp.sample new file mode 100644 index 00000000000..21025834182 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/PluginAuthenticatorImpl.cpp.sample @@ -0,0 +1,977 @@ +#include "pch.h" +#include "PluginAuthenticatorImpl.h" +#include +#include +#include +#include +#include +#include +#include +#include +namespace winrt +{ + using namespace winrt::Windows::Foundation; + using namespace winrt::Microsoft::UI::Windowing; + using namespace winrt::Microsoft::UI::Xaml; + using namespace winrt::Microsoft::UI::Xaml::Controls; + using namespace winrt::Microsoft::UI::Xaml::Navigation; + using namespace PasskeyManager; + using namespace PasskeyManager::implementation; + using namespace CborLite; +} + +namespace winrt::PasskeyManager::implementation +{ + static std::vector GetRequestSigningPubKey() + { + return wil::reg::get_value_binary(HKEY_CURRENT_USER, c_pluginRegistryPath, c_windowsPluginRequestSigningKeyRegKeyName, REG_BINARY); + } + + /* + * This function is used to verify the signature of a request buffer. + * The public key is part of response to plugin registration. + */ + static HRESULT VerifySignatureHelper( + std::vector& dataBuffer, + PBYTE pbKeyData, + DWORD cbKeyData, + PBYTE pbSignature, + DWORD cbSignature) + { + // Create key provider + wil::unique_ncrypt_prov hProvider; + wil::unique_ncrypt_key reqSigningKey; + + // Get the provider + RETURN_IF_FAILED(NCryptOpenStorageProvider(&hProvider, nullptr, 0)); + // Create a NCrypt key handle from the public key + RETURN_IF_FAILED(NCryptImportKey( + hProvider.get(), + NULL, + BCRYPT_ECCPUBLIC_BLOB, + NULL, + &reqSigningKey, + pbKeyData, + cbKeyData, 0)); + + // Verify the signature over the hash of dataBuffer using the hKey + DWORD objLenSize = 0; + DWORD bytesRead = 0; + RETURN_IF_NTSTATUS_FAILED(BCryptGetProperty( + BCRYPT_SHA256_ALG_HANDLE, + BCRYPT_OBJECT_LENGTH, + reinterpret_cast(&objLenSize), + sizeof(objLenSize), + &bytesRead, 0)); + + auto objLen = wil::make_unique_cotaskmem(objLenSize); + wil::unique_bcrypt_hash hashHandle; + RETURN_IF_NTSTATUS_FAILED(BCryptCreateHash( + BCRYPT_SHA256_ALG_HANDLE, + wil::out_param(hashHandle), + objLen.get(), + objLenSize, + nullptr, 0, 0)); + RETURN_IF_NTSTATUS_FAILED(BCryptHashData( + hashHandle.get(), + dataBuffer.data(), + static_cast(dataBuffer.size()), 0)); + + DWORD localHashByteCount = 0; + RETURN_IF_NTSTATUS_FAILED(BCryptGetProperty( + BCRYPT_SHA256_ALG_HANDLE, + BCRYPT_HASH_LENGTH, + reinterpret_cast(&localHashByteCount), + sizeof(localHashByteCount), + &bytesRead, 0)); + + auto localHashBuffer = wil::make_unique_cotaskmem(localHashByteCount); + RETURN_IF_NTSTATUS_FAILED(BCryptFinishHash(hashHandle.get(), localHashBuffer.get(), localHashByteCount, 0)); + RETURN_IF_WIN32_ERROR(NCryptVerifySignature( + reqSigningKey.get(), + nullptr, + localHashBuffer.get(), + localHashByteCount, + pbSignature, + cbSignature, 0)); + + return S_OK; + } + + HRESULT CheckHelloConsentCompleted() + { + winrt::com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as(); + HANDLE handles[2] = { curApp->m_hVaultConsentComplete.get(), curApp->m_hVaultConsentFailed.get() }; + + DWORD cWait = ARRAYSIZE(handles); + DWORD hIndex = 0; + RETURN_IF_FAILED(CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, INFINITE, cWait, handles, &hIndex)); + if (hIndex == 1) // Consent failed + { + RETURN_HR(E_FAIL); + } + return S_OK; + } + + HRESULT PerformUv( + winrt::com_ptr& curApp, + HWND hWnd, + wil::shared_hmodule webauthnDll, + GUID transactionId, + PluginOperationType operationType, + std::vector requestBuffer, + wil::shared_cotaskmem_string rpName, + wil::shared_cotaskmem_string userName) + { + curApp->SetPluginPerformOperationOptions(hWnd, operationType, rpName.get(), userName.get()); + + // Wait for the app main window to be ready. + DWORD hIndex = 0; + RETURN_IF_FAILED(CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, INFINITE, 1, curApp->m_hWindowReady.addressof(), &hIndex)); + + // Trigger a Consent Verifier Dialog to simulate a Windows Hello unlock flow + // This is to demonstrate a vault unlock flow using Windows Hello and is not the recommended way to secure the vault + if (PluginCredentialManager::getInstance().GetVaultLock()) + { + curApp->GetDispatcherQueue().TryEnqueue([curApp]() + { + curApp->SimulateUnLockVaultUsingConsentVerifier(); + }); + RETURN_IF_FAILED(CheckHelloConsentCompleted()); + } + else + { + SetEvent(curApp->m_hVaultConsentComplete.get()); + } + + // Wait for user confirmation to proceed with the operation Create/Signin/Cancel button + // This is a mock up for plugin requiring UI. + { + HANDLE handles[2] = { curApp->m_hPluginProceedButtonEvent.get(), curApp->m_hPluginUserCancelEvent.get() }; + DWORD cWait = ARRAYSIZE(handles); + + RETURN_IF_FAILED(CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, INFINITE, cWait, handles, &hIndex)); + if (hIndex == 1) // Cancel button clicked + { + // User cancelled the operation. NTE_USER_CANCELLED allows Windows to distinguish between user cancellation and other errors. + return NTE_USER_CANCELLED; + } + } + + // Skip user verification if the user has already performed a gesture to unlock the vault to avoid double prompting + if (PluginCredentialManager::getInstance().GetVaultLock()) + { + return S_OK; + } + + EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV pluginPerformUv{}; + pluginPerformUv.transactionId = &transactionId; + + if (curApp->m_silentMode) + { + // If the app did not display any UI, use the hwnd of the caller here. This was included in the request to the plugin. Refer: EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST + pluginPerformUv.hwnd = hWnd; + } + else + { + // If the app displayed UI, use the hwnd of the app window here + pluginPerformUv.hwnd = curApp->GetNativeWindowHandle(); + } + + EXPERIMENTAL_PWEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE pPluginPerformUvResponse = nullptr; + + auto webAuthNPluginPerformUv = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNPluginPerformUv); + RETURN_HR_IF_NULL(E_NOTIMPL, webAuthNPluginPerformUv); + + // Step 1: Get the UV count + pluginPerformUv.type = EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE::GetUvCount; + RETURN_IF_FAILED(webAuthNPluginPerformUv(&pluginPerformUv, &pPluginPerformUvResponse)); + + /* + * pPluginPerformUvResponse->pbResponse contains the UV count + * The UV count tracks the number of times the user has performed a gesture to unlock the vault + */ + + // Step 2: Get the public key + pluginPerformUv.type = EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE::GetPubKey; + RETURN_IF_FAILED(webAuthNPluginPerformUv(&pluginPerformUv, &pPluginPerformUvResponse)); + + // stash public key in a new buffer for later use + DWORD cbPubData = pPluginPerformUvResponse->cbResponse; + wil::unique_hlocal_ptr ppbPubKeyData = wil::make_unique_hlocal(cbPubData); + memcpy_s(ppbPubKeyData.get(), cbPubData, pPluginPerformUvResponse->pbResponse, pPluginPerformUvResponse->cbResponse); + + // Step 3: Perform UV. This step uses a Windows Hello prompt to authenticate the user + pluginPerformUv.type = EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE::PerformUv; + pluginPerformUv.pwszUsername = wil::make_cotaskmem_string(userName.get()).release(); + // pwszContext can be used to provide additional context to the user. This is displayed alongside the username in the Windows Hello passkey user verification dialog. + pluginPerformUv.pwszContext = wil::make_cotaskmem_string(L"Context String").release(); + RETURN_IF_FAILED(webAuthNPluginPerformUv(&pluginPerformUv, &pPluginPerformUvResponse)); + + // Verify the signature over the hash of requestBuffer using the hKey + auto signatureVerifyResult = VerifySignatureHelper( + requestBuffer, + ppbPubKeyData.get(), + cbPubData, + pPluginPerformUvResponse->pbResponse, + pPluginPerformUvResponse->cbResponse); + curApp->GetDispatcherQueue().TryEnqueue([curApp, signatureVerifyResult]() + { + if (FAILED(signatureVerifyResult)) + { + curApp->m_pluginOperationStatus.uvSignatureVerificationStatus = signatureVerifyResult; + } + }); + return S_OK; + } + + /* + * This function is used to create a simplified version of authenticator data for the webauthn authenticator operations. + * Refer: https://www.w3.org/TR/webauthn-3/#authenticator-data for more details. + */ + HRESULT CreateAuthenticatorData(wil::shared_ncrypt_key hKey, + DWORD cbRpId, + PBYTE pbRpId, + DWORD& pcbPackedAuthenticatorData, + wil::unique_hlocal_ptr& ppbpackedAuthenticatorData, + std::vector& vCredentialIdBuffer) + { + // Get the public key blob + DWORD cbPubKeyBlob = 0; + THROW_IF_FAILED(NCryptExportKey( + hKey.get(), + NULL, + BCRYPT_ECCPUBLIC_BLOB, + NULL, + NULL, + 0, + &cbPubKeyBlob, + 0)); + auto pbPubKeyBlob = std::make_unique(cbPubKeyBlob); + THROW_HR_IF(E_UNEXPECTED, pbPubKeyBlob == nullptr); + DWORD cbPubKeyBlobOutput = 0; + THROW_IF_FAILED(NCryptExportKey( + hKey.get(), + NULL, + BCRYPT_ECCPUBLIC_BLOB, + NULL, + pbPubKeyBlob.get(), + cbPubKeyBlob, + &cbPubKeyBlobOutput, + 0)); + + BCRYPT_ECCKEY_BLOB* pPubKeyBlobHeader = reinterpret_cast(pbPubKeyBlob.get()); + DWORD cbXCoord = pPubKeyBlobHeader->cbKey; + PBYTE pbXCoord = reinterpret_cast(&pPubKeyBlobHeader[1]); + DWORD cbYCoord = pPubKeyBlobHeader->cbKey; + PBYTE pbYCoord = pbXCoord + cbXCoord; + + // create byte span for x and y + std::span xCoord(pbXCoord, cbXCoord); + std::span yCoord(pbYCoord, cbYCoord); + + // CBOR encode the public key in this order: kty, alg, crv, x, y + std::vector buffer; + +#pragma warning(push) +#pragma warning(disable: 4293) + size_t bufferSize = CborLite::encodeMapSize(buffer, 5u); +#pragma warning(pop) + + // COSE CBOR encoding format. Refer to https://datatracker.ietf.org/doc/html/rfc9052#section-7 for more details. + const int8_t ktyIndex = 1; + const int8_t algIndex = 3; + const int8_t crvIndex = -1; + const int8_t xIndex = -2; + const int8_t yIndex = -3; + + // Example values for EC2 P-256 ES256 Keys. Refer to https://www.w3.org/TR/webauthn-3/#example-bdbd14cc + // Note that this sample authenticator only supports ES256 keys. + const int8_t kty = 2; // Key type is EC2 + const int8_t crv = 1; // Curve is P-256 + const int8_t alg = -7; // Algorithm is ES256 + + bufferSize += CborLite::encodeInteger(buffer, ktyIndex); + bufferSize += CborLite::encodeInteger(buffer, kty); + bufferSize += CborLite::encodeInteger(buffer, algIndex); + bufferSize += CborLite::encodeInteger(buffer, alg); + bufferSize += CborLite::encodeInteger(buffer, crvIndex); + bufferSize += CborLite::encodeInteger(buffer, crv); + bufferSize += CborLite::encodeInteger(buffer, xIndex); + bufferSize += CborLite::encodeBytes(buffer, xCoord); + bufferSize += CborLite::encodeInteger(buffer, yIndex); + bufferSize += CborLite::encodeBytes(buffer, yCoord); + + wil::unique_bcrypt_hash hashHandle; + THROW_IF_NTSTATUS_FAILED(BCryptCreateHash( + BCRYPT_SHA256_ALG_HANDLE, + &hashHandle, + nullptr, + 0, + nullptr, + 0, + 0)); + + THROW_IF_NTSTATUS_FAILED(BCryptHashData(hashHandle.get(), reinterpret_cast(pbXCoord), cbXCoord, 0)); + THROW_IF_NTSTATUS_FAILED(BCryptHashData(hashHandle.get(), reinterpret_cast(pbYCoord), cbYCoord, 0)); + + DWORD cbHash = 0; + DWORD bytesRead = 0; + THROW_IF_NTSTATUS_FAILED(BCryptGetProperty( + hashHandle.get(), + BCRYPT_HASH_LENGTH, + reinterpret_cast(&cbHash), + sizeof(cbHash), + &bytesRead, + 0)); + + wil::unique_hlocal_ptr pbCredentialId = wil::make_unique_hlocal(cbHash); + THROW_IF_NTSTATUS_FAILED(BCryptFinishHash(hashHandle.get(), pbCredentialId.get(), cbHash, 0)); + + // Close the key and hash handle + hKey.reset(); + hashHandle.reset(); + + com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as(); + PluginOperationType operationType = PLUGIN_OPERATION_TYPE_MAKE_CREDENTIAL; + if (curApp && + curApp->m_pluginOperationOptions.operationType == PLUGIN_OPERATION_TYPE_GET_ASSERTION) + { + operationType = PLUGIN_OPERATION_TYPE_GET_ASSERTION; + } + + // Refer to learn about packing credential data https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data + const DWORD rpidsha256Size = 32; // SHA256 hash of rpId + const DWORD flagsSize = 1; // flags + const DWORD signCountSize = 4; // signCount + DWORD cbPackedAuthenticatorData = rpidsha256Size + flagsSize + signCountSize; + + if (operationType == PLUGIN_OPERATION_TYPE_MAKE_CREDENTIAL) + { + cbPackedAuthenticatorData += sizeof(GUID); // aaGuid + cbPackedAuthenticatorData += sizeof(WORD); // credentialId length + cbPackedAuthenticatorData += cbHash; // credentialId + cbPackedAuthenticatorData += static_cast(buffer.size()); // public key + } + + std::vector vPackedAuthenticatorData(cbPackedAuthenticatorData); + auto writer = buffer_writer{ vPackedAuthenticatorData }; + + auto rgbRpIdHash = writer.reserve_space>(); // 32 bytes of rpIdHash which is SHA256 hash of rpName. https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data + DWORD cbRpIdHash; + THROW_IF_WIN32_BOOL_FALSE(CryptHashCertificate2(BCRYPT_SHA256_ALGORITHM, + 0, + nullptr, + pbRpId, + cbRpId, + rgbRpIdHash->data(), + &cbRpIdHash)); + + // Flags uv, up, be, and at are set + if (operationType == PLUGIN_OPERATION_TYPE_GET_ASSERTION) + { + // Refer https://www.w3.org/TR/webauthn-3/#authdata-flags + *writer.reserve_space() = 0x1d; // credential data flags of size 1 byte + + *writer.reserve_space() = 0u; // Sign count of size 4 bytes is set to 0 + + vCredentialIdBuffer.assign(pbCredentialId.get(), pbCredentialId.get() + cbHash); + } + else + { + // Refer https://www.w3.org/TR/webauthn-3/#authdata-flags + *writer.reserve_space() = 0x5d; // credential data flags of size 1 byte + + *writer.reserve_space() = 0u; // Sign count of size 4 bytes is set to 0 + + *writer.reserve_space() = GUID_NULL; // aaGuid of size 16 bytes is set to 0 + + // Retrieve credential id + WORD cbCredentialId = static_cast(cbHash); + WORD cbCredentialIdBigEndian = _byteswap_ushort(cbCredentialId); + + *writer.reserve_space() = cbCredentialIdBigEndian; // Size of credential id in unsigned big endian of size 2 bytes + + writer.add(std::span(pbCredentialId.get(), cbHash)); // Set credential id + + vCredentialIdBuffer.assign(pbCredentialId.get(), pbCredentialId.get() + cbHash); + + writer.add(std::span(buffer.data(), buffer.size())); // Set CBOR encoded public key + } + + pcbPackedAuthenticatorData = static_cast(vPackedAuthenticatorData.size()); + ppbpackedAuthenticatorData = wil::make_unique_hlocal(pcbPackedAuthenticatorData); + memcpy_s(ppbpackedAuthenticatorData.get(), pcbPackedAuthenticatorData, vPackedAuthenticatorData.data(), pcbPackedAuthenticatorData); + + return S_OK; + } + + /* + * This function is invoked by the platform to request the plugin to handle a make credential operation. + * Refer: pluginauthenticator.h/pluginauthenticator.idl + */ + HRESULT STDMETHODCALLTYPE ContosoPlugin::EXPERIMENTAL_PluginMakeCredential( + /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST pPluginMakeCredentialRequest, + /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE* response) noexcept + { + try + { + SetEvent(App::s_pluginOpRequestRecievedEvent.get()); // indicate COM message received + DWORD hIndex = 0; + RETURN_IF_FAILED(CoWaitForMultipleHandles( // wait for app to be ready + COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, + INFINITE, + 1, + App::s_hAppReadyForPluginOpEvent.addressof(), + &hIndex)); + com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as(); + + wil::shared_hmodule webauthnDll(LoadLibraryExW(L"webauthn.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)); + if (webauthnDll == nullptr) + { + return E_ABORT; + } + + wil::unique_cotaskmem_ptr pDecodedMakeCredentialRequest; + auto webauthnDecodeMakeCredentialRequest = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequest); + THROW_IF_FAILED(webauthnDecodeMakeCredentialRequest( + pPluginMakeCredentialRequest->cbEncodedRequest, + pPluginMakeCredentialRequest->pbEncodedRequest, + wil::out_param(pDecodedMakeCredentialRequest))); + auto rpName = wil::make_cotaskmem_string(pDecodedMakeCredentialRequest->pRpInformation->pwszName); + auto userName = wil::make_cotaskmem_string(pDecodedMakeCredentialRequest->pUserInformation->pwszName); + std::vector requestBuffer( + pPluginMakeCredentialRequest->pbEncodedRequest, + pPluginMakeCredentialRequest->pbEncodedRequest + pPluginMakeCredentialRequest->cbEncodedRequest); + + auto ppbPubKeyData = GetRequestSigningPubKey(); + HRESULT requestSignResult = E_FAIL; + if (!ppbPubKeyData.empty()) + { + requestSignResult = VerifySignatureHelper( + requestBuffer, + ppbPubKeyData.data(), + static_cast(ppbPubKeyData.size()), + pPluginMakeCredentialRequest->pbRequestSignature, + pPluginMakeCredentialRequest->cbRequestSignature); + } + { + std::lock_guard lock(curApp->m_pluginOperationOptionsMutex); + curApp->m_pluginOperationStatus.requestSignatureVerificationStatus = requestSignResult; + } + + THROW_IF_FAILED(PerformUv(curApp, + pPluginMakeCredentialRequest->hWnd, + webauthnDll, + pPluginMakeCredentialRequest->transactionId, + PLUGIN_OPERATION_TYPE_MAKE_CREDENTIAL, + requestBuffer, + std::move(rpName), + std::move(userName))); + + //create a persisted key using ncrypt + wil::unique_ncrypt_prov hProvider; + wil::unique_ncrypt_key hKey; + + // get the provider + THROW_IF_FAILED(NCryptOpenStorageProvider(&hProvider, nullptr, 0)); + + // get the user handle as a string + std::wstring keyNameStr = contosoplugin_key_domain; + std::wstringstream keyNameStream; + for (DWORD idx = 0; idx < pDecodedMakeCredentialRequest->pUserInformation->cbId; idx++) + { + keyNameStream << std::hex << std::setw(2) << std::setfill(L'0') << + static_cast(pDecodedMakeCredentialRequest->pUserInformation->pbId[idx]); + } + keyNameStr += keyNameStream.str(); + + // create the key + THROW_IF_FAILED(NCryptCreatePersistedKey( + hProvider.get(), + &hKey, + BCRYPT_ECDH_P256_ALGORITHM, + keyNameStr.c_str(), + 0, + NCRYPT_OVERWRITE_KEY_FLAG)); + + // set the export policy + DWORD exportPolicy = NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG; + THROW_IF_FAILED(NCryptSetProperty( + hKey.get(), + NCRYPT_EXPORT_POLICY_PROPERTY, + reinterpret_cast(&exportPolicy), + sizeof(exportPolicy), + NCRYPT_PERSIST_FLAG)); + + // allow both signing and encryption + DWORD keyUsage = NCRYPT_ALLOW_SIGNING_FLAG | NCRYPT_ALLOW_DECRYPT_FLAG; + THROW_IF_FAILED(NCryptSetProperty( + hKey.get(), + NCRYPT_KEY_USAGE_PROPERTY, + reinterpret_cast(&keyUsage), + sizeof(keyUsage), + NCRYPT_PERSIST_FLAG)); + HWND hWnd; + if (curApp->m_silentMode) + { + hWnd = curApp->m_pluginOperationOptions.hWnd; + } + else + { + hWnd = curApp->GetNativeWindowHandle(); + } + THROW_IF_FAILED(NCryptSetProperty( + hKey.get(), + NCRYPT_WINDOW_HANDLE_PROPERTY, + reinterpret_cast(&hWnd), + sizeof(HWND), + 0)); + + // finalize the key + THROW_IF_FAILED(NCryptFinalizeKey(hKey.get(), 0)); + + DWORD cbPackedAuthenticatorData = 0; + wil::unique_hlocal_ptr packedAuthenticatorData; + std::vector vCredentialIdBuffer; + THROW_IF_FAILED(CreateAuthenticatorData( + std::move(hKey), + pDecodedMakeCredentialRequest->cbRpId, + pDecodedMakeCredentialRequest->pbRpId, + cbPackedAuthenticatorData, + packedAuthenticatorData, + vCredentialIdBuffer)); + + auto operationResponse = wil::make_unique_cotaskmem(); + + WEBAUTHN_CREDENTIAL_ATTESTATION attestationResponse{}; + attestationResponse.dwVersion = WEBAUTHN_CREDENTIAL_ATTESTATION_CURRENT_VERSION; + attestationResponse.pwszFormatType = WEBAUTHN_ATTESTATION_TYPE_NONE; + attestationResponse.cbAttestation = 0; + attestationResponse.pbAttestation = nullptr; + attestationResponse.cbAuthenticatorData = 0; + attestationResponse.pbAuthenticatorData = nullptr; + + attestationResponse.pbAuthenticatorData = packedAuthenticatorData.get(); + attestationResponse.cbAuthenticatorData = cbPackedAuthenticatorData; + + DWORD cbAttestationBuffer = 0; + PBYTE pbattestationBuffer; + + auto webauthnEncodeMakeCredentialResponse = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNEncodeMakeCredentialResponse); + THROW_IF_FAILED(webauthnEncodeMakeCredentialResponse( + &attestationResponse, + &cbAttestationBuffer, + &pbattestationBuffer)); + operationResponse->cbEncodedResponse = cbAttestationBuffer; + operationResponse->pbEncodedResponse = wil::make_unique_cotaskmem(cbAttestationBuffer).release(); + memcpy_s(operationResponse->pbEncodedResponse, + operationResponse->cbEncodedResponse, + pbattestationBuffer, + cbAttestationBuffer); + + *response = operationResponse.release(); + + WEBAUTHN_CREDENTIAL_DETAILS credentialDetails{}; + credentialDetails.dwVersion = WEBAUTHN_CREDENTIAL_DETAILS_CURRENT_VERSION; + credentialDetails.pUserInformation = const_cast(pDecodedMakeCredentialRequest->pUserInformation); + credentialDetails.pRpInformation = const_cast(pDecodedMakeCredentialRequest->pRpInformation); + credentialDetails.cbCredentialID = static_cast(vCredentialIdBuffer.size()); + credentialDetails.pbCredentialID = wil::make_unique_cotaskmem(vCredentialIdBuffer.size()).release(); + memcpy_s(credentialDetails.pbCredentialID, credentialDetails.cbCredentialID, vCredentialIdBuffer.data(), static_cast(vCredentialIdBuffer.size())); + if (!PluginCredentialManager::getInstance().SaveCredentialMetadataToMockDB(credentialDetails)) + { + std::lock_guard lock(curApp->m_pluginOperationOptionsMutex); + curApp->m_pluginOperationStatus.performOperationStatus = E_FAIL; + } + pDecodedMakeCredentialRequest.reset(); + SetEvent(App::s_hPluginOpCompletedEvent.get()); + return S_OK; + } + catch (...) + { + HRESULT hr = wil::ResultFromCaughtException(); + com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as(); + if (curApp) + { + hr = winrt::to_hresult(); + std::lock_guard lock(curApp->m_pluginOperationOptionsMutex); + curApp->m_pluginOperationStatus.performOperationStatus = hr; + }; + SetEvent(App::s_hPluginOpCompletedEvent.get()); + return hr; + } + } + + /* + * This function is invoked by the platform to request the plugin to handle a get assertion operation. + * Refer: pluginauthenticator.h/pluginauthenticator.idl + */ + HRESULT STDMETHODCALLTYPE ContosoPlugin::EXPERIMENTAL_PluginGetAssertion( + /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST pPluginGetAssertionRequest, + /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE* response) noexcept + { + try + { + SetEvent(App::s_pluginOpRequestRecievedEvent.get()); + DWORD hIndex = 0; + RETURN_IF_FAILED(CoWaitForMultipleHandles( + COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, + INFINITE, + 1, + App::s_hAppReadyForPluginOpEvent.addressof(), + &hIndex)); + com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as(); + + wil::shared_hmodule webauthnDll(LoadLibraryExW(L"webauthn.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)); + if (webauthnDll == nullptr) + { + return E_ABORT; + } + + wil::unique_cotaskmem_ptr pDecodedAssertionRequest; + // The EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest function can be optionally used to decode the CBOR encoded request to a EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST structure. + auto webauthnDecodeGetAssertionRequest = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest); + webauthnDecodeGetAssertionRequest(pPluginGetAssertionRequest->cbEncodedRequest, pPluginGetAssertionRequest->pbEncodedRequest, wil::out_param(pDecodedAssertionRequest)); + wil::shared_cotaskmem_string rpName = wil::make_cotaskmem_string(pDecodedAssertionRequest->pwszRpId); + //load the user handle + auto& credManager = PluginCredentialManager::getInstance(); + const WEBAUTHN_CREDENTIAL_DETAILS* selectedCredential{}; + // create a list of credentials + std::vector selectedCredentials; + + while (true) + { + Sleep(100); + if (credManager.IsLocalCredentialMetadataLoaded()) + { + credManager.GetLocalCredsByRpIdAndAllowList(pDecodedAssertionRequest->pwszRpId, + pDecodedAssertionRequest->CredentialList.ppCredentials, + pDecodedAssertionRequest->CredentialList.cCredentials, + selectedCredentials); + break; + } + } + + if (selectedCredentials.empty()) + { + { + std::lock_guard lock(curApp->m_pluginOperationOptionsMutex); + curApp->m_pluginOperationStatus.performOperationStatus = NTE_NOT_FOUND; + } + SetEvent(App::s_hPluginOpCompletedEvent.get()); + return NTE_NOT_FOUND; + } + else if (selectedCredentials.size() == 1 && credManager.GetSilentOperation()) + { + selectedCredential = selectedCredentials[0]; + } + else + { + curApp->SetMatchingCredentials(pDecodedAssertionRequest->pwszRpId, selectedCredentials, pPluginGetAssertionRequest->hWnd); + hIndex = 0; + RETURN_IF_FAILED(CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, INFINITE, 1, curApp->m_hPluginCredentialSelected.addressof(), &hIndex)); + + { + std::lock_guard lock(curApp->m_pluginOperationOptionsMutex); + selectedCredential = curApp->m_pluginOperationOptions.selectedCredential; + } + + // Failed to select a credential + if (selectedCredential->cbCredentialID == 0 || + selectedCredential->pbCredentialID == nullptr || + selectedCredential->pUserInformation == nullptr || + selectedCredential->pUserInformation->pwszName == nullptr) + { + { + std::lock_guard lock(curApp->m_pluginOperationOptionsMutex); + curApp->m_pluginOperationStatus.performOperationStatus = NTE_NOT_FOUND; + } + SetEvent(App::s_hPluginOpCompletedEvent.get()); + return NTE_NOT_FOUND; + } + } + + wil::shared_cotaskmem_string userName = wil::make_cotaskmem_string(selectedCredential->pUserInformation->pwszName); + + std::vector requestBuffer( + pPluginGetAssertionRequest->pbEncodedRequest, + pPluginGetAssertionRequest->pbEncodedRequest + pPluginGetAssertionRequest->cbEncodedRequest); + + auto ppbPubKeyData = GetRequestSigningPubKey(); + HRESULT requestSignResult = E_FAIL; + if (!ppbPubKeyData.empty()) + { + requestSignResult = VerifySignatureHelper( + requestBuffer, + ppbPubKeyData.data(), + static_cast(ppbPubKeyData.size()), + pPluginGetAssertionRequest->pbRequestSignature, + pPluginGetAssertionRequest->cbRequestSignature); + } + + { + std::lock_guard lock(curApp->m_pluginOperationOptionsMutex); + curApp->m_pluginOperationStatus.requestSignatureVerificationStatus = requestSignResult; + } + + THROW_IF_FAILED(PerformUv(curApp, + pPluginGetAssertionRequest->hWnd, + webauthnDll, + pPluginGetAssertionRequest->transactionId, + PLUGIN_OPERATION_TYPE_GET_ASSERTION, + requestBuffer, + rpName, + userName)); + + // convert user handle to a string + std::wstring keyNameStr = contosoplugin_key_domain; + std::wstringstream keyNameStream; + for (DWORD idx = 0; idx < selectedCredential->pUserInformation->cbId; idx++) + { + keyNameStream << std::hex << std::setw(2) << std::setfill(L'0') << + static_cast(selectedCredential->pUserInformation->pbId[idx]); + } + keyNameStr += keyNameStream.str(); + + //open the key using ncrypt and sign the data + wil::unique_ncrypt_prov hProvider; + wil::shared_ncrypt_key hKey; + + // get the provider + THROW_IF_FAILED(NCryptOpenStorageProvider(&hProvider, nullptr, 0)); + + // open the key + THROW_IF_FAILED(NCryptOpenKey(hProvider.get(), &hKey, keyNameStr.c_str(), 0, 0)); + + // set hwnd property + wil::unique_hwnd hWnd; + if (curApp->m_silentMode) + { + hWnd.reset(curApp->m_pluginOperationOptions.hWnd); + } + else + { + hWnd.reset(curApp->GetNativeWindowHandle()); + } + THROW_IF_FAILED(NCryptSetProperty( + hKey.get(), + NCRYPT_WINDOW_HANDLE_PROPERTY, + (BYTE*)(hWnd.addressof()), + sizeof(HWND), + 0)); + + // create authenticator data + DWORD cbPackedAuthenticatorData = 0; + wil::unique_hlocal_ptr packedAuthenticatorData; + std::vector vCredentialIdBuffer; + THROW_IF_FAILED(CreateAuthenticatorData(hKey, + pDecodedAssertionRequest->cbRpId, + pDecodedAssertionRequest->pbRpId, + cbPackedAuthenticatorData, + packedAuthenticatorData, + vCredentialIdBuffer)); + + wil::unique_hlocal_ptr pbSignature = nullptr; + DWORD cbSignature = 0; + + { + wil::unique_bcrypt_hash hashHandle; + + + THROW_IF_NTSTATUS_FAILED(BCryptCreateHash( + BCRYPT_SHA256_ALG_HANDLE, + &hashHandle, + nullptr, + 0, + nullptr, + 0, + 0)); + + THROW_IF_NTSTATUS_FAILED(BCryptHashData(hashHandle.get(), const_cast(packedAuthenticatorData.get()), cbPackedAuthenticatorData, 0)); + THROW_IF_NTSTATUS_FAILED(BCryptHashData(hashHandle.get(), const_cast(pDecodedAssertionRequest->pbClientDataHash), pDecodedAssertionRequest->cbClientDataHash, 0)); + + DWORD bytesRead = 0; + DWORD cbSignatureBuffer = 0; + THROW_IF_NTSTATUS_FAILED(BCryptGetProperty( + hashHandle.get(), + BCRYPT_HASH_LENGTH, + reinterpret_cast(&cbSignatureBuffer), + sizeof(cbSignatureBuffer), + &bytesRead, + 0)); + + wil::unique_hlocal_ptr signatureBuffer = wil::make_unique_hlocal(cbSignatureBuffer); + THROW_HR_IF(E_UNEXPECTED, signatureBuffer == nullptr); + THROW_IF_NTSTATUS_FAILED(BCryptFinishHash(hashHandle.get(), signatureBuffer.get(), cbSignatureBuffer, 0)); + + // sign the data + THROW_IF_FAILED(NCryptSignHash(hKey.get(), nullptr, signatureBuffer.get(), cbSignatureBuffer, nullptr, 0, &cbSignature, 0)); + + pbSignature = wil::make_unique_hlocal(cbSignature); + THROW_HR_IF(E_UNEXPECTED, pbSignature == nullptr); + + THROW_IF_FAILED(NCryptSignHash(hKey.get(), nullptr, signatureBuffer.get(), cbSignatureBuffer, pbSignature.get(), cbSignature, &cbSignature, 0)); + signatureBuffer.reset(); + + auto encodeSignature = [](PBYTE signature, size_t signatureSize) + { + std::vector encodedSignature{}; + encodedSignature.push_back(0x02); // ASN integer tag + encodedSignature.push_back(static_cast(signatureSize)); // length of the signature + if (WI_IsFlagSet(signature[0], 0x80)) + { + encodedSignature[encodedSignature.size() - 1]++; + encodedSignature.push_back(0x00); // add a padding byte if the first byte has the high bit set + } + + encodedSignature.insert(encodedSignature.end(), signature, signature + signatureSize); + return encodedSignature; + }; + + auto signatureR = encodeSignature(pbSignature.get(), cbSignature / 2); + auto signatureS = encodeSignature(pbSignature.get() + cbSignature / 2, cbSignature / 2); + + std::vector encodedSignature{}; + encodedSignature.push_back(0x30); // ASN sequence tag + encodedSignature.push_back(static_cast(signatureR.size() + signatureS.size())); // length of the sequence + encodedSignature.insert(encodedSignature.end(), signatureR.begin(), signatureR.end()); + encodedSignature.insert(encodedSignature.end(), signatureS.begin(), signatureS.end()); + + cbSignature = static_cast(encodedSignature.size()); + pbSignature.reset(); + pbSignature = wil::make_unique_hlocal(cbSignature); + THROW_HR_IF(E_UNEXPECTED, pbSignature == nullptr); + memcpy_s(pbSignature.get(), cbSignature, encodedSignature.data(), static_cast(cbSignature)); + } + + // create the response + auto operationResponse = wil::make_unique_cotaskmem(); + + auto assertionResponse = wil::make_unique_cotaskmem(); + assertionResponse->dwVersion = WEBAUTHN_ASSERTION_CURRENT_VERSION; + + // [1] Credential (optional) + assertionResponse->Credential.dwVersion = WEBAUTHN_CREDENTIAL_CURRENT_VERSION; + assertionResponse->Credential.cbId = static_cast(vCredentialIdBuffer.size()); + assertionResponse->Credential.pbId = vCredentialIdBuffer.data(); + assertionResponse->Credential.pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + + // [2] AuthenticatorData + assertionResponse->cbAuthenticatorData = cbPackedAuthenticatorData; + assertionResponse->pbAuthenticatorData = packedAuthenticatorData.get(); + + // [3] Signature + assertionResponse->cbSignature = cbSignature; + assertionResponse->pbSignature = pbSignature.get(); + + // [4] User (optional) + assertionResponse->cbUserId = selectedCredential->pUserInformation->cbId; + auto userIdBuffer = wil::make_unique_cotaskmem(selectedCredential->pUserInformation->cbId); + memcpy_s(userIdBuffer.get(), + selectedCredential->pUserInformation->cbId, + selectedCredential->pUserInformation->pbId, + selectedCredential->pUserInformation->cbId); + assertionResponse->pbUserId = userIdBuffer.get(); + WEBAUTHN_USER_ENTITY_INFORMATION userEntityInformation{}; + userEntityInformation.dwVersion = WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION; + userEntityInformation.cbId = assertionResponse->cbUserId; + userEntityInformation.pbId = assertionResponse->pbUserId; + + auto ctapGetAssertionResponse = wil::make_unique_cotaskmem(); + ctapGetAssertionResponse->WebAuthNAssertion = *(assertionResponse.get()); // [1] Credential, [2] AuthenticatorData, [3] Signature + ctapGetAssertionResponse->pUserInformation = &userEntityInformation; // [4] User + ctapGetAssertionResponse->dwNumberOfCredentials = 1; // [5] NumberOfCredentials + + DWORD cbAssertionBuffer = 0; + PBYTE pbAssertionBuffer; + + // The EXPERIMENTAL_WebAuthNEncodeGetAssertionResponse function can be optionally used to encode the + // EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE structure to a CBOR encoded response. + auto webAuthNEncodeGetAssertionResponse = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNEncodeGetAssertionResponse); + THROW_IF_FAILED(webAuthNEncodeGetAssertionResponse( + (EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE)(ctapGetAssertionResponse.get()), + &cbAssertionBuffer, + &pbAssertionBuffer)); + + assertionResponse.reset(); + ctapGetAssertionResponse.reset(); + userIdBuffer.reset(); + packedAuthenticatorData.reset(); + pbSignature.reset(); + pDecodedAssertionRequest.reset(); + + operationResponse->cbEncodedResponse = cbAssertionBuffer; + // pbEncodedResponse must contain a CBOR encoded response as specified the FIDO CTAP. + // Refer: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#message-encoding. + operationResponse->pbEncodedResponse = wil::make_unique_cotaskmem(cbAssertionBuffer).release(); + memcpy_s( + operationResponse->pbEncodedResponse, + operationResponse->cbEncodedResponse, + pbAssertionBuffer, + cbAssertionBuffer); + + *response = operationResponse.release(); + SetEvent(App::s_hPluginOpCompletedEvent.get()); + return S_OK; + } + catch (...) + { + HRESULT localHr = wil::ResultFromCaughtException(); + { + winrt::com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as(); + std::lock_guard lock(curApp->m_pluginOperationOptionsMutex); + curApp->m_pluginOperationStatus.performOperationStatus = localHr; + } + SetEvent(App::s_hPluginOpCompletedEvent.get()); + return localHr; + } + } + + /* + * This function is invoked by the platform to request the plugin to cancel an ongoing operation. + */ + HRESULT STDMETHODCALLTYPE ContosoPlugin::EXPERIMENTAL_PluginCancelOperation( + /* [out] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST) + { + SetEvent(App::s_pluginOpRequestRecievedEvent.get()); + com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as(); + curApp->GetDispatcherQueue().TryEnqueue([curApp]() + { + curApp->PluginCancelAction(); + }); + return S_OK; + } + + /* + * This is a sample implementation of a factory method that creates an instance of the Class that implements the EXPERIMENTAL_IPluginAuthenticator interface. + * Refer: pluginauthenticator.h/pluginauthenticator.idl for the interface definition. + */ + HRESULT __stdcall ContosoPluginFactory::CreateInstance( + ::IUnknown* outer, + GUID const& iid, + void** result) noexcept + { + *result = nullptr; + + if (outer) + { + return CLASS_E_NOAGGREGATION; + } + + try + { + return make()->QueryInterface(iid, result); + } + catch (...) + { + return winrt::to_hresult(); + } + } + + HRESULT __stdcall ContosoPluginFactory::LockServer(BOOL) noexcept + { + return S_OK; + } + +} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs new file mode 100644 index 00000000000..a306d42f60b --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs @@ -0,0 +1,249 @@ +use std::alloc::{alloc, Layout}; +use std::ptr; +use serde_json; +use windows_core::{HRESULT, s}; + +use crate::types::*; +use crate::utils::{self as util, delay_load}; +use crate::com_provider::ExperimentalWebAuthnPluginOperationResponse; + +// Windows API types for WebAuthn (from webauthn.h.sample) +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST { + pub dwVersion: u32, + pub pwszRpId: *const u16, // PCWSTR + pub cbRpId: u32, + pub pbRpId: *const u8, + pub cbClientDataHash: u32, + pub pbClientDataHash: *const u8, + pub CredentialList: WEBAUTHN_CREDENTIAL_LIST, + pub cbCborExtensionsMap: u32, + pub pbCborExtensionsMap: *const u8, + // Add other fields as needed... +} + +pub type PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST = *mut EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_CREDENTIAL_LIST { + pub cCredentials: u32, + pub pCredentials: *const u8, // Placeholder +} + +// Windows API function signatures for decoding get assertion requests +type EXPERIMENTAL_WebAuthNDecodeGetAssertionRequestFn = unsafe extern "stdcall" fn( + cbEncoded: u32, + pbEncoded: *const u8, + ppGetAssertionRequest: *mut PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, +) -> HRESULT; + +type EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequestFn = unsafe extern "stdcall" fn( + pGetAssertionRequest: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, +); + +// RAII wrapper for decoded get assertion request +pub struct DecodedGetAssertionRequest { + ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, + free_fn: Option, +} + +impl DecodedGetAssertionRequest { + fn new(ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, free_fn: Option) -> Self { + Self { ptr, free_fn } + } + + pub fn as_ref(&self) -> &EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST { + unsafe { &*self.ptr } + } +} + +impl Drop for DecodedGetAssertionRequest { + fn drop(&mut self) { + if !self.ptr.is_null() { + if let Some(free_fn) = self.free_fn { + util::message("Freeing decoded get assertion request"); + unsafe { free_fn(self.ptr); } + } + } + } +} + +// Function to decode get assertion request using Windows API +pub unsafe fn decode_get_assertion_request(encoded_request: &[u8]) -> Result { + util::message("Attempting to decode get assertion request using Windows API"); + + // Load the Windows WebAuthn API function + let decode_fn: Option = delay_load( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest") + ); + + let decode_fn = decode_fn.ok_or("Failed to load EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest from webauthn.dll")?; + + // Load the free function + let free_fn: Option = delay_load( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequest") + ); + + let mut pp_get_assertion_request: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST = ptr::null_mut(); + + let result = decode_fn( + encoded_request.len() as u32, + encoded_request.as_ptr(), + &mut pp_get_assertion_request, + ); + + if result.is_err() || pp_get_assertion_request.is_null() { + return Err(format!("EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest failed with HRESULT: {}", result.0)); + } + + + Ok(DecodedGetAssertionRequest::new(pp_get_assertion_request, free_fn)) +} + +/// Context information parsed from the incoming request +#[derive(Debug, Clone)] +pub struct RequestContext { + pub rpid: Option, + pub allowed_credentials: Vec>, + pub user_verification: Option, + pub user_id: Option>, + pub user_name: Option, + pub user_display_name: Option, + pub client_data_hash: Option>, + pub supported_algorithms: Vec, // COSE algorithm identifiers +} + +impl Default for RequestContext { + fn default() -> Self { + Self { + rpid: None, + allowed_credentials: Vec::new(), + user_verification: None, + user_id: None, + user_name: None, + user_display_name: None, + client_data_hash: None, + supported_algorithms: Vec::new(), + } + } +} + +/// Helper for assertion requests +pub fn send_assertion_request(rpid: &str, transaction_id: &str, context: &RequestContext) -> Option { + // Extract client data hash from context - this is required for WebAuthn + let client_data_hash = match &context.client_data_hash { + Some(hash) if !hash.is_empty() => hash.clone(), + _ => { + util::message("ERROR: Client data hash is required for assertion but not provided"); + return None; + } + }; + + let request = PasskeyAssertionRequest { + rp_id: rpid.to_string(), + transaction_id: transaction_id.to_string(), + client_data_hash, + allowed_credentials: context.allowed_credentials.clone(), + user_verification: context.user_verification.unwrap_or(false), + }; + + util::message(&format!("Assertion request data - RP ID: {}, Client data hash: {} bytes, Allowed credentials: {}", + rpid, request.client_data_hash.len(), request.allowed_credentials.len())); + + match serde_json::to_string(&request) { + Ok(request_json) => { + util::message(&format!("Sending assertion request: {}", request_json)); + crate::ipc::send_passkey_request(RequestType::Assertion, request_json, rpid) + }, + Err(e) => { + util::message(&format!("ERROR: Failed to serialize assertion request: {}", e)); + None + } + } +} + +/// Creates a WebAuthn get assertion response from Bitwarden's assertion response +pub unsafe fn create_get_assertion_response( + credential_id: Vec, + authenticator_data: Vec, + signature: Vec, + user_handle: Vec +) -> std::result::Result<*mut ExperimentalWebAuthnPluginOperationResponse, HRESULT> { + // Construct a CTAP2 response with the proper structure + + // Create CTAP2 GetAssertion response map according to CTAP2 specification + let mut cbor_response: Vec<(ciborium::Value, ciborium::Value)> = Vec::new(); + + // [1] credential (optional) - Always include credential descriptor + let credential_map = vec![ + (ciborium::Value::Text("id".to_string()), ciborium::Value::Bytes(credential_id.clone())), + (ciborium::Value::Text("type".to_string()), ciborium::Value::Text("public-key".to_string())), + ]; + cbor_response.push(( + ciborium::Value::Integer(1.into()), + ciborium::Value::Map(credential_map) + )); + + // [2] authenticatorData (required) + cbor_response.push(( + ciborium::Value::Integer(2.into()), + ciborium::Value::Bytes(authenticator_data) + )); + + // [3] signature (required) + cbor_response.push(( + ciborium::Value::Integer(3.into()), + ciborium::Value::Bytes(signature) + )); + + // [4] user (optional) - include if user handle is provided + if !user_handle.is_empty() { + let user_map = vec![ + (ciborium::Value::Text("id".to_string()), ciborium::Value::Bytes(user_handle)), + ]; + cbor_response.push(( + ciborium::Value::Integer(4.into()), + ciborium::Value::Map(user_map) + )); + } + + let cbor_value = ciborium::Value::Map(cbor_response); + + // Encode to CBOR with error handling + let mut cbor_data = Vec::new(); + if let Err(e) = ciborium::ser::into_writer(&cbor_value, &mut cbor_data) { + util::message(&format!("ERROR: Failed to encode CBOR assertion response: {:?}", e)); + return Err(HRESULT(-1)); + } + + let response_len = cbor_data.len(); + + // Allocate memory for the response data + let layout = Layout::from_size_align(response_len, 1).map_err(|_| HRESULT(-1))?; + let response_ptr = alloc(layout); + if response_ptr.is_null() { + return Err(HRESULT(-1)); + } + + // Copy response data + ptr::copy_nonoverlapping(cbor_data.as_ptr(), response_ptr, response_len); + + // Allocate memory for the response structure + let response_layout = Layout::new::(); + let operation_response_ptr = alloc(response_layout) as *mut ExperimentalWebAuthnPluginOperationResponse; + if operation_response_ptr.is_null() { + return Err(HRESULT(-1)); + } + + // Initialize the response + ptr::write(operation_response_ptr, ExperimentalWebAuthnPluginOperationResponse { + encoded_response_byte_count: response_len as u32, + encoded_response_pointer: response_ptr, + }); + + Ok(operation_response_ptr) +} \ No newline at end of file diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_buffer.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_buffer.rs new file mode 100644 index 00000000000..af34107a050 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_buffer.rs @@ -0,0 +1,84 @@ +use std::alloc; +use std::mem::{align_of, MaybeUninit}; +use std::ptr::NonNull; +use windows::Win32::System::Com::CoTaskMemAlloc; + +#[repr(transparent)] +pub struct ComBuffer(NonNull>); + +impl ComBuffer { + /// Returns an COM-allocated buffer of `size`. + fn alloc(size: usize, for_slice: bool) -> Self { + #[expect(clippy::as_conversions)] + { + assert!(size <= isize::MAX as usize, "requested bad object size"); + } + + // SAFETY: Any size is valid to pass to Windows, even `0`. + let ptr = NonNull::new(unsafe { CoTaskMemAlloc(size) }).unwrap_or_else(|| { + // XXX: This doesn't have to be correct, just close enough for an OK OOM error. + let layout = alloc::Layout::from_size_align(size, align_of::()).unwrap(); + alloc::handle_alloc_error(layout) + }); + + if for_slice { + // Ininitialize the buffer so it can later be treated as `&mut [u8]`. + // SAFETY: The pointer is valid and we are using a valid value for a byte-wise allocation. + unsafe { ptr.write_bytes(0, size) }; + } + + Self(ptr.cast()) + } + + fn into_ptr(self) -> *mut T { + self.0.cast().as_ptr() + } + + /// Creates a new COM-allocated structure. + /// + /// Note that `T` must be [Copy] to avoid any possible memory leaks. + pub fn with_object(object: T) -> *mut T { + // NB: Vendored from Rust's alloc code since we can't yet allocate `Box` with a custom allocator. + const MIN_ALIGN: usize = if cfg!(target_pointer_width = "64") { + 16 + } else if cfg!(target_pointer_width = "32") { + 8 + } else { + panic!("unsupported arch") + }; + + // SAFETY: Validate that our alignment works for a normal size-based allocation for soundness. + let layout = const { + let layout = alloc::Layout::new::(); + assert!(layout.align() <= MIN_ALIGN); + layout + }; + + let buffer = Self::alloc(layout.size(), false); + // SAFETY: `ptr` is valid for writes of `T` because we correctly allocated the right sized buffer that + // accounts for any alignment requirements. + // + // Additionally, we ensure the value is treated as moved by forgetting the source. + unsafe { buffer.0.cast::().write(object) }; + buffer.into_ptr() + } + + pub fn from_buffer>(buffer: T) -> (*mut u8, u32) { + let buffer = buffer.as_ref(); + let len = buffer.len(); + let com_buffer = Self::alloc(len, true); + + // SAFETY: `ptr` points to a valid allocation that `len` matches, and we made sure + // the bytes were initialized. Additionally, bytes have no alignment requirements. + unsafe { + NonNull::slice_from_raw_parts(com_buffer.0.cast::(), len) + .as_mut() + .copy_from_slice(buffer) + } + + // Safety: The Windows API structures these buffers are placed into use `u32` (`DWORD`) to + // represent length. + #[expect(clippy::as_conversions)] + (com_buffer.into_ptr(), len as u32) + } +} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs new file mode 100644 index 00000000000..55a9b23e240 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs @@ -0,0 +1,417 @@ +use windows::Win32::System::Com::*; +use windows_core::{implement, interface, IInspectable, IUnknown, Interface, HRESULT}; +use std::ptr; + +use crate::types::*; +use crate::utils::{self as util, wstr_to_string}; +use crate::assert::{RequestContext, decode_get_assertion_request, create_get_assertion_response, send_assertion_request}; +use crate::make_credential::{decode_make_credential_request, create_make_credential_response, send_registration_request}; + +/// Used when creating and asserting credentials. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST +/// Header File Usage: EXPERIMENTAL_PluginMakeCredential() +/// EXPERIMENTAL_PluginGetAssertion() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginOperationRequest { + pub window_handle: windows::Win32::Foundation::HWND, + pub transaction_id: windows_core::GUID, + pub request_signature_byte_count: u32, + pub request_signature_pointer: *mut u8, + pub encoded_request_byte_count: u32, + pub encoded_request_pointer: *mut u8, +} + +/// Used as a response when creating and asserting credentials. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE +/// Header File Usage: EXPERIMENTAL_PluginMakeCredential() +/// EXPERIMENTAL_PluginGetAssertion() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginOperationResponse { + pub encoded_response_byte_count: u32, + pub encoded_response_pointer: *mut u8, +} + +/// Used to cancel an operation. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST +/// Header File Usage: EXPERIMENTAL_PluginCancelOperation() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginCancelOperationRequest { + pub transaction_id: windows_core::GUID, + pub request_signature_byte_count: u32, + pub request_signature_pointer: *mut u8, +} + +#[interface("e6466e9a-b2f3-47c5-b88d-89bc14a8d998")] +pub unsafe trait EXPERIMENTAL_IPluginAuthenticator: windows_core::IUnknown { + fn EXPERIMENTAL_PluginMakeCredential( + &self, + request: *const ExperimentalWebAuthnPluginOperationRequest, + response: *mut *mut ExperimentalWebAuthnPluginOperationResponse, + ) -> HRESULT; + fn EXPERIMENTAL_PluginGetAssertion( + &self, + request: *const ExperimentalWebAuthnPluginOperationRequest, + response: *mut *mut ExperimentalWebAuthnPluginOperationResponse, + ) -> HRESULT; + fn EXPERIMENTAL_PluginCancelOperation( + &self, + request: *const ExperimentalWebAuthnPluginCancelOperationRequest, + ) -> HRESULT; +} + +#[implement(EXPERIMENTAL_IPluginAuthenticator)] +pub struct PluginAuthenticatorComObject; + +#[implement(IClassFactory)] +pub struct Factory; + +impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl { + unsafe fn EXPERIMENTAL_PluginMakeCredential( + &self, + request: *const ExperimentalWebAuthnPluginOperationRequest, + response: *mut *mut ExperimentalWebAuthnPluginOperationResponse, + ) -> HRESULT { + util::message("=== EXPERIMENTAL_PluginMakeCredential() called ==="); + + if request.is_null() { + util::message("ERROR: NULL request pointer"); + return HRESULT(-1); + } + + if response.is_null() { + util::message("ERROR: NULL response pointer"); + return HRESULT(-1); + } + + let req = &*request; + let transaction_id = format!("{:?}", req.transaction_id); + + util::message(&format!("Transaction ID: {}", transaction_id)); + util::message(&format!("Window Handle: {:?}", req.window_handle)); + util::message(&format!("Request Signature Byte Count: {}", req.request_signature_byte_count)); + util::message(&format!("Encoded Request Byte Count: {}", req.encoded_request_byte_count)); + + if req.encoded_request_byte_count == 0 || req.encoded_request_pointer.is_null() { + util::message("ERROR: No encoded request data provided"); + return HRESULT(-1); + } + + let encoded_request_slice = std::slice::from_raw_parts( + req.encoded_request_pointer, + req.encoded_request_byte_count as usize + ); + + util::message(&format!("Encoded request: {} bytes", encoded_request_slice.len())); + + // Try to decode the request using Windows API + match decode_make_credential_request(encoded_request_slice) { + Ok(decoded_wrapper) => { + let decoded_request = decoded_wrapper.as_ref(); + util::message("Successfully decoded make credential request using Windows API"); + + // Extract RP information + if decoded_request.pRpInformation.is_null() { + util::message("ERROR: RP information is null"); + return HRESULT(-1); + } + + let rp_info = &*decoded_request.pRpInformation; + + let rpid = if rp_info.pwszId.is_null() { + util::message("ERROR: RP ID is null"); + return HRESULT(-1); + } else { + match wstr_to_string(rp_info.pwszId) { + Ok(id) => id, + Err(e) => { + util::message(&format!("ERROR: Failed to decode RP ID: {}", e)); + return HRESULT(-1); + } + } + }; + + let rp_name = if rp_info.pwszName.is_null() { + String::new() + } else { + wstr_to_string(rp_info.pwszName).unwrap_or_default() + }; + + // Extract user information + if decoded_request.pUserInformation.is_null() { + util::message("ERROR: User information is null"); + return HRESULT(-1); + } + + let user = &*decoded_request.pUserInformation; + + let user_id = if user.pbId.is_null() || user.cbId == 0 { + util::message("ERROR: User ID is required for registration"); + return HRESULT(-1); + } else { + let id_slice = std::slice::from_raw_parts(user.pbId, user.cbId as usize); + id_slice.to_vec() + }; + + let user_name = if user.pwszName.is_null() { + util::message("ERROR: User name is required for registration"); + return HRESULT(-1); + } else { + match wstr_to_string(user.pwszName) { + Ok(name) => name, + Err(_) => { + util::message("ERROR: Failed to decode user name"); + return HRESULT(-1); + } + } + }; + + let user_display_name = if user.pwszDisplayName.is_null() { + None + } else { + wstr_to_string(user.pwszDisplayName).ok() + }; + + let user_info = (user_id, user_name, user_display_name); + + // Extract client data hash + let client_data_hash = if decoded_request.cbClientDataHash == 0 || decoded_request.pbClientDataHash.is_null() { + util::message("ERROR: Client data hash is required for registration"); + return HRESULT(-1); + } else { + let hash_slice = std::slice::from_raw_parts( + decoded_request.pbClientDataHash, + decoded_request.cbClientDataHash as usize + ); + hash_slice.to_vec() + }; + + // Extract RP ID raw bytes for authenticator data + let rpid_bytes = if decoded_request.cbRpId > 0 && !decoded_request.pbRpId.is_null() { + let rpid_slice = std::slice::from_raw_parts( + decoded_request.pbRpId, + decoded_request.cbRpId as usize + ); + rpid_slice.to_vec() + } else { + rpid.as_bytes().to_vec() + }; + + // Extract supported algorithms + let supported_algorithms = if decoded_request.WebAuthNCredentialParameters.cCredentialParameters > 0 && + !decoded_request.WebAuthNCredentialParameters.pCredentialParameters.is_null() { + let params_count = decoded_request.WebAuthNCredentialParameters.cCredentialParameters as usize; + let params_ptr = decoded_request.WebAuthNCredentialParameters.pCredentialParameters; + + (0..params_count) + .map(|i| unsafe { &*params_ptr.add(i) }.lAlg) + .collect() + } else { + Vec::new() + }; + + // Create request context from properly decoded data + let mut request_context = RequestContext::default(); + request_context.rpid = Some(rpid.clone()); + request_context.user_id = Some(user_info.0); + request_context.user_name = Some(user_info.1); + request_context.user_display_name = user_info.2; + request_context.client_data_hash = Some(client_data_hash); + request_context.supported_algorithms = supported_algorithms; + + util::message(&format!("Make credential request - RP: {}, User: {}", + rpid, + request_context.user_name.as_deref().unwrap_or("unknown"))); + + // Send registration request + if let Some(passkey_response) = send_registration_request(&rpid, &transaction_id, &request_context) { + util::message(&format!("Registration response received: {:?}", passkey_response)); + + // Create proper WebAuthn response from passkey_response + match passkey_response { + PasskeyResponse::RegistrationResponse { credential_id, attestation_object } => { + util::message("Creating WebAuthn make credential response"); + + match create_make_credential_response(credential_id, attestation_object) { + Ok(webauthn_response) => { + util::message("Successfully created WebAuthn response"); + *response = webauthn_response; + HRESULT(0) + }, + Err(e) => { + util::message(&format!("ERROR: Failed to create WebAuthn response: {}", e)); + *response = ptr::null_mut(); + HRESULT(-1) + } + } + }, + PasskeyResponse::Error { message } => { + util::message(&format!("Registration request failed: {}", message)); + *response = ptr::null_mut(); + HRESULT(-1) + }, + _ => { + util::message("ERROR: Unexpected response type for registration request"); + *response = ptr::null_mut(); + HRESULT(-1) + } + } + } else { + util::message("ERROR: No response from registration request"); + *response = ptr::null_mut(); + HRESULT(-1) + } + }, + Err(e) => { + util::message(&format!("ERROR: Failed to decode make credential request: {}", e)); + *response = ptr::null_mut(); + HRESULT(-1) + } + } + } + + unsafe fn EXPERIMENTAL_PluginGetAssertion( + &self, + request: *const ExperimentalWebAuthnPluginOperationRequest, + response: *mut *mut ExperimentalWebAuthnPluginOperationResponse, + ) -> HRESULT { + util::message("EXPERIMENTAL_PluginGetAssertion() called"); + + // Validate input parameters + if request.is_null() || response.is_null() { + util::message("Invalid parameters passed to EXPERIMENTAL_PluginGetAssertion"); + return HRESULT(-1); + } + + let req = &*request; + let transaction_id = format!("{:?}", req.transaction_id); + + util::message(&format!("Get assertion request - Transaction: {}", transaction_id)); + + if req.encoded_request_byte_count == 0 || req.encoded_request_pointer.is_null() { + util::message("ERROR: No encoded request data provided"); + *response = ptr::null_mut(); + return HRESULT(-1); + } + + let encoded_request_slice = std::slice::from_raw_parts( + req.encoded_request_pointer, + req.encoded_request_byte_count as usize + ); + + // Try to decode the request using Windows API + match decode_get_assertion_request(encoded_request_slice) { + Ok(decoded_wrapper) => { + let decoded_request = decoded_wrapper.as_ref(); + util::message("Successfully decoded get assertion request using Windows API"); + + // Extract RP information + let rpid = if decoded_request.pwszRpId.is_null() { + util::message("ERROR: RP ID is null"); + *response = ptr::null_mut(); + return HRESULT(-1); + } else { + match wstr_to_string(decoded_request.pwszRpId) { + Ok(id) => id, + Err(e) => { + util::message(&format!("ERROR: Failed to decode RP ID: {}", e)); + *response = ptr::null_mut(); + return HRESULT(-1); + } + } + }; + + // Extract client data hash + let client_data_hash = if decoded_request.cbClientDataHash == 0 || decoded_request.pbClientDataHash.is_null() { + util::message("ERROR: Client data hash is required for assertion"); + *response = ptr::null_mut(); + return HRESULT(-1); + } else { + let hash_slice = std::slice::from_raw_parts( + decoded_request.pbClientDataHash, + decoded_request.cbClientDataHash as usize + ); + hash_slice.to_vec() + }; + + // Create request context from properly decoded data + let mut request_context = RequestContext::default(); + request_context.rpid = Some(rpid.clone()); + request_context.client_data_hash = Some(client_data_hash); + // TODO: Extract allowed credentials from CredentialList if available + + util::message(&format!("Get assertion request - RP: {}", rpid)); + + // Send assertion request + if let Some(passkey_response) = send_assertion_request(&rpid, &transaction_id, &request_context) { + util::message(&format!("Assertion response received: {:?}", passkey_response)); + + // Create proper WebAuthn response from passkey_response + match passkey_response { + PasskeyResponse::AssertionResponse { credential_id, authenticator_data, signature, user_handle } => { + util::message("Creating WebAuthn get assertion response"); + + match create_get_assertion_response(credential_id, authenticator_data, signature, user_handle) { + Ok(webauthn_response) => { + util::message("Successfully created WebAuthn assertion response"); + *response = webauthn_response; + HRESULT(0) + }, + Err(e) => { + util::message(&format!("ERROR: Failed to create WebAuthn assertion response: {}", e)); + *response = ptr::null_mut(); + HRESULT(-1) + } + } + }, + PasskeyResponse::Error { message } => { + util::message(&format!("Assertion request failed: {}", message)); + *response = ptr::null_mut(); + HRESULT(-1) + }, + _ => { + util::message("ERROR: Unexpected response type for assertion request"); + *response = ptr::null_mut(); + HRESULT(-1) + } + } + } else { + util::message("ERROR: No response from assertion request"); + *response = ptr::null_mut(); + HRESULT(-1) + } + }, + Err(e) => { + util::message(&format!("ERROR: Failed to decode get assertion request: {}", e)); + *response = ptr::null_mut(); + HRESULT(-1) + } + } + } + + unsafe fn EXPERIMENTAL_PluginCancelOperation( + &self, + request: *const ExperimentalWebAuthnPluginCancelOperationRequest, + ) -> HRESULT { + util::message("EXPERIMENTAL_PluginCancelOperation() called"); + HRESULT(0) + } +} + +impl IClassFactory_Impl for Factory_Impl { + fn CreateInstance( + &self, + outer: windows_core::Ref, + iid: *const windows_core::GUID, + object: *mut *mut core::ffi::c_void, + ) -> windows_core::Result<()> { + let unknown: IInspectable = PluginAuthenticatorComObject.into(); // TODO: IUnknown ? + unsafe { unknown.query(iid, object).ok() } + } + + fn LockServer(&self, lock: windows_core::BOOL) -> windows_core::Result<()> { + Ok(()) + } +} \ No newline at end of file diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_registration.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_registration.rs new file mode 100644 index 00000000000..6d82576fb63 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_registration.rs @@ -0,0 +1,212 @@ +use std::ffi::OsString; +use std::os::windows::ffi::OsStrExt; +use std::ffi::c_uchar; +use std::ptr; + +use windows::Win32::Foundation::*; +use windows::Win32::System::Com::*; +use windows_core::{s, HRESULT, PCWSTR, ComObjectInterface, GUID, HSTRING}; + +use crate::utils::{WindowsString, delay_load, message}; +use crate::webauthn::*; +use crate::com_provider; +use hex; + +const AUTHENTICATOR_NAME: &str = "Bitwarden Desktop Authenticator"; +const CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062"; +const RPID: &str = "bitwarden.com"; + +/// Initializes the COM library for use on the calling thread, +/// and registers + sets the security values. +pub fn initialize_com_library() -> std::result::Result<(), String> { + let result = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }; + + if result.is_err() { + return Err(format!( + "Error: couldn't initialize the COM library\n{}", + result.message() + )); + } + + match unsafe { + CoInitializeSecurity( + None, + -1, + None, + None, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + None, + EOAC_NONE, + None, + ) + } { + Ok(_) => Ok(()), + Err(e) => Err(format!( + "Error: couldn't initialize COM security\n{}", + e.message() + )), + } +} + +/// Registers the Bitwarden Plugin Authenticator COM library with Windows. +pub fn register_com_library() -> std::result::Result<(), String> { + static FACTORY: windows_core::StaticComObject = + com_provider::Factory.into_static(); + //let clsid: *const GUID = &GUID::from_u128(0xa98925d161f640de9327dc418fcb2ff4); + let clsid: *const GUID = &GUID::from_u128(0x0f7dc5d969ce465285726877fd695062); + + match unsafe { + CoRegisterClassObject( + clsid, + FACTORY.as_interface_ref(), + //FACTORY.as_interface::(), + CLSCTX_LOCAL_SERVER, + REGCLS_MULTIPLEUSE, + ) + } { + Ok(_) => Ok(()), + Err(e) => Err(format!( + "Error: couldn't register the COM library\n{}", + e.message() + )), + } +} + +// testing wide encoding +pub fn add_authenticator_using_wide_encoding() -> std::result::Result<(), String> { + // let (authenticator_name_pointer, authenticator_name_bytes) = String::from(AUTHENTICATOR_NAME).into_win_utf16_wide(); + let mut authenticator_name: Vec = OsString::from(AUTHENTICATOR_NAME).encode_wide().collect(); + //authenticator_name.push(0); + let authenticator_name_pointer = authenticator_name.as_mut_ptr(); + + // let (clsid_pointer, clsid_bytes) = String::from(CLSID).into_win_utf16_wide(); + let mut clsid: Vec = OsString::from(CLSID).encode_wide().collect(); + //clsid.push(0); + let clsid_pointer = clsid.as_mut_ptr(); + + // let (rpid_pointer, rpid_bytes) = String::from(RPID).into_win_utf16_wide(); + let mut rpid: Vec = OsString::from(RPID).encode_wide().collect(); + //rpid.push(0); + let rpid_pointer = rpid.as_mut_ptr(); + + // Example authenticator info blob + let cbor_authenticator_info = "A60182684649444F5F325F30684649444F5F325F310282637072666B686D61632D7365637265740350D548826E79B4DB40A3D811116F7E834904A362726BF5627570F5627576F5098168696E7465726E616C0A81A263616C672664747970656A7075626C69632D6B6579"; + let mut authenticator_info_bytes = hex::decode(cbor_authenticator_info).unwrap(); + + let add_authenticator_options = ExperimentalWebAuthnPluginAddAuthenticatorOptions { + authenticator_name: authenticator_name_pointer, + plugin_clsid: clsid_pointer, + rpid: rpid_pointer, + light_theme_logo: ptr::null(), + dark_theme_logo: ptr::null(), + cbor_authenticator_info_byte_count: authenticator_info_bytes.len() as u32, + cbor_authenticator_info: authenticator_info_bytes.as_mut_ptr(), + }; + + let plugin_signing_public_key_byte_count: u32 = 0; + let mut plugin_signing_public_key: c_uchar = 0; + let plugin_signing_public_key_ptr = &mut plugin_signing_public_key; + + let mut add_response = ExperimentalWebAuthnPluginAddAuthenticatorResponse { + plugin_operation_signing_key_byte_count: plugin_signing_public_key_byte_count, + plugin_operation_signing_key: plugin_signing_public_key_ptr, + }; + let mut add_response_ptr: *mut ExperimentalWebAuthnPluginAddAuthenticatorResponse = + &mut add_response; + + let result = unsafe { + delay_load::( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNPluginAddAuthenticator"), + ) + }; + + match result { + Some(api) => { + let result = unsafe { api(&add_authenticator_options, &mut add_response_ptr) }; + + if result.is_err() { + return Err(format!( + "Error: Error response from EXPERIMENTAL_WebAuthNPluginAddAuthenticator()\n{}", + result.message() + )); + } + + Ok(()) + }, + None => { + Err(String::from("Error: Can't complete add_authenticator(), as the function EXPERIMENTAL_WebAuthNPluginAddAuthenticator can't be found.")) + } + } +} + +/// Adds Bitwarden as a plugin authenticator. +pub fn add_authenticator() -> std::result::Result<(), String> { + let authenticator_name: HSTRING = AUTHENTICATOR_NAME.into(); + let authenticator_name_ptr = PCWSTR(authenticator_name.as_ptr()).as_ptr(); + + let clsid: HSTRING = format!("{{{}}}", CLSID).into(); + let clsid_ptr = PCWSTR(clsid.as_ptr()).as_ptr(); + + let relying_party_id: HSTRING = RPID.into(); + let relying_party_id_ptr = PCWSTR(relying_party_id.as_ptr()).as_ptr(); + + // let aaguid: HSTRING = format!("{{{}}}", AAGUID).into(); + // let aaguid_ptr = PCWSTR(aaguid.as_ptr()).as_ptr(); + + // Example authenticator info blob + let cbor_authenticator_info = "A60182684649444F5F325F30684649444F5F325F310282637072666B686D61632D7365637265740350D548826E79B4DB40A3D811116F7E834904A362726BF5627570F5627576F5098168696E7465726E616C0A81A263616C672664747970656A7075626C69632D6B6579"; + let mut authenticator_info_bytes = hex::decode(cbor_authenticator_info).unwrap(); + + let add_authenticator_options = ExperimentalWebAuthnPluginAddAuthenticatorOptions { + authenticator_name: authenticator_name_ptr, + plugin_clsid: clsid_ptr, + rpid: relying_party_id_ptr, + light_theme_logo: ptr::null(), + dark_theme_logo: ptr::null(), + cbor_authenticator_info_byte_count: authenticator_info_bytes.len() as u32, + cbor_authenticator_info: authenticator_info_bytes.as_mut_ptr(), + }; + + let plugin_signing_public_key_byte_count: u32 = 0; + let mut plugin_signing_public_key: c_uchar = 0; + let plugin_signing_public_key_ptr = &mut plugin_signing_public_key; + + let mut add_response = ExperimentalWebAuthnPluginAddAuthenticatorResponse { + plugin_operation_signing_key_byte_count: plugin_signing_public_key_byte_count, + plugin_operation_signing_key: plugin_signing_public_key_ptr, + }; + let mut add_response_ptr: *mut ExperimentalWebAuthnPluginAddAuthenticatorResponse = + &mut add_response; + + let result = unsafe { + delay_load::( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNPluginAddAuthenticator"), + ) + }; + + match result { + Some(api) => { + let result = unsafe { api(&add_authenticator_options, &mut add_response_ptr) }; + + if result.is_err() { + return Err(format!( + "Error: Error response from EXPERIMENTAL_WebAuthNPluginAddAuthenticator()\n{}", + result.message() + )); + } + + Ok(()) + }, + None => { + Err(String::from("Error: Can't complete add_authenticator(), as the function EXPERIMENTAL_WebAuthNPluginAddAuthenticator can't be found.")) + } + } +} + +type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "cdecl" fn( + pPluginAddAuthenticatorOptions: *const ExperimentalWebAuthnPluginAddAuthenticatorOptions, + ppPluginAddAuthenticatorResponse: *mut *mut ExperimentalWebAuthnPluginAddAuthenticatorResponse, +) -> HRESULT; \ No newline at end of file diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs new file mode 100644 index 00000000000..0f1e7be18bd --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs @@ -0,0 +1,65 @@ +use tokio::sync::{mpsc, oneshot}; +use std::sync::Mutex; + +use crate::types::*; +use crate::utils::{self as util}; + +/// Global channel sender for request notifications +static REQUEST_SENDER: Mutex>> = Mutex::new(None); + +/// Sets the channel sender for request notifications +pub fn set_request_sender(sender: mpsc::UnboundedSender) { + match REQUEST_SENDER.lock() { + Ok(mut tx) => { + *tx = Some(sender); + util::message("Passkey request callback registered"); + }, + Err(e) => { + util::message(&format!("Failed to register passkey callback: {:?}", e)); + } + } +} + +/// Sends a passkey request and waits for response +pub fn send_passkey_request(request_type: RequestType, request_json: String, rpid: &str) -> Option { + let request_desc = match &request_type { + RequestType::Assertion => format!("assertion request for {}", rpid), + RequestType::Registration => format!("registration request for {}", rpid), + RequestType::Sync => format!("sync request for {}", rpid), + }; + + util::message(&format!("Passkey {}", request_desc)); + + if let Ok(tx_guard) = REQUEST_SENDER.lock() { + if let Some(sender) = tx_guard.as_ref() { + let (response_tx, response_rx) = oneshot::channel(); + let event = RequestEvent { + request_type, + request_json, + response_sender: response_tx, + }; + + if let Ok(()) = sender.send(event) { + // Wait for response from TypeScript callback + match response_rx.blocking_recv() { + Ok(response) => { + util::message(&format!("Received callback response {:?}", response)); + Some(response) + }, + Err(_) => { + util::message("No response from callback"); + None + } + } + } else { + util::message("Failed to send event to callback"); + None + } + } else { + util::message("No callback registered for passkey requests"); + None + } + } else { + None + } +} \ No newline at end of file diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs index 1e8287ce4b4..7cd8910c8c4 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -2,23 +2,32 @@ #![allow(non_snake_case)] #![allow(non_camel_case_types)] -use std::ffi::OsString; +use std::ffi::{c_uchar, c_ulong, OsString}; use std::os::windows::ffi::OsStrExt; - -use std::ffi::c_uchar; -use std::ffi::c_ulong; -use std::ptr; use std::{thread, time::Duration}; -use util::WindowsString; -use webauthn::*; -use windows::Win32::Foundation::*; -use windows::Win32::System::Com::*; -use windows::Win32::System::LibraryLoader::*; use windows_core::*; -mod pluginauthenticator; -mod util; +// New modular structure +mod assert; +mod make_credential; +mod sync; +mod ipc; +mod com_registration; +pub mod utils; +mod types; mod webauthn; +mod com_provider; +mod com_buffer; + + +// Re-export main functionality +pub use sync::{send_sync_request, sync_credentials_to_windows, get_credentials_from_windows}; +pub use ipc::{set_request_sender, send_passkey_request}; +pub use types::{PasskeyRequest, PasskeyResponse, SyncedCredential, RequestEvent, RequestType}; +pub use com_registration::{initialize_com_library, register_com_library, add_authenticator}; + +// Re-export utilities +pub use utils as util; const AUTHENTICATOR_NAME: &str = "Bitwarden Desktop Authenticator"; //const AAGUID: &str = "d548826e-79b4-db40-a3d8-11116f7e8349"; @@ -29,518 +38,55 @@ const RPID: &str = "bitwarden.com"; /// plugin authenticator with Windows. /// For now, also adds the authenticator pub fn register() -> std::result::Result<(), String> { - util::message(String::from("register() called")); + util::message("register() called"); - util::message(String::from("About to call initialize_com_library()")); - let r = initialize_com_library(); - util::message(format!("initialized the com library: {:?}", r)); + util::message("About to call initialize_com_library()"); + let r = com_registration::initialize_com_library(); + util::message(&format!("initialized the com library: {:?}", r)); - util::message(String::from("About to call register_com_library()")); - let r = register_com_library(); - util::message(format!("registered the com library: {:?}", r)); + util::message("About to call register_com_library()"); + let r = com_registration::register_com_library(); + util::message(&format!("registered the com library: {:?}", r)); - util::message(String::from("About to call add_authenticator()")); - let r = add_authenticator(); + util::message("About to call add_authenticator()"); + let r = com_registration::add_authenticator(); //let r = add_authenticator_using_wide_encoding(); - util::message(format!("added the authenticator: {:?}", r)); + util::message(&format!("added the authenticator: {:?}", r)); - util::message(String::from("sleeping for 20 seconds...")); - thread::sleep(Duration::from_millis(20000)); - util::message(String::from("sleeping done")); + util::message("sleeping for 5 seconds..."); + thread::sleep(Duration::from_millis(5000)); + util::message("sleeping done"); - // // --------------------------------------- - // // ----- *** add test credential *** ----- - // // ----- using encode_utf16 ----- - // // --------------------------------------- - - // // Style 1, currently used: mem::forget - // let mut credential_id_string = String::from("32"); - // let credential_id_byte_count = credential_id_string.as_bytes().len() as c_ulong; - // let credential_id_pointer: *mut c_uchar = credential_id_string.as_mut_ptr(); - // std::mem::forget(credential_id_string); - - // // Style 2, experimental: Box::leak - // // Additionally, might need to Pin (same for style 1) - // // - // // let credential_id_string = String::from("32"); - // // let credential_id_byte_count = credential_id_string.as_bytes().len() as c_ulong; - // // let credential_id_box = Box::new(credential_id_string); - // // let credential_id_pointer: *mut c_uchar = credential_id_box.leak().as_mut_ptr(); - - // let mut rpid_string = String::from("webauthn.io"); - // let mut rpid_vec: Vec = rpid_string.encode_utf16().collect(); - // rpid_vec.push(0); - // let rpid: *mut u16 = rpid_vec.as_mut_ptr(); - // std::mem::forget(rpid_string); - // std::mem::forget(rpid_vec); - - // let mut rp_friendly_name_string = String::from("WebAuthn Website"); - // let mut rp_friendly_name_vec: Vec = rp_friendly_name_string.encode_utf16().collect(); - // rp_friendly_name_vec.push(0); - // let rp_friendly_name: *mut u16 = rp_friendly_name_vec.as_mut_ptr(); - // std::mem::forget(rp_friendly_name_string); - // std::mem::forget(rp_friendly_name_vec); - - // let mut user_id_string = String::from("14"); - // let user_id_byte_count = user_id_string.as_bytes().len() as c_ulong; - // let user_id_pointer: *mut c_uchar = user_id_string.as_mut_ptr(); - // std::mem::forget(user_id_string); - - // let mut user_name_string = String::from("webauthn.io username"); - // let mut user_name_vec: Vec = user_name_string.encode_utf16().collect(); - // user_name_vec.push(0); - // let user_name: *mut u16 = user_name_vec.as_mut_ptr(); - // std::mem::forget(user_name_string); - // std::mem::forget(user_name_vec); - - // let mut user_display_name_string = String::from("webauthn.io display name"); - // let mut user_display_name_vec: Vec = user_display_name_string.encode_utf16().collect(); - // user_display_name_vec.push(0); - // let user_display_name: *mut u16 = user_display_name_vec.as_mut_ptr(); - // std::mem::forget(user_display_name_string); - // std::mem::forget(user_display_name_vec); - - // let mut credential_details = ExperimentalWebAuthnPluginCredentialDetails { - // credential_id_byte_count, - // credential_id_pointer, - // rpid, - // rp_friendly_name, - // user_id_byte_count, - // user_id_pointer, - // user_name, - // user_display_name, - // }; - // let credential_details_ptr: *mut ExperimentalWebAuthnPluginCredentialDetails = - // &mut credential_details; - // std::mem::forget(credential_details); - - // let mut clsid_string = String::from(format!("{{{}}}", CLSID)); - // let mut clsid_vec: Vec = clsid_string.encode_utf16().collect(); - // clsid_vec.push(0); - // let plugin_clsid: *mut u16 = clsid_vec.as_mut_ptr(); - // std::mem::forget(clsid_string); - // std::mem::forget(clsid_vec); - - // let mut credentials: Vec<*mut ExperimentalWebAuthnPluginCredentialDetails> = - // vec![credential_details_ptr]; - // let credential_count: c_ulong = credentials.len() as c_ulong; - // let credentials_ptr: *mut *mut ExperimentalWebAuthnPluginCredentialDetails = - // credentials.as_mut_ptr(); - // std::mem::forget(credentials); - - // let mut credentials_details_list = ExperimentalWebAuthnPluginCredentialDetailsList { - // plugin_clsid, - // credential_count, - // credentials: credentials_ptr, - // }; - // let credentials_details_list_ptr: *mut ExperimentalWebAuthnPluginCredentialDetailsList = - // &mut credentials_details_list; - // std::mem::forget(credentials_details_list); - - // util::message(format!("about to link the fn pointer for add credentials")); - - // let result = unsafe { - // delay_load::( - // s!("webauthn.dll"), - // s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials"), - // ) - // }; - - // util::message(format!("about to call add credentials")); - - // let result = match result { - // Some(api) => { - // let result = unsafe { api(credentials_details_list_ptr) }; - - // if result.is_err() { - // return Err(format!( - // "Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials()\n{}", - // result.message() - // )); - // } - - // Ok(()) - // }, - // None => { - // Err(String::from("Error: Can't complete add_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials can't be loaded.")) - // } - // }; - - // util::message(format!("add credentials attempt: {:?}", result)); - - // -------------------------------------------------------------------------------------------- - - // std::mem::forget(credential_id); - // let mut test_credential = ExperimentalWebAuthnPluginCredentialDetails::create( - // String::from("32"), - // String::from("webauthn.io"), - // String::from("WebAuthn Website"), - // String::from("14"), - // String::from("web user name"), - // String::from("web user display name"), - // ); - // let test_credential_ptr: *mut ExperimentalWebAuthnPluginCredentialDetails = &mut test_credential; - // //std::mem::forget(test_credential); - // let mut test_credential_list: Vec<*mut ExperimentalWebAuthnPluginCredentialDetails> = vec![test_credential_ptr]; - // let test_credential_list_ptr: *mut *mut ExperimentalWebAuthnPluginCredentialDetails = test_credential_list.as_mut_ptr(); - // let pluginclsid = String::from(CLSID).into_win_utf16().0; - - // let credentials = ExperimentalWebAuthnPluginCredentialDetailsList { - // plugin_clsid: pluginclsid, - // credential_count: 1, - // credentials: test_credential_list_ptr, - // }; - - // let r = add_credentials(credentials); - // util::message(format!("added the credentials: {:?}", r)); - - // -------------------------------------------------------------------------------------------- - - // --------------------------------------- - // ----- *** add test credential *** ----- - // ----- using encode_wide ----- - // --------------------------------------- - - // Style 1, currently used: mem::forget - let mut credential_id_string = String::from("32"); - let credential_id_byte_count = credential_id_string.as_bytes().len() as c_ulong; - let credential_id_pointer: *mut c_uchar = credential_id_string.as_mut_ptr(); - std::mem::forget(credential_id_string); - - let rpid_string = String::from("webauthn.io"); - let mut rpid_vec: Vec = OsString::from(rpid_string).encode_wide().collect(); - rpid_vec.push(0); - let rpid: *mut u16 = rpid_vec.as_mut_ptr(); - std::mem::forget(rpid_vec); - - let rp_friendly_name_string = String::from("WebAuthn Websitewide "); - let mut rp_friendly_name_vec: Vec = OsString::from(rp_friendly_name_string).encode_wide().collect(); - rp_friendly_name_vec.push(0); - let rp_friendly_name: *mut u16 = rp_friendly_name_vec.as_mut_ptr(); - std::mem::forget(rp_friendly_name_vec); - - let mut user_id_string = String::from("14"); - let user_id_byte_count = user_id_string.as_bytes().len() as c_ulong; - let user_id_pointer: *mut c_uchar = user_id_string.as_mut_ptr(); - std::mem::forget(user_id_string); - - let user_name_string = String::from("webauthn.io wide username"); - let mut user_name_vec: Vec = OsString::from(user_name_string).encode_wide().collect(); - user_name_vec.push(0); - let user_name: *mut u16 = user_name_vec.as_mut_ptr(); - std::mem::forget(user_name_vec); - - let user_display_name_string = String::from("webauthn.io wide display name"); - let mut user_display_name_vec: Vec = OsString::from(user_display_name_string).encode_wide().collect(); - user_display_name_vec.push(0); - let user_display_name: *mut u16 = user_display_name_vec.as_mut_ptr(); - std::mem::forget(user_display_name_vec); - - let mut credential_details = ExperimentalWebAuthnPluginCredentialDetails { - credential_id_byte_count, - credential_id_pointer, - rpid, - rp_friendly_name, - user_id_byte_count, - user_id_pointer, - user_name, - user_display_name, - }; - let credential_details_ptr: *mut ExperimentalWebAuthnPluginCredentialDetails = - &mut credential_details; - std::mem::forget(credential_details); - - let clsid_string = String::from(format!("{{{}}}", CLSID)); - let mut clsid_vec: Vec = OsString::from(clsid_string).encode_wide().collect(); - clsid_vec.push(0); - let plugin_clsid: *mut u16 = clsid_vec.as_mut_ptr(); - std::mem::forget(clsid_vec); - - let mut credentials: Vec<*mut ExperimentalWebAuthnPluginCredentialDetails> = - vec![credential_details_ptr]; - let credential_count: c_ulong = credentials.len() as c_ulong; - let credentials_ptr: *mut *mut ExperimentalWebAuthnPluginCredentialDetails = - credentials.as_mut_ptr(); - std::mem::forget(credentials); - - let mut credentials_details_list = ExperimentalWebAuthnPluginCredentialDetailsList { - plugin_clsid, - credential_count, - credentials: credentials_ptr, - }; - let credentials_details_list_ptr: *mut ExperimentalWebAuthnPluginCredentialDetailsList = - &mut credentials_details_list; - std::mem::forget(credentials_details_list); - - util::message(format!("about to link the fn pointer for add credentials")); - - let result = unsafe { - delay_load::( - s!("webauthn.dll"), - s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials"), - ) - }; - - util::message(format!("about to call add credentials")); - - let result = match result { - Some(api) => { - let result = unsafe { api(credentials_details_list_ptr) }; - - if result.is_err() { - return Err(format!( - "Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials()\n{}", - result.message() - )); - } - - Ok(()) - }, - None => { - Err(String::from("Error: Can't complete add_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials can't be loaded.")) - } - }; - - util::message(format!("add credentials attempt: {:?}", result)); - - // -------------------------------------------------------------------------------------------- + let r = syncCredentials(); + util::message(&format!("sync credentials: {:?}", r)); + + if let Err(e) = r { + util::message(&format!("syncCredentials failed: {}", e)); + } Ok(()) } -// ----- -#[repr(C)] -pub struct ExperimentalWebAuthnPluginCredentialDetails { - pub credential_id_byte_count: c_ulong, // DWORD cbCredentialId - pub credential_id_pointer: *mut c_uchar, // PBYTE pbCredentialId - pub rpid: *mut u16, // PWSTR pwszRpId - pub rp_friendly_name: *mut u16, // PWSTR pwszRpName - pub user_id_byte_count: u32, // DWORD cbUserId - pub user_id_pointer: *mut c_uchar, // PBYTE pbUserId - pub user_name: *mut u16, // PWSTR pwszUserName - pub user_display_name: *mut u16, // PWSTR pwszUserDisplayName -} -#[repr(C)] -pub struct ExperimentalWebAuthnPluginCredentialDetailsList { - pub plugin_clsid: *mut u16, // PWSTR pwszPluginClsId - pub credential_count: c_ulong, // DWORD cCredentialDetails - pub credentials: *mut *mut ExperimentalWebAuthnPluginCredentialDetails, // CredentialDetailsPtr *pCredentialDetails -} -type EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration = - unsafe extern "cdecl" fn( - pCredentialDetailsList: *mut ExperimentalWebAuthnPluginCredentialDetailsList, - ) -> HRESULT; -// ----- - -/// Initializes the COM library for use on the calling thread, -/// and registers + sets the security values. -fn initialize_com_library() -> std::result::Result<(), String> { - let result = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }; - - if result.is_err() { - return Err(format!( - "Error: couldn't initialize the COM library\n{}", - result.message() - )); - } - - match unsafe { - CoInitializeSecurity( - None, - -1, - None, - None, - RPC_C_AUTHN_LEVEL_DEFAULT, - RPC_C_IMP_LEVEL_IMPERSONATE, - None, - EOAC_NONE, - None, - ) - } { - Ok(_) => Ok(()), - Err(e) => Err(format!( - "Error: couldn't initialize COM security\n{}", - e.message() - )), - } +fn syncCredentials() -> std::result::Result<(), String> { + // Create a test credential using the new sync module with more realistic data + let test_credential = types::SyncedCredential { + credential_id: vec![ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 + ], // 32 byte credential ID + rp_id: "webauthn.io".to_string(), + user_name: "testuser".to_string(), + user_id: vec![0x75, 0x73, 0x65, 0x72, 0x31, 0x32, 0x33, 0x34], // "user1234" as bytes + }; + + let credentials = vec![test_credential]; + + // Use the sync module to sync credentials + sync_credentials_to_windows(credentials, CLSID) } -/// Registers the Bitwarden Plugin Authenticator COM library with Windows. -fn register_com_library() -> std::result::Result<(), String> { - static FACTORY: windows_core::StaticComObject = - pluginauthenticator::Factory.into_static(); - //let clsid: *const GUID = &GUID::from_u128(0xa98925d161f640de9327dc418fcb2ff4); - let clsid: *const GUID = &GUID::from_u128(0x0f7dc5d969ce465285726877fd695062); - match unsafe { - CoRegisterClassObject( - clsid, - FACTORY.as_interface_ref(), - //FACTORY.as_interface::(), - CLSCTX_LOCAL_SERVER, - REGCLS_MULTIPLEUSE, - ) - } { - Ok(_) => Ok(()), - Err(e) => Err(format!( - "Error: couldn't register the COM library\n{}", - e.message() - )), - } -} -// testing wide encoding -fn add_authenticator_using_wide_encoding() -> std::result::Result<(), String> { - // let (authenticator_name_pointer, authenticator_name_bytes) = String::from(AUTHENTICATOR_NAME).into_win_utf16_wide(); - let mut authenticator_name: Vec = OsString::from(AUTHENTICATOR_NAME).encode_wide().collect(); - //authenticator_name.push(0); - let authenticator_name_pointer = authenticator_name.as_mut_ptr(); - // let (clsid_pointer, clsid_bytes) = String::from(CLSID).into_win_utf16_wide(); - let mut clsid: Vec = OsString::from(CLSID).encode_wide().collect(); - //clsid.push(0); - let clsid_pointer = clsid.as_mut_ptr(); - - // let (rpid_pointer, rpid_bytes) = String::from(RPID).into_win_utf16_wide(); - let mut rpid: Vec = OsString::from(RPID).encode_wide().collect(); - //rpid.push(0); - let rpid_pointer = rpid.as_mut_ptr(); - - // Example authenticator info blob - let cbor_authenticator_info = "A60182684649444F5F325F30684649444F5F325F310282637072666B686D61632D7365637265740350D548826E79B4DB40A3D811116F7E834904A362726BF5627570F5627576F5098168696E7465726E616C0A81A263616C672664747970656A7075626C69632D6B6579"; - let mut authenticator_info_bytes = hex::decode(cbor_authenticator_info).unwrap(); - - let add_authenticator_options = webauthn::ExperimentalWebAuthnPluginAddAuthenticatorOptions { - authenticator_name: authenticator_name_pointer, - plugin_clsid: clsid_pointer, - rpid: rpid_pointer, - light_theme_logo: ptr::null(), - dark_theme_logo: ptr::null(), - cbor_authenticator_info_byte_count: authenticator_info_bytes.len() as u32, - cbor_authenticator_info: authenticator_info_bytes.as_mut_ptr(), - }; - - let plugin_signing_public_key_byte_count: u32 = 0; - let mut plugin_signing_public_key: c_uchar = 0; - let plugin_signing_public_key_ptr = &mut plugin_signing_public_key; - - let mut add_response = webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse { - plugin_operation_signing_key_byte_count: plugin_signing_public_key_byte_count, - plugin_operation_signing_key: plugin_signing_public_key_ptr, - }; - let mut add_response_ptr: *mut webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse = - &mut add_response; - - let result = unsafe { - delay_load::( - s!("webauthn.dll"), - s!("EXPERIMENTAL_WebAuthNPluginAddAuthenticator"), - ) - }; - - match result { - Some(api) => { - let result = unsafe { api(&add_authenticator_options, &mut add_response_ptr) }; - - if result.is_err() { - return Err(format!( - "Error: Error response from EXPERIMENTAL_WebAuthNPluginAddAuthenticator()\n{}", - result.message() - )); - } - - Ok(()) - }, - None => { - Err(String::from("Error: Can't complete add_authenticator(), as the function EXPERIMENTAL_WebAuthNPluginAddAuthenticator can't be found.")) - } - } -} - -/// Adds Bitwarden as a plugin authenticator. -fn add_authenticator() -> std::result::Result<(), String> { - let authenticator_name: HSTRING = AUTHENTICATOR_NAME.into(); - let authenticator_name_ptr = PCWSTR(authenticator_name.as_ptr()).as_ptr(); - - let clsid: HSTRING = format!("{{{}}}", CLSID).into(); - let clsid_ptr = PCWSTR(clsid.as_ptr()).as_ptr(); - - let relying_party_id: HSTRING = RPID.into(); - let relying_party_id_ptr = PCWSTR(relying_party_id.as_ptr()).as_ptr(); - - // let aaguid: HSTRING = format!("{{{}}}", AAGUID).into(); - // let aaguid_ptr = PCWSTR(aaguid.as_ptr()).as_ptr(); - - // Example authenticator info blob - let cbor_authenticator_info = "A60182684649444F5F325F30684649444F5F325F310282637072666B686D61632D7365637265740350D548826E79B4DB40A3D811116F7E834904A362726BF5627570F5627576F5098168696E7465726E616C0A81A263616C672664747970656A7075626C69632D6B6579"; - let mut authenticator_info_bytes = hex::decode(cbor_authenticator_info).unwrap(); - - let add_authenticator_options = webauthn::ExperimentalWebAuthnPluginAddAuthenticatorOptions { - authenticator_name: authenticator_name_ptr, - plugin_clsid: clsid_ptr, - rpid: relying_party_id_ptr, - light_theme_logo: ptr::null(), - dark_theme_logo: ptr::null(), - cbor_authenticator_info_byte_count: authenticator_info_bytes.len() as u32, - cbor_authenticator_info: authenticator_info_bytes.as_mut_ptr(), - }; - - let plugin_signing_public_key_byte_count: u32 = 0; - let mut plugin_signing_public_key: c_uchar = 0; - let plugin_signing_public_key_ptr = &mut plugin_signing_public_key; - - let mut add_response = webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse { - plugin_operation_signing_key_byte_count: plugin_signing_public_key_byte_count, - plugin_operation_signing_key: plugin_signing_public_key_ptr, - }; - let mut add_response_ptr: *mut webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse = - &mut add_response; - - let result = unsafe { - delay_load::( - s!("webauthn.dll"), - s!("EXPERIMENTAL_WebAuthNPluginAddAuthenticator"), - ) - }; - - match result { - Some(api) => { - let result = unsafe { api(&add_authenticator_options, &mut add_response_ptr) }; - - if result.is_err() { - return Err(format!( - "Error: Error response from EXPERIMENTAL_WebAuthNPluginAddAuthenticator()\n{}", - result.message() - )); - } - - Ok(()) - }, - None => { - Err(String::from("Error: Can't complete add_authenticator(), as the function EXPERIMENTAL_WebAuthNPluginAddAuthenticator can't be found.")) - } - } -} - -type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "cdecl" fn( - pPluginAddAuthenticatorOptions: *const webauthn::ExperimentalWebAuthnPluginAddAuthenticatorOptions, - ppPluginAddAuthenticatorResponse: *mut *mut webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse, -) -> HRESULT; - -unsafe fn delay_load(library: PCSTR, function: PCSTR) -> Option { - let library = LoadLibraryExA(library, None, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); - - let Ok(library) = library else { - return None; - }; - - let address = GetProcAddress(library, function); - - if address.is_some() { - return Some(std::mem::transmute_copy(&address)); - } - - _ = FreeLibrary(library); - - None -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs new file mode 100644 index 00000000000..271e2911fb5 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs @@ -0,0 +1,253 @@ +use std::alloc::{alloc, Layout}; +use std::ptr; +use serde_json; +use windows_core::{HRESULT, s}; + +use crate::types::*; +use crate::utils::{self as util, delay_load}; +use crate::com_provider::ExperimentalWebAuthnPluginOperationResponse; +use crate::assert::RequestContext; + +// Windows API types for WebAuthn (from webauthn.h.sample) +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_RP_ENTITY_INFORMATION { + pub dwVersion: u32, + pub pwszId: *const u16, // PCWSTR + pub pwszName: *const u16, // PCWSTR + pub pwszIcon: *const u16, // PCWSTR +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_USER_ENTITY_INFORMATION { + pub dwVersion: u32, + pub cbId: u32, // DWORD + pub pbId: *const u8, // PBYTE + pub pwszName: *const u16, // PCWSTR + pub pwszIcon: *const u16, // PCWSTR + pub pwszDisplayName: *const u16, // PCWSTR +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_COSE_CREDENTIAL_PARAMETER { + pub dwVersion: u32, + pub pwszCredentialType: *const u16, // LPCWSTR + pub lAlg: i32, // LONG - COSE algorithm identifier +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { + pub cCredentialParameters: u32, + pub pCredentialParameters: *const WEBAUTHN_COSE_CREDENTIAL_PARAMETER, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_CREDENTIAL_LIST { + pub cCredentials: u32, + pub pCredentials: *const u8, // Placeholder +} + +// Make Credential Request structure (from sample header) +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST { + pub dwVersion: u32, + pub cbRpId: u32, + pub pbRpId: *const u8, + pub cbClientDataHash: u32, + pub pbClientDataHash: *const u8, + pub pRpInformation: *const WEBAUTHN_RP_ENTITY_INFORMATION, + pub pUserInformation: *const WEBAUTHN_USER_ENTITY_INFORMATION, + pub WebAuthNCredentialParameters: WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, // Matches C++ sample + pub CredentialList: WEBAUTHN_CREDENTIAL_LIST, + pub cbCborExtensionsMap: u32, + pub pbCborExtensionsMap: *const u8, + // Add other fields as needed... +} + +pub type PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST = *mut EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST; + +// Windows API function signatures +type EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequestFn = unsafe extern "stdcall" fn( + cbEncoded: u32, + pbEncoded: *const u8, + ppMakeCredentialRequest: *mut PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST, +) -> HRESULT; + +type EXPERIMENTAL_WebAuthNFreeDecodedMakeCredentialRequestFn = unsafe extern "stdcall" fn( + pMakeCredentialRequest: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST, +); + +// RAII wrapper for decoded make credential request +pub struct DecodedMakeCredentialRequest { + ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST, + free_fn: Option, +} + +impl DecodedMakeCredentialRequest { + fn new(ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST, free_fn: Option) -> Self { + Self { ptr, free_fn } + } + + pub fn as_ref(&self) -> &EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST { + unsafe { &*self.ptr } + } +} + +impl Drop for DecodedMakeCredentialRequest { + fn drop(&mut self) { + if !self.ptr.is_null() { + if let Some(free_fn) = self.free_fn { + util::message("Freeing decoded make credential request"); + unsafe { free_fn(self.ptr); } + } + } + } +} + +// Function to decode make credential request using Windows API +pub unsafe fn decode_make_credential_request(encoded_request: &[u8]) -> Result { + util::message("Attempting to decode make credential request using Windows API"); + + // Try to load the Windows API decode function + let decode_fn = match delay_load::( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequest"), + ) { + Some(func) => func, + None => { + return Err("Failed to load EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequest from webauthn.dll".to_string()); + } + }; + + // Try to load the free function (optional, might not be available in all versions) + let free_fn = delay_load::( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNFreeDecodedMakeCredentialRequest"), + ); + + + // Prepare parameters for the API call + let cb_encoded = encoded_request.len() as u32; + let pb_encoded = encoded_request.as_ptr(); + let mut pp_make_credential_request: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST = std::ptr::null_mut(); + + + // Call the Windows API function + let result = decode_fn( + cb_encoded, + pb_encoded, + &mut pp_make_credential_request, + ); + + // Check if the call succeeded (following C++ THROW_IF_FAILED pattern) + if result.is_err() { + util::message(&format!("ERROR: EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequest failed with HRESULT: 0x{:08x}", result.0)); + return Err(format!("Windows API call failed with HRESULT: 0x{:08x}", result.0)); + } + + if pp_make_credential_request.is_null() { + util::message("ERROR: Windows API succeeded but returned null pointer"); + return Err("Windows API returned null pointer".to_string()); + } + + + Ok(DecodedMakeCredentialRequest::new(pp_make_credential_request, free_fn)) +} + +/// Helper for registration requests +pub fn send_registration_request(rpid: &str, transaction_id: &str, context: &RequestContext) -> Option { + // Validate required fields + if rpid.is_empty() { + util::message("ERROR: RP ID is required but empty"); + return None; + } + + // Extract user ID from context - this is required for registration + let user_id = match &context.user_id { + Some(id) if !id.is_empty() => id.clone(), + _ => { + util::message("ERROR: User ID is required for registration but not provided"); + return None; + } + }; + + // Extract user name from context - this is required for registration + let user_name = match &context.user_name { + Some(name) if !name.is_empty() => name.clone(), + _ => { + util::message("ERROR: User name is required for registration but not provided"); + return None; + } + }; + + // Extract client data hash from context - this is required for WebAuthn + let client_data_hash = match &context.client_data_hash { + Some(hash) if !hash.is_empty() => hash.clone(), + _ => { + util::message("ERROR: Client data hash is required for registration but not provided"); + return None; + } + }; + + util::message(&format!("Registration request data - RP ID: {}, User ID: {} bytes, User name: {}, Client data hash: {} bytes, Algorithms: {:?}", + rpid, user_id.len(), user_name, client_data_hash.len(), context.supported_algorithms)); + + let request = PasskeyRegistrationRequest { + rp_id: rpid.to_string(), + transaction_id: transaction_id.to_string(), + user_id, + user_name, + client_data_hash, + user_verification: context.user_verification.unwrap_or(false), + supported_algorithms: context.supported_algorithms.clone(), + }; + + match serde_json::to_string(&request) { + Ok(request_json) => { + util::message(&format!("Sending registration request: {}", request_json)); + crate::ipc::send_passkey_request(RequestType::Registration, request_json, rpid) + }, + Err(e) => { + util::message(&format!("ERROR: Failed to serialize registration request: {}", e)); + None + } + } +} + +/// Creates a WebAuthn make credential response from Bitwarden's registration response +pub unsafe fn create_make_credential_response(credential_id: Vec, attestation_object: Vec) -> std::result::Result<*mut ExperimentalWebAuthnPluginOperationResponse, HRESULT> { + // Use the attestation object directly as the encoded response + let response_data = attestation_object; + let response_len = response_data.len(); + + // Allocate memory for the response data + let layout = Layout::from_size_align(response_len, 1).map_err(|_| HRESULT(-1))?; + let response_ptr = alloc(layout); + if response_ptr.is_null() { + return Err(HRESULT(-1)); + } + + // Copy response data + ptr::copy_nonoverlapping(response_data.as_ptr(), response_ptr, response_len); + + // Allocate memory for the response structure + let response_layout = Layout::new::(); + let operation_response_ptr = alloc(response_layout) as *mut ExperimentalWebAuthnPluginOperationResponse; + if operation_response_ptr.is_null() { + return Err(HRESULT(-1)); + } + + // Initialize the response + ptr::write(operation_response_ptr, ExperimentalWebAuthnPluginOperationResponse { + encoded_response_byte_count: response_len as u32, + encoded_response_pointer: response_ptr, + }); + + Ok(operation_response_ptr) +} + diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.h.sample b/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.h.sample new file mode 100644 index 00000000000..3e5bfcb80c9 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.h.sample @@ -0,0 +1,239 @@ + + +/* this ALWAYS GENERATED file contains the definitions for the interfaces */ + + + /* File created by MIDL compiler version 8.01.0628 */ +/* @@MIDL_FILE_HEADING( ) */ + + + +/* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCNDR_H_VERSION__ +#define __REQUIRED_RPCNDR_H_VERSION__ 501 +#endif + +/* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCSAL_H_VERSION__ +#define __REQUIRED_RPCSAL_H_VERSION__ 100 +#endif + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __RPCNDR_H_VERSION__ +#error this stub requires an updated version of +#endif /* __RPCNDR_H_VERSION__ */ + +#ifndef COM_NO_WINDOWS_H +#include "windows.h" +#include "ole2.h" +#endif /*COM_NO_WINDOWS_H*/ + +#ifndef __pluginauthenticator_h__ +#define __pluginauthenticator_h__ + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +#ifndef DECLSPEC_XFGVIRT +#if defined(_CONTROL_FLOW_GUARD_XFG) +#define DECLSPEC_XFGVIRT(base, func) __declspec(xfg_virtual(base, func)) +#else +#define DECLSPEC_XFGVIRT(base, func) +#endif +#endif + +/* Forward Declarations */ + +#ifndef __EXPERIMENTAL_IPluginAuthenticator_FWD_DEFINED__ +#define __EXPERIMENTAL_IPluginAuthenticator_FWD_DEFINED__ +typedef interface EXPERIMENTAL_IPluginAuthenticator EXPERIMENTAL_IPluginAuthenticator; + +#endif /* __EXPERIMENTAL_IPluginAuthenticator_FWD_DEFINED__ */ + + +/* header files for imported files */ +#include "oaidl.h" +#include "webauthn.h" + +#ifdef __cplusplus +extern "C"{ +#endif + + +/* interface __MIDL_itf_pluginauthenticator_0000_0000 */ +/* [local] */ + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST + { + HWND hWnd; + GUID transactionId; + DWORD cbRequestSignature; + /* [size_is] */ byte *pbRequestSignature; + DWORD cbEncodedRequest; + /* [size_is] */ byte *pbEncodedRequest; + } EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST *EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_REQUEST; + +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE + { + DWORD cbEncodedResponse; + /* [size_is] */ byte *pbEncodedResponse; + } EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE *EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE; + +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_RESPONSE; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST + { + GUID transactionId; + DWORD cbRequestSignature; + /* [size_is] */ byte *pbRequestSignature; + } EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST *EXPERIMENTAL_PWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; + +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; + + + +extern RPC_IF_HANDLE __MIDL_itf_pluginauthenticator_0000_0000_v0_0_c_ifspec; +extern RPC_IF_HANDLE __MIDL_itf_pluginauthenticator_0000_0000_v0_0_s_ifspec; + +#ifndef __EXPERIMENTAL_IPluginAuthenticator_INTERFACE_DEFINED__ +#define __EXPERIMENTAL_IPluginAuthenticator_INTERFACE_DEFINED__ + +/* interface EXPERIMENTAL_IPluginAuthenticator */ +/* [unique][version][uuid][object] */ + + +EXTERN_C const IID IID_EXPERIMENTAL_IPluginAuthenticator; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("e6466e9a-b2f3-47c5-b88d-89bc14a8d998") + EXPERIMENTAL_IPluginAuthenticator : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE EXPERIMENTAL_PluginMakeCredential( + /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE *response) = 0; + + virtual HRESULT STDMETHODCALLTYPE EXPERIMENTAL_PluginGetAssertion( + /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE *response) = 0; + + virtual HRESULT STDMETHODCALLTYPE EXPERIMENTAL_PluginCancelOperation( + /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST request) = 0; + + }; + + +#else /* C style interface */ + + typedef struct EXPERIMENTAL_IPluginAuthenticatorVtbl + { + BEGIN_INTERFACE + + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + __RPC__in EXPERIMENTAL_IPluginAuthenticator * This, + /* [in] */ __RPC__in REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + DECLSPEC_XFGVIRT(IUnknown, AddRef) + ULONG ( STDMETHODCALLTYPE *AddRef )( + __RPC__in EXPERIMENTAL_IPluginAuthenticator * This); + + DECLSPEC_XFGVIRT(IUnknown, Release) + ULONG ( STDMETHODCALLTYPE *Release )( + __RPC__in EXPERIMENTAL_IPluginAuthenticator * This); + + DECLSPEC_XFGVIRT(EXPERIMENTAL_IPluginAuthenticator, EXPERIMENTAL_PluginMakeCredential) + HRESULT ( STDMETHODCALLTYPE *EXPERIMENTAL_PluginMakeCredential )( + __RPC__in EXPERIMENTAL_IPluginAuthenticator * This, + /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE *response); + + DECLSPEC_XFGVIRT(EXPERIMENTAL_IPluginAuthenticator, EXPERIMENTAL_PluginGetAssertion) + HRESULT ( STDMETHODCALLTYPE *EXPERIMENTAL_PluginGetAssertion )( + __RPC__in EXPERIMENTAL_IPluginAuthenticator * This, + /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE *response); + + DECLSPEC_XFGVIRT(EXPERIMENTAL_IPluginAuthenticator, EXPERIMENTAL_PluginCancelOperation) + HRESULT ( STDMETHODCALLTYPE *EXPERIMENTAL_PluginCancelOperation )( + __RPC__in EXPERIMENTAL_IPluginAuthenticator * This, + /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST request); + + END_INTERFACE + } EXPERIMENTAL_IPluginAuthenticatorVtbl; + + interface EXPERIMENTAL_IPluginAuthenticator + { + CONST_VTBL struct EXPERIMENTAL_IPluginAuthenticatorVtbl *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define EXPERIMENTAL_IPluginAuthenticator_QueryInterface(This,riid,ppvObject) \ + ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) + +#define EXPERIMENTAL_IPluginAuthenticator_AddRef(This) \ + ( (This)->lpVtbl -> AddRef(This) ) + +#define EXPERIMENTAL_IPluginAuthenticator_Release(This) \ + ( (This)->lpVtbl -> Release(This) ) + + +#define EXPERIMENTAL_IPluginAuthenticator_EXPERIMENTAL_PluginMakeCredential(This,request,response) \ + ( (This)->lpVtbl -> EXPERIMENTAL_PluginMakeCredential(This,request,response) ) + +#define EXPERIMENTAL_IPluginAuthenticator_EXPERIMENTAL_PluginGetAssertion(This,request,response) \ + ( (This)->lpVtbl -> EXPERIMENTAL_PluginGetAssertion(This,request,response) ) + +#define EXPERIMENTAL_IPluginAuthenticator_EXPERIMENTAL_PluginCancelOperation(This,request) \ + ( (This)->lpVtbl -> EXPERIMENTAL_PluginCancelOperation(This,request) ) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + + + +#endif /* __EXPERIMENTAL_IPluginAuthenticator_INTERFACE_DEFINED__ */ + + +/* Additional Prototypes for ALL interfaces */ + +unsigned long __RPC_USER HWND_UserSize( __RPC__in unsigned long *, unsigned long , __RPC__in HWND * ); +unsigned char * __RPC_USER HWND_UserMarshal( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in HWND * ); +unsigned char * __RPC_USER HWND_UserUnmarshal(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out HWND * ); +void __RPC_USER HWND_UserFree( __RPC__in unsigned long *, __RPC__in HWND * ); + +unsigned long __RPC_USER HWND_UserSize64( __RPC__in unsigned long *, unsigned long , __RPC__in HWND * ); +unsigned char * __RPC_USER HWND_UserMarshal64( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in HWND * ); +unsigned char * __RPC_USER HWND_UserUnmarshal64(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out HWND * ); +void __RPC_USER HWND_UserFree64( __RPC__in unsigned long *, __RPC__in HWND * ); + +/* end of Additional Prototypes */ + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs deleted file mode 100644 index 40dacec2533..00000000000 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs +++ /dev/null @@ -1,126 +0,0 @@ -/* - This file exposes safe functions and types for interacting with the experimental - Windows Plugin Authenticator API defined here: - - https://github.com/microsoft/webauthn/blob/master/experimental/pluginauthenticator.h - - The Factory pattern & COM interactions are based on the examples provided here: - - https://github.com/microsoft/windows-rs/blob/bb15076311bf185400ecd244d47596b8415450fa/crates/tests/libs/implement/tests/class_factory.rs - - https://github.com/microsoft/windows-rs/pull/3531 - - https://kennykerr.ca/rust-getting-started/how-to-implement-com-interface.html - - https://github.com/bitwarden/clients/pull/10204/files#diff-a4de81fd5a2389d7b512dd37989a42a452fe36cbc1f32d16d5832880355d5669R106 -*/ - -use windows::Win32::System::Com::*; -//use windows::{Foundation::*, Win32::System::Com::*}; -use windows_core::*; - -use crate::util; - -/// Used when creating and asserting credentials. -/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST -/// Header File Usage: EXPERIMENTAL_PluginMakeCredential() -/// EXPERIMENTAL_PluginGetAssertion() -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ExperimentalWebAuthnPluginOperationRequest { - pub window_handle: windows::Win32::Foundation::HWND, - pub transaction_id: windows_core::GUID, - pub request_signature_byte_count: u32, - pub request_signature_pointer: *mut u8, - pub encoded_request_byte_count: u32, - pub encoded_request_pointer: *mut u8, -} - -/// Used as a response when creating and asserting credentials. -/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE -/// Header File Usage: EXPERIMENTAL_PluginMakeCredential() -/// EXPERIMENTAL_PluginGetAssertion() -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ExperimentalWebAuthnPluginOperationResponse { - pub encoded_response_byte_count: u32, - pub encoded_response_pointer: *mut u8, -} - -/// Used to cancel an operation. -/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST -/// Header File Usage: EXPERIMENTAL_PluginCancelOperation() -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ExperimentalWebAuthnPluginCancelOperationRequest { - pub transaction_id: windows_core::GUID, - pub request_signature_byte_count: u32, - pub request_signature_pointer: *mut u8, -} - -#[interface("e6466e9a-b2f3-47c5-b88d-89bc14a8d998")] -pub unsafe trait EXPERIMENTAL_IPluginAuthenticator: IUnknown { - fn EXPERIMENTAL_PluginMakeCredential( - &self, - request: *const ExperimentalWebAuthnPluginOperationRequest, - response: *mut *mut ExperimentalWebAuthnPluginOperationResponse, - ) -> HRESULT; - fn EXPERIMENTAL_PluginGetAssertion( - &self, - request: *const ExperimentalWebAuthnPluginOperationRequest, - response: *mut *mut ExperimentalWebAuthnPluginOperationResponse, - ) -> HRESULT; - fn EXPERIMENTAL_PluginCancelOperation( - &self, - request: *const ExperimentalWebAuthnPluginCancelOperationRequest, - ) -> HRESULT; -} - -#[implement(EXPERIMENTAL_IPluginAuthenticator)] -pub struct PluginAuthenticatorComObject; - -#[implement(IClassFactory)] -pub struct Factory; - -impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl { - unsafe fn EXPERIMENTAL_PluginMakeCredential( - &self, - request: *const ExperimentalWebAuthnPluginOperationRequest, - response: *mut *mut ExperimentalWebAuthnPluginOperationResponse, - ) -> HRESULT { - //panic!("EXPERIMENTAL_PluginMakeCredential() called"); - util::message(String::from("EXPERIMENTAL_PluginMakeCredential() called")); - HRESULT(0) - } - - unsafe fn EXPERIMENTAL_PluginGetAssertion( - &self, - request: *const ExperimentalWebAuthnPluginOperationRequest, - response: *mut *mut ExperimentalWebAuthnPluginOperationResponse, - ) -> HRESULT { - //panic!("EXPERIMENTAL_PluginGetAssertion() called"); - util::message(String::from("EXPERIMENTAL_PluginGetAssertion() called")); - HRESULT(0) - } - - unsafe fn EXPERIMENTAL_PluginCancelOperation( - &self, - request: *const ExperimentalWebAuthnPluginCancelOperationRequest, - ) -> HRESULT { - //panic!("EXPERIMENTAL_PluginCancelOperation() called"); - util::message(String::from("EXPERIMENTAL_PluginCancelOperation() called")); - HRESULT(0) - } -} - -impl IClassFactory_Impl for Factory_Impl { - fn CreateInstance( - &self, - outer: Ref, - iid: *const GUID, - object: *mut *mut core::ffi::c_void, - ) -> Result<()> { - let unknown: IInspectable = PluginAuthenticatorComObject.into(); // TODO: IUnknown ? - unsafe { unknown.query(iid, object).ok() } - } - - fn LockServer(&self, lock: BOOL) -> Result<()> { - Ok(()) - } -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/sync.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/sync.rs new file mode 100644 index 00000000000..b8d71e454da --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/sync.rs @@ -0,0 +1,207 @@ +use serde_json; +use hex; + +use crate::types::*; +use crate::utils::{self as util, wstr_to_string}; +use crate::webauthn::*; +use crate::ipc::send_passkey_request; + +/// Helper for sync requests - requests credentials from Electron for a specific RP ID +pub fn send_sync_request(rpid: &str) -> Option { + util::message(&format!("[SYNC] send_sync_request called for RP ID: {}", rpid)); + + let request = PasskeySyncRequest { + rp_id: rpid.to_string(), + }; + + util::message(&format!("[SYNC] Created sync request for RP ID: {}", rpid)); + + match serde_json::to_string(&request) { + Ok(request_json) => { + util::message(&format!("[SYNC] Serialized sync request to JSON: {}", request_json)); + util::message(&format!("[SYNC] Sending sync request to Electron via IPC")); + let response = send_passkey_request(RequestType::Sync, request_json, rpid); + match &response { + Some(resp) => util::message(&format!("[SYNC] Received response from Electron: {:?}", resp)), + None => util::message("[SYNC] No response received from Electron"), + } + response + }, + Err(e) => { + util::message(&format!("[SYNC] ERROR: Failed to serialize sync request: {}", e)); + None + } + } +} + +/// Initiates credential sync from Electron to Windows - called when Electron wants to push credentials to Windows +pub fn sync_credentials_to_windows(credentials: Vec, plugin_clsid: &str) -> Result<(), String> { + util::message(&format!("[SYNC_TO_WIN] sync_credentials_to_windows called with {} credentials for plugin CLSID: {}", credentials.len(), plugin_clsid)); + + // Format CLSID with curly braces to match Windows registration format + let formatted_clsid = format!("{{{}}}", plugin_clsid); + + if credentials.is_empty() { + util::message("[SYNC_TO_WIN] No credentials to sync, proceeding with empty sync"); + } + + // Convert Bitwarden credentials to Windows credential details + let mut win_credentials = Vec::new(); + + for (i, cred) in credentials.iter().enumerate() { + let truncated_cred_id = if cred.credential_id.len() > 16 { + format!("{}...", hex::encode(&cred.credential_id[..16])) + } else { + hex::encode(&cred.credential_id) + }; + let truncated_user_id = if cred.user_id.len() > 16 { + format!("{}...", hex::encode(&cred.user_id[..16])) + } else { + hex::encode(&cred.user_id) + }; + + util::message(&format!("[SYNC_TO_WIN] Converting credential {}: RP ID: {}, User: {}, Credential ID: {} ({} bytes), User ID: {} ({} bytes)", + i + 1, cred.rp_id, cred.user_name, truncated_cred_id, cred.credential_id.len(), truncated_user_id, cred.user_id.len())); + + let win_cred = ExperimentalWebAuthnPluginCredentialDetails::create_from_bytes( + cred.credential_id.clone(), // Pass raw bytes + cred.rp_id.clone(), + cred.rp_id.clone(), // Use RP ID as friendly name for now + cred.user_id.clone(), // Pass raw bytes + cred.user_name.clone(), + cred.user_name.clone(), // Use user name as display name for now + ); + + win_credentials.push(win_cred); + util::message(&format!("[SYNC_TO_WIN] Converted credential {} to Windows format", i + 1)); + } + + // Create credentials list + let credentials_list = ExperimentalWebAuthnPluginCredentialDetailsList::create( + formatted_clsid.clone(), + win_credentials + ); + + // First try to remove all existing credentials for this plugin + util::message("Attempting to remove all existing credentials before sync..."); + match remove_all_credentials(formatted_clsid.clone()) { + Ok(()) => { + util::message("Successfully removed existing credentials"); + }, + Err(e) if e.contains("can't be loaded") => { + util::message("RemoveAllCredentials function not available - this is expected for some Windows versions"); + // This is fine, the function might not exist in all versions + }, + Err(e) => { + util::message(&format!("Warning: Failed to remove existing credentials: {}", e)); + // Continue anyway, as this might be the first sync or an older Windows version + } + } + + // Add the new credentials (only if we have any) + if credentials.is_empty() { + util::message("No credentials to add to Windows - sync completed successfully"); + Ok(()) + } else { + util::message("Adding new credentials to Windows..."); + match add_credentials(credentials_list) { + Ok(()) => { + util::message("Successfully synced credentials to Windows"); + Ok(()) + }, + Err(e) => { + util::message(&format!("ERROR: Failed to add credentials to Windows: {}", e)); + Err(e) + } + } + } +} + +/// Gets all credentials from Windows for a specific plugin - used when Electron requests current state +pub fn get_credentials_from_windows(plugin_clsid: &str) -> Result, String> { + util::message(&format!("Getting all credentials from Windows for plugin CLSID: {}", plugin_clsid)); + + // Format CLSID with curly braces to match Windows registration format + let formatted_clsid = format!("{{{}}}", plugin_clsid); + + match get_all_credentials(formatted_clsid) { + Ok(Some(credentials_list)) => { + util::message(&format!("Retrieved {} credentials from Windows", credentials_list.credential_count)); + + let mut bitwarden_credentials = Vec::new(); + + // Convert Windows credentials to Bitwarden format + unsafe { + let credentials_array = std::slice::from_raw_parts( + credentials_list.credentials, + credentials_list.credential_count as usize + ); + + for &cred_ptr in credentials_array { + if !cred_ptr.is_null() { + let cred = &*cred_ptr; + + // Convert credential data back to Bitwarden format + let credential_id = if cred.credential_id_byte_count > 0 && !cred.credential_id_pointer.is_null() { + let id_slice = std::slice::from_raw_parts( + cred.credential_id_pointer, + cred.credential_id_byte_count as usize + ); + // Assume it's hex-encoded, try to decode + hex::decode(std::str::from_utf8(id_slice).unwrap_or("")).unwrap_or_else(|_| id_slice.to_vec()) + } else { + Vec::new() + }; + + let rp_id = if !cred.rpid.is_null() { + wstr_to_string(cred.rpid).unwrap_or_default() + } else { + String::new() + }; + + let user_name = if !cred.user_name.is_null() { + wstr_to_string(cred.user_name).unwrap_or_default() + } else { + String::new() + }; + + let user_id = if cred.user_id_byte_count > 0 && !cred.user_id_pointer.is_null() { + // Convert from UTF-8 bytes back to Vec + let user_id_slice = std::slice::from_raw_parts( + cred.user_id_pointer, + cred.user_id_byte_count as usize + ); + // Try to decode as hex string, or use raw bytes + let user_id_str = std::str::from_utf8(user_id_slice).unwrap_or(""); + hex::decode(user_id_str).unwrap_or_else(|_| user_id_slice.to_vec()) + } else { + Vec::new() + }; + + let synced_cred = SyncedCredential { + credential_id, + rp_id, + user_name, + user_id, + }; + + util::message(&format!("Converted Windows credential: RP ID: {}, User: {}, Credential ID: {} bytes", + synced_cred.rp_id, synced_cred.user_name, synced_cred.credential_id.len())); + + bitwarden_credentials.push(synced_cred); + } + } + } + + Ok(bitwarden_credentials) + }, + Ok(None) => { + util::message("No credentials found in Windows"); + Ok(Vec::new()) + }, + Err(e) => { + util::message(&format!("ERROR: Failed to get credentials from Windows: {}", e)); + Err(e) + } + } +} \ No newline at end of file diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs new file mode 100644 index 00000000000..43aafd0b975 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs @@ -0,0 +1,92 @@ +use tokio::sync::oneshot; + +/// Assertion request structure +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeyAssertionRequest { + pub rp_id: String, + pub transaction_id: String, + pub client_data_hash: Vec, + pub allowed_credentials: Vec>, + pub user_verification: bool, +} + +/// Registration request structure +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeyRegistrationRequest { + pub rp_id: String, + pub transaction_id: String, + pub user_id: Vec, + pub user_name: String, + pub client_data_hash: Vec, + pub user_verification: bool, + pub supported_algorithms: Vec, // COSE algorithm identifiers +} + +/// Sync request structure +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeySyncRequest { + pub rp_id: String, +} + +/// Union type for different request types +#[derive(Debug, Clone)] +pub enum PasskeyRequest { + AssertionRequest(PasskeyAssertionRequest), + RegistrationRequest(PasskeyRegistrationRequest), + SyncRequest(PasskeySyncRequest), +} + +/// Response types for different operations - kept as tagged enum for JSON compatibility +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum PasskeyResponse { + #[serde(rename = "assertion_response",rename_all = "camelCase")] + AssertionResponse { + credential_id: Vec, + authenticator_data: Vec, + signature: Vec, + user_handle: Vec, + }, + #[serde(rename = "registration_response",rename_all = "camelCase")] + RegistrationResponse { + credential_id: Vec, + attestation_object: Vec, + }, + #[serde(rename = "sync_response",rename_all = "camelCase")] + SyncResponse { + credentials: Vec, + }, + #[serde(rename = "error",rename_all = "camelCase")] + Error { + message: String, + }, +} + +/// Credential data for sync operations +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SyncedCredential { + pub credential_id: Vec, + pub rp_id: String, + pub user_name: String, + pub user_id: Vec, +} + +/// Request type enumeration for type discrimination +#[derive(Debug, Clone)] +pub enum RequestType { + Assertion, + Registration, + Sync, +} + +/// Internal request event with response channel and serializable request data +#[derive(Debug)] +pub struct RequestEvent { + pub request_type: RequestType, + pub request_json: String, + pub response_sender: oneshot::Sender, +} \ No newline at end of file diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/util.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/util.rs deleted file mode 100644 index b1f41564322..00000000000 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/util.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::ffi::OsString; -use std::os::windows::ffi::OsStrExt; - -use serde_json::json; -use windows::Win32::Foundation::*; -use windows::Win32::System::LibraryLoader::*; -use windows_core::*; - -pub unsafe fn delay_load(library: PCSTR, function: PCSTR) -> Option { - let library = LoadLibraryExA(library, None, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); - - let Ok(library) = library else { - return None; - }; - - let address = GetProcAddress(library, function); - - if address.is_some() { - return Some(std::mem::transmute_copy(&address)); - } - - _ = FreeLibrary(library); - - None -} - -pub trait WindowsString { - fn into_win_utf8(self: Self) -> (*mut u8, u32); - fn into_win_utf16(self: Self) -> (*mut u16, u32); - fn into_win_utf16_wide(self: Self) -> (*mut u16, u32); -} - -impl WindowsString for String { - fn into_win_utf8(self: Self) -> (*mut u8, u32) { - let mut v = self.into_bytes(); - v.push(0); - - (v.as_mut_ptr(), v.len() as u32) - } - - fn into_win_utf16(self: Self) -> (*mut u16, u32) { - let mut v: Vec = self.encode_utf16().collect(); - v.push(0); - - (v.as_mut_ptr(), v.len() as u32) - } - - fn into_win_utf16_wide(self: Self) -> (*mut u16, u32) { - let mut v: Vec = OsString::from(self).encode_wide().collect(); - v.push(0); - - (v.as_mut_ptr(), v.len() as u32) - } -} - -pub fn message(message: String) { - let json_data = json!({ - "message": message, - }); - - let request = reqwest::blocking::Client::new(); - let _ = request - .post("http://127.0.0.1:3000/message") - .json(&json_data) - .send(); -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/utils.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/utils.rs new file mode 100644 index 00000000000..83bda347c88 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/utils.rs @@ -0,0 +1,109 @@ +use std::ffi::OsString; +use std::os::windows::ffi::OsStrExt; + +use std::fs::{OpenOptions, create_dir_all}; +use std::io::Write; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::path::Path; + +use windows::Win32::Foundation::*; +use windows::Win32::System::LibraryLoader::*; +use windows_core::*; + +pub unsafe fn delay_load(library: PCSTR, function: PCSTR) -> Option { + let library = LoadLibraryExA(library, None, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + + let Ok(library) = library else { + return None; + }; + + let address = GetProcAddress(library, function); + + if address.is_some() { + return Some(std::mem::transmute_copy(&address)); + } + + _ = FreeLibrary(library); + + None +} + +pub trait WindowsString { + fn into_win_utf8(self: Self) -> (*mut u8, u32); + fn into_win_utf16(self: Self) -> (*mut u16, u32); + fn into_win_utf16_wide(self: Self) -> (*mut u16, u32); +} + +impl WindowsString for String { + fn into_win_utf8(self: Self) -> (*mut u8, u32) { + let mut v = self.into_bytes(); + v.push(0); + + (v.as_mut_ptr(), v.len() as u32) + } + + fn into_win_utf16(self: Self) -> (*mut u16, u32) { + let mut v: Vec = self.encode_utf16().collect(); + v.push(0); + + (v.as_mut_ptr(), v.len() as u32) + } + + fn into_win_utf16_wide(self: Self) -> (*mut u16, u32) { + let mut v: Vec = OsString::from(self).encode_wide().collect(); + v.push(0); + + (v.as_mut_ptr(), v.len() as u32) + } +} + +pub fn file_log(msg: &str) { + let log_path = "C:\\temp\\bitwarden_com_debug.log"; + + // Create the temp directory if it doesn't exist + if let Some(parent) = Path::new(log_path).parent() { + let _ = create_dir_all(parent); + } + + if let Ok(mut file) = OpenOptions::new() + .create(true) + .append(true) + .open(log_path) + { + let now = SystemTime::now(); + let timestamp = match now.duration_since(UNIX_EPOCH) { + Ok(duration) => { + let total_secs = duration.as_secs(); + let millis = duration.subsec_millis(); + let secs = total_secs % 60; + let mins = (total_secs / 60) % 60; + let hours = (total_secs / 3600) % 24; + format!("{:02}:{:02}:{:02}.{:03}", hours, mins, secs, millis) + }, + Err(_) => "??:??:??.???".to_string() + }; + + let _ = writeln!(file, "[{}] {}", timestamp, msg); + } +} + +pub fn message(message: &str) { + file_log(message) +} + +// Helper function to convert Windows wide string (UTF-16) to Rust String +pub unsafe fn wstr_to_string(wstr_ptr: *const u16) -> std::result::Result { + if wstr_ptr.is_null() { + return Ok(String::new()); + } + + // Find the length of the null-terminated wide string + let mut len = 0; + while *wstr_ptr.add(len) != 0 { + len += 1; + } + + // Convert to Rust string + let wide_slice = std::slice::from_raw_parts(wstr_ptr, len); + String::from_utf16(wide_slice) +} \ No newline at end of file diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.h.sample b/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.h.sample new file mode 100644 index 00000000000..2f50e771bed --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.h.sample @@ -0,0 +1,1727 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef __WEBAUTHN_H_ +#define __WEBAUTHN_H_ + +#pragma once + +#include + +#pragma region Desktop Family or OneCore Family +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WINAPI +#define WINAPI __stdcall +#endif + +#ifndef INITGUID +#define INITGUID +#include +#undef INITGUID +#else +#include +#endif + +//+------------------------------------------------------------------------------------------ +// API Version Information. +// Caller should check for WebAuthNGetApiVersionNumber to check the presence of relevant APIs +// and features for their usage. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_API_VERSION_1 1 +// WEBAUTHN_API_VERSION_1 : Baseline Version +// Data Structures and their sub versions: +// - WEBAUTHN_RP_ENTITY_INFORMATION : 1 +// - WEBAUTHN_USER_ENTITY_INFORMATION : 1 +// - WEBAUTHN_CLIENT_DATA : 1 +// - WEBAUTHN_COSE_CREDENTIAL_PARAMETER : 1 +// - WEBAUTHN_COSE_CREDENTIAL_PARAMETERS : Not Applicable +// - WEBAUTHN_CREDENTIAL : 1 +// - WEBAUTHN_CREDENTIALS : Not Applicable +// - WEBAUTHN_CREDENTIAL_EX : 1 +// - WEBAUTHN_CREDENTIAL_LIST : Not Applicable +// - WEBAUTHN_EXTENSION : Not Applicable +// - WEBAUTHN_EXTENSIONS : Not Applicable +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 3 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 4 +// - WEBAUTHN_COMMON_ATTESTATION : 1 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 3 +// - WEBAUTHN_ASSERTION : 1 +// Extensions: +// - WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET +// APIs: +// - WebAuthNGetApiVersionNumber +// - WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable +// - WebAuthNAuthenticatorMakeCredential +// - WebAuthNAuthenticatorGetAssertion +// - WebAuthNFreeCredentialAttestation +// - WebAuthNFreeAssertion +// - WebAuthNGetCancellationId +// - WebAuthNCancelCurrentOperation +// - WebAuthNGetErrorName +// - WebAuthNGetW3CExceptionDOMError +// Transports: +// - WEBAUTHN_CTAP_TRANSPORT_USB +// - WEBAUTHN_CTAP_TRANSPORT_NFC +// - WEBAUTHN_CTAP_TRANSPORT_BLE +// - WEBAUTHN_CTAP_TRANSPORT_INTERNAL + +#define WEBAUTHN_API_VERSION_2 2 +// WEBAUTHN_API_VERSION_2 : Delta From WEBAUTHN_API_VERSION_1 +// Added Extensions: +// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT +// + +#define WEBAUTHN_API_VERSION_3 3 +// WEBAUTHN_API_VERSION_3 : Delta From WEBAUTHN_API_VERSION_2 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 4 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 5 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 4 +// - WEBAUTHN_ASSERTION : 2 +// Added Extensions: +// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB +// - WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH +// + +#define WEBAUTHN_API_VERSION_4 4 +// WEBAUTHN_API_VERSION_4 : Delta From WEBAUTHN_API_VERSION_3 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 5 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 6 +// - WEBAUTHN_ASSERTION : 3 +// - WEBAUTHN_CREDENTIAL_DETAILS : 1 +// APIs: +// - WebAuthNGetPlatformCredentialList +// - WebAuthNFreePlatformCredentialList +// - WebAuthNDeletePlatformCredential +// + +#define WEBAUTHN_API_VERSION_5 5 +// WEBAUTHN_API_VERSION_5 : Delta From WEBAUTHN_API_VERSION_4 +// Data Structures and their sub versions: +// - WEBAUTHN_CREDENTIAL_DETAILS : 2 +// Extension Changes: +// - Enabled LARGE_BLOB Support +// + +#define WEBAUTHN_API_VERSION_6 6 +// WEBAUTHN_API_VERSION_6 : Delta From WEBAUTHN_API_VERSION_5 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 6 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 5 +// - WEBAUTHN_ASSERTION : 4 +// Transports: +// - WEBAUTHN_CTAP_TRANSPORT_HYBRID + +#define WEBAUTHN_API_VERSION_7 7 +// WEBAUTHN_API_VERSION_7 : Delta From WEBAUTHN_API_VERSION_6 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 7 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 7 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 6 +// - WEBAUTHN_ASSERTION : 5 + +#define WEBAUTHN_API_VERSION_8 8 +// WEBAUTHN_API_VERSION_8 : Delta From WEBAUTHN_API_VERSION_7 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 8 +// - WEBAUTHN_CREDENTIAL_DETAILS : 3 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 7 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 8 + +#define WEBAUTHN_API_CURRENT_VERSION WEBAUTHN_API_VERSION_8 + +//+------------------------------------------------------------------------------------------ +// Information about an RP Entity +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_RP_ENTITY_INFORMATION { + // Version of this structure, to allow for modifications in the future. + // This field is required and should be set to CURRENT_VERSION above. + DWORD dwVersion; + + // Identifier for the RP. This field is required. + PCWSTR pwszId; + + // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site". + // This field is required. + PCWSTR pwszName; + + // Optional URL pointing to RP's logo. + PCWSTR pwszIcon; +} WEBAUTHN_RP_ENTITY_INFORMATION, *PWEBAUTHN_RP_ENTITY_INFORMATION; +typedef const WEBAUTHN_RP_ENTITY_INFORMATION *PCWEBAUTHN_RP_ENTITY_INFORMATION; + +//+------------------------------------------------------------------------------------------ +// Information about an User Entity +//------------------------------------------------------------------------------------------- +#define WEBAUTHN_MAX_USER_ID_LENGTH 64 + +#define WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_USER_ENTITY_INFORMATION { + // Version of this structure, to allow for modifications in the future. + // This field is required and should be set to CURRENT_VERSION above. + DWORD dwVersion; + + // Identifier for the User. This field is required. + DWORD cbId; + _Field_size_bytes_(cbId) + PBYTE pbId; + + // Contains a detailed name for this account, such as "john.p.smith@example.com". + PCWSTR pwszName; + + // Optional URL that can be used to retrieve an image containing the user's current avatar, + // or a data URI that contains the image data. + PCWSTR pwszIcon; + + // For User: Contains the friendly name associated with the user account by the Relying Party, such as "John P. Smith". + PCWSTR pwszDisplayName; +} WEBAUTHN_USER_ENTITY_INFORMATION, *PWEBAUTHN_USER_ENTITY_INFORMATION; +typedef const WEBAUTHN_USER_ENTITY_INFORMATION *PCWEBAUTHN_USER_ENTITY_INFORMATION; + +//+------------------------------------------------------------------------------------------ +// Information about client data. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_HASH_ALGORITHM_SHA_256 L"SHA-256" +#define WEBAUTHN_HASH_ALGORITHM_SHA_384 L"SHA-384" +#define WEBAUTHN_HASH_ALGORITHM_SHA_512 L"SHA-512" + +#define WEBAUTHN_CLIENT_DATA_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_CLIENT_DATA { + // Version of this structure, to allow for modifications in the future. + // This field is required and should be set to CURRENT_VERSION above. + DWORD dwVersion; + + // Size of the pbClientDataJSON field. + DWORD cbClientDataJSON; + // UTF-8 encoded JSON serialization of the client data. + _Field_size_bytes_(cbClientDataJSON) + PBYTE pbClientDataJSON; + + // Hash algorithm ID used to hash the pbClientDataJSON field. + LPCWSTR pwszHashAlgId; +} WEBAUTHN_CLIENT_DATA, *PWEBAUTHN_CLIENT_DATA; +typedef const WEBAUTHN_CLIENT_DATA *PCWEBAUTHN_CLIENT_DATA; + +//+------------------------------------------------------------------------------------------ +// Information about credential parameters. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY L"public-key" + +#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256 -7 +#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384 -35 +#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P521_WITH_SHA512 -36 + +#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256 -257 +#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA384 -258 +#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA512 -259 + +#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA256 -37 +#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA384 -38 +#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA512 -39 + +#define WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETER { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Well-known credential type specifying a credential to create. + LPCWSTR pwszCredentialType; + + // Well-known COSE algorithm specifying the algorithm to use for the credential. + LONG lAlg; +} WEBAUTHN_COSE_CREDENTIAL_PARAMETER, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETER; +typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETER *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETER; + +typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { + DWORD cCredentialParameters; + _Field_size_(cCredentialParameters) + PWEBAUTHN_COSE_CREDENTIAL_PARAMETER pCredentialParameters; +} WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETERS; +typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS; + +//+------------------------------------------------------------------------------------------ +// Information about credential. +//------------------------------------------------------------------------------------------- +#define WEBAUTHN_CREDENTIAL_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_CREDENTIAL { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Size of pbID. + DWORD cbId; + // Unique ID for this particular credential. + _Field_size_bytes_(cbId) + PBYTE pbId; + + // Well-known credential type specifying what this particular credential is. + LPCWSTR pwszCredentialType; +} WEBAUTHN_CREDENTIAL, *PWEBAUTHN_CREDENTIAL; +typedef const WEBAUTHN_CREDENTIAL *PCWEBAUTHN_CREDENTIAL; + +typedef struct _WEBAUTHN_CREDENTIALS { + DWORD cCredentials; + _Field_size_(cCredentials) + PWEBAUTHN_CREDENTIAL pCredentials; +} WEBAUTHN_CREDENTIALS, *PWEBAUTHN_CREDENTIALS; +typedef const WEBAUTHN_CREDENTIALS *PCWEBAUTHN_CREDENTIALS; + +//+------------------------------------------------------------------------------------------ +// Information about credential with extra information, such as, dwTransports +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CTAP_TRANSPORT_USB 0x00000001 +#define WEBAUTHN_CTAP_TRANSPORT_NFC 0x00000002 +#define WEBAUTHN_CTAP_TRANSPORT_BLE 0x00000004 +#define WEBAUTHN_CTAP_TRANSPORT_TEST 0x00000008 +#define WEBAUTHN_CTAP_TRANSPORT_INTERNAL 0x00000010 +#define WEBAUTHN_CTAP_TRANSPORT_HYBRID 0x00000020 +#define WEBAUTHN_CTAP_TRANSPORT_FLAGS_MASK 0x0000003F + +#define WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_CREDENTIAL_EX { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Size of pbID. + DWORD cbId; + // Unique ID for this particular credential. + _Field_size_bytes_(cbId) + PBYTE pbId; + + // Well-known credential type specifying what this particular credential is. + LPCWSTR pwszCredentialType; + + // Transports. 0 implies no transport restrictions. + DWORD dwTransports; +} WEBAUTHN_CREDENTIAL_EX, *PWEBAUTHN_CREDENTIAL_EX; +typedef const WEBAUTHN_CREDENTIAL_EX *PCWEBAUTHN_CREDENTIAL_EX; + +//+------------------------------------------------------------------------------------------ +// Information about credential list with extra information +//------------------------------------------------------------------------------------------- + +typedef struct _WEBAUTHN_CREDENTIAL_LIST { + DWORD cCredentials; + _Field_size_(cCredentials) + PWEBAUTHN_CREDENTIAL_EX *ppCredentials; +} WEBAUTHN_CREDENTIAL_LIST, *PWEBAUTHN_CREDENTIAL_LIST; +typedef const WEBAUTHN_CREDENTIAL_LIST *PCWEBAUTHN_CREDENTIAL_LIST; + +//+------------------------------------------------------------------------------------------ +// Information about linked devices +//------------------------------------------------------------------------------------------- + +#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1 1 +#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_CURRENT_VERSION CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1 + +typedef struct _CTAPCBOR_HYBRID_STORAGE_LINKED_DATA +{ + // Version + DWORD dwVersion; + + // Contact Id + DWORD cbContactId; + _Field_size_bytes_(cbContactId) + PBYTE pbContactId; + + // Link Id + DWORD cbLinkId; + _Field_size_bytes_(cbLinkId) + PBYTE pbLinkId; + + // Link secret + DWORD cbLinkSecret; + _Field_size_bytes_(cbLinkSecret) + PBYTE pbLinkSecret; + + // Authenticator Public Key + DWORD cbPublicKey; + _Field_size_bytes_(cbPublicKey) + PBYTE pbPublicKey; + + // Authenticator Name + PCWSTR pwszAuthenticatorName; + + // Tunnel server domain + WORD wEncodedTunnelServerDomain; +} CTAPCBOR_HYBRID_STORAGE_LINKED_DATA, *PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA; +typedef const CTAPCBOR_HYBRID_STORAGE_LINKED_DATA *PCCTAPCBOR_HYBRID_STORAGE_LINKED_DATA; + +//+------------------------------------------------------------------------------------------ +// Credential Information for WebAuthNGetPlatformCredentialList API +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_1 1 +#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2 2 +#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_3 3 +#define WEBAUTHN_CREDENTIAL_DETAILS_CURRENT_VERSION WEBAUTHN_CREDENTIAL_DETAILS_VERSION_3 + +typedef struct _WEBAUTHN_CREDENTIAL_DETAILS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Size of pbCredentialID. + DWORD cbCredentialID; + _Field_size_bytes_(cbCredentialID) + PBYTE pbCredentialID; + + // RP Info + PWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation; + + // User Info + PWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation; + + // Removable or not. + BOOL bRemovable; + + // + // The following fields have been added in WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2 + // + + // Backed Up or not. + BOOL bBackedUp; + + // + // The following fields have been added in WEBAUTHN_CREDENTIAL_DETAILS_VERSION_3 + // + PCWSTR pwszAuthenticatorName; + + // The logo is expected to be in the svg format + DWORD cbAuthenticatorLogo; + _Field_size_bytes_(cbAuthenticatorLogo) + PBYTE pbAuthenticatorLogo; + + // ThirdPartyPayment Credential or not. + BOOL bThirdPartyPayment; + +} WEBAUTHN_CREDENTIAL_DETAILS, *PWEBAUTHN_CREDENTIAL_DETAILS; +typedef const WEBAUTHN_CREDENTIAL_DETAILS *PCWEBAUTHN_CREDENTIAL_DETAILS; + +typedef struct _WEBAUTHN_CREDENTIAL_DETAILS_LIST { + DWORD cCredentialDetails; + _Field_size_(cCredentialDetails) + PWEBAUTHN_CREDENTIAL_DETAILS *ppCredentialDetails; +} WEBAUTHN_CREDENTIAL_DETAILS_LIST, *PWEBAUTHN_CREDENTIAL_DETAILS_LIST; +typedef const WEBAUTHN_CREDENTIAL_DETAILS_LIST *PCWEBAUTHN_CREDENTIAL_DETAILS_LIST; + +#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1 1 +#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_CURRENT_VERSION WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1 + +typedef struct _WEBAUTHN_GET_CREDENTIALS_OPTIONS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Optional. + LPCWSTR pwszRpId; + + // Optional. BrowserInPrivate Mode. Defaulting to FALSE. + BOOL bBrowserInPrivateMode; +} WEBAUTHN_GET_CREDENTIALS_OPTIONS, *PWEBAUTHN_GET_CREDENTIALS_OPTIONS; +typedef const WEBAUTHN_GET_CREDENTIALS_OPTIONS *PCWEBAUTHN_GET_CREDENTIALS_OPTIONS; + +//+------------------------------------------------------------------------------------------ +// PRF values. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH 32 + +// SALT values below by default are converted into RAW Hmac-Secret values as per PRF extension. +// - SHA-256(UTF8Encode("WebAuthn PRF") || 0x00 || Value) +// +// Set WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG in dwFlags in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, +// if caller wants to provide RAW Hmac-Secret SALT values directly. In that case, +// values if provided MUST be of WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH size. + +typedef struct _WEBAUTHN_HMAC_SECRET_SALT { + // Size of pbFirst. + DWORD cbFirst; + _Field_size_bytes_(cbFirst) + PBYTE pbFirst; // Required + + // Size of pbSecond. + DWORD cbSecond; + _Field_size_bytes_(cbSecond) + PBYTE pbSecond; +} WEBAUTHN_HMAC_SECRET_SALT, *PWEBAUTHN_HMAC_SECRET_SALT; +typedef const WEBAUTHN_HMAC_SECRET_SALT *PCWEBAUTHN_HMAC_SECRET_SALT; + +typedef struct _WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT { + // Size of pbCredID. + DWORD cbCredID; + _Field_size_bytes_(cbCredID) + PBYTE pbCredID; // Required + + // PRF Values for above credential + PWEBAUTHN_HMAC_SECRET_SALT pHmacSecretSalt; // Required +} WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT, *PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT; +typedef const WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT *PCWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT; + +typedef struct _WEBAUTHN_HMAC_SECRET_SALT_VALUES { + PWEBAUTHN_HMAC_SECRET_SALT pGlobalHmacSalt; + + DWORD cCredWithHmacSecretSaltList; + _Field_size_(cCredWithHmacSecretSaltList) + PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT pCredWithHmacSecretSaltList; +} WEBAUTHN_HMAC_SECRET_SALT_VALUES, *PWEBAUTHN_HMAC_SECRET_SALT_VALUES; +typedef const WEBAUTHN_HMAC_SECRET_SALT_VALUES *PCWEBAUTHN_HMAC_SECRET_SALT_VALUES; + +//+------------------------------------------------------------------------------------------ +// Hmac-Secret extension +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET L"hmac-secret" +// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET +// MakeCredential Input Type: BOOL. +// - pvExtension must point to a BOOL with the value TRUE. +// - cbExtension must contain the sizeof(BOOL). +// MakeCredential Output Type: BOOL. +// - pvExtension will point to a BOOL with the value TRUE if credential +// was successfully created with HMAC_SECRET. +// - cbExtension will contain the sizeof(BOOL). +// GetAssertion Input Type: Not Supported +// GetAssertion Output Type: Not Supported + +//+------------------------------------------------------------------------------------------ +// credProtect extension +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_USER_VERIFICATION_ANY 0 +#define WEBAUTHN_USER_VERIFICATION_OPTIONAL 1 +#define WEBAUTHN_USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST 2 +#define WEBAUTHN_USER_VERIFICATION_REQUIRED 3 + +typedef struct _WEBAUTHN_CRED_PROTECT_EXTENSION_IN { + // One of the above WEBAUTHN_USER_VERIFICATION_* values + DWORD dwCredProtect; + // Set the following to TRUE to require authenticator support for the credProtect extension + BOOL bRequireCredProtect; +} WEBAUTHN_CRED_PROTECT_EXTENSION_IN, *PWEBAUTHN_CRED_PROTECT_EXTENSION_IN; +typedef const WEBAUTHN_CRED_PROTECT_EXTENSION_IN *PCWEBAUTHN_CRED_PROTECT_EXTENSION_IN; + + +#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT L"credProtect" +// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT +// MakeCredential Input Type: WEBAUTHN_CRED_PROTECT_EXTENSION_IN. +// - pvExtension must point to a WEBAUTHN_CRED_PROTECT_EXTENSION_IN struct +// - cbExtension will contain the sizeof(WEBAUTHN_CRED_PROTECT_EXTENSION_IN). +// MakeCredential Output Type: DWORD. +// - pvExtension will point to a DWORD with one of the above WEBAUTHN_USER_VERIFICATION_* values +// if credential was successfully created with CRED_PROTECT. +// - cbExtension will contain the sizeof(DWORD). +// GetAssertion Input Type: Not Supported +// GetAssertion Output Type: Not Supported + +//+------------------------------------------------------------------------------------------ +// credBlob extension +//------------------------------------------------------------------------------------------- + +typedef struct _WEBAUTHN_CRED_BLOB_EXTENSION { + // Size of pbCredBlob. + DWORD cbCredBlob; + _Field_size_bytes_(cbCredBlob) + PBYTE pbCredBlob; +} WEBAUTHN_CRED_BLOB_EXTENSION, *PWEBAUTHN_CRED_BLOB_EXTENSION; +typedef const WEBAUTHN_CRED_BLOB_EXTENSION *PCWEBAUTHN_CRED_BLOB_EXTENSION; + + +#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB L"credBlob" +// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB +// MakeCredential Input Type: WEBAUTHN_CRED_BLOB_EXTENSION. +// - pvExtension must point to a WEBAUTHN_CRED_BLOB_EXTENSION struct +// - cbExtension must contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION). +// MakeCredential Output Type: BOOL. +// - pvExtension will point to a BOOL with the value TRUE if credBlob was successfully created +// - cbExtension will contain the sizeof(BOOL). +// GetAssertion Input Type: BOOL. +// - pvExtension must point to a BOOL with the value TRUE to request the credBlob. +// - cbExtension must contain the sizeof(BOOL). +// GetAssertion Output Type: WEBAUTHN_CRED_BLOB_EXTENSION. +// - pvExtension will point to a WEBAUTHN_CRED_BLOB_EXTENSION struct if the authenticator +// returns the credBlob in the signed extensions +// - cbExtension will contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION). + +//+------------------------------------------------------------------------------------------ +// minPinLength extension +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH L"minPinLength" +// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH +// MakeCredential Input Type: BOOL. +// - pvExtension must point to a BOOL with the value TRUE to request the minPinLength. +// - cbExtension must contain the sizeof(BOOL). +// MakeCredential Output Type: DWORD. +// - pvExtension will point to a DWORD with the minimum pin length if returned by the authenticator +// - cbExtension will contain the sizeof(DWORD). +// GetAssertion Input Type: Not Supported +// GetAssertion Output Type: Not Supported + +//+------------------------------------------------------------------------------------------ +// Information about Extensions. +//------------------------------------------------------------------------------------------- +typedef struct _WEBAUTHN_EXTENSION { + LPCWSTR pwszExtensionIdentifier; + DWORD cbExtension; + PVOID pvExtension; +} WEBAUTHN_EXTENSION, *PWEBAUTHN_EXTENSION; +typedef const WEBAUTHN_EXTENSION *PCWEBAUTHN_EXTENSION; + +typedef struct _WEBAUTHN_EXTENSIONS { + DWORD cExtensions; + _Field_size_(cExtensions) + PWEBAUTHN_EXTENSION pExtensions; +} WEBAUTHN_EXTENSIONS, *PWEBAUTHN_EXTENSIONS; +typedef const WEBAUTHN_EXTENSIONS *PCWEBAUTHN_EXTENSIONS; + +//+------------------------------------------------------------------------------------------ +// Options. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY 0 +#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM 1 +#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM 2 +#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2 3 + +#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY 0 +#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED 1 +#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED 2 +#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED 3 + +#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY 0 +#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE 1 +#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT 2 +#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT 3 + +#define WEBAUTHN_ENTERPRISE_ATTESTATION_NONE 0 +#define WEBAUTHN_ENTERPRISE_ATTESTATION_VENDOR_FACILITATED 1 +#define WEBAUTHN_ENTERPRISE_ATTESTATION_PLATFORM_MANAGED 2 + +#define WEBAUTHN_LARGE_BLOB_SUPPORT_NONE 0 +#define WEBAUTHN_LARGE_BLOB_SUPPORT_REQUIRED 1 +#define WEBAUTHN_LARGE_BLOB_SUPPORT_PREFERRED 2 + +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1 1 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 2 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 3 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 4 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 5 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6 6 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7 7 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_8 8 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_8 + +typedef struct _WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Time that the operation is expected to complete within. + // This is used as guidance, and can be overridden by the platform. + DWORD dwTimeoutMilliseconds; + + // Credentials used for exclusion. + WEBAUTHN_CREDENTIALS CredentialList; + + // Optional extensions to parse when performing the operation. + WEBAUTHN_EXTENSIONS Extensions; + + // Optional. Platform vs Cross-Platform Authenticators. + DWORD dwAuthenticatorAttachment; + + // Optional. Require key to be resident or not. Defaulting to FALSE. + BOOL bRequireResidentKey; + + // User Verification Requirement. + DWORD dwUserVerificationRequirement; + + // Attestation Conveyance Preference. + DWORD dwAttestationConveyancePreference; + + // Reserved for future Use + DWORD dwFlags; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 + // + + // Cancellation Id - Optional - See WebAuthNGetCancellationId + GUID *pCancellationId; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 + // + + // Exclude Credential List. If present, "CredentialList" will be ignored. + PWEBAUTHN_CREDENTIAL_LIST pExcludeCredentialList; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 + // + + // Enterprise Attestation + DWORD dwEnterpriseAttestation; + + // Large Blob Support: none, required or preferred + // + // NTE_INVALID_PARAMETER when large blob required or preferred and + // bRequireResidentKey isn't set to TRUE + DWORD dwLargeBlobSupport; + + // Optional. Prefer key to be resident. Defaulting to FALSE. When TRUE, + // overrides the above bRequireResidentKey. + BOOL bPreferResidentKey; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 + // + + // Optional. BrowserInPrivate Mode. Defaulting to FALSE. + BOOL bBrowserInPrivateMode; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6 + // + + // Enable PRF + BOOL bEnablePrf; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7 + // + + // Optional. Linked Device Connection Info. + PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice; + + // Size of pbJsonExt + DWORD cbJsonExt; + _Field_size_bytes_(cbJsonExt) + PBYTE pbJsonExt; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_8 + // + + // PRF extension "eval" values which will be converted into HMAC-SECRET values according to WebAuthn Spec. + // Set WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG in dwFlags above, if caller wants to provide RAW Hmac-Secret SALT values directly. + // In that case, values provided MUST be of WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH size. + PWEBAUTHN_HMAC_SECRET_SALT pPRFGlobalEval; + + // PublicKeyCredentialHints (https://w3c.github.io/webauthn/#enum-hints) + DWORD cCredentialHints; + _Field_size_(cCredentialHints) + LPCWSTR *ppwszCredentialHints; + + // Enable ThirdPartyPayment + BOOL bThirdPartyPayment; + +} WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS; +typedef const WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS; + +#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE 0 +#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET 1 +#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET 2 +#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_DELETE 3 + +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1 1 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 2 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 3 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 4 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 5 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 6 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7 7 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_8 8 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_8 + +/* + Information about flags. +*/ + +#define WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG 0x00100000 + +typedef struct _WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Time that the operation is expected to complete within. + // This is used as guidance, and can be overridden by the platform. + DWORD dwTimeoutMilliseconds; + + // Allowed Credentials List. + WEBAUTHN_CREDENTIALS CredentialList; + + // Optional extensions to parse when performing the operation. + WEBAUTHN_EXTENSIONS Extensions; + + // Optional. Platform vs Cross-Platform Authenticators. + DWORD dwAuthenticatorAttachment; + + // User Verification Requirement. + DWORD dwUserVerificationRequirement; + + // Flags + DWORD dwFlags; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 + // + + // Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower cased. + PCWSTR pwszU2fAppId; + + // If the following is non-NULL, then, set to TRUE if the above pwszU2fAppid was used instead of + // PCWSTR pwszRpId; + BOOL *pbU2fAppId; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 + // + + // Cancellation Id - Optional - See WebAuthNGetCancellationId + GUID *pCancellationId; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 + // + + // Allow Credential List. If present, "CredentialList" will be ignored. + PWEBAUTHN_CREDENTIAL_LIST pAllowCredentialList; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 + // + + DWORD dwCredLargeBlobOperation; + + // Size of pbCredLargeBlob + DWORD cbCredLargeBlob; + _Field_size_bytes_(cbCredLargeBlob) + PBYTE pbCredLargeBlob; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 + // + + // PRF values which will be converted into HMAC-SECRET values according to WebAuthn Spec. + PWEBAUTHN_HMAC_SECRET_SALT_VALUES pHmacSecretSaltValues; + + // Optional. BrowserInPrivate Mode. Defaulting to FALSE. + BOOL bBrowserInPrivateMode; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7 + // + + // Optional. Linked Device Connection Info. + PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice; + + // Optional. Allowlist MUST contain 1 credential applicable for Hybrid transport. + BOOL bAutoFill; + + // Size of pbJsonExt + DWORD cbJsonExt; + _Field_size_bytes_(cbJsonExt) + PBYTE pbJsonExt; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_8 + // + + // PublicKeyCredentialHints (https://w3c.github.io/webauthn/#enum-hints) + DWORD cCredentialHints; + _Field_size_(cCredentialHints) + LPCWSTR *ppwszCredentialHints; + +} WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS; +typedef const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS; + + +//+------------------------------------------------------------------------------------------ +// Attestation Info. +// +//------------------------------------------------------------------------------------------- +#define WEBAUTHN_ATTESTATION_DECODE_NONE 0 +#define WEBAUTHN_ATTESTATION_DECODE_COMMON 1 +// WEBAUTHN_ATTESTATION_DECODE_COMMON supports format types +// L"packed" +// L"fido-u2f" + +#define WEBAUTHN_ATTESTATION_VER_TPM_2_0 L"2.0" + +typedef struct _WEBAUTHN_X5C { + // Length of X.509 encoded certificate + DWORD cbData; + // X.509 encoded certificate bytes + _Field_size_bytes_(cbData) + PBYTE pbData; +} WEBAUTHN_X5C, *PWEBAUTHN_X5C; + +// Supports either Self or Full Basic Attestation + +// Note, new fields will be added to the following data structure to +// support additional attestation format types, such as, TPM. +// When fields are added, the dwVersion will be incremented. +// +// Therefore, your code must make the following check: +// "if (dwVersion >= WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION)" + +#define WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_COMMON_ATTESTATION { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Hash and Padding Algorithm + // + // The following won't be set for "fido-u2f" which assumes "ES256". + PCWSTR pwszAlg; + LONG lAlg; // COSE algorithm + + // Signature that was generated for this attestation. + DWORD cbSignature; + _Field_size_bytes_(cbSignature) + PBYTE pbSignature; + + // Following is set for Full Basic Attestation. If not, set then, this is Self Attestation. + // Array of X.509 DER encoded certificates. The first certificate is the signer, leaf certificate. + DWORD cX5c; + _Field_size_(cX5c) + PWEBAUTHN_X5C pX5c; + + // Following are also set for tpm + PCWSTR pwszVer; // L"2.0" + DWORD cbCertInfo; + _Field_size_bytes_(cbCertInfo) + PBYTE pbCertInfo; + DWORD cbPubArea; + _Field_size_bytes_(cbPubArea) + PBYTE pbPubArea; +} WEBAUTHN_COMMON_ATTESTATION, *PWEBAUTHN_COMMON_ATTESTATION; +typedef const WEBAUTHN_COMMON_ATTESTATION *PCWEBAUTHN_COMMON_ATTESTATION; + +#define WEBAUTHN_ATTESTATION_TYPE_PACKED L"packed" +#define WEBAUTHN_ATTESTATION_TYPE_U2F L"fido-u2f" +#define WEBAUTHN_ATTESTATION_TYPE_TPM L"tpm" +#define WEBAUTHN_ATTESTATION_TYPE_NONE L"none" + +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_1 1 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 2 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 3 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 4 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 5 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 6 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 7 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_CURRENT_VERSION WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 + +typedef struct _WEBAUTHN_CREDENTIAL_ATTESTATION { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Attestation format type + PCWSTR pwszFormatType; + + // Size of cbAuthenticatorData. + DWORD cbAuthenticatorData; + // Authenticator data that was created for this credential. + _Field_size_bytes_(cbAuthenticatorData) + PBYTE pbAuthenticatorData; + + // Size of CBOR encoded attestation information + //0 => encoded as CBOR null value. + DWORD cbAttestation; + //Encoded CBOR attestation information + _Field_size_bytes_(cbAttestation) + PBYTE pbAttestation; + + DWORD dwAttestationDecodeType; + // Following depends on the dwAttestationDecodeType + // WEBAUTHN_ATTESTATION_DECODE_NONE + // NULL - not able to decode the CBOR attestation information + // WEBAUTHN_ATTESTATION_DECODE_COMMON + // PWEBAUTHN_COMMON_ATTESTATION; + PVOID pvAttestationDecode; + + // The CBOR encoded Attestation Object to be returned to the RP. + DWORD cbAttestationObject; + _Field_size_bytes_(cbAttestationObject) + PBYTE pbAttestationObject; + + // The CredentialId bytes extracted from the Authenticator Data. + // Used by Edge to return to the RP. + DWORD cbCredentialId; + _Field_size_bytes_(cbCredentialId) + PBYTE pbCredentialId; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 + // + + WEBAUTHN_EXTENSIONS Extensions; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 + // + + // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to + // the transport that was used. + DWORD dwUsedTransport; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 + // + + BOOL bEpAtt; + BOOL bLargeBlobSupported; + BOOL bResidentKey; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 + // + + BOOL bPrfEnabled; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 + // + + DWORD cbUnsignedExtensionOutputs; + _Field_size_bytes_(cbUnsignedExtensionOutputs) + PBYTE pbUnsignedExtensionOutputs; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 + // + + PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret; + + // ThirdPartyPayment Credential or not. + BOOL bThirdPartyPayment; + +} WEBAUTHN_CREDENTIAL_ATTESTATION, *PWEBAUTHN_CREDENTIAL_ATTESTATION; +typedef const WEBAUTHN_CREDENTIAL_ATTESTATION *PCWEBAUTHN_CREDENTIAL_ATTESTATION; + + +//+------------------------------------------------------------------------------------------ +// authenticatorGetAssertion output. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NONE 0 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS 1 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_SUPPORTED 2 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_DATA 3 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_PARAMETER 4 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_FOUND 5 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_MULTIPLE_CREDENTIALS 6 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_LACK_OF_SPACE 7 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_PLATFORM_ERROR 8 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_AUTHENTICATOR_ERROR 9 + +#define WEBAUTHN_ASSERTION_VERSION_1 1 +#define WEBAUTHN_ASSERTION_VERSION_2 2 +#define WEBAUTHN_ASSERTION_VERSION_3 3 +#define WEBAUTHN_ASSERTION_VERSION_4 4 +#define WEBAUTHN_ASSERTION_VERSION_5 5 +#define WEBAUTHN_ASSERTION_CURRENT_VERSION WEBAUTHN_ASSERTION_VERSION_5 + +typedef struct _WEBAUTHN_ASSERTION { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Size of cbAuthenticatorData. + DWORD cbAuthenticatorData; + // Authenticator data that was created for this assertion. + _Field_size_bytes_(cbAuthenticatorData) + PBYTE pbAuthenticatorData; + + // Size of pbSignature. + DWORD cbSignature; + // Signature that was generated for this assertion. + _Field_size_bytes_(cbSignature) + PBYTE pbSignature; + + // Credential that was used for this assertion. + WEBAUTHN_CREDENTIAL Credential; + + // Size of User Id + DWORD cbUserId; + // UserId + _Field_size_bytes_(cbUserId) + PBYTE pbUserId; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_2 + // + + WEBAUTHN_EXTENSIONS Extensions; + + // Size of pbCredLargeBlob + DWORD cbCredLargeBlob; + _Field_size_bytes_(cbCredLargeBlob) + PBYTE pbCredLargeBlob; + + DWORD dwCredLargeBlobStatus; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_3 + // + + PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_4 + // + + // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to + // the transport that was used. + DWORD dwUsedTransport; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_5 + // + + DWORD cbUnsignedExtensionOutputs; + _Field_size_bytes_(cbUnsignedExtensionOutputs) + PBYTE pbUnsignedExtensionOutputs; +} WEBAUTHN_ASSERTION, *PWEBAUTHN_ASSERTION; +typedef const WEBAUTHN_ASSERTION *PCWEBAUTHN_ASSERTION; + +//+------------------------------------------------------------------------------------------ +// APIs. +//------------------------------------------------------------------------------------------- + +DWORD +WINAPI +WebAuthNGetApiVersionNumber(); + +HRESULT +WINAPI +WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable( + _Out_ BOOL *pbIsUserVerifyingPlatformAuthenticatorAvailable); + + +HRESULT +WINAPI +WebAuthNAuthenticatorMakeCredential( + _In_ HWND hWnd, + _In_ PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation, + _In_ PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation, + _In_ PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS pPubKeyCredParams, + _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData, + _In_opt_ PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS pWebAuthNMakeCredentialOptions, + _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_ATTESTATION *ppWebAuthNCredentialAttestation); + + +HRESULT +WINAPI +WebAuthNAuthenticatorGetAssertion( + _In_ HWND hWnd, + _In_ LPCWSTR pwszRpId, + _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData, + _In_opt_ PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS pWebAuthNGetAssertionOptions, + _Outptr_result_maybenull_ PWEBAUTHN_ASSERTION *ppWebAuthNAssertion); + +void +WINAPI +WebAuthNFreeCredentialAttestation( + _In_opt_ PWEBAUTHN_CREDENTIAL_ATTESTATION pWebAuthNCredentialAttestation); + +void +WINAPI +WebAuthNFreeAssertion( + _In_ PWEBAUTHN_ASSERTION pWebAuthNAssertion); + +HRESULT +WINAPI +WebAuthNGetCancellationId( + _Out_ GUID* pCancellationId); + +HRESULT +WINAPI +WebAuthNCancelCurrentOperation( + _In_ const GUID* pCancellationId); + +// Returns NTE_NOT_FOUND when credentials are not found. +HRESULT +WINAPI +WebAuthNGetPlatformCredentialList( + _In_ PCWEBAUTHN_GET_CREDENTIALS_OPTIONS pGetCredentialsOptions, + _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST *ppCredentialDetailsList); + +void +WINAPI +WebAuthNFreePlatformCredentialList( + _In_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST pCredentialDetailsList); + +HRESULT +WINAPI +WebAuthNDeletePlatformCredential( + _In_ DWORD cbCredentialId, + _In_reads_bytes_(cbCredentialId) const BYTE *pbCredentialId + ); + +// +// Returns the following Error Names: +// L"Success" - S_OK +// L"InvalidStateError" - NTE_EXISTS +// L"ConstraintError" - HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), +// NTE_NOT_SUPPORTED, +// NTE_TOKEN_KEYSET_STORAGE_FULL +// L"NotSupportedError" - NTE_INVALID_PARAMETER +// L"NotAllowedError" - NTE_DEVICE_NOT_FOUND, +// NTE_NOT_FOUND, +// HRESULT_FROM_WIN32(ERROR_CANCELLED), +// NTE_USER_CANCELLED, +// HRESULT_FROM_WIN32(ERROR_TIMEOUT) +// L"UnknownError" - All other hr values +// +PCWSTR +WINAPI +WebAuthNGetErrorName( + _In_ HRESULT hr); + +HRESULT +WINAPI +WebAuthNGetW3CExceptionDOMError( + _In_ HRESULT hr); + +typedef enum _EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE +{ + PluginAuthenticatorState_Unknown = 0, + PluginAuthenticatorState_Disabled, + PluginAuthenticatorState_Enabled +} EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE; + +// +// Plugin Authenticator API: WebAuthNPluginGetAuthenticatorState: Get Plugin Authenticator State +// +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginGetAuthenticatorState( + _In_ LPCWSTR pwszPluginClsId, + _Out_ EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE* pluginAuthenticatorState +); + +// +// Plugin Authenticator API: WebAuthNAddPluginAuthenticator: Add Plugin Authenticator +// + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS { + // Authenticator Name + LPCWSTR pwszAuthenticatorName; + + // Plugin COM ClsId + LPCWSTR pwszPluginClsId; + + // Plugin RPID (Optional. Required for a nested WebAuthN call originating from a plugin) + LPCWSTR pwszPluginRpId; + + // Plugin Authenticator Logo for the Light themes. base64 svg (Optional) + LPCWSTR pwszLightThemeLogo; + + // Plugin Authenticator Logo for the Dark themes. base64 svg (Optional) + LPCWSTR pwszDarkThemeLogo; + + // CTAP CBOR encoded authenticatorGetInfo + DWORD cbAuthenticatorInfo; + _Field_size_bytes_(cbAuthenticatorInfo) + PBYTE pbAuthenticatorInfo; + +} EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS, *EXPERIMENTAL_PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS; +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE { + // Plugin operation signing Public Key - Used to sign the request in the EXPERIMENTAL_PluginPerformOperation. Refer pluginauthenticator.h. + DWORD cbOpSignPubKey; + _Field_size_bytes_(cbOpSignPubKey) + PBYTE pbOpSignPubKey; + +} EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE, *EXPERIMENTAL_PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE; +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE; + +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginAddAuthenticator( + _In_ EXPERIMENTAL_PCWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS pPluginAddAuthenticatorOptions, + _Outptr_result_maybenull_ EXPERIMENTAL_PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE *ppPluginAddAuthenticatorResponse); + +void +WINAPI +EXPERIMENTAL_WebAuthNPluginFreeAddAuthenticatorResponse( + _In_opt_ EXPERIMENTAL_PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE pPluginAddAuthenticatorResponse); + +// +// Plugin Authenticator API: WebAuthNRemovePluginAuthenticator: Remove Plugin Authenticator +// + +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginRemoveAuthenticator( + _In_ LPCWSTR pwszPluginClsId); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorUpdateDetails: Update Credential Metadata for Browser AutoFill Scenarios +// + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS { + // Authenticator Name (Optional) + LPCWSTR pwszAuthenticatorName; + + // Plugin COM ClsId + LPCWSTR pwszPluginClsId; + + // Plugin COM New ClsId (Optional) + LPCWSTR pwszNewPluginClsId; + + // Plugin Authenticator Logo for the Light themes. base64 svg (Optional) + LPCWSTR pwszLightThemeLogo; + + // Plugin Authenticator Logo for the Dark themes. base64 svg (Optional) + LPCWSTR pwszDarkThemeLogo; + + // CTAP CBOR encoded authenticatorGetInfo (Optional) + DWORD cbAuthenticatorInfo; + _Field_size_bytes_(cbAuthenticatorInfo) + PBYTE pbAuthenticatorInfo; + +} EXPERIMENTAL_WEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS, *EXPERIMENTAL_PWEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS; +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS; + +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginUpdateAuthenticatorDetails( + _In_ EXPERIMENTAL_PCWEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS pPluginUpdateAuthenticatorDetails); + +#endif //__midl + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorAddCredentials: Add Credential Metadata for Browser AutoFill Scenarios +// + + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS { + // Size of pbCredentialId. + DWORD cbCredentialId; + + // Credential Identifier bytes. This field is required. + #ifdef __midl + [size_is(cbCredentialId)] + #else + _Field_size_bytes_(cbCredentialId) + #endif + PBYTE pbCredentialId; + + // Identifier for the RP. This field is required. + PWSTR pwszRpId; + + // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site". + // This field is required. + PWSTR pwszRpName; + + // Identifier for the User. This field is required. + DWORD cbUserId; + + // User Identifier bytes. This field is required. + #ifdef __midl + [size_is(cbUserId)] + #else + _Field_size_bytes_(cbUserId) + #endif + PBYTE pbUserId; + + // Contains a detailed name for this account, such as "john.p.smith@example.com". + PWSTR pwszUserName; + + // For User: Contains the friendly name associated with the user account such as "John P. Smith". + PWSTR pwszUserDisplayName; + +} EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS, *EXPERIMENTAL_PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS; +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST { + // Plugin COM ClsId + PWSTR pwszPluginClsId; + + // count of credentials + DWORD cCredentialDetails; + + #ifdef __midl + [size_is(cCredentialDetails)] + #else + _Field_size_(cCredentialDetails) + #endif + EXPERIMENTAL_PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS *pCredentialDetails; + +} EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST, *EXPERIMENTAL_PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST; +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST; + +#ifndef __midl + +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials( + _In_ EXPERIMENTAL_PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST pCredentialDetailsList); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorRemoveCredentials: Remove Credential Metadata for Browser AutoFill Scenarios +// + +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials( + _In_ EXPERIMENTAL_PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST pCredentialDetailsList); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorRemoveCredentials: Remove All Credential Metadata for Browser AutoFill Scenarios +// + +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials( + _In_ LPCWSTR pwszPluginClsId); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorGetAllCredentials: Get All Credential Metadata cached for Browser AutoFill Scenarios +// +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials( + _In_ LPCWSTR pwszPluginClsId, + _Outptr_result_maybenull_ EXPERIMENTAL_PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST *ppCredentialDetailsList); + +// +// Hello UV API for Plugin: WebAuthNPluginPerformUv: Perform Hello UV related operations +// + +typedef enum _EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE +{ + PerformUv = 1, + GetUvCount, + GetPubKey +} EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV { + HWND hwnd; + GUID* transactionId; + EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE type; + PCWSTR pwszUsername; + PCWSTR pwszContext; +} EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV, *EXPERIMENTAL_PWEBAUTHN_PLUGIN_PERFROM_UV; +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_PERFORM_UV; + +typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE { + DWORD cbResponse; + PBYTE pbResponse; +} EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE, *EXPERIMENTAL_PWEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE; +typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE; + +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNPluginPerformUv( + _In_ EXPERIMENTAL_PCWEBAUTHN_PLUGIN_PERFORM_UV pPluginPerformUv, + _Outptr_result_maybenull_ EXPERIMENTAL_PWEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE *ppPluginPerformUvRespose); + +void +WINAPI +EXPERIMENTAL_WebAuthNPluginFreePerformUvResponse( + _In_opt_ EXPERIMENTAL_PWEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE ppPluginPerformUvResponse); + +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS_VERSION_1 1 +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS_CURRENT_VERSION EXPERIMENTAL_WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS_VERSION_1 +typedef struct _EXPERIMENTAL_WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Following have following values: + // +1 - TRUE + // 0 - Not defined + // -1 - FALSE + //up: "true" | "false" + LONG lUp; + //uv: "true" | "false" + LONG lUv; + //rk: "true" | "false" + LONG lRequireResidentKey; +} EXPERIMENTAL_WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS, *EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS; +typedef const EXPERIMENTAL_WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS *EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS; + +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY_VERSION_1 1 +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY_CURRENT_VERSION EXPERIMENTAL_WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY_VERSION_1 +typedef struct _EXPERIMENTAL_WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Key type + LONG lKty; + + // Hash Algorithm: ES256, ES384, ES512 + LONG lAlg; + + // Curve + LONG lCrv; + + //Size of "x" (X Coordinate) + DWORD cbX; + + //"x" (X Coordinate) data. Big Endian. + PBYTE pbX; + + //Size of "y" (Y Coordinate) + DWORD cbY; + + //"y" (Y Coordinate) data. Big Endian. + PBYTE pbY; +} EXPERIMENTAL_WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY, *EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY; +typedef const EXPERIMENTAL_WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY *EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY; + +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION_VERSION_1 1 +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION_CURRENT_VERSION EXPERIMENTAL_WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION_VERSION_1 +typedef struct _EXPERIMENTAL_WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Platform's key agreement public key + EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY pKeyAgreement; + + DWORD cbEncryptedSalt; + PBYTE pbEncryptedSalt; + + DWORD cbSaltAuth; + PBYTE pbSaltAuth; +} EXPERIMENTAL_WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION, *EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION; +typedef const EXPERIMENTAL_WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION *EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION; + +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST_VERSION_1 1 +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST_CURRENT_VERSION EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST_VERSION_1 +typedef struct _EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + //Input RP ID. Raw UTF8 bytes before conversion. + //These are the bytes to be hashed in the Authenticator Data. + DWORD cbRpId; + PBYTE pbRpId; + + //Client Data Hash + DWORD cbClientDataHash; + PBYTE pbClientDataHash; + + //RP Information + PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation; + + //User Information + PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation; + + // Crypto Parameters + WEBAUTHN_COSE_CREDENTIAL_PARAMETERS WebAuthNCredentialParameters; + + //Credentials used for exclusion + WEBAUTHN_CREDENTIAL_LIST CredentialList; + + //Optional extensions to parse when performing the operation. + DWORD cbCborExtensionsMap; + PBYTE pbCborExtensionsMap; + + // Authenticator Options (Optional) + EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS pAuthenticatorOptions; + + // Pin Auth (Optional) + BOOL fEmptyPinAuth; // Zero length PinAuth is included in the request + DWORD cbPinAuth; + PBYTE pbPinAuth; + + //"hmac-secret": true extension + LONG lHmacSecretExt; + + // "hmac-secret-mc" extension + EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION pHmacSecretMcExtension; + + //"prf" extension + LONG lPrfExt; + DWORD cbHmacSecretSaltValues; + PBYTE pbHmacSecretSaltValues; + + //"credProtect" extension. Nonzero if present + DWORD dwCredProtect; + + // Nonzero if present + DWORD dwPinProtocol; + + // Nonzero if present + DWORD dwEnterpriseAttestation; + + //"credBlob" extension. Nonzero if present + DWORD cbCredBlobExt; + PBYTE pbCredBlobExt; + + //"largeBlobKey": true extension + LONG lLargeBlobKeyExt; + + //"largeBlob": extension + DWORD dwLargeBlobSupport; + + //"minPinLength": true extension + LONG lMinPinLengthExt; + + // "json" extension. Nonzero if present + DWORD cbJsonExt; + PBYTE pbJsonExt; +} EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST, *EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST; +typedef const EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST *EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST; + +_Success_(return == S_OK) +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNEncodeMakeCredentialResponse( + _In_ PCWEBAUTHN_CREDENTIAL_ATTESTATION pCredentialAttestation, + _Out_ DWORD *pcbResp, + _Outptr_result_buffer_maybenull_(*pcbResp) BYTE **ppbResp + ); + +_Success_(return == S_OK) +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequest( + _In_ DWORD cbEncoded, + _In_reads_bytes_(cbEncoded) const BYTE *pbEncoded, + _Outptr_ EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST *ppMakeCredentialRequest + ); + +void +WINAPI +EXPERIMENTAL_WebAuthNFreeDecodedMakeCredentialRequest( + _In_opt_ EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST pMakeCredentialRequest + ); + +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST_VERSION_1 1 +#define EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST_CURRENT_VERSION EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST_VERSION_1 +typedef struct _EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + //RP ID. After UTF8 to Unicode conversion, + PCWSTR pwszRpId; + + //Input RP ID. Raw UTF8 bytes before conversion. + //These are the bytes to be hashed in the Authenticator Data. + DWORD cbRpId; + PBYTE pbRpId; + + //Client Data Hash + DWORD cbClientDataHash; + PBYTE pbClientDataHash; + + //Credentials used for inclusion + WEBAUTHN_CREDENTIAL_LIST CredentialList; + + //Optional extensions to parse when performing the operation. + DWORD cbCborExtensionsMap; + PBYTE pbCborExtensionsMap; + + // Authenticator Options (Optional) + EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS pAuthenticatorOptions; + + // Pin Auth (Optional) + BOOL fEmptyPinAuth; // Zero length PinAuth is included in the request + DWORD cbPinAuth; + PBYTE pbPinAuth; + + // HMAC Salt Extension (Optional) + EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION pHmacSaltExtension; + + // PRF Extension + DWORD cbHmacSecretSaltValues; + PBYTE pbHmacSecretSaltValues; + + DWORD dwPinProtocol; + + //"credBlob": true extension + LONG lCredBlobExt; + + //"largeBlobKey": true extension + LONG lLargeBlobKeyExt; + + //"largeBlob" extension + DWORD dwCredLargeBlobOperation; + DWORD cbCredLargeBlobCompressed; + PBYTE pbCredLargeBlobCompressed; + DWORD dwCredLargeBlobOriginalSize; + + // "json" extension. Nonzero if present + DWORD cbJsonExt; + PBYTE pbJsonExt; +} EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, *EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST; +typedef const EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST *EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST; + +_Success_(return == S_OK) +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest( + _In_ DWORD cbEncoded, + _In_reads_bytes_(cbEncoded) const BYTE *pbEncoded, + _Outptr_ EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST *ppGetAssertionRequest + ); + +void +WINAPI +EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequest( + _In_opt_ EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST pGetAssertionRequest + ); + +typedef struct _EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE { + // [1] credential (optional) + // [2] authenticatorData + // [3] signature + WEBAUTHN_ASSERTION WebAuthNAssertion; + + // [4] user (optional) + PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation; + + // [5] numberOfCredentials (optional) + DWORD dwNumberOfCredentials; + + // [6] userSelected (optional) + LONG lUserSelected; + + // [7] largeBlobKey (optional) + DWORD cbLargeBlobKey; + PBYTE pbLargeBlobKey; + + // [8] unsignedExtensionOutputs + DWORD cbUnsignedExtensionOutputs; + PBYTE pbUnsignedExtensionOutputs; +} EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE, *EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE; +typedef const EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE *EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE; + +_Success_(return == S_OK) +HRESULT +WINAPI +EXPERIMENTAL_WebAuthNEncodeGetAssertionResponse( + _In_ EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE pGetAssertionResponse, + _Out_ DWORD *pcbResp, + _Outptr_result_buffer_maybenull_(*pcbResp) BYTE **ppbResp + ); + +#endif //__midl + + +#ifdef __cplusplus +} // Balance extern "C" above +#endif + +#endif // WINAPI_FAMILY_PARTITION +#pragma endregion + diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs index 4dffff794e0..5a9c06e382f 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs @@ -13,6 +13,7 @@ use windows::Win32::System::LibraryLoader::*; use windows_core::*; use crate::util::*; +use crate::com_buffer::ComBuffer; /// Used when adding a Windows plugin authenticator. /// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS @@ -51,7 +52,7 @@ pub struct ExperimentalWebAuthnPluginCredentialDetails { pub rpid: *mut u16, pub rp_friendly_name: *mut u16, pub user_id_byte_count: u32, - pub user_id: *mut u16, + pub user_id_pointer: *mut u8, // Should be *mut u8 like credential_id_pointer pub user_name: *mut u16, pub user_display_name: *mut u16, } @@ -65,18 +66,92 @@ impl ExperimentalWebAuthnPluginCredentialDetails { user_name: String, user_display_name: String, ) -> Self { - let (credential_id_pointer, credential_id_byte_count) = credential_id.into_win_utf8(); - let (user_id, user_id_byte_count) = user_id.into_win_utf16(); + // Use COM allocation for all strings + let (credential_id_pointer, credential_id_byte_count) = ComBuffer::from_buffer(credential_id.as_bytes()); + let (user_id_pointer, user_id_byte_count) = ComBuffer::from_buffer(user_id.as_bytes()); + + // Convert to wide strings and allocate with COM + let mut rpid_wide: Vec = rpid.encode_utf16().collect(); + rpid_wide.push(0); + let rpid_bytes: Vec = rpid_wide.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (rpid_ptr, _) = ComBuffer::from_buffer(rpid_bytes); + + let mut rp_friendly_name_wide: Vec = rp_friendly_name.encode_utf16().collect(); + rp_friendly_name_wide.push(0); + let rp_friendly_name_bytes: Vec = rp_friendly_name_wide.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (rp_friendly_name_ptr, _) = ComBuffer::from_buffer(rp_friendly_name_bytes); + + let mut user_name_wide: Vec = user_name.encode_utf16().collect(); + user_name_wide.push(0); + let user_name_bytes: Vec = user_name_wide.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (user_name_ptr, _) = ComBuffer::from_buffer(user_name_bytes); + + let mut user_display_name_wide: Vec = user_display_name.encode_utf16().collect(); + user_display_name_wide.push(0); + let user_display_name_bytes: Vec = user_display_name_wide.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (user_display_name_ptr, _) = ComBuffer::from_buffer(user_display_name_bytes); Self { credential_id_byte_count, credential_id_pointer, - rpid: rpid.into_win_utf16().0, - rp_friendly_name: rp_friendly_name.into_win_utf16().0, + rpid: rpid_ptr as *mut u16, + rp_friendly_name: rp_friendly_name_ptr as *mut u16, user_id_byte_count, - user_id, - user_name: user_name.into_win_utf16().0, - user_display_name: user_display_name.into_win_utf16().0, + user_id_pointer, + user_name: user_name_ptr as *mut u16, + user_display_name: user_display_name_ptr as *mut u16, + } + } + + pub fn create_from_bytes( + credential_id: Vec, + rpid: String, + rp_friendly_name: String, + user_id: Vec, + user_name: String, + user_display_name: String, + ) -> Self { + use std::ffi::OsString; + use std::os::windows::ffi::OsStrExt; + + // Convert credential_id bytes to hex string, then allocate with COM + let credential_id_string = hex::encode(&credential_id); + let (credential_id_pointer, credential_id_byte_count) = ComBuffer::from_buffer(credential_id_string.as_bytes()); + + // Convert user_id bytes to hex string, then allocate with COM + let user_id_string = hex::encode(&user_id); + let (user_id_pointer, user_id_byte_count) = ComBuffer::from_buffer(user_id_string.as_bytes()); + + // Convert strings to null-terminated wide strings and allocate with COM + let mut rpid_vec: Vec = OsString::from(rpid).encode_wide().collect(); + rpid_vec.push(0); + let rpid_bytes: Vec = rpid_vec.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (rpid_ptr, _) = ComBuffer::from_buffer(rpid_bytes); + + let mut rp_friendly_name_vec: Vec = OsString::from(rp_friendly_name).encode_wide().collect(); + rp_friendly_name_vec.push(0); + let rp_friendly_name_bytes: Vec = rp_friendly_name_vec.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (rp_friendly_name_ptr, _) = ComBuffer::from_buffer(rp_friendly_name_bytes); + + let mut user_name_vec: Vec = OsString::from(user_name).encode_wide().collect(); + user_name_vec.push(0); + let user_name_bytes: Vec = user_name_vec.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (user_name_ptr, _) = ComBuffer::from_buffer(user_name_bytes); + + let mut user_display_name_vec: Vec = OsString::from(user_display_name).encode_wide().collect(); + user_display_name_vec.push(0); + let user_display_name_bytes: Vec = user_display_name_vec.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (user_display_name_ptr, _) = ComBuffer::from_buffer(user_display_name_bytes); + + Self { + credential_id_byte_count, + credential_id_pointer, + rpid: rpid_ptr as *mut u16, + rp_friendly_name: rp_friendly_name_ptr as *mut u16, + user_id_byte_count, + user_id_pointer, + user_name: user_name_ptr as *mut u16, + user_display_name: user_display_name_ptr as *mut u16, } } } @@ -111,38 +186,73 @@ std::mem::forget(credentials); impl ExperimentalWebAuthnPluginCredentialDetailsList { pub fn create( clsid: String, - mut credentials: Vec, + credentials: Vec, ) -> Self { - let mut credentials: Vec<*mut ExperimentalWebAuthnPluginCredentialDetails> = credentials + // Convert credentials to COM-allocated pointers + let mut credential_pointers: Vec<*mut ExperimentalWebAuthnPluginCredentialDetails> = credentials .into_iter() - .map(|mut cred| { - let cred_pointer: *mut ExperimentalWebAuthnPluginCredentialDetails = &mut cred; - cred_pointer + .map(|cred| { + // Use COM allocation for each credential struct + ComBuffer::with_object(cred) }) .collect(); - let credentials_len = credentials.len(); - let _credentials_capacity = credentials.capacity(); - let mut credentials_pointer = credentials.as_mut_ptr(); - // TODO: remember the above 3 so it can be re-created and dropped later - refactor this - std::mem::forget(credentials); // forget so Rust doesn't drop the memory + let credentials_len = credential_pointers.len(); + + // Allocate the array of pointers using COM as well + let credentials_pointer = if credentials_len > 0 { + let pointer_array_bytes = credential_pointers.len() * std::mem::size_of::<*mut ExperimentalWebAuthnPluginCredentialDetails>(); + let (ptr, _) = ComBuffer::from_buffer(unsafe { + std::slice::from_raw_parts( + credential_pointers.as_ptr() as *const u8, + pointer_array_bytes + ) + }); + ptr as *mut *mut ExperimentalWebAuthnPluginCredentialDetails + } else { + std::ptr::null_mut() + }; + // Convert CLSID to wide string and allocate with COM + let mut clsid_wide: Vec = clsid.encode_utf16().collect(); + clsid_wide.push(0); // null terminator + let clsid_bytes: Vec = clsid_wide.iter().flat_map(|&x| x.to_le_bytes()).collect(); + let (clsid_ptr, _) = ComBuffer::from_buffer(clsid_bytes); + Self { - plugin_clsid: clsid.into_win_utf16().0, + plugin_clsid: clsid_ptr as *mut u16, credential_count: credentials_len as u32, credentials: credentials_pointer, } } } -type EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration = +pub type EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration = unsafe extern "cdecl" fn( pCredentialDetailsList: *mut ExperimentalWebAuthnPluginCredentialDetailsList, ) -> HRESULT; +pub type EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentialsFnDeclaration = + unsafe extern "cdecl" fn( + pCredentialDetailsList: *mut ExperimentalWebAuthnPluginCredentialDetailsList, + ) -> HRESULT; + +pub type EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentialsFnDeclaration = + unsafe extern "cdecl" fn( + pwszPluginClsId: *const u16, + ppCredentialDetailsList: *mut *mut ExperimentalWebAuthnPluginCredentialDetailsList, + ) -> HRESULT; + +pub type EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentialsFnDeclaration = + unsafe extern "cdecl" fn( + pwszPluginClsId: *const u16, + ) -> HRESULT; + pub fn add_credentials( mut credentials_list: ExperimentalWebAuthnPluginCredentialDetailsList, ) -> std::result::Result<(), String> { + crate::utils::message("Loading EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials function..."); + let result = unsafe { delay_load::( s!("webauthn.dll"), @@ -150,13 +260,50 @@ pub fn add_credentials( ) }; + match result { + Some(api) => { + crate::utils::message("Function loaded successfully, calling API..."); + crate::utils::message(&format!("Credential list: plugin_clsid valid: {}, credential_count: {}", + !credentials_list.plugin_clsid.is_null(), credentials_list.credential_count)); + + let result = unsafe { api(&mut credentials_list) }; + + if result.is_err() { + let error_code = result.0; + crate::utils::message(&format!("API call failed with HRESULT: 0x{:x}", error_code)); + return Err(format!( + "Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials()\nHRESULT: 0x{:x}\n{}", + error_code, result.message() + )); + } + + crate::utils::message("API call succeeded"); + Ok(()) + }, + None => { + crate::utils::message("Failed to load EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials function from webauthn.dll"); + Err(String::from("Error: Can't complete add_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials can't be loaded.")) + } + } +} + +pub fn remove_credentials( + mut credentials_list: ExperimentalWebAuthnPluginCredentialDetailsList, +) -> std::result::Result<(), String> { + let result = unsafe { + delay_load::( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials"), + ) + }; + match result { Some(api) => { let result = unsafe { api(&mut credentials_list) }; if result.is_err() { return Err(format!( - "Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials()\n{}", + "Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials()\n{}", result.message() )); } @@ -164,7 +311,196 @@ pub fn add_credentials( Ok(()) }, None => { - Err(String::from("Error: Can't complete add_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials can't be loaded.")) + Err(String::from("Error: Can't complete remove_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials can't be loaded.")) } } } + +pub fn get_all_credentials( + plugin_clsid: String, +) -> std::result::Result, String> { + let result = unsafe { + delay_load::( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials"), + ) + }; + + match result { + Some(api) => { + // Create the wide string and keep it alive during the API call + let mut clsid_wide: Vec = plugin_clsid.encode_utf16().collect(); + clsid_wide.push(0); // null terminator + let mut credentials_list_ptr: *mut ExperimentalWebAuthnPluginCredentialDetailsList = std::ptr::null_mut(); + + let result = unsafe { api(clsid_wide.as_ptr(), &mut credentials_list_ptr) }; + + if result.is_err() { + return Err(format!( + "Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials()\n{}", + result.message() + )); + } + + if credentials_list_ptr.is_null() { + Ok(None) + } else { + // Note: The caller is responsible for managing the memory of the returned list + Ok(Some(unsafe { *credentials_list_ptr })) + } + }, + None => { + Err(String::from("Error: Can't complete get_all_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials can't be loaded.")) + } + } +} + +pub fn remove_all_credentials( + plugin_clsid: String, +) -> std::result::Result<(), String> { + crate::utils::message("Loading EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials function..."); + + let result = unsafe { + delay_load::( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials"), + ) + }; + + match result { + Some(api) => { + crate::utils::message("Function loaded successfully, calling API..."); + // Create the wide string and keep it alive during the API call + let mut clsid_wide: Vec = plugin_clsid.encode_utf16().collect(); + clsid_wide.push(0); // null terminator + + let result = unsafe { api(clsid_wide.as_ptr()) }; + + if result.is_err() { + let error_code = result.0; + crate::utils::message(&format!("API call failed with HRESULT: 0x{:x}", error_code)); + + return Err(format!( + "Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials()\nHRESULT: 0x{:x}\n{}", + error_code, result.message() + )); + } + + crate::utils::message("API call succeeded"); + Ok(()) + }, + None => { + crate::utils::message("Failed to load EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials function from webauthn.dll"); + Err(String::from("Error: Can't complete remove_all_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials can't be loaded.")) + } + } +} + +// Forward declarations for Windows types we need +type WEBAUTHN_ASSERTION = *const u8; // Placeholder - would need actual definition +type PCWEBAUTHN_USER_ENTITY_INFORMATION = *const u8; // Placeholder - would need actual definition +type WEBAUTHN_CREDENTIAL_LIST = *const u8; // Placeholder - would need actual definition +type EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS = *const u8; // Placeholder +type EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION = *const u8; // Placeholder + +/// CTAP CBOR Get Assertion Request structure +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnCtapCborGetAssertionRequest { + // Version of this structure, to allow for modifications in the future. + pub version: u32, // DWORD dwVersion + + // RP ID. After UTF8 to Unicode conversion, + pub rpid_unicode: *const u16, // PCWSTR pwszRpId + + // Input RP ID. Raw UTF8 bytes before conversion. + // These are the bytes to be hashed in the Authenticator Data. + pub rpid_byte_count: u32, // DWORD cbRpId + pub rpid_bytes: *const u8, // PBYTE pbRpId + + // Client Data Hash + pub client_data_hash_byte_count: u32, // DWORD cbClientDataHash + pub client_data_hash: *const u8, // PBYTE pbClientDataHash + + // Credentials used for inclusion + pub credential_list: WEBAUTHN_CREDENTIAL_LIST, // WEBAUTHN_CREDENTIAL_LIST CredentialList + + // Optional extensions to parse when performing the operation. + pub cbor_extensions_map_byte_count: u32, // DWORD cbCborExtensionsMap + pub cbor_extensions_map: *const u8, // PBYTE pbCborExtensionsMap + + // Authenticator Options (Optional) + pub authenticator_options: EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS, + + // Pin Auth (Optional) + pub empty_pin_auth: i32, // BOOL fEmptyPinAuth + pub pin_auth_byte_count: u32, // DWORD cbPinAuth + pub pin_auth: *const u8, // PBYTE pbPinAuth + + // HMAC Salt Extension (Optional) + pub hmac_salt_extension: EXPERIMENTAL_PWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION, + + // PRF Extension + pub hmac_secret_salt_values_byte_count: u32, // DWORD cbHmacSecretSaltValues + pub hmac_secret_salt_values: *const u8, // PBYTE pbHmacSecretSaltValues + + pub pin_protocol: u32, // DWORD dwPinProtocol + + // "credBlob": true extension + pub cred_blob_ext: i32, // LONG lCredBlobExt + + // "largeBlobKey": true extension + pub large_blob_key_ext: i32, // LONG lLargeBlobKeyExt + + // "largeBlob" extension + pub cred_large_blob_operation: u32, // DWORD dwCredLargeBlobOperation + pub cred_large_blob_compressed_byte_count: u32, // DWORD cbCredLargeBlobCompressed + pub cred_large_blob_compressed: *const u8, // PBYTE pbCredLargeBlobCompressed + pub cred_large_blob_original_size: u32, // DWORD dwCredLargeBlobOriginalSize + + // "json" extension. Nonzero if present + pub json_ext_byte_count: u32, // DWORD cbJsonExt + pub json_ext: *const u8, // PBYTE pbJsonExt +} + +pub type ExperimentalPWebAuthnCtapCborGetAssertionRequest = *mut ExperimentalWebAuthnCtapCborGetAssertionRequest; +pub type ExperimentalPcWebAuthnCtapCborGetAssertionRequest = *const ExperimentalWebAuthnCtapCborGetAssertionRequest; + +/// CTAP CBOR Get Assertion Response structure +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnCtapCborGetAssertionResponse { + // [1] credential (optional) + // [2] authenticatorData + // [3] signature + pub webauthn_assertion: WEBAUTHN_ASSERTION, + + // [4] user (optional) + pub user_information: PCWEBAUTHN_USER_ENTITY_INFORMATION, + + // [5] numberOfCredentials (optional) + pub number_of_credentials: u32, // DWORD + + // [6] userSelected (optional) + pub user_selected: i32, // LONG + + // [7] largeBlobKey (optional) + pub large_blob_key_byte_count: u32, // DWORD + pub large_blob_key: *mut u8, // PBYTE + + // [8] unsignedExtensionOutputs + pub unsigned_extension_outputs_byte_count: u32, // DWORD + pub unsigned_extension_outputs: *mut u8, // PBYTE +} + +pub type ExperimentalPWebAuthnCtapCborGetAssertionResponse = *mut ExperimentalWebAuthnCtapCborGetAssertionResponse; +pub type ExperimentalPcWebAuthnCtapCborGetAssertionResponse = *const ExperimentalWebAuthnCtapCborGetAssertionResponse; + +/// Function signature for encoding get assertion response +type EXPERIMENTAL_WebAuthNEncodeGetAssertionResponseFnDeclaration = unsafe extern "stdcall" fn( + pGetAssertionResponse: ExperimentalPcWebAuthnCtapCborGetAssertionResponse, + pcbResp: *mut u32, + ppbResp: *mut *mut u8, +) -> HRESULT; diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 6703dbb1c20..f4c60b3afb7 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -1,4 +1,6 @@ { + "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", + "extraMetadata": { "name": "bitwarden" }, @@ -88,9 +90,10 @@ }, "win": { "electronUpdaterCompatibility": ">=0.0.1", - "target": ["portable", "nsis-web", "appx"], + "target": ["appx"], "signtoolOptions": { - "sign": "./sign.js" + "sign": "./sign.js", + "publisherName": "CN=com.bitwarden.localdevelopment" }, "extraFiles": [ { @@ -162,8 +165,9 @@ "artifactName": "${productName}-Portable-${version}.${ext}" }, "appx": { - "artifactName": "${productName}-${version}-${arch}.${ext}", - "customManifestPath": "./custom-appx-manifest.xml" + "artifactName": "${productName}-${arch}.${ext}", + "customManifestPath": "./custom-appx-manifest.xml", + "publisher": "CN=com.bitwarden.localdevelopment" }, "deb": { "artifactName": "${productName}-${version}-${arch}.${ext}", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 9488828c146..9b9ccec9f2a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.6.0", + "version": "2025.6.4", "keywords": [ "bitwarden", "password", @@ -67,6 +67,7 @@ "upload:mas": "xcrun altool --upload-app --type osx --file \"$(find ./dist/mas-universal/Bitwarden*.pkg)\" --apiKey $APP_STORE_CONNECT_AUTH_KEY --apiIssuer $APP_STORE_CONNECT_TEAM_ISSUER", "test": "jest", "test:watch": "jest --watch", - "test:watch:all": "jest --watchAll" + "test:watch:all": "jest --watchAll", + "local:win": "cd desktop_native/napi && npm run build && cd ../.. && npm run build:dev && npm run pack:win" } } diff --git a/apps/desktop/sign.ps1 b/apps/desktop/sign.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..7709b3be8f3783970d353b93f862be63c64dca70 GIT binary patch literal 166 zcmZXMOA5kJ5Cm%-@D8~^-osz^x^f>c5RD0fpNA35>0^rEP6nEpp056m6<0bQ9C6KQF5Dh}*ev8D8i)U&qgS>{gQB=hFu=OuBmFOZ~%IhoX@{F;qhW41RP+5b_6 NL}@D6l$?PD857z^95(;} literal 0 HcmV?d00001 diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts index 2c006b5c928..c722035edf1 100644 --- a/apps/desktop/src/autofill/preload.ts +++ b/apps/desktop/src/autofill/preload.ts @@ -4,8 +4,12 @@ import type { autofill } from "@bitwarden/desktop-napi"; import { Command } from "../platform/main/autofill/command"; import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main"; +import { NativeAutofillSyncParams } from "../platform/main/autofill/sync.command"; export default { + + syncPasskeys: (params: NativeAutofillSyncParams): Promise => ipcRenderer.invoke("autofill.syncPasskeys", params), + runCommand: (params: RunCommandParams): Promise> => ipcRenderer.invoke("autofill.runCommand", params), diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index 7e60c6b8d76..03f14085def 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -61,10 +61,7 @@ export class DesktopAutofillService implements OnDestroy { .pipe( distinctUntilChanged(), switchMap((enabled) => { - if (!enabled) { - return EMPTY; - } - + return this.accountService.activeAccount$.pipe( map((account) => account?.id), filter((userId): userId is UserId => userId != null), @@ -80,47 +77,40 @@ export class DesktopAutofillService implements OnDestroy { .subscribe(); this.listenIpc(); + } /** Give metadata about all available credentials in the users vault */ async sync(cipherViews: CipherView[]) { - const status = await this.status(); - if (status.type === "error") { - return this.logService.error("Error getting autofill status", status.error); - } + this.logService.info("Syncing autofill credentials: ", cipherViews.length); + // const status = await this.status(); + // if (status.type === "error") { + // return this.logService.error("Error getting autofill status", status.error); + // } - if (!status.value.state.enabled) { - // Autofill is disabled - return; - } + // if (!status.value.state.enabled) { + // // Autofill is disabled + // return; + // } let fido2Credentials: NativeAutofillFido2Credential[]; let passwordCredentials: NativeAutofillPasswordCredential[]; - if (status.value.support.password) { - passwordCredentials = cipherViews - .filter( - (cipher) => - cipher.type === CipherType.Login && - cipher.login.uris?.length > 0 && - cipher.login.uris.some((uri) => uri.match !== UriMatchStrategy.Never) && - cipher.login.uris.some((uri) => !Utils.isNullOrWhitespace(uri.uri)) && - !Utils.isNullOrWhitespace(cipher.login.username), - ) - .map((cipher) => ({ - type: "password", - cipherId: cipher.id, - uri: cipher.login.uris.find((uri) => uri.match !== UriMatchStrategy.Never).uri, - username: cipher.login.username, - })); - } - - if (status.value.support.fido2) { - fido2Credentials = (await getCredentialsForAutofill(cipherViews)).map((credential) => ({ + fido2Credentials = (await getCredentialsForAutofill(cipherViews)).map((credential) => ({ type: "fido2", ...credential, })); - } + + + this.logService.info("Found FIDO2 credentials", fido2Credentials.length); + + console.log("ipc.autofill",ipc.autofill); + console.log("ipc.autofill.syncpasskeys", ipc.autofill.syncPasskeys); + const res = await ipc.autofill.syncPasskeys({ + credentials: [...fido2Credentials], + }); + this.logService.warning("syncPasskeys result", res); + const syncResult = await ipc.autofill.runCommand({ namespace: "autofill", diff --git a/apps/desktop/src/entry.ts b/apps/desktop/src/entry.ts index 2446f2519c5..9f03a84e627 100644 --- a/apps/desktop/src/entry.ts +++ b/apps/desktop/src/entry.ts @@ -3,8 +3,6 @@ import * as path from "path"; import { app } from "electron"; -import { passkey_authenticator } from "@bitwarden/desktop-napi"; - if ( process.platform === "darwin" && process.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1) @@ -42,8 +40,6 @@ if ( // eslint-disable-next-line const Main = require("./main").Main; - passkey_authenticator.register(); - const main = new Main(); main.bootstrap(); } diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index a3d811e572f..d4f372f9a33 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.6.0", + "version": "2025.6.17", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts index f66eea180cf..5b7dd5e1df9 100644 --- a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts +++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts @@ -1,11 +1,12 @@ import { ipcMain } from "electron"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { autofill } from "@bitwarden/desktop-napi"; +import { autofill, passkey_authenticator } from "@bitwarden/desktop-napi"; import { WindowMain } from "../../../main/window.main"; import { CommandDefinition } from "./command"; +import { NativeAutofillFido2Credential, NativeAutofillSyncParams } from "./sync.command"; export type RunCommandParams = { namespace: C["namespace"]; @@ -17,13 +18,278 @@ export type RunCommandResult = C["output"]; export class NativeAutofillMain { private ipcServer: autofill.IpcServer | null; + private pendingPasskeyRequests = new Map void>(); constructor( private logService: LogService, private windowMain: WindowMain, ) {} + initWindows() { + passkey_authenticator.register(); + void passkey_authenticator.onRequest(async (error, event) => { + this.logService.info("Passkey request received:", { error, event }); + + try { + const request = JSON.parse(event.requestJson); + this.logService.info("Parsed passkey request:", { type: event.requestType, request }); + + // Handle different request types based on the requestType field + switch (event.requestType) { + case "assertion": + return await this.handleAssertionRequest(request); + case "registration": + return await this.handleRegistrationRequest(request); + case "sync": + return await this.handleSyncRequest(request); + default: + this.logService.error("Unknown passkey request type:", event.requestType); + return JSON.stringify({ + type: "error", + message: `Unknown request type: ${event.requestType}`, + }); + } + } catch (parseError) { + this.logService.error("Failed to parse passkey request:", parseError); + return JSON.stringify({ + type: "error", + message: "Failed to parse request JSON", + }); + } + }); + } + + private async handleAssertionRequest( + request: passkey_authenticator.PasskeyAssertionRequest, + ): Promise { + this.logService.info("Handling assertion request for rpId:", request.rpId); + + const normalized_request: autofill.PasskeyAssertionRequest = { + rpId: request.rpId, + allowedCredentials: request.allowedCredentials, + clientDataHash: request.clientDataHash, + userVerification: autofill.UserVerification.Required, + windowXy: { x: 400, y: 400 }, + }; + + try { + // Generate unique identifiers for tracking this request + const clientId = Date.now(); + const sequenceNumber = Math.floor(Math.random() * 1000000); + + // Send request and wait for response + const response = await this.sendAndOptionallyWait( + "autofill.passkeyAssertion", + { + clientId, + sequenceNumber, + request: normalized_request, + }, + { waitForResponse: true, timeout: 60000 }, + ); + + if (response) { + // Convert the response to the format expected by the NAPI bridge + return JSON.stringify({ + type: "assertion_response", + credentialId: response.credentialId, + authenticatorData: response.authenticatorData, + signature: response.signature, + userHandle: response.userHandle, + }); + } else { + return JSON.stringify({ + type: "error", + message: "No response received from renderer", + }); + } + } catch (error) { + this.logService.error("Error in assertion request:", error); + return JSON.stringify({ + type: "error", + message: `Assertion request failed: ${error.message}`, + }); + } + } + + private async handleRegistrationRequest( + request: passkey_authenticator.PasskeyRegistrationRequest, + ): Promise { + this.logService.info("Handling registration request for rpId:", request.rpId); + + const normalized_request: autofill.PasskeyRegistrationRequest = { + rpId: request.rpId, + clientDataHash: request.clientDataHash, + userName: request.userName, + userHandle: request.userId, + userVerification: autofill.UserVerification.Required, + supportedAlgorithms: request.supportedAlgorithms, + windowXy: { x: 400, y: 400 }, + }; + + try { + // Generate unique identifiers for tracking this request + const clientId = Date.now(); + const sequenceNumber = Math.floor(Math.random() * 1000000); + + // Send request and wait for response + const response = await this.sendAndOptionallyWait( + "autofill.passkeyRegistration", + { + clientId, + sequenceNumber, + request: normalized_request, + }, + { waitForResponse: true, timeout: 60000 }, + ); + + this.logService.info("Received response for registration request:", response); + + if (response) { + // Convert the response to the format expected by the NAPI bridge + return JSON.stringify({ + type: "registration_response", + credentialId: response.credentialId, + attestationObject: response.attestationObject, + }); + } else { + return JSON.stringify({ + type: "error", + message: "No response received from renderer", + }); + } + } catch (error) { + this.logService.error("Error in registration request:", error); + return JSON.stringify({ + type: "error", + message: `Registration request failed: ${error.message}`, + }); + } + } + + private async handleSyncRequest( + request: passkey_authenticator.PasskeySyncRequest, + ): Promise { + this.logService.info("Handling sync request for rpId:", request.rpId); + + try { + // Generate unique identifiers for tracking this request + const clientId = Date.now(); + const sequenceNumber = Math.floor(Math.random() * 1000000); + + // Send sync request and wait for response + const response = await this.sendAndOptionallyWait( + "autofill.passkeySync", + { + clientId, + sequenceNumber, + request: { rpId: request.rpId }, + }, + { waitForResponse: true, timeout: 60000 }, + ); + + this.logService.info("Received response for sync request:", response); + + if (response && response.credentials) { + // Convert the response to the format expected by the NAPI bridge + return JSON.stringify({ + type: "sync_response", + credentials: response.credentials, + }); + } else { + return JSON.stringify({ + type: "error", + message: "No credentials received from renderer", + }); + } + } catch (error) { + this.logService.error("Error in sync request:", error); + return JSON.stringify({ + type: "error", + message: `Sync request failed: ${error.message}`, + }); + } + } + + /** + * Wrapper for webContents.send that optionally waits for a response + * @param channel The IPC channel to send to + * @param data The data to send + * @param options Optional configuration + * @returns Promise that resolves with the response if waitForResponse is true + */ + private async sendAndOptionallyWait( + channel: string, + data: any, + options?: { waitForResponse?: boolean; timeout?: number; responseChannel?: string }, + ): Promise { + if (!options?.waitForResponse) { + // Just send without waiting for response (existing behavior) + this.logService.info(`Sending fire-and-forget message to ${channel}`); + this.windowMain.win.webContents.send(channel, data); + return; + } + + // Use clientId and sequenceNumber as the tracking key + const trackingKey = `${data.clientId}_${data.sequenceNumber}`; + const responseChannel = options.responseChannel || `${channel}_response`; + const timeout = options.timeout || 30000; // 30 second default timeout + + // Send the original data without adding requestId + const dataWithId = { ...data }; + + this.logService.info(`Sending awaitable request ${trackingKey} to ${channel}`, { dataWithId }); + + return new Promise((resolve, reject) => { + // Set up timeout + const timeoutId = setTimeout(() => { + this.logService.warning(`Request ${trackingKey} timed out after ${timeout}ms`); + this.pendingPasskeyRequests.delete(trackingKey); + reject(new Error(`Request timeout after ${timeout}ms`)); + }, timeout); + + // Store the resolver + this.pendingPasskeyRequests.set(trackingKey, (response: T) => { + this.logService.info(`Request ${trackingKey} resolved with response:`, response); + clearTimeout(timeoutId); + this.pendingPasskeyRequests.delete(trackingKey); + resolve(response); + }); + + this.logService.info( + `Stored resolver for request ${trackingKey}, total pending: ${this.pendingPasskeyRequests.size}`, + ); + + // Send the request + this.windowMain.win.webContents.send(channel, dataWithId); + }); + } + async init() { + this.initWindows(); + + ipcMain.handle("autofill.syncPasskeys", async (event, data: NativeAutofillSyncParams): Promise => { + this.logService.info("autofill.syncPasskeys", data); + const { credentials } = data; + const mapped = credentials.map((cred: NativeAutofillFido2Credential) => { + const x: passkey_authenticator.SyncedCredential = { + credentialId: cred.credentialId, + rpId: cred.rpId, + userName: cred.userName, + userId: cred.userHandle + }; + this.logService.info("Mapped credential:", x); + return x; + }); + + this.logService.info("Syncing passkeys to Windows:", mapped); + + passkey_authenticator.syncCredentialsToWindows(mapped); + + return "worked"; + }); + + ipcMain.handle( "autofill.runCommand", ( @@ -79,23 +345,103 @@ export class NativeAutofillMain { ipcMain.on("autofill.completePasskeyRegistration", (event, data) => { this.logService.warning("autofill.completePasskeyRegistration", data); - const { clientId, sequenceNumber, response } = data; - this.ipcServer.completeRegistration(clientId, sequenceNumber, response); + const { clientId, sequenceNumber, response, requestId } = data; + + // Handle both IpcServer and awaitable requests + if (this.ipcServer && clientId !== -1) { + this.ipcServer.completeRegistration(clientId, sequenceNumber, response); + } + + // Handle awaitable passkey requests using clientId and sequenceNumber + if (clientId !== undefined && sequenceNumber !== undefined) { + const trackingKey = `${clientId}_${sequenceNumber}`; + this.handlePasskeyResponse(trackingKey, response); + } + // Fallback to requestId for backward compatibility + else if (requestId) { + this.handlePasskeyResponse(requestId, response); + } }); ipcMain.on("autofill.completePasskeyAssertion", (event, data) => { this.logService.warning("autofill.completePasskeyAssertion", data); - const { clientId, sequenceNumber, response } = data; - this.ipcServer.completeAssertion(clientId, sequenceNumber, response); + const { clientId, sequenceNumber, response, requestId } = data; + + // Handle both IpcServer and awaitable requests + if (this.ipcServer && clientId !== -1) { + this.ipcServer.completeAssertion(clientId, sequenceNumber, response); + } + + // Handle awaitable passkey requests using clientId and sequenceNumber + if (clientId !== undefined && sequenceNumber !== undefined) { + const trackingKey = `${clientId}_${sequenceNumber}`; + this.handlePasskeyResponse(trackingKey, response); + } + // Fallback to requestId for backward compatibility + else if (requestId) { + this.handlePasskeyResponse(requestId, response); + } + }); + + + ipcMain.on("autofill.completePasskeySync", (event, data) => { + this.logService.warning("autofill.completePasskeySync", data); + const { clientId, sequenceNumber, response, requestId } = data; + + // Handle awaitable passkey requests using clientId and sequenceNumber + if (clientId !== undefined && sequenceNumber !== undefined) { + const trackingKey = `${clientId}_${sequenceNumber}`; + this.handlePasskeyResponse(trackingKey, response); + } + // Fallback to requestId for backward compatibility + else if (requestId) { + this.handlePasskeyResponse(requestId, response); + } }); ipcMain.on("autofill.completeError", (event, data) => { this.logService.warning("autofill.completeError", data); - const { clientId, sequenceNumber, error } = data; - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + const { clientId, sequenceNumber, error, requestId } = data; + + // Handle both IpcServer and awaitable requests + if (this.ipcServer && clientId !== -1) { + this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + } + + // Handle awaitable passkey requests using clientId and sequenceNumber + if (clientId !== undefined && sequenceNumber !== undefined) { + const trackingKey = `${clientId}_${sequenceNumber}`; + this.handlePasskeyResponse(trackingKey, { error: String(error) }); + } + // Fallback to requestId for backward compatibility + else if (requestId) { + this.handlePasskeyResponse(requestId, { error: String(error) }); + } }); } + private handlePasskeyResponse(trackingKey: string, response: any): void { + this.logService.info("Received passkey response for tracking key:", trackingKey, response); + + if (!trackingKey) { + this.logService.error("Response missing tracking key:", response); + return; + } + + this.logService.info(`Looking for pending request with tracking key: ${trackingKey}`); + this.logService.info( + `Current pending requests: ${Array.from(this.pendingPasskeyRequests.keys())}`, + ); + + const resolver = this.pendingPasskeyRequests.get(trackingKey); + if (resolver) { + this.logService.info("Found resolver, calling with response data:", response); + resolver(response); + } else { + this.logService.warning("No pending request found for tracking key:", trackingKey); + } + } + private async runCommand( command: RunCommandParams, ): Promise> {