Skip to content

Instantly share code, notes, and snippets.

@dzmitry-savitski
Last active August 13, 2025 21:30
Show Gist options
  • Select an option

  • Save dzmitry-savitski/b6dabcc3defd54cdde55a3dd7876c466 to your computer and use it in GitHub Desktop.

Select an option

Save dzmitry-savitski/b6dabcc3defd54cdde55a3dd7876c466 to your computer and use it in GitHub Desktop.
// 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);
# 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