diff --git a/core/src/apps/webauthn/__init__.py b/core/src/apps/webauthn/__init__.py index 401ce0d61..b1c3637d0 100644 --- a/core/src/apps/webauthn/__init__.py +++ b/core/src/apps/webauthn/__init__.py @@ -1,9 +1,6 @@ -from trezor import loop - -import usb - -from .fido2 import handle_reports - - def boot() -> None: + from trezor import loop + import usb + from .fido2 import handle_reports + loop.schedule(handle_reports(usb.iface_webauthn)) diff --git a/core/src/apps/webauthn/add_resident_credential.py b/core/src/apps/webauthn/add_resident_credential.py index 9c505359f..12e1836af 100644 --- a/core/src/apps/webauthn/add_resident_credential.py +++ b/core/src/apps/webauthn/add_resident_credential.py @@ -1,17 +1,11 @@ from typing import TYPE_CHECKING -import storage.device -from trezor import wire -from trezor.messages import Success from trezor.ui.components.common.webauthn import ConfirmInfo -from trezor.ui.layouts import show_error_and_raise -from trezor.ui.layouts.webauthn import confirm_webauthn - -from .credential import Fido2Credential -from .resident_credentials import store_resident_credential if TYPE_CHECKING: - from trezor.messages import WebAuthnAddResidentCredential + from trezor.messages import WebAuthnAddResidentCredential, Success + from trezor.wire import Context + from .credential import Fido2Credential class ConfirmAddCredential(ConfirmInfo): @@ -31,9 +25,17 @@ class ConfirmAddCredential(ConfirmInfo): async def add_resident_credential( - ctx: wire.Context, msg: WebAuthnAddResidentCredential + ctx: Context, msg: WebAuthnAddResidentCredential ) -> Success: - if not storage.device.is_initialized(): + import storage.device as storage_device + from trezor import wire + from trezor.ui.layouts import show_error_and_raise + from trezor.ui.layouts.webauthn import confirm_webauthn + from trezor.messages import Success + from .credential import Fido2Credential + from .resident_credentials import store_resident_credential + + if not storage_device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if not msg.credential_id: raise wire.ProcessError("Missing credential ID parameter.") @@ -44,9 +46,9 @@ async def add_resident_credential( await show_error_and_raise( ctx, "warning_credential", - header="Import credential", + "The credential you are trying to import does\nnot belong to this authenticator.", + "Import credential", button="Close", - content="The credential you are trying to import does\nnot belong to this authenticator.", red=True, ) diff --git a/core/src/apps/webauthn/credential.py b/core/src/apps/webauthn/credential.py index 1bb627a7e..aa7eae6c5 100644 --- a/core/src/apps/webauthn/credential.py +++ b/core/src/apps/webauthn/credential.py @@ -1,21 +1,26 @@ import ustruct from micropython import const -from typing import Iterable +from typing import TYPE_CHECKING from ubinascii import hexlify -import storage.device -from trezor import log, utils -from trezor.crypto import bip32, chacha20poly1305, der, hashlib, hmac, random +import storage.device as storage_device +from trezor import utils +from trezor.crypto import chacha20poly1305, der, hashlib, hmac, random from trezor.crypto.curve import ed25519, nist256p1 from apps.common import cbor, seed from apps.common.paths import HARDENED -from . import common +from .common import COSE_ALG_EDDSA, COSE_ALG_ES256, COSE_CURVE_ED25519, COSE_CURVE_P256 + +if TYPE_CHECKING: + from typing import Iterable + from trezor.crypto import bip32 + # Credential ID values _CRED_ID_VERSION = b"\xf1\xd0\x02\x00" -CRED_ID_MIN_LENGTH = const(33) +_CRED_ID_MIN_LENGTH = const(33) CRED_ID_MAX_LENGTH = const(1024) _KEY_HANDLE_LENGTH = const(64) @@ -24,7 +29,7 @@ _USER_ID_MAX_LENGTH = const(64) # Maximum supported length of the RP name, user name or user displayName in bytes. # Note: The WebAuthn spec allows authenticators to truncate to 64 bytes or more. -NAME_MAX_LENGTH = const(100) +_NAME_MAX_LENGTH = const(100) # Credential ID keys _CRED_ID_RP_ID = const(1) @@ -39,13 +44,13 @@ _CRED_ID_ALGORITHM = const(9) _CRED_ID_CURVE = const(10) # Defaults -_DEFAULT_ALGORITHM = common.COSE_ALG_ES256 -_DEFAULT_CURVE = common.COSE_CURVE_P256 +_DEFAULT_ALGORITHM = COSE_ALG_ES256 +_DEFAULT_CURVE = COSE_CURVE_P256 # Curves _CURVE_NAME = { - common.COSE_CURVE_ED25519: "ed25519", - common.COSE_CURVE_P256: "nist256p1", + COSE_CURVE_ED25519: "ed25519", + COSE_CURVE_P256: "nist256p1", } # Key paths @@ -92,7 +97,7 @@ class Credential: return None def next_signature_counter(self) -> int: - return storage.device.next_u2f_counter() or 0 + return storage_device.next_u2f_counter() or 0 @staticmethod def from_bytes(data: bytes, rp_id_hash: bytes) -> "Credential": @@ -128,7 +133,7 @@ class Fido2Credential(Credential): return True def generate_id(self) -> None: - self.creation_time = storage.device.next_u2f_counter() or 0 + self.creation_time = storage_device.next_u2f_counter() or 0 if not self.check_required_fields(): raise AssertionError @@ -169,7 +174,7 @@ class Fido2Credential(Credential): def from_cred_id( cls, cred_id: bytes, rp_id_hash: bytes | None ) -> "Fido2Credential": - if len(cred_id) < CRED_ID_MIN_LENGTH or cred_id[0:4] != _CRED_ID_VERSION: + if len(cred_id) < _CRED_ID_MIN_LENGTH or cred_id[0:4] != _CRED_ID_VERSION: raise ValueError # invalid length or version key = seed.derive_slip21_node_without_passphrase( @@ -202,18 +207,20 @@ class Fido2Credential(Credential): if not isinstance(data, dict): raise ValueError # invalid CBOR data + get = data.get # local_cache_attribute + cred = cls() - cred.rp_id = data.get(_CRED_ID_RP_ID, None) + cred.rp_id = get(_CRED_ID_RP_ID, None) cred.rp_id_hash = rp_id_hash - cred.rp_name = data.get(_CRED_ID_RP_NAME, None) - cred.user_id = data.get(_CRED_ID_USER_ID, None) - cred.user_name = data.get(_CRED_ID_USER_NAME, None) - cred.user_display_name = data.get(_CRED_ID_USER_DISPLAY_NAME, None) - cred.creation_time = data.get(_CRED_ID_CREATION_TIME, 0) - cred.hmac_secret = data.get(_CRED_ID_HMAC_SECRET, False) - cred.use_sign_count = data.get(_CRED_ID_USE_SIGN_COUNT, False) - cred.algorithm = data.get(_CRED_ID_ALGORITHM, _DEFAULT_ALGORITHM) - cred.curve = data.get(_CRED_ID_CURVE, _DEFAULT_CURVE) + cred.rp_name = get(_CRED_ID_RP_NAME, None) + cred.user_id = get(_CRED_ID_USER_ID, None) + cred.user_name = get(_CRED_ID_USER_NAME, None) + cred.user_display_name = get(_CRED_ID_USER_DISPLAY_NAME, None) + cred.creation_time = get(_CRED_ID_CREATION_TIME, 0) + cred.hmac_secret = get(_CRED_ID_HMAC_SECRET, False) + cred.use_sign_count = get(_CRED_ID_USE_SIGN_COUNT, False) + cred.algorithm = get(_CRED_ID_ALGORITHM, _DEFAULT_ALGORITHM) + cred.curve = get(_CRED_ID_CURVE, _DEFAULT_CURVE) cred.id = cred_id if ( @@ -228,14 +235,14 @@ class Fido2Credential(Credential): def truncate_names(self) -> None: if self.rp_name: - self.rp_name = utils.truncate_utf8(self.rp_name, NAME_MAX_LENGTH) + self.rp_name = utils.truncate_utf8(self.rp_name, _NAME_MAX_LENGTH) if self.user_name: - self.user_name = utils.truncate_utf8(self.user_name, NAME_MAX_LENGTH) + self.user_name = utils.truncate_utf8(self.user_name, _NAME_MAX_LENGTH) if self.user_display_name: self.user_display_name = utils.truncate_utf8( - self.user_display_name, NAME_MAX_LENGTH + self.user_display_name, _NAME_MAX_LENGTH ) def check_required_fields(self) -> bool: @@ -288,24 +295,28 @@ class Fido2Credential(Credential): return node.private_key() def public_key(self) -> bytes: - if self.curve == common.COSE_CURVE_P256: + from . import common + + curve = self.curve # local_cache_attribute + + if curve == COSE_CURVE_P256: pubkey = nist256p1.publickey(self._private_key(), False) return cbor.encode( { common.COSE_KEY_ALG: self.algorithm, common.COSE_KEY_KTY: common.COSE_KEYTYPE_EC2, - common.COSE_KEY_CRV: self.curve, + common.COSE_KEY_CRV: curve, common.COSE_KEY_X: pubkey[1:33], common.COSE_KEY_Y: pubkey[33:], } ) - elif self.curve == common.COSE_CURVE_ED25519: + elif curve == COSE_CURVE_ED25519: pubkey = ed25519.publickey(self._private_key()) return cbor.encode( { common.COSE_KEY_ALG: self.algorithm, common.COSE_KEY_KTY: common.COSE_KEYTYPE_OKP, - common.COSE_KEY_CRV: self.curve, + common.COSE_KEY_CRV: curve, common.COSE_KEY_X: pubkey, } ) @@ -313,13 +324,13 @@ class Fido2Credential(Credential): def sign(self, data: Iterable[bytes]) -> bytes: if (self.algorithm, self.curve) == ( - common.COSE_ALG_ES256, - common.COSE_CURVE_P256, + COSE_ALG_ES256, + COSE_CURVE_P256, ): return self._u2f_sign(data) elif (self.algorithm, self.curve) == ( - common.COSE_ALG_EDDSA, - common.COSE_CURVE_ED25519, + COSE_ALG_EDDSA, + COSE_CURVE_ED25519, ): return ed25519.sign( self._private_key(), b"".join(segment for segment in data) @@ -329,13 +340,13 @@ class Fido2Credential(Credential): def bogus_signature(self) -> bytes: if (self.algorithm, self.curve) == ( - common.COSE_ALG_ES256, - common.COSE_CURVE_P256, + COSE_ALG_ES256, + COSE_CURVE_P256, ): return der.encode_seq((b"\x0a" * 32, b"\x0a" * 32)) elif (self.algorithm, self.curve) == ( - common.COSE_ALG_EDDSA, - common.COSE_CURVE_ED25519, + COSE_ALG_EDDSA, + COSE_CURVE_ED25519, ): return b"\x0a" * 64 @@ -440,6 +451,8 @@ class U2fCredential(Credential): def _node_from_key_handle( rp_id_hash: bytes, keyhandle: bytes, pathformat: str ) -> bip32.HDNode | None: + from trezor import log + # unpack the keypath from the first half of keyhandle keypath = keyhandle[:32] path = ustruct.unpack(pathformat, keypath) diff --git a/core/src/apps/webauthn/fido2.py b/core/src/apps/webauthn/fido2.py index 3790102de..f669fba58 100644 --- a/core/src/apps/webauthn/fido2.py +++ b/core/src/apps/webauthn/fido2.py @@ -2,25 +2,29 @@ import uctypes import ustruct import utime from micropython import const -from typing import Any, Callable, Coroutine, Iterable, Iterator +from typing import TYPE_CHECKING -import storage -import storage.resident_credentials -from storage.fido2 import KEY_AGREEMENT_PRIVKEY, KEY_AGREEMENT_PUBKEY -from trezor import config, io, log, loop, ui, utils, workflow -from trezor.crypto import aes, der, hashlib, hmac, random +import storage.device as storage_device +from trezor import config, io, log, loop, utils, workflow +from trezor.crypto import hashlib from trezor.crypto.curve import nist256p1 from trezor.ui.components.common.confirm import Pageable from trezor.ui.components.common.webauthn import ConfirmInfo from trezor.ui.layouts import show_popup -from trezor.ui.layouts.webauthn import confirm_webauthn, confirm_webauthn_reset +from trezor.ui.layouts.webauthn import confirm_webauthn from apps.base import set_homescreen from apps.common import cbor from . import common -from .credential import CRED_ID_MAX_LENGTH, Credential, Fido2Credential, U2fCredential -from .resident_credentials import find_by_rp_id_hash, store_resident_credential +from .credential import Credential, Fido2Credential + +if TYPE_CHECKING: + from typing import Any, Callable, Coroutine, Iterable, Iterator + from .credential import U2fCredential + + HID = io.HID + _CID_BROADCAST = const(0xFFFF_FFFF) # broadcast channel id @@ -39,7 +43,6 @@ _CMD_INIT_NONCE_SIZE = const(8) # types of cmd _CMD_PING = const(0x81) # echo data through local processor only _CMD_MSG = const(0x83) # send U2F message frame -_CMD_LOCK = const(0x84) # send lock channel command _CMD_INIT = const(0x86) # channel initialization _CMD_WINK = const(0x88) # send device identification wink _CMD_CBOR = const(0x90) # send encapsulated CTAP CBOR encoded message @@ -88,8 +91,6 @@ _GETASSERT_RESP_CREDENTIAL = const(0x01) # map, optional _GETASSERT_RESP_AUTH_DATA = const(0x02) # bytes, required _GETASSERT_RESP_SIGNATURE = const(0x03) # bytes, required _GETASSERT_RESP_USER = const(0x04) # map, optional -_GETASSERT_RESP_PUB_KEY_CREDENTIAL_USER_ENTITY = const(0x04) # map, optional -_GETASSERT_RESP_NUM_OF_CREDENTIALS = const(0x05) # int, optional # CBOR GetInfo response member keys _GETINFO_RESP_VERSIONS = const(0x01) # array of str, required @@ -128,12 +129,10 @@ _UV_CACHE_TIME_MS = const(3 * 60 * 1000) # user verification cache time # hid error codes _ERR_NONE = const(0x00) # no error _ERR_INVALID_CMD = const(0x01) # invalid command -_ERR_INVALID_PAR = const(0x02) # invalid parameter _ERR_INVALID_LEN = const(0x03) # invalid message length _ERR_INVALID_SEQ = const(0x04) # invalid message sequencing _ERR_MSG_TIMEOUT = const(0x05) # message has timed out _ERR_CHANNEL_BUSY = const(0x06) # channel busy -_ERR_LOCK_REQUIRED = const(0x0A) # command requires channel lock _ERR_INVALID_CID = const(0x0B) # command not allowed on this cid _ERR_CBOR_UNEXPECTED_TYPE = const(0x11) # invalid/unexpected CBOR _ERR_INVALID_CBOR = const(0x12) # error when parsing CBOR @@ -154,7 +153,6 @@ _ERR_EXTENSION_FIRST = const(0xE0) # extension specific error # command status responses _SW_NO_ERROR = const(0x9000) _SW_WRONG_LENGTH = const(0x6700) -_SW_DATA_INVALID = const(0x6984) _SW_CONDITIONS_NOT_SATISFIED = const(0x6985) _SW_WRONG_DATA = const(0x6A80) _SW_INS_NOT_SUPPORTED = const(0x6D00) @@ -200,15 +198,15 @@ _RESULT_CANCEL = const(3) # Request was cancelled by _CMD_CANCEL. _RESULT_TIMEOUT = const(4) # Request exceeded _FIDO2_CONFIRM_TIMEOUT_MS. # FIDO2 configuration. -_ALLOW_FIDO2 = True -_ALLOW_RESIDENT_CREDENTIALS = True -_ALLOW_WINK = False +_ALLOW_FIDO2 = const(1) +_ALLOW_RESIDENT_CREDENTIALS = const(1) +_ALLOW_WINK = const(0) # The default attestation type to use in MakeCredential responses. If false, then basic attestation will be used by default. -_DEFAULT_USE_SELF_ATTESTATION = True +_DEFAULT_USE_SELF_ATTESTATION = const(1) # The default value of the use_sign_count flag for newly created credentials. -_DEFAULT_USE_SIGN_COUNT = True +_DEFAULT_USE_SIGN_COUNT = const(1) # The maximum number of credential IDs that can be supplied in the GetAssertion allow list. _MAX_CRED_COUNT_IN_LIST = const(10) @@ -251,7 +249,8 @@ def frame_cont() -> uctypes.StructDict: } -def resp_cmd_init() -> uctypes.StructDict: +def _resp_cmd_init() -> uctypes.StructDict: + UINT8 = uctypes.UINT8 # local_cache_attribute # uint8_t nonce[8]; // Client application nonce # uint32_t cid; // Channel identifier # uint8_t versionInterface; // Interface version @@ -260,20 +259,22 @@ def resp_cmd_init() -> uctypes.StructDict: # uint8_t versionBuild; // Build version number # uint8_t capFlags; // Capabilities flags return { - "nonce": (0 | uctypes.ARRAY, 8 | uctypes.UINT8), + "nonce": (0 | uctypes.ARRAY, 8 | UINT8), "cid": 8 | uctypes.UINT32, - "versionInterface": 12 | uctypes.UINT8, - "versionMajor": 13 | uctypes.UINT8, - "versionMinor": 14 | uctypes.UINT8, - "versionBuild": 15 | uctypes.UINT8, - "capFlags": 16 | uctypes.UINT8, + "versionInterface": 12 | UINT8, + "versionMajor": 13 | UINT8, + "versionMinor": 14 | UINT8, + "versionBuild": 15 | UINT8, + "capFlags": 16 | UINT8, } -def resp_cmd_register(khlen: int, certlen: int, siglen: int) -> dict: +def _resp_cmd_register(khlen: int, certlen: int, siglen: int) -> dict: cert_ofs = 67 + khlen sig_ofs = cert_ofs + certlen status_ofs = sig_ofs + siglen + UINT8 = uctypes.UINT8 # local_cache_attribute + ARRAY = uctypes.ARRAY # local_cache_attribute # uint8_t registerId; // Registration identifier (U2F_REGISTER_ID) # uint8_t pubKey[65]; // Generated public key # uint8_t keyHandleLen; // Length of key handle @@ -282,12 +283,12 @@ def resp_cmd_register(khlen: int, certlen: int, siglen: int) -> dict: # uint8_t sig[siglen]; // Registration signature # uint16_t status; return { - "registerId": 0 | uctypes.UINT8, - "pubKey": (1 | uctypes.ARRAY, 65 | uctypes.UINT8), - "keyHandleLen": 66 | uctypes.UINT8, - "keyHandle": (67 | uctypes.ARRAY, khlen | uctypes.UINT8), - "cert": (cert_ofs | uctypes.ARRAY, certlen | uctypes.UINT8), - "sig": (sig_ofs | uctypes.ARRAY, siglen | uctypes.UINT8), + "registerId": 0 | UINT8, + "pubKey": (1 | ARRAY, 65 | UINT8), + "keyHandleLen": 66 | UINT8, + "keyHandle": (67 | ARRAY, khlen | UINT8), + "cert": (cert_ofs | ARRAY, certlen | UINT8), + "sig": (sig_ofs | ARRAY, siglen | UINT8), "status": status_ofs | uctypes.UINT16, } @@ -296,20 +297,18 @@ def resp_cmd_register(khlen: int, certlen: int, siglen: int) -> dict: _REQ_CMD_AUTHENTICATE_KHLEN = const(64) -def req_cmd_authenticate(khlen: int) -> uctypes.StructDict: - # uint8_t chal[32]; // Challenge - # uint8_t appId[32]; // Application id - # uint8_t keyHandleLen; // Length of key handle - # uint8_t keyHandle[khlen]; // Key handle +def _req_cmd_authenticate(khlen: int) -> uctypes.StructDict: + UINT8 = uctypes.UINT8 # local_cache_attribute + ARRAY = uctypes.ARRAY # local_cache_attribute return { - "chal": (0 | uctypes.ARRAY, 32 | uctypes.UINT8), - "appId": (32 | uctypes.ARRAY, 32 | uctypes.UINT8), - "keyHandleLen": 64 | uctypes.UINT8, - "keyHandle": (65 | uctypes.ARRAY, khlen | uctypes.UINT8), + "chal": (0 | ARRAY, 32 | UINT8), + "appId": (32 | ARRAY, 32 | UINT8), + "keyHandleLen": 64 | UINT8, + "keyHandle": (65 | ARRAY, khlen | UINT8), } -def resp_cmd_authenticate(siglen: int) -> uctypes.StructDict: +def _resp_cmd_authenticate(siglen: int) -> uctypes.StructDict: status_ofs = 5 + siglen # uint8_t flags; // U2F_AUTH_FLAG_ values # uint32_t ctr; // Counter field (big-endian) @@ -356,20 +355,22 @@ class Cmd: self.data = data def to_msg(self) -> Msg: - cla = self.data[_APDU_CLA] - ins = self.data[_APDU_INS] - p1 = self.data[_APDU_P1] - p2 = self.data[_APDU_P2] + self_data = self.data # local_cache_attribute + + cla = self_data[_APDU_CLA] + ins = self_data[_APDU_INS] + p1 = self_data[_APDU_P1] + p2 = self_data[_APDU_P2] lc = ( - (self.data[_APDU_LC1] << 16) - + (self.data[_APDU_LC2] << 8) - + (self.data[_APDU_LC3]) + (self_data[_APDU_LC1] << 16) + + (self_data[_APDU_LC2] << 8) + + (self_data[_APDU_LC3]) ) - data = self.data[_APDU_DATA : _APDU_DATA + lc] + data = self_data[_APDU_DATA : _APDU_DATA + lc] return Msg(self.cid, cla, ins, p1, p2, lc, data) -async def read_cmd(iface: io.HID) -> Cmd | None: +async def _read_cmd(iface: HID) -> Cmd | None: desc_init = frame_init() desc_cont = frame_cont() read = loop.wait(iface.iface_num() | io.POLL_READ) @@ -381,23 +382,25 @@ async def read_cmd(iface: io.HID) -> Cmd | None: data = ifrm.data datalen = len(data) seq = 0 + ifrm_cid = ifrm.cid # local_cache_attribute + warning = log.warning # local_cache_attribute if ifrm.cmd & _TYPE_MASK == _TYPE_CONT: # unexpected cont packet, abort current msg if __debug__: - log.warning(__name__, "_TYPE_CONT") + warning(__name__, "_TYPE_CONT") return None - if ifrm.cid == 0 or ((ifrm.cid == _CID_BROADCAST) and (ifrm.cmd != _CMD_INIT)): + if ifrm_cid == 0 or ((ifrm_cid == _CID_BROADCAST) and (ifrm.cmd != _CMD_INIT)): # CID 0 is reserved for future use and _CID_BROADCAST is reserved for channel allocation - await send_cmd(cmd_error(ifrm.cid, _ERR_INVALID_CID), iface) + await send_cmd(cmd_error(ifrm_cid, _ERR_INVALID_CID), iface) return None if bcnt > _MAX_U2FHID_MSG_PAYLOAD_LEN: # invalid payload length, abort current msg if __debug__: - log.warning(__name__, "_MAX_U2FHID_MSG_PAYLOAD_LEN") - await send_cmd(cmd_error(ifrm.cid, _ERR_INVALID_LEN), iface) + warning(__name__, "_MAX_U2FHID_MSG_PAYLOAD_LEN") + await send_cmd(cmd_error(ifrm_cid, _ERR_INVALID_LEN), iface) return None if datalen < bcnt: @@ -411,17 +414,18 @@ async def read_cmd(iface: io.HID) -> Cmd | None: buf = await loop.race(read, loop.sleep(_CTAP_HID_TIMEOUT_MS)) if not isinstance(buf, bytes): if __debug__: - log.warning(__name__, "_ERR_MSG_TIMEOUT") - await send_cmd(cmd_error(ifrm.cid, _ERR_MSG_TIMEOUT), iface) + warning(__name__, "_ERR_MSG_TIMEOUT") + await send_cmd(cmd_error(ifrm_cid, _ERR_MSG_TIMEOUT), iface) return None cfrm = overlay_struct(bytearray(buf), desc_cont) + cfrm_cid = cfrm.cid # local_cache_attribute if cfrm.seq == _CMD_INIT: - if cfrm.cid == ifrm.cid: + if cfrm_cid == ifrm_cid: # _CMD_INIT command on current channel, abort current transaction. if __debug__: - log.warning( + warning( __name__, "U2FHID: received CMD_INIT command during active tran, aborting", ) @@ -436,25 +440,25 @@ async def read_cmd(iface: io.HID) -> Cmd | None: cfrm = overlay_struct(bytearray(buf), desc_init) await send_cmd( cmd_init( - Cmd(cfrm.cid, cfrm.cmd, bytes(cfrm.data[: cfrm.bcnt])) + Cmd(cfrm_cid, cfrm.cmd, bytes(cfrm.data[: cfrm.bcnt])) ), iface, ) continue - if cfrm.cid != ifrm.cid: + if cfrm_cid != ifrm_cid: # Frame for a different channel, continue waiting for next frame on the active CID. # For init frames reply with BUSY. Ignore continuation frames. if cfrm.seq & _TYPE_MASK == _TYPE_INIT: if __debug__: - log.warning( + warning( __name__, "U2FHID: received init frame for different CID, _ERR_CHANNEL_BUSY", ) - await send_cmd(cmd_error(cfrm.cid, _ERR_CHANNEL_BUSY), iface) + await send_cmd(cmd_error(cfrm_cid, _ERR_CHANNEL_BUSY), iface) else: if __debug__: - log.warning( + warning( __name__, "U2FHID: received cont frame for different CID" ) continue @@ -463,17 +467,17 @@ async def read_cmd(iface: io.HID) -> Cmd | None: # cont frame for this channel, but incorrect seq number, abort # current msg if __debug__: - log.warning(__name__, "_ERR_INVALID_SEQ") - await send_cmd(cmd_error(cfrm.cid, _ERR_INVALID_SEQ), iface) + warning(__name__, "_ERR_INVALID_SEQ") + await send_cmd(cmd_error(cfrm_cid, _ERR_INVALID_SEQ), iface) return None datalen += utils.memcpy(data, datalen, cfrm.data, 0, bcnt - datalen) seq += 1 else: - return Cmd(ifrm.cid, ifrm.cmd, bytes(data)) + return Cmd(ifrm_cid, ifrm.cmd, bytes(data)) -async def send_cmd(cmd: Cmd, iface: io.HID) -> None: +async def send_cmd(cmd: Cmd, iface: HID) -> None: init_desc = frame_init() cont_desc = frame_cont() offset = 0 @@ -508,7 +512,7 @@ async def send_cmd(cmd: Cmd, iface: io.HID) -> None: seq += 1 -def send_cmd_sync(cmd: Cmd, iface: io.HID) -> None: +def send_cmd_sync(cmd: Cmd, iface: HID) -> None: init_desc = frame_init() cont_desc = frame_cont() offset = 0 @@ -536,12 +540,12 @@ def send_cmd_sync(cmd: Cmd, iface: io.HID) -> None: seq += 1 -async def handle_reports(iface: io.HID) -> None: +async def handle_reports(iface: HID) -> None: dialog_mgr = DialogManager(iface) while True: try: - req = await read_cmd(iface) + req = await _read_cmd(iface) if req is None: continue if dialog_mgr.is_busy() and req.cid not in ( @@ -550,7 +554,7 @@ async def handle_reports(iface: io.HID) -> None: ): resp: Cmd | None = cmd_error(req.cid, _ERR_CHANNEL_BUSY) else: - resp = dispatch_cmd(req, dialog_mgr) + resp = _dispatch_cmd(req, dialog_mgr) if resp is not None: await send_cmd(resp, iface) except Exception as e: @@ -558,7 +562,7 @@ async def handle_reports(iface: io.HID) -> None: class KeepaliveCallback: - def __init__(self, cid: int, iface: io.HID) -> None: + def __init__(self, cid: int, iface: HID) -> None: self.cid = cid self.iface = iface @@ -584,7 +588,7 @@ async def verify_user(keepalive_callback: KeepaliveCallback) -> bool: class State: - def __init__(self, cid: int, iface: io.HID) -> None: + def __init__(self, cid: int, iface: HID) -> None: self.cid = cid self.iface = iface self.finished = False @@ -613,9 +617,7 @@ class State: class U2fState(State, ConfirmInfo): - def __init__( - self, cid: int, iface: io.HID, req_data: bytes, cred: Credential - ) -> None: + def __init__(self, cid: int, iface: HID, req_data: bytes, cred: Credential) -> None: State.__init__(self, cid, iface) ConfirmInfo.__init__(self) self._cred = cred @@ -634,7 +636,7 @@ class U2fState(State, ConfirmInfo): class U2fConfirmRegister(U2fState): def __init__( - self, cid: int, iface: io.HID, req_data: bytes, cred: U2fCredential + self, cid: int, iface: HID, req_data: bytes, cred: U2fCredential ) -> None: super().__init__(cid, iface, req_data, cred) @@ -642,16 +644,16 @@ class U2fConfirmRegister(U2fState): if self._cred.rp_id_hash in _BOGUS_APPIDS: if self.cid == _last_good_auth_check_cid: await show_popup( - title="U2F", - subtitle="Already registered.", - description="This device is already\nregistered with this\napplication.", + "U2F", + "This device is already\nregistered with this\napplication.", + "Already registered.", timeout_ms=_POPUP_TIMEOUT_MS, ) else: await show_popup( - title="U2F", - subtitle="Not registered.", - description="This device is not\nregistered with this\napplication.", + "U2F", + "This device is not\nregistered with this\napplication.", + "Not registered.", timeout_ms=_POPUP_TIMEOUT_MS, ) return False @@ -670,9 +672,7 @@ class U2fConfirmRegister(U2fState): class U2fConfirmAuthenticate(U2fState): - def __init__( - self, cid: int, iface: io.HID, req_data: bytes, cred: Credential - ) -> None: + def __init__(self, cid: int, iface: HID, req_data: bytes, cred: Credential) -> None: super().__init__(cid, iface, req_data, cred) def get_header(self) -> str: @@ -709,7 +709,7 @@ class U2fUnlock(State): class Fido2State(State): - def __init__(self, cid: int, iface: io.HID) -> None: + def __init__(self, cid: int, iface: HID) -> None: super().__init__(cid, iface) def keepalive_status(self) -> int: @@ -771,7 +771,7 @@ class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo): def __init__( self, cid: int, - iface: io.HID, + iface: HID, client_data_hash: bytes, cred: Fido2Credential, resident: bool, @@ -802,25 +802,27 @@ class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo): return True async def on_confirm(self) -> None: + from .resident_credentials import store_resident_credential + + cid = self.cid # local_cache_attribute + self._cred.generate_id() - send_cmd_sync(cmd_keepalive(self.cid, _KEEPALIVE_STATUS_PROCESSING), self.iface) - response_data = cbor_make_credential_sign( + send_cmd_sync(cmd_keepalive(cid, _KEEPALIVE_STATUS_PROCESSING), self.iface) + response_data = _cbor_make_credential_sign( self._client_data_hash, self._cred, self._user_verification ) - cmd = Cmd(self.cid, _CMD_CBOR, bytes([_ERR_NONE]) + response_data) + cmd = Cmd(cid, _CMD_CBOR, bytes([_ERR_NONE]) + response_data) if self._resident: - send_cmd_sync( - cmd_keepalive(self.cid, _KEEPALIVE_STATUS_PROCESSING), self.iface - ) + send_cmd_sync(cmd_keepalive(cid, _KEEPALIVE_STATUS_PROCESSING), self.iface) if not store_resident_credential(self._cred): - cmd = cbor_error(self.cid, _ERR_KEY_STORE_FULL) + cmd = cbor_error(cid, _ERR_KEY_STORE_FULL) await send_cmd(cmd, self.iface) self.finished = True class Fido2ConfirmExcluded(Fido2ConfirmMakeCredential): - def __init__(self, cid: int, iface: io.HID, cred: Fido2Credential) -> None: + def __init__(self, cid: int, iface: HID, cred: Fido2Credential) -> None: super().__init__(cid, iface, b"", cred, resident=False, user_verification=False) async def on_confirm(self) -> None: @@ -829,11 +831,11 @@ class Fido2ConfirmExcluded(Fido2ConfirmMakeCredential): self.finished = True await show_popup( - title="FIDO2 Register", - subtitle="Already registered.", - description="This device is already\nregistered with {}.", - description_param=self._cred.rp_id, - timeout_ms=_POPUP_TIMEOUT_MS, + "FIDO2 Register", + "This device is already\nregistered with {}.", + "Already registered.", + self._cred.rp_id, # description_param + _POPUP_TIMEOUT_MS, ) @@ -841,7 +843,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable): def __init__( self, cid: int, - iface: io.HID, + iface: HID, client_data_hash: bytes, creds: list[Credential], hmac_secret: dict | None, @@ -878,11 +880,11 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable): return True async def on_confirm(self) -> None: + cid = self.cid # local_cache_attribute + cred = self._creds[self.page()] try: - send_cmd_sync( - cmd_keepalive(self.cid, _KEEPALIVE_STATUS_PROCESSING), self.iface - ) + send_cmd_sync(cmd_keepalive(cid, _KEEPALIVE_STATUS_PROCESSING), self.iface) response_data = cbor_get_assertion_sign( self._client_data_hash, cred.rp_id_hash, @@ -892,16 +894,16 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable): True, self._user_verification, ) - cmd = Cmd(self.cid, _CMD_CBOR, bytes([_ERR_NONE]) + response_data) + cmd = Cmd(cid, _CMD_CBOR, bytes([_ERR_NONE]) + response_data) except CborError as e: - cmd = cbor_error(self.cid, e.code) + cmd = cbor_error(cid, e.code) except KeyError: - cmd = cbor_error(self.cid, _ERR_MISSING_PARAMETER) + cmd = cbor_error(cid, _ERR_MISSING_PARAMETER) except Exception as e: # Firmware error. if __debug__: log.exception(__name__, e) - cmd = cbor_error(self.cid, _ERR_OTHER) + cmd = cbor_error(cid, _ERR_OTHER) await send_cmd(cmd, self.iface) self.finished = True @@ -917,16 +919,16 @@ class Fido2ConfirmNoPin(State): self.finished = True await show_popup( - title="FIDO2 Verify User", - subtitle="Unable to verify user.", - description="Please enable PIN\nprotection.", + "FIDO2 Verify User", + "Please enable PIN\nprotection.", + "Unable to verify user.", timeout_ms=_POPUP_TIMEOUT_MS, ) return False class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion): - def __init__(self, cid: int, iface: io.HID, rp_id: str) -> None: + def __init__(self, cid: int, iface: HID, rp_id: str) -> None: cred = Fido2Credential() cred.rp_id = rp_id super().__init__( @@ -939,22 +941,26 @@ class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion): self.finished = True await show_popup( - title="FIDO2 Authenticate", - subtitle="Not registered.", - description="This device is not\nregistered with\n{}.", - description_param=self._creds[0].app_name(), - timeout_ms=_POPUP_TIMEOUT_MS, + "FIDO2 Authenticate", + "This device is not\nregistered with\n{}.", + "Not registered.", + self._creds[0].app_name(), # description_param + _POPUP_TIMEOUT_MS, ) class Fido2ConfirmReset(Fido2State): - def __init__(self, cid: int, iface: io.HID) -> None: + def __init__(self, cid: int, iface: HID) -> None: super().__init__(cid, iface) async def confirm_dialog(self) -> bool: + from trezor.ui.layouts.webauthn import confirm_webauthn_reset + return await confirm_webauthn_reset() async def on_confirm(self) -> None: + import storage.resident_credentials + storage.resident_credentials.delete_all() cmd = Cmd(self.cid, _CMD_CBOR, bytes([_ERR_NONE])) await send_cmd(cmd, self.iface) @@ -962,7 +968,7 @@ class Fido2ConfirmReset(Fido2State): class DialogManager: - def __init__(self, iface: io.HID) -> None: + def __init__(self, iface: HID) -> None: self.iface = iface self._clear() @@ -1022,12 +1028,14 @@ class DialogManager: return True async def keepalive_loop(self) -> None: + state = self.state # local_cache_attribute + try: - if not self.state: + if not state: return while utime.ticks_ms() < self.deadline: - if self.state.keepalive_status() != _KEEPALIVE_STATUS_NONE: - cmd = cmd_keepalive(self.state.cid, self.state.keepalive_status()) + if state.keepalive_status() != _KEEPALIVE_STATUS_NONE: + cmd = cmd_keepalive(state.cid, state.keepalive_status()) await send_cmd(cmd, self.iface) await loop.sleep(_KEEPALIVE_INTERVAL_MS) finally: @@ -1053,118 +1061,134 @@ class DialogManager: finally: if self.keepalive is not None: loop.close(self.keepalive) + result = self.result # local_cache_attribute + state = self.state # local_cache_attribute - if self.result == _RESULT_CONFIRM: - await self.state.on_confirm() - elif self.result == _RESULT_CANCEL: - await self.state.on_cancel() - elif self.result == _RESULT_TIMEOUT: - await self.state.on_timeout() + if result == _RESULT_CONFIRM: + await state.on_confirm() + elif result == _RESULT_CANCEL: + await state.on_cancel() + elif result == _RESULT_TIMEOUT: + await state.on_timeout() else: - await self.state.on_decline() + await state.on_decline() -def dispatch_cmd(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: - if req.cmd == _CMD_MSG: +def _dispatch_cmd(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: + debug = log.debug # local_cache_attribute + warning = log.warning # local_cache_attribute + cid = req.cid # local_cache_attribute + cmd = req.cmd # local_cache_attribute + + if cmd == _CMD_MSG: try: m = req.to_msg() except IndexError: - return cmd_error(req.cid, _ERR_INVALID_LEN) + return cmd_error(cid, _ERR_INVALID_LEN) + ins = m.ins # local_cache_attribute if m.cla != 0: if __debug__: - log.warning(__name__, "_SW_CLA_NOT_SUPPORTED") - return msg_error(req.cid, _SW_CLA_NOT_SUPPORTED) + warning(__name__, "_SW_CLA_NOT_SUPPORTED") + return msg_error(cid, _SW_CLA_NOT_SUPPORTED) if m.lc + _APDU_DATA > len(req.data): if __debug__: - log.warning(__name__, "_SW_WRONG_LENGTH") - return msg_error(req.cid, _SW_WRONG_LENGTH) + warning(__name__, "_SW_WRONG_LENGTH") + return msg_error(cid, _SW_WRONG_LENGTH) - if m.ins == _MSG_REGISTER: + if ins == _MSG_REGISTER: if __debug__: - log.debug(__name__, "_MSG_REGISTER") - return msg_register(m, dialog_mgr) - elif m.ins == _MSG_AUTHENTICATE: + debug(__name__, "_MSG_REGISTER") + return _msg_register(m, dialog_mgr) + elif ins == _MSG_AUTHENTICATE: if __debug__: - log.debug(__name__, "_MSG_AUTHENTICATE") - return msg_authenticate(m, dialog_mgr) - elif m.ins == _MSG_VERSION: + debug(__name__, "_MSG_AUTHENTICATE") + return _msg_authenticate(m, dialog_mgr) + elif ins == _MSG_VERSION: if __debug__: - log.debug(__name__, "_MSG_VERSION") - return msg_version(m) + debug(__name__, "_MSG_VERSION") + # msg_version + if m.data: + return msg_error(m.cid, _SW_WRONG_LENGTH) + return Cmd(m.cid, _CMD_MSG, b"U2F_V2\x90\x00") # includes _SW_NO_ERROR else: if __debug__: - log.warning(__name__, "_SW_INS_NOT_SUPPORTED: %d", m.ins) - return msg_error(req.cid, _SW_INS_NOT_SUPPORTED) + warning(__name__, "_SW_INS_NOT_SUPPORTED: %d", ins) + return msg_error(cid, _SW_INS_NOT_SUPPORTED) - elif req.cmd == _CMD_INIT: + elif cmd == _CMD_INIT: if __debug__: - log.debug(__name__, "_CMD_INIT") + debug(__name__, "_CMD_INIT") return cmd_init(req) - elif req.cmd == _CMD_PING: + elif cmd == _CMD_PING: if __debug__: - log.debug(__name__, "_CMD_PING") + debug(__name__, "_CMD_PING") return req - elif req.cmd == _CMD_WINK and _ALLOW_WINK: + elif cmd == _CMD_WINK and _ALLOW_WINK: if __debug__: - log.debug(__name__, "_CMD_WINK") - return cmd_wink(req) - elif req.cmd == _CMD_CBOR and _ALLOW_FIDO2: + debug(__name__, "_CMD_WINK") + return _cmd_wink(req) + elif cmd == _CMD_CBOR and _ALLOW_FIDO2: if not req.data: - return cmd_error(req.cid, _ERR_INVALID_LEN) - if req.data[0] == _CBOR_MAKE_CREDENTIAL: + return cmd_error(cid, _ERR_INVALID_LEN) + req_data_first = req.data[0] + if req_data_first == _CBOR_MAKE_CREDENTIAL: if __debug__: - log.debug(__name__, "_CBOR_MAKE_CREDENTIAL") - return cbor_make_credential(req, dialog_mgr) - elif req.data[0] == _CBOR_GET_ASSERTION: + debug(__name__, "_CBOR_MAKE_CREDENTIAL") + return _cbor_make_credential(req, dialog_mgr) + elif req_data_first == _CBOR_GET_ASSERTION: if __debug__: - log.debug(__name__, "_CBOR_GET_ASSERTION") - return cbor_get_assertion(req, dialog_mgr) - elif req.data[0] == _CBOR_GET_INFO: + debug(__name__, "_CBOR_GET_ASSERTION") + return _cbor_get_assertion(req, dialog_mgr) + elif req_data_first == _CBOR_GET_INFO: if __debug__: - log.debug(__name__, "_CBOR_GET_INFO") - return cbor_get_info(req) - elif req.data[0] == _CBOR_CLIENT_PIN: + debug(__name__, "_CBOR_GET_INFO") + return _cbor_get_info(req) + elif req_data_first == _CBOR_CLIENT_PIN: if __debug__: - log.debug(__name__, "_CBOR_CLIENT_PIN") - return cbor_client_pin(req) - elif req.data[0] == _CBOR_RESET: + debug(__name__, "_CBOR_CLIENT_PIN") + return _cbor_client_pin(req) + elif req_data_first == _CBOR_RESET: if __debug__: - log.debug(__name__, "_CBOR_RESET") - return cbor_reset(req, dialog_mgr) - elif req.data[0] == _CBOR_GET_NEXT_ASSERTION: + debug(__name__, "_CBOR_RESET") + return _cbor_reset(req, dialog_mgr) + elif req_data_first == _CBOR_GET_NEXT_ASSERTION: if __debug__: - log.debug(__name__, "_CBOR_GET_NEXT_ASSERTION") - return cbor_error(req.cid, _ERR_NOT_ALLOWED) + debug(__name__, "_CBOR_GET_NEXT_ASSERTION") + return cbor_error(cid, _ERR_NOT_ALLOWED) else: if __debug__: - log.warning(__name__, "_ERR_INVALID_CMD _CMD_CBOR %d", req.data[0]) - return cbor_error(req.cid, _ERR_INVALID_CMD) + warning(__name__, "_ERR_INVALID_CMD _CMD_CBOR %d", req_data_first) + return cbor_error(cid, _ERR_INVALID_CMD) - elif req.cmd == _CMD_CANCEL: + elif cmd == _CMD_CANCEL: if __debug__: - log.debug(__name__, "_CMD_CANCEL") + debug(__name__, "_CMD_CANCEL") dialog_mgr.result = _RESULT_CANCEL dialog_mgr.reset() return None else: if __debug__: - log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd) - return cmd_error(req.cid, _ERR_INVALID_CMD) + warning(__name__, "_ERR_INVALID_CMD: %d", cmd) + return cmd_error(cid, _ERR_INVALID_CMD) def cmd_init(req: Cmd) -> Cmd: - if req.cid == _CID_BROADCAST: + from trezor.crypto import random + + cid = req.cid # local_cache_attribute + + if cid == _CID_BROADCAST: # uint32_t except 0 and 0xffff_ffff resp_cid = random.uniform(0xFFFF_FFFE) + 1 else: - resp_cid = req.cid + resp_cid = cid if len(req.data) != _CMD_INIT_NONCE_SIZE: - return cmd_error(req.cid, _ERR_INVALID_LEN) + return cmd_error(cid, _ERR_INVALID_LEN) - buf, resp = make_struct(resp_cmd_init()) + buf, resp = make_struct(_resp_cmd_init()) utils.memcpy(resp.nonce, 0, req.data, 0, len(req.data)) resp.cid = resp_cid resp.versionInterface = _U2FHID_IF_VERSION @@ -1173,10 +1197,12 @@ def cmd_init(req: Cmd) -> Cmd: resp.versionBuild = 0 resp.capFlags = (_CAPFLAG_WINK * _ALLOW_WINK) | _CAPFLAG_CBOR - return Cmd(req.cid, req.cmd, bytes(buf)) + return Cmd(cid, req.cmd, bytes(buf)) -def cmd_wink(req: Cmd) -> Cmd: +def _cmd_wink(req: Cmd) -> Cmd: + from trezor import ui + global _last_wink_cid if _last_wink_cid != req.cid: _last_wink_cid = req.cid @@ -1184,60 +1210,67 @@ def cmd_wink(req: Cmd) -> Cmd: return req -def msg_register(req: Msg, dialog_mgr: DialogManager) -> Cmd: - if not config.is_unlocked(): - new_state: State = U2fUnlock(req.cid, dialog_mgr.iface) - dialog_mgr.set_state(new_state) - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) +def _msg_register(req: Msg, dialog_mgr: DialogManager) -> Cmd: + from .credential import U2fCredential - if not storage.device.is_initialized(): + cid = req.cid # local_cache_attribute + data = req.data # local_cache_attribute + + if not config.is_unlocked(): + new_state: State = U2fUnlock(cid, dialog_mgr.iface) + dialog_mgr.set_state(new_state) + return msg_error(cid, _SW_CONDITIONS_NOT_SATISFIED) + + if not storage_device.is_initialized(): if __debug__: log.warning(__name__, "not initialized") # There is no standard way to decline a U2F request, but responding with ERR_CHANNEL_BUSY # doesn't seem to violate the protocol and at least stops Chrome from polling. - return cmd_error(req.cid, _ERR_CHANNEL_BUSY) + return cmd_error(cid, _ERR_CHANNEL_BUSY) # check length of input data - if len(req.data) != 64: + if len(data) != 64: if __debug__: - log.warning(__name__, "_SW_WRONG_LENGTH req.data") - return msg_error(req.cid, _SW_WRONG_LENGTH) + log.warning(__name__, "_SW_WRONG_LENGTH req_data") + return msg_error(cid, _SW_WRONG_LENGTH) # parse challenge and rp_id_hash - chal = req.data[:32] + chal = data[:32] cred = U2fCredential() - cred.rp_id_hash = req.data[32:] + cred.rp_id_hash = data[32:] cred.generate_key_handle() # check equality with last request - new_state = U2fConfirmRegister(req.cid, dialog_mgr.iface, req.data, cred) + new_state = U2fConfirmRegister(cid, dialog_mgr.iface, data, cred) if not dialog_mgr.set_state(new_state): - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) + return msg_error(cid, _SW_CONDITIONS_NOT_SATISFIED) # wait for a button or continue if dialog_mgr.result == _RESULT_NONE: if __debug__: log.info(__name__, "waiting for button") - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) + return msg_error(cid, _SW_CONDITIONS_NOT_SATISFIED) if dialog_mgr.result != _RESULT_CONFIRM: if __debug__: log.info(__name__, "request declined") # There is no standard way to decline a U2F request, but responding with ERR_CHANNEL_BUSY # doesn't seem to violate the protocol and at least stops Chrome from polling. - return cmd_error(req.cid, _ERR_CHANNEL_BUSY) + return cmd_error(cid, _ERR_CHANNEL_BUSY) # sign the registration challenge and return if __debug__: log.info(__name__, "signing register") - buf = msg_register_sign(chal, cred) + buf = _msg_register_sign(chal, cred) dialog_mgr.reset() - return Cmd(req.cid, _CMD_MSG, buf) + return Cmd(cid, _CMD_MSG, buf) def basic_attestation_sign(data: Iterable[bytes]) -> bytes: + from trezor.crypto import der + dig = hashlib.sha256() for segment in data: dig.update(segment) @@ -1245,47 +1278,52 @@ def basic_attestation_sign(data: Iterable[bytes]) -> bytes: return der.encode_seq((sig[1:33], sig[33:])) -def msg_register_sign(challenge: bytes, cred: U2fCredential) -> bytes: +def _msg_register_sign(challenge: bytes, cred: U2fCredential) -> bytes: + memcpy = utils.memcpy # local_cache_attribute + id = cred.id # local_cache_attribute + pubkey = cred.public_key() - sig = basic_attestation_sign((b"\x00", cred.rp_id_hash, challenge, cred.id, pubkey)) + sig = basic_attestation_sign((b"\x00", cred.rp_id_hash, challenge, id, pubkey)) # pack to a response - buf, resp = make_struct( - resp_cmd_register(len(cred.id), len(_FIDO_ATT_CERT), len(sig)) - ) + buf, resp = make_struct(_resp_cmd_register(len(id), len(_FIDO_ATT_CERT), len(sig))) resp.registerId = _U2F_REGISTER_ID - utils.memcpy(resp.pubKey, 0, pubkey, 0, len(pubkey)) - resp.keyHandleLen = len(cred.id) - utils.memcpy(resp.keyHandle, 0, cred.id, 0, len(cred.id)) - utils.memcpy(resp.cert, 0, _FIDO_ATT_CERT, 0, len(_FIDO_ATT_CERT)) - utils.memcpy(resp.sig, 0, sig, 0, len(sig)) + memcpy(resp.pubKey, 0, pubkey, 0, len(pubkey)) + resp.keyHandleLen = len(id) + memcpy(resp.keyHandle, 0, id, 0, len(id)) + memcpy(resp.cert, 0, _FIDO_ATT_CERT, 0, len(_FIDO_ATT_CERT)) + memcpy(resp.sig, 0, sig, 0, len(sig)) resp.status = _SW_NO_ERROR return bytes(buf) -def msg_authenticate(req: Msg, dialog_mgr: DialogManager) -> Cmd: - if not config.is_unlocked(): - new_state: State = U2fUnlock(req.cid, dialog_mgr.iface) - dialog_mgr.set_state(new_state) - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) +def _msg_authenticate(req: Msg, dialog_mgr: DialogManager) -> Cmd: + cid = req.cid # local_cache_attribute + data = req.data # local_cache_attribute + info = log.info # local_cache_attribute - if not storage.device.is_initialized(): + if not config.is_unlocked(): + new_state: State = U2fUnlock(cid, dialog_mgr.iface) + dialog_mgr.set_state(new_state) + return msg_error(cid, _SW_CONDITIONS_NOT_SATISFIED) + + if not storage_device.is_initialized(): if __debug__: log.warning(__name__, "not initialized") # Device is not registered with the RP. - return msg_error(req.cid, _SW_WRONG_DATA) + return msg_error(cid, _SW_WRONG_DATA) # we need at least keyHandleLen - if len(req.data) <= _REQ_CMD_AUTHENTICATE_KHLEN: + if len(data) <= _REQ_CMD_AUTHENTICATE_KHLEN: if __debug__: - log.warning(__name__, "_SW_WRONG_LENGTH req.data") - return msg_error(req.cid, _SW_WRONG_LENGTH) + log.warning(__name__, "_SW_WRONG_LENGTH req_data") + return msg_error(cid, _SW_WRONG_LENGTH) # check keyHandleLen - khlen = req.data[_REQ_CMD_AUTHENTICATE_KHLEN] - auth = overlay_struct(bytearray(req.data), req_cmd_authenticate(khlen)) + khlen = data[_REQ_CMD_AUTHENTICATE_KHLEN] + auth = overlay_struct(bytearray(data), _req_cmd_authenticate(khlen)) challenge = bytes(auth.chal) rp_id_hash = bytes(auth.appId) key_handle = bytes(auth.keyHandle) @@ -1294,51 +1332,51 @@ def msg_authenticate(req: Msg, dialog_mgr: DialogManager) -> Cmd: cred = Credential.from_bytes(key_handle, rp_id_hash) except Exception: # specific error logged in _node_from_key_handle - return msg_error(req.cid, _SW_WRONG_DATA) + return msg_error(cid, _SW_WRONG_DATA) # if _AUTH_CHECK_ONLY is requested, return, because keyhandle has been checked already if req.p1 == _AUTH_CHECK_ONLY: if __debug__: - log.info(__name__, "_AUTH_CHECK_ONLY") + info(__name__, "_AUTH_CHECK_ONLY") global _last_good_auth_check_cid - _last_good_auth_check_cid = req.cid - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) + _last_good_auth_check_cid = cid + return msg_error(cid, _SW_CONDITIONS_NOT_SATISFIED) # from now on, only _AUTH_ENFORCE is supported if req.p1 != _AUTH_ENFORCE: if __debug__: - log.info(__name__, "_AUTH_ENFORCE") - return msg_error(req.cid, _SW_WRONG_DATA) + info(__name__, "_AUTH_ENFORCE") + return msg_error(cid, _SW_WRONG_DATA) # check equality with last request - new_state = U2fConfirmAuthenticate(req.cid, dialog_mgr.iface, req.data, cred) + new_state = U2fConfirmAuthenticate(cid, dialog_mgr.iface, data, cred) if not dialog_mgr.set_state(new_state): - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) + return msg_error(cid, _SW_CONDITIONS_NOT_SATISFIED) # wait for a button or continue if dialog_mgr.result == _RESULT_NONE: if __debug__: - log.info(__name__, "waiting for button") - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) + info(__name__, "waiting for button") + return msg_error(cid, _SW_CONDITIONS_NOT_SATISFIED) if dialog_mgr.result != _RESULT_CONFIRM: if __debug__: - log.info(__name__, "request declined") + info(__name__, "request declined") # There is no standard way to decline a U2F request, but responding with ERR_CHANNEL_BUSY # doesn't seem to violate the protocol and at least stops Chrome from polling. - return cmd_error(req.cid, _ERR_CHANNEL_BUSY) + return cmd_error(cid, _ERR_CHANNEL_BUSY) # sign the authentication challenge and return if __debug__: - log.info(__name__, "signing authentication") - buf = msg_authenticate_sign(challenge, rp_id_hash, cred) + info(__name__, "signing authentication") + buf = _msg_authenticate_sign(challenge, rp_id_hash, cred) dialog_mgr.reset() - return Cmd(req.cid, _CMD_MSG, buf) + return Cmd(cid, _CMD_MSG, buf) -def msg_authenticate_sign( +def _msg_authenticate_sign( challenge: bytes, rp_id_hash: bytes, cred: Credential ) -> bytes: flags = bytes([_AUTH_FLAG_UP]) @@ -1351,7 +1389,7 @@ def msg_authenticate_sign( sig = cred.sign((rp_id_hash, flags, ctrbuf, challenge)) # pack to a response - buf, resp = make_struct(resp_cmd_authenticate(len(sig))) + buf, resp = make_struct(_resp_cmd_authenticate(len(sig))) resp.flags = flags[0] resp.ctr = ctr utils.memcpy(resp.sig, 0, sig, 0, len(sig)) @@ -1360,12 +1398,6 @@ def msg_authenticate_sign( return bytes(buf) -def msg_version(req: Msg) -> Cmd: - if req.data: - return msg_error(req.cid, _SW_WRONG_LENGTH) - return Cmd(req.cid, _CMD_MSG, b"U2F_V2\x90\x00") # includes _SW_NO_ERROR - - def msg_error(cid: int, code: int) -> Cmd: return Cmd(cid, _CMD_MSG, ustruct.pack(">H", code)) @@ -1399,7 +1431,7 @@ def credentials_from_descriptor_list( yield cred -def distinguishable_cred_list(credentials: Iterable[Credential]) -> list[Credential]: +def _distinguishable_cred_list(credentials: Iterable[Credential]) -> list[Credential]: """Reduces the input to a list of credentials which can be distinguished by the user. It is assumed that all input credentials share the same RP ID.""" cred_list: list[Credential] = [] @@ -1416,7 +1448,7 @@ def distinguishable_cred_list(credentials: Iterable[Credential]) -> list[Credent return cred_list -def algorithms_from_pub_key_cred_params(pub_key_cred_params: list[dict]) -> list[int]: +def _algorithms_from_pub_key_cred_params(pub_key_cred_params: list[dict]) -> list[int]: alg_list = [] for pkcp in pub_key_cred_params: pub_key_cred_type = pkcp["type"] @@ -1432,11 +1464,11 @@ def algorithms_from_pub_key_cred_params(pub_key_cred_params: list[dict]) -> list return alg_list -def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: +def _cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: if config.is_unlocked(): - resp = cbor_make_credential_process(req, dialog_mgr) + resp = _cbor_make_credential_process(req, dialog_mgr) else: - resp = Fido2Unlock(cbor_make_credential_process, req, dialog_mgr) + resp = Fido2Unlock(_cbor_make_credential_process, req, dialog_mgr) if isinstance(resp, State): if dialog_mgr.set_state(resp): @@ -1447,13 +1479,15 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: return resp -def cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | Cmd: +def _cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | Cmd: from . import knownapps - if not storage.device.is_initialized(): + cid = req.cid # local_cache_attribute + + if not storage_device.is_initialized(): if __debug__: log.warning(__name__, "not initialized") - return cbor_error(req.cid, _ERR_OTHER) + return cbor_error(cid, _ERR_OTHER) try: param = cbor.decode(req.data, offset=1) @@ -1477,11 +1511,11 @@ def cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | excluded_creds = credentials_from_descriptor_list(exclude_list, rp_id_hash) if not utils.is_empty_iterator(excluded_creds): # This authenticator is already registered. - return Fido2ConfirmExcluded(req.cid, dialog_mgr.iface, cred) + return Fido2ConfirmExcluded(cid, dialog_mgr.iface, cred) # Check that the relying party supports ECDSA with SHA-256 or EdDSA. We don't support any other algorithms. pub_key_cred_params = param[_MAKECRED_CMD_PUB_KEY_CRED_PARAMS] - for alg in algorithms_from_pub_key_cred_params(pub_key_cred_params): + for alg in _algorithms_from_pub_key_cred_params(pub_key_cred_params): if alg == common.COSE_ALG_ES256: cred.algorithm = alg cred.curve = common.COSE_CURVE_P256 @@ -1491,7 +1525,7 @@ def cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | cred.curve = common.COSE_CURVE_ED25519 break else: - return cbor_error(req.cid, _ERR_UNSUPPORTED_ALGORITHM) + return cbor_error(cid, _ERR_UNSUPPORTED_ALGORITHM) # Get options. options = param.get(_MAKECRED_CMD_OPTIONS, {}) @@ -1505,17 +1539,17 @@ def cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | client_data_hash = param[_MAKECRED_CMD_CLIENT_DATA_HASH] except TypeError: - return cbor_error(req.cid, _ERR_CBOR_UNEXPECTED_TYPE) + return cbor_error(cid, _ERR_CBOR_UNEXPECTED_TYPE) except KeyError: - return cbor_error(req.cid, _ERR_MISSING_PARAMETER) + return cbor_error(cid, _ERR_MISSING_PARAMETER) except Exception: - return cbor_error(req.cid, _ERR_INVALID_CBOR) + return cbor_error(cid, _ERR_INVALID_CBOR) app = knownapps.by_rp_id_hash(rp_id_hash) if app is not None and app.use_sign_count is not None: cred.use_sign_count = app.use_sign_count else: - cred.use_sign_count = _DEFAULT_USE_SIGN_COUNT + cred.use_sign_count = bool(_DEFAULT_USE_SIGN_COUNT) # Check data types. if ( @@ -1526,26 +1560,26 @@ def cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | or not isinstance(resident_key, bool) or not isinstance(user_verification, bool) ): - return cbor_error(req.cid, _ERR_CBOR_UNEXPECTED_TYPE) + return cbor_error(cid, _ERR_CBOR_UNEXPECTED_TYPE) # Check options. if "up" in options: - return cbor_error(req.cid, _ERR_INVALID_OPTION) + return cbor_error(cid, _ERR_INVALID_OPTION) if resident_key and not _ALLOW_RESIDENT_CREDENTIALS: - return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION) + return cbor_error(cid, _ERR_UNSUPPORTED_OPTION) if user_verification and not config.has_pin(): # User verification requested, but PIN is not enabled. - return Fido2ConfirmNoPin(req.cid, dialog_mgr.iface) + return Fido2ConfirmNoPin(cid, dialog_mgr.iface) # Check that the pinAuth parameter is absent. Client PIN is not supported. if _MAKECRED_CMD_PIN_AUTH in param: - return cbor_error(req.cid, _ERR_PIN_AUTH_INVALID) + return cbor_error(cid, _ERR_PIN_AUTH_INVALID) # Ask user to confirm registration. return Fido2ConfirmMakeCredential( - req.cid, + cid, dialog_mgr.iface, client_data_hash, cred, @@ -1554,19 +1588,11 @@ def cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | ) -def use_self_attestation(rp_id_hash: bytes) -> bool: - from . import knownapps - - app = knownapps.by_rp_id_hash(rp_id_hash) - if app is not None and app.use_self_attestation is not None: - return app.use_self_attestation - else: - return _DEFAULT_USE_SELF_ATTESTATION - - -def cbor_make_credential_sign( +def _cbor_make_credential_sign( client_data_hash: bytes, cred: Fido2Credential, user_verification: bool ) -> bytes: + from . import knownapps + flags = _AUTH_FLAG_UP | _AUTH_FLAG_AT if user_verification: flags |= _AUTH_FLAG_UV @@ -1591,7 +1617,14 @@ def cbor_make_credential_sign( + extensions ) - if use_self_attestation(cred.rp_id_hash): + # use_self_attestation + app = knownapps.by_rp_id_hash(cred.rp_id_hash) + if app is not None and app.use_self_attestation is not None: + use_self_attestation = app.use_self_attestation + else: + use_self_attestation = _DEFAULT_USE_SELF_ATTESTATION + + if use_self_attestation: sig = cred.sign((authenticator_data, client_data_hash)) attestation_statement = {"alg": cred.algorithm, "sig": sig} else: @@ -1612,11 +1645,11 @@ def cbor_make_credential_sign( ) -def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: +def _cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: if config.is_unlocked(): - resp = cbor_get_assertion_process(req, dialog_mgr) + resp = _cbor_get_assertion_process(req, dialog_mgr) else: - resp = Fido2Unlock(cbor_get_assertion_process, req, dialog_mgr) + resp = Fido2Unlock(_cbor_get_assertion_process, req, dialog_mgr) if isinstance(resp, State): if dialog_mgr.set_state(resp): @@ -1627,11 +1660,15 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: return resp -def cbor_get_assertion_process(req: Cmd, dialog_mgr: DialogManager) -> State | Cmd: - if not storage.device.is_initialized(): +def _cbor_get_assertion_process(req: Cmd, dialog_mgr: DialogManager) -> State | Cmd: + from .resident_credentials import find_by_rp_id_hash + + cid = req.cid # local_cache_attribute + + if not storage_device.is_initialized(): if __debug__: log.warning(__name__, "not initialized") - return cbor_error(req.cid, _ERR_OTHER) + return cbor_error(cid, _ERR_OTHER) try: param = cbor.decode(req.data, offset=1) @@ -1643,7 +1680,7 @@ def cbor_get_assertion_process(req: Cmd, dialog_mgr: DialogManager) -> State | C if allow_list: # Get all credentials from the allow list that belong to this authenticator. allowed_creds = credentials_from_descriptor_list(allow_list, rp_id_hash) - cred_list = distinguishable_cred_list(allowed_creds) + cred_list = _distinguishable_cred_list(allowed_creds) for cred in cred_list: if cred.rp_id is None: cred.rp_id = rp_id @@ -1661,7 +1698,7 @@ def cbor_get_assertion_process(req: Cmd, dialog_mgr: DialogManager) -> State | C # Check that the pinAuth parameter is absent. Client PIN is not supported. if _GETASSERT_CMD_PIN_AUTH in param: - return cbor_error(req.cid, _ERR_PIN_AUTH_INVALID) + return cbor_error(cid, _ERR_PIN_AUTH_INVALID) # Get options. options = param.get(_GETASSERT_CMD_OPTIONS, {}) @@ -1673,11 +1710,11 @@ def cbor_get_assertion_process(req: Cmd, dialog_mgr: DialogManager) -> State | C client_data_hash = param[_GETASSERT_CMD_CLIENT_DATA_HASH] except TypeError: - return cbor_error(req.cid, _ERR_CBOR_UNEXPECTED_TYPE) + return cbor_error(cid, _ERR_CBOR_UNEXPECTED_TYPE) except KeyError: - return cbor_error(req.cid, _ERR_MISSING_PARAMETER) + return cbor_error(cid, _ERR_MISSING_PARAMETER) except Exception: - return cbor_error(req.cid, _ERR_INVALID_CBOR) + return cbor_error(cid, _ERR_INVALID_CBOR) # Check data types. if ( @@ -1686,22 +1723,22 @@ def cbor_get_assertion_process(req: Cmd, dialog_mgr: DialogManager) -> State | C or not isinstance(user_presence, bool) or not isinstance(user_verification, bool) ): - return cbor_error(req.cid, _ERR_CBOR_UNEXPECTED_TYPE) + return cbor_error(cid, _ERR_CBOR_UNEXPECTED_TYPE) # Check options. if "rk" in options: - return cbor_error(req.cid, _ERR_INVALID_OPTION) + return cbor_error(cid, _ERR_INVALID_OPTION) if user_verification and not config.has_pin(): # User verification requested, but PIN is not enabled. - return Fido2ConfirmNoPin(req.cid, dialog_mgr.iface) + return Fido2ConfirmNoPin(cid, dialog_mgr.iface) if not cred_list: # No credentials. This authenticator is not registered. if user_presence: - return Fido2ConfirmNoCredentials(req.cid, dialog_mgr.iface, rp_id) + return Fido2ConfirmNoCredentials(cid, dialog_mgr.iface, rp_id) else: - return cbor_error(req.cid, _ERR_NO_CREDENTIALS) + return cbor_error(cid, _ERR_NO_CREDENTIALS) elif not user_presence and not user_verification: # Silent authentication. try: @@ -1714,16 +1751,16 @@ def cbor_get_assertion_process(req: Cmd, dialog_mgr: DialogManager) -> State | C user_presence, user_verification, ) - return Cmd(req.cid, _CMD_CBOR, bytes([_ERR_NONE]) + response_data) + return Cmd(cid, _CMD_CBOR, bytes([_ERR_NONE]) + response_data) except Exception as e: # Firmware error. if __debug__: log.exception(__name__, e) - return cbor_error(req.cid, _ERR_OTHER) + return cbor_error(cid, _ERR_OTHER) else: # Ask user to confirm one of the credentials. return Fido2ConfirmGetAssertion( - req.cid, + cid, dialog_mgr.iface, client_data_hash, cred_list, @@ -1733,7 +1770,13 @@ def cbor_get_assertion_process(req: Cmd, dialog_mgr: DialogManager) -> State | C ) -def cbor_get_assertion_hmac_secret(cred: Credential, hmac_secret: dict) -> bytes | None: +def _cbor_get_assertion_hmac_secret( + cred: Credential, hmac_secret: dict +) -> bytes | None: + from storage.fido2 import KEY_AGREEMENT_PRIVKEY + from trezor.crypto import aes + from trezor.crypto import hmac + key_agreement = hmac_secret[1] # The public key of platform key agreement key. # NOTE: We should check the key_agreement[COSE_KEY_ALG] here, but to avoid compatibility issues we don't, # because there is currently no valid value which describes the actual key agreement algorithm. @@ -1794,7 +1837,7 @@ def cbor_get_assertion_sign( # Spec deviation: Do not reveal hmac-secret during silent authentication. if hmac_secret and user_presence: - encrypted_output = cbor_get_assertion_hmac_secret(cred, hmac_secret) + encrypted_output = _cbor_get_assertion_hmac_secret(cred, hmac_secret) if encrypted_output is not None: extensions["hmac-secret"] = encrypted_output @@ -1836,7 +1879,9 @@ def cbor_get_assertion_sign( return cbor.encode(response) -def cbor_get_info(req: Cmd) -> Cmd: +def _cbor_get_info(req: Cmd) -> Cmd: + from .credential import CRED_ID_MAX_LENGTH + # Note: We claim that the PIN is set even when it's not, because otherwise # login.live.com shows an error, but doesn't instruct the user to set a PIN. response_data = { @@ -1855,20 +1900,24 @@ def cbor_get_info(req: Cmd) -> Cmd: return Cmd(req.cid, _CMD_CBOR, bytes([_ERR_NONE]) + cbor.encode(response_data)) -def cbor_client_pin(req: Cmd) -> Cmd: +def _cbor_client_pin(req: Cmd) -> Cmd: + from storage.fido2 import KEY_AGREEMENT_PUBKEY + + cid = req.cid # local_cache_attribute + try: param = cbor.decode(req.data, offset=1) pin_protocol = param[_CLIENTPIN_CMD_PIN_PROTOCOL] subcommand = param[_CLIENTPIN_CMD_SUBCOMMAND] except Exception: - return cbor_error(req.cid, _ERR_INVALID_CBOR) + return cbor_error(cid, _ERR_INVALID_CBOR) if pin_protocol != 1: - return cbor_error(req.cid, _ERR_PIN_AUTH_INVALID) + return cbor_error(cid, _ERR_PIN_AUTH_INVALID) # We only support the get key agreement command which is required for the hmac-secret extension. if subcommand != _CLIENTPIN_SUBCMD_GET_KEY_AGREEMENT: - return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION) + return cbor_error(cid, _ERR_UNSUPPORTED_OPTION) # Encode the public key of the authenticator key agreement key. # NOTE: There is currently no valid value for COSE_KEY_ALG which describes the actual @@ -1884,11 +1933,11 @@ def cbor_client_pin(req: Cmd) -> Cmd: } } - return Cmd(req.cid, _CMD_CBOR, bytes([_ERR_NONE]) + cbor.encode(response_data)) + return Cmd(cid, _CMD_CBOR, bytes([_ERR_NONE]) + cbor.encode(response_data)) -def cbor_reset(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: - if not storage.device.is_initialized(): +def _cbor_reset(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: + if not storage_device.is_initialized(): if __debug__: log.warning(__name__, "not initialized") # Return success, because the authenticator is already in factory default state. diff --git a/core/src/apps/webauthn/knownapps.py b/core/src/apps/webauthn/knownapps.py index e01eb93a4..72c62b6cb 100644 --- a/core/src/apps/webauthn/knownapps.py +++ b/core/src/apps/webauthn/knownapps.py @@ -2,6 +2,8 @@ # (by running `make templates` in `core`) # do not edit manually! +# NOTE: using positional arguments saves 520 bytes in flash space + class FIDOApp: def __init__( @@ -22,338 +24,338 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: if rp_id_hash == b"\x96\x89\x78\xa2\x99\x53\xde\x52\xd3\xef\x0f\x0c\x71\xb7\xb7\xb6\xb1\xaf\x9f\x08\xe2\x57\x89\x6a\x8d\x81\x26\x91\x85\x30\x29\x3b": # U2F key for Amazon Web Services return FIDOApp( - label="aws.amazon.com", - icon="apps/webauthn/res/icon_aws.toif", - use_sign_count=None, - use_self_attestation=None, + "aws.amazon.com", # label + "apps/webauthn/res/icon_aws.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xc3\x40\x8c\x04\x47\x88\xae\xa5\xb3\xdf\x30\x89\x52\xfd\x8c\xa3\xc7\x0e\x21\xfe\xf4\xf6\xc1\xc2\x37\x4c\xaa\x1d\xf9\xb2\x8d\xdd": # WebAuthn key for Binance return FIDOApp( - label="www.binance.com", - icon="apps/webauthn/res/icon_binance.toif", - use_sign_count=False, - use_self_attestation=True, + "www.binance.com", # label + "apps/webauthn/res/icon_binance.toif", # icon + False, # use_sign_count + True, # use_self_attestation ) if rp_id_hash == b"\x20\xf6\x61\xb1\x94\x0c\x34\x70\xac\x54\xfa\x2e\xb4\x99\x90\xfd\x33\xb5\x6d\xe8\xde\x60\x18\x70\xff\x02\xa8\x06\x0f\x3b\x7c\x58": # WebAuthn key for Binance return FIDOApp( - label="binance.com", - icon="apps/webauthn/res/icon_binance.toif", - use_sign_count=False, - use_self_attestation=True, + "binance.com", # label + "apps/webauthn/res/icon_binance.toif", # icon + False, # use_sign_count + True, # use_self_attestation ) if rp_id_hash == b"\x12\x74\x3b\x92\x12\x97\xb7\x7f\x11\x35\xe4\x1f\xde\xdd\x4a\x84\x6a\xfe\x82\xe1\xf3\x69\x32\xa9\x91\x2f\x3b\x0d\x8d\xfb\x7d\x0e": # U2F key for Bitbucket return FIDOApp( - label="bitbucket.org", - icon="apps/webauthn/res/icon_bitbucket.toif", - use_sign_count=None, - use_self_attestation=None, + "bitbucket.org", # label + "apps/webauthn/res/icon_bitbucket.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x30\x2f\xd5\xb4\x49\x2a\x07\xb9\xfe\xbb\x30\xe7\x32\x69\xec\xa5\x01\x20\x5c\xcf\xe0\xc2\x0b\xf7\xb4\x72\xfa\x2d\x31\xe2\x1e\x63": # U2F key for Bitfinex return FIDOApp( - label="www.bitfinex.com", - icon="apps/webauthn/res/icon_bitfinex.toif", - use_sign_count=None, - use_self_attestation=None, + "www.bitfinex.com", # label + "apps/webauthn/res/icon_bitfinex.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xa3\x4d\x30\x9f\xfa\x28\xc1\x24\x14\xb8\xba\x6c\x07\xee\x1e\xfa\xe1\xa8\x5e\x8a\x04\x61\x48\x59\xa6\x7c\x04\x93\xb6\x95\x61\x90": # U2F key for Bitwarden return FIDOApp( - label="vault.bitwarden.com", - icon="apps/webauthn/res/icon_bitwarden.toif", - use_sign_count=None, - use_self_attestation=None, + "vault.bitwarden.com", # label + "apps/webauthn/res/icon_bitwarden.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x19\x81\x5c\xb9\xa5\xfb\x25\xd8\x05\xde\xbd\x7b\x32\x53\x7e\xd5\x78\x63\x9b\x3e\xd1\x08\xec\x7c\x5b\xb9\xe8\xf0\xdf\xb1\x68\x73": # WebAuthn key for Cloudflare return FIDOApp( - label="dash.cloudflare.com", - icon="apps/webauthn/res/icon_cloudflare.toif", - use_sign_count=None, - use_self_attestation=None, + "dash.cloudflare.com", # label + "apps/webauthn/res/icon_cloudflare.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xe2\x7d\x61\xb4\xe9\x9d\xe0\xed\x98\x16\x3c\xb3\x8b\x7a\xf9\x33\xc6\x66\x5e\x55\x09\xe8\x49\x08\x37\x05\x58\x13\x77\x8e\x23\x6a": # WebAuthn key for Coinbase return FIDOApp( - label="coinbase.com", - icon="apps/webauthn/res/icon_coinbase.toif", - use_sign_count=None, - use_self_attestation=None, + "coinbase.com", # label + "apps/webauthn/res/icon_coinbase.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x68\x20\x19\x15\xd7\x4c\xb4\x2a\xf5\xb3\xcc\x5c\x95\xb9\x55\x3e\x3e\x3a\x83\xb4\xd2\xa9\x3b\x45\xfb\xad\xaa\x84\x69\xff\x8e\x6e": # U2F key for Dashlane return FIDOApp( - label="www.dashlane.com", - icon="apps/webauthn/res/icon_dashlane.toif", - use_sign_count=None, - use_self_attestation=None, + "www.dashlane.com", # label + "apps/webauthn/res/icon_dashlane.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xc5\x0f\x8a\x7b\x70\x8e\x92\xf8\x2e\x7a\x50\xe2\xbd\xc5\x5d\x8f\xd9\x1a\x22\xfe\x6b\x29\xc0\xcd\xf7\x80\x55\x30\x84\x2a\xf5\x81": # U2F key for Dropbox return FIDOApp( - label="www.dropbox.com", - icon="apps/webauthn/res/icon_dropbox.toif", - use_sign_count=False, - use_self_attestation=None, + "www.dropbox.com", # label + "apps/webauthn/res/icon_dropbox.toif", # icon + False, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x82\xf4\xa8\xc9\x5f\xec\x94\xb2\x6b\xaf\x9e\x37\x25\x0e\x95\x63\xd9\xa3\x66\xc7\xbe\x26\x1c\xa4\xdd\x01\x01\xf4\xd5\xef\xcb\x83": # WebAuthn key for Dropbox return FIDOApp( - label="www.dropbox.com", - icon="apps/webauthn/res/icon_dropbox.toif", - use_sign_count=False, - use_self_attestation=None, + "www.dropbox.com", # label + "apps/webauthn/res/icon_dropbox.toif", # icon + False, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xf3\xe2\x04\x2f\x94\x60\x7d\xa0\xa9\xc1\xf3\xb9\x5e\x0d\x2f\x2b\xb2\xe0\x69\xc5\xbb\x4f\xa7\x64\xaf\xfa\x64\x7d\x84\x7b\x7e\xd6": # U2F key for Duo return FIDOApp( - label="duosecurity.com", - icon="apps/webauthn/res/icon_duo.toif", - use_sign_count=None, - use_self_attestation=None, + "duosecurity.com", # label + "apps/webauthn/res/icon_duo.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x31\x19\x33\x28\xf8\xe2\x1d\xfb\x6c\x99\xf3\x22\xd2\x2d\x7b\x0b\x50\x87\x78\xe6\x4f\xfb\xba\x86\xe5\x22\x93\x37\x90\x31\xb8\x74": # WebAuthn key for Facebook return FIDOApp( - label="facebook.com", - icon="apps/webauthn/res/icon_facebook.toif", - use_sign_count=None, - use_self_attestation=None, + "facebook.com", # label + "apps/webauthn/res/icon_facebook.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x69\x66\xab\xe3\x67\x4e\xa2\xf5\x30\x79\xeb\x71\x01\x97\x84\x8c\x9b\xe6\xf3\x63\x99\x2f\xd0\x29\xe9\x89\x84\x47\xcb\x9f\x00\x84": # U2F key for FastMail return FIDOApp( - label="www.fastmail.com", - icon="apps/webauthn/res/icon_fastmail.toif", - use_sign_count=None, - use_self_attestation=None, + "www.fastmail.com", # label + "apps/webauthn/res/icon_fastmail.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x3f\xcb\x82\x82\xb8\x46\x76\xeb\xee\x71\x40\xe3\x9e\xca\xe1\x6e\xeb\x19\x90\x64\xc7\xc7\xe4\x43\x2e\x28\xc9\xb5\x7e\x4b\x60\x39": # WebAuthn key for FastMail return FIDOApp( - label="fastmail.com", - icon="apps/webauthn/res/icon_fastmail.toif", - use_sign_count=None, - use_self_attestation=None, + "fastmail.com", # label + "apps/webauthn/res/icon_fastmail.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x9d\x61\x44\x2f\x5c\xe1\x33\xbd\x46\x54\x4f\xc4\x2f\x0a\x6d\x54\xc0\xde\xb8\x88\x40\xca\xc2\xb6\xae\xfa\x65\x14\xf8\x93\x49\xe9": # U2F key for Fedora return FIDOApp( - label="fedoraproject.org", - icon="apps/webauthn/res/icon_fedora.toif", - use_sign_count=None, - use_self_attestation=None, + "fedoraproject.org", # label + "apps/webauthn/res/icon_fedora.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xa4\xe2\x2d\xca\xfe\xa7\xe9\x0e\x12\x89\x50\x11\x39\x89\xfc\x45\x97\x8d\xc9\xfb\x87\x76\x75\x60\x51\x6c\x1c\x69\xdf\xdf\xd1\x96": # U2F key for Gandi return FIDOApp( - label="gandi.net", - icon="apps/webauthn/res/icon_gandi.toif", - use_sign_count=False, - use_self_attestation=None, + "gandi.net", # label + "apps/webauthn/res/icon_gandi.toif", # icon + False, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x54\xce\x65\x1e\xd7\x15\xb4\xaa\xa7\x55\xee\xce\xbd\x4e\xa0\x95\x08\x15\xb3\x34\xbd\x07\xd1\x09\x89\x3e\x96\x30\x18\xcd\xdb\xd9": # WebAuthn key for Gandi return FIDOApp( - label="gandi.net", - icon="apps/webauthn/res/icon_gandi.toif", - use_sign_count=False, - use_self_attestation=None, + "gandi.net", # label + "apps/webauthn/res/icon_gandi.toif", # icon + False, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x86\x06\xc1\x68\xe5\x1f\xc1\x31\xe5\x46\xad\x57\xa1\x9f\x32\x97\xb1\x1e\x0e\x5c\xe8\x3e\x8e\x89\x31\xb2\x85\x08\x11\xcf\xa8\x81": # WebAuthn key for Gemini return FIDOApp( - label="gemini.com", - icon="apps/webauthn/res/icon_gemini.toif", - use_sign_count=False, - use_self_attestation=True, + "gemini.com", # label + "apps/webauthn/res/icon_gemini.toif", # icon + False, # use_sign_count + True, # use_self_attestation ) if rp_id_hash == b"\x70\x61\x7d\xfe\xd0\x65\x86\x3a\xf4\x7c\x15\x55\x6c\x91\x79\x88\x80\x82\x8c\xc4\x07\xfd\xf7\x0a\xe8\x50\x11\x56\x94\x65\xa0\x75": # U2F key for GitHub return FIDOApp( - label="github.com", - icon="apps/webauthn/res/icon_github.toif", - use_sign_count=True, - use_self_attestation=None, + "github.com", # label + "apps/webauthn/res/icon_github.toif", # icon + True, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x3a\xeb\x00\x24\x60\x38\x1c\x6f\x25\x8e\x83\x95\xd3\x02\x6f\x57\x1f\x0d\x9a\x76\x48\x8d\xcd\x83\x76\x39\xb1\x3a\xed\x31\x65\x60": # WebAuthn key for GitHub return FIDOApp( - label="github.com", - icon="apps/webauthn/res/icon_github.toif", - use_sign_count=True, - use_self_attestation=None, + "github.com", # label + "apps/webauthn/res/icon_github.toif", # icon + True, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xe7\xbe\x96\xa5\x1b\xd0\x19\x2a\x72\x84\x0d\x2e\x59\x09\xf7\x2b\xa8\x2a\x2f\xe9\x3f\xaa\x62\x4f\x03\x39\x6b\x30\xe4\x94\xc8\x04": # U2F key for GitLab return FIDOApp( - label="gitlab.com", - icon="apps/webauthn/res/icon_gitlab.toif", - use_sign_count=None, - use_self_attestation=None, + "gitlab.com", # label + "apps/webauthn/res/icon_gitlab.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xa5\x46\x72\xb2\x22\xc4\xcf\x95\xe1\x51\xed\x8d\x4d\x3c\x76\x7a\x6c\xc3\x49\x43\x59\x43\x79\x4e\x88\x4f\x3d\x02\x3a\x82\x29\xfd": # U2F key for Google return FIDOApp( - label="google.com", - icon="apps/webauthn/res/icon_google.toif", - use_sign_count=None, - use_self_attestation=None, + "google.com", # label + "apps/webauthn/res/icon_google.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xd4\xc9\xd9\x02\x73\x26\x27\x1a\x89\xce\x51\xfc\xaf\x32\x8e\xd6\x73\xf1\x7b\xe3\x34\x69\xff\x97\x9e\x8a\xb8\xdd\x50\x1e\x66\x4f": # WebAuthn key for Google return FIDOApp( - label="google.com", - icon="apps/webauthn/res/icon_google.toif", - use_sign_count=None, - use_self_attestation=None, + "google.com", # label + "apps/webauthn/res/icon_google.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x9c\x2e\x02\xc4\xff\xf7\x76\x62\xe1\xde\x80\x3b\x43\x9e\x11\xc0\xdd\x0c\x3f\x66\x42\xce\xc4\xe6\x84\xd6\x49\x87\x0a\xd1\xbb\x59": # WebAuthn key for Invity return FIDOApp( - label="invity.io", - icon="apps/webauthn/res/icon_invity.toif", - use_sign_count=None, - use_self_attestation=None, + "invity.io", # label + "apps/webauthn/res/icon_invity.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x53\xa1\x5b\xa4\x2a\x7c\x03\x25\xb8\xdb\xee\x28\x96\x34\xa4\x8f\x58\xae\xa3\x24\x66\x45\xd5\xff\x41\x8f\x9b\xb8\x81\x98\x85\xa9": # U2F key for Keeper return FIDOApp( - label="keepersecurity.com", - icon="apps/webauthn/res/icon_keeper.toif", - use_sign_count=None, - use_self_attestation=None, + "keepersecurity.com", # label + "apps/webauthn/res/icon_keeper.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xd6\x5f\x00\x5e\xf4\xde\xa9\x32\x0c\x99\x73\x05\x3c\x95\xff\x60\x20\x11\x5d\x5f\xec\x1b\x7f\xee\x41\xa5\x78\xe1\x8d\xf9\xca\x8c": # U2F key for Keeper return FIDOApp( - label="keepersecurity.eu", - icon="apps/webauthn/res/icon_keeper.toif", - use_sign_count=None, - use_self_attestation=None, + "keepersecurity.eu", # label + "apps/webauthn/res/icon_keeper.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x3f\x37\x50\x85\x33\x2c\xac\x4f\xad\xf9\xe5\xdd\x28\xcd\x54\x69\x8f\xab\x98\x4b\x75\xd9\xc3\x6a\x07\x2c\xb1\x60\x77\x3f\x91\x52": # WebAuthn key for Kraken return FIDOApp( - label="kraken.com", - icon="apps/webauthn/res/icon_kraken.toif", - use_sign_count=None, - use_self_attestation=None, + "kraken.com", # label + "apps/webauthn/res/icon_kraken.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xf8\x3f\xc3\xa1\xb2\x89\xa0\xde\xc5\xc1\xc8\xaa\x07\xe9\xb5\xdd\x9c\xbb\x76\xf6\xb2\xf5\x60\x60\x17\x66\x72\x68\xe5\xb9\xc4\x5e": # WebAuthn key for login.gov return FIDOApp( - label="secure.login.gov", - icon="apps/webauthn/res/icon_login.gov.toif", - use_sign_count=False, - use_self_attestation=None, + "secure.login.gov", # label + "apps/webauthn/res/icon_login.gov.toif", # icon + False, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x35\x6c\x9e\xd4\xa0\x93\x21\xb9\x69\x5f\x1e\xaf\x91\x82\x03\xf1\xb5\x5f\x68\x9d\xa6\x1f\xbc\x96\x18\x4c\x15\x7d\xda\x68\x0c\x81": # WebAuthn key for Microsoft return FIDOApp( - label="login.microsoft.com", - icon="apps/webauthn/res/icon_microsoft.toif", - use_sign_count=False, - use_self_attestation=False, + "login.microsoft.com", # label + "apps/webauthn/res/icon_microsoft.toif", # icon + False, # use_sign_count + False, # use_self_attestation ) if rp_id_hash == b"\xab\x2d\xaf\x07\x43\xde\x78\x2a\x70\x18\x9a\x0f\x5e\xfc\x30\x90\x2f\x92\x5b\x9f\x9a\x18\xc5\xd7\x14\x1b\x7b\x12\xf8\xa0\x10\x0c": # WebAuthn key for mojeID return FIDOApp( - label="mojeid.cz", - icon="apps/webauthn/res/icon_mojeid.toif", - use_sign_count=None, - use_self_attestation=None, + "mojeid.cz", # label + "apps/webauthn/res/icon_mojeid.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x85\x71\x01\x36\x1b\x20\xa9\x54\x4c\xdb\x9b\xef\x65\x85\x8b\x6b\xac\x70\x13\x55\x0d\x8f\x84\xf7\xef\xee\x25\x2b\x96\xfa\x7c\x1e": # WebAuthn key for Namecheap return FIDOApp( - label="www.namecheap.com", - icon="apps/webauthn/res/icon_namecheap.toif", - use_sign_count=None, - use_self_attestation=None, + "www.namecheap.com", # label + "apps/webauthn/res/icon_namecheap.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xa2\x59\xc2\xb5\x0d\x78\x50\x80\xf8\xbe\x7f\x17\xca\xf8\x15\x6c\x8d\x18\xf4\x7e\xdb\xaf\x51\x8f\xa6\xf5\x9f\x29\xcd\x28\xf1\x5c": # WebAuthn key for Proton return FIDOApp( - label="proton.me", - icon="apps/webauthn/res/icon_proton.toif", - use_sign_count=None, - use_self_attestation=None, + "proton.me", # label + "apps/webauthn/res/icon_proton.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x08\xb2\xa3\xd4\x19\x39\xaa\x31\x66\x84\x93\xcb\x36\xcd\xcc\x4f\x16\xc4\xd9\xb4\xc8\x23\x8b\x73\xc2\xf6\x72\xc0\x33\x00\x71\x97": # U2F key for Slush Pool return FIDOApp( - label="slushpool.com", - icon="apps/webauthn/res/icon_slushpool.toif", - use_sign_count=None, - use_self_attestation=None, + "slushpool.com", # label + "apps/webauthn/res/icon_slushpool.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x38\x80\x4f\x2e\xff\x74\xf2\x28\xb7\x41\x51\xc2\x01\xaa\x82\xe7\xe8\xee\xfc\xac\xfe\xcf\x23\xfa\x14\x6b\x13\xa3\x76\x66\x31\x4f": # U2F key for Slush Pool return FIDOApp( - label="slushpool.com", - icon="apps/webauthn/res/icon_slushpool.toif", - use_sign_count=None, - use_self_attestation=None, + "slushpool.com", # label + "apps/webauthn/res/icon_slushpool.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x2a\xc6\xad\x09\xa6\xd0\x77\x2c\x44\xda\x73\xa6\x07\x2f\x9d\x24\x0f\xc6\x85\x4a\x70\xd7\x9c\x10\x24\xff\x7c\x75\x59\x59\x32\x92": # U2F key for Stripe return FIDOApp( - label="stripe.com", - icon="apps/webauthn/res/icon_stripe.toif", - use_sign_count=None, - use_self_attestation=None, + "stripe.com", # label + "apps/webauthn/res/icon_stripe.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xfa\xbe\xec\xe3\x98\x2f\xad\x9d\xdc\xc9\x8f\x91\xbd\x2e\x75\xaf\xc7\xd1\xf4\xca\x54\x49\x29\xb2\xd0\xd0\x42\x12\xdf\xfa\x30\xfa": # U2F key for Tutanota return FIDOApp( - label="tutanota.com", - icon="apps/webauthn/res/icon_tutanota.toif", - use_sign_count=None, - use_self_attestation=None, + "tutanota.com", # label + "apps/webauthn/res/icon_tutanota.toif", # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x1b\x3c\x16\xdd\x2f\x7c\x46\xe2\xb4\xc2\x89\xdc\x16\x74\x6b\xcc\x60\xdf\xcf\x0f\xb8\x18\xe1\x32\x15\x52\x6e\x14\x08\xe7\xf4\x68": # U2F key for u2f.bin.coffee return FIDOApp( - label="u2f.bin.coffee", - icon=None, - use_sign_count=None, - use_self_attestation=None, + "u2f.bin.coffee", # label + None, # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xa6\x42\xd2\x1b\x7c\x6d\x55\xe1\xce\x23\xc5\x39\x98\x28\xd2\xc7\x49\xbf\x6a\x6e\xf2\xfe\x03\xcc\x9e\x10\xcd\xf4\xed\x53\x08\x8b": # WebAuthn key for webauthn.bin.coffee return FIDOApp( - label="webauthn.bin.coffee", - icon=None, - use_sign_count=None, - use_self_attestation=None, + "webauthn.bin.coffee", # label + None, # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\x74\xa6\xea\x92\x13\xc9\x9c\x2f\x74\xb2\x24\x92\xb3\x20\xcf\x40\x26\x2a\x94\xc1\xa9\x50\xa0\x39\x7f\x29\x25\x0b\x60\x84\x1e\xf0": # WebAuthn key for WebAuthn.io return FIDOApp( - label="webauthn.io", - icon=None, - use_sign_count=None, - use_self_attestation=None, + "webauthn.io", # label + None, # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xf9\x5b\xc7\x38\x28\xee\x21\x0f\x9f\xd3\xbb\xe7\x2d\x97\x90\x80\x13\xb0\xa3\x75\x9e\x9a\xea\x3d\x0a\xe3\x18\x76\x6c\xd2\xe1\xad": # WebAuthn key for WebAuthn.me return FIDOApp( - label="webauthn.me", - icon=None, - use_sign_count=None, - use_self_attestation=None, + "webauthn.me", # label + None, # icon + None, # use_sign_count + None, # use_self_attestation ) if rp_id_hash == b"\xc4\x6c\xef\x82\xad\x1b\x54\x64\x77\x59\x1d\x00\x8b\x08\x75\x9e\xc3\xe6\xd2\xec\xb4\xf3\x94\x74\xbf\xea\x69\x69\x92\x5d\x03\xb7": # WebAuthn key for demo.yubico.com return FIDOApp( - label="demo.yubico.com", - icon=None, - use_sign_count=None, - use_self_attestation=None, + "demo.yubico.com", # label + None, # icon + None, # use_sign_count + None, # use_self_attestation ) return None diff --git a/core/src/apps/webauthn/knownapps.py.mako b/core/src/apps/webauthn/knownapps.py.mako index bd21af0a0..99102dfdf 100644 --- a/core/src/apps/webauthn/knownapps.py.mako +++ b/core/src/apps/webauthn/knownapps.py.mako @@ -2,6 +2,8 @@ # (by running `make templates` in `core`) # do not edit manually! +# NOTE: using positional arguments saves 520 bytes in flash space + class FIDOApp: def __init__( @@ -38,10 +40,10 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: if rp_id_hash == ${black_repr(rp_id_hash)}: # ${type} key for ${app.name} return FIDOApp( - label=${black_repr(label)}, - icon=${black_repr(app.icon_res)}, - use_sign_count=${black_repr(app.use_sign_count)}, - use_self_attestation=${black_repr(app.use_self_attestation)}, + ${black_repr(label)}, # label + ${black_repr(app.icon_res)}, # icon + ${black_repr(app.use_sign_count)}, # use_sign_count + ${black_repr(app.use_self_attestation)}, # use_self_attestation ) % endfor diff --git a/core/src/apps/webauthn/list_resident_credentials.py b/core/src/apps/webauthn/list_resident_credentials.py index bbcfe6ea9..dda4bc2c2 100644 --- a/core/src/apps/webauthn/list_resident_credentials.py +++ b/core/src/apps/webauthn/list_resident_credentials.py @@ -1,22 +1,22 @@ from typing import TYPE_CHECKING -from trezor import wire -from trezor.messages import WebAuthnCredential, WebAuthnCredentials -from trezor.ui.layouts import confirm_action - -from . import resident_credentials - if TYPE_CHECKING: - from trezor.messages import WebAuthnListResidentCredentials + from trezor.messages import WebAuthnListResidentCredentials, WebAuthnCredentials + from trezor.wire import Context async def list_resident_credentials( - ctx: wire.Context, msg: WebAuthnListResidentCredentials + ctx: Context, msg: WebAuthnListResidentCredentials ) -> WebAuthnCredentials: + from trezor.messages import WebAuthnCredential, WebAuthnCredentials + from trezor.ui.layouts import confirm_action + + from . import resident_credentials + await confirm_action( ctx, "credentials_list", - title="List credentials", + "List credentials", description="Do you want to export information about the resident credentials stored on this device?", ) creds = [ diff --git a/core/src/apps/webauthn/remove_resident_credential.py b/core/src/apps/webauthn/remove_resident_credential.py index 33de0d340..dc6c380f0 100644 --- a/core/src/apps/webauthn/remove_resident_credential.py +++ b/core/src/apps/webauthn/remove_resident_credential.py @@ -1,17 +1,11 @@ from typing import TYPE_CHECKING -import storage.device -import storage.resident_credentials -from trezor import wire -from trezor.messages import Success from trezor.ui.components.common.webauthn import ConfirmInfo -from trezor.ui.layouts.webauthn import confirm_webauthn - -from .credential import Fido2Credential -from .resident_credentials import get_resident_credential if TYPE_CHECKING: - from trezor.messages import WebAuthnRemoveResidentCredential + from trezor.messages import WebAuthnRemoveResidentCredential, Success + from .credential import Fido2Credential + from trezor.wire import Context class ConfirmRemoveCredential(ConfirmInfo): @@ -31,8 +25,15 @@ class ConfirmRemoveCredential(ConfirmInfo): async def remove_resident_credential( - ctx: wire.Context, msg: WebAuthnRemoveResidentCredential + ctx: Context, msg: WebAuthnRemoveResidentCredential ) -> Success: + import storage.device + import storage.resident_credentials + from trezor import wire + from trezor.messages import Success + from trezor.ui.layouts.webauthn import confirm_webauthn + from .resident_credentials import get_resident_credential + if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if msg.index is None: diff --git a/core/src/apps/webauthn/resident_credentials.py b/core/src/apps/webauthn/resident_credentials.py index eda12d86c..386bf83d4 100644 --- a/core/src/apps/webauthn/resident_credentials.py +++ b/core/src/apps/webauthn/resident_credentials.py @@ -1,17 +1,22 @@ from micropython import const -from typing import Iterator +from typing import TYPE_CHECKING -import storage.resident_credentials +import storage.resident_credentials as storage_resident_credentials from storage.resident_credentials import MAX_RESIDENT_CREDENTIALS -from .credential import Fido2Credential +if TYPE_CHECKING: + from typing import Iterator + from .credential import Fido2Credential -RP_ID_HASH_LENGTH = const(32) + +_RP_ID_HASH_LENGTH = const(32) def _credential_from_data(index: int, data: bytes) -> Fido2Credential: - rp_id_hash = data[:RP_ID_HASH_LENGTH] - cred_id = data[RP_ID_HASH_LENGTH:] + from .credential import Fido2Credential + + rp_id_hash = data[:_RP_ID_HASH_LENGTH] + cred_id = data[_RP_ID_HASH_LENGTH:] cred = Fido2Credential.from_cred_id(cred_id, rp_id_hash) cred.index = index return cred @@ -19,20 +24,20 @@ def _credential_from_data(index: int, data: bytes) -> Fido2Credential: def find_all() -> Iterator[Fido2Credential]: for index in range(MAX_RESIDENT_CREDENTIALS): - data = storage.resident_credentials.get(index) + data = storage_resident_credentials.get(index) if data is not None: yield _credential_from_data(index, data) def find_by_rp_id_hash(rp_id_hash: bytes) -> Iterator[Fido2Credential]: for index in range(MAX_RESIDENT_CREDENTIALS): - data = storage.resident_credentials.get(index) + data = storage_resident_credentials.get(index) if data is None: # empty slot continue - if data[:RP_ID_HASH_LENGTH] != rp_id_hash: + if data[:_RP_ID_HASH_LENGTH] != rp_id_hash: # rp_id_hash mismatch continue @@ -43,7 +48,7 @@ def get_resident_credential(index: int) -> Fido2Credential | None: if not 0 <= index < MAX_RESIDENT_CREDENTIALS: return None - data = storage.resident_credentials.get(index) + data = storage_resident_credentials.get(index) if data is None: return None @@ -53,14 +58,14 @@ def get_resident_credential(index: int) -> Fido2Credential | None: def store_resident_credential(cred: Fido2Credential) -> bool: slot = None for index in range(MAX_RESIDENT_CREDENTIALS): - stored_data = storage.resident_credentials.get(index) + stored_data = storage_resident_credentials.get(index) if stored_data is None: # found candidate empty slot if slot is None: slot = index continue - if cred.rp_id_hash != stored_data[:RP_ID_HASH_LENGTH]: + if cred.rp_id_hash != stored_data[:_RP_ID_HASH_LENGTH]: # slot is occupied by a different rp_id_hash continue @@ -74,5 +79,5 @@ def store_resident_credential(cred: Fido2Credential) -> bool: return False cred_data = cred.rp_id_hash + cred.id - storage.resident_credentials.set(slot, cred_data) + storage_resident_credentials.set(slot, cred_data) return True diff --git a/core/tests/test_apps.webauthn.credential.py b/core/tests/test_apps.webauthn.credential.py index 70800099e..07b4497f9 100644 --- a/core/tests/test_apps.webauthn.credential.py +++ b/core/tests/test_apps.webauthn.credential.py @@ -2,9 +2,8 @@ from common import * import storage import storage.device from apps.common import mnemonic -from apps.webauthn.credential import Fido2Credential, U2fCredential, NAME_MAX_LENGTH -from apps.webauthn.fido2 import distinguishable_cred_list -from trezor.crypto.curve import nist256p1 +from apps.webauthn.credential import Fido2Credential, U2fCredential, _NAME_MAX_LENGTH +from apps.webauthn.fido2 import _distinguishable_cred_list from trezor.crypto.hashlib import sha256 @@ -68,13 +67,13 @@ class TestCredential(unittest.TestCase): self.assertIsNone(cred.user_name) self.assertIsNone(cred.user_display_name) - cred.rp_name = "a" * (NAME_MAX_LENGTH - 2) + "\u0123" - cred.user_name = "a" * (NAME_MAX_LENGTH - 1) + "\u0123" - cred.user_display_name = "a" * NAME_MAX_LENGTH + "\u0123" + cred.rp_name = "a" * (_NAME_MAX_LENGTH - 2) + "\u0123" + cred.user_name = "a" * (_NAME_MAX_LENGTH - 1) + "\u0123" + cred.user_display_name = "a" * _NAME_MAX_LENGTH + "\u0123" cred.truncate_names() - self.assertEqual(cred.rp_name, "a" * (NAME_MAX_LENGTH - 2) + "\u0123") - self.assertEqual(cred.user_name, "a" * (NAME_MAX_LENGTH - 1)) - self.assertEqual(cred.user_display_name, "a" * NAME_MAX_LENGTH) + self.assertEqual(cred.rp_name, "a" * (_NAME_MAX_LENGTH - 2) + "\u0123") + self.assertEqual(cred.user_name, "a" * (_NAME_MAX_LENGTH - 1)) + self.assertEqual(cred.user_display_name, "a" * _NAME_MAX_LENGTH) def test_allow_list_processing(self): a1 = Fido2Credential() @@ -108,17 +107,17 @@ class TestCredential(unittest.TestCase): c2 = U2fCredential() - self.assertEqual(sorted(distinguishable_cred_list([a1, a2, a3, b1, b2, c1, c2])), [b2, a3, a1, c1]) - self.assertEqual(sorted(distinguishable_cred_list([c2, c1, b2, b1, a3, a2, a1])), [b2, a3, a1, c2]) + self.assertEqual(sorted(_distinguishable_cred_list([a1, a2, a3, b1, b2, c1, c2])), [b2, a3, a1, c1]) + self.assertEqual(sorted(_distinguishable_cred_list([c2, c1, b2, b1, a3, a2, a1])), [b2, a3, a1, c2]) # Test input by creation time. - self.assertEqual(sorted(distinguishable_cred_list([b2, a3, c1, a2, b1, a1, c2])), [b2, a3, a1, c1]) - self.assertEqual(sorted(distinguishable_cred_list([c2, a1, b1, a2, c1, a3, b2])), [b2, a3, a1, c2]) + self.assertEqual(sorted(_distinguishable_cred_list([b2, a3, c1, a2, b1, a1, c2])), [b2, a3, a1, c1]) + self.assertEqual(sorted(_distinguishable_cred_list([c2, a1, b1, a2, c1, a3, b2])), [b2, a3, a1, c2]) # Test duplicities. - self.assertEqual(sorted(distinguishable_cred_list([c1, a1, a1, c2, c1])), [a1, c1]) - self.assertEqual(sorted(distinguishable_cred_list([b2, b3])), [b2]) - self.assertEqual(sorted(distinguishable_cred_list([b3, b2])), [b3]) + self.assertEqual(sorted(_distinguishable_cred_list([c1, a1, a1, c2, c1])), [a1, c1]) + self.assertEqual(sorted(_distinguishable_cred_list([b2, b3])), [b2]) + self.assertEqual(sorted(_distinguishable_cred_list([b3, b2])), [b3]) if __name__ == '__main__':