Obtained from: https://github.com/ronf/asyncssh/commit/b9e58a3914c7d1df7f2c096e8c1c0220799e247f --- asyncssh/sk.py.orig 2025-08-23 02:54:29 UTC +++ asyncssh/sk.py @@ -128,7 +128,9 @@ def _win_enroll(alg: int, application: str, user: str) def _win_enroll(alg: int, application: str, user: str) -> Tuple[bytes, bytes]: """Enroll a new security key using Windows WebAuthn API""" - client = WindowsClient(application, verify=_verify_rp_id) + data_collector = DefaultClientDataCollector(origin=application, + verify=_verify_rp_id) + client = WindowsClient(data_collector) rp = {'id': application, 'name': application} user_cred = {'id': user.encode('utf-8'), 'name': user} @@ -137,7 +139,8 @@ def _win_enroll(alg: int, application: str, user: str) 'pubKeyCredParams': key_params} result = client.make_credential(options) - cdata = result.attestation_object.auth_data.credential_data + response = result.response + cdata = response.attestation_object.auth_data.credential_data # pylint: disable=no-member return _decode_public_key(alg, cdata.public_key), cdata.credential_id @@ -188,17 +191,20 @@ def _win_sign(data: bytes, application: str, key_handle: bytes) -> Tuple[int, int, bytes, bytes]: """Sign a message with a security key using Windows WebAuthn API""" - client = WindowsClient(application, verify=_verify_rp_id) + data_collector = DefaultClientDataCollector(origin=application, + verify=_verify_rp_id) + client = WindowsClient(data_collector) creds = [{'type': 'public-key', 'id': key_handle}] options = {'challenge': data, 'rpId': application, 'allowCredentials': creds} result = client.get_assertion(options).get_response(0) - auth_data = result.authenticator_data + response = result.response + auth_data = response.authenticator_data return auth_data.flags, auth_data.counter, \ - result.signature, bytes(result.client_data) + response.signature, bytes(response.client_data) def sk_webauthn_prefix(data: bytes, application: str) -> bytes: @@ -327,7 +333,7 @@ try: try: - from fido2.client import WindowsClient + from fido2.client import DefaultClientDataCollector from fido2.ctap import CtapError from fido2.ctap1 import Ctap1, APDU, ApduError from fido2.ctap2 import Ctap2, ClientPin, PinProtocolV1 @@ -335,13 +341,8 @@ try: from fido2.hid import CtapHidDevice sk_available = True - - sk_use_webauthn = WindowsClient.is_available() and \ - hasattr(ctypes, 'windll') and \ - not ctypes.windll.shell32.IsUserAnAdmin() except (ImportError, OSError, AttributeError): # pragma: no cover sk_available = False - sk_use_webauthn = False def _sk_not_available(*args: object, **kwargs: object) -> NoReturn: """Report that security key support is unavailable""" @@ -351,3 +352,13 @@ except (ImportError, OSError, AttributeError): # pragm sk_enroll = _sk_not_available sk_sign = _sk_not_available sk_get_resident = _sk_not_available + +try: + from fido2.client.windows import WindowsClient + + sk_use_webauthn = WindowsClient.is_available() and \ + hasattr(ctypes, 'windll') and \ + not ctypes.windll.shell32.IsUserAnAdmin() +except ImportError: + WindowsClient = None + sk_use_webauthn = False --- pyproject.toml.orig 2025-09-28 13:31:10 UTC +++ pyproject.toml @@ -35,7 +35,7 @@ bcrypt = ['bcrypt >= 3.1.3'] [project.optional-dependencies] bcrypt = ['bcrypt >= 3.1.3'] -fido2 = ['fido2 >= 0.9.2, < 2'] +fido2 = ['fido2 >= 2'] gssapi = ['gssapi >= 1.2.0'] libnacl = ['libnacl >= 1.4.2'] pkcs11 = ['python-pkcs11 >= 0.7.0'] --- tests/sk_stub.py.orig 2025-05-29 03:09:38 UTC +++ tests/sk_stub.py @@ -93,6 +93,13 @@ class _AttestationResponse: self.attestation_object = attestation_object +class _RegistrationResponse: + """Security key registration response""" + + def __init__(self, attestation_response): + self.response = attestation_response + + class _AuthenticatorData: """Security key authenticator data in aseertion""" @@ -110,6 +117,13 @@ class _AssertionResponse: self.signature = signature +class _AuthenticationResponse: + """Security key authentication response""" + + def __init__(self, response): + self.response = response + + class _AssertionSelection: """Security key assertion response list""" @@ -261,9 +275,9 @@ class WindowsClient(_CtapStub): class WindowsClient(_CtapStub): """Stub for unit testing U2F security keys via Windows WebAuthn""" - def __init__(self, origin, verify): - self._origin = origin - self._verify = verify + def __init__(self, data_collector): + self._origin = data_collector._origin + self._verify = data_collector._verify def make_credential(self, options): """Make a credential using Windows WebAuthN API""" @@ -275,8 +289,9 @@ class WindowsClient(_CtapStub): public_key, key_handle = self._enroll(alg) cdata = _CredentialData(alg, public_key, key_handle) + attestation_object = _Credential(_CredentialAuthData(cdata)) - return _AttestationResponse(_Credential(_CredentialAuthData(cdata))) + return _RegistrationResponse(_AttestationResponse(attestation_object)) def get_assertion(self, options): """Get assertion using Windows WebAuthN API""" @@ -297,7 +312,8 @@ class WindowsClient(_CtapStub): key_handle, flags) auth_data = _AuthenticatorData(flags, counter) - assertion = _AssertionResponse(data, auth_data, sig) + response = _AssertionResponse(data, auth_data, sig) + assertion = _AuthenticationResponse(response) return _AssertionSelection([assertion])