Last active
August 13, 2025 21:30
-
-
Save dzmitry-savitski/b6dabcc3defd54cdde55a3dd7876c466 to your computer and use it in GitHub Desktop.
Revisions
-
dzmitry-savitski revised this gist
Aug 13, 2025 . 1 changed file with 22 additions and 7 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,21 +1,36 @@ # pip install fido2 import os from fido2.webauthn import PublicKeyCredentialRequestOptions, UserVerificationRequirement from fido2.client.windows import WindowsClient # Try to import the new collector (python-fido2 >= 1.2/2.0) collector = None try: from fido2.client import DefaultClientDataCollector collector = DefaultClientDataCollector(origin="https://webauthn.io") except Exception: collector = None # Instantiate WindowsClient for both API shapes if collector is not None: # New API: pass a ClientDataCollector client = WindowsClient(client_data_collector=collector) else: # Old API: constructor accepted origin directly client = WindowsClient("https://webauthn.io") # Build proper WebAuthn options for an assertion (sign-in) options = PublicKeyCredentialRequestOptions( challenge=os.urandom(32), rp_id="webauthn.io", timeout=15000, # ms user_verification=UserVerificationRequirement.DISCOURAGED, # we're just probing transport ) try: result = client.get_assertion(options) # If we got here, transport worked and Windows Security likely popped on the client. resp = result.get_response(0) # select the first assertion print("Success. Signature length:", len(resp.signature)) except Exception as e: print("GetAssertion failed:", repr(e)) -
dzmitry-savitski revised this gist
Aug 13, 2025 . 2 changed files with 18 additions and 122 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,28 +0,0 @@ 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 charactersOriginal file line number Diff line number Diff line change @@ -1,97 +1,21 @@ # pip install fido2 import os from fido2.client.windows import WindowsClient from fido2.webauthn import PublicKeyCredentialRequestOptions origin = "https://webauthn.io" client = WindowsClient(origin) # wraps webauthn.dll on Windows # Build proper WebAuthn request options options = PublicKeyCredentialRequestOptions( challenge=os.urandom(32), rp_id="webauthn.io", timeout=60000, user_verification="discouraged", # we're just probing the transport ) try: result = client.get_assertion(options) print("OK, transport works. Got", len(result.get_response(0).signature), "bytes of signature") except Exception as e: print("GetAssertion failed:", repr(e)) -
dzmitry-savitski created this gist
Aug 13, 2025 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,28 @@ // 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,97 @@ # 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)