From afdd38e838af397cd5edf9ebe921be8ebe28517b Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Thu, 18 Dec 2025 12:54:56 -0600 Subject: [PATCH] Add win_webauthn lib --- apps/desktop/desktop_native/Cargo.lock | 66 +- apps/desktop/desktop_native/Cargo.toml | 2 + .../desktop_native/win_webauthn/Cargo.toml | 25 + .../include/pluginauthenticator.h | 263 ++++ .../include/pluginauthenticator.idl | 83 + .../win_webauthn/include/webauthn.h | 1381 +++++++++++++++++ .../win_webauthn/include/webauthnplugin.h | 588 +++++++ .../desktop_native/win_webauthn/src/lib.rs | 72 + .../win_webauthn/src/plugin/com.rs | 480 ++++++ .../win_webauthn/src/plugin/crypto.rs | 249 +++ .../win_webauthn/src/plugin/mod.rs | 376 +++++ .../win_webauthn/src/plugin/types.rs | 1050 +++++++++++++ .../win_webauthn/src/types/mod.rs | 793 ++++++++++ .../desktop_native/win_webauthn/src/util.rs | 99 ++ 14 files changed, 5522 insertions(+), 5 deletions(-) create mode 100644 apps/desktop/desktop_native/win_webauthn/Cargo.toml create mode 100644 apps/desktop/desktop_native/win_webauthn/include/pluginauthenticator.h create mode 100644 apps/desktop/desktop_native/win_webauthn/include/pluginauthenticator.idl create mode 100644 apps/desktop/desktop_native/win_webauthn/include/webauthn.h create mode 100644 apps/desktop/desktop_native/win_webauthn/include/webauthnplugin.h create mode 100644 apps/desktop/desktop_native/win_webauthn/src/lib.rs create mode 100644 apps/desktop/desktop_native/win_webauthn/src/plugin/com.rs create mode 100644 apps/desktop/desktop_native/win_webauthn/src/plugin/crypto.rs create mode 100644 apps/desktop/desktop_native/win_webauthn/src/plugin/mod.rs create mode 100644 apps/desktop/desktop_native/win_webauthn/src/plugin/types.rs create mode 100644 apps/desktop/desktop_native/win_webauthn/src/types/mod.rs create mode 100644 apps/desktop/desktop_native/win_webauthn/src/util.rs diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index f5e5cf7ee18..4e983427409 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -603,6 +603,33 @@ dependencies = [ "windows", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -724,6 +751,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -1410,6 +1443,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.3" @@ -1671,7 +1715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.3", ] [[package]] @@ -3840,6 +3884,18 @@ 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 = [ + "base64", + "ciborium", + "hex", + "tracing", + "windows", + "windows-core", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4466,18 +4522,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 86eb507a6c1..64d58c9c5c4 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -9,6 +9,7 @@ members = [ "napi", "process_isolation", "proxy", + "win_webauthn", "windows_plugin_authenticator" ] @@ -30,6 +31,7 @@ byteorder = "=1.5.0" bytes = "=1.11.0" cbc = "=0.1.2" chacha20poly1305 = "=0.10.1" +ciborium = "=0.2.2" core-foundation = "=0.10.1" ctor = "=0.5.0" dirs = "=6.0.0" diff --git a/apps/desktop/desktop_native/win_webauthn/Cargo.toml b/apps/desktop/desktop_native/win_webauthn/Cargo.toml new file mode 100644 index 00000000000..81361617827 --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "win_webauthn" +version.workspace = true +license.workspace = true +edition.workspace = true +publish.workspace = true + +[dev-dependencies] +hex = { workspace = true } + +[lints] +workspace = true + +[target.'cfg(windows)'.dependencies] +base64 = { workspace = true } +ciborium = { workspace = true } +tracing = { workspace = true } +windows = { workspace = true, features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_Security_Cryptography", + "Win32_System_Com", + "Win32_System_LibraryLoader", +] } +windows-core = { workspace = true } diff --git a/apps/desktop/desktop_native/win_webauthn/include/pluginauthenticator.h b/apps/desktop/desktop_native/win_webauthn/include/pluginauthenticator.h new file mode 100644 index 00000000000..edb3ffd4f00 --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/include/pluginauthenticator.h @@ -0,0 +1,263 @@ + + +/* this ALWAYS GENERATED file contains the definitions for the interfaces */ + + + /* File created by MIDL compiler version 8.01.0628 */ +/* @@MIDL_FILE_HEADING( ) */ + + + +/* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCNDR_H_VERSION__ +#define __REQUIRED_RPCNDR_H_VERSION__ 501 +#endif + +/* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCSAL_H_VERSION__ +#define __REQUIRED_RPCSAL_H_VERSION__ 100 +#endif + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __RPCNDR_H_VERSION__ +#error this stub requires an updated version of +#endif /* __RPCNDR_H_VERSION__ */ + +#ifndef COM_NO_WINDOWS_H +#include "windows.h" +#include "ole2.h" +#endif /*COM_NO_WINDOWS_H*/ + +#ifndef __pluginauthenticator_h__ +#define __pluginauthenticator_h__ + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +#ifndef DECLSPEC_XFGVIRT +#if defined(_CONTROL_FLOW_GUARD_XFG) +#define DECLSPEC_XFGVIRT(base, func) __declspec(xfg_virtual(base, func)) +#else +#define DECLSPEC_XFGVIRT(base, func) +#endif +#endif + +/* Forward Declarations */ + +#ifndef __IPluginAuthenticator_FWD_DEFINED__ +#define __IPluginAuthenticator_FWD_DEFINED__ +typedef interface IPluginAuthenticator IPluginAuthenticator; + +#endif /* __IPluginAuthenticator_FWD_DEFINED__ */ + + +/* header files for imported files */ +#include "oaidl.h" + +#ifdef __cplusplus +extern "C"{ +#endif + + +/* interface __MIDL_itf_pluginauthenticator_0000_0000 */ +/* [local] */ + +typedef +enum _WEBAUTHN_PLUGIN_REQUEST_TYPE + { + WEBAUTHN_PLUGIN_REQUEST_TYPE_CTAP2_CBOR = 0x1 + } WEBAUTHN_PLUGIN_REQUEST_TYPE; + +typedef struct _WEBAUTHN_PLUGIN_OPERATION_REQUEST + { + HWND hWnd; + GUID transactionId; + DWORD cbRequestSignature; + /* [size_is] */ byte *pbRequestSignature; + WEBAUTHN_PLUGIN_REQUEST_TYPE requestType; + DWORD cbEncodedRequest; + /* [size_is] */ byte *pbEncodedRequest; + } WEBAUTHN_PLUGIN_OPERATION_REQUEST; + +typedef struct _WEBAUTHN_PLUGIN_OPERATION_REQUEST *PWEBAUTHN_PLUGIN_OPERATION_REQUEST; + +typedef const WEBAUTHN_PLUGIN_OPERATION_REQUEST *PCWEBAUTHN_PLUGIN_OPERATION_REQUEST; + +typedef struct _WEBAUTHN_PLUGIN_OPERATION_RESPONSE + { + DWORD cbEncodedResponse; + /* [size_is] */ byte *pbEncodedResponse; + } WEBAUTHN_PLUGIN_OPERATION_RESPONSE; + +typedef struct _WEBAUTHN_PLUGIN_OPERATION_RESPONSE *PWEBAUTHN_PLUGIN_OPERATION_RESPONSE; + +typedef const WEBAUTHN_PLUGIN_OPERATION_RESPONSE *PCWEBAUTHN_PLUGIN_OPERATION_RESPONSE; + +typedef struct _WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST + { + GUID transactionId; + DWORD cbRequestSignature; + /* [size_is] */ byte *pbRequestSignature; + } WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; + +typedef struct _WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST *PWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; + +typedef const WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST *PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; + +typedef +enum _PLUGIN_LOCK_STATUS + { + PluginLocked = 0, + PluginUnlocked = ( PluginLocked + 1 ) + } PLUGIN_LOCK_STATUS; + + + +extern RPC_IF_HANDLE __MIDL_itf_pluginauthenticator_0000_0000_v0_0_c_ifspec; +extern RPC_IF_HANDLE __MIDL_itf_pluginauthenticator_0000_0000_v0_0_s_ifspec; + +#ifndef __IPluginAuthenticator_INTERFACE_DEFINED__ +#define __IPluginAuthenticator_INTERFACE_DEFINED__ + +/* interface IPluginAuthenticator */ +/* [ref][version][uuid][object] */ + + +EXTERN_C const IID IID_IPluginAuthenticator; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("d26bcf6f-b54c-43ff-9f06-d5bf148625f7") + IPluginAuthenticator : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE MakeCredential( + /* [in] */ __RPC__in PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + /* [retval][out] */ __RPC__out PWEBAUTHN_PLUGIN_OPERATION_RESPONSE response) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetAssertion( + /* [in] */ __RPC__in PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + /* [retval][out] */ __RPC__out PWEBAUTHN_PLUGIN_OPERATION_RESPONSE response) = 0; + + virtual HRESULT STDMETHODCALLTYPE CancelOperation( + /* [in] */ __RPC__in PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST request) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetLockStatus( + /* [retval][out] */ __RPC__out PLUGIN_LOCK_STATUS *lockStatus) = 0; + + }; + + +#else /* C style interface */ + + typedef struct IPluginAuthenticatorVtbl + { + BEGIN_INTERFACE + + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + __RPC__in IPluginAuthenticator * This, + /* [in] */ __RPC__in REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + DECLSPEC_XFGVIRT(IUnknown, AddRef) + ULONG ( STDMETHODCALLTYPE *AddRef )( + __RPC__in IPluginAuthenticator * This); + + DECLSPEC_XFGVIRT(IUnknown, Release) + ULONG ( STDMETHODCALLTYPE *Release )( + __RPC__in IPluginAuthenticator * This); + + DECLSPEC_XFGVIRT(IPluginAuthenticator, MakeCredential) + HRESULT ( STDMETHODCALLTYPE *MakeCredential )( + __RPC__in IPluginAuthenticator * This, + /* [in] */ __RPC__in PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + /* [retval][out] */ __RPC__out PWEBAUTHN_PLUGIN_OPERATION_RESPONSE response); + + DECLSPEC_XFGVIRT(IPluginAuthenticator, GetAssertion) + HRESULT ( STDMETHODCALLTYPE *GetAssertion )( + __RPC__in IPluginAuthenticator * This, + /* [in] */ __RPC__in PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + /* [retval][out] */ __RPC__out PWEBAUTHN_PLUGIN_OPERATION_RESPONSE response); + + DECLSPEC_XFGVIRT(IPluginAuthenticator, CancelOperation) + HRESULT ( STDMETHODCALLTYPE *CancelOperation )( + __RPC__in IPluginAuthenticator * This, + /* [in] */ __RPC__in PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST request); + + DECLSPEC_XFGVIRT(IPluginAuthenticator, GetLockStatus) + HRESULT ( STDMETHODCALLTYPE *GetLockStatus )( + __RPC__in IPluginAuthenticator * This, + /* [retval][out] */ __RPC__out PLUGIN_LOCK_STATUS *lockStatus); + + END_INTERFACE + } IPluginAuthenticatorVtbl; + + interface IPluginAuthenticator + { + CONST_VTBL struct IPluginAuthenticatorVtbl *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define IPluginAuthenticator_QueryInterface(This,riid,ppvObject) \ + ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) + +#define IPluginAuthenticator_AddRef(This) \ + ( (This)->lpVtbl -> AddRef(This) ) + +#define IPluginAuthenticator_Release(This) \ + ( (This)->lpVtbl -> Release(This) ) + + +#define IPluginAuthenticator_MakeCredential(This,request,response) \ + ( (This)->lpVtbl -> MakeCredential(This,request,response) ) + +#define IPluginAuthenticator_GetAssertion(This,request,response) \ + ( (This)->lpVtbl -> GetAssertion(This,request,response) ) + +#define IPluginAuthenticator_CancelOperation(This,request) \ + ( (This)->lpVtbl -> CancelOperation(This,request) ) + +#define IPluginAuthenticator_GetLockStatus(This,lockStatus) \ + ( (This)->lpVtbl -> GetLockStatus(This,lockStatus) ) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + + + +#endif /* __IPluginAuthenticator_INTERFACE_DEFINED__ */ + + +/* Additional Prototypes for ALL interfaces */ + +unsigned long __RPC_USER HWND_UserSize( __RPC__in unsigned long *, unsigned long , __RPC__in HWND * ); +unsigned char * __RPC_USER HWND_UserMarshal( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in HWND * ); +unsigned char * __RPC_USER HWND_UserUnmarshal(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out HWND * ); +void __RPC_USER HWND_UserFree( __RPC__in unsigned long *, __RPC__in HWND * ); + +unsigned long __RPC_USER HWND_UserSize64( __RPC__in unsigned long *, unsigned long , __RPC__in HWND * ); +unsigned char * __RPC_USER HWND_UserMarshal64( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in HWND * ); +unsigned char * __RPC_USER HWND_UserUnmarshal64(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out HWND * ); +void __RPC_USER HWND_UserFree64( __RPC__in unsigned long *, __RPC__in HWND * ); + +/* end of Additional Prototypes */ + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/apps/desktop/desktop_native/win_webauthn/include/pluginauthenticator.idl b/apps/desktop/desktop_native/win_webauthn/include/pluginauthenticator.idl new file mode 100644 index 00000000000..b33ca0615a2 --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/include/pluginauthenticator.idl @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import "oaidl.idl"; +import "objidl.idl"; +import "wtypes.idl"; + +typedef enum _WEBAUTHN_PLUGIN_REQUEST_TYPE { + WEBAUTHN_PLUGIN_REQUEST_TYPE_CTAP2_CBOR = 0x01 // CBOR encoded CTAP2 message. Refer to the FIDO Specifications: Client to Authenticator Protocol (CTAP) +} WEBAUTHN_PLUGIN_REQUEST_TYPE; + +typedef struct _WEBAUTHN_PLUGIN_OPERATION_REQUEST { + // Handle of the top level Window of the caller + HWND hWnd; + + // Transaction ID + GUID transactionId; + + // Request Hash Signature Bytes Buffer Size + DWORD cbRequestSignature; + + // Request Hash Signature Bytes Buffer - Signature verified using the "pbOpSignPubKey" in PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE + [size_is(cbRequestSignature)] byte* pbRequestSignature; + + // Request Type - Determines the encoding of the request and response buffers + WEBAUTHN_PLUGIN_REQUEST_TYPE requestType; + + // Encoded Request Buffer Size + DWORD cbEncodedRequest; + + // Encoded Request Buffer - Encoding type is determined by the requestType + [size_is(cbEncodedRequest)] byte* pbEncodedRequest; +} WEBAUTHN_PLUGIN_OPERATION_REQUEST, *PWEBAUTHN_PLUGIN_OPERATION_REQUEST; +typedef const WEBAUTHN_PLUGIN_OPERATION_REQUEST *PCWEBAUTHN_PLUGIN_OPERATION_REQUEST; + +typedef struct _WEBAUTHN_PLUGIN_OPERATION_RESPONSE { + // Encoded Response Buffer Size + DWORD cbEncodedResponse; + + // Encoded Response Buffer - Encoding type must match the request + [size_is(cbEncodedResponse)] byte* pbEncodedResponse; +} WEBAUTHN_PLUGIN_OPERATION_RESPONSE, *PWEBAUTHN_PLUGIN_OPERATION_RESPONSE; +typedef const WEBAUTHN_PLUGIN_OPERATION_RESPONSE *PCWEBAUTHN_PLUGIN_OPERATION_RESPONSE; + +typedef struct _WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST { + // Transaction ID + GUID transactionId; + + // Request Hash Signature Bytes Buffer Size + DWORD cbRequestSignature; + + // Request Hash Signature Bytes Buffer - Signature verified using the "pbOpSignPubKey" in PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE + [size_is(cbRequestSignature)] byte* pbRequestSignature; +} WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST, *PWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; +typedef const WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST *PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; + +typedef enum _PLUGIN_LOCK_STATUS { + PluginLocked = 0, + PluginUnlocked +} PLUGIN_LOCK_STATUS; + +[ + object, + uuid(d26bcf6f-b54c-43ff-9f06-d5bf148625f7), + version(1.0), + pointer_default(ref) +] +interface IPluginAuthenticator : IUnknown +{ + HRESULT MakeCredential( + [in] PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + [out, retval] PWEBAUTHN_PLUGIN_OPERATION_RESPONSE response); + + HRESULT GetAssertion( + [in] PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, + [out, retval] PWEBAUTHN_PLUGIN_OPERATION_RESPONSE response); + + HRESULT CancelOperation( + [in] PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST request); + + HRESULT GetLockStatus( + [out, retval] PLUGIN_LOCK_STATUS* lockStatus); +} \ No newline at end of file diff --git a/apps/desktop/desktop_native/win_webauthn/include/webauthn.h b/apps/desktop/desktop_native/win_webauthn/include/webauthn.h new file mode 100644 index 00000000000..b5eaca30f1c --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/include/webauthn.h @@ -0,0 +1,1381 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef __WEBAUTHN_H_ +#define __WEBAUTHN_H_ + +#pragma once + +#include + +#pragma region Desktop Family or OneCore Family +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WINAPI +#define WINAPI __stdcall +#endif + +#ifndef INITGUID +#define INITGUID +#include +#undef INITGUID +#else +#include +#endif + +//+------------------------------------------------------------------------------------------ +// API Version Information. +// Caller should check for WebAuthNGetApiVersionNumber to check the presence of relevant APIs +// and features for their usage. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_API_VERSION_1 1 +// WEBAUTHN_API_VERSION_1 : Baseline Version +// Data Structures and their sub versions: +// - WEBAUTHN_RP_ENTITY_INFORMATION : 1 +// - WEBAUTHN_USER_ENTITY_INFORMATION : 1 +// - WEBAUTHN_CLIENT_DATA : 1 +// - WEBAUTHN_COSE_CREDENTIAL_PARAMETER : 1 +// - WEBAUTHN_COSE_CREDENTIAL_PARAMETERS : Not Applicable +// - WEBAUTHN_CREDENTIAL : 1 +// - WEBAUTHN_CREDENTIALS : Not Applicable +// - WEBAUTHN_CREDENTIAL_EX : 1 +// - WEBAUTHN_CREDENTIAL_LIST : Not Applicable +// - WEBAUTHN_EXTENSION : Not Applicable +// - WEBAUTHN_EXTENSIONS : Not Applicable +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 3 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 4 +// - WEBAUTHN_COMMON_ATTESTATION : 1 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 3 +// - WEBAUTHN_ASSERTION : 1 +// Extensions: +// - WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET +// APIs: +// - WebAuthNGetApiVersionNumber +// - WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable +// - WebAuthNAuthenticatorMakeCredential +// - WebAuthNAuthenticatorGetAssertion +// - WebAuthNFreeCredentialAttestation +// - WebAuthNFreeAssertion +// - WebAuthNGetCancellationId +// - WebAuthNCancelCurrentOperation +// - WebAuthNGetErrorName +// - WebAuthNGetW3CExceptionDOMError +// Transports: +// - WEBAUTHN_CTAP_TRANSPORT_USB +// - WEBAUTHN_CTAP_TRANSPORT_NFC +// - WEBAUTHN_CTAP_TRANSPORT_BLE +// - WEBAUTHN_CTAP_TRANSPORT_INTERNAL + +#define WEBAUTHN_API_VERSION_2 2 +// WEBAUTHN_API_VERSION_2 : Delta From WEBAUTHN_API_VERSION_1 +// Added Extensions: +// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT +// + +#define WEBAUTHN_API_VERSION_3 3 +// WEBAUTHN_API_VERSION_3 : Delta From WEBAUTHN_API_VERSION_2 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 4 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 5 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 4 +// - WEBAUTHN_ASSERTION : 2 +// Added Extensions: +// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB +// - WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH +// + +#define WEBAUTHN_API_VERSION_4 4 +// WEBAUTHN_API_VERSION_4 : Delta From WEBAUTHN_API_VERSION_3 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 5 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 6 +// - WEBAUTHN_ASSERTION : 3 +// - WEBAUTHN_GET_CREDENTIALS_OPTIONS : 1 +// - WEBAUTHN_CREDENTIAL_DETAILS : 1 +// APIs: +// - WebAuthNGetPlatformCredentialList +// - WebAuthNFreePlatformCredentialList +// - WebAuthNDeletePlatformCredential +// + +#define WEBAUTHN_API_VERSION_5 5 +// WEBAUTHN_API_VERSION_5 : Delta From WEBAUTHN_API_VERSION_4 +// Data Structures and their sub versions: +// - WEBAUTHN_CREDENTIAL_DETAILS : 2 +// Extension Changes: +// - Enabled LARGE_BLOB Support +// + +#define WEBAUTHN_API_VERSION_6 6 +// WEBAUTHN_API_VERSION_6 : Delta From WEBAUTHN_API_VERSION_5 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 6 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 5 +// - WEBAUTHN_ASSERTION : 4 +// Transports: +// - WEBAUTHN_CTAP_TRANSPORT_HYBRID + +#define WEBAUTHN_API_VERSION_7 7 +// WEBAUTHN_API_VERSION_7 : Delta From WEBAUTHN_API_VERSION_6 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 7 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 7 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 6 +// - WEBAUTHN_ASSERTION : 5 + +#define WEBAUTHN_API_VERSION_8 8 +// WEBAUTHN_API_VERSION_8 : Delta From WEBAUTHN_API_VERSION_7 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 8 +// - WEBAUTHN_CREDENTIAL_DETAILS : 3 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 7 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 8 +// + +#define WEBAUTHN_API_VERSION_9 9 +// WEBAUTHN_API_VERSION_9 : Delta From WEBAUTHN_API_VERSION_8 +// Data Structures and their sub versions: +// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 9 +// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 9 +// - WEBAUTHN_ASSERTION : 6 +// - WEBAUTHN_CREDENTIAL_DETAILS : 4 +// - WEBAUTHN_CREDENTIAL_ATTESTATION : 8 +// - WEBAUTHN_AUTHENTICATOR_DETAILS : 1 +// - WEBAUTHN_AUTHENTICATOR_DETAILS_LIST : Not Applicable +// APIs: +// - WebAuthNGetAuthenticatorList +// - WebAuthNFreeAuthenticatorList + +#define WEBAUTHN_API_CURRENT_VERSION WEBAUTHN_API_VERSION_9 + +//+------------------------------------------------------------------------------------------ +// Information about an RP Entity +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION 1 + +typedef 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. + DWORD dwVersion; + + // Identifier for the RP. This field is required. + PCWSTR pwszId; + + // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site". + // This field is required. + PCWSTR pwszName; + + // Optional URL pointing to RP's logo. + PCWSTR pwszIcon; +} WEBAUTHN_RP_ENTITY_INFORMATION, *PWEBAUTHN_RP_ENTITY_INFORMATION; +typedef const WEBAUTHN_RP_ENTITY_INFORMATION *PCWEBAUTHN_RP_ENTITY_INFORMATION; + +//+------------------------------------------------------------------------------------------ +// Information about an User Entity +//------------------------------------------------------------------------------------------- +#define WEBAUTHN_MAX_USER_ID_LENGTH 64 + +#define WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION 1 + +typedef 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. + DWORD dwVersion; + + // Identifier for the User. This field is required. + DWORD cbId; + _Field_size_bytes_(cbId) + PBYTE pbId; + + // Contains a detailed name for this account, such as "john.p.smith@example.com". + PCWSTR pwszName; + + // 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. + PCWSTR pwszIcon; + + // For User: Contains the friendly name associated with the user account by the Relying Party, such as "John P. Smith". + PCWSTR pwszDisplayName; +} WEBAUTHN_USER_ENTITY_INFORMATION, *PWEBAUTHN_USER_ENTITY_INFORMATION; +typedef const WEBAUTHN_USER_ENTITY_INFORMATION *PCWEBAUTHN_USER_ENTITY_INFORMATION; + +//+------------------------------------------------------------------------------------------ +// Information about client data. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_HASH_ALGORITHM_SHA_256 L"SHA-256" +#define WEBAUTHN_HASH_ALGORITHM_SHA_384 L"SHA-384" +#define WEBAUTHN_HASH_ALGORITHM_SHA_512 L"SHA-512" + +#define WEBAUTHN_CLIENT_DATA_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_CLIENT_DATA { + // Version of this structure, to allow for modifications in the future. + // This field is required and should be set to CURRENT_VERSION above. + DWORD dwVersion; + + // Size of the pbClientDataJSON field. + DWORD cbClientDataJSON; + // UTF-8 encoded JSON serialization of the client data. + _Field_size_bytes_(cbClientDataJSON) + PBYTE pbClientDataJSON; + + // Hash algorithm ID used to hash the pbClientDataJSON field. + LPCWSTR pwszHashAlgId; +} WEBAUTHN_CLIENT_DATA, *PWEBAUTHN_CLIENT_DATA; +typedef const WEBAUTHN_CLIENT_DATA *PCWEBAUTHN_CLIENT_DATA; + +//+------------------------------------------------------------------------------------------ +// Information about credential parameters. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY L"public-key" + +#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256 -7 +#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384 -35 +#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P521_WITH_SHA512 -36 + +#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256 -257 +#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA384 -258 +#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA512 -259 + +#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA256 -37 +#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA384 -38 +#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA512 -39 + +#define WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETER { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Well-known credential type specifying a credential to create. + LPCWSTR pwszCredentialType; + + // Well-known COSE algorithm specifying the algorithm to use for the credential. + LONG lAlg; +} WEBAUTHN_COSE_CREDENTIAL_PARAMETER, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETER; +typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETER *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETER; + +typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { + DWORD cCredentialParameters; + _Field_size_(cCredentialParameters) + PWEBAUTHN_COSE_CREDENTIAL_PARAMETER pCredentialParameters; +} WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETERS; +typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS; + +//+------------------------------------------------------------------------------------------ +// Information about credential. +//------------------------------------------------------------------------------------------- +#define WEBAUTHN_CREDENTIAL_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_CREDENTIAL { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Size of pbID. + DWORD cbId; + // Unique ID for this particular credential. + _Field_size_bytes_(cbId) + PBYTE pbId; + + // Well-known credential type specifying what this particular credential is. + LPCWSTR pwszCredentialType; +} WEBAUTHN_CREDENTIAL, *PWEBAUTHN_CREDENTIAL; +typedef const WEBAUTHN_CREDENTIAL *PCWEBAUTHN_CREDENTIAL; + +typedef struct _WEBAUTHN_CREDENTIALS { + DWORD cCredentials; + _Field_size_(cCredentials) + PWEBAUTHN_CREDENTIAL pCredentials; +} WEBAUTHN_CREDENTIALS, *PWEBAUTHN_CREDENTIALS; +typedef const WEBAUTHN_CREDENTIALS *PCWEBAUTHN_CREDENTIALS; + +//+------------------------------------------------------------------------------------------ +// Information about credential with extra information, such as, dwTransports +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CTAP_TRANSPORT_USB 0x00000001 +#define WEBAUTHN_CTAP_TRANSPORT_NFC 0x00000002 +#define WEBAUTHN_CTAP_TRANSPORT_BLE 0x00000004 +#define WEBAUTHN_CTAP_TRANSPORT_TEST 0x00000008 +#define WEBAUTHN_CTAP_TRANSPORT_INTERNAL 0x00000010 +#define WEBAUTHN_CTAP_TRANSPORT_HYBRID 0x00000020 +#define WEBAUTHN_CTAP_TRANSPORT_SMART_CARD 0x00000040 +#define WEBAUTHN_CTAP_TRANSPORT_FLAGS_MASK 0x0000007F + +#define WEBAUTHN_CTAP_TRANSPORT_USB_STRING "usb" +#define WEBAUTHN_CTAP_TRANSPORT_NFC_STRING "nfc" +#define WEBAUTHN_CTAP_TRANSPORT_BLE_STRING "ble" +#define WEBAUTHN_CTAP_TRANSPORT_SMART_CARD_STRING "smart-card" +#define WEBAUTHN_CTAP_TRANSPORT_HYBRID_STRING "hybrid" +#define WEBAUTHN_CTAP_TRANSPORT_INTERNAL_STRING "internal" + +#define WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_CREDENTIAL_EX { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Size of pbID. + DWORD cbId; + // Unique ID for this particular credential. + _Field_size_bytes_(cbId) + PBYTE pbId; + + // Well-known credential type specifying what this particular credential is. + LPCWSTR pwszCredentialType; + + // Transports. 0 implies no transport restrictions. + DWORD dwTransports; +} WEBAUTHN_CREDENTIAL_EX, *PWEBAUTHN_CREDENTIAL_EX; +typedef const WEBAUTHN_CREDENTIAL_EX *PCWEBAUTHN_CREDENTIAL_EX; + +//+------------------------------------------------------------------------------------------ +// Information about credential list with extra information +//------------------------------------------------------------------------------------------- + +typedef struct _WEBAUTHN_CREDENTIAL_LIST { + DWORD cCredentials; + _Field_size_(cCredentials) + PWEBAUTHN_CREDENTIAL_EX *ppCredentials; +} WEBAUTHN_CREDENTIAL_LIST, *PWEBAUTHN_CREDENTIAL_LIST; +typedef const WEBAUTHN_CREDENTIAL_LIST *PCWEBAUTHN_CREDENTIAL_LIST; + +//+------------------------------------------------------------------------------------------ +// Information about linked devices +//------------------------------------------------------------------------------------------- + +#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1 1 +#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_CURRENT_VERSION CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1 + +// Deprecated +typedef struct _CTAPCBOR_HYBRID_STORAGE_LINKED_DATA +{ + // Version + DWORD dwVersion; + + // Contact Id + DWORD cbContactId; + _Field_size_bytes_(cbContactId) + PBYTE pbContactId; + + // Link Id + DWORD cbLinkId; + _Field_size_bytes_(cbLinkId) + PBYTE pbLinkId; + + // Link secret + DWORD cbLinkSecret; + _Field_size_bytes_(cbLinkSecret) + PBYTE pbLinkSecret; + + // Authenticator Public Key + DWORD cbPublicKey; + _Field_size_bytes_(cbPublicKey) + PBYTE pbPublicKey; + + // Authenticator Name + PCWSTR pwszAuthenticatorName; + + // Tunnel server domain + WORD wEncodedTunnelServerDomain; +} CTAPCBOR_HYBRID_STORAGE_LINKED_DATA, *PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA; +typedef const CTAPCBOR_HYBRID_STORAGE_LINKED_DATA *PCCTAPCBOR_HYBRID_STORAGE_LINKED_DATA; + +//+------------------------------------------------------------------------------------------ +// Authenticator Information for WebAuthNGetAuthenticatorList API +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS_VERSION_1 1 +#define WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS_VERSION_1 + +typedef struct _WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + +} WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS; +typedef const WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS; + +#define WEBAUTHN_AUTHENTICATOR_DETAILS_VERSION_1 1 +#define WEBAUTHN_AUTHENTICATOR_DETAILS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_DETAILS_VERSION_1 + +typedef struct _WEBAUTHN_AUTHENTICATOR_DETAILS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Authenticator ID + DWORD cbAuthenticatorId; + _Field_size_bytes_(cbAuthenticatorId) + PBYTE pbAuthenticatorId; + + // Authenticator Name + PCWSTR pwszAuthenticatorName; + + // Authenticator logo (expected to be in SVG format) + DWORD cbAuthenticatorLogo; + _Field_size_bytes_(cbAuthenticatorLogo) + PBYTE pbAuthenticatorLogo; + + // Is the authenticator currently locked? When locked, this authenticator's credentials + // might not be present or updated in WebAuthNGetPlatformCredentialList. + BOOL bLocked; + +} WEBAUTHN_AUTHENTICATOR_DETAILS, *PWEBAUTHN_AUTHENTICATOR_DETAILS; +typedef const WEBAUTHN_AUTHENTICATOR_DETAILS *PCWEBAUTHN_AUTHENTICATOR_DETAILS; + +typedef struct _WEBAUTHN_AUTHENTICATOR_DETAILS_LIST { + // Authenticator Details + DWORD cAuthenticatorDetails; + _Field_size_(cAuthenticatorDetails) + PWEBAUTHN_AUTHENTICATOR_DETAILS *ppAuthenticatorDetails; + +} WEBAUTHN_AUTHENTICATOR_DETAILS_LIST, *PWEBAUTHN_AUTHENTICATOR_DETAILS_LIST; +typedef const WEBAUTHN_AUTHENTICATOR_DETAILS_LIST *PCWEBAUTHN_AUTHENTICATOR_DETAILS_LIST; + +//+------------------------------------------------------------------------------------------ +// Credential Information for WebAuthNGetPlatformCredentialList API +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_1 1 +#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2 2 +#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_3 3 +#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_4 4 +#define WEBAUTHN_CREDENTIAL_DETAILS_CURRENT_VERSION WEBAUTHN_CREDENTIAL_DETAILS_VERSION_4 + +typedef struct _WEBAUTHN_CREDENTIAL_DETAILS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Size of pbCredentialID. + DWORD cbCredentialID; + _Field_size_bytes_(cbCredentialID) + PBYTE pbCredentialID; + + // RP Info + PWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation; + + // User Info + PWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation; + + // Removable or not. + BOOL bRemovable; + + // + // The following fields have been added in WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2 + // + + // Backed Up or not. + BOOL bBackedUp; + + // + // The following fields have been added in WEBAUTHN_CREDENTIAL_DETAILS_VERSION_3 + // + PCWSTR pwszAuthenticatorName; + + // The logo is expected to be in the svg format + DWORD cbAuthenticatorLogo; + _Field_size_bytes_(cbAuthenticatorLogo) + PBYTE pbAuthenticatorLogo; + + // ThirdPartyPayment Credential or not. + BOOL bThirdPartyPayment; + + // + // The following fields have been added in WEBAUTHN_CREDENTIAL_DETAILS_VERSION_4 + // + + // Applicable Transports + DWORD dwTransports; + +} WEBAUTHN_CREDENTIAL_DETAILS, *PWEBAUTHN_CREDENTIAL_DETAILS; +typedef const WEBAUTHN_CREDENTIAL_DETAILS *PCWEBAUTHN_CREDENTIAL_DETAILS; + +typedef struct _WEBAUTHN_CREDENTIAL_DETAILS_LIST { + DWORD cCredentialDetails; + _Field_size_(cCredentialDetails) + PWEBAUTHN_CREDENTIAL_DETAILS *ppCredentialDetails; +} WEBAUTHN_CREDENTIAL_DETAILS_LIST, *PWEBAUTHN_CREDENTIAL_DETAILS_LIST; +typedef const WEBAUTHN_CREDENTIAL_DETAILS_LIST *PCWEBAUTHN_CREDENTIAL_DETAILS_LIST; + +#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1 1 +#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_CURRENT_VERSION WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1 + +typedef struct _WEBAUTHN_GET_CREDENTIALS_OPTIONS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Optional. + LPCWSTR pwszRpId; + + // Optional. BrowserInPrivate Mode. Defaulting to FALSE. + BOOL bBrowserInPrivateMode; +} WEBAUTHN_GET_CREDENTIALS_OPTIONS, *PWEBAUTHN_GET_CREDENTIALS_OPTIONS; +typedef const WEBAUTHN_GET_CREDENTIALS_OPTIONS *PCWEBAUTHN_GET_CREDENTIALS_OPTIONS; + +//+------------------------------------------------------------------------------------------ +// PRF values. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH 32 + +// SALT values below by default are converted into RAW Hmac-Secret values as per PRF extension. +// - SHA-256(UTF8Encode("WebAuthn PRF") || 0x00 || Value) +// +// Set WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG in dwFlags in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, +// if caller wants to provide RAW Hmac-Secret SALT values directly. In that case, +// values if provided MUST be of WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH size. + +typedef struct _WEBAUTHN_HMAC_SECRET_SALT { + // Size of pbFirst. + DWORD cbFirst; + _Field_size_bytes_(cbFirst) + PBYTE pbFirst; // Required + + // Size of pbSecond. + DWORD cbSecond; + _Field_size_bytes_(cbSecond) + PBYTE pbSecond; +} WEBAUTHN_HMAC_SECRET_SALT, *PWEBAUTHN_HMAC_SECRET_SALT; +typedef const WEBAUTHN_HMAC_SECRET_SALT *PCWEBAUTHN_HMAC_SECRET_SALT; + +typedef struct _WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT { + // Size of pbCredID. + DWORD cbCredID; + _Field_size_bytes_(cbCredID) + PBYTE pbCredID; // Required + + // PRF Values for above credential + PWEBAUTHN_HMAC_SECRET_SALT pHmacSecretSalt; // Required +} WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT, *PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT; +typedef const WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT *PCWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT; + +typedef struct _WEBAUTHN_HMAC_SECRET_SALT_VALUES { + PWEBAUTHN_HMAC_SECRET_SALT pGlobalHmacSalt; + + DWORD cCredWithHmacSecretSaltList; + _Field_size_(cCredWithHmacSecretSaltList) + PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT pCredWithHmacSecretSaltList; +} WEBAUTHN_HMAC_SECRET_SALT_VALUES, *PWEBAUTHN_HMAC_SECRET_SALT_VALUES; +typedef const WEBAUTHN_HMAC_SECRET_SALT_VALUES *PCWEBAUTHN_HMAC_SECRET_SALT_VALUES; + +//+------------------------------------------------------------------------------------------ +// Hmac-Secret extension +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET L"hmac-secret" +// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET +// MakeCredential Input Type: BOOL. +// - pvExtension must point to a BOOL with the value TRUE. +// - cbExtension must contain the sizeof(BOOL). +// MakeCredential Output Type: BOOL. +// - pvExtension will point to a BOOL with the value TRUE if credential +// was successfully created with HMAC_SECRET. +// - cbExtension will contain the sizeof(BOOL). +// GetAssertion Input Type: Not Supported +// GetAssertion Output Type: Not Supported + +//+------------------------------------------------------------------------------------------ +// credProtect extension +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_USER_VERIFICATION_ANY 0 +#define WEBAUTHN_USER_VERIFICATION_OPTIONAL 1 +#define WEBAUTHN_USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST 2 +#define WEBAUTHN_USER_VERIFICATION_REQUIRED 3 + +typedef struct _WEBAUTHN_CRED_PROTECT_EXTENSION_IN { + // One of the above WEBAUTHN_USER_VERIFICATION_* values + DWORD dwCredProtect; + // Set the following to TRUE to require authenticator support for the credProtect extension + BOOL bRequireCredProtect; +} WEBAUTHN_CRED_PROTECT_EXTENSION_IN, *PWEBAUTHN_CRED_PROTECT_EXTENSION_IN; +typedef const WEBAUTHN_CRED_PROTECT_EXTENSION_IN *PCWEBAUTHN_CRED_PROTECT_EXTENSION_IN; + + +#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT L"credProtect" +// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT +// MakeCredential Input Type: WEBAUTHN_CRED_PROTECT_EXTENSION_IN. +// - pvExtension must point to a WEBAUTHN_CRED_PROTECT_EXTENSION_IN struct +// - cbExtension will contain the sizeof(WEBAUTHN_CRED_PROTECT_EXTENSION_IN). +// MakeCredential Output Type: DWORD. +// - pvExtension will point to a DWORD with one of the above WEBAUTHN_USER_VERIFICATION_* values +// if credential was successfully created with CRED_PROTECT. +// - cbExtension will contain the sizeof(DWORD). +// GetAssertion Input Type: Not Supported +// GetAssertion Output Type: Not Supported + +//+------------------------------------------------------------------------------------------ +// credBlob extension +//------------------------------------------------------------------------------------------- + +typedef struct _WEBAUTHN_CRED_BLOB_EXTENSION { + // Size of pbCredBlob. + DWORD cbCredBlob; + _Field_size_bytes_(cbCredBlob) + PBYTE pbCredBlob; +} WEBAUTHN_CRED_BLOB_EXTENSION, *PWEBAUTHN_CRED_BLOB_EXTENSION; +typedef const WEBAUTHN_CRED_BLOB_EXTENSION *PCWEBAUTHN_CRED_BLOB_EXTENSION; + + +#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB L"credBlob" +// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB +// MakeCredential Input Type: WEBAUTHN_CRED_BLOB_EXTENSION. +// - pvExtension must point to a WEBAUTHN_CRED_BLOB_EXTENSION struct +// - cbExtension must contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION). +// MakeCredential Output Type: BOOL. +// - pvExtension will point to a BOOL with the value TRUE if credBlob was successfully created +// - cbExtension will contain the sizeof(BOOL). +// GetAssertion Input Type: BOOL. +// - pvExtension must point to a BOOL with the value TRUE to request the credBlob. +// - cbExtension must contain the sizeof(BOOL). +// GetAssertion Output Type: WEBAUTHN_CRED_BLOB_EXTENSION. +// - pvExtension will point to a WEBAUTHN_CRED_BLOB_EXTENSION struct if the authenticator +// returns the credBlob in the signed extensions +// - cbExtension will contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION). + +//+------------------------------------------------------------------------------------------ +// minPinLength extension +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH L"minPinLength" +// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH +// MakeCredential Input Type: BOOL. +// - pvExtension must point to a BOOL with the value TRUE to request the minPinLength. +// - cbExtension must contain the sizeof(BOOL). +// MakeCredential Output Type: DWORD. +// - pvExtension will point to a DWORD with the minimum pin length if returned by the authenticator +// - cbExtension will contain the sizeof(DWORD). +// GetAssertion Input Type: Not Supported +// GetAssertion Output Type: Not Supported + +//+------------------------------------------------------------------------------------------ +// Information about Extensions. +//------------------------------------------------------------------------------------------- +typedef struct _WEBAUTHN_EXTENSION { + LPCWSTR pwszExtensionIdentifier; + DWORD cbExtension; + PVOID pvExtension; +} WEBAUTHN_EXTENSION, *PWEBAUTHN_EXTENSION; +typedef const WEBAUTHN_EXTENSION *PCWEBAUTHN_EXTENSION; + +typedef struct _WEBAUTHN_EXTENSIONS { + DWORD cExtensions; + _Field_size_(cExtensions) + PWEBAUTHN_EXTENSION pExtensions; +} WEBAUTHN_EXTENSIONS, *PWEBAUTHN_EXTENSIONS; +typedef const WEBAUTHN_EXTENSIONS *PCWEBAUTHN_EXTENSIONS; + +//+------------------------------------------------------------------------------------------ +// Options. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY 0 +#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM 1 +#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM 2 +#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2 3 + +#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY 0 +#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED 1 +#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED 2 +#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED 3 + +#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY 0 +#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE 1 +#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT 2 +#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT 3 + +#define WEBAUTHN_ENTERPRISE_ATTESTATION_NONE 0 +#define WEBAUTHN_ENTERPRISE_ATTESTATION_VENDOR_FACILITATED 1 +#define WEBAUTHN_ENTERPRISE_ATTESTATION_PLATFORM_MANAGED 2 + +#define WEBAUTHN_LARGE_BLOB_SUPPORT_NONE 0 +#define WEBAUTHN_LARGE_BLOB_SUPPORT_REQUIRED 1 +#define WEBAUTHN_LARGE_BLOB_SUPPORT_PREFERRED 2 + +#define WEBAUTHN_CREDENTIAL_HINT_SECURITY_KEY L"security-key" +#define WEBAUTHN_CREDENTIAL_HINT_CLIENT_DEVICE L"client-device" +#define WEBAUTHN_CREDENTIAL_HINT_HYBRID L"hybrid" + +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1 1 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 2 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 3 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 4 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 5 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6 6 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7 7 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_8 8 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_9 9 +#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_9 + +typedef struct _WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Time that the operation is expected to complete within. + // This is used as guidance, and can be overridden by the platform. + DWORD dwTimeoutMilliseconds; + + // Credentials used for exclusion. + WEBAUTHN_CREDENTIALS CredentialList; + + // Optional extensions to parse when performing the operation. + WEBAUTHN_EXTENSIONS Extensions; + + // Optional. Platform vs Cross-Platform Authenticators. + DWORD dwAuthenticatorAttachment; + + // Optional. Require key to be resident or not. Defaulting to FALSE. + BOOL bRequireResidentKey; + + // User Verification Requirement. + DWORD dwUserVerificationRequirement; + + // Attestation Conveyance Preference. + DWORD dwAttestationConveyancePreference; + + // Reserved for future Use + DWORD dwFlags; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 + // + + // Cancellation Id - Optional - See WebAuthNGetCancellationId + GUID *pCancellationId; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 + // + + // Exclude Credential List. If present, "CredentialList" will be ignored. + PWEBAUTHN_CREDENTIAL_LIST pExcludeCredentialList; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 + // + + // Enterprise Attestation + DWORD dwEnterpriseAttestation; + + // Large Blob Support: none, required or preferred + // + // NTE_INVALID_PARAMETER when large blob required or preferred and + // bRequireResidentKey isn't set to TRUE + DWORD dwLargeBlobSupport; + + // Optional. Prefer key to be resident. Defaulting to FALSE. When TRUE, + // overrides the above bRequireResidentKey. + BOOL bPreferResidentKey; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 + // + + // Optional. BrowserInPrivate Mode. Defaulting to FALSE. + BOOL bBrowserInPrivateMode; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6 + // + + // Enable PRF + BOOL bEnablePrf; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7 + // + + // Deprecated + // Optional. Linked Device Connection Info. + PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice; + + // Size of pbJsonExt + DWORD cbJsonExt; + _Field_size_bytes_(cbJsonExt) + PBYTE pbJsonExt; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_8 + // + + // PRF extension "eval" values which will be converted into HMAC-SECRET values according to WebAuthn Spec. + // Set WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG in dwFlags above, if caller wants to provide RAW Hmac-Secret SALT values directly. + // In that case, values provided MUST be of WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH size. + PWEBAUTHN_HMAC_SECRET_SALT pPRFGlobalEval; + + // PublicKeyCredentialHints (https://w3c.github.io/webauthn/#enum-hints) + DWORD cCredentialHints; + _Field_size_(cCredentialHints) + LPCWSTR *ppwszCredentialHints; + + // Enable ThirdPartyPayment + BOOL bThirdPartyPayment; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_9 + // + + // Web Origin. For Remote Web App scenario. + PCWSTR pwszRemoteWebOrigin; + + // UTF-8 encoded JSON serialization of the PublicKeyCredentialCreationOptions. + DWORD cbPublicKeyCredentialCreationOptionsJSON; + _Field_size_bytes_(cbPublicKeyCredentialCreationOptionsJSON) + PBYTE pbPublicKeyCredentialCreationOptionsJSON; + + // Authenticator ID got from WebAuthNGetAuthenticatorList API. + DWORD cbAuthenticatorId; + _Field_size_bytes_(cbAuthenticatorId) + PBYTE pbAuthenticatorId; + +} WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS; +typedef const WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS; + +#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE 0 +#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET 1 +#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET 2 +#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_DELETE 3 + +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1 1 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 2 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 3 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 4 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 5 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 6 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7 7 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_8 8 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_9 9 +#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_9 + +/* + Information about flags. +*/ + +#define WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG 0x00100000 + +typedef struct _WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Time that the operation is expected to complete within. + // This is used as guidance, and can be overridden by the platform. + DWORD dwTimeoutMilliseconds; + + // Allowed Credentials List. + WEBAUTHN_CREDENTIALS CredentialList; + + // Optional extensions to parse when performing the operation. + WEBAUTHN_EXTENSIONS Extensions; + + // Optional. Platform vs Cross-Platform Authenticators. + DWORD dwAuthenticatorAttachment; + + // User Verification Requirement. + DWORD dwUserVerificationRequirement; + + // Flags + DWORD dwFlags; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 + // + + // Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower cased. + PCWSTR pwszU2fAppId; + + // If the following is non-NULL, then, set to TRUE if the above pwszU2fAppid was used instead of + // PCWSTR pwszRpId; + BOOL *pbU2fAppId; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 + // + + // Cancellation Id - Optional - See WebAuthNGetCancellationId + GUID *pCancellationId; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 + // + + // Allow Credential List. If present, "CredentialList" will be ignored. + PWEBAUTHN_CREDENTIAL_LIST pAllowCredentialList; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 + // + + DWORD dwCredLargeBlobOperation; + + // Size of pbCredLargeBlob + DWORD cbCredLargeBlob; + _Field_size_bytes_(cbCredLargeBlob) + PBYTE pbCredLargeBlob; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 + // + + // PRF values which will be converted into HMAC-SECRET values according to WebAuthn Spec. + PWEBAUTHN_HMAC_SECRET_SALT_VALUES pHmacSecretSaltValues; + + // Optional. BrowserInPrivate Mode. Defaulting to FALSE. + BOOL bBrowserInPrivateMode; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7 + // + + // Deprecated + // Optional. Linked Device Connection Info. + PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice; + + // Optional. Allowlist MUST contain 1 credential applicable for Hybrid transport. + BOOL bAutoFill; + + // Size of pbJsonExt + DWORD cbJsonExt; + _Field_size_bytes_(cbJsonExt) + PBYTE pbJsonExt; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_8 + // + + // PublicKeyCredentialHints (https://w3c.github.io/webauthn/#enum-hints) + DWORD cCredentialHints; + _Field_size_(cCredentialHints) + LPCWSTR *ppwszCredentialHints; + + // + // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_9 + // + + // Web Origin. For Remote Web App scenario. + PCWSTR pwszRemoteWebOrigin; + + // UTF-8 encoded JSON serialization of the PublicKeyCredentialRequestOptions. + DWORD cbPublicKeyCredentialRequestOptionsJSON; + _Field_size_bytes_(cbPublicKeyCredentialRequestOptionsJSON) + PBYTE pbPublicKeyCredentialRequestOptionsJSON; + + // Authenticator ID got from WebAuthNGetAuthenticatorList API. + DWORD cbAuthenticatorId; + _Field_size_bytes_(cbAuthenticatorId) + PBYTE pbAuthenticatorId; + +} WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS; +typedef const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS; + + +//+------------------------------------------------------------------------------------------ +// Attestation Info. +// +//------------------------------------------------------------------------------------------- +#define WEBAUTHN_ATTESTATION_DECODE_NONE 0 +#define WEBAUTHN_ATTESTATION_DECODE_COMMON 1 +// WEBAUTHN_ATTESTATION_DECODE_COMMON supports format types +// L"packed" +// L"fido-u2f" + +#define WEBAUTHN_ATTESTATION_VER_TPM_2_0 L"2.0" + +typedef struct _WEBAUTHN_X5C { + // Length of X.509 encoded certificate + DWORD cbData; + // X.509 encoded certificate bytes + _Field_size_bytes_(cbData) + PBYTE pbData; +} WEBAUTHN_X5C, *PWEBAUTHN_X5C; + +// Supports either Self or Full Basic Attestation + +// Note, new fields will be added to the following data structure to +// support additional attestation format types, such as, TPM. +// When fields are added, the dwVersion will be incremented. +// +// Therefore, your code must make the following check: +// "if (dwVersion >= WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION)" + +#define WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION 1 + +typedef struct _WEBAUTHN_COMMON_ATTESTATION { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Hash and Padding Algorithm + // + // The following won't be set for "fido-u2f" which assumes "ES256". + PCWSTR pwszAlg; + LONG lAlg; // COSE algorithm + + // Signature that was generated for this attestation. + DWORD cbSignature; + _Field_size_bytes_(cbSignature) + PBYTE pbSignature; + + // Following is set for Full Basic Attestation. If not, set then, this is Self Attestation. + // Array of X.509 DER encoded certificates. The first certificate is the signer, leaf certificate. + DWORD cX5c; + _Field_size_(cX5c) + PWEBAUTHN_X5C pX5c; + + // Following are also set for tpm + PCWSTR pwszVer; // L"2.0" + DWORD cbCertInfo; + _Field_size_bytes_(cbCertInfo) + PBYTE pbCertInfo; + DWORD cbPubArea; + _Field_size_bytes_(cbPubArea) + PBYTE pbPubArea; +} WEBAUTHN_COMMON_ATTESTATION, *PWEBAUTHN_COMMON_ATTESTATION; +typedef const WEBAUTHN_COMMON_ATTESTATION *PCWEBAUTHN_COMMON_ATTESTATION; + +#define WEBAUTHN_ATTESTATION_TYPE_PACKED L"packed" +#define WEBAUTHN_ATTESTATION_TYPE_U2F L"fido-u2f" +#define WEBAUTHN_ATTESTATION_TYPE_TPM L"tpm" +#define WEBAUTHN_ATTESTATION_TYPE_NONE L"none" + +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_1 1 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 2 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 3 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 4 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 5 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 6 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 7 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8 8 +#define WEBAUTHN_CREDENTIAL_ATTESTATION_CURRENT_VERSION WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8 + +typedef struct _WEBAUTHN_CREDENTIAL_ATTESTATION { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Attestation format type + PCWSTR pwszFormatType; + + // Size of cbAuthenticatorData. + DWORD cbAuthenticatorData; + // Authenticator data that was created for this credential. + _Field_size_bytes_(cbAuthenticatorData) + PBYTE pbAuthenticatorData; + + // Size of CBOR encoded attestation information + //0 => encoded as CBOR null value. + DWORD cbAttestation; + //Encoded CBOR attestation information + _Field_size_bytes_(cbAttestation) + PBYTE pbAttestation; + + DWORD dwAttestationDecodeType; + // 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; + PVOID pvAttestationDecode; + + // The CBOR encoded Attestation Object to be returned to the RP. + DWORD cbAttestationObject; + _Field_size_bytes_(cbAttestationObject) + PBYTE pbAttestationObject; + + // The CredentialId bytes extracted from the Authenticator Data. + // Used by Edge to return to the RP. + DWORD cbCredentialId; + _Field_size_bytes_(cbCredentialId) + PBYTE pbCredentialId; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 + // + + WEBAUTHN_EXTENSIONS 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. + DWORD dwUsedTransport; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 + // + + BOOL bEpAtt; + BOOL bLargeBlobSupported; + BOOL bResidentKey; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 + // + + BOOL bPrfEnabled; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 + // + + DWORD cbUnsignedExtensionOutputs; + _Field_size_bytes_(cbUnsignedExtensionOutputs) + PBYTE pbUnsignedExtensionOutputs; + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 + // + + PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret; + + // ThirdPartyPayment Credential or not. + BOOL bThirdPartyPayment; + + // + // 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. + DWORD dwTransports; + + // UTF-8 encoded JSON serialization of the client data. + DWORD cbClientDataJSON; + _Field_size_bytes_(cbClientDataJSON) + PBYTE pbClientDataJSON; + + // UTF-8 encoded JSON serialization of the RegistrationResponse. + DWORD cbRegistrationResponseJSON; + _Field_size_bytes_(cbRegistrationResponseJSON) + PBYTE pbRegistrationResponseJSON; + +} WEBAUTHN_CREDENTIAL_ATTESTATION, *PWEBAUTHN_CREDENTIAL_ATTESTATION; +typedef const WEBAUTHN_CREDENTIAL_ATTESTATION *PCWEBAUTHN_CREDENTIAL_ATTESTATION; + + +//+------------------------------------------------------------------------------------------ +// authenticatorGetAssertion output. +//------------------------------------------------------------------------------------------- + +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NONE 0 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS 1 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_SUPPORTED 2 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_DATA 3 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_PARAMETER 4 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_FOUND 5 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_MULTIPLE_CREDENTIALS 6 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_LACK_OF_SPACE 7 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_PLATFORM_ERROR 8 +#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_AUTHENTICATOR_ERROR 9 + +#define WEBAUTHN_ASSERTION_VERSION_1 1 +#define WEBAUTHN_ASSERTION_VERSION_2 2 +#define WEBAUTHN_ASSERTION_VERSION_3 3 +#define WEBAUTHN_ASSERTION_VERSION_4 4 +#define WEBAUTHN_ASSERTION_VERSION_5 5 +#define WEBAUTHN_ASSERTION_VERSION_6 6 +#define WEBAUTHN_ASSERTION_CURRENT_VERSION WEBAUTHN_ASSERTION_VERSION_6 + +typedef struct _WEBAUTHN_ASSERTION { + // Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Size of cbAuthenticatorData. + DWORD cbAuthenticatorData; + // Authenticator data that was created for this assertion. + _Field_size_bytes_(cbAuthenticatorData) + PBYTE pbAuthenticatorData; + + // Size of pbSignature. + DWORD cbSignature; + // Signature that was generated for this assertion. + _Field_size_bytes_(cbSignature) + PBYTE pbSignature; + + // Credential that was used for this assertion. + WEBAUTHN_CREDENTIAL Credential; + + // Size of User Id + DWORD cbUserId; + // UserId + _Field_size_bytes_(cbUserId) + PBYTE pbUserId; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_2 + // + + WEBAUTHN_EXTENSIONS Extensions; + + // Size of pbCredLargeBlob + DWORD cbCredLargeBlob; + _Field_size_bytes_(cbCredLargeBlob) + PBYTE pbCredLargeBlob; + + DWORD dwCredLargeBlobStatus; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_3 + // + + PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_4 + // + + // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to + // the transport that was used. + DWORD dwUsedTransport; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_5 + // + + DWORD cbUnsignedExtensionOutputs; + _Field_size_bytes_(cbUnsignedExtensionOutputs) + PBYTE pbUnsignedExtensionOutputs; + + // + // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_6 + // + + // UTF-8 encoded JSON serialization of the client data. + DWORD cbClientDataJSON; + _Field_size_bytes_(cbClientDataJSON) + PBYTE pbClientDataJSON; + + // UTF-8 encoded JSON serialization of the AuthenticationResponse. + DWORD cbAuthenticationResponseJSON; + _Field_size_bytes_(cbAuthenticationResponseJSON) + PBYTE pbAuthenticationResponseJSON; + +} WEBAUTHN_ASSERTION, *PWEBAUTHN_ASSERTION; +typedef const WEBAUTHN_ASSERTION *PCWEBAUTHN_ASSERTION; + +//+------------------------------------------------------------------------------------------ +// APIs. +//------------------------------------------------------------------------------------------- + +DWORD +WINAPI +WebAuthNGetApiVersionNumber(); + +HRESULT +WINAPI +WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable( + _Out_ BOOL *pbIsUserVerifyingPlatformAuthenticatorAvailable); + + +HRESULT +WINAPI +WebAuthNAuthenticatorMakeCredential( + _In_ HWND hWnd, + _In_ PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation, + _In_ PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation, + _In_ PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS pPubKeyCredParams, + _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData, + _In_opt_ PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS pWebAuthNMakeCredentialOptions, + _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_ATTESTATION *ppWebAuthNCredentialAttestation); + + +HRESULT +WINAPI +WebAuthNAuthenticatorGetAssertion( + _In_ HWND hWnd, + _In_ LPCWSTR pwszRpId, + _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData, + _In_opt_ PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS pWebAuthNGetAssertionOptions, + _Outptr_result_maybenull_ PWEBAUTHN_ASSERTION *ppWebAuthNAssertion); + +void +WINAPI +WebAuthNFreeCredentialAttestation( + _In_opt_ PWEBAUTHN_CREDENTIAL_ATTESTATION pWebAuthNCredentialAttestation); + +void +WINAPI +WebAuthNFreeAssertion( + _In_ PWEBAUTHN_ASSERTION pWebAuthNAssertion); + +HRESULT +WINAPI +WebAuthNGetCancellationId( + _Out_ GUID* pCancellationId); + +HRESULT +WINAPI +WebAuthNCancelCurrentOperation( + _In_ const GUID* pCancellationId); + +// Returns NTE_NOT_FOUND when credentials are not found. +HRESULT +WINAPI +WebAuthNGetPlatformCredentialList( + _In_ PCWEBAUTHN_GET_CREDENTIALS_OPTIONS pGetCredentialsOptions, + _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST *ppCredentialDetailsList); + +void +WINAPI +WebAuthNFreePlatformCredentialList( + _In_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST pCredentialDetailsList); + +HRESULT +WINAPI +WebAuthNDeletePlatformCredential( + _In_ DWORD cbCredentialId, + _In_reads_bytes_(cbCredentialId) const BYTE *pbCredentialId + ); + +// Returns NTE_NOT_FOUND when authenticator details are not found. +HRESULT +WINAPI +WebAuthNGetAuthenticatorList( + _In_opt_ PCWEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS pWebAuthNGetAuthenticatorListOptions, + _Outptr_result_maybenull_ PWEBAUTHN_AUTHENTICATOR_DETAILS_LIST* ppAuthenticatorDetailsList); + +void +WINAPI +WebAuthNFreeAuthenticatorList( + _In_ PWEBAUTHN_AUTHENTICATOR_DETAILS_LIST pAuthenticatorDetailsList); + +// +// Returns the following Error Names: +// L"Success" - S_OK +// L"InvalidStateError" - NTE_EXISTS +// L"ConstraintError" - HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), +// NTE_NOT_SUPPORTED, +// NTE_TOKEN_KEYSET_STORAGE_FULL +// L"NotSupportedError" - NTE_INVALID_PARAMETER +// L"NotAllowedError" - NTE_DEVICE_NOT_FOUND, +// NTE_NOT_FOUND, +// HRESULT_FROM_WIN32(ERROR_CANCELLED), +// NTE_USER_CANCELLED, +// HRESULT_FROM_WIN32(ERROR_TIMEOUT) +// L"UnknownError" - All other hr values +// +PCWSTR +WINAPI +WebAuthNGetErrorName( + _In_ HRESULT hr); + +HRESULT +WINAPI +WebAuthNGetW3CExceptionDOMError( + _In_ HRESULT hr); + + +#ifdef __cplusplus +} // Balance extern "C" above +#endif + +#endif // WINAPI_FAMILY_PARTITION +#pragma endregion + +#endif // __WEBAUTHN_H_ diff --git a/apps/desktop/desktop_native/win_webauthn/include/webauthnplugin.h b/apps/desktop/desktop_native/win_webauthn/include/webauthnplugin.h new file mode 100644 index 00000000000..bffd2049187 --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/include/webauthnplugin.h @@ -0,0 +1,588 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include + +#pragma region Desktop Family or OneCore Family +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WINAPI +#define WINAPI __stdcall +#endif + +#ifndef INITGUID +#define INITGUID +#include +#undef INITGUID +#else +#include +#endif + +//+------------------------------------------------------------------------------------------ +// APIs. +//------------------------------------------------------------------------------------------- + +typedef enum _PLUGIN_AUTHENTICATOR_STATE +{ + AuthenticatorState_Disabled = 0, + AuthenticatorState_Enabled +} AUTHENTICATOR_STATE; + +HRESULT +WINAPI +WebAuthNPluginGetAuthenticatorState( + _In_ REFCLSID rclsid, + _Out_ AUTHENTICATOR_STATE* pluginAuthenticatorState +); + +// +// Plugin Authenticator API: WebAuthNAddPluginAuthenticator: Add Plugin Authenticator +// + +typedef struct _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS { + // Authenticator Name + LPCWSTR pwszAuthenticatorName; + + // Plugin COM ClsId + REFCLSID rclsid; + + // Plugin RPID (Optional. Required for a nested WebAuthN call originating from a plugin) + LPCWSTR pwszPluginRpId; + + // Plugin Authenticator Logo for the Light themes. base64 encoded SVG 1.1 (Optional) + LPCWSTR pwszLightThemeLogoSvg; + + // Plugin Authenticator Logo for the Dark themes. base64 encoded SVG 1.1 (Optional) + LPCWSTR pwszDarkThemeLogoSvg; + + // CTAP CBOR encoded authenticatorGetInfo + DWORD cbAuthenticatorInfo; + _Field_size_bytes_(cbAuthenticatorInfo) + const BYTE* pbAuthenticatorInfo; + + // List of supported RP IDs (Relying Party IDs). Should be 0/nullptr if all RPs are supported. + DWORD cSupportedRpIds; + const LPCWSTR* ppwszSupportedRpIds; + +} WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS, *PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS; +typedef const WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS *PCWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS; + +typedef struct _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE { + // Plugin operation signing Public Key - Used to sign the request in PCWEBAUTHN_PLUGIN_OPERATION_REQUEST. Refer pluginauthenticator.h. + DWORD cbOpSignPubKey; + _Field_size_bytes_(cbOpSignPubKey) + PBYTE pbOpSignPubKey; + +} WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE, *PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE; +typedef const WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE *PCWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE; + +HRESULT +WINAPI +WebAuthNPluginAddAuthenticator( + _In_ PCWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS pPluginAddAuthenticatorOptions, + _Outptr_result_maybenull_ PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE* ppPluginAddAuthenticatorResponse); + +void +WINAPI +WebAuthNPluginFreeAddAuthenticatorResponse( + _In_opt_ PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE pPluginAddAuthenticatorResponse); + +// +// Plugin Authenticator API: WebAuthNRemovePluginAuthenticator: Remove Plugin Authenticator +// + +HRESULT +WINAPI +WebAuthNPluginRemoveAuthenticator( + _In_ REFCLSID rclsid); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorUpdateDetails: Update Credential Metadata for Browser AutoFill Scenarios +// + +typedef struct _WEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS { + // Authenticator Name (Optional) + LPCWSTR pwszAuthenticatorName; + + // Plugin COM ClsId + REFCLSID rclsid; + + // New Plugin COM ClsId (Optional) + REFCLSID rclsidNew; + + // Plugin Authenticator Logo for the Light themes. base64 encoded SVG 1.1 (Optional) + LPCWSTR pwszLightThemeLogoSvg; + + // Plugin Authenticator Logo for the Dark themes. base64 encoded SVG 1.1 (Optional) + LPCWSTR pwszDarkThemeLogoSvg; + + // CTAP CBOR encoded authenticatorGetInfo + DWORD cbAuthenticatorInfo; + _Field_size_bytes_(cbAuthenticatorInfo) + const BYTE* pbAuthenticatorInfo; + + // List of supported RP IDs (Relying Party IDs). Should be 0/nullptr if all RPs are supported. + DWORD cSupportedRpIds; + const LPCWSTR* ppwszSupportedRpIds; + +} WEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS, *PWEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS; +typedef const WEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS *PCWEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS; + +HRESULT +WINAPI +WebAuthNPluginUpdateAuthenticatorDetails( + _In_ PCWEBAUTHN_PLUGIN_UPDATE_AUTHENTICATOR_DETAILS pPluginUpdateAuthenticatorDetails); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorAddCredentials: Add Credential Metadata for Browser AutoFill Scenarios +// + +typedef struct _WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS { + // Size of pbCredentialId. + DWORD cbCredentialId; + + // Credential Identifier bytes. This field is required. + _Field_size_bytes_(cbCredentialId) + const BYTE* pbCredentialId; + + // Identifier for the RP. This field is required. + LPCWSTR pwszRpId; + + // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site". + // This field is required. + LPCWSTR pwszRpName; + + // Identifier for the User. This field is required. + DWORD cbUserId; + + // User Identifier bytes. This field is required. + _Field_size_bytes_(cbUserId) + const BYTE* pbUserId; + + // Contains a detailed name for this account, such as "john.p.smith@example.com". + LPCWSTR pwszUserName; + + // For User: Contains the friendly name associated with the user account such as "John P. Smith". + LPCWSTR pwszUserDisplayName; + +} WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS, *PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS; +typedef const WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS *PCWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS; + +HRESULT +WINAPI +WebAuthNPluginAuthenticatorAddCredentials( + _In_ REFCLSID rclsid, + _In_ DWORD cCredentialDetails, + _In_reads_(cCredentialDetails) PCWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS pCredentialDetails); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorRemoveCredentials: Remove Credential Metadata for Browser AutoFill Scenarios +// + +HRESULT +WINAPI +WebAuthNPluginAuthenticatorRemoveCredentials( + _In_ REFCLSID rclsid, + _In_ DWORD cCredentialDetails, + _In_reads_(cCredentialDetails) PCWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS pCredentialDetails); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorRemoveCredentials: Remove All Credential Metadata for Browser AutoFill Scenarios +// + +HRESULT +WINAPI +WebAuthNPluginAuthenticatorRemoveAllCredentials( + _In_ REFCLSID rclsid); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorGetAllCredentials: Get All Credential Metadata cached for Browser AutoFill Scenarios +// + +HRESULT +WINAPI +WebAuthNPluginAuthenticatorGetAllCredentials( + _In_ REFCLSID rclsid, + _Out_ DWORD* pcCredentialDetails, + _Outptr_result_buffer_maybenull_(*pcCredentialDetails) PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS* ppCredentialDetailsArray); + +// +// Plugin Authenticator API: WebAuthNPluginAuthenticatorFreeCredentialDetailsList: Free Credential Metadata cached for Browser AutoFill Scenarios +// + +void +WINAPI +WebAuthNPluginAuthenticatorFreeCredentialDetailsArray( + _In_ DWORD cCredentialDetails, + _In_reads_(cCredentialDetails) PWEBAUTHN_PLUGIN_CREDENTIAL_DETAILS pCredentialDetailsArray); + +// +// Hello UV API for Plugin: WebAuthNPluginPerformUv: Perform Hello UV related operations +// + +typedef enum _WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE +{ + PerformUserVerification = 1, + GetUserVerificationCount, + GetPublicKey +} WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE; + +typedef 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. + HWND hwnd; + + // The webauthn transaction id from the WEBAUTHN_PLUGIN_OPERATION_REQUEST + REFGUID rguidTransactionId; + + // The username attached to the credential that is in use for this webauthn operation + LPCWSTR pwszUsername; + + // A text hint displayed on the windows hello prompt + LPCWSTR pwszDisplayHint; +} WEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST, *PWEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST; +typedef const WEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST *PCWEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST; + +HRESULT +WINAPI +WebAuthNPluginPerformUserVerification( + _In_ PCWEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST pPluginUserVerification, + _Out_ DWORD* pcbResponse, + _Outptr_result_bytebuffer_maybenull_(*pcbResponse) PBYTE* ppbResponse); + +void +WINAPI +WebAuthNPluginFreeUserVerificationResponse( + _In_opt_ PBYTE ppbResponse); + +HRESULT +WINAPI +WebAuthNPluginGetUserVerificationCount( + _In_ REFCLSID rclsid, + _Out_ DWORD* pdwVerificationCount); + +HRESULT +WINAPI +WebAuthNPluginGetUserVerificationPublicKey( + _In_ REFCLSID rclsid, + _Out_ DWORD* pcbPublicKey, + _Outptr_result_bytebuffer_(*pcbPublicKey) PBYTE* ppbPublicKey); // Free using WebAuthNPluginFreePublicKeyResponse + +HRESULT +WINAPI +WebAuthNPluginGetOperationSigningPublicKey( + _In_ REFCLSID rclsid, + _Out_ DWORD* pcbOpSignPubKey, + _Outptr_result_buffer_maybenull_(*pcbOpSignPubKey) PBYTE* ppbOpSignPubKey); // Free using WebAuthNPluginFreePublicKeyResponse + +void WINAPI WebAuthNPluginFreePublicKeyResponse( + _In_opt_ PBYTE pbOpSignPubKey); + +#define WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS_VERSION_1 1 +#define WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS_CURRENT_VERSION WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS_VERSION_1 +typedef struct _WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Following have following values: + // +1 - TRUE + // 0 - Not defined + // -1 - FALSE + //up: "true" | "false" + LONG lUp; + //uv: "true" | "false" + LONG lUv; + //rk: "true" | "false" + LONG lRequireResidentKey; +} WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS, *PWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS; +typedef const WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS *PCWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS; + +#define WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY_VERSION_1 1 +#define WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY_CURRENT_VERSION WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY_VERSION_1 +typedef struct _WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Key type + LONG lKty; + + // Hash Algorithm: ES256, ES384, ES512 + LONG lAlg; + + // Curve + LONG lCrv; + + //Size of "x" (X Coordinate) + DWORD cbX; + + //"x" (X Coordinate) data. Big Endian. + PBYTE pbX; + + //Size of "y" (Y Coordinate) + DWORD cbY; + + //"y" (Y Coordinate) data. Big Endian. + PBYTE pbY; +} WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY, *PWEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY; +typedef const WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY *PCWEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY; + +#define WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION_VERSION_1 1 +#define WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION_CURRENT_VERSION WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION_VERSION_1 +typedef struct _WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + // Platform's key agreement public key + PWEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY pKeyAgreement; + + DWORD cbEncryptedSalt; + PBYTE pbEncryptedSalt; + + DWORD cbSaltAuth; + PBYTE pbSaltAuth; +} WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION, *PWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION; +typedef const WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION *PCWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION; + +#define WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST_VERSION_1 1 +#define WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST_CURRENT_VERSION WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST_VERSION_1 +typedef struct _WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + //Input RP ID. Raw UTF8 bytes before conversion. + //These are the bytes to be hashed in the Authenticator Data. + DWORD cbRpId; + PBYTE pbRpId; + + //Client Data Hash + DWORD cbClientDataHash; + PBYTE pbClientDataHash; + + //RP Information + PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation; + + //User Information + PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation; + + // Crypto Parameters + WEBAUTHN_COSE_CREDENTIAL_PARAMETERS WebAuthNCredentialParameters; + + //Credentials used for exclusion + WEBAUTHN_CREDENTIAL_LIST CredentialList; + + //Optional extensions to parse when performing the operation. + DWORD cbCborExtensionsMap; + PBYTE pbCborExtensionsMap; + + // Authenticator Options (Optional) + PWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS pAuthenticatorOptions; + + // Pin Auth (Optional) + BOOL fEmptyPinAuth; // Zero length PinAuth is included in the request + DWORD cbPinAuth; + PBYTE pbPinAuth; + + //"hmac-secret": true extension + LONG lHmacSecretExt; + + // "hmac-secret-mc" extension + PWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION pHmacSecretMcExtension; + + //"prf" extension + LONG lPrfExt; + DWORD cbHmacSecretSaltValues; + PBYTE pbHmacSecretSaltValues; + + //"credProtect" extension. Nonzero if present + DWORD dwCredProtect; + + // Nonzero if present + DWORD dwPinProtocol; + + // Nonzero if present + DWORD dwEnterpriseAttestation; + + //"credBlob" extension. Nonzero if present + DWORD cbCredBlobExt; + PBYTE pbCredBlobExt; + + //"largeBlobKey": true extension + LONG lLargeBlobKeyExt; + + //"largeBlob": extension + DWORD dwLargeBlobSupport; + + //"minPinLength": true extension + LONG lMinPinLengthExt; + + // "json" extension. Nonzero if present + DWORD cbJsonExt; + PBYTE pbJsonExt; +} WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST, *PWEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST; +typedef const WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST *PCWEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST; + +_Success_(return == S_OK) +HRESULT +WINAPI +WebAuthNEncodeMakeCredentialResponse( + _In_ PCWEBAUTHN_CREDENTIAL_ATTESTATION pCredentialAttestation, + _Out_ DWORD* pcbResp, + _Outptr_result_buffer_maybenull_(*pcbResp) BYTE** ppbResp + ); + +_Success_(return == S_OK) +HRESULT +WINAPI +WebAuthNDecodeMakeCredentialRequest( + _In_ DWORD cbEncoded, + _In_reads_bytes_(cbEncoded) const BYTE* pbEncoded, + _Outptr_ PWEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST* ppMakeCredentialRequest + ); + +void +WINAPI +WebAuthNFreeDecodedMakeCredentialRequest( + _In_opt_ PWEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST pMakeCredentialRequest + ); + +#define WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST_VERSION_1 1 +#define WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST_CURRENT_VERSION WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST_VERSION_1 +typedef struct _WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST { + //Version of this structure, to allow for modifications in the future. + DWORD dwVersion; + + //RP ID. After UTF8 to Unicode conversion, + PCWSTR pwszRpId; + + //Input RP ID. Raw UTF8 bytes before conversion. + //These are the bytes to be hashed in the Authenticator Data. + DWORD cbRpId; + PBYTE pbRpId; + + //Client Data Hash + DWORD cbClientDataHash; + PBYTE pbClientDataHash; + + //Credentials used for inclusion + WEBAUTHN_CREDENTIAL_LIST CredentialList; + + //Optional extensions to parse when performing the operation. + DWORD cbCborExtensionsMap; + PBYTE pbCborExtensionsMap; + + // Authenticator Options (Optional) + PWEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS pAuthenticatorOptions; + + // Pin Auth (Optional) + BOOL fEmptyPinAuth; // Zero length PinAuth is included in the request + DWORD cbPinAuth; + PBYTE pbPinAuth; + + // HMAC Salt Extension (Optional) + PWEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION pHmacSaltExtension; + + // PRF Extension + DWORD cbHmacSecretSaltValues; + PBYTE pbHmacSecretSaltValues; + + DWORD dwPinProtocol; + + //"credBlob": true extension + LONG lCredBlobExt; + + //"largeBlobKey": true extension + LONG lLargeBlobKeyExt; + + //"largeBlob" extension + DWORD dwCredLargeBlobOperation; + DWORD cbCredLargeBlobCompressed; + PBYTE pbCredLargeBlobCompressed; + DWORD dwCredLargeBlobOriginalSize; + + // "json" extension. Nonzero if present + DWORD cbJsonExt; + PBYTE pbJsonExt; +} WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, *PWEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST; +typedef const WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST *PCWEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST; + +_Success_(return == S_OK) +HRESULT +WINAPI +WebAuthNDecodeGetAssertionRequest( + _In_ DWORD cbEncoded, + _In_reads_bytes_(cbEncoded) const BYTE* pbEncoded, + _Outptr_ PWEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST* ppGetAssertionRequest + ); + +void +WINAPI +WebAuthNFreeDecodedGetAssertionRequest( + _In_opt_ PWEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST pGetAssertionRequest + ); + +typedef struct _WEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE { + // [1] credential (optional) + // [2] authenticatorData + // [3] signature + WEBAUTHN_ASSERTION WebAuthNAssertion; + + // [4] user (optional) + PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation; + + // [5] numberOfCredentials (optional) + DWORD dwNumberOfCredentials; + + // [6] userSelected (optional) + LONG lUserSelected; + + // [7] largeBlobKey (optional) + DWORD cbLargeBlobKey; + PBYTE pbLargeBlobKey; + + // [8] unsignedExtensionOutputs + DWORD cbUnsignedExtensionOutputs; + PBYTE pbUnsignedExtensionOutputs; +} WEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE, *PWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE; +typedef const WEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE *PCWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE; + +_Success_(return == S_OK) +HRESULT +WINAPI +WebAuthNEncodeGetAssertionResponse( + _In_ PCWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE pGetAssertionResponse, + _Out_ DWORD* pcbResp, + _Outptr_result_buffer_maybenull_(*pcbResp) BYTE** ppbResp + ); + +typedef void (CALLBACK* WEBAUTHN_PLUGIN_STATUS_CHANGE_CALLBACK )(void* context); + +HRESULT +WINAPI +WebAuthNPluginRegisterStatusChangeCallback( + _In_ WEBAUTHN_PLUGIN_STATUS_CHANGE_CALLBACK callback, + _In_ void* context, + _In_ REFCLSID rclsid, + _Out_ DWORD* pdwRegister + ); + +HRESULT +WINAPI +WebAuthNPluginUnregisterStatusChangeCallback( + _In_ DWORD* pdwRegister + ); + + +#ifdef __cplusplus +} // Balance extern "C" above +#endif + +#endif // WINAPI_FAMILY_PARTITION +#pragma endregion + + diff --git a/apps/desktop/desktop_native/win_webauthn/src/lib.rs b/apps/desktop/desktop_native/win_webauthn/src/lib.rs new file mode 100644 index 00000000000..1887869fea5 --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/src/lib.rs @@ -0,0 +1,72 @@ +#![cfg(target_os = "windows")] +pub mod plugin; +mod types; +mod util; + +use std::{error::Error, fmt::Display}; + +pub use types::{ + AuthenticatorInfo, CredentialId, CtapTransport, CtapVersion, PublicKeyCredentialParameters, + UserId, +}; + +#[derive(Debug)] +pub struct WinWebAuthnError { + kind: ErrorKind, + description: Option, + cause: Option>, +} + +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( + kind: ErrorKind, + description: &str, + cause: E, + ) -> Self { + let cause: Box = Box::new(cause); + Self { + kind, + description: Some(description.to_string()), + cause: Some(cause), + } + } +} + +#[derive(Debug)] +enum ErrorKind { + DllLoad, + Serialization, + InvalidArguments, + Other, + 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 {} diff --git a/apps/desktop/desktop_native/win_webauthn/src/plugin/com.rs b/apps/desktop/desktop_native/win_webauthn/src/plugin/com.rs new file mode 100644 index 00000000000..52ff474b7f4 --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/src/plugin/com.rs @@ -0,0 +1,480 @@ +//! Functions for interacting with Windows COM. +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +use std::{ + alloc, + mem::MaybeUninit, + ptr::{self, NonNull}, + sync::{Arc, OnceLock}, +}; + +use windows::{ + core::{implement, interface, ComObjectInterface, IUnknown, GUID, HRESULT}, + Win32::{ + Foundation::{E_FAIL, E_INVALIDARG, S_FALSE, S_OK}, + System::Com::*, + }, +}; +use windows_core::{IInspectable, Interface}; + +use super::{ + types::{ + PluginLockStatus, WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST, + WEBAUTHN_PLUGIN_OPERATION_REQUEST, WEBAUTHN_PLUGIN_OPERATION_RESPONSE, + }, + PluginAuthenticator, +}; +use crate::{ + plugin::{crypto, PluginGetAssertionRequest, PluginMakeCredentialRequest}, + ErrorKind, WinWebAuthnError, +}; + +static HANDLER: OnceLock<(GUID, Arc)> = OnceLock::new(); +static SHUTDOWN: OnceLock = OnceLock::new(); + +#[implement(IClassFactory)] +pub struct Factory; + +impl IClassFactory_Impl for Factory_Impl { + fn CreateInstance( + &self, + _outer: windows::core::Ref, + iid: *const windows::core::GUID, + object: *mut *mut core::ffi::c_void, + ) -> windows::core::Result<()> { + let (clsid, handler) = match HANDLER.get() { + Some(state) => state, + None => { + tracing::error!("Cannot create COM class object instance because the handler is not initialized. register_server() must be called before starting the COM server."); + return Err(E_FAIL.into()); + } + }.clone(); + let unknown: IInspectable = PluginAuthenticatorComObject { clsid, handler }.into(); + unsafe { unknown.query(iid, object).ok() } + } + + fn LockServer(&self, _lock: windows::core::BOOL) -> windows::core::Result<()> { + // TODO: Implement lock server + Ok(()) + } +} + +// IPluginAuthenticator interface +#[interface("d26bcf6f-b54c-43ff-9f06-d5bf148625f7")] +pub unsafe trait IPluginAuthenticator: windows::core::IUnknown { + fn MakeCredential( + &self, + request: *const WEBAUTHN_PLUGIN_OPERATION_REQUEST, + response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE, + ) -> HRESULT; + fn GetAssertion( + &self, + request: *const WEBAUTHN_PLUGIN_OPERATION_REQUEST, + response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE, + ) -> HRESULT; + fn CancelOperation(&self, request: *const WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST) -> HRESULT; + fn GetLockStatus(&self, lock_status: *mut PluginLockStatus) -> HRESULT; +} + +#[implement(IPluginAuthenticator)] +struct PluginAuthenticatorComObject { + clsid: GUID, + handler: Arc, +} + +impl IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl { + unsafe fn MakeCredential( + &self, + request: *const WEBAUTHN_PLUGIN_OPERATION_REQUEST, + response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE, + ) -> HRESULT { + tracing::debug!("MakeCredential called"); + let response = match NonNull::new(response) { + Some(p) => p, + None => { + tracing::warn!( + "MakeCredential called with null response pointer from Windows. Aborting request." + ); + return E_INVALIDARG; + } + }; + let op_request_ptr = match NonNull::new(request.cast_mut()) { + Some(p) => p, + None => { + tracing::warn!( + "MakeCredential called with null request pointer from Windows. Aborting request." + ); + return E_INVALIDARG; + } + }; + + if let Err(err) = verify_operation_request(op_request_ptr.as_ref(), &self.clsid) { + tracing::error!("Failed to verify request signature: {err}"); + return E_INVALIDARG; + } + + // SAFETY: we received the pointer from Windows, so we trust that the values are set + // properly. + let registration_request = match PluginMakeCredentialRequest::try_from_ptr(op_request_ptr) { + 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) => { + // SAFETY: response pointer was given to us by Windows, so we assume it's valid. + match write_operation_response(®istration_response, response) { + Ok(()) => { + tracing::debug!("MakeCredential completed successfully"); + S_OK + } + Err(err) => { + tracing::error!( + "Failed to write MakeCredential response to Windows: {err}" + ); + return E_FAIL; + } + } + } + Err(err) => { + tracing::error!("MakeCredential failed: {err}"); + E_FAIL + } + } + } + + unsafe fn GetAssertion( + &self, + request: *const WEBAUTHN_PLUGIN_OPERATION_REQUEST, + response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE, + ) -> HRESULT { + tracing::debug!("GetAssertion called"); + let response = match NonNull::new(response) { + Some(p) => p, + None => { + tracing::warn!( + "GetAssertion called with null response pointer from Windows. Aborting request." + ); + return E_INVALIDARG; + } + }; + let op_request_ptr = match NonNull::new(request.cast_mut()) { + Some(p) => p, + None => { + tracing::warn!( + "GetAssertion called with null request pointer from Windows. Aborting request." + ); + return E_INVALIDARG; + } + }; + + if let Err(err) = verify_operation_request(op_request_ptr.as_ref(), &self.clsid) { + tracing::error!("Failed to verify request signature: {err}"); + return E_INVALIDARG; + } + + let assertion_request = match PluginGetAssertionRequest::try_from_ptr(op_request_ptr) { + Ok(assertion_request) => assertion_request, + Err(err) => { + tracing::error!("Could not deserialize GetAssertion request: {err}"); + return E_FAIL; + } + }; + match self.handler.get_assertion(assertion_request) { + Ok(assertion_response) => { + // SAFETY: response pointer was given to us by Windows, so we assume it's valid. + match write_operation_response(&assertion_response, response) { + Ok(()) => { + tracing::debug!("GetAssertion completed successfully"); + S_OK + } + Err(err) => { + tracing::error!("Failed to write GetCredential response to Windows: {err}"); + return E_FAIL; + } + } + } + Err(err) => { + tracing::error!("GetAssertion failed: {err}"); + E_FAIL + } + } + } + + unsafe fn CancelOperation( + &self, + request: *const WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST, + ) -> HRESULT { + tracing::debug!("CancelOperation called"); + let request = match NonNull::new(request as *mut WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST) { + Some(request) => request, + None => { + tracing::warn!("Received null CancelOperation request"); + return E_INVALIDARG; + } + }; + + match self.handler.cancel_operation(request.into()) { + Ok(()) => { + tracing::error!("CancelOperation completed successfully"); + S_OK + } + Err(err) => { + tracing::error!("CancelOperation failed: {err}"); + E_FAIL + } + } + } + + unsafe fn GetLockStatus(&self, lock_status: *mut PluginLockStatus) -> HRESULT { + tracing::debug!( + "GetLockStatus() called ", + std::process::id(), + std::thread::current().id() + ); + if lock_status.is_null() { + return HRESULT(-2147024809); // E_INVALIDARG + } + + match self.handler.lock_status() { + Ok(status) => { + tracing::debug!("GetLockStatus received {status:?}"); + *lock_status = status; + S_OK + } + Err(err) => { + tracing::error!("GetLockStatus failed: {err}"); + E_FAIL + } + } + } +} + +/// Copies data as COM-allocated buffer and writes to response pointer. +/// +/// Safety constraints: [response] must point to a valid +/// WEBAUTHN_PLUGIN_OPERATION_RESPONSE struct. +unsafe fn write_operation_response( + data: &[u8], + response: NonNull, +) -> Result<(), WinWebAuthnError> { + let len = match data.len().try_into() { + Ok(len) => len, + Err(err) => { + return Err(WinWebAuthnError::with_cause( + ErrorKind::Serialization, + "Response is too long to return to OS", + err, + )); + } + }; + let buf = data.to_com_buffer(); + + response.write(WEBAUTHN_PLUGIN_OPERATION_RESPONSE { + cbEncodedResponse: len, + pbEncodedResponse: buf.leak(), + }); + Ok(()) +} + +/// Registers the plugin authenticator COM library with Windows. +pub(super) fn register_server(clsid: &GUID, handler: T) -> Result<(), WinWebAuthnError> +where + T: PluginAuthenticator + Send + Sync + 'static, +{ + if HANDLER.get().is_some() { + return Err(WinWebAuthnError::new( + ErrorKind::Other, + "server can only be registered one time per process", + )); + } + unsafe { + // Initialize COM on this thread. + let com_init_result = CoInitializeEx(None, COINIT_APARTMENTTHREADED); + match com_init_result { + S_OK | S_FALSE => {} // COM successfully initialized, and should be uninitialized with CoUninitialize later. + code => { + return Err(WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Could not initialize the COM library", + windows::core::Error::from_hresult(code), + )); + } + } + + // If this call fails, some library in this process may have already set + // it. We will ignore any failures. + if let Err(err) = CoInitializeSecurity( + None, + -1, + None, + None, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + None, + EOAC_NONE, + None, + ) { + tracing::warn!("Could not initialize COM security: {err}",); + }; + } + + // Store the handler as a static so it can be initialized + HANDLER.set((*clsid, Arc::new(handler))).map_err(|_| { + WinWebAuthnError::new(ErrorKind::WindowsInternal, "Handler already initialized") + })?; + + // Register the COM class object so that Windows RPC knows how to start it. + static FACTORY: windows::core::StaticComObject = Factory.into_static(); + unsafe { + CoRegisterClassObject( + ptr::from_ref(clsid), + FACTORY.as_interface_ref(), + CLSCTX_LOCAL_SERVER, + REGCLS_MULTIPLEUSE, + ) + } + .map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Couldn't register the COM library with Windows", + err, + ) + })?; + Ok(()) +} + +pub(super) fn shutdown_server() -> std::result::Result<(), WinWebAuthnError> { + if HANDLER.get().is_some() { + if let Ok(()) = SHUTDOWN.set(true) { + unsafe { CoUninitialize() }; + } else { + tracing::debug!("server already shut down"); + } + } else { + tracing::debug!("server was not registered. Ignoring."); + } + Ok(()) +} + +#[repr(transparent)] +pub(super) struct ComBuffer(NonNull>); + +impl ComBuffer { + /// Returns an COM-allocated buffer of `size`. + fn alloc(size: usize, for_slice: bool) -> Self { + #[expect(clippy::as_conversions)] + { + assert!(size <= isize::MAX as usize, "requested bad object size"); + } + + // SAFETY: Any size is valid to pass to Windows, even `0`. + let ptr = NonNull::new(unsafe { CoTaskMemAlloc(size) }).unwrap_or_else(|| { + // XXX: This doesn't have to be correct, just close enough for an OK OOM error. + let layout = alloc::Layout::from_size_align(size, align_of::()).unwrap(); + alloc::handle_alloc_error(layout) + }); + + if for_slice { + // Ininitialize the buffer so it can later be treated as `&mut [u8]`. + // SAFETY: The pointer is valid and we are using a valid value for a byte-wise + // allocation. + unsafe { ptr.write_bytes(0, size) }; + } + + Self(ptr.cast()) + } + + pub fn leak(self) -> *mut T { + self.0.cast().as_ptr() + } +} + +pub(super) trait ComBufferExt { + fn to_com_buffer(&self) -> ComBuffer; +} + +impl ComBufferExt for Vec { + fn to_com_buffer(&self) -> ComBuffer { + ComBuffer::from(&self) + } +} + +impl ComBufferExt for &[u8] { + fn to_com_buffer(&self) -> ComBuffer { + ComBuffer::from(self) + } +} + +impl ComBufferExt for Vec { + fn to_com_buffer(&self) -> ComBuffer { + let buffer: Vec = self.into_iter().flat_map(|x| x.to_le_bytes()).collect(); + ComBuffer::from(&buffer) + } +} + +impl ComBufferExt for &[u16] { + fn to_com_buffer(&self) -> ComBuffer { + let buffer: Vec = self + .as_ref() + .into_iter() + .flat_map(|x| x.to_le_bytes()) + .collect(); + ComBuffer::from(&buffer) + } +} + +impl> From for ComBuffer { + fn from(value: T) -> Self { + let buffer: Vec = value + .as_ref() + .into_iter() + .flat_map(|x| x.to_le_bytes()) + .collect(); + let len = buffer.len(); + let com_buffer = Self::alloc(len, true); + // SAFETY: `ptr` points to a valid allocation that `len` matches, and we made sure + // the bytes were initialized. Additionally, bytes have no alignment requirements. + unsafe { + NonNull::slice_from_raw_parts(com_buffer.0.cast::(), len) + .as_mut() + .copy_from_slice(&buffer); + } + com_buffer + } +} + +/// Verify the signature on an operation request. +/// +/// # Safety +/// The caller must ensure that: +/// - pbEncodedRequest points to a valid non-null byte string of length cbEncodedRequest. +/// - pbRequestSignature points to a valid non-null byte string of length cbRequestSignature. +unsafe fn verify_operation_request( + request: &WEBAUTHN_PLUGIN_OPERATION_REQUEST, + clsid: &GUID, +) -> Result<(), WinWebAuthnError> { + tracing::debug!("Verifying request"); + let request_data = + std::slice::from_raw_parts(request.pbEncodedRequest, request.cbEncodedRequest as usize); + let request_hash = crypto::hash_sha256(request_data).map_err(|err| { + WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "failed to hash request", err) + })?; + let signature = std::slice::from_raw_parts( + request.pbRequestSignature, + request.cbRequestSignature as usize, + ); + tracing::debug!("Retrieving signing key"); + let op_pub_key = crypto::get_operation_signing_public_key(clsid).map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Failed to get signing key for operation", + err, + ) + })?; + tracing::debug!("Verifying signature"); + op_pub_key.verify_signature(&request_hash, signature) +} diff --git a/apps/desktop/desktop_native/win_webauthn/src/plugin/crypto.rs b/apps/desktop/desktop_native/win_webauthn/src/plugin/crypto.rs new file mode 100644 index 00000000000..f399cc534a9 --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/src/plugin/crypto.rs @@ -0,0 +1,249 @@ +#![allow(non_snake_case)] +use std::{mem::MaybeUninit, ptr::NonNull}; + +use windows::{ + core::{GUID, HRESULT, PCWSTR}, + Win32::{ + Foundation::E_INVALIDARG, + Security::Cryptography::{ + BCryptCreateHash, BCryptFinishHash, BCryptGetProperty, BCryptHashData, NCryptImportKey, + NCryptOpenStorageProvider, NCryptVerifySignature, BCRYPT_HASH_LENGTH, BCRYPT_KEY_BLOB, + BCRYPT_OBJECT_LENGTH, BCRYPT_PKCS1_PADDING_INFO, BCRYPT_PUBLIC_KEY_BLOB, + BCRYPT_RSAPUBLIC_MAGIC, BCRYPT_SHA256_ALGORITHM, BCRYPT_SHA256_ALG_HANDLE, + NCRYPT_FLAGS, NCRYPT_PAD_PKCS1_FLAG, + }, + }, +}; + +use crate::{util::webauthn_call, ErrorKind, WinWebAuthnError}; + +webauthn_call!("WebAuthNPluginGetUserVerificationPublicKey" as fn webauthn_plugin_get_user_verification_public_key( + rclsid: *const GUID, + pcbPublicKey: *mut u32, + ppbPublicKey: *mut *mut u8) -> HRESULT); // Free using WebAuthNPluginFreePublicKeyResponse + +webauthn_call!("WebAuthNPluginGetOperationSigningPublicKey" as fn webauthn_plugin_get_operation_signing_public_key( + rclsid: *const GUID, + pcbOpSignPubKey: *mut u32, + ppbOpSignPubKey: *mut *mut u8 + ) -> HRESULT); // Free using WebAuthNPluginFreePublicKeyResponse + +webauthn_call!("WebAuthNPluginFreePublicKeyResponse" as fn webauthn_plugin_free_public_key_response( + pbOpSignPubKey: *mut u8 + ) -> ()); + +pub(super) fn get_operation_signing_public_key( + clsid: &GUID, +) -> Result { + let mut len = 0; + let mut data = MaybeUninit::uninit(); + unsafe { + // SAFETY: We check the OS error code before using the written pointer. + webauthn_plugin_get_operation_signing_public_key(clsid, &mut len, data.as_mut_ptr())? + .ok() + .map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Failed to retrieve operation signing public key", + err, + ) + })?; + match NonNull::new(data.assume_init()) { + Some(data) => Ok(SigningKey { + cbPublicKey: len, + pbPublicKey: data, + }), + None => Err(WinWebAuthnError::new( + ErrorKind::WindowsInternal, + "Windows returned null pointer when requesting operation signing public key", + )), + } + } +} + +pub(super) fn get_user_verification_public_key( + clsid: &GUID, +) -> Result { + let mut len = 0; + let mut data = MaybeUninit::uninit(); + // SAFETY: We check the OS error code before using the written pointer. + unsafe { + webauthn_plugin_get_user_verification_public_key(clsid, &mut len, data.as_mut_ptr())? + .ok() + .map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Failed to retrieve user verification public key", + err, + ) + })?; + match NonNull::new(data.assume_init()) { + Some(data) => Ok(SigningKey { + cbPublicKey: len, + pbPublicKey: data, + }), + None => Err(WinWebAuthnError::new( + ErrorKind::WindowsInternal, + "Windows returned null pointer when requesting user verification public key", + )), + } + } +} + +fn verify_signature( + public_key: &SigningKey, + hash: &[u8], + signature: &[u8], +) -> Result<(), windows::core::Error> { + // Verify the signature over the hash of dataBuffer using the hKey + unsafe { + tracing::debug!("Getting provider"); + // Get the provider + let mut provider = MaybeUninit::uninit(); + + NCryptOpenStorageProvider(provider.as_mut_ptr(), PCWSTR::null(), 0)?; + let provider = provider.assume_init(); + + tracing::debug!("Getting key handle"); + // Create a NCrypt key handle from the public key + let mut key_handle = MaybeUninit::uninit(); + NCryptImportKey( + provider, + None, + BCRYPT_PUBLIC_KEY_BLOB, + None, + key_handle.as_mut_ptr(), + public_key.as_ref(), + NCRYPT_FLAGS(0), + )?; + let key_handle = key_handle.assume_init(); + + tracing::debug!("Trying to read public key slice as key blob"); + let public_key = public_key.as_ref(); + if public_key.len() < size_of::() { + return Err(windows::core::Error::from_hresult(E_INVALIDARG)); + } + let key_blob: &BCRYPT_KEY_BLOB = &*public_key.as_ptr().cast(); + tracing::debug!(" got key magic: {}", key_blob.Magic); + let (padding_info, cng_flags) = if key_blob.Magic == BCRYPT_RSAPUBLIC_MAGIC.0 { + tracing::debug!("Detected RSA key, adding PKCS1 padding"); + let padding_info = BCRYPT_PKCS1_PADDING_INFO { + pszAlgId: BCRYPT_SHA256_ALGORITHM, + }; + (Some(padding_info), NCRYPT_PAD_PKCS1_FLAG) + } else { + tracing::debug!("Non-RSA key, no PKCS1 padding added"); + (None, NCRYPT_FLAGS(0)) + }; + + tracing::debug!("Verifying signature"); + NCryptVerifySignature( + key_handle, + padding_info + .as_ref() + .map(|padding: &BCRYPT_PKCS1_PADDING_INFO| std::ptr::from_ref(padding).cast()), + hash, + signature, + cng_flags, + )?; + tracing::debug!("Verified"); + Ok(()) + } +} + +pub(super) fn hash_sha256(data: &[u8]) -> Result, windows::core::Error> { + unsafe { + tracing::debug!("Getting length of hash buffer object"); + // Get length of SHA-256 buffer + let mut len_size_buf = [0; size_of::()]; + let mut bytes_read = 0; + BCryptGetProperty( + BCRYPT_SHA256_ALG_HANDLE.into(), + BCRYPT_OBJECT_LENGTH, + Some(&mut len_size_buf), + &mut bytes_read, + 0, + ) + .ok()?; + // SAFETY: We explicitly set the size of the buffer to u32, and we only + // support platforms where usize is at least 32-bits. + let len_size: usize = u32::from_ne_bytes(len_size_buf) as usize; + tracing::debug!(" Length of hash buffer object: {len_size}"); + let mut hash_obj_buf = Vec::with_capacity(len_size); + hash_obj_buf.set_len(len_size); + + tracing::debug!("Creating hash algorithm handle with buffer object"); + // Get SHA256 handle + let mut hash_handle = MaybeUninit::uninit(); + BCryptCreateHash( + BCRYPT_SHA256_ALG_HANDLE, + hash_handle.as_mut_ptr(), + Some(hash_obj_buf.as_mut_slice()), + None, + 0, + ) + .ok()?; + let hash_handle = hash_handle.assume_init(); + + tracing::debug!("Hashing data"); + // Hash data + BCryptHashData(hash_handle, data, 0).ok()?; + + // Get length of SHA256 hash output + tracing::debug!("Getting length of hash output"); + let mut hash_output_len_buf = [0; size_of::()]; + let mut bytes_read = 0; + BCryptGetProperty( + BCRYPT_SHA256_ALG_HANDLE.into(), + BCRYPT_HASH_LENGTH, + Some(&mut hash_output_len_buf), + &mut bytes_read, + 0, + ) + .ok()?; + + let hash_output_len = u32::from_ne_bytes(hash_output_len_buf) as usize; + tracing::debug!(" Length of hash output: {hash_output_len}"); + + tracing::debug!("Completing hash"); + let mut hash_buffer = Vec::with_capacity(hash_output_len); + hash_buffer.set_len(hash_output_len); + BCryptFinishHash(hash_handle, hash_buffer.as_mut_slice(), 0).ok()?; + tracing::debug!(" Hash: {hash_buffer:?}"); + Ok(hash_buffer) + } +} + +/// Signing key for an operation request or user verification response buffer. +pub struct SigningKey { + cbPublicKey: u32, + pbPublicKey: NonNull, +} + +impl SigningKey { + /// Verifies a signature over some data with the associated public key. + pub fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result<(), WinWebAuthnError> { + verify_signature(self, data, signature).map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Failed to verify signature", + err, + ) + }) + } +} +impl Drop for SigningKey { + fn drop(&mut self) { + unsafe { + _ = webauthn_plugin_free_public_key_response(self.pbPublicKey.as_mut()); + } + } +} +impl AsRef<[u8]> for SigningKey { + fn as_ref(&self) -> &[u8] { + // SAFETY: We only support platforms where usize >= 32-bts + let len = self.cbPublicKey as usize; + // SAFETY: This pointer was given to us from Windows, so we trust it. + unsafe { std::slice::from_raw_parts(self.pbPublicKey.as_ptr(), len) } + } +} diff --git a/apps/desktop/desktop_native/win_webauthn/src/plugin/mod.rs b/apps/desktop/desktop_native/win_webauthn/src/plugin/mod.rs new file mode 100644 index 00000000000..10d447cea2e --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/src/plugin/mod.rs @@ -0,0 +1,376 @@ +pub(crate) mod com; +pub(crate) mod crypto; +pub(crate) mod types; + +use std::{error::Error, ptr::NonNull}; + +use types::*; +pub use types::{ + PluginAddAuthenticatorOptions, PluginAddAuthenticatorResponse, PluginCancelOperationRequest, + PluginCredentialDetails, PluginGetAssertionRequest, PluginLockStatus, + PluginMakeCredentialRequest, PluginMakeCredentialResponse, PluginUserVerificationRequest, + PluginUserVerificationResponse, +}; +use windows::{ + core::GUID, + Win32::Foundation::{NTE_USER_CANCELLED, S_OK}, +}; + +use super::{ErrorKind, WinWebAuthnError}; +use crate::{ + plugin::{ + com::{ComBuffer, ComBufferExt}, + crypto::SigningKey, + }, + util::WindowsString, +}; + +#[derive(Clone, Copy)] +pub struct Clsid(GUID); + +impl TryFrom<&str> for Clsid { + type Error = WinWebAuthnError; + + fn try_from(value: &str) -> Result { + // Remove hyphens and parse as hex + let clsid_clean = value.replace("-", "").replace("{", "").replace("}", ""); + if clsid_clean.len() != 32 { + return Err(WinWebAuthnError::new( + ErrorKind::Serialization, + "Invalid CLSID format", + )); + } + + // Convert to u128 and create GUID + let clsid_u128 = u128::from_str_radix(&clsid_clean, 16).map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::Serialization, + "Failed to parse CLSID as hex", + err, + ) + })?; + + let clsid = Clsid(GUID::from_u128(clsid_u128)); + Ok(clsid) + } +} + +pub struct WebAuthnPlugin { + clsid: Clsid, +} + +impl WebAuthnPlugin { + pub fn new(clsid: Clsid) -> Self { + WebAuthnPlugin { clsid } + } + + /// Registers a COM server with Windows. + /// + /// The handler should be an instance of your type that implements PluginAuthenticator. + /// The same instance will be shared across all COM calls. + /// + /// This only needs to be called at the start of your application. + pub fn register_server(&self, handler: T) -> Result<(), WinWebAuthnError> + where + T: PluginAuthenticator + Send + Sync + 'static, + { + com::register_server(&self.clsid.0, handler) + } + + /// Uninitializes the COM library for the calling thread. + pub fn shutdown_server() -> Result<(), WinWebAuthnError> { + com::shutdown_server() + } + + /// Adds this implementation as a Windows WebAuthn plugin. + /// + /// This only needs to be called on installation of your application. + pub fn add_authenticator( + options: PluginAddAuthenticatorOptions, + ) -> Result { + #![allow(non_snake_case)] + let mut response_ptr: *mut WebAuthnPluginAddAuthenticatorResponse = std::ptr::null_mut(); + + // We need to be careful to use .as_ref() to ensure that we're not + // sending dangling pointers to the OS. + let authenticator_name = options.authenticator_name.to_utf16(); + + let rp_id = options.rp_id.as_ref().map(|rp_id| rp_id.to_utf16()); + let pwszPluginRpId = rp_id.as_ref().map_or(std::ptr::null(), |v| v.as_ptr()); + + let light_logo_b64 = options.light_theme_logo_b64(); + let pwszLightThemeLogoSvg = light_logo_b64 + .as_ref() + .map_or(std::ptr::null(), |v| v.as_ptr()); + let dark_logo_b64 = options.dark_theme_logo_b64(); + let pwszDarkThemeLogoSvg = dark_logo_b64 + .as_ref() + .map_or(std::ptr::null(), |v| v.as_ptr()); + + let authenticator_info = options.authenticator_info.as_ctap_bytes()?; + + let supported_rp_ids: Option>> = options + .supported_rp_ids + .map(|ids| ids.iter().map(|id| id.to_utf16()).collect()); + let supported_rp_id_ptrs: Option> = supported_rp_ids + .as_ref() + .map(|ids| ids.iter().map(Vec::as_ptr).collect()); + let pbSupportedRpIds = supported_rp_id_ptrs + .as_ref() + .map_or(std::ptr::null(), |v| v.as_ptr()); + + let options_c = WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS { + pwszAuthenticatorName: authenticator_name.as_ptr(), + rclsid: &options.clsid.0, + pwszPluginRpId, + pwszLightThemeLogoSvg, + pwszDarkThemeLogoSvg, + cbAuthenticatorInfo: authenticator_info.len() as u32, + pbAuthenticatorInfo: authenticator_info.as_ptr(), + cSupportedRpIds: supported_rp_id_ptrs.map_or(0, |ids| ids.len() as u32), + pbSupportedRpIds, + }; + unsafe { + // SAFETY: We are holding references to all the input data beyond the OS call, so it is valid during the call. + let result = webauthn_plugin_add_authenticator(&options_c, &mut response_ptr)?; + result.ok().map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Failed to add authenticator", + err, + ) + })?; + + if let Some(response) = NonNull::new(response_ptr) { + // SAFETY: The pointer was allocated by a successful call to + // webauthn_plugin_add_authenticator, so we trust that it's valid. + Ok(PluginAddAuthenticatorResponse::try_from_ptr(response)) + } else { + Err(WinWebAuthnError::new( + ErrorKind::WindowsInternal, + "WebAuthNPluginAddAuthenticatorResponse returned null", + )) + } + } + } + + /// Perform user verification related to an associated MakeCredential or GetAssertion request. + pub fn perform_user_verification( + &self, + request: PluginUserVerificationRequest, + operation_request: &[u8], + ) -> Result { + tracing::debug!(?request, "Handling user verification request"); + + // Get pub key + let pub_key = crypto::get_user_verification_public_key(&self.clsid.0)?; + + // Send UV request + let user_name = request.user_name.to_utf16().to_com_buffer(); + let hint = request.display_hint.map(|d| d.to_utf16().to_com_buffer()); + let uv_request = WEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST { + hwnd: request.window_handle, + rguidTransactionId: &request.transaction_id, + pwszUsername: user_name.leak(), + pwszDisplayHint: hint.map_or(std::ptr::null(), |buf| buf.leak()), + }; + let mut response_len = 0; + let mut response_ptr = std::ptr::null_mut(); + unsafe { + let hresult = webauthn_plugin_perform_user_verification( + &uv_request, + &mut response_len, + &mut response_ptr, + )?; + match hresult { + S_OK => { + let signature = if response_len > 0 { + Vec::new() + } else { + // SAFETY: Windows only runs on platforms where usize >= u32; + let len = response_len as usize; + // SAFETY: Windows returned successful response code and length, so we + // assume that the data is initialized + let signature = std::slice::from_raw_parts(response_ptr, len).to_vec(); + pub_key.verify_signature(operation_request, &signature)?; + signature + }; + webauthn_plugin_free_user_verification_response(response_ptr)?; + Ok(PluginUserVerificationResponse { + transaction_id: request.transaction_id, + signature, + }) + } + NTE_USER_CANCELLED => Err(WinWebAuthnError::new( + ErrorKind::Other, + "User cancelled user verification", + )), + _ => Err(WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Unknown error occurred while performing user verification", + windows::core::Error::from_hresult(hresult), + )), + } + } + } + + /// Synchronize credentials to Windows Hello cache. + /// + /// Number of credentials to sync must be less than [u32::MAX]. + pub fn sync_credentials( + &self, + credentials: Vec, + ) -> Result<(), WinWebAuthnError> { + if credentials.is_empty() { + tracing::debug!("[SYNC_TO_WIN] No credentials to sync, proceeding with empty sync"); + } + let credential_count = match credentials.len().try_into() { + Ok(c) => c, + Err(err) => { + return Err(WinWebAuthnError::with_cause( + ErrorKind::InvalidArguments, + "Too many credentials passed to sync", + err, + )); + } + }; + + // First try to remove all existing credentials for this plugin + tracing::debug!("Attempting to remove all existing credentials before sync..."); + // SAFETY: API definition matches actual DLL. + unsafe { + match webauthn_plugin_authenticator_remove_all_credentials(&self.clsid.0)?.ok() { + Ok(()) => { + tracing::debug!("Successfully removed existing credentials"); + } + Err(e) => { + tracing::warn!("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() { + tracing::debug!("No credentials to add to Windows - sync completed successfully"); + Ok(()) + } else { + tracing::debug!("Adding new credentials to Windows..."); + + // Convert Bitwarden credentials to Windows credential details + // All buffers must be allocated with the COM task allocator to be passed over COM. + // The receiver is responsible for freeing the COM memory, which is why we leak all the buffers here. + let mut win_credentials = Vec::new(); + for (i, cred) in credentials.iter().enumerate() { + tracing::debug!("[SYNC_TO_WIN] Converting credential {}: RP ID: {}, User: {}, Credential ID: {:?} ({} bytes), User ID: {:?} ({} bytes)", + i + 1, cred.rp_id, cred.user_name, &cred.credential_id, cred.credential_id.len(), &cred.user_id, cred.user_id.len()); + + // Allocate credential_id bytes with COM + let credential_id_buf = cred.credential_id.as_ref().to_com_buffer(); + + // Allocate user_id bytes with COM + let user_id_buf = cred.user_id.as_ref().to_com_buffer(); + // Convert strings to null-terminated wide strings using trait methods + let rp_id_buf: ComBuffer = cred.rp_id.to_utf16().to_com_buffer(); + let rp_friendly_name_buf: Option = cred + .rp_friendly_name + .as_ref() + .map(|display_name| display_name.to_utf16().to_com_buffer()); + let user_name_buf: ComBuffer = (cred.user_name.to_utf16()).to_com_buffer(); + let user_display_name_buf: ComBuffer = + cred.user_display_name.to_utf16().to_com_buffer(); + let win_cred = WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS { + credential_id_byte_count: u32::from(cred.credential_id.len()), + credential_id_pointer: credential_id_buf.leak(), + rpid: rp_id_buf.leak(), + rp_friendly_name: rp_friendly_name_buf + .map_or(std::ptr::null(), |buf| buf.leak()), + user_id_byte_count: u32::from(cred.user_id.len()), + user_id_pointer: user_id_buf.leak(), + user_name: user_name_buf.leak(), + user_display_name: user_display_name_buf.leak(), + }; + win_credentials.push(win_cred); + tracing::debug!( + "[SYNC_TO_WIN] Converted credential {} to Windows format", + i + 1 + ); + } + + // SAFETY: The pointer to win_credentials lives longer than the call to + // webauthn_plugin_authenticator_add_credentials(). The nested + // buffers are allocated with COM, which the OS is responsible for + // cleaning up. + let result = unsafe { + webauthn_plugin_authenticator_add_credentials( + &self.clsid.0, + credential_count, + win_credentials.as_ptr(), + ) + }; + match result { + Ok(hresult) => { + if let Err(err) = hresult.ok() { + let err = + WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "failed", err); + tracing::error!( + "Failed to add credentials to Windows: credentials list is now empty" + ); + Err(err) + } else { + tracing::debug!("Successfully synced credentials to Windows"); + Ok(()) + } + } + Err(e) => { + tracing::error!("Failed to add credentials to Windows: {}", e); + Err(e) + } + } + } + } + + /// Retrieve the public key used to sign operation requests. + pub fn operation_signing_public_key(&self) -> Result { + crypto::get_operation_signing_public_key(&self.clsid.0) + } + + /// Retrieve the public key used to sign user verification responses. + pub fn user_verification_public_key(&self) -> Result { + crypto::get_user_verification_public_key(&self.clsid.0) + } +} +pub trait PluginAuthenticator { + /// Process a request to create a new credential. + /// + /// Returns a [CTAP authenticatorMakeCredential response structure](https://fidoalliance.org/specs/fido-v2.2-ps-20250714/fido-client-to-authenticator-protocol-v2.2-ps-20250714.html#authenticatormakecredential-response-structure). + fn make_credential( + &self, + request: PluginMakeCredentialRequest, + ) -> Result, Box>; + + /// Process a request to assert a credential. + /// + /// Returns a [CTAP authenticatorGetAssertion response structure](https://fidoalliance.org/specs/fido-v2.2-ps-20250714/fido-client-to-authenticator-protocol-v2.2-ps-20250714.html#authenticatorgetassertion-response-structure). + fn get_assertion(&self, request: PluginGetAssertionRequest) -> Result, Box>; + + /// Cancel an ongoing operation. + fn cancel_operation(&self, request: PluginCancelOperationRequest) + -> Result<(), Box>; + + /// Retrieve lock status. + fn lock_status(&self) -> Result>; +} + +#[cfg(test)] +mod tests { + use super::Clsid; + + const CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062"; + + #[test] + fn test_parse_clsid_to_guid() { + let result = Clsid::try_from(CLSID); + assert!(result.is_ok(), "CLSID parsing should succeed"); + } +} diff --git a/apps/desktop/desktop_native/win_webauthn/src/plugin/types.rs b/apps/desktop/desktop_native/win_webauthn/src/plugin/types.rs new file mode 100644 index 00000000000..39c516be67d --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/src/plugin/types.rs @@ -0,0 +1,1050 @@ +//! Types pertaining to registering a plugin implementation and handling plugin +//! authenticator requests. + +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +use std::{mem::MaybeUninit, ptr::NonNull}; + +use base64::{engine::general_purpose::STANDARD, Engine as _}; +use windows::{ + core::{GUID, HRESULT}, + Win32::{Foundation::HWND, System::Com::CoTaskMemFree}, +}; +use windows_core::BOOL; + +use super::Clsid; +use crate::{ + plugin::crypto, + types::{ + AuthenticatorInfo, CredentialEx, CtapTransport, HmacSecretSalt, RpEntityInformation, + UserEntityInformation, UserId, WebAuthnExtensionMakeCredentialOutput, + WEBAUTHN_COSE_CREDENTIAL_PARAMETER, WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, + WEBAUTHN_CREDENTIAL_ATTESTATION, WEBAUTHN_CREDENTIAL_LIST, WEBAUTHN_EXTENSIONS, + WEBAUTHN_RP_ENTITY_INFORMATION, WEBAUTHN_USER_ENTITY_INFORMATION, + }, + util::{webauthn_call, WindowsString}, + CredentialId, ErrorKind, WinWebAuthnError, +}; + +// Plugin Registration types + +/// Windows WebAuthn Authenticator Options structure +/// Header File Name: _WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS { + dwVersion: u32, + // LONG lUp: +1=TRUE, 0=Not defined, -1=FALSE + lUp: i32, + // LONG lUv: +1=TRUE, 0=Not defined, -1=FALSE + lUv: i32, + // LONG lRequireResidentKey: +1=TRUE, 0=Not defined, -1=FALSE + lRequireResidentKey: i32, +} + +impl WebAuthnCtapCborAuthenticatorOptions { + pub fn version(&self) -> u32 { + self.dwVersion + } + + pub fn user_presence(&self) -> Option { + Self::to_optional_bool(self.lUp) + } + + pub fn user_verification(&self) -> Option { + Self::to_optional_bool(self.lUv) + } + + pub fn require_resident_key(&self) -> Option { + Self::to_optional_bool(self.lRequireResidentKey) + } + + fn to_optional_bool(value: i32) -> Option { + match value { + x if x > 0 => Some(true), + x if x < 0 => Some(false), + _ => None, + } + } +} + +pub type WebAuthnCtapCborAuthenticatorOptions = WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS; + +/// 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(super) struct WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS { + /// Authenticator Name + pub(super) pwszAuthenticatorName: *const u16, + + /// Plugin COM ClsId + pub(super) rclsid: *const GUID, + + /// Plugin RPID + /// + /// Required for a nested WebAuthN call originating from a plugin. + pub(super) 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(super) 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(super) pwszDarkThemeLogoSvg: *const u16, + + pub(super) cbAuthenticatorInfo: u32, + /// CTAP CBOR-encoded authenticatorGetInfo output + pub(super) pbAuthenticatorInfo: *const u8, + + pub(super) cSupportedRpIds: u32, + /// List of supported RP IDs (Relying Party IDs). + /// + /// Should be null if all RPs are supported. + pub(super) pbSupportedRpIds: *const *const u16, +} + +pub struct PluginAddAuthenticatorOptions { + /// Authenticator Name + pub authenticator_name: String, + + /// Plugin COM ClsId + pub clsid: Clsid, + + /// Plugin RPID + /// + /// Required for a nested WebAuthN call originating from a plugin. + pub rp_id: Option, + + /// Plugin Authenticator Logo for the Light themes. + /// + /// String should contain a valid SVG 1.1 document. + pub light_theme_logo_svg: Option, + + // Plugin Authenticator Logo for the Dark themes. Bytes of SVG 1.1. + /// + /// String should contain a valid SVG 1.1 element. + pub dark_theme_logo_svg: Option, + + /// CTAP authenticatorGetInfo values + pub authenticator_info: AuthenticatorInfo, + + /// List of supported RP IDs (Relying Party IDs). + /// + /// Should be [None] if all RPs are supported. + pub supported_rp_ids: Option>, +} + +impl PluginAddAuthenticatorOptions { + pub(super) fn light_theme_logo_b64(&self) -> Option> { + self.light_theme_logo_svg + .as_ref() + .map(|svg| Self::encode_svg(&svg)) + } + + pub(super) fn dark_theme_logo_b64(&self) -> Option> { + self.dark_theme_logo_svg + .as_ref() + .map(|svg| Self::encode_svg(&svg)) + } + + fn encode_svg(svg: &str) -> Vec { + let logo_b64: String = STANDARD.encode(svg); + logo_b64.to_utf16() + } +} + +/// 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(super) struct WebAuthnPluginAddAuthenticatorResponse { + cbOpSignPubKey: u32, + pbOpSignPubKey: *mut u8, +} + +type WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE = WebAuthnPluginAddAuthenticatorResponse; + +/// Response received when registering a plugin +#[derive(Debug)] +pub struct PluginAddAuthenticatorResponse { + inner: NonNull, +} + +impl PluginAddAuthenticatorResponse { + pub fn plugin_operation_signing_key(&self) -> &[u8] { + // SAFETY: when constructed from Self::try_from_ptr(), the caller + // ensures that Windows created the pointer, which we trust to create + // valid responses. + unsafe { + std::slice::from_raw_parts( + self.inner.as_ref().pbOpSignPubKey, + // SAFETY: We only support 32-bit or 64-bit platforms, so u32 will always fit in + // usize. + self.inner.as_ref().cbOpSignPubKey as usize, + ) + } + } + + /// # Safety + /// When calling this function, the caller must ensure that the pointer was + /// initialized by a successful call to [webauthn_plugin_add_authenticator()]. + pub(super) unsafe fn try_from_ptr( + value: NonNull, + ) -> Self { + if value.as_ref().pbOpSignPubKey.is_null() {} + Self { inner: value } + } +} + +impl Drop for PluginAddAuthenticatorResponse { + fn drop(&mut self) { + unsafe { + // SAFETY: This should only fail if: + // - we cannot load the webauthn.dll, which we already have if we have constructed this + // type, or + // - we spelled the function wrong, which is a library error. + webauthn_plugin_free_add_authenticator_response(self.inner.as_mut()) + .expect("function to load properly"); + } + } +} + +webauthn_call!("WebAuthNPluginAddAuthenticator" as +fn webauthn_plugin_add_authenticator( + pPluginAddAuthenticatorOptions: *const WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS, + ppPluginAddAuthenticatorResponse: *mut *mut WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE +) -> HRESULT); + +webauthn_call!("WebAuthNPluginFreeAddAuthenticatorResponse" as +fn webauthn_plugin_free_add_authenticator_response( + pPluginAddAuthenticatorOptions: *mut WebAuthnPluginAddAuthenticatorResponse +) -> ()); + +// Credential syncing types + +/// Represents a credential. +/// Header File Name: _WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS +/// Header File Usage: WebAuthNPluginAuthenticatorAddCredentials, etc. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(super) struct WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS { + pub credential_id_byte_count: u32, + pub credential_id_pointer: *const u8, + pub rpid: *const u16, + pub rp_friendly_name: *const u16, + pub user_id_byte_count: u32, + pub user_id_pointer: *const u8, + pub user_name: *const u16, + pub user_display_name: *const u16, +} + +/// Credential metadata to sync to Windows Hello credential autofill list. +#[derive(Debug)] +pub struct PluginCredentialDetails { + /// Credential ID. + pub credential_id: CredentialId, + + /// Relying party ID. + pub rp_id: String, + + /// Relying party display name. + pub rp_friendly_name: Option, + + /// User handle. + pub user_id: UserId, + + /// User name. + /// + /// Corresponds to [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name) field of WebAuthn `PublicKeyCredentialUserEntity`. + pub user_name: String, + + /// User name. + /// + /// Corresponds to [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-displayname) field of WebAuthn `PublicKeyCredentialUserEntity`. + pub user_display_name: String, +} + +webauthn_call!("WebAuthNPluginAuthenticatorAddCredentials" as fn webauthn_plugin_authenticator_add_credentials( + rclsid: *const GUID, + cCredentialDetails: u32, + pCredentialDetails: *const WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS +) -> HRESULT); + +webauthn_call!("WebAuthNPluginAuthenticatorRemoveAllCredentials" as fn webauthn_plugin_authenticator_remove_all_credentials( + rclsid: *const GUID +) -> HRESULT); + +#[repr(C)] +#[derive(Debug)] +pub(super) 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(super) hwnd: HWND, + + /// The WebAuthn transaction id from the WEBAUTHN_PLUGIN_OPERATION_REQUEST + pub(super) rguidTransactionId: *const GUID, + + /// The username attached to the credential that is in use for this WebAuthn + /// operation. + pub(super) pwszUsername: *const u16, + + /// A text hint displayed on the Windows Hello prompt. + pub(super) pwszDisplayHint: *const u16, +} + +#[derive(Debug)] +pub struct PluginUserVerificationRequest { + /// Windows handle of the top-level window displayed by the plugin and + /// currently is in foreground as part of the ongoing WebAuthn operation. + pub window_handle: HWND, + + /// The WebAuthn transaction id from the WEBAUTHN_PLUGIN_OPERATION_REQUEST + pub transaction_id: GUID, + + /// The username attached to the credential that is in use for this WebAuthn + /// operation. + pub user_name: String, + + /// A text hint displayed on the Windows Hello prompt. + pub display_hint: Option, +} + +/// Response details from user verification. +pub struct PluginUserVerificationResponse { + pub transaction_id: GUID, + /// Bytes of the signature over the response. + pub signature: Vec, +} + +webauthn_call!("WebAuthNPluginPerformUserVerification" as fn webauthn_plugin_perform_user_verification( + pPluginUserVerification: *const WEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST, + pcbResponse: *mut u32, + ppbResponse: *mut *mut u8 +) -> HRESULT); + +webauthn_call!("WebAuthNPluginFreeUserVerificationResponse" as fn webauthn_plugin_free_user_verification_response( + pbResponse: *mut u8 +) -> ()); + +// Plugin Authenticator types + +/// 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(super) struct WEBAUTHN_PLUGIN_OPERATION_REQUEST { + /// Window handle to client that requesting a WebAuthn credential. + pub hWnd: HWND, + pub transactionId: GUID, + pub cbRequestSignature: u32, + /// Signature over request made with the signing key created during authenticator registration. + pub pbRequestSignature: *mut u8, + pub requestType: WebAuthnPluginRequestType, + pub cbEncodedRequest: u32, + pub pbEncodedRequest: *const u8, +} + +/// 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(crate) struct WEBAUTHN_PLUGIN_OPERATION_RESPONSE { + pub cbEncodedResponse: u32, + pub pbEncodedResponse: *mut u8, +} + +/// Plugin request type enum as defined in the IDL +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum WebAuthnPluginRequestType { + // 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, +} + +// MakeCredential types + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(super) 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, + 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, + /// SHA-256 hash of the request. + /// + /// Can be used to verify the request later, for example in associated + /// prompts for user verification. + pub request_hash: Vec, +} + +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) -> RpEntityInformation<'_> { + let ptr = self.as_ref().pRpInformation; + // SAFETY: When this is constructed using Self::try_from_ptr(), the caller must ensure that + // pRpInformation is valid. + unsafe { RpEntityInformation::new(ptr.as_ref().expect("pRpInformation to be non-null")) } + } + + pub fn user_information(&self) -> UserEntityInformation<'_> { + // SAFETY: When this is constructed using Self::try_from_ptr(), the caller must ensure that + // pUserInformation is valid. + let ptr = self.as_ref().pUserInformation; + assert!(!ptr.is_null()); + unsafe { + UserEntityInformation::new(ptr.as_ref().expect("pUserInformation to be non-null")) + } + } + + pub fn pub_key_cred_params(&self) -> impl Iterator { + // SAFETY: When this is constructed from Self::try_from_ptr(), the Windows decode API + // constructs valid pointers. + unsafe { self.as_ref().WebAuthNCredentialParameters.iter() } + } + + pub fn exclude_credentials(&self) -> impl Iterator> { + // SAFETY: When this is constructed from Self::try_from_ptr(), the Windows decode API + // constructs valid pointers. + unsafe { self.as_ref().CredentialList.iter() } + } + + /// 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 { + let ptr = self.as_ref().pAuthenticatorOptions; + if ptr.is_null() { + return None; + } + unsafe { Some(*ptr) } + } + + /// # Safety + /// When calling this method, callers must ensure: + /// - `ptr` must be convertible to a reference. + /// - `ptr` must have been allocated by Windows COM + /// - pbEncodedRequest must be non-null and have the length specified in cbEncodedRequest. + /// - pbRequestSignature must be non-null and have the length specified in cbRequestSignature. + pub(super) unsafe fn try_from_ptr( + ptr: NonNull, + ) -> Result { + let request = ptr.as_ref(); + if !matches!(request.requestType, WebAuthnPluginRequestType::CTAP2_CBOR) { + return Err(WinWebAuthnError::new( + ErrorKind::Serialization, + "Unknown plugin operation request type", + )); + } + let request_slice = + std::slice::from_raw_parts(request.pbEncodedRequest, request.cbEncodedRequest as usize); + let request_hash = crypto::hash_sha256(request_slice).map_err(|err| { + WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "failed to hash request", err) + })?; + 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: std::slice::from_raw_parts( + request.pbRequestSignature, + request.cbRequestSignature as usize, + ) + .to_vec(), + request_hash, + }) + } +} + +impl AsRef 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() { + // SAFETY: the caller is responsible for ensuring that this pointer + // is allocated with an allocator corresponding to this free + // function. + unsafe { + // leak memory if we cannot find the free function + _ = webauthn_free_decoded_make_credential_request( + self.inner as *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST, + ); + } + } + } +} + +// 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 { + /// Attestation format type + pub format_type: String, // PCWSTR + + /// Authenticator data that was created for this credential. + pub authenticator_data: Vec, + + ///Encoded CBOR attestation information + pub attestation_statement: Option>, + + // 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>, + + /// The CredentialId bytes extracted from the Authenticator Data. + /// Used by Edge to return to the RP. + pub credential_id: Option>, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 + /// Since VERSION 2 + pub extensions: Option>, + + // + // 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>, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 + pub hmac_secret: Option, + + /// 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>, + + /// UTF-8 encoded JSON serialization of the client data. + pub client_data_json: Option>, + + /// UTF-8 encoded JSON serialization of the RegistrationResponse. + pub registration_response_json: Option>, +} + +impl PluginMakeCredentialResponse { + pub fn to_ctap_response(self) -> Result, WinWebAuthnError> { + let attestation = self.try_into()?; + let mut response_len = 0; + let mut response_ptr = std::ptr::null_mut(); + // SAFETY: we construct valid input and check the OS error code before using the returned + // value. + unsafe { + 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 = std::slice::from_raw_parts(response_ptr, response_len as usize).to_vec(); + // Ideally, we wouldn't have Windows allocate this in COM, and then + // we reallocate locally and then reallocate for COM. + CoTaskMemFree(Some(response_ptr.cast())); + + Ok(response) + } + } +} + +impl TryFrom for WEBAUTHN_CREDENTIAL_ATTESTATION { + type Error = WinWebAuthnError; + + fn try_from(value: PluginMakeCredentialResponse) -> Result { + // 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); + +// GetAssertion types + +pub(super) struct WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY { + /// Version of this structure, to allow for modifications in the future. + pub _dwVersion: u32, + + /// Key type + pub _lKty: i32, + + /// Hash Algorithm: ES256, ES384, ES512 + pub _lAlg: i32, + + /// Curve + pub _lCrv: i32, + + /// Size of "x" (X Coordinate) + pub _cbX: u32, + + /// "x" (X Coordinate) data. Big Endian. + pub _pbX: *const u8, + + /// Size of "y" (Y Coordinate) + pub _cbY: u32, + + /// "y" (Y Coordinate) data. Big Endian. + pub _pbY: *const u8, +} + +pub(super) struct WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION { + /// Version of this structure, to allow for modifications in the future. + pub _dwVersion: u32, + + // Platform's key agreement public key + pub _pKeyAgreement: *const WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY, + + pub _cbEncryptedSalt: u32, + pub _pbEncryptedSalt: *const u8, + + pub _cbSaltAuth: u32, + pub _pbSaltAuth: *const u8, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(super) struct WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST { + pub dwVersion: u32, + pub pwszRpId: *const u16, // PCWSTR + pub cbRpId: u32, + pub pbRpId: *const u8, + pub cbClientDataHash: u32, + pub pbClientDataHash: *const u8, + pub CredentialList: WEBAUTHN_CREDENTIAL_LIST, + pub cbCborExtensionsMap: u32, + pub pbCborExtensionsMap: *const u8, + pub pAuthenticatorOptions: *const WebAuthnCtapCborAuthenticatorOptions, + + // Pin Auth (Optional) + /// Zero length PinAuth is included in the request + pub fEmptyPinAuth: BOOL, + pub cbPinAuth: u32, + pub pbPinAuth: *const u8, + + // HMAC Salt Extension (Optional) + pub pHmacSaltExtension: *const WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION, + + // PRF Extension + pub cbHmacSecretSaltValues: u32, + pub pbHmacSecretSaltValues: *const u8, + + pub dwPinProtocol: u32, + + //"credBlob": true extension + pub lCredBlobExt: i32, + + //"largeBlobKey": true extension + pub lLargeBlobKeyExt: i32, + + //"largeBlob" extension + pub dwCredLargeBlobOperation: u32, + pub cbCredLargeBlobCompressed: u32, + pub pbCredLargeBlobCompressed: *const u8, + pub dwCredLargeBlobOriginalSize: u32, + + // "json" extension. Nonzero if present + pub cbJsonExt: u32, + pub pbJsonExt: *const u8, +} + +#[derive(Debug)] +pub struct PluginGetAssertionRequest { + inner: *const WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, + pub window_handle: HWND, + pub transaction_id: GUID, + pub request_signature: Vec, + pub request_hash: Vec, +} + +impl PluginGetAssertionRequest { + pub fn rp_id(&self) -> &str { + unsafe { + let request = &*self.inner; + let slice = std::slice::from_raw_parts(request.pbRpId, request.cbRpId as usize); + str::from_utf8_unchecked(slice) + } + } + + pub fn client_data_hash(&self) -> &[u8] { + let inner = self.as_ref(); + // SAFETY: Verified by Windows + unsafe { + std::slice::from_raw_parts(inner.pbClientDataHash, inner.cbClientDataHash as usize) + } + } + + pub fn allow_credentials(&self) -> impl Iterator> { + // SAFETY: When this is constructed from Self::try_from_ptr(), the Windows decode API + // constructs valid pointers. + unsafe { self.as_ref().CredentialList.iter() } + } + + // TODO: Support extensions + // pub fn extensions(&self) -> Options {} + + pub fn authenticator_options(&self) -> Option { + let ptr = self.as_ref().pAuthenticatorOptions; + if ptr.is_null() { + return None; + } + unsafe { Some(*ptr) } + } + + /// # Safety + /// When calling this method, callers must ensure: + /// - `ptr` must be convertible to a reference. + /// - pbEncodedRequest must be non-null and have the length specified in cbEncodedRequest. + /// - pbEncodedRequest must point to a valid byte string of a CTAP GetAssertion request. + pub(super) unsafe fn try_from_ptr( + value: NonNull, + ) -> Result { + // SAFETY: caller must ensure that ptr is convertible to a reference. + let request = value.as_ref(); + if !matches!(request.requestType, WebAuthnPluginRequestType::CTAP2_CBOR) { + return Err(WinWebAuthnError::new( + ErrorKind::Serialization, + "Unknown plugin operation request type", + )); + } + // SAFETY: Caller must ensure that the pointer and count is valid. + let request_slice = + std::slice::from_raw_parts(request.pbEncodedRequest, request.cbEncodedRequest as usize); + let request_hash = crypto::hash_sha256(request_slice).map_err(|err| { + WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "failed to hash request", err) + })?; + let mut assertion_request: *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST = + std::ptr::null_mut(); + webauthn_decode_get_assertion_request( + request.cbEncodedRequest, + request.pbEncodedRequest, + &mut assertion_request, + )? + .ok() + .map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Failed to decode get assertion request", + err, + ) + })?; + Ok(Self { + // SAFETY: Windows should return a valid decoded assertion request struct. + inner: assertion_request as *const WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, + window_handle: request.hWnd, + transaction_id: request.transactionId, + // SAFETY: Caller is expected to ensure that signature buffer parameters are correct. + request_signature: std::slice::from_raw_parts( + request.pbRequestSignature, + request.cbRequestSignature as usize, + ) + .to_vec(), + request_hash, + }) + } +} + +impl AsRef for PluginGetAssertionRequest { + fn as_ref(&self) -> &WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST { + unsafe { &*self.inner } + } +} + +impl Drop for PluginGetAssertionRequest { + fn drop(&mut self) { + if !self.inner.is_null() { + // SAFETY: the caller is responsible for ensuring that this pointer + // is allocated with an allocator corresponding to this free + // function. + unsafe { + // leak memory if we cannot find the free function + _ = webauthn_free_decoded_get_assertion_request( + self.inner as *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, + ); + } + } + } +} + +// Windows API function signatures for decoding get assertion requests +webauthn_call!("WebAuthNDecodeGetAssertionRequest" as fn webauthn_decode_get_assertion_request( + cbEncoded: u32, + pbEncoded: *const u8, + ppGetAssertionRequest: *mut *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST +) -> HRESULT); + +webauthn_call!("WebAuthNFreeDecodedGetAssertionRequest" as fn webauthn_free_decoded_get_assertion_request( + pGetAssertionRequest: *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST +) -> ()); + +// CancelOperation Types +pub(super) struct WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST { + transactionId: GUID, + cbRequestSignature: u32, + pbRequestSignature: *const u8, +} + +pub struct PluginCancelOperationRequest { + inner: NonNull, +} + +impl PluginCancelOperationRequest { + /// Request transaction ID + pub fn transaction_id(&self) -> GUID { + self.as_ref().transactionId + } + + /// Request signature. + pub fn request_signature(&self) -> &[u8] { + unsafe { + std::slice::from_raw_parts( + self.as_ref().pbRequestSignature, + self.as_ref().cbRequestSignature as usize, + ) + } + } +} + +impl AsRef for PluginCancelOperationRequest { + fn as_ref(&self) -> &WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST { + // SAFETY: Pointer is received from Windows so we assume it is correct. + unsafe { self.inner.as_ref() } + } +} + +#[doc(hidden)] +impl From> for PluginCancelOperationRequest { + fn from(value: NonNull) -> Self { + Self { inner: value } + } +} + +/// Plugin lock status enum as defined in the IDL +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum PluginLockStatus { + PluginLocked = 0, + PluginUnlocked = 1, +} diff --git a/apps/desktop/desktop_native/win_webauthn/src/types/mod.rs b/apps/desktop/desktop_native/win_webauthn/src/types/mod.rs new file mode 100644 index 00000000000..1fc9c3b29fd --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/src/types/mod.rs @@ -0,0 +1,793 @@ +//! Types and functions defined in the Windows WebAuthn API. + +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +use std::{collections::HashSet, marker::PhantomData, num::NonZeroU32, ptr::NonNull}; + +use ciborium::Value; +use windows_core::PCWSTR; + +use crate::{util::ArrayPointerIterator, ErrorKind, WinWebAuthnError}; + +/// List of its supported protocol versions and extensions, its AAGUID, and +/// other aspects of its overall capabilities. +pub struct AuthenticatorInfo { + /// List of supported versions. + pub versions: HashSet, + + /// The claimed AAGUID. 16 bytes in length and encoded the same as + /// MakeCredential AuthenticatorData, as specified in [WebAuthn](https://www.w3.org/TR/webauthn-3/#aaguid). + /// + /// Note: even though the name has "guid" in it, this is actually an RFC 4122 + /// UUID, which is deserialized differently than a Windows GUID. + pub aaguid: Uuid, + + /// List of supported options. + pub options: Option>, + + /// List of supported transports. Values are taken from the + /// [AuthenticatorTransport enum in WebAuthn][authenticator-transport]. + /// The list MUST NOT include duplicate values nor be empty if present. + /// Platforms MUST tolerate unknown values. + /// [authenticator-transport]: https://www.w3.org/TR/webauthn-3/#enum-transport + pub transports: Option>, + + /// List of supported algorithms for credential generation, as specified in + /// [WebAuthn]. The array is ordered from most preferred to least preferred + /// and MUST NOT include duplicate entries nor be empty if present. + /// PublicKeyCredentialParameters' algorithm identifiers are values that + /// SHOULD be registered in the IANA COSE Algorithms registry + /// [IANA-COSE-ALGS-REG]. + pub algorithms: Option>, +} + +impl AuthenticatorInfo { + pub fn as_ctap_bytes(&self) -> Result, super::WinWebAuthnError> { + // Create the authenticator info map according to CTAP2 spec + // Using Vec<(Value, Value)> because that's what ciborium::Value::Map expects + let mut authenticator_info = Vec::new(); + + // 1: versions - Array of supported FIDO versions + let versions = self + .versions + .iter() + .map(|v| Value::Text(v.into())) + .collect(); + authenticator_info.push((Value::Integer(1.into()), Value::Array(versions))); + + // 2: extensions - Array of supported extensions (empty for now) + authenticator_info.push((Value::Integer(2.into()), Value::Array(vec![]))); + + // 3: aaguid - 16-byte AAGUID + authenticator_info.push(( + Value::Integer(3.into()), + Value::Bytes(self.aaguid.0.to_vec()), + )); + + // 4: options - Map of supported options + if let Some(options) = &self.options { + let options = options + .iter() + .map(|o| (Value::Text(o.into()), Value::Bool(true))) + .collect(); + authenticator_info.push((Value::Integer(4.into()), Value::Map(options))); + } + + // 9: transports - Array of supported transports + if let Some(transports) = &self.transports { + let transports = transports.iter().map(|t| Value::Text(t.clone())).collect(); + authenticator_info.push((Value::Integer(9.into()), Value::Array(transports))); + } + + // 10: algorithms - Array of supported algorithms + if let Some(algorithms) = &self.algorithms { + let algorithms: Vec = algorithms + .iter() + .map(|a| { + Value::Map(vec![ + (Value::Text("alg".to_string()), Value::Integer(a.alg.into())), + (Value::Text("type".to_string()), Value::Text(a.typ.clone())), + ]) + }) + .collect(); + authenticator_info.push((Value::Integer(10.into()), Value::Array(algorithms))); + } + + // Encode to CBOR + let mut buffer = Vec::new(); + ciborium::ser::into_writer(&Value::Map(authenticator_info), &mut buffer).map_err(|e| { + WinWebAuthnError::with_cause( + ErrorKind::Serialization, + "Failed to serialize authenticator info into CBOR", + e, + ) + })?; + + Ok(buffer) + } +} + +// A UUID is not the same as a Windows GUID +/// An RFC4122 UUID. +pub struct Uuid([u8; 16]); + +impl TryFrom<&str> for Uuid { + type Error = WinWebAuthnError; + + fn try_from(value: &str) -> Result { + let uuid_clean = value.replace("-", "").replace("{", "").replace("}", ""); + if uuid_clean.len() != 32 { + return Err(WinWebAuthnError::new( + ErrorKind::Serialization, + "Invalid UUID format", + )); + } + + let bytes = uuid_clean + .chars() + .collect::>() + .chunks(2) + .map(|chunk| { + let hex_str: String = chunk.iter().collect(); + u8::from_str_radix(&hex_str, 16).map_err(|_| { + WinWebAuthnError::new( + ErrorKind::Serialization, + &format!("Invalid hex character in UUID: {}", hex_str), + ) + }) + }) + .collect::, WinWebAuthnError>>()?; + + // SAFETY: We already checked the length of the string before, so this should result in the + // correct number of bytes. + let b: [u8; 16] = bytes.try_into().expect("16 bytes to be parsed"); + Ok(Uuid(b)) + } +} + +#[derive(Hash, Eq, PartialEq)] +pub enum CtapVersion { + Fido2_0, + Fido2_1, +} + +pub struct PublicKeyCredentialParameters { + pub alg: i32, + pub typ: String, +} + +impl From<&CtapVersion> for String { + fn from(value: &CtapVersion) -> Self { + match value { + CtapVersion::Fido2_0 => "FIDO_2_0", + CtapVersion::Fido2_1 => "FIDO_2_1", + } + .to_string() + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(crate) 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. + dwVersion: u32, + + /// Identifier for the RP. This field is required. + pwszId: NonNull, // 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. + 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 +} + +/// A wrapper around WEBAUTHN_RP_ENTITY_INFORMATION. +pub struct RpEntityInformation<'a> { + ptr: NonNull, + _phantom: PhantomData<&'a WEBAUTHN_RP_ENTITY_INFORMATION>, +} + +impl RpEntityInformation<'_> { + /// # Safety + /// When calling this method, you must ensure that + /// - the pointer is convertible to a reference, + /// - pwszId points to a valid null-terminated UTF-16 string. + /// - pwszName is null or points to a valid null-terminated UTF-16 string. + pub(crate) unsafe fn new(ptr: &WEBAUTHN_RP_ENTITY_INFORMATION) -> Self { + Self { + ptr: NonNull::from_ref(ptr), + _phantom: PhantomData, + } + } + + /// Identifier for the RP. + pub fn id(&self) -> String { + // SAFETY: If the caller upholds the constraints of the struct in + // Self::new(), then pwszId is valid UTF-16. + unsafe { + assert!(self.ptr.is_aligned()); + PCWSTR(self.ptr.as_ref().pwszId.as_ptr()) + .to_string() + .expect("valid null-terminated UTF-16 string") + } + } + + /// Contains the friendly name of the Relying Party, such as "Acme + /// Corporation", "Widgets Inc" or "Awesome Site". + pub fn name(&self) -> Option { + // SAFETY: If the caller upholds the constraints of the struct in + // Self::new(), then pwszName is either null or valid UTF-16. + unsafe { + if self.ptr.as_ref().pwszName.is_null() { + return None; + } + let s = PCWSTR(self.ptr.as_ref().pwszName) + .to_string() + .expect("null-terminated UTF-16 string or null"); + + // WebAuthn Level 3 deprecates the use of the `name` field, so verify whether this is + // empty or not. + if s.is_empty() { + None + } else { + Some(s) + } + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(crate) 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 dwVersion: u32, + + /// Identifier for the User. This field is required. + pub cbId: NonZeroU32, // DWORD + pub pbId: NonNull, // PBYTE + + /// Contains a detailed name for this account, such as "john.p.smith@example.com". + pub pwszName: NonNull, // 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 pwszIcon: Option>, // PCWSTR + + /// Contains the friendly name associated with the user account by the Relying Party, such as + /// "John P. Smith". + pub pwszDisplayName: NonNull, // PCWSTR +} + +pub struct UserEntityInformation<'a> { + ptr: NonNull, + _phantom: PhantomData<&'a WEBAUTHN_USER_ENTITY_INFORMATION>, +} + +impl UserEntityInformation<'_> { + /// # Safety + /// When calling this method, the caller must ensure that + /// - `ptr` is convertible to a reference, + /// - pbId is non-null and points to a valid memory allocation with length of cbId. + /// - pwszName is non-null and points to a valid null-terminated UTF-16 string. + /// - pwszDisplayName is non-null and points to a valid null-terminated UTF-16 string. + pub(crate) unsafe fn new(ptr: &WEBAUTHN_USER_ENTITY_INFORMATION) -> Self { + Self { + ptr: NonNull::from_ref(ptr), + _phantom: PhantomData, + } + } + + /// User handle. + pub fn id(&self) -> &[u8] { + // SAFETY: If the caller upholds the constraints on Self::new(), then pbId + // is non-null and points to valid memory. + unsafe { + let ptr = self.ptr.as_ref(); + std::slice::from_raw_parts(ptr.pbId.as_ptr(), ptr.cbId.get() as usize) + } + } + + /// User name. + pub fn name(&self) -> String { + // SAFETY: If the caller upholds the constraints on Self::new(), then ID + // is non-null and points to valid memory. + unsafe { + let ptr = self.ptr.as_ref(); + PCWSTR(ptr.pwszName.as_ptr()) + .to_string() + .expect("valid UTF-16 string") + } + } + + /// User display name. + pub fn display_name(&self) -> String { + unsafe { + let ptr = self.ptr.as_ref(); + + PCWSTR(ptr.pwszDisplayName.as_ptr()) + .to_string() + .expect("valid UTF-16 string") + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_COSE_CREDENTIAL_PARAMETER { + dwVersion: u32, + pwszCredentialType: NonNull, // LPCWSTR + lAlg: i32, // LONG - COSE algorithm identifier +} + +impl WEBAUTHN_COSE_CREDENTIAL_PARAMETER { + pub fn credential_type(&self) -> Result { + unsafe { + PCWSTR(self.pwszCredentialType.as_ptr()) + .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(crate) struct WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { + cCredentialParameters: u32, + pCredentialParameters: *const WEBAUTHN_COSE_CREDENTIAL_PARAMETER, +} + +impl WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { + /// # Safety + /// The caller must ensure that the pCredentialParameters is either null or + /// marks the beginning of a list whose count is represented accurately by + /// cCredentialParameters. + pub unsafe fn iter(&self) -> ArrayPointerIterator<'_, WEBAUTHN_COSE_CREDENTIAL_PARAMETER> { + ArrayPointerIterator::new( + self.pCredentialParameters, + self.cCredentialParameters as usize, + ) + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(crate) struct WEBAUTHN_CREDENTIAL_ATTESTATION { + /// Version of this structure, to allow for modifications in the future. + pub(crate) dwVersion: u32, + + /// Attestation format type + pub(crate) pwszFormatType: *const u16, // PCWSTR + + /// Size of cbAuthenticatorData. + pub(crate) cbAuthenticatorData: u32, + /// Authenticator data that was created for this credential. + //_Field_size_bytes_(cbAuthenticatorData) + pub(crate) pbAuthenticatorData: *const u8, + + /// Size of CBOR encoded attestation information + /// 0 => encoded as CBOR null value. + pub(crate) cbAttestation: u32, + ///Encoded CBOR attestation information + // _Field_size_bytes_(cbAttestation) + pub(crate) pbAttestation: *const u8, + + pub(crate) 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(crate) pvAttestationDecode: *const u8, + + /// The CBOR encoded Attestation Object to be returned to the RP. + pub(crate) cbAttestationObject: u32, + // _Field_size_bytes_(cbAttestationObject) + pub(crate) pbAttestationObject: *const u8, + + /// The CredentialId bytes extracted from the Authenticator Data. + /// Used by Edge to return to the RP. + pub(crate) cbCredentialId: u32, + // _Field_size_bytes_(cbCredentialId) + pub(crate) pbCredentialId: *const u8, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 + /// Since VERSION 2 + pub(crate) 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(crate) dwUsedTransport: u32, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 + pub(crate) bEpAtt: bool, + pub(crate) bLargeBlobSupported: bool, + pub(crate) bResidentKey: bool, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 + pub(crate) bPrfEnabled: bool, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 + pub(crate) cbUnsignedExtensionOutputs: u32, + // _Field_size_bytes_(cbUnsignedExtensionOutputs) + pub(crate) pbUnsignedExtensionOutputs: *const u8, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 + pub(crate) pHmacSecret: *const WEBAUTHN_HMAC_SECRET_SALT, + + // ThirdPartyPayment Credential or not. + pub(crate) 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(crate) dwTransports: u32, + + // UTF-8 encoded JSON serialization of the client data. + pub(crate) cbClientDataJSON: u32, + // _Field_size_bytes_(cbClientDataJSON) + pub(crate) pbClientDataJSON: *const u8, + + // UTF-8 encoded JSON serialization of the RegistrationResponse. + pub(crate) cbRegistrationResponseJSON: u32, + // _Field_size_bytes_(cbRegistrationResponseJSON) + pub(crate) pbRegistrationResponseJSON: *const u8, +} + +pub(crate) 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, + _second: Option>, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(crate) 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)] +pub(crate) struct WEBAUTHN_EXTENSIONS { + pub(crate) cExtensions: u32, + // _Field_size_(cExtensions) + pub(crate) pExtensions: *const WEBAUTHN_EXTENSION, +} + +#[derive(Debug)] +pub struct UserId(Vec); + +impl UserId { + pub fn len(&self) -> u8 { + // SAFETY: User ID guaranteed to be <= 64 bytes + self.0.len() as u8 + } +} +impl AsRef<[u8]> for UserId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl TryFrom> for UserId { + type Error = WinWebAuthnError; + + fn try_from(value: Vec) -> Result { + if value.len() > 64 { + return Err(WinWebAuthnError::new( + ErrorKind::Serialization, + &format!( + "User ID exceeds maximum length of 64, received {}", + value.len() + ), + )); + } + Ok(UserId(value)) + } +} + +#[derive(Debug)] +pub struct CredentialId(Vec); + +impl CredentialId { + pub fn len(&self) -> u16 { + // SAFETY: CredentialId guaranteed to be < 1024 bytes + self.0.len() as u16 + } +} + +impl AsRef<[u8]> for CredentialId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl TryFrom> for CredentialId { + type Error = WinWebAuthnError; + + fn try_from(value: Vec) -> Result { + if value.len() > 1023 { + return Err(WinWebAuthnError::new( + ErrorKind::Serialization, + &format!( + "Credential ID exceeds maximum length of 1023, received {}", + value.len() + ), + )); + } + Ok(CredentialId(value)) + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(crate) struct WEBAUTHN_CREDENTIAL_EX { + dwVersion: u32, + cbId: u32, + pbId: *const u8, + pwszCredentialType: *const u16, // LPCWSTR + dwTransports: u32, +} + +pub struct CredentialEx<'a> { + inner: &'a WEBAUTHN_CREDENTIAL_EX, +} + +impl CredentialEx<'_> { + pub fn credential_id(&self) -> Option<&[u8]> { + if self.inner.cbId == 0 || self.inner.pbId.is_null() { + None + } else { + unsafe { + Some(std::slice::from_raw_parts( + self.inner.pbId, + self.inner.cbId as usize, + )) + } + } + } + + pub fn credential_type(&self) -> Result { + if self.inner.pwszCredentialType.is_null() { + return Err(WinWebAuthnError::new( + ErrorKind::WindowsInternal, + "Received invalid credential ID", + )); + } + unsafe { + PCWSTR(self.inner.pwszCredentialType) + .to_string() + .map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Invalid credential ID", + err, + ) + }) + } + } + + pub fn transports(&self) -> Vec { + let mut transports = Vec::new(); + let mut t = self.inner.dwTransports; + if t == 0 { + return transports; + }; + const TRANSPORTS: [CtapTransport; 7] = [ + CtapTransport::Usb, + CtapTransport::Nfc, + CtapTransport::Ble, + CtapTransport::Test, + CtapTransport::Internal, + CtapTransport::Hybrid, + CtapTransport::SmartCard, + ]; + for a in TRANSPORTS { + if t == 0 { + break; + } + if a as u32 & t > 0 { + transports.push(a.clone()); + t -= a as u32; + } + } + transports + } +} + +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum CtapTransport { + Usb = 1, + Nfc = 2, + Ble = 4, + Test = 8, + Internal = 0x10, + Hybrid = 0x20, + SmartCard = 0x40, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(crate) struct WEBAUTHN_CREDENTIAL_LIST { + pub cCredentials: u32, + pub ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX, +} + +impl WEBAUTHN_CREDENTIAL_LIST { + pub unsafe 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 { + inner: ArrayPointerIterator::new(self.ppCredentials, self.cCredentials as usize), + } + } + } +} + +pub struct CredentialListIterator<'a> { + inner: ArrayPointerIterator<'a, *const WEBAUTHN_CREDENTIAL_EX>, +} + +impl<'a> Iterator for CredentialListIterator<'a> { + type Item = CredentialEx<'a>; + + fn next(&mut self) -> Option { + 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().map(|inner| CredentialEx { inner }) } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const AAGUID: &str = "d548826e-79b4-db40-a3d8-11116f7e8349"; + #[test] + fn test_generate_cbor_authenticator_info() { + let aaguid = Uuid::try_from(AAGUID).unwrap(); + let authenticator_info = AuthenticatorInfo { + versions: HashSet::from([CtapVersion::Fido2_0, CtapVersion::Fido2_1]), + aaguid: aaguid, + options: Some(HashSet::from([ + "rk".to_string(), + "up".to_string(), + "uv".to_string(), + ])), + transports: Some(HashSet::from([ + "internal".to_string(), + "hybrid".to_string(), + ])), + algorithms: Some(vec![PublicKeyCredentialParameters { + alg: -7, + typ: "public-key".to_string(), + }]), + }; + let result = authenticator_info.as_ctap_bytes(); + assert!(result.is_ok(), "CBOR generation should succeed"); + + let cbor_bytes = result.unwrap(); + assert!(!cbor_bytes.is_empty(), "CBOR bytes should not be empty"); + + // Verify the CBOR can be decoded back + let decoded: Result = ciborium::de::from_reader(&cbor_bytes[..]); + assert!(decoded.is_ok(), "Generated CBOR should be valid"); + + // Verify it's a map with expected keys + if let Value::Map(map) = decoded.unwrap() { + assert!( + map.iter().any(|(k, _)| k == &Value::Integer(1.into())), + "Should contain versions (key 1)" + ); + assert!( + map.iter().any(|(k, _)| k == &Value::Integer(2.into())), + "Should contain extensions (key 2)" + ); + assert!( + map.iter().any(|(k, _)| k == &Value::Integer(3.into())), + "Should contain aaguid (key 3)" + ); + assert!( + map.iter().any(|(k, _)| k == &Value::Integer(4.into())), + "Should contain options (key 4)" + ); + assert!( + map.iter().any(|(k, _)| k == &Value::Integer(9.into())), + "Should contain transports (key 9)" + ); + assert!( + map.iter().any(|(k, _)| k == &Value::Integer(10.into())), + "Should contain algorithms (key 10)" + ); + } else { + panic!("CBOR should decode to a map"); + } + + // Print the generated CBOR for verification + println!("Generated CBOR hex: {}", hex::encode(&cbor_bytes)); + } + + #[test] + fn test_aaguid_parsing() { + let result = Uuid::try_from(AAGUID); + assert!(result.is_ok(), "AAGUID parsing should succeed"); + + let aaguid_bytes = result.unwrap(); + assert_eq!(aaguid_bytes.0.len(), 16, "AAGUID should be 16 bytes"); + assert_eq!(aaguid_bytes.0[0], 0xd5, "First byte should be 0xd5"); + assert_eq!(aaguid_bytes.0[1], 0x48, "Second byte should be 0x48"); + + // Verify full expected AAGUID + let expected_hex = "d548826e79b4db40a3d811116f7e8349"; + let expected_bytes = hex::decode(expected_hex).unwrap(); + assert_eq!( + &aaguid_bytes.0[..], + expected_bytes, + "AAGUID should match expected value" + ); + } +} diff --git a/apps/desktop/desktop_native/win_webauthn/src/util.rs b/apps/desktop/desktop_native/win_webauthn/src/util.rs new file mode 100644 index 00000000000..f8b1fa9fb2d --- /dev/null +++ b/apps/desktop/desktop_native/win_webauthn/src/util.rs @@ -0,0 +1,99 @@ +use windows::{ + core::s, + Win32::{ + Foundation::{FreeLibrary, HMODULE}, + System::LibraryLoader::{LoadLibraryExA, LOAD_LIBRARY_SEARCH_SYSTEM32}, + }, +}; + +use crate::{ErrorKind, WinWebAuthnError}; + +macro_rules! webauthn_call { + ($symbol:literal as fn $fn_name:ident($($arg:ident: $arg_type:ty),+) -> $result_type:ty) => ( + pub(super) unsafe fn $fn_name($($arg: $arg_type),*) -> Result<$result_type, crate::WinWebAuthnError> { + let library = crate::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),*) + }; + crate::util::free_webauthn_lib(library)?; + Ok(response) + } + ) +} + +pub(crate) use webauthn_call; + +pub(super) fn load_webauthn_lib() -> Result { + unsafe { + LoadLibraryExA(s!("webauthn.dll"), None, LOAD_LIBRARY_SEARCH_SYSTEM32).map_err(|err| { + WinWebAuthnError::with_cause(ErrorKind::DllLoad, "Failed to load webauthn.dll", err) + }) + } +} + +pub(super) fn free_webauthn_lib(library: HMODULE) -> Result<(), WinWebAuthnError> { + unsafe { + FreeLibrary(library).map_err(|err| { + WinWebAuthnError::with_cause( + ErrorKind::WindowsInternal, + "Failed to free webauthn.dll library", + err, + ) + }) + } +} +pub(super) trait WindowsString { + fn to_utf16(&self) -> Vec; +} + +impl WindowsString for str { + fn to_utf16(&self) -> Vec { + // null-terminated UTF-16 + self.encode_utf16().chain(std::iter::once(0)).collect() + } +} + +pub struct ArrayPointerIterator<'a, T> { + pos: usize, + list: Option<&'a [T]>, +} + +impl ArrayPointerIterator<'_, T> { + /// # Safety + /// 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 { + let current = self.list?.get(self.pos); + self.pos += 1; + current + } +}