Skip to content

Commit

Permalink
Retry make_credential on PUAT_REQUIRED
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Oct 16, 2024
1 parent 7d131bb commit 4d8e7ac
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 64 deletions.
4 changes: 2 additions & 2 deletions examples/cred_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ def request_uv(self, permissions, rd_id):
sys.exit(1)

# Prefer UV token if supported
if client.info.options.get("pinUvAuthToken") or client.info.options.get("uv"):
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator supports UV token")
print("Authenticator is configured for User Verification")


server = Fido2Server({"id": "example.com", "name": "Example RP"})
Expand Down
2 changes: 1 addition & 1 deletion examples/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def request_uv(self, permissions, rd_id):
client = Fido2Client(dev, "https://example.com", user_interaction=CliInteraction())

# Prefer UV if supported and configured
if client.info.options.get("uv") or client.info.options.get("pinUvAuthToken"):
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator supports User Verification")

Expand Down
4 changes: 2 additions & 2 deletions examples/large_blobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ def request_uv(self, permissions, rd_id):
sys.exit(1)

# Prefer UV token if supported
if client.info.options.get("pinUvAuthToken") or client.info.options.get("uv"):
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator supports UV token")
print("Authenticator is configured for User Verification")


server = Fido2Server({"id": "example.com", "name": "Example RP"})
Expand Down
4 changes: 2 additions & 2 deletions examples/resident_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ def request_uv(self, permissions, rd_id):
sys.exit(1)

# Prefer UV if supported and configured
if client.info.options.get("uv") or client.info.options.get("pinUvAuthToken"):
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator supports User Verification")
print("Authenticator is configured for User Verification")

server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct")

Expand Down
140 changes: 83 additions & 57 deletions fido2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,9 @@ def _should_use_uv(self, user_verification, permissions):
self.info.options.get(k) for k in ("uv", "clientPin", "bioEnroll")
)
mc = ClientPin.PERMISSION.MAKE_CREDENTIAL & permissions != 0
additional_perms = permissions & ~(
ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.GET_ASSERTION
)

if (
user_verification == UserVerificationRequirement.REQUIRED
Expand All @@ -561,6 +564,8 @@ def _should_use_uv(self, user_verification, permissions):
return True
elif mc and uv_configured and not self.info.options.get("makeCredUvNotRqd"):
return True
elif uv_configured and additional_perms:
return True
return False

def _get_token(
Expand Down Expand Up @@ -597,12 +602,16 @@ def _get_auth_params(
pin_protocol = None
pin_token = None
internal_uv = False
additional_perms = permissions & ~(
ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.GET_ASSERTION
)
if self._should_use_uv(user_verification, permissions) or additional_perms:
if self._should_use_uv(user_verification, permissions):
client_pin = ClientPin(self.ctap2)
allow_internal_uv = not additional_perms
allow_internal_uv = (
permissions
& ~(
ClientPin.PERMISSION.MAKE_CREDENTIAL
| ClientPin.PERMISSION.GET_ASSERTION
)
== 0
)
pin_token = self._get_token(
client_pin, permissions, rp_id, event, on_keepalive, allow_internal_uv
)
Expand Down Expand Up @@ -635,66 +644,83 @@ def do_make_credential(

# Get extension permissions
extension_instances = [cls(self.ctap2) for cls in self.extensions]
used_extensions = []
client_inputs = extensions or {}
for ext in extension_instances:
permissions |= ext.get_create_permissions(client_inputs)

# Handle auth
pin_protocol, pin_token, internal_uv = self._get_auth_params(
rp.id, user_verification, permissions, event, on_keepalive
)

if exclude_list:
cred_list = self._filter_creds(
rp.id, exclude_list, pin_protocol, pin_token, event, on_keepalive
def _do_make():
# Handle auth
pin_protocol, pin_token, internal_uv = self._get_auth_params(
rp.id, user_verification, permissions, event, on_keepalive
)
else:
cred_list = None

# Process extensions
extension_inputs = {}
used_extensions = []
permissions = ClientPin.PERMISSION.MAKE_CREDENTIAL
try:
for ext in extension_instances:
auth_input = ext.process_create_input(client_inputs)
if auth_input is not None:
used_extensions.append(ext)
extension_inputs[ext.NAME] = auth_input
except ValueError as e:
raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e)
if exclude_list:
cred_list = self._filter_creds(
rp.id, exclude_list, pin_protocol, pin_token, event, on_keepalive
)
else:
cred_list = None

if not (rk or internal_uv):
options = None
else:
options = {}
if rk:
options["rk"] = True
if internal_uv:
options["uv"] = True

# Calculate pin_auth
client_data_hash = client_data.hash
if pin_token:
pin_auth = pin_protocol.authenticate(pin_token, client_data_hash)
else:
pin_auth = None
# Process extensions
extension_inputs = {}
try:
for ext in extension_instances:
auth_input = ext.process_create_input(client_inputs)
if auth_input is not None:
used_extensions.append(ext)
extension_inputs[ext.NAME] = auth_input
except ValueError as e:
raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e)

# Perform make credential
att_obj = self.ctap2.make_credential(
client_data_hash,
_as_cbor(rp),
_as_cbor(user),
_cbor_list(key_params),
_cbor_list(cred_list),
extension_inputs or None,
options,
pin_auth,
pin_protocol.VERSION if pin_protocol else None,
enterprise_attestation,
event=event,
on_keepalive=on_keepalive,
)
if not (rk or internal_uv):
options = None
else:
options = {}
if rk:
options["rk"] = True
if internal_uv:
options["uv"] = True

# Calculate pin_auth
client_data_hash = client_data.hash
if pin_token:
pin_auth = pin_protocol.authenticate(pin_token, client_data_hash)
else:
pin_auth = None

# Perform make credential
return (
self.ctap2.make_credential(
client_data_hash,
_as_cbor(rp),
_as_cbor(user),
_cbor_list(key_params),
_cbor_list(cred_list),
extension_inputs or None,
options,
pin_auth,
pin_protocol.VERSION if pin_protocol else None,
enterprise_attestation,
event=event,
on_keepalive=on_keepalive,
),
pin_protocol,
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
att_obj, pin_protocol, pin_token = _do_make()
else:
raise

# Process extenstion outputs
extension_outputs = {}
Expand Down

0 comments on commit 4d8e7ac

Please sign in to comment.