Skip to content

Commit

Permalink
Always filter down to a single credential ID
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Oct 17, 2024
1 parent 4d8e7ac commit 44f5e23
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 40 deletions.
39 changes: 17 additions & 22 deletions fido2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ def _filter_creds(
else:
pin_auth = None
version = None
matches = []

for chunk in chunks:
try:
assertions = self.ctap2.get_assertions(
Expand All @@ -495,24 +495,19 @@ def _filter_creds(
event=event,
on_keepalive=on_keepalive,
)
if len(chunk) == 1:
# Credential ID might be omitted from assertions
return chunk[0]
else:
return PublicKeyCredentialDescriptor(**assertions[0].credential)
except CtapError as e:
if e.code == CtapError.ERR.NO_CREDENTIALS:
# All creds in chunk are discarded
continue
raise

if len(chunk) == 1 and len(assertions) == 1:
# Credential ID might be omitted from assertions
matches.append(chunk[0])
else:
matches.extend(
[PublicKeyCredentialDescriptor(**a.credential) for a in assertions]
)

if len(matches) > max_creds:
# Too many matches? Just return as many as we can handle
return matches[:max_creds]
return matches
# No matches found
return None

def selection(self, event):
if "FIDO_2_1" in self.info.versions:
Expand Down Expand Up @@ -656,11 +651,11 @@ def _do_make():
)

if exclude_list:
cred_list = self._filter_creds(
exclude_cred = self._filter_creds(
rp.id, exclude_list, pin_protocol, pin_token, event, on_keepalive
)
else:
cred_list = None
if exclude_cred:
raise CtapError(CtapError.ERR.CREDENTIAL_EXCLUDED)

# Process extensions
extension_inputs = {}
Expand Down Expand Up @@ -696,7 +691,7 @@ def _do_make():
_as_cbor(rp),
_as_cbor(user),
_cbor_list(key_params),
_cbor_list(cred_list),
None,
extension_inputs or None,
options,
pin_auth,
Expand Down Expand Up @@ -768,21 +763,21 @@ def _do_auth():
)

if allow_list:
cred_list = self._filter_creds(
allow_cred = self._filter_creds(
rp_id, allow_list, pin_protocol, pin_token, event, on_keepalive
)
if not cred_list:
if not allow_cred:
raise CtapError(CtapError.ERR.NO_CREDENTIALS)
else:
cred_list = None
allow_cred = None

# Process extensions
extension_inputs = {}
used_extensions = []
try:
for ext in extension_instances:
auth_input = ext._process_get_input_w_allow_list(
client_inputs, cred_list
client_inputs, allow_cred
)
if auth_input is not None:
used_extensions.append(ext)
Expand All @@ -803,7 +798,7 @@ def _do_auth():
assertions = self.ctap2.get_assertions(
rp_id,
client_data_hash,
_cbor_list(cred_list),
[_as_cbor(allow_cred)] if allow_cred else None,
extension_inputs or None,
options,
pin_auth,
Expand Down
29 changes: 11 additions & 18 deletions fido2/ctap2/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from ..utils import sha256, websafe_encode
from ..webauthn import PublicKeyCredentialDescriptor
from enum import Enum, unique
from typing import Dict, Tuple, Any, Optional, List
from typing import Dict, Tuple, Any, Optional
import abc
import warnings
import inspect
Expand Down Expand Up @@ -87,7 +87,7 @@ def get_get_permissions(self, inputs: Dict[str, Any]) -> ClientPin.PERMISSION:
def process_get_input(
self,
inputs: Dict[str, Any],
allow_list: Optional[List[PublicKeyCredentialDescriptor]] = None,
allow_credential: Optional[PublicKeyCredentialDescriptor] = None,
) -> Any:
"""Returns a value to include in the authenticator extension input,
or None.
Expand All @@ -97,19 +97,19 @@ def process_get_input(
def _process_get_input_w_allow_list(
self,
inputs: Dict[str, Any],
allow_list: Optional[List[PublicKeyCredentialDescriptor]],
allow_credential: Optional[PublicKeyCredentialDescriptor],
) -> Any:
s = inspect.signature(self.process_get_input)
try:
s.bind(inputs, allow_list)
s.bind(inputs, allow_credential)
except TypeError:
warnings.warn(
f"{type(self)}.process_get_input() does not take allow_list, "
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_list)
return self.process_get_input(inputs, allow_credential)

def process_get_input_with_permissions(
self, inputs: Dict[str, Any]
Expand Down Expand Up @@ -162,25 +162,18 @@ def process_create_output(self, attestation_response, *args):
else:
return {"hmacCreateSecret": enabled}

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

data = inputs.get("prf")
if data:
by_creds = data.get("evalByCredential")
if by_creds:
if not allow_list:
raise ValueError("evalByCredentials requires allowList")
for cred in allow_list:
key = websafe_encode(cred.id)
if key in by_creds:
secrets = by_creds[key]
# Remove any other creds from the allow_list
allow_list.clear()
allow_list.append(cred)
break
else:
if not allow_credential:
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")
Expand Down

0 comments on commit 44f5e23

Please sign in to comment.