Skip to content

Commit

Permalink
Support predefined values for ExtensionProcessors
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Nov 18, 2024
1 parent 912cdb6 commit fb01773
Showing 1 changed file with 61 additions and 37 deletions.
98 changes: 61 additions & 37 deletions fido2/ctap2/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,19 @@ def __repr__(self):
return repr(dict(self))


class RegistrationExtensionProcessor:
class ExtensionProcessor(abc.ABC):
def __init__(
self,
permissions: ClientPin.PERMISSION = ClientPin.PERMISSION(0),
inputs: Optional[Dict[str, Any]] = None,
outputs: Optional[Dict[str, Any]] = None,
):
self.permissions = permissions
self._inputs = inputs
self._outputs = outputs


class RegistrationExtensionProcessor(ExtensionProcessor):
"""Processing state for a CTAP2 extension, for single use.
The ExtensionProcessor holds state and logic for client processing of an extension,
Expand All @@ -87,12 +99,9 @@ class RegistrationExtensionProcessor:
:param permissions: PinUvAuthToken permissions required by the extension.
"""

def __init__(self, permissions: ClientPin.PERMISSION = ClientPin.PERMISSION(0)):
self.permissions = permissions

def prepare_inputs(self) -> Optional[Dict[str, Any]]:
"Prepare authenticator extension inputs, to be passed to the Authenenticator."
return None
return self._inputs

def prepare_outputs(
self,
Expand All @@ -101,10 +110,10 @@ def prepare_outputs(
pin_protocol: Optional[PinProtocol],
) -> Optional[Dict[str, Any]]:
"Prepare client extension outputs, to be returned to the caller."
return None
return self._outputs


class AuthenticationExtensionProcessor:
class AuthenticationExtensionProcessor(ExtensionProcessor):
"""Processing state for a CTAP2 extension, for single use.
The ExtensionProcessor holds state and logic for client processing of an extension,
Expand All @@ -113,14 +122,11 @@ class AuthenticationExtensionProcessor:
:param permissions: PinUvAuthToken permissions required by the extension.
"""

def __init__(self, permissions: ClientPin.PERMISSION = ClientPin.PERMISSION(0)):
self.permissions = permissions

def prepare_inputs(
self, selected: Optional[PublicKeyCredentialDescriptor]
) -> Optional[Dict[str, Any]]:
"Prepare authenticator extension inputs, to be passed to the Authenenticator."
return None
return self._inputs

def prepare_outputs(
self,
Expand All @@ -129,7 +135,7 @@ def prepare_outputs(
pin_protocol: Optional[PinProtocol],
) -> Optional[Dict[str, Any]]:
"Prepare client extension outputs, to be returned to the caller."
return None
return self._outputs


# TODO 2.0: Make changes as described below
Expand Down Expand Up @@ -605,9 +611,6 @@ def get_assertion(self, ctap, options):
raise ValueError("Authenticator does not support large blob storage")

class Processor(AuthenticationExtensionProcessor):
def prepare_inputs(self, selected):
return {LargeBlobExtension.NAME: True}

def prepare_outputs(self, response, pin_token, pin_protocol):
blob_key = response.large_blob_key
if blob_key:
Expand All @@ -621,9 +624,12 @@ def prepare_outputs(self, response, pin_token, pin_protocol):
return {"largeBlob": _LargeBlobOutputs(written=True)}

return Processor(
ClientPin.PERMISSION.LARGE_BLOB_WRITE
if data.write
else ClientPin.PERMISSION(0)
(
ClientPin.PERMISSION.LARGE_BLOB_WRITE
if data.write
else ClientPin.PERMISSION(0)
),
inputs={LargeBlobExtension.NAME: True},
)


Expand All @@ -645,6 +651,19 @@ def process_get_input(self, inputs):
if self.is_supported() and inputs.get("getCredBlob") is True:
return True

def make_credential(self, ctap, options):
inputs = options.extensions or {}
if self.is_supported():
blob = inputs.get("credBlob")
assert ctap.info.max_cred_blob_length is not None # nosec
if blob and len(blob) <= ctap.info.max_cred_blob_length:
return RegistrationExtensionProcessor(inputs={self.NAME: blob})

def get_assertion(self, ctap, options):
inputs = options.extensions or {}
if self.is_supported(ctap) and inputs.get("getCredBlob") is True:
return AuthenticationExtensionProcessor(inputs={self.NAME: True})


class CredProtectExtension(Ctap2Extension):
"""
Expand All @@ -671,6 +690,19 @@ def process_create_input(self, inputs):
raise ValueError("Authenticator does not support Credential Protection")
return index + 1

def make_credential(self, ctap, options):
inputs = options.extensions or {}
policy = inputs.get("credentialProtectionPolicy")
if policy:
index = list(CredProtectExtension.POLICY).index(
CredProtectExtension.POLICY(policy)
)
enforce = inputs.get("enforceCredentialProtectionPolicy", False)
if enforce and not self.is_supported(ctap) and index > 0:
raise ValueError("Authenticator does not support Credential Protection")

return RegistrationExtensionProcessor(inputs={self.NAME: index + 1})


class MinPinLengthExtension(Ctap2Extension):
"""
Expand All @@ -692,12 +724,7 @@ def process_create_input(self, inputs):
def make_credential(self, ctap, options):
inputs = options.extensions or {}
if self.is_supported(ctap) and inputs.get(self.NAME) is True:

class Processor(RegistrationExtensionProcessor):
def prepare_inputs(self):
return {MinPinLengthExtension.NAME: True}

return Processor()
return RegistrationExtensionProcessor(inputs={self.NAME: True})


@dataclass(eq=False, frozen=True)
Expand All @@ -718,17 +745,14 @@ def is_supported(self, ctap=None): # NB: There is no key in the extensions fiel
def make_credential(self, ctap, options):
inputs = options.extensions or {}
if inputs.get(self.NAME) is True:
selection = (
options.authenticator_selection or AuthenticatorSelectionCriteria()
)
rk = selection.resident_key == ResidentKeyRequirement.REQUIRED or (
selection.resident_key == ResidentKeyRequirement.PREFERRED
and ctap.info.options.get("rk")
)

class Processor(RegistrationExtensionProcessor):
def prepare_outputs(self, response, pin_token, pin_protocol):
selection = (
options.authenticator_selection
or AuthenticatorSelectionCriteria()
)
rk = selection.resident_key == ResidentKeyRequirement.REQUIRED or (
selection.resident_key == ResidentKeyRequirement.PREFERRED
and ctap.info.options.get("rk")
)
return {CredPropsExtension.NAME: _CredPropsOutputs(rk=rk)}

return Processor()
return RegistrationExtensionProcessor(
outputs={self.NAME: _CredPropsOutputs(rk=rk)}
)

0 comments on commit fb01773

Please sign in to comment.