diff --git a/examples/hmac_secret.py b/examples/hmac_secret.py index 236207a..4ef8575 100644 --- a/examples/hmac_secret.py +++ b/examples/hmac_secret.py @@ -34,32 +34,28 @@ is now allowed in a browser setting. See also prf.py for an example which uses the PRF extension which is enabled by default. """ -from fido2.hid import CtapHidDevice from fido2.server import Fido2Server -from fido2.client import Fido2Client, WindowsClient +from fido2.client import Fido2Client from fido2.ctap2.extensions import HmacSecretExtension -from exampleutils import CliInteraction +from exampleutils import CliInteraction, enumerate_devices import ctypes import sys import os +# Use the Windows WebAuthn API if available, and we're not running as admin try: - from fido2.pcsc import CtapPcscDevice -except ImportError: - CtapPcscDevice = None - + from fido2.client.windows import WindowsClient -def enumerate_devices(): - for dev in CtapHidDevice.list_devices(): - yield dev - if CtapPcscDevice: - for dev in CtapPcscDevice.list_devices(): - yield dev + use_winclient = ( + WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin() + ) +except ImportError: + use_winclient = False uv = "discouraged" -if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin(): +if use_winclient: # Use the Windows WebAuthn API if available, and we're not running as admin # By default only the PRF extension is allowed, we need to explicitly # configure the client to allow hmac-secret @@ -105,12 +101,15 @@ def enumerate_devices(): credentials = [auth_data.credential_data] # HmacSecret result: -if not result.client_extension_results.get("hmacCreateSecret"): - print("Failed to create credential with HmacSecret") - sys.exit(1) +if result.client_extension_results.get("hmacCreateSecret"): + print("New credential created, with HmacSecret") +else: + # This fails on Windows, but we might still be able to use hmac-secret even if + # the credential wasn't made with it, so keep going + print("Failed to create credential with HmacSecret, it might not work") + credential = auth_data.credential_data -print("New credential created, with the HmacSecret extension.") # Prepare parameters for getAssertion allow_list = [{"type": "public-key", "id": credential.credential_id}] diff --git a/examples/prf.py b/examples/prf.py index 553e40d..d70a7fd 100644 --- a/examples/prf.py +++ b/examples/prf.py @@ -33,7 +33,6 @@ from fido2.server import Fido2Server from fido2.utils import websafe_encode from exampleutils import get_client -import sys import os @@ -66,9 +65,12 @@ credential = auth_data.credential_data # PRF result: -if not result.client_extension_results.get("prf", {}).get("enabled"): - print("Failed to create credential with PRF", result.client_extension_results) - sys.exit(1) +if result.client_extension_results.get("prf", {}).get("enabled"): + print("New credential created, with PRF") +else: + # This fails on Windows, but we might still be able to use prf even if + # the credential wasn't made with it, so keep going + print("Failed to create credential with PRF, it might not work") print("New credential created, with the PRF extension.") diff --git a/fido2/client/windows.py b/fido2/client/windows.py index 5e2578b..33b4510 100644 --- a/fido2/client/windows.py +++ b/fido2/client/windows.py @@ -35,7 +35,6 @@ WebAuthNAttestationConveyancePreference, WebAuthNEnterpriseAttestation, ) -from ..ctap2 import AssertionResponse from ..rpid import verify_rp_id from ..webauthn import ( CollectedClientData, @@ -50,6 +49,14 @@ AuthenticatorAttachment, PublicKeyCredentialType, ) +from ..ctap2 import AssertionResponse +from ..ctap2.extensions import ( + HMACGetSecretOutput, + AuthenticatorExtensionsPRFOutputs, + AuthenticatorExtensionsLargeBlobOutputs, + CredentialPropertiesOutput, +) +from ..utils import _JsonDataObject from typing import Callable, Sequence import sys @@ -57,6 +64,19 @@ logger = logging.getLogger(__name__) +_extension_output_types: dict[str, type[_JsonDataObject]] = { + "hmacGetSecret": HMACGetSecretOutput, + "prf": AuthenticatorExtensionsPRFOutputs, + "largeBlob": AuthenticatorExtensionsLargeBlobOutputs, + "credProps": CredentialPropertiesOutput, +} + + +def _wrap_ext(key, value): + if key in _extension_output_types: + return _extension_output_types[key].from_dict(value) + return value + class WindowsClient(WebAuthnClient, _BaseClient): """Fido2Client-like class using the Windows WebAuthn API. @@ -160,7 +180,9 @@ def make_credential(self, options, event=None): raw_id=credential.credential_id, response=AuthenticatorAttestationResponse(client_data, att_obj), authenticator_attachment=AuthenticatorAttachment.CROSS_PLATFORM, - client_extension_results=AuthenticationExtensionsClientOutputs(extensions), + client_extension_results=AuthenticationExtensionsClientOutputs( + {k: _wrap_ext(k, v) for k, v in extensions.items()} + ), type=PublicKeyCredentialType.PUBLIC_KEY, ) @@ -210,5 +232,5 @@ def get_assertion(self, options, event=None): user=user, ) ], - extensions, + {k: _wrap_ext(k, v) for k, v in extensions.items()}, )