File fido2-compat.patch of Package python-asyncssh

From b9e58a3914c7d1df7f2c096e8c1c0220799e247f Mon Sep 17 00:00:00 2001
From: Ron Frederick <ronf@timeheart.net>
Date: Fri, 3 Oct 2025 17:44:39 -0700
Subject: [PATCH] Update asycnssh to use version 2 of the fido2 package

---
 asyncssh/sk.py   | 33 ++++++++++++++++++++++-----------
 pyproject.toml   |  2 +-
 tests/sk_stub.py | 26 +++++++++++++++++++++-----
 3 files changed, 44 insertions(+), 17 deletions(-)

diff --git a/asyncssh/sk.py b/asyncssh/sk.py
index ca5aef7..bb02ed2 100644
--- a/asyncssh/sk.py
+++ b/asyncssh/sk.py
@@ -128,7 +128,9 @@ def _ctap2_enroll(dev: 'CtapHidDevice', alg: int, application: 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) -> Tuple[bytes, bytes]:
                '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 @@ def sk_get_resident(application: str, user: Optional[str],
 
 
 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 @@ def sk_get_resident(application: str, user: Optional[str],
     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 @@ def _sk_not_available(*args: object, **kwargs: object) -> NoReturn:
     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
diff --git a/pyproject.toml b/pyproject.toml
index ea30886..2f4f113 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,7 +35,7 @@ dynamic = ['version']
 
 [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']
diff --git a/tests/sk_stub.py b/tests/sk_stub.py
index 0926e4e..090f150 100644
--- a/tests/sk_stub.py
+++ b/tests/sk_stub.py
@@ -93,6 +93,13 @@ def __init__(self, attestation_object):
         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 @@ def __init__(self, client_data, auth_data, signature):
         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 @@ def get_assertions(self, application, message_hash, allow_creds, options):
 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 @@ def make_credential(self, options):
         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 @@ def get_assertion(self, options):
                                          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])
 
openSUSE Build Service is sponsored by