Last active
August 13, 2025 21:30
-
-
Save dzmitry-savitski/b6dabcc3defd54cdde55a3dd7876c466 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // webauthn_probe.js | |
| const ffi = require('ffi-napi'); | |
| const ref = require('ref-napi'); | |
| // Map a couple of functions. (We avoid wide-string handling here to keep it simple.) | |
| const webauthn = ffi.Library('webauthn', { | |
| // DWORD WebAuthNGetApiVersionNumber(void) | |
| WebAuthNGetApiVersionNumber: ['uint32', []], | |
| // HRESULT WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(BOOL *out) | |
| WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable: ['int32', ['pointer']], | |
| // If you want error names, add: | |
| // WebAuthNGetErrorName: ['pointer', ['int32']], // returns PCWSTR (UTF-16) | |
| }); | |
| const apiVersion = webauthn.WebAuthNGetApiVersionNumber(); | |
| console.log('WebAuthN API version:', apiVersion); | |
| const BOOL = ref.types.int; // Windows BOOL is a 32-bit int | |
| const outPtr = ref.alloc(BOOL); | |
| const hr = webauthn.WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(outPtr); | |
| const uvpaa = outPtr.deref() !== 0; | |
| console.log('UVPAA available:', uvpaa, 'hr=0x' + (hr >>> 0).toString(16)); | |
| // If you also mapped WebAuthNGetErrorName (PCWSTR), you can read it like this: | |
| // const namePtr = webauthn.WebAuthNGetErrorName(hr); | |
| // const name = namePtr.isNull() ? '' : namePtr.reinterpretUntilZeros(2).toString('ucs2'); | |
| // console.log('HRESULT name:', name); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # webauthn_probe.py | |
| import ctypes | |
| from ctypes import wintypes | |
| import json | |
| import os | |
| # --- Types & DLL ------------------------------------------------------------ | |
| HRESULT = ctypes.c_long # 32-bit signed | |
| DWORD = wintypes.DWORD | |
| BOOL = wintypes.BOOL | |
| LPCWSTR = wintypes.LPCWSTR | |
| HWND = wintypes.HWND | |
| webauthn = ctypes.WinDLL("webauthn.dll") # loads %SystemRoot%\System32\webauthn.dll | |
| # DWORD WebAuthNGetApiVersionNumber(void); | |
| webauthn.WebAuthNGetApiVersionNumber.restype = DWORD | |
| # HRESULT WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(BOOL *out); | |
| webauthn.WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable.argtypes = [ctypes.POINTER(BOOL)] | |
| webauthn.WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable.restype = HRESULT | |
| # PCWSTR WebAuthNGetErrorName(HRESULT hr); | |
| webauthn.WebAuthNGetErrorName.argtypes = [HRESULT] | |
| webauthn.WebAuthNGetErrorName.restype = LPCWSTR | |
| print("WebAuthN API version:", webauthn.WebAuthNGetApiVersionNumber()) | |
| is_uvpaa = BOOL() | |
| hr = webauthn.WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(ctypes.byref(is_uvpaa)) | |
| print("UVPAA available:", bool(is_uvpaa.value), "hr=0x%08X" % (hr & 0xFFFFFFFF), | |
| webauthn.WebAuthNGetErrorName(hr) or "") | |
| # --- Minimal GetAssertion probe -------------------------------------------- | |
| # This builds the tiny structs needed for WebAuthNAuthenticatorGetAssertion | |
| # and calls it with *no options*. If a WebAuthn channel is available in a | |
| # remote session, you'll see the Windows Security UI on the client. Otherwise, | |
| # you'll typically get a timeout / NotAllowedError. | |
| # | |
| # Signatures: https://learn.microsoft.com/windows/win32/api/webauthn/ | |
| # GetAssertion: https://learn.microsoft.com/windows/win32/api/webauthn/nf-webauthn-webauthnauthenticatorgetassertion | |
| # WEBAUTHN_CLIENT_DATA shape: https://learn.microsoft.com/windows/win32/api/webauthn/ns-webauthn-webauthn_client_data | |
| # Constants from webauthn.h | |
| WEBAUTHN_CLIENT_DATA_CURRENT_VERSION = 1 # current version | |
| WEBAUTHN_HASH_ALGORITHM_SHA_256 = "SHA-256" # documented hash alg ID | |
| class WEBAUTHN_CLIENT_DATA(ctypes.Structure): | |
| _fields_ = [ | |
| ("dwVersion", DWORD), | |
| ("cbClientDataJSON", DWORD), | |
| ("pbClientDataJSON", ctypes.POINTER(ctypes.c_ubyte)), | |
| ("pwszHashAlgId", LPCWSTR), | |
| ] | |
| # HRESULT WebAuthNAuthenticatorGetAssertion( | |
| # HWND hWnd, LPCWSTR rpId, PCWEBAUTHN_CLIENT_DATA pClientData, | |
| # PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS pOptions, PWEBAUTHN_ASSERTION *ppAssertion); | |
| webauthn.WebAuthNAuthenticatorGetAssertion.argtypes = [ | |
| HWND, LPCWSTR, ctypes.POINTER(WEBAUTHN_CLIENT_DATA), ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p) | |
| ] | |
| webauthn.WebAuthNAuthenticatorGetAssertion.restype = HRESULT | |
| # VOID WebAuthNFreeAssertion(PWEBAUTHN_ASSERTION pAssertion); | |
| webauthn.WebAuthNFreeAssertion.argtypes = [ctypes.c_void_p] | |
| webauthn.WebAuthNFreeAssertion.restype = None | |
| # Build a minimal CollectedClientData for an assertion ("webauthn.get"). | |
| # The challenge can be any bytes for this probe; real flows use server-provided values. | |
| challenge_b64url = "dGVzdC1jaGFsbGVuZ2U" # "test-challenge" base64url, no '=' padding | |
| client_data = { | |
| "type": "webauthn.get", | |
| "challenge": challenge_b64url, | |
| # IMPORTANT: origin must match the RP you expect credentials for; this is a probe. | |
| "origin": "https://webauthn.io", | |
| } | |
| client_json = json.dumps(client_data).encode("utf-8") | |
| buf = (ctypes.c_ubyte * len(client_json)).from_buffer_copy(client_json) | |
| cd = WEBAUTHN_CLIENT_DATA() | |
| cd.dwVersion = WEBAUTHN_CLIENT_DATA_CURRENT_VERSION | |
| cd.cbClientDataJSON = len(client_json) | |
| cd.pbClientDataJSON = ctypes.cast(buf, ctypes.POINTER(ctypes.c_ubyte)) | |
| cd.pwszHashAlgId = WEBAUTHN_HASH_ALGORITHM_SHA_256 | |
| # rpId typically matches the site (e.g., "webauthn.io"). Options = NULL for defaults. | |
| ppAssertion = ctypes.c_void_p() | |
| hr = webauthn.WebAuthNAuthenticatorGetAssertion( | |
| HWND(0), LPCWSTR("webauthn.io"), ctypes.byref(cd), None, ctypes.byref(ppAssertion) | |
| ) | |
| print("GetAssertion hr=0x%08X" % (hr & 0xFFFFFFFF), webauthn.WebAuthNGetErrorName(hr) or "") | |
| if hr == 0: # S_OK | |
| # In a real program you'd parse the WEBAUTHN_ASSERTION struct here. | |
| # Always free the assertion blob. | |
| webauthn.WebAuthNFreeAssertion(ppAssertion) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment