Scaffold win_webauthn [PM-29785] (#20278)

This commit is contained in:
Isaiah Inuwa 2026-04-21 06:42:51 -05:00 committed by GitHub
parent dc888f9acc
commit 21a2be00a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1024 additions and 0 deletions

View File

@ -4273,6 +4273,14 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
[[package]]
name = "win_webauthn"
version = "0.0.0"
dependencies = [
"windows 0.62.2",
"windows-core 0.62.2",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

@ -10,6 +10,7 @@ members = [
"process_isolation",
"proxy",
"ssh_agent",
"win_webauthn",
"windows_plugin_authenticator",
]

View File

@ -0,0 +1,19 @@
[package]
name = "win_webauthn"
version.workspace = true
license.workspace = true
edition.workspace = true
publish.workspace = true
[target.'cfg(windows)'.dependencies]
windows = { workspace = true, features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Security_Cryptography",
"Win32_System_Com",
"Win32_System_LibraryLoader",
] }
windows-core = { workspace = true }
[lints]
workspace = true

View File

@ -0,0 +1,28 @@
Rust wrapper for the Windows WebAuthn Library.
Based on [microsoft/webauthn@c3ed95f][webauthn-ref], released in Windows 11
November 2025 update.
[webauthn-ref]: https://github.com/microsoft/webauthn/tree/c3ed95fd7603441a0253c55c14e79239cb556a9f
# Current Limitations
In this initial version, there are some limitations that need to be addressed:
- The `ErrorKind` enum is too broad. The use needs to be audited and more variants should be added for specific error cases.
- C structs for use in webauthn.dll functions are defined manually. As of
[microsoft/windows-rs@95dfa93](https://github.com/microsoft/windows-rs/tree/95dfa93ce7a004449d5309b36dda9f2300f57db6),
these are included in the `windows` crate, but there has not been a released
version of that crate since it was added. As soon as it is released, we can
update to it.
- We are not using the `dwVersion` fields of the structs and are instead
hard-coding availability for all the structs as of the Windows 11 passkey plugin
authenticator (November 2025). To make this more generally useful, we should
read these fields and optionally omit known fields that were introduced in a
higher version than `dwVersion` specifies (e.g., by returning an Option<T> for
those fields).
- End-to-end tests for the plugin authenticator handler code would be good to
include. We could consider using a Rust trait to wrap the FFI calls, or to
create a DLL from a Rust cdylib that mocks the required webauthn.dll
functionality and is called from the tests.

View File

@ -0,0 +1,2 @@
//! Safe wrappers around raw types and functions for webauthn.dll.
mod sys;

View File

@ -0,0 +1,222 @@
//! Raw types for the Windows webauthn.dll library.
//! The top-level crate includes types defined in webauthn.h.
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
pub(super) mod plugin;
mod util;
use std::{num::NonZeroU32, ptr::NonNull};
use windows::core::BOOL;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_COSE_CREDENTIAL_PARAMETER {
/// Version of this structure, to allow for modifications in the future.
pub(super) dwVersion: u32,
/// Well-known credential type specifying a credential to create.
pub(super) pwszCredentialType: NonNull<u16>,
/// Well-known COSE algorithm specifying the algorithm to use for the credential.
pub(super) lAlg: i32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
pub(super) cCredentialParameters: u32,
pub(super) pCredentialParameters: *const WEBAUTHN_COSE_CREDENTIAL_PARAMETER,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_CREDENTIAL_ATTESTATION {
/// Version of this structure, to allow for modifications in the future.
pub(super) dwVersion: u32,
/// Attestation format type
pub(super) pwszFormatType: *const u16, // PCWSTR
/// Size of cbAuthenticatorData.
pub(super) cbAuthenticatorData: u32,
/// Authenticator data that was created for this credential.
//_Field_size_bytes_(cbAuthenticatorData)
pub(super) pbAuthenticatorData: *const u8,
/// Size of CBOR encoded attestation information
/// 0 => encoded as CBOR null value.
pub(super) cbAttestation: u32,
///Encoded CBOR attestation information
// _Field_size_bytes_(cbAttestation)
pub(super) pbAttestation: *const u8,
pub(super) 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(super) pvAttestationDecode: *const u8,
/// The CBOR encoded Attestation Object to be returned to the RP.
pub(super) cbAttestationObject: u32,
// _Field_size_bytes_(cbAttestationObject)
pub(super) pbAttestationObject: *const u8,
/// The CredentialId bytes extracted from the Authenticator Data.
/// Used by Edge to return to the RP.
pub(super) cbCredentialId: u32,
// _Field_size_bytes_(cbCredentialId)
pub(super) pbCredentialId: *const u8,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
/// Since VERSION 2
pub(super) 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.
pub(super) dwUsedTransport: u32,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
pub(super) bEpAtt: BOOL,
pub(super) bLargeBlobSupported: BOOL,
pub(super) bResidentKey: BOOL,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5
pub(super) bPrfEnabled: BOOL,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
pub(super) cbUnsignedExtensionOutputs: u32,
// _Field_size_bytes_(cbUnsignedExtensionOutputs)
pub(super) pbUnsignedExtensionOutputs: *const u8,
//
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7
pub(super) pHmacSecret: *const WEBAUTHN_HMAC_SECRET_SALT,
/// ThirdPartyPayment Credential or not.
pub(super) 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.
pub(super) dwTransports: u32,
/// UTF-8 encoded JSON serialization of the client data.
pub(super) cbClientDataJSON: u32,
// _Field_size_bytes_(cbClientDataJSON)
pub(super) pbClientDataJSON: *const u8,
/// UTF-8 encoded JSON serialization of the RegistrationResponse.
pub(super) cbRegistrationResponseJSON: u32,
// _Field_size_bytes_(cbRegistrationResponseJSON)
pub(super) pbRegistrationResponseJSON: *const u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_CREDENTIAL_EX {
/// Version of this structure, to allow for modifications in the future.
pub(super) dwVersion: u32,
/// Size of pbID.
pub(super) cbId: u32,
/// Unique ID for this particular credential.
pub(super) pbId: *const u8,
/// Well-known credential type specifying what this particular credential is.
pub(super) pwszCredentialType: *const u16,
/// Transports. 0 implies no transport restrictions.
pub(super) dwTransports: u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_CREDENTIAL_LIST {
pub(super) cCredentials: u32,
pub(super) ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_EXTENSION {
pwszExtensionIdentifier: *const u16,
cbExtension: u32,
pvExtension: *mut u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_EXTENSIONS {
pub(super) cExtensions: u32,
// _Field_size_(cExtensions)
pub(super) pExtensions: *const WEBAUTHN_EXTENSION,
}
#[repr(C)]
pub(super) 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,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_RP_ENTITY_INFORMATION {
/// Version of this structure, to allow for modifications in the future.
/// This field is required and should be set to CURRENT_VERSION above.
pub(super) dwVersion: u32,
/// Identifier for the RP. This field is required.
pub(super) pwszId: NonNull<u16>, // PCWSTR
/// Contains the friendly name of the Relying Party, such as "Acme
/// Corporation", "Widgets Inc" or "Awesome Site".
///
/// This member is deprecated in WebAuthn Level 3 because many clients do not display it, but
/// it remains a required dictionary member for backwards compatibility. Relying
/// Parties MAY, as a safe default, set this equal to the RP ID.
pub(super) pwszName: *const u16, // PCWSTR
/// Optional URL pointing to RP's logo.
///
/// This field was removed in WebAuthn Level 2. Keeping this here for proper struct sizing.
#[deprecated]
_pwszIcon: *const u16, // PCWSTR
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_USER_ENTITY_INFORMATION {
/// Version of this structure, to allow for modifications in the future.
/// This field is required and should be set to CURRENT_VERSION above.
pub(super) dwVersion: u32,
/// Identifier for the User. This field is required.
pub(super) cbId: NonZeroU32, // DWORD
pub(super) pbId: NonNull<u8>, // PBYTE
/// Contains a detailed name for this account, such as "john.p.smith@example.com".
pub(super) pwszName: NonNull<u16>, // PCWSTR
/// Optional URL that can be used to retrieve an image containing the user's current avatar,
/// or a data URI that contains the image data.
#[deprecated]
pub(super) pwszIcon: Option<NonNull<u16>>, // PCWSTR
/// Contains the friendly name associated with the user account by the Relying Party, such as
/// "John P. Smith".
pub(super) pwszDisplayName: NonNull<u16>, // PCWSTR
}

View File

@ -0,0 +1,565 @@
//! Types from webauthn.dll as defined in webauthnplugin.h and pluginauthenticator.h.
use std::num::NonZeroU32;
use windows::{
core::{BOOL, GUID, HRESULT},
Win32::{Foundation::HWND, Security::Cryptography::BCRYPT_KEY_BLOB},
};
use super::{
util::webauthn_call, WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, WEBAUTHN_CREDENTIAL_ATTESTATION,
WEBAUTHN_CREDENTIAL_LIST, WEBAUTHN_RP_ENTITY_INFORMATION, WEBAUTHN_USER_ENTITY_INFORMATION,
};
/// Plugin lock status enum as defined in the IDL
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum PLUGIN_LOCK_STATUS {
PluginLocked = 0,
PluginUnlocked = 1,
}
/// Windows WebAuthn Authenticator Options structure
/// Header File Name: _WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS {
/// Version of this structure, to allow for modifications in the future.
pub(in crate::api) dwVersion: u32,
/// "up" option: +1=TRUE, 0=Not defined, -1=FALSE
pub(in crate::api) lUp: i32,
/// "uv" option: +1=TRUE, 0=Not defined, -1=FALSE
pub(in crate::api) lUv: i32,
/// "rk" option: +1=TRUE, 0=Not defined, -1=FALSE
pub(in crate::api) lRequireResidentKey: i32,
}
#[repr(C)]
pub(in crate::api) struct WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY {
/// Version of this structure, to allow for modifications in the future.
pub(in crate::api) _dwVersion: u32,
/// Key type
pub(in crate::api) _lKty: i32,
/// Hash Algorithm: ES256, ES384, ES512
pub(in crate::api) _lAlg: i32,
/// Curve
pub(in crate::api) _lCrv: i32,
/// Size of "x" (X Coordinate)
pub(in crate::api) _cbX: u32,
/// "x" (X Coordinate) data. Big Endian.
pub(in crate::api) _pbX: *const u8,
/// Size of "y" (Y Coordinate)
pub(in crate::api) _cbY: u32,
/// "y" (Y Coordinate) data. Big Endian.
pub(in crate::api) _pbY: *const u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(in crate::api) struct WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
/// Version of this structure, to allow for modifications in the future.
pub(in crate::api) dwVersion: u32,
/// RP ID (after UTF-8 to Unicode conversion)
pub(in crate::api) pwszRpId: *const u16,
/// Input RP ID size (raw UTF-8 bytes before conversion)
pub(in crate::api) cbRpId: u32,
/// Raw UTF-8 bytes before conversion to UTF-16 in pwszRpId. These are the
/// bytes to be hashed in the Authenticator Data.
pub(in crate::api) pbRpId: *const u8,
/// Client Data Hash size
pub(in crate::api) cbClientDataHash: u32,
/// Client Data Hash data
pub(in crate::api) pbClientDataHash: *const u8,
/// Credentials used for inclusion
pub(in crate::api) CredentialList: WEBAUTHN_CREDENTIAL_LIST,
/// CBOR extensions map size
pub(in crate::api) cbCborExtensionsMap: u32,
/// CBOR extensions map data
pub(in crate::api) pbCborExtensionsMap: *const u8,
/// Authenticator Options (Optional)
pub(in crate::api) pAuthenticatorOptions: *const WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS,
// Pin Auth (Optional)
/// Zero length PinAuth is included in the request
pub(in crate::api) fEmptyPinAuth: BOOL,
/// Pin Auth size
pub(in crate::api) cbPinAuth: u32,
/// Pin Auth data
pub(in crate::api) pbPinAuth: *const u8,
/// HMAC Salt Extension (Optional)
pub(in crate::api) pHmacSaltExtension: *const WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION,
/// PRF Extension / HMAC secret salt values size
pub(in crate::api) cbHmacSecretSaltValues: u32,
/// PRF Extension / HMAC secret salt values data
pub(in crate::api) pbHmacSecretSaltValues: *const u8,
/// Pin protocol
pub(in crate::api) dwPinProtocol: u32,
/// "credBlob": true extension
pub(in crate::api) lCredBlobExt: i32,
/// "largeBlobKey": true extension
pub(in crate::api) lLargeBlobKeyExt: i32,
/// "largeBlob" extension operation
pub(in crate::api) dwCredLargeBlobOperation: u32,
/// Large blob compressed size
pub(in crate::api) cbCredLargeBlobCompressed: u32,
/// Large blob compressed data
pub(in crate::api) pbCredLargeBlobCompressed: *const u8,
/// Large blob original size
pub(in crate::api) dwCredLargeBlobOriginalSize: u32,
/// "json" extension size. Nonzero if present
pub(in crate::api) cbJsonExt: u32,
/// "json" extension data
pub(in crate::api) pbJsonExt: *const u8,
}
#[repr(C)]
pub(in crate::api) struct WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION {
/// Version of this structure, to allow for modifications in the future.
pub(in crate::api) _dwVersion: u32,
/// Platform's key agreement public key
pub(in crate::api) _pKeyAgreement: *const WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY,
/// Encrypted salt size
pub(in crate::api) _cbEncryptedSalt: u32,
/// Encrypted salt data
pub(in crate::api) _pbEncryptedSalt: *const u8,
/// Salt authentication size
pub(in crate::api) _cbSaltAuth: u32,
/// Salt authentication data
pub(in crate::api) _pbSaltAuth: *const u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(in crate::api) struct WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST<'a> {
/// Version of this structure, to allow for modifications in the future.
pub(in crate::api) dwVersion: u32,
/// Input RP ID size (raw UTF-8 bytes before conversion)
pub(in crate::api) cbRpId: u32,
/// Input RP ID data (bytes hashed in Authenticator Data)
pub(in crate::api) pbRpId: *const u8,
/// Client Data Hash size
pub(in crate::api) cbClientDataHash: u32,
/// Client Data Hash data
pub(in crate::api) pbClientDataHash: *const u8,
/// RP Information
pub(in crate::api) pRpInformation: *const WEBAUTHN_RP_ENTITY_INFORMATION,
/// User Information
pub(in crate::api) pUserInformation: *const WEBAUTHN_USER_ENTITY_INFORMATION,
/// Crypto Parameters
pub(in crate::api) WebAuthNCredentialParameters: WEBAUTHN_COSE_CREDENTIAL_PARAMETERS,
/// Credentials used for exclusion
pub(in crate::api) CredentialList: WEBAUTHN_CREDENTIAL_LIST,
/// CBOR extensions map size
pub(in crate::api) cbCborExtensionsMap: u32,
/// CBOR extensions map data
pub(in crate::api) pbCborExtensionsMap: *const u8,
/// Authenticator Options (Optional)
pub(in crate::api) pAuthenticatorOptions: Option<&'a WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS>,
// Pin Auth (Optional)
/// Indicates zero length PinAuth is included in the request
pub(in crate::api) fEmptyPinAuth: BOOL,
/// Pin Auth size
pub(in crate::api) cbPinAuth: u32,
/// Pin Auth data
pub(in crate::api) pbPinAuth: *const u8,
/// "hmac-secret": true extension
pub(in crate::api) lHmacSecretExt: i32,
/// "hmac-secret-mc" extension
pub(in crate::api) pHmacSecretMcExtension: *const WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION,
/// "prf" extension
pub(in crate::api) lPrfExt: i32,
/// HMAC secret salt values size
pub(in crate::api) cbHmacSecretSaltValues: u32,
/// HMAC secret salt values data
pub(in crate::api) pbHmacSecretSaltValues: *const u8,
/// "credProtect" extension. Nonzero if present
pub(in crate::api) dwCredProtect: Option<NonZeroU32>,
/// Nonzero if present
pub(in crate::api) dwPinProtocol: Option<NonZeroU32>,
/// Nonzero if present
pub(in crate::api) dwEnterpriseAttestation: Option<NonZeroU32>,
/// "credBlob" extension. Nonzero if present
pub(in crate::api) cbCredBlobExt: Option<NonZeroU32>,
/// "credBlob" extension data
pub(in crate::api) pbCredBlobExt: *const u8,
/// "largeBlobKey": true extension
pub(in crate::api) lLargeBlobKeyExt: i32,
/// "largeBlob": extension
pub(in crate::api) dwLargeBlobSupport: u32,
/// "minPinLength": true extension
pub(in crate::api) lMinPinLengthExt: i32,
/// "json" extension. Nonzero if present
pub(in crate::api) cbJsonExt: u32,
/// "json" extension data
pub(in crate::api) pbJsonExt: *const u8,
}
/// Used when adding a Windows plugin authenticator (stable API).
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS
/// Header File Usage: WebAuthNPluginAddAuthenticator()
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(in crate::api) struct WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS {
/// Authenticator Name
pub(in crate::api) pwszAuthenticatorName: *const u16,
/// Plugin COM ClsId
pub(in crate::api) rclsid: *const GUID,
/// Plugin RPID
///
/// Required for a nested WebAuthN call originating from a plugin.
pub(in crate::api) pwszPluginRpId: *const u16,
/// Plugin Authenticator Logo for the Light themes. base64-encoded SVG 1.1
///
/// The data should be encoded as `UTF16(BASE64(UTF8(svg_text)))`.
pub(in crate::api) pwszLightThemeLogoSvg: *const u16,
/// Plugin Authenticator Logo for the Dark themes. base64-encoded SVG 1.1
///
/// The data should be encoded as `UTF16(BASE64(UTF8(svg_text)))`.
pub(in crate::api) pwszDarkThemeLogoSvg: *const u16,
/// CTAP CBOR-encoded authenticatorGetInfo response (size)
pub(in crate::api) cbAuthenticatorInfo: u32,
/// CTAP CBOR-encoded authenticatorGetInfo output
pub(in crate::api) pbAuthenticatorInfo: *const u8,
/// Count of supported RP IDs
pub(in crate::api) cSupportedRpIds: u32,
/// List of supported RP IDs (Relying Party IDs).
///
/// Should be null if all RPs are supported.
pub(in crate::api) pbSupportedRpIds: *const *const u16,
}
/// Used as a response type when adding a Windows plugin authenticator.
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
/// Header File Usage: WebAuthNPluginAddAuthenticator()
/// WebAuthNPluginFreeAddAuthenticatorResponse()
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(in crate::api) struct WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE {
/// Size in bytes of the public key pointed to by `pbOpSignPubKey`.
pub(in crate::api) cbOpSignPubKey: u32,
/// Pointer to a [BCRYPT_KEY_BLOB](windows::Win32::Security::Cryptography::BCRYPT_KEY_BLOB).
pub(in crate::api) pbOpSignPubKey: *mut u8,
}
#[repr(C)]
pub(in crate::api) struct WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST {
pub(in crate::api) transactionId: GUID,
pub(in crate::api) cbRequestSignature: u32,
pub(in crate::api) pbRequestSignature: *const u8,
}
/// Represents a credential.
/// Header File Name: _WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS
/// Header File Usage: WebAuthNPluginAuthenticatorAddCredentials, etc.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(in crate::api) struct WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS {
/// Credential Identifier bytes (size)
pub(in crate::api) credential_id_byte_count: u32,
/// Credential Identifier bytes (data, required)
pub(in crate::api) credential_id_pointer: *const u8,
/// Identifier for the RP (required)
pub(in crate::api) rpid: *const u16,
/// Friendly name of the Relying Party (required)
pub(in crate::api) rp_friendly_name: *const u16,
/// User Identifier bytes (size)
pub(in crate::api) user_id_byte_count: u32,
/// User Identifier bytes (data, required)
pub(in crate::api) user_id_pointer: *const u8,
/// Detailed account name (e.g., "john.p.smith@example.com")
pub(in crate::api) user_name: *const u16,
/// Friendly name for the user account (e.g., "John P. Smith")
pub(in crate::api) user_display_name: *const u16,
}
/// Used when creating and asserting credentials.
/// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_REQUEST
/// Header File Usage: MakeCredential()
/// GetAssertion()
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(in crate::api) struct WEBAUTHN_PLUGIN_OPERATION_REQUEST {
/// Window handle to client that requesting a WebAuthn credential.
pub(in crate::api) hWnd: HWND,
pub(in crate::api) transactionId: GUID,
pub(in crate::api) cbRequestSignature: u32,
/// Signature over request made with the signing key created during authenticator registration.
pub(in crate::api) pbRequestSignature: *mut u8,
pub(in crate::api) requestType: WEBAUTHN_PLUGIN_REQUEST_TYPE,
pub(in crate::api) cbEncodedRequest: u32,
pub(in crate::api) pbEncodedRequest: *const u8,
}
/// Plugin request type enum as defined in the IDL
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub(in crate::api) enum WEBAUTHN_PLUGIN_REQUEST_TYPE {
// This is being used to check the value that Windows gives us, but it isn't
// ever constructed by our library.
#[allow(unused)]
CTAP2_CBOR = 0x01,
}
/// Used as a response when creating and asserting credentials.
/// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_RESPONSE
/// Header File Usage: MakeCredential()
/// GetAssertion()
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(in crate::api) struct WEBAUTHN_PLUGIN_OPERATION_RESPONSE {
pub(in crate::api) cbEncodedResponse: u32,
pub(in crate::api) pbEncodedResponse: *mut u8,
}
#[repr(C)]
#[derive(Debug)]
pub(in crate::api) struct WEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST {
/// Windows handle of the top-level window displayed by the plugin and
/// currently is in foreground as part of the ongoing WebAuthn operation.
pub(in crate::api) hwnd: HWND,
/// The WebAuthn transaction id from the WEBAUTHN_PLUGIN_OPERATION_REQUEST
pub(in crate::api) rguidTransactionId: *const GUID,
/// The username attached to the credential that is in use for this WebAuthn
/// operation.
pub(in crate::api) pwszUsername: *const u16,
/// A text hint displayed on the Windows Hello prompt.
pub(in crate::api) pwszDisplayHint: *const u16,
}
webauthn_call!("WebAuthNDecodeGetAssertionRequest" as
/// Decodes a CTAP GetAssertion request.
///
/// On success, a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST] will be written to
/// `ppGetAssertionRequest`, which must be freed by a call to
/// [webauthn_free_decoded_get_assertion_request].
///
/// # Arguments
/// - `pbEncoded`: a COM-allocated buffer pointing to a CTAP CBOR get assertion request.
/// - `ppGetAssertionRequest`: An indirect pointer to a [WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST].
///
/// # Safety
/// - `pbEncoded` must have been allocated by Windows COM.
/// - `pbEncoded` must be non-null and have the length specified in cbEncoded.
fn webauthn_decode_get_assertion_request(
cbEncoded: u32,
pbEncoded: *const u8,
ppGetAssertionRequest: *mut *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST
) -> HRESULT);
webauthn_call!("WebAuthNDecodeMakeCredentialRequest" as
/// Decodes a CTAP CBOR `authenticatorMakeCredential` request.
///
/// On success, a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST] will be written to
/// `ppMakeCredentialRequest`, which must be freed by a call to
/// [webauthn_free_decoded_make_credential_request].
///
/// # Arguments
/// - `pbEncoded`: a COM-allocated buffer pointing to a CTAP CBOR make credential request.
/// - `ppMakeCredentialRequest`: An indirect pointer to a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST].
///
/// # Safety
/// - `pbEncoded` must have been allocated by Windows COM.
/// - `pbEncoded` must be non-null and have the length specified in cbEncoded.
fn webauthn_decode_make_credential_request(
cbEncoded: u32,
pbEncoded: *const u8,
ppMakeCredentialRequest: *mut *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
) -> HRESULT);
webauthn_call!("WebAuthNEncodeMakeCredentialResponse" as
/// Encode a credential attestation response to a COM-allocated byte buffer
/// containing a CTAP CBOR `authenticatorMakeCredential` response structure.
///
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
///
/// # Arguments
/// - `pCredentialAttestation`: A pointer to [WEBAUTHN_CREDENTIAL_ATTESTATION] to encode.
/// - `pcbResp`: A pointer to a u32, which will be filled with the length of the response buffer.
/// - `ppbResponse`: An indirect pointer to a byte buffer, which will be written to on succces.
fn webauthn_encode_make_credential_response(
pCredentialAttestation: *const WEBAUTHN_CREDENTIAL_ATTESTATION,
pcbResp: *mut u32,
ppbResponse: *mut *mut u8
) -> HRESULT);
webauthn_call!("WebAuthNFreeDecodedGetAssertionRequest" as
/// Frees a decoded get assertion request from [webauthn_free_decoded_get_assertion_request].
///
/// # Arguments
/// - `pGetAssertionRequest`: An pointer to a [WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST] to be freed.
fn webauthn_free_decoded_get_assertion_request(
pGetAssertionRequest: *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST
) -> ());
webauthn_call!("WebAuthNFreeDecodedMakeCredentialRequest" as
/// Frees a decoded make credential request from [webauthn_free_decoded_make_credential_request].
///
/// # Arguments
/// - `pMakeCredentialRequest`: An pointer to a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST] to be freed.
fn webauthn_free_decoded_make_credential_request(
pMakeCredentialRequest: *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
) -> ());
webauthn_call!("WebAuthNPluginAddAuthenticator" as
/// Register authenticator info for a plugin COM server.
///
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
///
/// # Arguments
/// - `pPluginAddAuthenticatorOptions`: Details about the authenticator to set.
/// - `ppPluginAddAuthenticatorResponse`:
/// An indirect pointer to a [WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE], which will be written to on success.
/// If the request succeeds, the data must be freed by a call to [webauthn_plugin_free_add_authenticator_response].
fn webauthn_plugin_add_authenticator(
pPluginAddAuthenticatorOptions: *const WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS,
ppPluginAddAuthenticatorResponse: *mut *mut WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
) -> HRESULT);
webauthn_call!("WebAuthNPluginAuthenticatorAddCredentials" as
/// Add metadata for a list of WebAuthn credentials to the autofill store for
/// this plugin authenticator.
///
/// This will make the credentials available for discovery in Windows Hello
/// WebAuthn autofill dialogs.
///
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
///
/// # Arguments
/// - `rclsid`: The CLSID corresponding to this plugin's COM server.
/// - `cCredentialDetails`: The number of credentials in the array pointed to by `pCredentialDetails`.
/// - `pCredentialDetails`: An array of credential metadata.
fn webauthn_plugin_authenticator_add_credentials(
rclsid: *const GUID,
cCredentialDetails: u32,
pCredentialDetails: *const WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS
) -> HRESULT);
webauthn_call!("WebAuthNPluginAuthenticatorRemoveAllCredentials" as
/// Removes metadata for all credentials currently stored in the autofill store
/// for this plugin authenticator.
///
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
///
/// # Arguments
/// - `rclsid`: The CLSID corresponding to this plugin's COM server.
fn webauthn_plugin_authenticator_remove_all_credentials(rclsid: *const GUID) -> HRESULT);
webauthn_call!("WebAuthNPluginFreeAddAuthenticatorResponse" as
/// Free memory from a [WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE].
///
/// # Arguments
/// - `pPluginAddAuthenticatorResponse`: An pointer to a [WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE] to be freed.
fn webauthn_plugin_free_add_authenticator_response(
pPluginAddAuthenticatorResponse: *mut WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
) -> ());
webauthn_call!("WebAuthNPluginFreeUserVerificationResponse" as
/// Free a user verification response received from a call to [webauthn_plugin_perform_user_verification].
fn webauthn_plugin_free_user_verification_response(
pbResponse: *mut u8
) -> ());
webauthn_call!("WebAuthNPluginPerformUserVerification" as
/// Request user verification for a WebAuthn operation.
///
/// The OS will prompt the user for verification, and if the user is
/// successfully verified, will write a signature to `ppbResponse`, which must
/// be freed by a call to [webauthn_plugin_free_user_verification_response].
///
/// The signature is over the SHA-256 hash of the original WebAuthn operation request buffer
/// corresponding to `pPluginUserVerification.rguidTransactionId`. It can be
/// verified using the user verification public key, which can be retrieved
/// using
/// [webauthn_plugin_get_user_verification_public_key][crate::plugin::crypto::webauthn_plugin_get_user_verification_public_key].
///
/// This request will block while the user interacts with the dialog.
///
/// # Arguments
/// - `pPluginUserVerification`: The user verification prompt and transaction context for the request.
/// - `pcbResponse`: Length in bytes of the signature.
/// - `ppbResponse`: The signature of the request.
fn webauthn_plugin_perform_user_verification(
pPluginUserVerification: *const WEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST,
pcbResponse: *mut u32,
ppbResponse: *mut *mut u8
) -> HRESULT);
webauthn_call!("WebAuthNPluginGetUserVerificationPublicKey" as
/// Retrieve the public key used to verify user verification responses from the OS.
///
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
///
/// # Arguments
/// - `rclsid`: The CLSID corresponding to this plugin's COM server.
/// - `pcbPublicKey`: A pointer to an unsigned integer, which will be filled in with the length of the buffer at `ppbPublicKey`.
/// - `ppbPublicKey`: A pointer to a [BCRYPT_PUBLIC_KEY_BLOB], which will be written to on success.
/// On success, this must be freed by a call to [webauthn_plugin_free_public_key_response].
fn webauthn_plugin_get_user_verification_public_key(
rclsid: *const GUID,
pcbPublicKey: *mut u32,
ppbPublicKey: *mut *mut BCRYPT_KEY_BLOB,
) -> HRESULT); // Free using WebAuthNPluginFreePublicKeyResponse
webauthn_call!("WebAuthNPluginGetOperationSigningPublicKey" as
/// Retrieve the public key used to verify plugin operation requests from the OS.
///
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
///
/// # Arguments
/// - `rclsid`: The CLSID corresponding to this plugin's COM server.
/// - `pcbOpSignPubKey`: A pointer to an unsigned integer, which will be filled in with the length of the buffer at `ppbOpSignPubKey`.
/// - `ppbOpSignPubKey`: An indirect pointer to a [BCRYPT_PUBLIC_KEY_BLOB], which will be written to on success.
/// On success, this must be freed by a call to [webauthn_plugin_free_public_key_response].
fn webauthn_plugin_get_operation_signing_public_key(
rclsid: *const GUID,
pcbOpSignPubKey: *mut u32,
ppbOpSignPubKey: *mut *mut BCRYPT_KEY_BLOB
) -> HRESULT); // Free using WebAuthNPluginFreePublicKeyResponse
webauthn_call!("WebAuthNPluginFreePublicKeyResponse" as
/// Free public key memory retrieved from the OS.
///
/// # Arguments
/// - `pbOpSignPubKey`: A pointer to a [BCRYPT_KEY_BLOB] retrieved from a method in this library.
fn webauthn_plugin_free_public_key_response(
pbOpSignPubKey: *mut BCRYPT_KEY_BLOB
) -> ());

View File

@ -0,0 +1,94 @@
use std::sync::OnceLock;
use windows::{
core::s,
Win32::{
Foundation::{FreeLibrary, HMODULE},
System::LibraryLoader::{
GetModuleHandleExA, LoadLibraryExA, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
GET_MODULE_HANDLE_EX_FLAG_PIN, LOAD_LIBRARY_SEARCH_SYSTEM32,
},
},
};
use windows_core::{PCSTR, PCWSTR};
use crate::{ErrorKind, WinWebAuthnError};
struct SafeModule(HMODULE);
impl SafeModule {
unsafe fn new(mut module: HMODULE) -> windows::core::Result<Self> {
unsafe {
// Pin the module so that it cannot be unloaded.
GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
PCSTR::from_raw(module.0.cast()),
&mut module,
)?;
}
Ok(Self(module))
}
}
static WEBAUTHN_LIB: OnceLock<windows::core::Result<SafeModule>> = OnceLock::new();
unsafe impl Send for SafeModule {}
unsafe impl Sync for SafeModule {}
/// Defines a Rust function to call a webauthn.dll function over FFI based on
/// the name of the function. Documentation comments will be captured, and the
/// return type will be wrapped in a WinWebAuthnError that will be returned if
/// the function cannot be loaded from webauthn.dll.
///
/// # Examples
///
/// ```ignore
/// use crate::api::sys::util::webauthn_call;
///
/// webauthn_call!("WebAuthNFreeDecodedMakeCredentialRequest" as
/// /// Frees a decoded make credential request from [webauthn_free_decoded_make_credential_request].
/// ///
/// /// # Arguments
/// /// - `pMakeCredentialRequest`: An pointer to a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST] to be freed.
/// fn webauthn_free_decoded_make_credential_request(
/// pMakeCredentialRequest: *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
/// ) -> ());
/// ```
macro_rules! webauthn_call {
($symbol:literal as $(#[$attr:meta])* fn $fn_name:ident($($arg:ident: $arg_type:ty),+ $(,)?) -> $result_type:ty) => (
$(#[$attr])*
pub(in crate::api) unsafe fn $fn_name($($arg: $arg_type),*) -> Result<$result_type, crate::WinWebAuthnError> {
let library = crate::api::sys::util::load_webauthn_lib()?;
let response = unsafe {
let address = windows::Win32::System::LibraryLoader::GetProcAddress(*library, windows::core::s!($symbol)).ok_or(
crate::WinWebAuthnError::new(
crate::ErrorKind::DllLoad,
&format!(
"Failed to load function {}",
$symbol
),
),
)?;
let function: unsafe extern "C" fn(
$($arg: $arg_type),*
) -> $result_type = std::mem::transmute_copy(&address);
function($($arg),*)
};
Ok(response)
}
)
}
pub(super) use webauthn_call;
pub(super) fn load_webauthn_lib() -> Result<&'static HMODULE, WinWebAuthnError> {
WEBAUTHN_LIB
.get_or_init(|| unsafe {
LoadLibraryExA(s!("webauthn.dll"), None, LOAD_LIBRARY_SEARCH_SYSTEM32)
.and_then(|library| SafeModule::new(library))
})
.as_ref()
.map(|module| &module.0)
.map_err(|err| {
WinWebAuthnError::with_cause(ErrorKind::DllLoad, "Failed to load webauthn.dll", err)
})
}

View File

@ -0,0 +1,85 @@
//! A Rust wrapper around the webauthn.dll API.
//!
//! The root module contains common types for WebAuthn clients and plugins.
//!
//! The [plugin] module has types useful for implementing a Windows passkey
//! plugin authenticator.
#![cfg(target_os = "windows")]
// TODO: Temporarily allow unused code while scaffolding. Remove once PR set is finished.
#![expect(unused)]
#[allow(unsafe_code)]
pub(crate) mod api;
use std::{error::Error, fmt::Display};
/// Errors that may be returned when interacting with this library.
#[derive(Debug)]
pub struct WinWebAuthnError {
kind: ErrorKind,
description: Option<String>,
cause: Option<Box<dyn std::error::Error>>,
}
impl WinWebAuthnError {
pub(crate) fn new(kind: ErrorKind, description: &str) -> Self {
Self {
kind,
description: Some(description.to_string()),
cause: None,
}
}
pub(crate) fn with_cause<E: std::error::Error + 'static>(
kind: ErrorKind,
description: &str,
cause: E,
) -> Self {
let cause: Box<dyn std::error::Error> = Box::new(cause);
Self {
kind,
description: Some(description.to_string()),
cause: Some(cause),
}
}
}
#[derive(Debug)]
enum ErrorKind {
/// There was an error loading the webauthn.dll library.
DllLoad,
/// There was an error parsing or serializing data.
Serialization,
/// An invalid argument was passed.
InvalidArguments,
/// An unknown error occurred.
Other,
/// An internal library error occurred.
WindowsInternal,
}
impl Display for WinWebAuthnError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self.kind {
ErrorKind::Serialization => "Failed to serialize data",
ErrorKind::DllLoad => "Failed to load function from DLL",
ErrorKind::InvalidArguments => "Invalid arguments passed to function",
ErrorKind::Other => "An error occurred",
ErrorKind::WindowsInternal => "A Windows error occurred",
};
f.write_str(msg)?;
if let Some(d) = &self.description {
write!(f, ": {d}")?;
}
if let Some(e) = &self.cause {
write!(f, ". Caused by: {e}")?;
}
Ok(())
}
}
impl Error for WinWebAuthnError {}