mirror of https://github.com/Yubico/python-fido2
630 lines
24 KiB
Python
630 lines
24 KiB
Python
# Copyright (c) 2018 Yubico AB
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or
|
|
# without modification, are permitted provided that the following
|
|
# conditions are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above
|
|
# copyright notice, this list of conditions and the following
|
|
# disclaimer in the documentation and/or other materials provided
|
|
# with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
from .. import cbor
|
|
from ..ctap import CtapDevice, CtapError
|
|
from ..cose import CoseKey
|
|
from ..hid import CTAPHID, CAPABILITY
|
|
from ..webauthn import AuthenticatorData
|
|
|
|
from enum import IntEnum, unique
|
|
from dataclasses import dataclass, field, fields, MISSING
|
|
from threading import Event
|
|
from typing import Mapping, Dict, Any, List, Optional, Type, TypeVar, Callable
|
|
import struct
|
|
|
|
|
|
def args(*params) -> Dict[int, Any]:
|
|
"""Constructs a dict from a list of arguments for sending a CBOR command.
|
|
None elements will be omitted.
|
|
|
|
:param params: Arguments, in order, to add to the command.
|
|
:return: The input parameters as a dict.
|
|
"""
|
|
return dict((i, v) for i, v in enumerate(params, 1) if v is not None)
|
|
|
|
|
|
_T = TypeVar("_T", bound="_CborDataObject")
|
|
|
|
|
|
@dataclass(init=False)
|
|
class _CborDataObject(Mapping[int, Any]):
|
|
def __init__(self, data: Mapping[int, Any]):
|
|
self._data = dict(data)
|
|
for f in fields(self):
|
|
k = f.metadata.get("cbor_field")
|
|
if k:
|
|
transform = f.metadata["transform"]
|
|
if k in data:
|
|
v = data[k]
|
|
elif f.default is not MISSING:
|
|
v = f.default
|
|
elif f.default_factory is not MISSING: # type: ignore
|
|
v = f.default_factory() # type: ignore
|
|
# see https://github.com/python/mypy/issues/6910
|
|
else:
|
|
raise TypeError(
|
|
"Input data missing required field %s: %s" % (k, f.name)
|
|
)
|
|
setattr(self, f.name, transform(v))
|
|
|
|
@classmethod
|
|
def parse(cls: Type[_T], binary: bytes) -> _T:
|
|
decoded = cbor.decode(binary)
|
|
if isinstance(decoded, Mapping):
|
|
return cls(decoded) # type: ignore
|
|
raise TypeError("Decoded value of incorrect type!")
|
|
|
|
@classmethod
|
|
def create(cls: Type[_T], **kwargs) -> _T:
|
|
data = {}
|
|
fs = {f.name: f.metadata for f in fields(cls)}
|
|
for name, v in kwargs.items():
|
|
k = fs[name]["cbor_field"]
|
|
data[k] = v
|
|
return cls(data)
|
|
|
|
def __getitem__(self, key):
|
|
return self._data[key]
|
|
|
|
def __iter__(self):
|
|
return iter(self._data)
|
|
|
|
def __len__(self):
|
|
return len(self._data)
|
|
|
|
|
|
def cbor_field(key: int, *, transform=lambda x: x, **kwargs):
|
|
return field(
|
|
init=False, metadata={"cbor_field": key, "transform": transform}, **kwargs
|
|
)
|
|
|
|
|
|
@dataclass(init=False)
|
|
class Info(_CborDataObject):
|
|
"""Binary CBOR encoded response data returned by the CTAP2 GET_INFO command.
|
|
|
|
:param _: The binary content of the Info data.
|
|
:ivar versions: The versions supported by the authenticator.
|
|
:ivar extensions: The extensions supported by the authenticator.
|
|
:ivar aaguid: The AAGUID of the authenticator.
|
|
:ivar options: The options supported by the authenticator.
|
|
:ivar max_msg_size: The max message size supported by the authenticator.
|
|
:ivar pin_uv_protocols: The PIN/UV protocol versions supported by the authenticator.
|
|
:ivar max_creds_in_list: Max number of credentials supported in list at a time.
|
|
:ivar max_cred_id_length: Max length of Credential ID supported.
|
|
:ivar transports: List of supported transports.
|
|
:ivar algorithms: List of supported algorithms for credential creation.
|
|
:ivar data: The Info members, in the form of a dict.
|
|
"""
|
|
|
|
versions: List[str] = cbor_field(0x01)
|
|
extensions: List[str] = cbor_field(0x02, default_factory=list)
|
|
aaguid: bytes = cbor_field(0x03)
|
|
options: Dict[str, bool] = cbor_field(0x04, default_factory=dict)
|
|
max_msg_size: int = cbor_field(0x05, default=1024)
|
|
pin_uv_protocols: List[int] = cbor_field(0x06, default_factory=list)
|
|
max_creds_in_list: Optional[int] = cbor_field(0x07, default=None)
|
|
max_cred_id_length: Optional[int] = cbor_field(0x08, default=None)
|
|
transports: List[str] = cbor_field(0x09, default_factory=list)
|
|
algorithms: Optional[List[int]] = cbor_field(0x0A, default=None)
|
|
max_large_blob: Optional[int] = cbor_field(0x0B, default=None)
|
|
force_pin_change: bool = cbor_field(0x0C, default=False)
|
|
min_pin_length: int = cbor_field(0x0D, default=4)
|
|
firmware_version: Optional[int] = cbor_field(0x0E, default=None)
|
|
max_cred_blob_length: Optional[int] = cbor_field(0x0F, default=None)
|
|
max_rpids_for_min_pin: int = cbor_field(0x10, default=0)
|
|
preferred_platform_uv_attempts: int = cbor_field(0x11, default=None)
|
|
uv_modality: Optional[int] = cbor_field(0x12, default=None)
|
|
certifications: Optional[Dict] = cbor_field(0x13, default=None)
|
|
remaining_disc_creds: Optional[int] = cbor_field(0x14, default=None)
|
|
|
|
|
|
@dataclass(init=False)
|
|
class AttestationResponse(_CborDataObject):
|
|
"""Binary CBOR encoded attestation object.
|
|
|
|
:param _: The binary representation of the attestation object.
|
|
:type _: bytes
|
|
:ivar fmt: The type of attestation used.
|
|
:type fmt: str
|
|
:ivar auth_data: The attested authenticator data.
|
|
:type auth_data: AuthenticatorData
|
|
:ivar att_stmt: The attestation statement.
|
|
:type att_stmt: Dict[str, Any]
|
|
"""
|
|
|
|
fmt: str = cbor_field(0x01)
|
|
auth_data: AuthenticatorData = cbor_field(0x02, transform=AuthenticatorData)
|
|
att_stmt: Dict[str, Any] = cbor_field(0x03)
|
|
ep_att: Optional[bool] = cbor_field(0x04, default=None)
|
|
large_blob_key: Optional[bytes] = cbor_field(0x05, default=None)
|
|
|
|
|
|
@dataclass(init=False)
|
|
class AssertionResponse(_CborDataObject):
|
|
"""Binary CBOR encoded assertion response.
|
|
|
|
:param _: The binary representation of the assertion response.
|
|
:ivar credential: The credential used for the assertion.
|
|
:ivar auth_data: The authenticator data part of the response.
|
|
:ivar signature: The digital signature of the assertion.
|
|
:ivar user: The user data of the credential.
|
|
:ivar number_of_credentials: The total number of responses available
|
|
(only set for the first response, if > 1).
|
|
"""
|
|
|
|
credential: Dict[str, Any] = cbor_field(0x01)
|
|
auth_data: AuthenticatorData = cbor_field(0x02, transform=AuthenticatorData)
|
|
signature: bytes = cbor_field(0x03)
|
|
user: Optional[Dict[str, Any]] = cbor_field(0x04, default=None)
|
|
number_of_credentials: Optional[int] = cbor_field(0x05, default=None)
|
|
user_selected: Optional[bool] = cbor_field(0x06, default=None)
|
|
large_blob_key: Optional[bytes] = cbor_field(0x07, default=None)
|
|
|
|
def verify(self, client_param: bytes, public_key: CoseKey):
|
|
"""Verify the digital signature of the response with regard to the
|
|
client_param, using the given public key.
|
|
|
|
:param client_param: SHA256 hash of the ClientData used for the request.
|
|
:param public_key: The public key of the credential, to verify.
|
|
"""
|
|
public_key.verify(self.auth_data + client_param, self.signature)
|
|
|
|
@classmethod
|
|
def from_ctap1(
|
|
cls, app_param: bytes, credential: Dict[str, Any], authentication
|
|
) -> "AssertionResponse":
|
|
"""Create an AssertionResponse from a CTAP1 SignatureData instance.
|
|
|
|
:param app_param: SHA256 hash of the RP ID used for the CTAP1 request.
|
|
:param credential: Credential used for the CTAP1 request (from the
|
|
allowList).
|
|
:param authentication: The CTAP1 signature data.
|
|
:return: The assertion response.
|
|
"""
|
|
return cls.create(
|
|
credential=credential,
|
|
auth_data=AuthenticatorData.create(
|
|
app_param,
|
|
authentication.user_presence & AuthenticatorData.FLAG.USER_PRESENT,
|
|
authentication.counter,
|
|
),
|
|
signature=authentication.signature,
|
|
)
|
|
|
|
|
|
class Ctap2:
|
|
"""Implementation of the CTAP2 specification.
|
|
|
|
:param device: A CtapHidDevice handle supporting CTAP2.
|
|
:param strict_cbor: Validate that CBOR returned from the Authenticator is
|
|
canonical, defaults to True.
|
|
"""
|
|
|
|
@unique
|
|
class CMD(IntEnum):
|
|
MAKE_CREDENTIAL = 0x01
|
|
GET_ASSERTION = 0x02
|
|
GET_INFO = 0x04
|
|
CLIENT_PIN = 0x06
|
|
RESET = 0x07
|
|
GET_NEXT_ASSERTION = 0x08
|
|
BIO_ENROLLMENT = 0x09
|
|
CREDENTIAL_MGMT = 0x0A
|
|
SELECTION = 0x0B
|
|
LARGE_BLOBS = 0x0C
|
|
CONFIG = 0x0D
|
|
|
|
BIO_ENROLLMENT_PRE = 0x40
|
|
CREDENTIAL_MGMT_PRE = 0x41
|
|
|
|
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
|
|
if not device.capabilities & CAPABILITY.CBOR:
|
|
raise ValueError("Device does not support CTAP2.")
|
|
self.device = device
|
|
self._strict_cbor = strict_cbor
|
|
self._info = self.get_info()
|
|
|
|
@property
|
|
def info(self) -> Info:
|
|
"""Get a cached Info object which can be used to determine capabilities.
|
|
|
|
:rtype: Info
|
|
:return: The response of calling GetAuthenticatorInfo.
|
|
"""
|
|
return self._info
|
|
|
|
def send_cbor(
|
|
self,
|
|
cmd: int,
|
|
data: Optional[Mapping[int, Any]] = None,
|
|
event: Optional[Event] = None,
|
|
on_keepalive: Optional[Callable[[int], None]] = None,
|
|
) -> Mapping[int, Any]:
|
|
"""Sends a CBOR message to the device, and waits for a response.
|
|
|
|
:param cmd: The command byte of the request.
|
|
:param data: The payload to send (to be CBOR encoded).
|
|
:param event: Optional threading.Event used to cancel the request.
|
|
:param on_keepalive: Optional function called when keep-alive is sent by
|
|
the authenticator.
|
|
"""
|
|
request = struct.pack(">B", cmd)
|
|
if data is not None:
|
|
request += cbor.encode(data)
|
|
response = self.device.call(CTAPHID.CBOR, request, event, on_keepalive)
|
|
status = response[0]
|
|
if status != 0x00:
|
|
raise CtapError(status)
|
|
enc = response[1:]
|
|
if not enc:
|
|
return {}
|
|
decoded = cbor.decode(enc)
|
|
if self._strict_cbor:
|
|
expected = cbor.encode(decoded)
|
|
if expected != enc:
|
|
enc_h = enc.hex()
|
|
exp_h = expected.hex()
|
|
raise ValueError(
|
|
"Non-canonical CBOR from Authenticator.\n"
|
|
"Got: {}\n".format(enc_h) + "Expected: {}".format(exp_h)
|
|
)
|
|
if isinstance(decoded, Mapping):
|
|
return decoded
|
|
raise TypeError("Decoded value of wrong type")
|
|
|
|
def get_info(self) -> Info:
|
|
"""CTAP2 getInfo command.
|
|
|
|
:return: Information about the authenticator.
|
|
"""
|
|
return Info(self.send_cbor(Ctap2.CMD.GET_INFO))
|
|
|
|
def client_pin(
|
|
self,
|
|
pin_uv_protocol: int,
|
|
sub_cmd: int,
|
|
key_agreement: Optional[Mapping[int, Any]] = None,
|
|
pin_uv_param: Optional[bytes] = None,
|
|
new_pin_enc: Optional[bytes] = None,
|
|
pin_hash_enc: Optional[bytes] = None,
|
|
permissions: Optional[int] = None,
|
|
permissions_rpid: Optional[str] = None,
|
|
event: Optional[Event] = None,
|
|
on_keepalive: Optional[Callable[[int], None]] = None,
|
|
):
|
|
"""CTAP2 clientPin command, used for various PIN operations.
|
|
|
|
This method is not intended to be called directly. It is intended to be used by
|
|
an instance of the PinProtocolV1 class.
|
|
|
|
:param pin_uv_protocol: The PIN/UV protocol version to use.
|
|
:param sub_cmd: A clientPin sub command.
|
|
:param key_agreement: The keyAgreement parameter.
|
|
:param pin_uv_param: The pinAuth parameter.
|
|
:param new_pin_enc: The newPinEnc parameter.
|
|
:param pin_hash_enc: The pinHashEnc parameter.
|
|
:param permissions: The permissions parameter.
|
|
:param permissions_rpid: The permissions RPID parameter.
|
|
:param event: Optional threading.Event used to cancel the request.
|
|
:param on_keepalive: Optional callback function to handle keep-alive
|
|
messages from the authenticator.
|
|
:return: The response of the command, decoded.
|
|
"""
|
|
return self.send_cbor(
|
|
Ctap2.CMD.CLIENT_PIN,
|
|
args(
|
|
pin_uv_protocol,
|
|
sub_cmd,
|
|
key_agreement,
|
|
pin_uv_param,
|
|
new_pin_enc,
|
|
pin_hash_enc,
|
|
None,
|
|
None,
|
|
permissions,
|
|
permissions_rpid,
|
|
),
|
|
event=event,
|
|
on_keepalive=on_keepalive,
|
|
)
|
|
|
|
def reset(
|
|
self,
|
|
event: Optional[Event] = None,
|
|
on_keepalive: Optional[Callable[[int], None]] = None,
|
|
):
|
|
"""CTAP2 reset command, erases all credentials and PIN.
|
|
|
|
:param event: Optional threading.Event object used to cancel the request.
|
|
:param on_keepalive: Optional callback function to handle keep-alive
|
|
messages from the authenticator.
|
|
"""
|
|
self.send_cbor(Ctap2.CMD.RESET, event=event, on_keepalive=on_keepalive)
|
|
|
|
def make_credential(
|
|
self,
|
|
client_data_hash: bytes,
|
|
rp: Mapping[str, Any],
|
|
user: Mapping[str, Any],
|
|
key_params: List[Mapping[str, Any]],
|
|
exclude_list: Optional[List[Mapping[str, Any]]] = None,
|
|
extensions: Optional[Mapping[str, Any]] = None,
|
|
options: Optional[Mapping[str, Any]] = None,
|
|
pin_uv_param: Optional[bytes] = None,
|
|
pin_uv_protocol: Optional[int] = None,
|
|
event: Optional[Event] = None,
|
|
on_keepalive: Optional[Callable[[int], None]] = None,
|
|
) -> AttestationResponse:
|
|
"""CTAP2 makeCredential operation.
|
|
|
|
:param client_data_hash: SHA256 hash of the ClientData.
|
|
:param rp: PublicKeyCredentialRpEntity parameters.
|
|
:param user: PublicKeyCredentialUserEntity parameters.
|
|
:param key_params: List of acceptable credential types.
|
|
:param exclude_list: Optional list of PublicKeyCredentialDescriptors.
|
|
:param extensions: Optional dict of extensions.
|
|
:param options: Optional dict of options.
|
|
:param pin_uv_param: Optional PIN/UV auth parameter.
|
|
:param pin_uv_protocol: The version of PIN/UV protocol used, if any.
|
|
:param event: Optional threading.Event used to cancel the request.
|
|
:param on_keepalive: Optional callback function to handle keep-alive
|
|
messages from the authenticator.
|
|
:return: The new credential.
|
|
"""
|
|
return AttestationResponse(
|
|
self.send_cbor(
|
|
Ctap2.CMD.MAKE_CREDENTIAL,
|
|
args(
|
|
client_data_hash,
|
|
rp,
|
|
user,
|
|
key_params,
|
|
exclude_list,
|
|
extensions,
|
|
options,
|
|
pin_uv_param,
|
|
pin_uv_protocol,
|
|
),
|
|
event,
|
|
on_keepalive,
|
|
)
|
|
)
|
|
|
|
def get_assertion(
|
|
self,
|
|
rp_id: str,
|
|
client_data_hash: bytes,
|
|
allow_list: Optional[List[Mapping[str, Any]]] = None,
|
|
extensions: Optional[Mapping[str, Any]] = None,
|
|
options: Optional[Mapping[str, Any]] = None,
|
|
pin_uv_param: Optional[bytes] = None,
|
|
pin_uv_protocol: Optional[int] = None,
|
|
event: Optional[Event] = None,
|
|
on_keepalive: Optional[Callable[[int], None]] = None,
|
|
) -> AssertionResponse:
|
|
"""CTAP2 getAssertion command.
|
|
|
|
:param rp_id: The RP ID of the credential.
|
|
:param client_data_hash: SHA256 hash of the ClientData used.
|
|
:param allow_list: Optional list of PublicKeyCredentialDescriptors.
|
|
:param extensions: Optional dict of extensions.
|
|
:param options: Optional dict of options.
|
|
:param pin_uv_param: Optional PIN/UV auth parameter.
|
|
:param pin_uv_protocol: The version of PIN/UV protocol used, if any.
|
|
:param event: Optional threading.Event used to cancel the request.
|
|
:param on_keepalive: Optional callback function to handle keep-alive messages
|
|
from the authenticator.
|
|
:return: The new assertion.
|
|
"""
|
|
return AssertionResponse(
|
|
self.send_cbor(
|
|
Ctap2.CMD.GET_ASSERTION,
|
|
args(
|
|
rp_id,
|
|
client_data_hash,
|
|
allow_list,
|
|
extensions,
|
|
options,
|
|
pin_uv_param,
|
|
pin_uv_protocol,
|
|
),
|
|
event,
|
|
on_keepalive,
|
|
)
|
|
)
|
|
|
|
def get_next_assertion(self) -> AssertionResponse:
|
|
"""CTAP2 getNextAssertion command.
|
|
|
|
:return: The next available assertion response.
|
|
"""
|
|
return AssertionResponse(self.send_cbor(Ctap2.CMD.GET_NEXT_ASSERTION))
|
|
|
|
def get_assertions(self, *args, **kwargs) -> List[AssertionResponse]:
|
|
"""Convenience method to get list of assertions.
|
|
|
|
See get_assertion and get_next_assertion for details.
|
|
"""
|
|
first = self.get_assertion(*args, **kwargs)
|
|
rest = [
|
|
self.get_next_assertion()
|
|
for _ in range(1, first.number_of_credentials or 1)
|
|
]
|
|
return [first] + rest
|
|
|
|
def credential_mgmt(
|
|
self,
|
|
sub_cmd: int,
|
|
sub_cmd_params: Optional[Mapping[int, Any]] = None,
|
|
pin_uv_protocol: Optional[int] = None,
|
|
pin_uv_param: Optional[bytes] = None,
|
|
):
|
|
"""CTAP2 credentialManagement command, used to manage resident
|
|
credentials.
|
|
|
|
NOTE: This implements the current draft version of the CTAP2 specification and
|
|
should be considered highly experimental.
|
|
|
|
This method is not intended to be called directly. It is intended to be used by
|
|
an instance of the CredentialManagement class.
|
|
|
|
:param sub_cmd: A CredentialManagement sub command.
|
|
:param sub_cmd_params: Sub command specific parameters.
|
|
:param pin_uv_protocol: PIN/UV auth protocol version used.
|
|
:param pin_uv_param: PIN/UV Auth parameter.
|
|
"""
|
|
if "credMgmt" in self.info.options:
|
|
cmd = Ctap2.CMD.CREDENTIAL_MGMT
|
|
elif "credentialMgmtPreview" in self.info.options:
|
|
cmd = Ctap2.CMD.CREDENTIAL_MGMT_PRE
|
|
else:
|
|
raise ValueError(
|
|
"Credential Management not supported by this Authenticator"
|
|
)
|
|
return self.send_cbor(
|
|
cmd,
|
|
args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param),
|
|
)
|
|
|
|
def bio_enrollment(
|
|
self,
|
|
modality: Optional[int] = None,
|
|
sub_cmd: Optional[int] = None,
|
|
sub_cmd_params: Optional[Mapping[int, Any]] = None,
|
|
pin_uv_protocol: Optional[int] = None,
|
|
pin_uv_param: Optional[bytes] = None,
|
|
get_modality: Optional[bool] = None,
|
|
event: Optional[Event] = None,
|
|
on_keepalive: Optional[Callable[[int], None]] = None,
|
|
):
|
|
"""CTAP2 bio enrollment command. Used to provision/enumerate/delete bio
|
|
enrollments in the authenticator.
|
|
|
|
NOTE: This implements the current draft version of the CTAP2 specification and
|
|
should be considered highly experimental.
|
|
|
|
This method is not intended to be called directly. It is intended to be used by
|
|
an instance of the BioEnrollment class.
|
|
|
|
:param modality: The user verification modality being used.
|
|
:param sub_cmd: A BioEnrollment sub command.
|
|
:param sub_cmd_params: Sub command specific parameters.
|
|
:param pin_uv_protocol: PIN/UV protocol version used.
|
|
:param pin_uv_param: PIN/UV auth param.
|
|
:param get_modality: Get the user verification type modality.
|
|
"""
|
|
if "bioEnroll" in self.info.options:
|
|
cmd = Ctap2.CMD.BIO_ENROLLMENT
|
|
elif "userVerificationMgmtPreview" in self.info.options:
|
|
cmd = Ctap2.CMD.BIO_ENROLLMENT_PRE
|
|
else:
|
|
raise ValueError("Authenticator does not support Bio Enroll")
|
|
return self.send_cbor(
|
|
cmd,
|
|
args(
|
|
modality,
|
|
sub_cmd,
|
|
sub_cmd_params,
|
|
pin_uv_protocol,
|
|
pin_uv_param,
|
|
get_modality,
|
|
),
|
|
event=event,
|
|
on_keepalive=on_keepalive,
|
|
)
|
|
|
|
def selection(
|
|
self,
|
|
event: Optional[Event] = None,
|
|
on_keepalive: Optional[Callable[[int], None]] = None,
|
|
):
|
|
"""CTAP2 authenticator selection command.
|
|
|
|
This command allows the platform to let a user select a certain authenticator
|
|
by asking for user presence.
|
|
|
|
:param event: Optional threading.Event used to cancel the request.
|
|
:param on_keepalive: Optional callback function to handle keep-alive messages
|
|
from the authenticator.
|
|
"""
|
|
self.send_cbor(Ctap2.CMD.SELECTION, event=event, on_keepalive=on_keepalive)
|
|
|
|
def large_blobs(
|
|
self,
|
|
offset: int,
|
|
get: Optional[int] = None,
|
|
set: Optional[bytes] = None,
|
|
length: Optional[int] = None,
|
|
pin_uv_param: Optional[bytes] = None,
|
|
pin_uv_protocol: Optional[int] = None,
|
|
):
|
|
"""CTAP2 authenticator large blobs command.
|
|
|
|
This command is used to read and write the large blob array.
|
|
|
|
This method is not intended to be called directly. It is intended to be used by
|
|
an instance of the LargeBlobs class.
|
|
|
|
:param offset: The offset of where to start reading/writing data.
|
|
:param get: Optional (max) length of data to read.
|
|
:param set: Optional data to write.
|
|
:param length: Length of the payload in set.
|
|
:param pin_uv_protocol: PIN/UV protocol version used.
|
|
:param pin_uv_param: PIN/UV auth param.
|
|
"""
|
|
return self.send_cbor(
|
|
Ctap2.CMD.LARGE_BLOBS,
|
|
args(get, set, offset, length, pin_uv_param, pin_uv_protocol),
|
|
)
|
|
|
|
def config(
|
|
self,
|
|
sub_cmd: int,
|
|
sub_cmd_params: Optional[Mapping[int, Any]] = None,
|
|
pin_uv_protocol: Optional[int] = None,
|
|
pin_uv_param: Optional[bytes] = None,
|
|
):
|
|
"""CTAP2 authenticator config command.
|
|
|
|
This command is used to configure various authenticator features through the
|
|
use of its subcommands.
|
|
|
|
This method is not intended to be called directly. It is intended to be used by
|
|
an instance of the Config class.
|
|
|
|
:param sub_cmd: A Config sub command.
|
|
:param sub_cmd_params: Sub command specific parameters.
|
|
:param pin_uv_protocol: PIN/UV auth protocol version used.
|
|
:param pin_uv_param: PIN/UV Auth parameter.
|
|
"""
|
|
return self.send_cbor(
|
|
Ctap2.CMD.CONFIG,
|
|
args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param),
|
|
)
|