From 5bcdf759d0e5d7ab253e7b4494b8f789f3ce5617 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 6 Nov 2024 12:09:27 +0100 Subject: [PATCH] Handle PIN_AUTH_BLOCKED better --- fido2/client.py | 74 +++++++++++++++++++++++++++++++++---------------- fido2/pcsc.py | 9 ++++-- 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/fido2/client.py b/fido2/client.py index 00493616..c8576a75 100644 --- a/fido2/client.py +++ b/fido2/client.py @@ -726,17 +726,30 @@ def _do_make(): pin_token, ) - try: - att_obj, pin_protocol, pin_token = _do_make() - except CtapError as e: - # The Authenticator may still require UV, try again - if ( - e.code == CtapError.ERR.PUAT_REQUIRED - and user_verification == UserVerificationRequirement.DISCOURAGED - ): - user_verification = UserVerificationRequirement.REQUIRED + dev = self.ctap2.device + reconnected = False + while True: + try: att_obj, pin_protocol, pin_token = _do_make() - else: + break + except CtapError as e: + # The Authenticator may still require UV, try again + if ( + e.code == CtapError.ERR.PUAT_REQUIRED + and user_verification == UserVerificationRequirement.DISCOURAGED + ): + user_verification = UserVerificationRequirement.REQUIRED + continue + # NFC may require reconnect + if ( + e.code == CtapError.ERR.PIN_AUTH_BLOCKED + and hasattr(dev, "connect") + and not reconnected + ): + dev.close() + dev.connect() + reconnected = True # We only want to try this once + continue raise # Process extenstion outputs @@ -847,17 +860,30 @@ def _do_auth(): pin_protocol, ) - try: - return _do_auth() - except CtapError as e: - # The Authenticator may still require UV, try again - if ( - e.code == CtapError.ERR.PUAT_REQUIRED - and user_verification == UserVerificationRequirement.DISCOURAGED - ): - user_verification = UserVerificationRequirement.REQUIRED + dev = self.ctap2.device + reconnected = False + while True: + try: return _do_auth() - raise + except CtapError as e: + # The Authenticator may still require UV, try again + if ( + e.code == CtapError.ERR.PUAT_REQUIRED + and user_verification == UserVerificationRequirement.DISCOURAGED + ): + user_verification = UserVerificationRequirement.REQUIRED + continue + # NFC may require reconnect + if ( + e.code == CtapError.ERR.PIN_AUTH_BLOCKED + and hasattr(dev, "connect") + and not reconnected + ): + dev.close() + dev.connect() + reconnected = True # We only want to try this once + continue + raise class Fido2Client(WebAuthnClient, _BaseClient): @@ -1037,7 +1063,7 @@ def __init__( def is_available() -> bool: return platform.system().lower() == "windows" and WinAPI.version > 0 - def make_credential(self, options, **kwargs): + def make_credential(self, options, event=None): """Create a credential using Windows WebAuthN APIs. :param options: PublicKeyCredentialCreationOptions data. @@ -1092,7 +1118,7 @@ def make_credential(self, options, **kwargs): ), options.exclude_credentials, options.extensions, - kwargs.get("event"), + event, enterprise_attestation, ) except OSError as e: @@ -1103,7 +1129,7 @@ def make_credential(self, options, **kwargs): client_data, AttestationObject(result), extensions ) - def get_assertion(self, options, **kwargs): + def get_assertion(self, options, event=None): """Get assertion using Windows WebAuthN APIs. :param options: PublicKeyCredentialRequestOptions data. @@ -1131,7 +1157,7 @@ def get_assertion(self, options, **kwargs): ), options.allow_credentials, options.extensions, - kwargs.get("event"), + event, ) ) except OSError as e: diff --git a/fido2/pcsc.py b/fido2/pcsc.py index 965f7b70..7ebb59ab 100644 --- a/fido2/pcsc.py +++ b/fido2/pcsc.py @@ -59,12 +59,11 @@ class CtapPcscDevice(CtapDevice): """ def __init__(self, connection: CardConnection, name: str): + self._name = name self._capabilities = CAPABILITY(0) self.use_ext_apdu = False self._conn = connection - self._conn.connect() - self._name = name - self._select() + self.connect() try: # Probe for CTAP2 by calling GET_INFO self.call(CTAPHID.CBOR, b"\x04") @@ -73,6 +72,10 @@ def __init__(self, connection: CardConnection, name: str): if not self._capabilities: raise ValueError("Unsupported device") + def connect(self): + self._conn.connect() + self._select() + def __repr__(self): return f"CtapPcscDevice({self._name})"