mirror of
https://github.com/bitwarden/browser
synced 2026-02-06 03:33:30 +00:00
UserId -> UserHandle
This commit is contained in:
4
apps/desktop/desktop_native/napi/index.d.ts
vendored
4
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -194,7 +194,7 @@ export declare namespace passkey_authenticator {
|
||||
credentialId: string
|
||||
rpId: string
|
||||
userName: string
|
||||
userId: string
|
||||
userHandle: string
|
||||
}
|
||||
export interface PasskeyAssertionRequest {
|
||||
rpId: string
|
||||
@@ -206,7 +206,7 @@ export declare namespace passkey_authenticator {
|
||||
export interface PasskeyRegistrationRequest {
|
||||
rpId: string
|
||||
transactionId: string
|
||||
userId: Array<number>
|
||||
userHandle: Array<number>
|
||||
userName: string
|
||||
clientDataHash: Array<number>
|
||||
userVerification: boolean
|
||||
|
||||
@@ -825,7 +825,7 @@ pub mod passkey_authenticator {
|
||||
pub credential_id: String, // base64url encoded
|
||||
pub rp_id: String,
|
||||
pub user_name: String,
|
||||
pub user_id: String, // base64url encoded
|
||||
pub user_handle: String, // base64url encoded
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
@@ -846,7 +846,7 @@ pub mod passkey_authenticator {
|
||||
pub struct PasskeyRegistrationRequest {
|
||||
pub rp_id: String,
|
||||
pub transaction_id: String,
|
||||
pub user_id: Vec<u8>,
|
||||
pub user_handle: Vec<u8>,
|
||||
pub user_name: String,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub user_verification: bool,
|
||||
|
||||
@@ -98,7 +98,7 @@ impl From<windows_plugin_authenticator::SyncedCredential> for SyncedCredential {
|
||||
.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),
|
||||
user_handle: base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cred.user_handle),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,8 @@ impl From<SyncedCredential> for windows_plugin_authenticator::SyncedCredential {
|
||||
.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)
|
||||
user_handle: base64::engine::general_purpose::URL_SAFE_NO_PAD
|
||||
.decode(&cred.user_handle)
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
@@ -134,10 +134,10 @@ pub fn sync_credentials_to_windows(credentials: Vec<SyncedCredential>) -> napi::
|
||||
} else {
|
||||
cred.credential_id.clone()
|
||||
};
|
||||
let truncated_user_id = if cred.user_id.len() > 16 {
|
||||
format!("{}...", &cred.user_id[..16])
|
||||
let truncated_user_id = if cred.user_handle.len() > 16 {
|
||||
format!("{}...", &cred.user_handle[..16])
|
||||
} else {
|
||||
cred.user_id.clone()
|
||||
cred.user_handle.clone()
|
||||
};
|
||||
log::info!(
|
||||
"[NAPI] Credential {}: RP={}, User={}, CredID={}, UserID={}",
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use serde_json;
|
||||
use std::alloc::{alloc, Layout};
|
||||
use std::ptr;
|
||||
use serde_json;
|
||||
use windows_core::{HRESULT, s};
|
||||
use windows_core::{s, HRESULT};
|
||||
|
||||
use crate::com_provider::ExperimentalWebAuthnPluginOperationResponse;
|
||||
use crate::types::*;
|
||||
use crate::utils::{self as util, delay_load};
|
||||
use crate::com_provider::ExperimentalWebAuthnPluginOperationResponse;
|
||||
use crate::webauthn::WEBAUTHN_CREDENTIAL_LIST;
|
||||
|
||||
// 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 pwszRpId: *const u16, // PCWSTR
|
||||
pub cbRpId: u32,
|
||||
pub pbRpId: *const u8,
|
||||
pub cbClientDataHash: u32,
|
||||
@@ -20,28 +21,13 @@ pub struct EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
|
||||
pub CredentialList: WEBAUTHN_CREDENTIAL_LIST,
|
||||
pub cbCborExtensionsMap: u32,
|
||||
pub pbCborExtensionsMap: *const u8,
|
||||
pub pAuthenticatorOptions: *const crate::webauthn::ExperimentalWebAuthnCtapCborAuthenticatorOptions,
|
||||
pub pAuthenticatorOptions:
|
||||
*const crate::webauthn::ExperimentalWebAuthnCtapCborAuthenticatorOptions,
|
||||
// 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_EX {
|
||||
pub dwVersion: u32,
|
||||
pub cbId: u32,
|
||||
pub pbId: *const u8,
|
||||
pub pwszCredentialType: *const u16, // LPCWSTR
|
||||
pub dwTransports: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WEBAUTHN_CREDENTIAL_LIST {
|
||||
pub cCredentials: u32,
|
||||
pub ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX,
|
||||
}
|
||||
pub type PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST =
|
||||
*mut EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST;
|
||||
|
||||
// Windows API function signatures for decoding get assertion requests
|
||||
type EXPERIMENTAL_WebAuthNDecodeGetAssertionRequestFn = unsafe extern "stdcall" fn(
|
||||
@@ -61,10 +47,13 @@ pub struct DecodedGetAssertionRequest {
|
||||
}
|
||||
|
||||
impl DecodedGetAssertionRequest {
|
||||
fn new(ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, free_fn: Option<EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequestFn>) -> Self {
|
||||
fn new(
|
||||
ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
|
||||
free_fn: Option<EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequestFn>,
|
||||
) -> Self {
|
||||
Self { ptr, free_fn }
|
||||
}
|
||||
|
||||
|
||||
pub fn as_ref(&self) -> &EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
@@ -75,44 +64,55 @@ impl Drop for DecodedGetAssertionRequest {
|
||||
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); }
|
||||
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<DecodedGetAssertionRequest, String> {
|
||||
pub unsafe fn decode_get_assertion_request(
|
||||
encoded_request: &[u8],
|
||||
) -> Result<DecodedGetAssertionRequest, String> {
|
||||
util::message("Attempting to decode get assertion request using Windows API");
|
||||
|
||||
|
||||
// Load the Windows WebAuthn API function
|
||||
let decode_fn: Option<EXPERIMENTAL_WebAuthNDecodeGetAssertionRequestFn> = delay_load(
|
||||
s!("webauthn.dll"),
|
||||
s!("EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest")
|
||||
s!("EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest"),
|
||||
);
|
||||
|
||||
let decode_fn = decode_fn.ok_or("Failed to load EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest from webauthn.dll")?;
|
||||
|
||||
|
||||
let decode_fn = decode_fn
|
||||
.ok_or("Failed to load EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest from webauthn.dll")?;
|
||||
|
||||
// Load the free function
|
||||
let free_fn: Option<EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequestFn> = delay_load(
|
||||
s!("webauthn.dll"),
|
||||
s!("EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequest")
|
||||
s!("EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequest"),
|
||||
);
|
||||
|
||||
let mut pp_get_assertion_request: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST = ptr::null_mut();
|
||||
|
||||
|
||||
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));
|
||||
return Err(format!(
|
||||
"EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest failed with HRESULT: {}",
|
||||
result.0
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Ok(DecodedGetAssertionRequest::new(pp_get_assertion_request, free_fn))
|
||||
|
||||
Ok(DecodedGetAssertionRequest::new(
|
||||
pp_get_assertion_request,
|
||||
free_fn,
|
||||
))
|
||||
}
|
||||
|
||||
/// Windows WebAuthn assertion request context
|
||||
@@ -124,65 +124,11 @@ pub struct WindowsAssertionRequest {
|
||||
pub user_verification: UserVerificationRequirement,
|
||||
}
|
||||
|
||||
/// Windows WebAuthn registration request context
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowsRegistrationRequest {
|
||||
pub rpid: String,
|
||||
pub user_id: Vec<u8>,
|
||||
pub user_name: String,
|
||||
pub user_display_name: Option<String>,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub excluded_credentials: Vec<Vec<u8>>,
|
||||
pub user_verification: UserVerificationRequirement,
|
||||
pub supported_algorithms: Vec<i32>,
|
||||
}
|
||||
|
||||
/// Parse allowed credentials from WEBAUTHN_CREDENTIAL_LIST
|
||||
pub unsafe fn parse_credential_list(credential_list: &WEBAUTHN_CREDENTIAL_LIST) -> Vec<Vec<u8>> {
|
||||
let mut allowed_credentials = Vec::new();
|
||||
|
||||
if credential_list.cCredentials == 0 || credential_list.ppCredentials.is_null() {
|
||||
util::message("No credentials in credential list");
|
||||
return allowed_credentials;
|
||||
}
|
||||
|
||||
util::message(&format!("Parsing {} credentials from credential list", credential_list.cCredentials));
|
||||
|
||||
// ppCredentials is an array of pointers to WEBAUTHN_CREDENTIAL_EX
|
||||
let credentials_array = std::slice::from_raw_parts(
|
||||
credential_list.ppCredentials,
|
||||
credential_list.cCredentials as usize
|
||||
);
|
||||
|
||||
for (i, &credential_ptr) in credentials_array.iter().enumerate() {
|
||||
if credential_ptr.is_null() {
|
||||
util::message(&format!("WARNING: Credential {} is null, skipping", i));
|
||||
continue;
|
||||
}
|
||||
|
||||
let credential = &*credential_ptr;
|
||||
|
||||
if credential.cbId == 0 || credential.pbId.is_null() {
|
||||
util::message(&format!("WARNING: Credential {} has invalid ID, skipping", i));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract credential ID bytes
|
||||
let credential_id_slice = std::slice::from_raw_parts(
|
||||
credential.pbId,
|
||||
credential.cbId as usize
|
||||
);
|
||||
|
||||
allowed_credentials.push(credential_id_slice.to_vec());
|
||||
util::message(&format!("Parsed credential {}: {} bytes", i, credential.cbId));
|
||||
}
|
||||
|
||||
util::message(&format!("Successfully parsed {} allowed credentials", allowed_credentials.len()));
|
||||
allowed_credentials
|
||||
}
|
||||
|
||||
/// Helper for assertion requests
|
||||
pub fn send_assertion_request(transaction_id: &str, request: &WindowsAssertionRequest) -> Option<PasskeyResponse> {
|
||||
pub fn send_assertion_request(
|
||||
transaction_id: &str,
|
||||
request: &WindowsAssertionRequest,
|
||||
) -> Option<PasskeyResponse> {
|
||||
let passkey_request = PasskeyAssertionRequest {
|
||||
rp_id: request.rpid.clone(),
|
||||
transaction_id: transaction_id.to_string(),
|
||||
@@ -190,17 +136,24 @@ pub fn send_assertion_request(transaction_id: &str, request: &WindowsAssertionRe
|
||||
allowed_credentials: request.allowed_credentials.clone(),
|
||||
user_verification: request.user_verification.clone(),
|
||||
};
|
||||
|
||||
util::message(&format!("Assertion request data - RP ID: {}, Client data hash: {} bytes, Allowed credentials: {}",
|
||||
request.rpid, request.client_data_hash.len(), request.allowed_credentials.len()));
|
||||
|
||||
|
||||
util::message(&format!(
|
||||
"Assertion request data - RP ID: {}, Client data hash: {} bytes, Allowed credentials: {}",
|
||||
request.rpid,
|
||||
request.client_data_hash.len(),
|
||||
request.allowed_credentials.len()
|
||||
));
|
||||
|
||||
match serde_json::to_string(&passkey_request) {
|
||||
Ok(request_json) => {
|
||||
util::message(&format!("Sending assertion request: {}", request_json));
|
||||
crate::ipc::send_passkey_request(RequestType::Assertion, request_json, &request.rpid)
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
util::message(&format!("ERROR: Failed to serialize assertion request: {}", e));
|
||||
util::message(&format!(
|
||||
"ERROR: Failed to serialize assertion request: {}",
|
||||
e
|
||||
));
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -211,79 +164,93 @@ pub unsafe fn create_get_assertion_response(
|
||||
credential_id: Vec<u8>,
|
||||
authenticator_data: Vec<u8>,
|
||||
signature: Vec<u8>,
|
||||
user_handle: Vec<u8>
|
||||
user_handle: Vec<u8>,
|
||||
) -> 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())),
|
||||
(
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)),
|
||||
];
|
||||
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)
|
||||
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));
|
||||
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::<ExperimentalWebAuthnPluginOperationResponse>();
|
||||
let operation_response_ptr = alloc(response_layout) as *mut ExperimentalWebAuthnPluginOperationResponse;
|
||||
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,
|
||||
});
|
||||
|
||||
ptr::write(
|
||||
operation_response_ptr,
|
||||
ExperimentalWebAuthnPluginOperationResponse {
|
||||
encoded_response_byte_count: response_len as u32,
|
||||
encoded_response_pointer: response_ptr,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(operation_response_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
use std::ptr;
|
||||
use windows::Win32::System::Com::*;
|
||||
use windows_core::{implement, interface, IInspectable, IUnknown, Interface, HRESULT};
|
||||
use std::ptr;
|
||||
|
||||
use crate::assert::{
|
||||
create_get_assertion_response, decode_get_assertion_request, send_assertion_request,
|
||||
WindowsAssertionRequest,
|
||||
};
|
||||
use crate::make_credential::{
|
||||
create_make_credential_response, decode_make_credential_request, send_registration_request,
|
||||
WindowsRegistrationRequest,
|
||||
};
|
||||
use crate::types::*;
|
||||
use crate::utils::{self as util, wstr_to_string};
|
||||
use crate::assert::{WindowsAssertionRequest, decode_get_assertion_request, create_get_assertion_response, send_assertion_request, parse_credential_list};
|
||||
use crate::make_credential::{WindowsRegistrationRequest, decode_make_credential_request, create_make_credential_response, send_registration_request};
|
||||
use crate::webauthn::WEBAUTHN_CREDENTIAL_LIST;
|
||||
|
||||
/// Used when creating and asserting credentials.
|
||||
/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST
|
||||
@@ -62,6 +69,59 @@ pub unsafe trait EXPERIMENTAL_IPluginAuthenticator: windows_core::IUnknown {
|
||||
) -> HRESULT;
|
||||
}
|
||||
|
||||
pub unsafe fn parse_credential_list(credential_list: &WEBAUTHN_CREDENTIAL_LIST) -> Vec<Vec<u8>> {
|
||||
let mut allowed_credentials = Vec::new();
|
||||
|
||||
if credential_list.cCredentials == 0 || credential_list.ppCredentials.is_null() {
|
||||
util::message("No credentials in credential list");
|
||||
return allowed_credentials;
|
||||
}
|
||||
|
||||
util::message(&format!(
|
||||
"Parsing {} credentials from credential list",
|
||||
credential_list.cCredentials
|
||||
));
|
||||
|
||||
// ppCredentials is an array of pointers to WEBAUTHN_CREDENTIAL_EX
|
||||
let credentials_array = std::slice::from_raw_parts(
|
||||
credential_list.ppCredentials,
|
||||
credential_list.cCredentials as usize,
|
||||
);
|
||||
|
||||
for (i, &credential_ptr) in credentials_array.iter().enumerate() {
|
||||
if credential_ptr.is_null() {
|
||||
util::message(&format!("WARNING: Credential {} is null, skipping", i));
|
||||
continue;
|
||||
}
|
||||
|
||||
let credential = &*credential_ptr;
|
||||
|
||||
if credential.cbId == 0 || credential.pbId.is_null() {
|
||||
util::message(&format!(
|
||||
"WARNING: Credential {} has invalid ID, skipping",
|
||||
i
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract credential ID bytes
|
||||
let credential_id_slice =
|
||||
std::slice::from_raw_parts(credential.pbId, credential.cbId as usize);
|
||||
|
||||
allowed_credentials.push(credential_id_slice.to_vec());
|
||||
util::message(&format!(
|
||||
"Parsed credential {}: {} bytes",
|
||||
i, credential.cbId
|
||||
));
|
||||
}
|
||||
|
||||
util::message(&format!(
|
||||
"Successfully parsed {} allowed credentials",
|
||||
allowed_credentials.len()
|
||||
));
|
||||
allowed_credentials
|
||||
}
|
||||
|
||||
#[implement(EXPERIMENTAL_IPluginAuthenticator)]
|
||||
pub struct PluginAuthenticatorComObject;
|
||||
|
||||
@@ -75,51 +135,60 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
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));
|
||||
|
||||
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
|
||||
req.encoded_request_pointer,
|
||||
req.encoded_request_byte_count as usize,
|
||||
);
|
||||
|
||||
util::message(&format!("Encoded request: {} bytes", encoded_request_slice.len()));
|
||||
|
||||
|
||||
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);
|
||||
@@ -132,21 +201,21 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
@@ -154,7 +223,7 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
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);
|
||||
@@ -167,51 +236,65 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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() {
|
||||
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
|
||||
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_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
|
||||
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;
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
|
||||
// Extract user verification requirement from authenticator options
|
||||
let user_verification = if !decoded_request.pAuthenticatorOptions.is_null() {
|
||||
let auth_options = &*decoded_request.pAuthenticatorOptions;
|
||||
@@ -223,13 +306,16 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
|
||||
// Extract excluded credentials from credential list (for make credential, these are credentials to exclude)
|
||||
let excluded_credentials = parse_credential_list(&decoded_request.CredentialList);
|
||||
if !excluded_credentials.is_empty() {
|
||||
util::message(&format!("Found {} excluded credentials for make credential", excluded_credentials.len()));
|
||||
util::message(&format!(
|
||||
"Found {} excluded credentials for make credential",
|
||||
excluded_credentials.len()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
// Create Windows registration request
|
||||
let registration_request = WindowsRegistrationRequest {
|
||||
rpid: rpid.clone(),
|
||||
@@ -241,40 +327,55 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
user_verification: user_verification.unwrap_or_default(),
|
||||
supported_algorithms,
|
||||
};
|
||||
|
||||
util::message(&format!("Make credential request - RP: {}, User: {}",
|
||||
rpid,
|
||||
registration_request.user_name));
|
||||
|
||||
|
||||
util::message(&format!(
|
||||
"Make credential request - RP: {}, User: {}",
|
||||
rpid, registration_request.user_name
|
||||
));
|
||||
|
||||
// Send registration request
|
||||
if let Some(passkey_response) = send_registration_request(&transaction_id, ®istration_request) {
|
||||
util::message(&format!("Registration response received: {:?}", passkey_response));
|
||||
|
||||
if let Some(passkey_response) =
|
||||
send_registration_request(&transaction_id, ®istration_request)
|
||||
{
|
||||
util::message(&format!(
|
||||
"Registration response received: {:?}",
|
||||
passkey_response
|
||||
));
|
||||
|
||||
// Create proper WebAuthn response from passkey_response
|
||||
match passkey_response {
|
||||
PasskeyResponse::RegistrationResponse { credential_id, attestation_object } => {
|
||||
PasskeyResponse::RegistrationResponse {
|
||||
credential_id,
|
||||
attestation_object,
|
||||
} => {
|
||||
util::message("Creating WebAuthn make credential response");
|
||||
|
||||
match create_make_credential_response(credential_id, attestation_object) {
|
||||
|
||||
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));
|
||||
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");
|
||||
util::message(
|
||||
"ERROR: Unexpected response type for registration request",
|
||||
);
|
||||
*response = ptr::null_mut();
|
||||
HRESULT(-1)
|
||||
}
|
||||
@@ -284,9 +385,12 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
*response = ptr::null_mut();
|
||||
HRESULT(-1)
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
util::message(&format!("ERROR: Failed to decode make credential request: {}", e));
|
||||
util::message(&format!(
|
||||
"ERROR: Failed to decode make credential request: {}",
|
||||
e
|
||||
));
|
||||
*response = ptr::null_mut();
|
||||
HRESULT(-1)
|
||||
}
|
||||
@@ -299,35 +403,38 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
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));
|
||||
|
||||
|
||||
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
|
||||
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");
|
||||
@@ -343,20 +450,22 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Extract client data hash
|
||||
let client_data_hash = if decoded_request.cbClientDataHash == 0 || decoded_request.pbClientDataHash.is_null() {
|
||||
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
|
||||
decoded_request.pbClientDataHash,
|
||||
decoded_request.cbClientDataHash as usize,
|
||||
);
|
||||
hash_slice.to_vec()
|
||||
};
|
||||
|
||||
|
||||
// Extract user verification requirement from authenticator options
|
||||
let user_verification = if !decoded_request.pAuthenticatorOptions.is_null() {
|
||||
let auth_options = &*decoded_request.pAuthenticatorOptions;
|
||||
@@ -368,10 +477,10 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
|
||||
// Extract allowed credentials from credential list
|
||||
let allowed_credentials = parse_credential_list(&decoded_request.CredentialList);
|
||||
|
||||
|
||||
// Create Windows assertion request
|
||||
let assertion_request = WindowsAssertionRequest {
|
||||
rpid: rpid.clone(),
|
||||
@@ -379,36 +488,60 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
allowed_credentials: allowed_credentials.clone(),
|
||||
user_verification: user_verification.unwrap_or_default(),
|
||||
};
|
||||
|
||||
util::message(&format!("Get assertion request - RP: {}, Allowed credentials: {}", rpid, allowed_credentials.len()));
|
||||
|
||||
|
||||
util::message(&format!(
|
||||
"Get assertion request - RP: {}, Allowed credentials: {}",
|
||||
rpid,
|
||||
allowed_credentials.len()
|
||||
));
|
||||
|
||||
// Send assertion request
|
||||
if let Some(passkey_response) = send_assertion_request(&transaction_id, &assertion_request) {
|
||||
util::message(&format!("Assertion response received: {:?}", passkey_response));
|
||||
|
||||
if let Some(passkey_response) =
|
||||
send_assertion_request(&transaction_id, &assertion_request)
|
||||
{
|
||||
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 } => {
|
||||
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) {
|
||||
|
||||
match create_get_assertion_response(
|
||||
credential_id,
|
||||
authenticator_data,
|
||||
signature,
|
||||
user_handle,
|
||||
) {
|
||||
Ok(webauthn_response) => {
|
||||
util::message("Successfully created WebAuthn assertion 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));
|
||||
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();
|
||||
@@ -420,9 +553,12 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp
|
||||
*response = ptr::null_mut();
|
||||
HRESULT(-1)
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
util::message(&format!("ERROR: Failed to decode get assertion request: {}", e));
|
||||
util::message(&format!(
|
||||
"ERROR: Failed to decode get assertion request: {}",
|
||||
e
|
||||
));
|
||||
*response = ptr::null_mut();
|
||||
HRESULT(-1)
|
||||
}
|
||||
@@ -445,11 +581,11 @@ impl IClassFactory_Impl for Factory_Impl {
|
||||
iid: *const windows_core::GUID,
|
||||
object: *mut *mut core::ffi::c_void,
|
||||
) -> windows_core::Result<()> {
|
||||
let unknown: IInspectable = PluginAuthenticatorComObject.into(); // TODO: IUnknown ?
|
||||
let unknown: IInspectable = PluginAuthenticatorComObject.into(); // TODO: IUnknown ?
|
||||
unsafe { unknown.query(iid, object).ok() }
|
||||
}
|
||||
|
||||
fn LockServer(&self, lock: windows_core::BOOL) -> windows_core::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,23 +9,26 @@ use windows_core::*;
|
||||
|
||||
// New modular structure
|
||||
mod assert;
|
||||
mod com_buffer;
|
||||
mod com_provider;
|
||||
mod com_registration;
|
||||
mod ipc;
|
||||
mod make_credential;
|
||||
mod sync;
|
||||
mod ipc;
|
||||
mod com_registration;
|
||||
pub mod utils;
|
||||
mod types;
|
||||
pub mod utils;
|
||||
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, UserVerificationRequirement};
|
||||
pub use assert::{WindowsAssertionRequest, WindowsRegistrationRequest};
|
||||
pub use com_registration::{initialize_com_library, register_com_library, add_authenticator};
|
||||
pub use assert::WindowsAssertionRequest;
|
||||
pub use com_registration::{add_authenticator, initialize_com_library, register_com_library};
|
||||
pub use ipc::{send_passkey_request, set_request_sender};
|
||||
pub use make_credential::WindowsRegistrationRequest;
|
||||
pub use sync::{get_credentials_from_windows, send_sync_request, sync_credentials_to_windows};
|
||||
pub use types::{
|
||||
PasskeyRequest, PasskeyResponse, RequestEvent, RequestType, SyncedCredential,
|
||||
UserVerificationRequirement,
|
||||
};
|
||||
|
||||
// Re-export utilities
|
||||
pub use utils as util;
|
||||
@@ -60,7 +63,7 @@ pub fn register() -> std::result::Result<(), String> {
|
||||
|
||||
let r = syncCredentials();
|
||||
util::message(&format!("sync credentials: {:?}", r));
|
||||
|
||||
|
||||
if let Err(e) = r {
|
||||
util::message(&format!("syncCredentials failed: {}", e));
|
||||
}
|
||||
@@ -72,22 +75,17 @@ 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
|
||||
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
|
||||
user_handle: 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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,31 +1,44 @@
|
||||
use serde_json;
|
||||
use std::alloc::{alloc, Layout};
|
||||
use std::ptr;
|
||||
use serde_json;
|
||||
use windows_core::{HRESULT, s};
|
||||
use windows_core::{s, HRESULT};
|
||||
|
||||
use crate::com_provider::ExperimentalWebAuthnPluginOperationResponse;
|
||||
use crate::types::*;
|
||||
use crate::utils::{self as util, delay_load};
|
||||
use crate::com_provider::ExperimentalWebAuthnPluginOperationResponse;
|
||||
use crate::assert::{WindowsRegistrationRequest, parse_credential_list};
|
||||
use crate::webauthn::WEBAUTHN_CREDENTIAL_LIST;
|
||||
|
||||
/// Windows WebAuthn registration request context
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowsRegistrationRequest {
|
||||
pub rpid: String,
|
||||
pub user_id: Vec<u8>,
|
||||
pub user_name: String,
|
||||
pub user_display_name: Option<String>,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub excluded_credentials: Vec<Vec<u8>>,
|
||||
pub user_verification: UserVerificationRequirement,
|
||||
pub supported_algorithms: Vec<i32>,
|
||||
}
|
||||
|
||||
// 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
|
||||
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 cbId: u32, // DWORD
|
||||
pub pbId: *const u8, // PBYTE
|
||||
pub pwszName: *const u16, // PCWSTR
|
||||
pub pwszIcon: *const u16, // PCWSTR
|
||||
pub pwszDisplayName: *const u16, // PCWSTR
|
||||
}
|
||||
|
||||
@@ -33,8 +46,8 @@ pub struct WEBAUTHN_USER_ENTITY_INFORMATION {
|
||||
#[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
|
||||
pub pwszCredentialType: *const u16, // LPCWSTR
|
||||
pub lAlg: i32, // LONG - COSE algorithm identifier
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@@ -44,23 +57,6 @@ pub struct WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
|
||||
pub pCredentialParameters: *const WEBAUTHN_COSE_CREDENTIAL_PARAMETER,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WEBAUTHN_CREDENTIAL_EX {
|
||||
pub dwVersion: u32,
|
||||
pub cbId: u32,
|
||||
pub pbId: *const u8,
|
||||
pub pwszCredentialType: *const u16, // LPCWSTR
|
||||
pub dwTransports: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WEBAUTHN_CREDENTIAL_LIST {
|
||||
pub cCredentials: u32,
|
||||
pub ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX,
|
||||
}
|
||||
|
||||
// Make Credential Request structure (from sample header)
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -72,15 +68,17 @@ pub struct EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
|
||||
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 WebAuthNCredentialParameters: WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, // Matches C++ sample
|
||||
pub CredentialList: WEBAUTHN_CREDENTIAL_LIST,
|
||||
pub cbCborExtensionsMap: u32,
|
||||
pub pbCborExtensionsMap: *const u8,
|
||||
pub pAuthenticatorOptions: *const crate::webauthn::ExperimentalWebAuthnCtapCborAuthenticatorOptions,
|
||||
pub pAuthenticatorOptions:
|
||||
*const crate::webauthn::ExperimentalWebAuthnCtapCborAuthenticatorOptions,
|
||||
// Add other fields as needed...
|
||||
}
|
||||
|
||||
pub type PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST = *mut EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST;
|
||||
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(
|
||||
@@ -100,10 +98,13 @@ pub struct DecodedMakeCredentialRequest {
|
||||
}
|
||||
|
||||
impl DecodedMakeCredentialRequest {
|
||||
fn new(ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST, free_fn: Option<EXPERIMENTAL_WebAuthNFreeDecodedMakeCredentialRequestFn>) -> Self {
|
||||
fn new(
|
||||
ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
|
||||
free_fn: Option<EXPERIMENTAL_WebAuthNFreeDecodedMakeCredentialRequestFn>,
|
||||
) -> Self {
|
||||
Self { ptr, free_fn }
|
||||
}
|
||||
|
||||
|
||||
pub fn as_ref(&self) -> &EXPERIMENTAL_WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
@@ -114,16 +115,20 @@ impl Drop for DecodedMakeCredentialRequest {
|
||||
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); }
|
||||
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<DecodedMakeCredentialRequest, String> {
|
||||
pub unsafe fn decode_make_credential_request(
|
||||
encoded_request: &[u8],
|
||||
) -> Result<DecodedMakeCredentialRequest, String> {
|
||||
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::<EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequestFn>(
|
||||
s!("webauthn.dll"),
|
||||
@@ -131,102 +136,120 @@ pub unsafe fn decode_make_credential_request(encoded_request: &[u8]) -> Result<D
|
||||
) {
|
||||
Some(func) => func,
|
||||
None => {
|
||||
return Err("Failed to load EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequest from webauthn.dll".to_string());
|
||||
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::<EXPERIMENTAL_WebAuthNFreeDecodedMakeCredentialRequestFn>(
|
||||
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();
|
||||
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
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));
|
||||
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))
|
||||
|
||||
Ok(DecodedMakeCredentialRequest::new(
|
||||
pp_make_credential_request,
|
||||
free_fn,
|
||||
))
|
||||
}
|
||||
|
||||
/// Helper for registration requests
|
||||
pub fn send_registration_request(transaction_id: &str, request: &WindowsRegistrationRequest) -> Option<PasskeyResponse> {
|
||||
pub fn send_registration_request(
|
||||
transaction_id: &str,
|
||||
request: &WindowsRegistrationRequest,
|
||||
) -> Option<PasskeyResponse> {
|
||||
util::message(&format!("Registration request data - RP ID: {}, User ID: {} bytes, User name: {}, Client data hash: {} bytes, Algorithms: {:?}, Excluded credentials: {}",
|
||||
request.rpid, request.user_id.len(), request.user_name, request.client_data_hash.len(), request.supported_algorithms, request.excluded_credentials.len()));
|
||||
|
||||
|
||||
let passkey_request = PasskeyRegistrationRequest {
|
||||
rp_id: request.rpid.clone(),
|
||||
transaction_id: transaction_id.to_string(),
|
||||
user_id: request.user_id.clone(),
|
||||
user_handle: request.user_id.clone(),
|
||||
user_name: request.user_name.clone(),
|
||||
client_data_hash: request.client_data_hash.clone(),
|
||||
user_verification: request.user_verification.clone(),
|
||||
supported_algorithms: request.supported_algorithms.clone(),
|
||||
excluded_credentials: request.excluded_credentials.clone(),
|
||||
};
|
||||
|
||||
|
||||
match serde_json::to_string(&passkey_request) {
|
||||
Ok(request_json) => {
|
||||
util::message(&format!("Sending registration request: {}", request_json));
|
||||
crate::ipc::send_passkey_request(RequestType::Registration, request_json, &request.rpid)
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
util::message(&format!("ERROR: Failed to serialize registration request: {}", 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<u8>, attestation_object: Vec<u8>) -> std::result::Result<*mut ExperimentalWebAuthnPluginOperationResponse, HRESULT> {
|
||||
pub unsafe fn create_make_credential_response(
|
||||
credential_id: Vec<u8>,
|
||||
attestation_object: Vec<u8>,
|
||||
) -> 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::<ExperimentalWebAuthnPluginOperationResponse>();
|
||||
let operation_response_ptr = alloc(response_layout) as *mut ExperimentalWebAuthnPluginOperationResponse;
|
||||
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,
|
||||
});
|
||||
|
||||
ptr::write(
|
||||
operation_response_ptr,
|
||||
ExperimentalWebAuthnPluginOperationResponse {
|
||||
encoded_response_byte_count: response_len as u32,
|
||||
encoded_response_pointer: response_ptr,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(operation_response_ptr)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,103 +1,128 @@
|
||||
use serde_json;
|
||||
use hex;
|
||||
use serde_json;
|
||||
|
||||
use crate::ipc::send_passkey_request;
|
||||
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<PasskeyResponse> {
|
||||
util::message(&format!("[SYNC] send_sync_request called for RP ID: {}", rpid));
|
||||
|
||||
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] 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)),
|
||||
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));
|
||||
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<SyncedCredential>, 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));
|
||||
|
||||
pub fn sync_credentials_to_windows(
|
||||
credentials: Vec<SyncedCredential>,
|
||||
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]))
|
||||
let truncated_user_id = if cred.user_handle.len() > 16 {
|
||||
format!("{}...", hex::encode(&cred.user_handle[..16]))
|
||||
} else {
|
||||
hex::encode(&cred.user_id)
|
||||
hex::encode(&cred.user_handle)
|
||||
};
|
||||
|
||||
|
||||
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()));
|
||||
|
||||
i + 1, cred.rp_id, cred.user_name, truncated_cred_id, cred.credential_id.len(), truncated_user_id, cred.user_handle.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.rp_id.clone(), // Use RP ID as friendly name for now
|
||||
cred.user_handle.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));
|
||||
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
|
||||
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));
|
||||
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");
|
||||
@@ -108,9 +133,12 @@ pub fn sync_credentials_to_windows(credentials: Vec<SyncedCredential>, plugin_cl
|
||||
Ok(()) => {
|
||||
util::message("Successfully synced credentials to Windows");
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
util::message(&format!("ERROR: Failed to add credentials to Windows: {}", e));
|
||||
util::message(&format!(
|
||||
"ERROR: Failed to add credentials to Windows: {}",
|
||||
e
|
||||
));
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
@@ -119,89 +147,102 @@ pub fn sync_credentials_to_windows(credentials: Vec<SyncedCredential>, plugin_cl
|
||||
|
||||
/// 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<Vec<SyncedCredential>, String> {
|
||||
util::message(&format!("Getting all credentials from Windows for plugin CLSID: {}", plugin_clsid));
|
||||
|
||||
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));
|
||||
|
||||
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
|
||||
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 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
|
||||
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())
|
||||
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<u8>
|
||||
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 user_id =
|
||||
if cred.user_id_byte_count > 0 && !cred.user_id_pointer.is_null() {
|
||||
// Convert from UTF-8 bytes back to Vec<u8>
|
||||
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,
|
||||
user_handle: 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));
|
||||
util::message(&format!(
|
||||
"ERROR: Failed to get credentials from Windows: {}",
|
||||
e
|
||||
));
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use tokio::sync::oneshot;
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum UserVerificationRequirement {
|
||||
Required,
|
||||
Preferred,
|
||||
Preferred,
|
||||
Discouraged,
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ pub struct PasskeyAssertionRequest {
|
||||
pub struct PasskeyRegistrationRequest {
|
||||
pub rp_id: String,
|
||||
pub transaction_id: String,
|
||||
pub user_id: Vec<u8>,
|
||||
pub user_handle: Vec<u8>,
|
||||
pub user_name: String,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub user_verification: UserVerificationRequirement,
|
||||
pub supported_algorithms: Vec<i32>, // COSE algorithm identifiers
|
||||
pub excluded_credentials: Vec<Vec<u8>>, // Credentials to exclude from creation
|
||||
pub supported_algorithms: Vec<i32>, // COSE algorithm identifiers
|
||||
pub excluded_credentials: Vec<Vec<u8>>, // Credentials to exclude from creation
|
||||
}
|
||||
|
||||
/// Sync request structure
|
||||
@@ -80,26 +80,22 @@ pub enum PasskeyRequest {
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum PasskeyResponse {
|
||||
#[serde(rename = "assertion_response",rename_all = "camelCase")]
|
||||
#[serde(rename = "assertion_response", rename_all = "camelCase")]
|
||||
AssertionResponse {
|
||||
credential_id: Vec<u8>,
|
||||
authenticator_data: Vec<u8>,
|
||||
signature: Vec<u8>,
|
||||
user_handle: Vec<u8>,
|
||||
},
|
||||
#[serde(rename = "registration_response",rename_all = "camelCase")]
|
||||
#[serde(rename = "registration_response", rename_all = "camelCase")]
|
||||
RegistrationResponse {
|
||||
credential_id: Vec<u8>,
|
||||
attestation_object: Vec<u8>,
|
||||
},
|
||||
#[serde(rename = "sync_response",rename_all = "camelCase")]
|
||||
SyncResponse {
|
||||
credentials: Vec<SyncedCredential>,
|
||||
},
|
||||
#[serde(rename = "error",rename_all = "camelCase")]
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
#[serde(rename = "sync_response", rename_all = "camelCase")]
|
||||
SyncResponse { credentials: Vec<SyncedCredential> },
|
||||
#[serde(rename = "error", rename_all = "camelCase")]
|
||||
Error { message: String },
|
||||
}
|
||||
|
||||
/// Credential data for sync operations
|
||||
@@ -109,7 +105,7 @@ pub struct SyncedCredential {
|
||||
pub credential_id: Vec<u8>,
|
||||
pub rp_id: String,
|
||||
pub user_name: String,
|
||||
pub user_id: Vec<u8>,
|
||||
pub user_handle: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Request type enumeration for type discrimination
|
||||
@@ -126,4 +122,4 @@ pub struct RequestEvent {
|
||||
pub request_type: RequestType,
|
||||
pub request_json: String,
|
||||
pub response_sender: oneshot::Sender<PasskeyResponse>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
|
||||
use std::fs::{OpenOptions, create_dir_all};
|
||||
use std::fs::{create_dir_all, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use windows::Win32::Foundation::*;
|
||||
use windows::Win32::System::LibraryLoader::*;
|
||||
@@ -59,17 +59,13 @@ impl WindowsString for String {
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
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) => {
|
||||
@@ -79,10 +75,10 @@ pub fn file_log(msg: &str) {
|
||||
let mins = (total_secs / 60) % 60;
|
||||
let hours = (total_secs / 3600) % 24;
|
||||
format!("{:02}:{:02}:{:02}.{:03}", hours, mins, secs, millis)
|
||||
},
|
||||
Err(_) => "??:??:??.???".to_string()
|
||||
}
|
||||
Err(_) => "??:??:??.???".to_string(),
|
||||
};
|
||||
|
||||
|
||||
let _ = writeln!(file, "[{}] {}", timestamp, msg);
|
||||
}
|
||||
}
|
||||
@@ -92,18 +88,20 @@ pub fn message(message: &str) {
|
||||
}
|
||||
|
||||
// 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<String, std::string::FromUtf16Error> {
|
||||
pub unsafe fn wstr_to_string(
|
||||
wstr_ptr: *const u16,
|
||||
) -> std::result::Result<String, std::string::FromUtf16Error> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,10 +407,27 @@ pub fn remove_all_credentials(
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WEBAUTHN_CREDENTIAL_EX {
|
||||
pub dwVersion: u32,
|
||||
pub cbId: u32,
|
||||
pub pbId: *const u8,
|
||||
pub pwszCredentialType: *const u16, // LPCWSTR
|
||||
pub dwTransports: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WEBAUTHN_CREDENTIAL_LIST {
|
||||
pub cCredentials: u32,
|
||||
pub ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX,
|
||||
}
|
||||
|
||||
// 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 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
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ export class NativeAutofillMain {
|
||||
rpId: request.rpId,
|
||||
clientDataHash: request.clientDataHash,
|
||||
userName: request.userName,
|
||||
userHandle: request.userId,
|
||||
userHandle: request.userHandle,
|
||||
userVerification: autofill.UserVerification.Required,
|
||||
supportedAlgorithms: request.supportedAlgorithms,
|
||||
windowXy: { x: 400, y: 400 },
|
||||
@@ -221,7 +221,7 @@ export class NativeAutofillMain {
|
||||
private async sendAndOptionallyWait<T = any>(
|
||||
channel: string,
|
||||
data: any,
|
||||
options?: { waitForResponse?: boolean; timeout?: number; responseChannel?: string },
|
||||
options?: { waitForResponse?: boolean; timeout?: number },
|
||||
): Promise<T | void> {
|
||||
if (!options?.waitForResponse) {
|
||||
// Just send without waiting for response (existing behavior)
|
||||
@@ -232,7 +232,6 @@ export class NativeAutofillMain {
|
||||
|
||||
// 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
|
||||
@@ -268,27 +267,29 @@ export class NativeAutofillMain {
|
||||
async init() {
|
||||
this.initWindows();
|
||||
|
||||
ipcMain.handle("autofill.syncPasskeys", async (event, data: NativeAutofillSyncParams): Promise<string> => {
|
||||
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;
|
||||
});
|
||||
ipcMain.handle(
|
||||
"autofill.syncPasskeys",
|
||||
async (event, data: NativeAutofillSyncParams): Promise<string> => {
|
||||
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,
|
||||
userHandle: cred.userHandle,
|
||||
};
|
||||
this.logService.info("Mapped credential:", x);
|
||||
return x;
|
||||
});
|
||||
|
||||
this.logService.info("Syncing passkeys to Windows:", mapped);
|
||||
|
||||
passkey_authenticator.syncCredentialsToWindows(mapped);
|
||||
this.logService.info("Syncing passkeys to Windows:", mapped);
|
||||
|
||||
return "worked";
|
||||
});
|
||||
passkey_authenticator.syncCredentialsToWindows(mapped);
|
||||
|
||||
return "worked";
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
"autofill.runCommand",
|
||||
@@ -383,7 +384,6 @@ export class NativeAutofillMain {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ipcMain.on("autofill.completePasskeySync", (event, data) => {
|
||||
this.logService.warning("autofill.completePasskeySync", data);
|
||||
const { clientId, sequenceNumber, response, requestId } = data;
|
||||
|
||||
Reference in New Issue
Block a user