Skip to content

Commit

Permalink
Improve docstrings
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Nov 25, 2024
1 parent 55b0b22 commit c38aa49
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 16 deletions.
19 changes: 15 additions & 4 deletions fido2/attestation/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,24 @@


class InvalidAttestation(Exception):
pass
"""Base exception for attestation-related errors."""


class InvalidData(InvalidAttestation):
pass
"""Attestation contains invalid data."""


class InvalidSignature(InvalidAttestation):
pass
"""The signature of the attestation could not be verified."""


class UntrustedAttestation(InvalidAttestation):
pass
"""The CA of the attestation is not trusted."""


class UnsupportedType(InvalidAttestation):
"""The attestation format is not supported."""

def __init__(self, auth_data, fmt=None):
super().__init__(
f'Attestation format "{fmt}" is not supported'
Expand All @@ -69,6 +71,8 @@ def __init__(self, auth_data, fmt=None):

@unique
class AttestationType(IntEnum):
"""Supported attestation types."""

BASIC = 1
SELF = 2
ATT_CA = 3
Expand All @@ -78,11 +82,15 @@ class AttestationType(IntEnum):

@dataclass
class AttestationResult:
"""The result of verifying an attestation."""

attestation_type: AttestationType
trust_path: List[bytes]


def catch_builtins(f):
"""Utility decoractor to wrap common exceptions related to InvalidData."""

@wraps(f)
def inner(*args, **kwargs):
try:
Expand Down Expand Up @@ -129,6 +137,8 @@ def verify_x509_chain(chain: List[bytes]) -> None:


class Attestation(abc.ABC):
"""Implements verification of a specific attestation type."""

@abc.abstractmethod
def verify(
self,
Expand All @@ -143,6 +153,7 @@ def verify(

@staticmethod
def for_type(fmt: str) -> Type[Attestation]:
"""Get an Attestation subclass type for the given format."""
for cls in Attestation.__subclasses__():
if getattr(cls, "FORMAT", None) == fmt:
return cls
Expand Down
31 changes: 24 additions & 7 deletions fido2/cbor.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
"""
Minimal CBOR implementation supporting a subset of functionality and types
required for FIDO 2 CTAP.
Use the :func:`encode`, :func:`decode` and :func:`decode_from` functions to encode
and decode objects to/from CBOR.
DO NOT use the dump_x/load_x functions directly, these will be made private in
python-fido2 2.0.
"""

from __future__ import annotations
Expand All @@ -40,6 +46,7 @@
CborType = Union[int, bool, str, bytes, Sequence[Any], Mapping[Any, Any]]


# TODO 2.0: Make dump_x/load_x functions private
def dump_int(data: int, mt: int = 0) -> bytes:
if data < 0:
mt = 1
Expand Down Expand Up @@ -97,13 +104,6 @@ def dump_text(data: str) -> bytes:
]


def encode(data: CborType) -> bytes:
for k, v in _SERIALIZERS:
if isinstance(data, k):
return v(data)
raise ValueError(f"Unsupported value: {data!r}")


def load_int(ai: int, data: bytes) -> Tuple[int, bytes]:
if ai < 24:
return ai, data
Expand Down Expand Up @@ -167,12 +167,29 @@ def load_map(ai: int, data: bytes) -> Tuple[Mapping[CborType, CborType], bytes]:
}


def encode(data: CborType) -> bytes:
"""Encodes data to a CBOR byte string."""
for k, v in _SERIALIZERS:
if isinstance(data, k):
return v(data)
raise ValueError(f"Unsupported value: {data!r}")


def decode_from(data: bytes) -> Tuple[Any, bytes]:
"""Decodes a CBOR-encoded value from the start of a byte string.
Additional data after a valid CBOR object is returned as well.
:return: The decoded object, and any remaining data."""
fb = data[0]
return _DESERIALIZERS[fb >> 5](fb & 0b11111, data[1:])


def decode(data) -> CborType:
"""Decodes data from a CBOR-encoded byte string.
Also validates that no extra data follows the encoded object.
"""
value, rest = decode_from(data)
if rest != b"":
raise ValueError("Extraneous data")
Expand Down
8 changes: 8 additions & 0 deletions fido2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,12 @@


class ClientError(Exception):
"""Base error raised by clients."""

@unique
class ERR(IntEnum):
"""Error codes for ClientError."""

OTHER_ERROR = 1
BAD_REQUEST = 2
CONFIGURATION_UNSUPPORTED = 3
Expand Down Expand Up @@ -142,6 +146,8 @@ def _ctap2client_err(e, err_cls=ClientError):


class PinRequiredError(ClientError):
"""Raised when a call cannot be completed without providing PIN."""

def __init__(
self, code=ClientError.ERR.BAD_REQUEST, cause="PIN required but not provided"
):
Expand Down Expand Up @@ -228,6 +234,8 @@ def get_response(self, index: int) -> AuthenticatorAssertionResponse:


class WebAuthnClient(abc.ABC):
"""Base class for a WebAuthn client, supporting registration and authentication."""

@abc.abstractmethod
def make_credential(
self,
Expand Down
19 changes: 16 additions & 3 deletions fido2/ctap.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@

@unique
class STATUS(IntEnum):
"""Status code for CTAP keep-alive message."""

PROCESSING = 1
UPNEEDED = 2


class CtapDevice(abc.ABC):
"""
CTAP-capable device. Subclasses of this should implement call, as well as
list_devices, which should return a generator over discoverable devices.
CTAP-capable device.
Subclasses of this should implement :func:`call`, as well as :func:`list_devices`,
which should return a generator over discoverable devices.
"""

@property
Expand All @@ -57,7 +61,7 @@ def call(
cmd: int,
data: bytes = b"",
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
on_keepalive: Optional[Callable[[STATUS], None]] = None,
) -> bytes:
"""Sends a command to the authenticator, and reads the response.
Expand Down Expand Up @@ -87,7 +91,11 @@ def list_devices(cls) -> Iterator[CtapDevice]:


class CtapError(Exception):
"""Error returned from the Authenticator when a command fails."""

class UNKNOWN_ERR(int):
"""CTAP error status code that is not recognized."""

name = "UNKNOWN_ERR"

@property
Expand All @@ -102,6 +110,11 @@ def __str__(self):

@unique
class ERR(IntEnum):
"""CTAP status codes.
https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html#error-responses
"""

SUCCESS = 0x00
INVALID_COMMAND = 0x01
INVALID_PARAMETER = 0x02
Expand Down
2 changes: 1 addition & 1 deletion fido2/hid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def call(
cmd: int,
data: bytes = b"",
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
on_keepalive: Optional[Callable[[STATUS], None]] = None,
) -> bytes:
event = event or Event()

Expand Down
2 changes: 2 additions & 0 deletions fido2/mds3.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ class EcdaaTrustAnchor(_JsonDataObject):

@unique
class AuthenticatorStatus(str, Enum):
"""Status of an Authenitcator."""

NOT_FIDO_CERTIFIED = "NOT_FIDO_CERTIFIED"
FIDO_CERTIFIED = "FIDO_CERTIFIED"
USER_VERIFICATION_BYPASS = "USER_VERIFICATION_BYPASS"
Expand Down
6 changes: 5 additions & 1 deletion fido2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ def read(self, size: Optional[int] = -1) -> bytes:


class _DataClassMapping(Mapping[_T, Any]):
# TODO: This requires Python 3.9, and fixes the tpye errors we now ignore
"""A data class with members also accessible as a Mapping."""

# TODO: This requires Python 3.9, and fixes the type errors we now ignore
# __dataclass_fields__: ClassVar[Dict[str, Field[Any]]]

def __post_init__(self):
Expand Down Expand Up @@ -299,6 +301,8 @@ def from_dict(cls, data):


class _JsonDataObject(_DataClassMapping[str]):
"""A data class with members also accessible as a JSON-serializable Mapping."""

@classmethod
def _get_field_key(cls, field: Field) -> str:
name = field.metadata.get("name")
Expand Down

0 comments on commit c38aa49

Please sign in to comment.