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 cdea50aee99..2fc91742657 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -4,12 +4,14 @@ use std::ffi::c_uchar; use std::ptr; +use webauthn::*; use windows::Win32::Foundation::*; use windows::Win32::System::Com::*; use windows::Win32::System::LibraryLoader::*; use windows_core::*; mod pluginauthenticator; +mod util; mod webauthn; const AUTHENTICATOR_NAME: &str = "Bitwarden Desktop Authenticator"; @@ -27,6 +29,25 @@ pub fn register() -> std::result::Result<(), String> { add_authenticator()?; + // add test credential + let 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_list: Vec = + vec![test_credential]; + let credentials = ExperimentalWebAuthnPluginCredentialDetailsList::create( + String::from(CLSID), + test_credential_list, + ); + + let result = add_credentials(credentials); + println!("test: {:?}", result); + Ok(()) } @@ -105,7 +126,7 @@ fn add_authenticator() -> std::result::Result<(), String> { let add_authenticator_options = webauthn::ExperimentalWebAuthnPluginAddAuthenticatorOptions { authenticator_name: authenticator_name_ptr, - com_clsid: clsid_ptr, + plugin_clsid: clsid_ptr, rpid: relying_party_id_ptr, light_theme_logo: ptr::null(), // unused by Windows dark_theme_logo: ptr::null(), // unused by Windows diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs index 132f9effcde..410226ead41 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs @@ -1,5 +1,8 @@ /* - This file exposes the functions and types defined here: https://github.com/microsoft/webauthn/blob/master/experimental/pluginauthenticator.h + 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 */ use windows::Win32::System::Com::*; diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/util.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/util.rs new file mode 100644 index 00000000000..8302a3796eb --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/util.rs @@ -0,0 +1,42 @@ +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); +} + +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) + } +} 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 18c7563ffd8..4dffff794e0 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs @@ -1,7 +1,19 @@ /* - This file exposes the functions and types defined here: https://github.com/microsoft/webauthn/blob/master/experimental/webauthn.h + This file exposes safe functions and types for interacting with the experimental + Windows WebAuthn API defined here: + + https://github.com/microsoft/webauthn/blob/master/experimental/webauthn.h */ +use std::ffi::c_uchar; +use std::ptr; +use windows::Win32::Foundation::*; +use windows::Win32::System::Com::*; +use windows::Win32::System::LibraryLoader::*; +use windows_core::*; + +use crate::util::*; + /// Used when adding a Windows plugin authenticator. /// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS /// Header File Usage: EXPERIMENTAL_WebAuthNPluginAddAuthenticator() @@ -9,7 +21,7 @@ #[derive(Debug, Copy, Clone)] pub struct ExperimentalWebAuthnPluginAddAuthenticatorOptions { pub authenticator_name: *const u16, - pub com_clsid: *const u16, + pub plugin_clsid: *const u16, pub rpid: *const u16, pub light_theme_logo: *const u16, pub dark_theme_logo: *const u16, @@ -27,3 +39,132 @@ pub struct ExperimentalWebAuthnPluginAddAuthenticatorResponse { pub plugin_operation_signing_key_byte_count: u32, pub plugin_operation_signing_key: *mut u8, } + +/// Represents a credential. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS +/// Header File Usage: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginCredentialDetails { + pub credential_id_byte_count: u32, + pub credential_id_pointer: *mut u8, + pub rpid: *mut u16, + pub rp_friendly_name: *mut u16, + pub user_id_byte_count: u32, + pub user_id: *mut u16, + pub user_name: *mut u16, + pub user_display_name: *mut u16, +} + +impl ExperimentalWebAuthnPluginCredentialDetails { + pub fn create( + credential_id: String, + rpid: String, + rp_friendly_name: String, + user_id: String, + 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(); + + Self { + credential_id_byte_count, + credential_id_pointer, + rpid: rpid.into_win_utf16().0, + rp_friendly_name: rp_friendly_name.into_win_utf16().0, + user_id_byte_count, + user_id, + user_name: user_name.into_win_utf16().0, + user_display_name: user_display_name.into_win_utf16().0, + } + } +} + +/// Represents a list of credentials. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST +/// Header File Usage: EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials() +/// EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials() +/// EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginCredentialDetailsList { + pub plugin_clsid: *mut u16, + pub credential_count: u32, + pub credentials: *mut *mut ExperimentalWebAuthnPluginCredentialDetails, +} + +/* +let mut credentials: Vec + +let mut credentials: Vec<*mut ExperimentalWebAuthnPluginCredentialDetails> = credentials + .iter() + .map(|cred| cred as *mut _) + .collect(); + +let credentials_len = credentials.len(); +let credentials_capacity = credentials.capacity(); +let mut credentials_pointer = credentials.as_mut_ptr(); +std::mem::forget(credentials); +*/ + +impl ExperimentalWebAuthnPluginCredentialDetailsList { + pub fn create( + clsid: String, + mut credentials: Vec, + ) -> Self { + let mut credentials: Vec<*mut ExperimentalWebAuthnPluginCredentialDetails> = credentials + .into_iter() + .map(|mut cred| { + let cred_pointer: *mut ExperimentalWebAuthnPluginCredentialDetails = &mut cred; + cred_pointer + }) + .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 + + Self { + plugin_clsid: clsid.into_win_utf16().0, + credential_count: credentials_len as u32, + credentials: credentials_pointer, + } + } +} + +type EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration = + unsafe extern "cdecl" fn( + pCredentialDetailsList: *mut ExperimentalWebAuthnPluginCredentialDetailsList, + ) -> HRESULT; + +pub fn add_credentials( + mut credentials_list: ExperimentalWebAuthnPluginCredentialDetailsList, +) -> std::result::Result<(), String> { + let result = unsafe { + delay_load::( + s!("webauthn.dll"), + s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials"), + ) + }; + + 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{}", + result.message() + )); + } + + Ok(()) + }, + None => { + Err(String::from("Error: Can't complete add_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials can't be loaded.")) + } + } +} diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index d51d9412d80..c53afa06d93 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -162,7 +162,7 @@ "backgroundColor": "#175DDC", "applicationId": "bitwardendesktop", "identityName": "8bitSolutionsLLC.bitwardendesktop", - "publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418", + "publisher": "CN=com.bitwarden.localdevelopment", "publisherDisplayName": "Bitwarden Inc", "languages": [ "en-US", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index e00df0b26df..aaca0f56b1a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -46,7 +46,7 @@ "pack:mac:mas:with-extension": "npm run clean:dist && npm run build:macos-extension:mas && electron-builder --mac mas --universal -p never", "pack:mac:masdev": "npm run clean:dist && electron-builder --mac mas-dev --universal -p never", "pack:mac:masdev:with-extension": "npm run clean:dist && npm run build:macos-extension:masdev && electron-builder --mac mas-dev --universal -p never", - "pack:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"", + "pack:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never", "pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never", "dist:dir": "npm run build && npm run pack:dir", "dist:lin": "npm run build && npm run pack:lin", diff --git a/apps/desktop/src/entry.ts b/apps/desktop/src/entry.ts index 9f03a84e627..2446f2519c5 100644 --- a/apps/desktop/src/entry.ts +++ b/apps/desktop/src/entry.ts @@ -3,6 +3,8 @@ 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) @@ -40,6 +42,8 @@ if ( // eslint-disable-next-line const Main = require("./main").Main; + passkey_authenticator.register(); + const main = new Main(); main.bootstrap(); }