Skip to content

Commit

Permalink
Fix WindowsClient extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Dec 12, 2024
1 parent 0a84a3f commit 06ab049
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 32 deletions.
2 changes: 1 addition & 1 deletion examples/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@


# Prefer UV if supported and configured
if info and info.options.get("uv") or info.options.get("bioEnroll"):
if info and (info.options.get("uv") or info.options.get("bioEnroll")):
uv = "preferred"
print("Authenticator supports User Verification")
else:
Expand Down
35 changes: 17 additions & 18 deletions examples/hmac_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}]
Expand Down
10 changes: 6 additions & 4 deletions examples/prf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
from fido2.server import Fido2Server
from fido2.utils import websafe_encode
from exampleutils import get_client
import sys
import os


Expand Down Expand Up @@ -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.")

Expand Down
41 changes: 32 additions & 9 deletions fido2/client/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,14 @@

from __future__ import annotations

from . import WebAuthnClient, _BaseClient, AssertionSelection, ClientError
from . import WebAuthnClient, _BaseClient, AssertionSelection, ClientError, _cbor_list
from .win_api import (
WinAPI,
WebAuthNAuthenticatorAttachment,
WebAuthNUserVerificationRequirement,
WebAuthNAttestationConveyancePreference,
WebAuthNEnterpriseAttestation,
)
from ..ctap2 import AssertionResponse
from ..rpid import verify_rp_id
from ..webauthn import (
CollectedClientData,
Expand All @@ -49,14 +48,36 @@
ResidentKeyRequirement,
AuthenticatorAttachment,
PublicKeyCredentialType,
_as_cbor,
)
from ..ctap2 import AssertionResponse
from ..ctap2.extensions import (
HMACGetSecretOutput,
AuthenticatorExtensionsPRFOutputs,
AuthenticatorExtensionsLargeBlobOutputs,
CredentialPropertiesOutput,
)
from ..utils import _JsonDataObject

from typing import Callable, Sequence
import sys
import logging

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.
Expand Down Expand Up @@ -130,9 +151,9 @@ def make_credential(self, options, event=None):

try:
att_obj, extensions = self.api.make_credential(
options.rp,
options.user,
options.pub_key_cred_params,
_as_cbor(options.rp),
_as_cbor(options.user),
_cbor_list(options.pub_key_cred_params),
client_data,
options.timeout or 0,
selection.resident_key or ResidentKeyRequirement.DISCOURAGED,
Expand All @@ -143,7 +164,7 @@ def make_credential(self, options, event=None):
selection.user_verification or "discouraged"
),
attestation,
options.exclude_credentials,
_cbor_list(options.exclude_credentials),
options.extensions,
event,
enterprise_attestation,
Expand All @@ -160,7 +181,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,
)

Expand Down Expand Up @@ -191,7 +214,7 @@ def get_assertion(self, options, event=None):
WebAuthNUserVerificationRequirement.from_string(
options.user_verification or "discouraged"
),
options.allow_credentials,
_cbor_list(options.allow_credentials),
options.extensions,
event,
)
Expand All @@ -210,5 +233,5 @@ def get_assertion(self, options, event=None):
user=user,
)
],
extensions,
{k: _wrap_ext(k, v) for k, v in extensions.items()},
)

0 comments on commit 06ab049

Please sign in to comment.