1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-04 10:43:47 +00:00

PM-19255: Initial add (refactor) for add credentials

This commit is contained in:
Colton Hurst
2025-04-25 19:11:08 -04:00
parent 24786a08df
commit e98f56f565
7 changed files with 217 additions and 6 deletions

View File

@@ -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<ExperimentalWebAuthnPluginCredentialDetails> =
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

View File

@@ -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::*;

View File

@@ -0,0 +1,42 @@
use windows::Win32::Foundation::*;
use windows::Win32::System::LibraryLoader::*;
use windows_core::*;
pub unsafe fn delay_load<T>(library: PCSTR, function: PCSTR) -> Option<T> {
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<u16> = self.encode_utf16().collect();
v.push(0);
(v.as_mut_ptr(), v.len() as u32)
}
}

View File

@@ -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<ExperimentalWebAuthnPluginCredentialDetails>
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<ExperimentalWebAuthnPluginCredentialDetails>,
) -> 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::<EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration>(
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."))
}
}
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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();
}