mirror of
https://github.com/bitwarden/browser
synced 2026-01-30 16:23:53 +00:00
Add win_webauthn lib
This commit is contained in:
66
apps/desktop/desktop_native/Cargo.lock
generated
66
apps/desktop/desktop_native/Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
25
apps/desktop/desktop_native/win_webauthn/Cargo.toml
Normal file
25
apps/desktop/desktop_native/win_webauthn/Cargo.toml
Normal file
@@ -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 }
|
||||
@@ -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 <rpcndr.h> version is high enough to compile this file*/
|
||||
#ifndef __REQUIRED_RPCNDR_H_VERSION__
|
||||
#define __REQUIRED_RPCNDR_H_VERSION__ 501
|
||||
#endif
|
||||
|
||||
/* verify that the <rpcsal.h> 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 <rpcndr.h>
|
||||
#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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
1381
apps/desktop/desktop_native/win_webauthn/include/webauthn.h
Normal file
1381
apps/desktop/desktop_native/win_webauthn/include/webauthn.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,588 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <winapifamily.h>
|
||||
|
||||
#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 <guiddef.h>
|
||||
#undef INITGUID
|
||||
#else
|
||||
#include <guiddef.h>
|
||||
#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
|
||||
|
||||
|
||||
72
apps/desktop/desktop_native/win_webauthn/src/lib.rs
Normal file
72
apps/desktop/desktop_native/win_webauthn/src/lib.rs
Normal file
@@ -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<String>,
|
||||
cause: Option<Box<dyn std::error::Error>>,
|
||||
}
|
||||
|
||||
impl WinWebAuthnError {
|
||||
pub(crate) fn new(kind: ErrorKind, description: &str) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
description: Some(description.to_string()),
|
||||
cause: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_cause<E: std::error::Error + 'static>(
|
||||
kind: ErrorKind,
|
||||
description: &str,
|
||||
cause: E,
|
||||
) -> Self {
|
||||
let cause: Box<dyn std::error::Error> = Box::new(cause);
|
||||
Self {
|
||||
kind,
|
||||
description: Some(description.to_string()),
|
||||
cause: Some(cause),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ErrorKind {
|
||||
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 {}
|
||||
480
apps/desktop/desktop_native/win_webauthn/src/plugin/com.rs
Normal file
480
apps/desktop/desktop_native/win_webauthn/src/plugin/com.rs
Normal file
@@ -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<dyn PluginAuthenticator + Send + Sync>)> = OnceLock::new();
|
||||
static SHUTDOWN: OnceLock<bool> = OnceLock::new();
|
||||
|
||||
#[implement(IClassFactory)]
|
||||
pub struct Factory;
|
||||
|
||||
impl IClassFactory_Impl for Factory_Impl {
|
||||
fn CreateInstance(
|
||||
&self,
|
||||
_outer: windows::core::Ref<IUnknown>,
|
||||
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<dyn PluginAuthenticator + Send + Sync>,
|
||||
}
|
||||
|
||||
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 <PID {}, Thread {:?}>",
|
||||
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<WEBAUTHN_PLUGIN_OPERATION_RESPONSE>,
|
||||
) -> 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<T>(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> = 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<MaybeUninit<u8>>);
|
||||
|
||||
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::<u8>()).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<T>(self) -> *mut T {
|
||||
self.0.cast().as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait ComBufferExt {
|
||||
fn to_com_buffer(&self) -> ComBuffer;
|
||||
}
|
||||
|
||||
impl ComBufferExt for Vec<u8> {
|
||||
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<u16> {
|
||||
fn to_com_buffer(&self) -> ComBuffer {
|
||||
let buffer: Vec<u8> = 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<u8> = self
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.flat_map(|x| x.to_le_bytes())
|
||||
.collect();
|
||||
ComBuffer::from(&buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> From<T> for ComBuffer {
|
||||
fn from(value: T) -> Self {
|
||||
let buffer: Vec<u8> = 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::<u8>(), 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)
|
||||
}
|
||||
249
apps/desktop/desktop_native/win_webauthn/src/plugin/crypto.rs
Normal file
249
apps/desktop/desktop_native/win_webauthn/src/plugin/crypto.rs
Normal file
@@ -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<SigningKey, WinWebAuthnError> {
|
||||
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<SigningKey, WinWebAuthnError> {
|
||||
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::<BCRYPT_KEY_BLOB>() {
|
||||
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<Vec<u8>, 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::<u32>()];
|
||||
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::<u32>()];
|
||||
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<u8>,
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
376
apps/desktop/desktop_native/win_webauthn/src/plugin/mod.rs
Normal file
376
apps/desktop/desktop_native/win_webauthn/src/plugin/mod.rs
Normal file
@@ -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<Self, Self::Error> {
|
||||
// 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<T>(&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<PluginAddAuthenticatorResponse, WinWebAuthnError> {
|
||||
#![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<Vec<Vec<u16>>> = options
|
||||
.supported_rp_ids
|
||||
.map(|ids| ids.iter().map(|id| id.to_utf16()).collect());
|
||||
let supported_rp_id_ptrs: Option<Vec<*const u16>> = 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<PluginUserVerificationResponse, WinWebAuthnError> {
|
||||
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<PluginCredentialDetails>,
|
||||
) -> 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<ComBuffer> = 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<SigningKey, WinWebAuthnError> {
|
||||
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<SigningKey, WinWebAuthnError> {
|
||||
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<Vec<u8>, Box<dyn Error>>;
|
||||
|
||||
/// 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<Vec<u8>, Box<dyn Error>>;
|
||||
|
||||
/// Cancel an ongoing operation.
|
||||
fn cancel_operation(&self, request: PluginCancelOperationRequest)
|
||||
-> Result<(), Box<dyn Error>>;
|
||||
|
||||
/// Retrieve lock status.
|
||||
fn lock_status(&self) -> Result<PluginLockStatus, Box<dyn Error>>;
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
1050
apps/desktop/desktop_native/win_webauthn/src/plugin/types.rs
Normal file
1050
apps/desktop/desktop_native/win_webauthn/src/plugin/types.rs
Normal file
File diff suppressed because it is too large
Load Diff
793
apps/desktop/desktop_native/win_webauthn/src/types/mod.rs
Normal file
793
apps/desktop/desktop_native/win_webauthn/src/types/mod.rs
Normal file
@@ -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<CtapVersion>,
|
||||
|
||||
/// 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<HashSet<String>>,
|
||||
|
||||
/// 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<HashSet<String>>,
|
||||
|
||||
/// 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<Vec<PublicKeyCredentialParameters>>,
|
||||
}
|
||||
|
||||
impl AuthenticatorInfo {
|
||||
pub fn as_ctap_bytes(&self) -> Result<Vec<u8>, 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<Value> = 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<Self, Self::Error> {
|
||||
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::<Vec<char>>()
|
||||
.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::<Result<Vec<u8>, 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<u16>, // PCWSTR
|
||||
|
||||
/// Contains the friendly name of the Relying Party, such as "Acme
|
||||
/// Corporation", "Widgets Inc" or "Awesome Site".
|
||||
///
|
||||
/// This member is deprecated in WebAuthn Level 3 because many clients do not display it, but
|
||||
/// it remains a required dictionary member for backwards compatibility. Relying
|
||||
/// Parties MAY, as a safe default, set this equal to the RP ID.
|
||||
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<WEBAUTHN_RP_ENTITY_INFORMATION>,
|
||||
_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<String> {
|
||||
// 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<u8>, // PBYTE
|
||||
|
||||
/// Contains a detailed name for this account, such as "john.p.smith@example.com".
|
||||
pub pwszName: NonNull<u16>, // PCWSTR
|
||||
|
||||
/// Optional URL that can be used to retrieve an image containing the user's current avatar,
|
||||
/// or a data URI that contains the image data.
|
||||
#[deprecated]
|
||||
pub pwszIcon: Option<NonNull<u16>>, // PCWSTR
|
||||
|
||||
/// Contains the friendly name associated with the user account by the Relying Party, such as
|
||||
/// "John P. Smith".
|
||||
pub pwszDisplayName: NonNull<u16>, // PCWSTR
|
||||
}
|
||||
|
||||
pub struct UserEntityInformation<'a> {
|
||||
ptr: NonNull<WEBAUTHN_USER_ENTITY_INFORMATION>,
|
||||
_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<u16>, // LPCWSTR
|
||||
lAlg: i32, // LONG - COSE algorithm identifier
|
||||
}
|
||||
|
||||
impl WEBAUTHN_COSE_CREDENTIAL_PARAMETER {
|
||||
pub fn credential_type(&self) -> Result<String, WinWebAuthnError> {
|
||||
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<u8>,
|
||||
_second: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[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<u8>);
|
||||
|
||||
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<Vec<u8>> for UserId {
|
||||
type Error = WinWebAuthnError;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
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<u8>);
|
||||
|
||||
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<Vec<u8>> for CredentialId {
|
||||
type Error = WinWebAuthnError;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
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<String, WinWebAuthnError> {
|
||||
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<CtapTransport> {
|
||||
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<Self::Item> {
|
||||
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<Value, _> = 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
99
apps/desktop/desktop_native/win_webauthn/src/util.rs
Normal file
99
apps/desktop/desktop_native/win_webauthn/src/util.rs
Normal file
@@ -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<HMODULE, WinWebAuthnError> {
|
||||
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<u16>;
|
||||
}
|
||||
|
||||
impl WindowsString for str {
|
||||
fn to_utf16(&self) -> Vec<u16> {
|
||||
// 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<T> 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<Self::Item> {
|
||||
let current = self.list?.get(self.pos);
|
||||
self.pos += 1;
|
||||
current
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user