Skip to content

Commit

Permalink
Pass options to extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Oct 17, 2024
1 parent c804a4e commit 4b5a325
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 36 deletions.
15 changes: 10 additions & 5 deletions fido2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,9 @@ def do_make_credential(
rk = selection.require_resident_key
user_verification = selection.user_verification

on_keepalive = _user_keepalive(self.user_interaction)

# Handle enterprise attestation
enterprise_attestation = None
if options.attestation == AttestationConveyancePreference.ENTERPRISE:
if self.info.options.get("ep"):
Expand All @@ -645,8 +648,6 @@ def do_make_credential(
# Vendor facilitated
enterprise_attestation = 1

on_keepalive = _user_keepalive(self.user_interaction)

# Gather up permissions
permissions = ClientPin.PERMISSION.MAKE_CREDENTIAL
if exclude_list:
Expand All @@ -658,6 +659,8 @@ def do_make_credential(
used_extensions = []
client_inputs = extensions or {}
for ext in extension_instances:
# TODO: Move options to the constructor instead
ext._create_options = options
permissions |= ext.get_create_permissions(client_inputs)

def _do_make():
Expand Down Expand Up @@ -775,6 +778,8 @@ def do_get_assertion(
extension_instances = [cls(self.ctap2) for cls in self.extensions]
client_inputs = extensions or {}
for ext in extension_instances:
# TODO: Move options to get_get_permissions and process_get_input
ext._get_options = options
permissions |= ext.get_get_permissions(client_inputs)

def _do_auth():
Expand All @@ -798,9 +803,9 @@ def _do_auth():
used_extensions = []
try:
for ext in extension_instances:
auth_input = ext._process_get_input_w_allow_list(
client_inputs, selected_cred
)
# TODO: Move to process_get_input()
ext._selected = selected_cred
auth_input = ext.process_get_input(client_inputs)
if auth_input is not None:
used_extensions.append(ext)
extension_inputs[ext.NAME] = auth_input
Expand Down
57 changes: 26 additions & 31 deletions fido2/ctap2/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
from .pin import ClientPin, PinProtocol
from .blob import LargeBlobs
from ..utils import sha256, websafe_encode
from ..webauthn import PublicKeyCredentialDescriptor
from ..webauthn import (
PublicKeyCredentialDescriptor,
PublicKeyCredentialCreationOptions,
PublicKeyCredentialRequestOptions,
)
from enum import Enum, unique
from typing import Dict, Tuple, Any, Optional
import abc
import warnings
import inspect


class Ctap2Extension(abc.ABC):
Expand All @@ -49,6 +52,10 @@ class Ctap2Extension(abc.ABC):

def __init__(self, ctap: Ctap2):
self.ctap = ctap
# TODO: Pass options and selected to the various methods that need them instead
self._create_options: PublicKeyCredentialCreationOptions
self._get_options: PublicKeyCredentialRequestOptions
self._selected: Optional[PublicKeyCredentialDescriptor]

def is_supported(self) -> bool:
"""Whether or not the extension is supported by the authenticator."""
Expand Down Expand Up @@ -84,33 +91,12 @@ def process_create_output(
def get_get_permissions(self, inputs: Dict[str, Any]) -> ClientPin.PERMISSION:
return ClientPin.PERMISSION(0)

def process_get_input(
self,
inputs: Dict[str, Any],
allow_credential: Optional[PublicKeyCredentialDescriptor] = None,
) -> Any:
def process_get_input(self, inputs: Dict[str, Any]) -> Any:
"""Returns a value to include in the authenticator extension input,
or None.
"""
return None

def _process_get_input_w_allow_list(
self,
inputs: Dict[str, Any],
allow_credential: Optional[PublicKeyCredentialDescriptor],
) -> Any:
s = inspect.signature(self.process_get_input)
try:
s.bind(inputs, allow_credential)
except TypeError:
warnings.warn(
f"{type(self)}.process_get_input() does not take allow_credential, "
"which is deprecated.",
DeprecationWarning,
)
return self.process_get_input(inputs)
return self.process_get_input(inputs, allow_credential)

def process_get_input_with_permissions(
self, inputs: Dict[str, Any]
) -> Tuple[Any, ClientPin.PERMISSION]:
Expand Down Expand Up @@ -162,21 +148,30 @@ def process_create_output(self, attestation_response, *args):
else:
return {"hmacCreateSecret": enabled}

def process_get_input(self, inputs, allow_credential=None):
def process_get_input(self, inputs):
if not self.is_supported():
return

data = inputs.get("prf")
if data:
secrets = data.get("eval")
by_creds = data.get("evalByCredential")
if by_creds:
if not allow_credential:
# Make sure all keys are valid IDs from allow_credentials
allow_list = self._get_options.allow_credentials
if not allow_list:
raise ValueError("evalByCredentials requires allowCredentials")
secrets = by_creds.get(websafe_encode(allow_credential.id))
if not secrets:
raise ValueError("No matching credential ID found")
else:
secrets = data.get("eval")
ids = {websafe_encode(c.id) for c in allow_list}
if not ids.issuperset(by_creds):
raise ValueError("evalByCredentials contains invalid key")
if self._selected:
key = websafe_encode(self._selected.id)
if key in by_creds:
secrets = by_creds[key]

if not secrets:
return

salts = (
_prf_salt(secrets["first"]),
_prf_salt(secrets["second"]) if "second" in secrets else b"",
Expand Down

0 comments on commit 4b5a325

Please sign in to comment.