1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 21:50:15 +00:00

Implement PluginAuthenticator::make_credential

This commit is contained in:
Isaiah Inuwa
2025-11-20 17:49:56 -06:00
parent 6ce04191ed
commit ab1e92e8da
7 changed files with 967 additions and 76 deletions

View File

@@ -21,7 +21,10 @@ pub fn get_assertion(
let client_data_hash = request.client_data_hash().to_vec();
// Extract user verification requirement from authenticator options
let user_verification = match request.authenticator_options().user_verification() {
let user_verification = match request
.authenticator_options()
.and_then(|opts| opts.user_verification())
{
Some(true) => UserVerification::Required,
Some(false) => UserVerification::Discouraged,
None => UserVerification::Preferred,
@@ -29,7 +32,7 @@ pub fn get_assertion(
// Extract allowed credentials from credential list
let allowed_credential_ids: Vec<Vec<u8>> = request
.credential_list()
.allow_credentials()
.iter()
.filter_map(|cred| cred.credential_id())
.map(|id| id.to_vec())

View File

@@ -22,6 +22,7 @@ use win_webauthn::{PluginAddAuthenticatorOptions, WebAuthnPlugin};
use crate::{
ipc2::{ConnectionStatus, TimedCallback, WindowsProviderClient},
make_credential::make_credential,
win_webauthn::{
AuthenticatorInfo, CtapVersion, PluginAuthenticator, PluginCancelOperationRequest,
PluginGetAssertionRequest, PluginLockStatus, PluginMakeCredentialRequest,
@@ -105,7 +106,8 @@ impl PluginAuthenticator for BitwardenPluginAuthenticator {
request: PluginMakeCredentialRequest,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
tracing::debug!("Received MakeCredential: {request:?}");
Err(format!("MakeCredential not implemented").into())
let client = self.get_client();
make_credential::make_credential(&client, request)
}
fn get_assertion(

View File

@@ -1,5 +1,6 @@
use serde_json;
use std::collections::HashMap;
use std::io::Write;
use std::mem::ManuallyDrop;
use std::ptr;
use std::sync::Arc;
@@ -15,6 +16,120 @@ use crate::ipc2::{
};
use crate::util::{delay_load, wstr_to_string, WindowsString};
use crate::webauthn::WEBAUTHN_CREDENTIAL_LIST;
use crate::win_webauthn::{
CtapTransport, ErrorKind, HwndExt, PluginMakeCredentialRequest, PluginMakeCredentialResponse,
WinWebAuthnError,
};
pub fn make_credential(
ipc_client: &WindowsProviderClient,
request: PluginMakeCredentialRequest,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
tracing::debug!("=== PluginMakeCredential() called ===");
// Extract RP information
let rp_info = request
.rp_information()
.ok_or_else(|| "RP information is null".to_string())?;
let rpid = rp_info.id()?;
// let rp_name = rp_info.name().unwrap_or_else(|| String::new());
// Extract user information
let user = request
.user_information()
.ok_or_else(|| "User information is null".to_string())?;
let user_handle = user
.id()
.map_err(|err| format!("User ID is required for registration: {err}"))?
.to_vec();
let user_name = user
.name()
.map_err(|err| format!("User name is required for registration: {err}"))?;
// let user_display_name = user.display_name();
// Extract client data hash
let client_data_hash = request
.client_data_hash()
.map_err(|err| format!("Client data hash is required for registration: {err}"))?
.to_vec();
// Extract supported algorithms
let supported_algorithms: Vec<i32> = request
.pub_key_cred_params()
.iter()
.map(|params| params.alg())
.collect();
// Extract user verification requirement from authenticator options
let user_verification = match request
.authenticator_options()
.and_then(|opts| opts.user_verification())
{
Some(true) => UserVerification::Required,
Some(false) => UserVerification::Discouraged,
None => UserVerification::Preferred,
};
// Extract excluded credentials from credential list
let excluded_credentials: Vec<Vec<u8>> = request
.exclude_credentials()
.iter()
.filter_map(|cred| cred.credential_id())
.map(|id| id.to_vec())
.collect();
if !excluded_credentials.is_empty() {
tracing::debug!(
"Found {} excluded credentials for make credential",
excluded_credentials.len()
);
}
let transaction_id = request.transaction_id.to_u128().to_le_bytes().to_vec();
let client_pos = request
.window_handle
.center_position()
.unwrap_or((640, 480));
// Create Windows registration request
let registration_request = PasskeyRegistrationRequest {
rp_id: rpid.clone(),
user_handle: user_handle,
user_name: user_name,
// user_display_name: user_info.2,
client_data_hash,
excluded_credentials,
user_verification: user_verification,
supported_algorithms,
window_xy: Position {
x: client_pos.0,
y: client_pos.1,
},
context: transaction_id,
};
tracing::debug!(
"Make credential request - RP: {}, User: {}",
rpid,
registration_request.user_name
);
// Send registration request
let passkey_response = send_registration_request(ipc_client, registration_request)
.map_err(|err| format!("Registration request failed: {err}"))?;
tracing::debug!("Registration response received: {:?}", passkey_response);
// Create proper WebAuthn response from passkey_response
tracing::debug!("Creating WebAuthn make credential response");
let webauthn_response = create_make_credential_response(passkey_response.attestation_object)
.map_err(|err| format!("Failed to create WebAuthn response: {err}"))?;
tracing::debug!("Successfully created WebAuthn response: {webauthn_response:?}");
Ok(webauthn_response)
}
// Windows API types for WebAuthn (from webauthn.h.sample)
#[repr(C)]
@@ -331,39 +446,64 @@ fn send_registration_request(
}
/// Creates a CTAP make credential response from Bitwarden's WebAuthn registration response
unsafe fn create_make_credential_response(
fn create_make_credential_response(
attestation_object: Vec<u8>,
) -> std::result::Result<Vec<u8>, HRESULT> {
) -> std::result::Result<Vec<u8>, WinWebAuthnError> {
use ciborium::Value;
// Use the attestation object directly as the encoded response
let att_obj_items = ciborium::from_reader::<Value, _>(&attestation_object[..])
.map_err(|_| HRESULT(-1))?
.map_err(|err| {
WinWebAuthnError::with_cause(
ErrorKind::Serialization,
"Failed to deserialize WebAuthn attestation object",
err,
)
})?
.into_map()
.map_err(|_| HRESULT(-1))?;
.map_err(|_| WinWebAuthnError::new(ErrorKind::Serialization, "object is not a CBOR map"))?;
let webauthn_att_obj: HashMap<&str, &Value> = att_obj_items
.iter()
.map(|(k, v)| (k.as_text().unwrap(), v))
.collect();
let webauthn_encode_make_credential_response =
delay_load::<WebAuthNEncodeMakeCredentialResponseFn>(
s!("webauthn.dll"),
s!("WebAuthNEncodeMakeCredentialResponse"),
)
.unwrap();
let att_fmt = webauthn_att_obj
.get("fmt")
.ok_or(HRESULT(-1))?
.as_text()
.ok_or(HRESULT(-1))?
.to_utf16();
.and_then(|s| s.as_text())
.ok_or(WinWebAuthnError::new(
ErrorKind::Serialization,
"could not read `fmt` key as a string",
))?
.to_string();
let authenticator_data = webauthn_att_obj
.get("authData")
.ok_or(HRESULT(-1))?
.as_bytes()
.ok_or(HRESULT(-1))?;
let attestation = WEBAUTHN_CREDENTIAL_ATTESTATION {
.and_then(|d| d.as_bytes())
.ok_or(WinWebAuthnError::new(
ErrorKind::Serialization,
"could not read `authData` key as bytes",
))?
.clone();
let attestation = PluginMakeCredentialResponse {
format_type: att_fmt,
authenticator_data: authenticator_data,
attestation_statement: None,
attestation_object: None,
credential_id: None,
extensions: None,
used_transport: CtapTransport::Internal,
ep_att: false,
large_blob_supported: false,
resident_key: true,
prf_enabled: false,
unsigned_extension_outputs: None,
hmac_secret: None,
third_party_payment: false,
transports: Some(vec![CtapTransport::Internal, CtapTransport::Hybrid]),
client_data_json: None,
registration_response_json: None,
};
/*
{
dwVersion: 8,
pwszFormatType: att_fmt.as_ptr(),
cbAuthenticatorData: authenticator_data.len() as u32,
@@ -395,19 +535,8 @@ unsafe fn create_make_credential_response(
cbRegistrationResponseJSON: 0,
pbRegistrationResponseJSON: ptr::null_mut(),
};
let mut response_len = 0;
let mut response_ptr = ptr::null_mut();
let result = webauthn_encode_make_credential_response(
&attestation,
&mut response_len,
&mut response_ptr,
);
if result.is_err() {
return Err(result);
}
let response = Vec::from_raw_parts(response_ptr, response_len as usize, response_len as usize);
Ok(response)
*/
attestation.to_ctap_response()
}
/// Implementation of PluginMakeCredential moved from com_provider.rs
@@ -429,7 +558,6 @@ pub unsafe fn plugin_make_credential(
}
let req = &*request;
let transaction_id = format!("{:?}", req.transaction_id);
let coords = req.window_coordinates().unwrap_or((400, 400));

View File

@@ -105,27 +105,44 @@ impl IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE,
) -> HRESULT {
tracing::debug!("MakeCredential called");
// Convert to legacy format for internal processing
if request.is_null() || response.is_null() {
tracing::debug!("MakeCredential: Invalid request or response pointers passed");
return HRESULT(-1);
if response.is_null() {
tracing::warn!(
"GetAssertion called with null response pointer from Windows. Aborting request."
);
return E_INVALIDARG;
}
// TODO: verify request signature
return HRESULT(-1);
let op_request_ptr = match NonNull::new(request as *mut WEBAUTHN_PLUGIN_OPERATION_REQUEST) {
Some(p) => p,
None => {
tracing::warn!(
"GetAssertion called with null request pointer from Windows. Aborting request."
);
return E_INVALIDARG;
}
};
/*
match self.handler.make_credential(request) {
Ok(response) => {
// todo DECODE
// TODO: verify request signature
let registration_request = match op_request_ptr.try_into() {
Ok(r) => r,
Err(err) => {
tracing::error!("Could not deserialize MakeCredential request: {err}");
return E_FAIL;
}
};
match self.handler.make_credential(registration_request) {
Ok(registration_response) => {
let (ptr, len) = ComBuffer::from_buffer(registration_response);
(*response).cbEncodedResponse = len;
(*response).pbEncodedResponse = ptr;
tracing::debug!("MakeCredential completed successfully");
S_OK
}
Err(err) => {
tracing::error!("MakeCredential failed: {err}");
HRESULT(-1)
E_FAIL
}
}
*/
}
unsafe fn GetAssertion(
@@ -162,13 +179,6 @@ impl IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
let (ptr, len) = ComBuffer::from_buffer(assertion_response);
(*response).cbEncodedResponse = len;
(*response).pbEncodedResponse = ptr;
/*
std::ptr::copy_nonoverlapping(
assertion_response.as_ptr(),
(*response).pbEncodedResponse,
assertion_response.len(),
);
*/
tracing::debug!("GetAssertion completed successfully");
S_OK
}

View File

@@ -7,9 +7,9 @@ use std::{error::Error, fmt::Display, ptr::NonNull};
use windows::core::GUID;
pub use types::{
AuthenticatorInfo, CtapVersion, PluginAddAuthenticatorOptions, PluginCancelOperationRequest,
PluginGetAssertionRequest, PluginLockStatus, PluginMakeCredentialRequest,
PublicKeyCredentialParameters,
AuthenticatorInfo, CtapTransport, CtapVersion, PluginAddAuthenticatorOptions,
PluginCancelOperationRequest, PluginGetAssertionRequest, PluginLockStatus,
PluginMakeCredentialRequest, PluginMakeCredentialResponse, PublicKeyCredentialParameters,
};
pub use com::PluginAuthenticator;

View File

@@ -1,17 +1,22 @@
//! Types and functions defined in the Windows WebAuthn API.
use std::{collections::HashSet, mem::MaybeUninit, ptr::NonNull};
use std::{collections::HashSet, fmt::Display, mem::MaybeUninit, ptr::NonNull};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use ciborium::Value;
use windows::{
core::{GUID, HRESULT},
Win32::{Foundation::HWND, System::LibraryLoader::GetProcAddress},
Win32::{
Foundation::HWND, System::LibraryLoader::GetProcAddress,
UI::WindowsAndMessaging::WindowFromPoint,
},
};
use windows_core::{s, PCWSTR};
use crate::win_webauthn::{
com::ComBuffer, util::WindowsString, Clsid, ErrorKind, WinWebAuthnError,
com::ComBuffer,
util::{ArrayPointerIterator, WindowsString},
Clsid, ErrorKind, WinWebAuthnError,
};
macro_rules! webauthn_call {
@@ -379,14 +384,719 @@ pub enum WebAuthnPluginRequestType {
CTAP2_CBOR = 0x01,
}
#[derive(Debug)]
pub struct PluginMakeCredentialRequest {}
// pub struct PluginMakeCredentialResponse {}
// Windows API types for WebAuthn (from webauthn.h.sample)
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
pub struct WEBAUTHN_RP_ENTITY_INFORMATION {
dwVersion: u32,
pwszId: *const u16, // PCWSTR
pwszName: *const u16, // PCWSTR
pwszIcon: *const u16, // PCWSTR
}
impl WEBAUTHN_RP_ENTITY_INFORMATION {
/// Relying party ID.
pub fn id(&self) -> Result<String, WinWebAuthnError> {
if self.pwszId.is_null() {
return Err(WinWebAuthnError::new(
ErrorKind::WindowsInternal,
"Received invalid RP ID",
));
}
unsafe {
PCWSTR(self.pwszId).to_string().map_err(|err| {
WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "Invalid RP ID", err)
})
}
}
/// Relying party name.
pub fn name(&self) -> Result<String, WinWebAuthnError> {
if self.pwszName.is_null() {
return Err(WinWebAuthnError::new(
ErrorKind::WindowsInternal,
"Received invalid RP name",
));
}
unsafe {
PCWSTR(self.pwszName).to_string().map_err(|err| {
WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "Invalid RP name", err)
})
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct WEBAUTHN_USER_ENTITY_INFORMATION {
pub dwVersion: u32,
pub cbId: u32, // DWORD
pub pbId: *const u8, // PBYTE
pub pwszName: *const u16, // PCWSTR
pub pwszIcon: *const u16, // PCWSTR
pub pwszDisplayName: *const u16, // PCWSTR
}
impl WEBAUTHN_USER_ENTITY_INFORMATION {
/// User handle.
pub fn id(&self) -> Result<&[u8], WinWebAuthnError> {
if self.cbId == 0 || self.pbId.is_null() {
return Err(WinWebAuthnError::new(
ErrorKind::WindowsInternal,
"Received invalid user ID",
));
}
unsafe { Ok(std::slice::from_raw_parts(self.pbId, self.cbId as usize)) }
}
/// User name.
pub fn name(&self) -> Result<String, WinWebAuthnError> {
if self.pwszName.is_null() {
return Err(WinWebAuthnError::new(
ErrorKind::WindowsInternal,
"Received invalid user name",
));
}
unsafe {
PCWSTR(self.pwszName).to_string().map_err(|err| {
WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "Invalid user name", err)
})
}
}
/// User display name.
pub fn display_name(&self) -> Result<String, WinWebAuthnError> {
if self.pwszDisplayName.is_null() {
return Err(WinWebAuthnError::new(
ErrorKind::WindowsInternal,
"Received invalid user name",
));
}
unsafe {
PCWSTR(self.pwszDisplayName).to_string().map_err(|err| {
WinWebAuthnError::with_cause(
ErrorKind::WindowsInternal,
"Invalid user display name",
err,
)
})
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct WEBAUTHN_COSE_CREDENTIAL_PARAMETER {
pub dwVersion: u32,
pub pwszCredentialType: *const u16, // LPCWSTR
pub lAlg: i32, // LONG - COSE algorithm identifier
}
impl WEBAUTHN_COSE_CREDENTIAL_PARAMETER {
pub fn credential_type(&self) -> Result<String, WinWebAuthnError> {
if self.pwszCredentialType.is_null() {
return Err(WinWebAuthnError::new(
ErrorKind::WindowsInternal,
"Invalid credential type",
));
}
unsafe {
PCWSTR(self.pwszCredentialType).to_string().map_err(|err| {
WinWebAuthnError::with_cause(
ErrorKind::WindowsInternal,
"Invalid credential type",
err,
)
})
}
}
pub fn alg(&self) -> i32 {
self.lAlg
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
cCredentialParameters: u32,
pCredentialParameters: *const WEBAUTHN_COSE_CREDENTIAL_PARAMETER,
}
impl WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
pub fn iter(&self) -> ArrayPointerIterator<'_, WEBAUTHN_COSE_CREDENTIAL_PARAMETER> {
unsafe {
ArrayPointerIterator::new(
self.pCredentialParameters,
self.cCredentialParameters as usize,
)
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
pub dwVersion: u32,
pub cbRpId: u32,
pub pbRpId: *const u8,
pub cbClientDataHash: u32,
pub pbClientDataHash: *const u8,
pub pRpInformation: *const WEBAUTHN_RP_ENTITY_INFORMATION,
pub pUserInformation: *const WEBAUTHN_USER_ENTITY_INFORMATION,
pub WebAuthNCredentialParameters: WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, // Matches C++ sample
pub CredentialList: WEBAUTHN_CREDENTIAL_LIST,
pub cbCborExtensionsMap: u32,
pub pbCborExtensionsMap: *const u8,
pub pAuthenticatorOptions: *const WebAuthnCtapCborAuthenticatorOptions,
// Add other fields as needed...
}
#[derive(Debug)]
pub struct PluginMakeCredentialRequest {
inner: *const WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
pub window_handle: HWND,
pub transaction_id: GUID,
pub request_signature: Vec<u8>,
}
impl PluginMakeCredentialRequest {
pub fn client_data_hash(&self) -> Result<&[u8], WinWebAuthnError> {
if self.as_ref().cbClientDataHash == 0 || self.as_ref().pbClientDataHash.is_null() {
return Err(WinWebAuthnError::new(
ErrorKind::WindowsInternal,
"Received invalid client data hash",
));
}
unsafe {
Ok(std::slice::from_raw_parts(
self.as_ref().pbClientDataHash,
self.as_ref().cbClientDataHash as usize,
))
}
}
pub fn rp_information(&self) -> Option<&WEBAUTHN_RP_ENTITY_INFORMATION> {
let ptr = self.as_ref().pRpInformation;
if ptr.is_null() {
return None;
}
unsafe { Some(&*ptr) }
}
pub fn user_information(&self) -> Option<&WEBAUTHN_USER_ENTITY_INFORMATION> {
let ptr = self.as_ref().pUserInformation;
if ptr.is_null() {
return None;
}
unsafe { Some(&*ptr) }
}
pub fn pub_key_cred_params(&self) -> WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
self.as_ref().WebAuthNCredentialParameters
}
pub fn exclude_credentials(&self) -> CredentialList {
self.as_ref().CredentialList
}
/// CTAP CBOR extensions map
pub fn extensions(&self) -> Option<&[u8]> {
let (len, ptr) = (
self.as_ref().cbCborExtensionsMap,
self.as_ref().pbCborExtensionsMap,
);
if len == 0 || ptr.is_null() {
return None;
}
unsafe { Some(std::slice::from_raw_parts(ptr, len as usize)) }
}
pub fn authenticator_options(&self) -> Option<WebAuthnCtapCborAuthenticatorOptions> {
let ptr = self.as_ref().pAuthenticatorOptions;
if ptr.is_null() {
return None;
}
unsafe { Some(*ptr) }
}
}
impl AsRef<WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST> for PluginMakeCredentialRequest {
fn as_ref(&self) -> &WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
unsafe { &*self.inner }
}
}
impl Drop for PluginMakeCredentialRequest {
fn drop(&mut self) {
if !self.inner.is_null() {
// leak memory if we cannot find the free function
_ = webauthn_free_decoded_make_credential_request(
self.inner as *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
);
}
}
}
impl TryFrom<NonNull<WEBAUTHN_PLUGIN_OPERATION_REQUEST>> for PluginMakeCredentialRequest {
type Error = WinWebAuthnError;
fn try_from(value: NonNull<WEBAUTHN_PLUGIN_OPERATION_REQUEST>) -> Result<Self, Self::Error> {
unsafe {
let request = value.as_ref();
if !matches!(request.requestType, WebAuthnPluginRequestType::CTAP2_CBOR) {
return Err(WinWebAuthnError::new(
ErrorKind::Serialization,
"Unknown plugin operation request type",
));
}
let mut registration_request = MaybeUninit::uninit();
webauthn_decode_make_credential_request(
request.cbEncodedRequest,
request.pbEncodedRequest,
registration_request.as_mut_ptr(),
)?
.ok()
.map_err(|err| {
WinWebAuthnError::with_cause(
ErrorKind::WindowsInternal,
"Failed to decode get assertion request",
err,
)
})?;
let registration_request = registration_request.assume_init();
Ok(Self {
inner: registration_request as *const WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
window_handle: request.hWnd,
transaction_id: request.transactionId,
request_signature: Vec::from_raw_parts(
request.pbRequestSignature,
request.cbEncodedRequest as usize,
request.cbEncodedRequest as usize,
),
})
}
}
}
// Windows API function signatures for decoding make credential requests
webauthn_call!("WebAuthNDecodeMakeCredentialRequest" as fn webauthn_decode_make_credential_request(
cbEncoded: u32,
pbEncoded: *const u8,
ppMakeCredentialRequest: *mut *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
) -> HRESULT);
webauthn_call!("WebAuthNFreeDecodedMakeCredentialRequest" as fn webauthn_free_decoded_make_credential_request(
pMakeCredentialRequest: *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
) -> ());
// pub struct PluginMakeCredentialResponse {}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct WEBAUTHN_CREDENTIAL_ATTESTATION {
/// Version of this structure, to allow for modifications in the future.
dwVersion: u32,
/// Attestation format type
pwszFormatType: *const u16, // PCWSTR
/// Size of cbAuthenticatorData.
cbAuthenticatorData: u32,
/// Authenticator data that was created for this credential.
//_Field_size_bytes_(cbAuthenticatorData)
pbAuthenticatorData: *const u8,
/// Size of CBOR encoded attestation information
/// 0 => encoded as CBOR null value.
cbAttestation: u32,
///Encoded CBOR attestation information
// _Field_size_bytes_(cbAttestation)
pbAttestation: *const u8,
dwAttestationDecodeType: u32,
/// Following depends on the dwAttestationDecodeType
/// WEBAUTHN_ATTESTATION_DECODE_NONE
/// NULL - not able to decode the CBOR attestation information
/// WEBAUTHN_ATTESTATION_DECODE_COMMON
/// PWEBAUTHN_COMMON_ATTESTATION;
pvAttestationDecode: *const u8,
/// The CBOR encoded Attestation Object to be returned to the RP.
cbAttestationObject: u32,
// _Field_size_bytes_(cbAttestationObject)
pbAttestationObject: *const u8,
/// The CredentialId bytes extracted from the Authenticator Data.
/// Used by Edge to return to the RP.
cbCredentialId: u32,
// _Field_size_bytes_(cbCredentialId)
pbCredentialId: *const u8,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
//
/// Since VERSION 2
Extensions: WEBAUTHN_EXTENSIONS,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3
//
/// One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
/// the transport that was used.
dwUsedTransport: u32,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
//
bEpAtt: bool,
bLargeBlobSupported: bool,
bResidentKey: bool,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5
//
bPrfEnabled: bool,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
//
cbUnsignedExtensionOutputs: u32,
// _Field_size_bytes_(cbUnsignedExtensionOutputs)
pbUnsignedExtensionOutputs: *const u8,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7
//
pHmacSecret: *const WEBAUTHN_HMAC_SECRET_SALT,
// ThirdPartyPayment Credential or not.
bThirdPartyPayment: bool,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8
//
// Multiple WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
// the transports that are supported.
dwTransports: u32,
// UTF-8 encoded JSON serialization of the client data.
cbClientDataJSON: u32,
// _Field_size_bytes_(cbClientDataJSON)
pbClientDataJSON: *const u8,
// UTF-8 encoded JSON serialization of the RegistrationResponse.
cbRegistrationResponseJSON: u32,
// _Field_size_bytes_(cbRegistrationResponseJSON)
pbRegistrationResponseJSON: *const u8,
}
pub enum AttestationFormat {
Packed,
Tpm,
AndroidKey,
FidoU2f,
None,
Compound,
Apple,
}
impl Display for AttestationFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Packed => "packed",
Self::Tpm => "tpm",
Self::AndroidKey => "android-key",
Self::FidoU2f => "fido-u2f",
Self::None => "none",
Self::Compound => "compound",
Self::Apple => "apple",
})
}
}
pub enum AttestationDecodeType {
None,
Common(),
}
struct WEBAUTHN_HMAC_SECRET_SALT {
/// Size of pbFirst.
cbFirst: u32,
// _Field_size_bytes_(cbFirst)
/// Required
pbFirst: *mut u8,
/// Size of pbSecond.
cbSecond: u32,
// _Field_size_bytes_(cbSecond)
pbSecond: *mut u8,
}
pub struct HmacSecretSalt {
first: Vec<u8>,
second: Option<Vec<u8>>,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct WEBAUTHN_EXTENSION {
pwszExtensionIdentifier: *const u16,
cbExtension: u32,
pvExtension: *mut u8,
}
pub enum CredProtectOutput {
UserVerificationAny,
UserVerificationOptional,
UserVerificationOptionalWithCredentialIdList,
UserVerificationRequired,
}
pub enum WebAuthnExtensionMakeCredentialOutput {
HmacSecret(bool),
CredProtect(CredProtectOutput),
CredBlob(bool),
MinPinLength(u32),
// LargeBlob,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct WEBAUTHN_EXTENSIONS {
cExtensions: u32,
// _Field_size_(cExtensions)
pExtensions: *const WEBAUTHN_EXTENSION,
}
pub struct PluginMakeCredentialResponse {
/// Attestation format type
pub format_type: String, // PCWSTR
/// Authenticator data that was created for this credential.
pub authenticator_data: Vec<u8>,
///Encoded CBOR attestation information
pub attestation_statement: Option<Vec<u8>>,
// dwAttestationDecodeType: u32,
/// Following depends on the dwAttestationDecodeType
/// WEBAUTHN_ATTESTATION_DECODE_NONE
/// NULL - not able to decode the CBOR attestation information
/// WEBAUTHN_ATTESTATION_DECODE_COMMON
/// PWEBAUTHN_COMMON_ATTESTATION;
// pub pvAttestationDecode: *mut u8,
/// The CBOR-encoded Attestation Object to be returned to the RP.
pub attestation_object: Option<Vec<u8>>,
/// The CredentialId bytes extracted from the Authenticator Data.
/// Used by Edge to return to the RP.
pub credential_id: Option<Vec<u8>>,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
//
/// Since VERSION 2
pub extensions: Option<Vec<WebAuthnExtensionMakeCredentialOutput>>,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3
//
/// One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
/// the transport that was used.
pub used_transport: CtapTransport,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
//
pub ep_att: bool,
pub large_blob_supported: bool,
pub resident_key: bool,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5
//
pub prf_enabled: bool,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
//
pub unsigned_extension_outputs: Option<Vec<u8>>,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7
//
pub hmac_secret: Option<HmacSecretSalt>,
/// ThirdPartyPayment Credential or not.
pub third_party_payment: bool,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8
//
/// Multiple WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
/// the transports that are supported.
pub transports: Option<Vec<CtapTransport>>,
/// UTF-8 encoded JSON serialization of the client data.
pub client_data_json: Option<Vec<u8>>,
/// UTF-8 encoded JSON serialization of the RegistrationResponse.
pub registration_response_json: Option<Vec<u8>>,
}
impl PluginMakeCredentialResponse {
pub fn to_ctap_response(mut self) -> Result<Vec<u8>, WinWebAuthnError> {
let attestation = self.try_into()?;
let mut response_len = 0;
let mut response_ptr = std::ptr::null_mut();
let result = webauthn_encode_make_credential_response(
&attestation,
&mut response_len,
&mut response_ptr,
)?
.ok()
.map_err(|err| {
WinWebAuthnError::with_cause(
ErrorKind::WindowsInternal,
"WebAuthNEncodeMakeCredentialResponse() failed",
err,
)
})?;
if response_ptr.is_null() {
return Err(WinWebAuthnError::new(
ErrorKind::WindowsInternal,
"Received null pointer from WebAuthNEncodeMakeCredentialResponse",
));
}
let response = unsafe {
Vec::from_raw_parts(response_ptr, response_len as usize, response_len as usize)
};
Ok(response)
}
}
impl TryFrom<PluginMakeCredentialResponse> for WEBAUTHN_CREDENTIAL_ATTESTATION {
type Error = WinWebAuthnError;
fn try_from(value: PluginMakeCredentialResponse) -> Result<Self, Self::Error> {
// Convert format type to UTF-16
let format_type_utf16 = value.format_type.to_utf16();
let pwszFormatType = format_type_utf16.as_ptr();
std::mem::forget(format_type_utf16);
// Get authenticator data pointer and length
let pbAuthenticatorData = value.authenticator_data.as_ptr();
let cbAuthenticatorData = value.authenticator_data.len() as u32;
std::mem::forget(value.authenticator_data);
// Get optional attestation statement pointer and length
let (pbAttestation, cbAttestation) = match value.attestation_statement.as_ref() {
Some(data) => (data.as_ptr(), data.len() as u32),
None => (std::ptr::null(), 0),
};
std::mem::forget(value.attestation_statement);
// Get optional attestation object pointer and length
let (pbAttestationObject, cbAttestationObject) = match value.attestation_object.as_ref() {
Some(data) => (data.as_ptr(), data.len() as u32),
None => (std::ptr::null(), 0),
};
std::mem::forget(value.attestation_object);
// Get optional credential ID pointer and length
let (pbCredentialId, cbCredentialId) = match value.credential_id.as_ref() {
Some(data) => (data.as_ptr(), data.len() as u32),
None => (std::ptr::null(), 0),
};
std::mem::forget(value.credential_id);
// Convert extensions (TODO: implement proper extension conversion)
let extensions = WEBAUTHN_EXTENSIONS {
cExtensions: 0,
pExtensions: std::ptr::null(),
};
// Convert used transport enum to bitmask
let dwUsedTransport = value.used_transport as u32;
// Get optional unsigned extension outputs pointer and length
let (pbUnsignedExtensionOutputs, cbUnsignedExtensionOutputs) =
match value.unsigned_extension_outputs.as_ref() {
Some(data) => (data.as_ptr(), data.len() as u32),
None => (std::ptr::null(), 0),
};
std::mem::forget(value.unsigned_extension_outputs);
// Convert optional HMAC secret (TODO: implement proper conversion)
let pHmacSecret = std::ptr::null();
// Convert optional transports to bitmask
let dwTransports = value
.transports
.as_ref()
.map_or(0, |t| t.iter().map(|transport| *transport as u32).sum());
// Get optional client data JSON pointer and length
let (pbClientDataJSON, cbClientDataJSON) = match value.client_data_json.as_ref() {
Some(data) => (data.as_ptr(), data.len() as u32),
None => (std::ptr::null(), 0),
};
std::mem::forget(value.client_data_json);
// Get optional registration response JSON pointer and length
let (pbRegistrationResponseJSON, cbRegistrationResponseJSON) =
match value.registration_response_json.as_ref() {
Some(data) => (data.as_ptr(), data.len() as u32),
None => (std::ptr::null(), 0),
};
std::mem::forget(value.registration_response_json);
let attestation = WEBAUTHN_CREDENTIAL_ATTESTATION {
// Use version 8 to include all fields
dwVersion: 8,
pwszFormatType,
cbAuthenticatorData,
pbAuthenticatorData,
cbAttestation,
pbAttestation,
// TODO: Support decode type. Just using WEBAUTHN_ATTESTATION_DECODE_NONE (0) for now.
dwAttestationDecodeType: 0,
pvAttestationDecode: std::ptr::null(),
cbAttestationObject,
pbAttestationObject,
cbCredentialId,
pbCredentialId,
Extensions: extensions,
dwUsedTransport,
bEpAtt: value.ep_att,
bLargeBlobSupported: value.large_blob_supported,
bResidentKey: value.resident_key,
bPrfEnabled: value.prf_enabled,
cbUnsignedExtensionOutputs,
pbUnsignedExtensionOutputs,
pHmacSecret,
bThirdPartyPayment: value.third_party_payment,
dwTransports,
cbClientDataJSON,
pbClientDataJSON,
cbRegistrationResponseJSON,
pbRegistrationResponseJSON,
};
Ok(attestation)
}
}
webauthn_call!("WebAuthNEncodeMakeCredentialResponse" as fn webauthn_encode_make_credential_response(
cbEncoded: *const WEBAUTHN_CREDENTIAL_ATTESTATION,
pbEncoded: *mut u32,
response_bytes: *mut *mut u8
) -> HRESULT);
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
pub dwVersion: u32,
pub pwszRpId: *const u16, // PCWSTR
pub cbRpId: u32,
@@ -425,15 +1135,19 @@ impl PluginGetAssertionRequest {
}
}
pub fn credential_list(&self) -> CredentialList {
pub fn allow_credentials(&self) -> CredentialList {
self.as_ref().CredentialList
}
// TODO: Support extensions
// pub fn extensions(&self) -> Options<Extensions> {}
pub fn authenticator_options(&self) -> WebAuthnCtapCborAuthenticatorOptions {
unsafe { *self.as_ref().pAuthenticatorOptions }
pub fn authenticator_options(&self) -> Option<WebAuthnCtapCborAuthenticatorOptions> {
let ptr = self.as_ref().pAuthenticatorOptions;
if ptr.is_null() {
return None;
}
unsafe { Some(*ptr) }
}
}
@@ -749,26 +1463,29 @@ pub struct CredentialList {
type WEBAUTHN_CREDENTIAL_LIST = CredentialList;
pub struct CredentialListIterator<'a> {
pos: usize,
list: &'a [*const WEBAUTHN_CREDENTIAL_EX],
inner: ArrayPointerIterator<'a, *const WEBAUTHN_CREDENTIAL_EX>,
}
impl<'a> Iterator for CredentialListIterator<'a> {
type Item = &'a WEBAUTHN_CREDENTIAL_EX;
fn next(&mut self) -> Option<Self::Item> {
let current = self.list.get(self.pos);
self.pos += 1;
current.and_then(|c| unsafe { c.as_ref() })
let item = self.inner.next()?;
// SAFETY: This type can only be constructed from this library using
// responses from Windows APIs, and we trust that the pointer and length
// of each inner item of the array is valid.
unsafe { item.as_ref() }
}
}
impl CredentialList {
pub fn iter(&self) -> CredentialListIterator<'_> {
// SAFETY: This type can only be constructed from this library using
// responses from Windows APIs. The pointer is checked for null safety
// on construction.
unsafe {
CredentialListIterator {
pos: 0,
list: std::slice::from_raw_parts(self.ppCredentials, self.cCredentials as usize),
inner: ArrayPointerIterator::new(self.ppCredentials, self.cCredentials as usize),
}
}
}

View File

@@ -68,3 +68,34 @@ impl WindowsString for str {
(ptr as *mut u16, byte_count)
}
}
pub struct ArrayPointerIterator<'a, T> {
pos: usize,
list: Option<&'a [T]>,
}
impl<T> ArrayPointerIterator<'_, T> {
/// Safety constraints: The caller must ensure that the pointer and length is
/// valid. A null pointer returns an empty iterator.
pub unsafe fn new(data: *const T, len: usize) -> Self {
let slice = if !data.is_null() {
Some(std::slice::from_raw_parts(data, len))
} else {
None
};
Self {
pos: 0,
list: slice,
}
}
}
impl<'a, T> Iterator for ArrayPointerIterator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let current = self.list?.get(self.pos);
self.pos += 1;
current
}
}