Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions core/src/apps/webauthn/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,11 @@ def app_name(self) -> str:
return self.rp_id

def account_name(self) -> str | None:
if self.user_name:
# Some relying parties send a blank/whitespace-only "name" (e.g. " ");
# treat those as absent and fall back to displayName, then user id.
if self.user_name and self.user_name.strip():
return self.user_name
elif self.user_display_name:
elif self.user_display_name and self.user_display_name.strip():
return self.user_display_name
elif self.user_id:
return hexlify(self.user_id).decode()
Expand Down
111 changes: 74 additions & 37 deletions core/src/apps/webauthn/fido2.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
from trezor.lvglui.i18n import gettext as _, keys as i18n_keys
from trezor.ui.components.common.confirm import Pageable
from trezor.ui.components.common.webauthn import ConfirmInfo
from trezor.ui.layouts.lvgl.webauthn import confirm_webauthn, confirm_webauthn_reset
from trezor.ui.layouts.lvgl.webauthn import (
confirm_webauthn,
confirm_webauthn_reset,
select_webauthn_account,
)

from apps.base import device_is_unlocked, set_homescreen
from apps.common import cbor
Expand Down Expand Up @@ -176,7 +180,7 @@
_FIDO_ATT_PRIV_KEY = b"q&\xac+\xf6D\xdca\x86\xad\x83\xef\x1f\xcd\xf1*W\xb5\xcf\xa2\x00\x0b\x8a\xd0'\xe9V\xe8T\xc5\n\x8b"
_FIDO_ATT_CERT = b"0\x82\x01\xcd0\x82\x01s\xa0\x03\x02\x01\x02\x02\x04\x03E`\xc40\n\x06\x08*\x86H\xce=\x04\x03\x020.1,0*\x06\x03U\x04\x03\x0c#Trezor FIDO Root CA Serial 841513560 \x17\r200406100417Z\x18\x0f20500406100417Z0x1\x0b0\t\x06\x03U\x04\x06\x13\x02CZ1\x1c0\x1a\x06\x03U\x04\n\x0c\x13SatoshiLabs, s.r.o.1\"0 \x06\x03U\x04\x0b\x0c\x19Authenticator Attestation1'0%\x06\x03U\x04\x03\x0c\x1eTrezor FIDO EE Serial 548784040Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x04\xd9\x18\xbd\xfa\x8aT\xac\x92\xe9\r\xa9\x1f\xcaz\xa2dT\xc0\xd1s61M\xde\x83\xa5K\x86\xb5\xdfN\xf0Re\x9a\x1do\xfc\xb7F\x7f\x1a\xcd\xdb\x8a3\x08\x0b^\xed\x91\x89\x13\xf4C\xa5&\x1b\xc7{h`o\xc1\xa33010!\x06\x0b+\x06\x01\x04\x01\x82\xe5\x1c\x01\x01\x04\x04\x12\x04\x10\xd6\xd0\xbd\xc3b\xee\xc4\xdb\xde\x8dzenJD\x870\x0c\x06\x03U\x1d\x13\x01\x01\xff\x04\x020\x000\n\x06\x08*\x86H\xce=\x04\x03\x02\x03H\x000E\x02 \x0b\xce\xc4R\xc3\n\x11'\xe5\xd5\xf5\xfc\xf5\xd6Wy\x11+\xe50\xad\x9d-TXJ\xbeE\x86\xda\x93\xc6\x02!\x00\xaf\xca=\xcf\xd8A\xb0\xadz\x9e$}\x0ff\xf4L,\x83\xf9T\xab\x95O\x896\xc15\x08\x7fX\xf1\x95"
else:
_FIDO_ATT_CERT = b"\x30\x82\x02\x65\x30\x82\x02\x0C\xA0\x03\x02\x01\x02\x02\x08\x2F\x1F\xAB\x58\x0B\xEB\xE5\xF0\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x30\x81\x97\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44\x49\x41\x4E\x31\x1F\x30\x1D\x06\x03\x55\x04\x0A\x13\x16\x4F\x4E\x45\x4B\x45\x59\x20\x47\x4C\x4F\x42\x41\x4C\x20\x43\x4F\x2E\x2C\x20\x4C\x54\x44\x31\x0F\x30\x0D\x06\x03\x55\x04\x0B\x13\x06\x4F\x4E\x45\x4B\x45\x59\x31\x14\x30\x12\x06\x03\x55\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x52\x4F\x4F\x54\x31\x1C\x30\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x1E\x17\x0D\x32\x34\x31\x30\x31\x38\x30\x32\x30\x37\x30\x30\x5A\x17\x0D\x32\x39\x31\x30\x31\x38\x30\x32\x30\x37\x30\x30\x5A\x30\x81\xAA\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44\x49\x41\x4E\x31\x1F\x30\x1D\x06\x03\x55\x04\x0A\x13\x16\x4F\x4E\x45\x4B\x45\x59\x20\x47\x4C\x4F\x42\x41\x4C\x20\x43\x4F\x2E\x2C\x20\x4C\x54\x44\x31\x22\x30\x20\x06\x03\x55\x04\x0B\x13\x19\x41\x75\x74\x68\x65\x6E\x74\x69\x63\x61\x74\x6F\x72\x20\x41\x74\x74\x65\x73\x74\x61\x74\x69\x6F\x6E\x31\x14\x30\x12\x06\x03\x55\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x46\x49\x44\x4F\x31\x1C\x30\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x59\x30\x13\x06\x07\x2A\x86\x48\xCE\x3D\x02\x01\x06\x08\x2A\x86\x48\xCE\x3D\x03\x01\x07\x03\x42\x00\x04\x20\xC4\xC2\xCA\x28\x36\x66\xB2\xD7\xA0\x7C\x25\xB7\x2C\x5F\xC3\xAC\xFE\xB4\x9C\x64\xB0\x27\xC1\x84\xA3\xEA\x10\xE8\xD0\x3D\x48\xA4\xA4\x12\x6C\x3D\xBC\xC6\x1F\x9F\x54\xDA\xB5\xDE\x30\x85\xB7\x30\x9F\x28\x2A\xC7\x63\xAF\x6C\x0B\xF2\xFA\xA2\x33\x88\x0F\x75\xA3\x2D\x30\x2B\x30\x09\x06\x03\x55\x1D\x13\x04\x02\x30\x00\x30\x1E\x06\x09\x60\x86\x48\x01\x86\xF8\x42\x01\x0D\x04\x11\x16\x0F\x78\x63\x61\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x03\x47\x00\x30\x44\x02\x20\x2F\x73\x6A\x80\xBC\x4F\x38\x0D\xDE\x21\xC1\x35\x40\x59\x09\x8E\x4C\x81\x9D\x3E\xA9\x6A\x51\x2F\xB3\x54\xEE\xEF\x5B\x84\xA5\xF9\x02\x20\x04\xD8\x37\x35\x88\x76\xED\x71\x02\x84\x82\x6E\x26\x3A\xB8\x8F\x82\xA4\xF8\xD4\x2E\x15\x94\xB6\xE2\x7E\xDD\xC3\x7D\xE1\xA5\x63"
_FIDO_ATT_CERT = b"\x30\x82\x02\x7C\x30\x82\x02\x21\xA0\x03\x02\x01\x02\x02\x08\x2F\x1F\xAB\x58\x0B\xEB\xE5\xF0\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x30\x81\x97\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44\x49\x41\x4E\x31\x1F\x30\x1D\x06\x03\x55\x04\x0A\x13\x16\x4F\x4E\x45\x4B\x45\x59\x20\x47\x4C\x4F\x42\x41\x4C\x20\x43\x4F\x2E\x2C\x20\x4C\x54\x44\x31\x0F\x30\x0D\x06\x03\x55\x04\x0B\x13\x06\x4F\x4E\x45\x4B\x45\x59\x31\x14\x30\x12\x06\x03\x55\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x52\x4F\x4F\x54\x31\x1C\x30\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x1E\x17\x0D\x32\x34\x31\x30\x31\x38\x30\x32\x30\x37\x30\x30\x5A\x17\x0D\x34\x34\x31\x30\x31\x38\x30\x32\x30\x37\x30\x30\x5A\x30\x81\xAA\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44\x49\x41\x4E\x31\x1F\x30\x1D\x06\x03\x55\x04\x0A\x13\x16\x4F\x4E\x45\x4B\x45\x59\x20\x47\x4C\x4F\x42\x41\x4C\x20\x43\x4F\x2E\x2C\x20\x4C\x54\x44\x31\x22\x30\x20\x06\x03\x55\x04\x0B\x13\x19\x41\x75\x74\x68\x65\x6E\x74\x69\x63\x61\x74\x6F\x72\x20\x41\x74\x74\x65\x73\x74\x61\x74\x69\x6F\x6E\x31\x14\x30\x12\x06\x03\x55\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x46\x49\x44\x4F\x31\x1C\x30\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x59\x30\x13\x06\x07\x2A\x86\x48\xCE\x3D\x02\x01\x06\x08\x2A\x86\x48\xCE\x3D\x03\x01\x07\x03\x42\x00\x04\x20\xC4\xC2\xCA\x28\x36\x66\xB2\xD7\xA0\x7C\x25\xB7\x2C\x5F\xC3\xAC\xFE\xB4\x9C\x64\xB0\x27\xC1\x84\xA3\xEA\x10\xE8\xD0\x3D\x48\xA4\xA4\x12\x6C\x3D\xBC\xC6\x1F\x9F\x54\xDA\xB5\xDE\x30\x85\xB7\x30\x9F\x28\x2A\xC7\x63\xAF\x6C\x0B\xF2\xFA\xA2\x33\x88\x0F\x75\xA3\x42\x30\x40\x30\x09\x06\x03\x55\x1D\x13\x04\x02\x30\x00\x30\x13\x06\x0B\x2B\x06\x01\x04\x01\x82\xE5\x1C\x02\x01\x01\x04\x04\x03\x02\x05\x60\x30\x1E\x06\x09\x60\x86\x48\x01\x86\xF8\x42\x01\x0D\x04\x11\x16\x0F\x78\x63\x61\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x03\x49\x00\x30\x46\x02\x21\x00\xF8\xCD\xE4\x2F\x83\xBB\x8A\x97\xF4\x58\x64\x8F\x49\x46\x08\x31\x0F\x09\xBB\xB3\xBF\x54\x83\xFF\x27\x07\x25\xEB\x3D\xC4\x01\x08\x02\x21\x00\xF9\xA2\x7C\x63\x94\x2B\xB6\x66\x46\xC6\x8A\x87\x5E\x39\x23\x11\xEC\x39\x83\x10\xDD\x6F\x06\x74\x22\x9F\x26\x9D\x11\x04\x3B\x17"
_BOGUS_APPID_CHROME = b"A" * 32
_BOGUS_APPID_FIREFOX = b"\0" * 32
_BOGUS_APPIDS = (_BOGUS_APPID_CHROME, _BOGUS_APPID_FIREFOX)
Expand Down Expand Up @@ -212,9 +216,6 @@
_ALLOW_RESIDENT_CREDENTIALS = storage.device.get_se01_version() >= "1.1.5"
_ALLOW_WINK = False

# The default attestation type to use in MakeCredential responses. If false, then basic attestation will be used by default.
_DEFAULT_USE_SELF_ATTESTATION = True

# The default value of the use_sign_count flag for newly created credentials.
_DEFAULT_USE_SIGN_COUNT = True

Expand Down Expand Up @@ -620,6 +621,11 @@ async def handle_reports(usb_face: io.HID, spi_iface: io.SPI) -> None:

if req is None:
continue
if dialog_mgr.is_busy() and dialog_mgr.state is not None:
active_iface = dialog_mgr.state.iface
if dialog_mgr.iface is not active_iface:
dialog_mgr.set_iface(active_iface)
continue
if dialog_mgr.is_busy() and (
req.cid
not in (
Expand Down Expand Up @@ -860,8 +866,9 @@ async def on_timeout(self) -> None:
await self.on_decline()

async def on_cancel(self) -> None:
cmd = cbor_error(self.cid, _ERR_KEEPALIVE_CANCEL)
await send_cmd(cmd, self.iface)
# The CTAP2 cancel response (_ERR_KEEPALIVE_CANCEL) is sent synchronously
# from the _CMD_CANCEL dispatch path, because this coroutine is torn down
# by DialogManager.reset() and can no longer await a send here.
self.finished = True


Expand Down Expand Up @@ -1021,10 +1028,29 @@ def app_name(self) -> str:
def account_name(self) -> str | None:
return self._creds[self.page()].account_name()

def _single_line_label(self, label: str) -> str:
return label.replace("\r\n", " ").replace("\n", " ").replace("\r", " ")

def account_names(self) -> list[str]:
return [
self._single_line_label(
cred.account_name() or f"{cred.app_name()} #{i + 1}"
)
for i, cred in enumerate(self._creds)
]

def page_count(self) -> int:
return len(self._creds)

def select_account(self, index: int) -> None:
self._page = min(max(index, 0), self.page_count() - 1)

async def confirm_dialog(self) -> bool:
if self.page_count() > 1:
selected = await select_webauthn_account(None, self)
if selected is None:
return False
self.select_account(selected)
if not await confirm_webauthn(None, self):
return False
if self._user_verification:
Expand Down Expand Up @@ -1225,6 +1251,39 @@ async def dialog_workflow(self) -> None:
await self.state.on_decline()


def cmd_cancel(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None:
if __debug__:
log.debug(__name__, "_CMD_CANCEL")
state = dialog_mgr.state
# Was a FIDO2/CTAP2 request pending? Only those get a CBOR cancel response.
is_fido2 = isinstance(state, Fido2State)
pending_cid = state.cid if is_fido2 else req.cid
pending_iface = state.iface if is_fido2 else dialog_mgr.iface
if is_fido2 and dialog_mgr.iface is not pending_iface:
dialog_mgr.set_iface(pending_iface)
return None
# Dismiss the on-screen overlay. lvgl windows are retained and are not removed
# when reset() closes the workflow coroutine, so destroy them explicitly here,
# otherwise the confirmation / PIN page stays up after the host cancels.
if state is not None:
# The register / authenticate confirmation window.
if isinstance(state, ConfirmInfo) and state.screen is not None:
state.screen.destroy()
state.screen = None
# The user-verification PIN window shown during verify_user(), if any.
from trezor.lvglui.scrs.pinscreen import InputPin

pin_wind = InputPin.get_window_if_visible()
if pin_wind is not None:
pin_wind.destroy()
dialog_mgr.result = _RESULT_CANCEL
dialog_mgr.reset()
if is_fido2:
dialog_mgr.set_iface(pending_iface)
return cbor_error(pending_cid, _ERR_KEEPALIVE_CANCEL)
return None


def dispatch_cmd_hid(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None:
if req.cmd == _CMD_MSG:
try:
Expand Down Expand Up @@ -1304,11 +1363,7 @@ def dispatch_cmd_hid(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None:
return cbor_error(req.cid, _ERR_INVALID_CMD)

elif req.cmd == _CMD_CANCEL:
if __debug__:
log.debug(__name__, "_CMD_CANCEL")
dialog_mgr.result = _RESULT_CANCEL
dialog_mgr.reset()
return None
return cmd_cancel(req, dialog_mgr)
else:
if __debug__:
log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd)
Expand Down Expand Up @@ -1394,11 +1449,7 @@ def dispatch_cmd_ble(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None:
log.debug(__name__, "_CMD_WINK")
return cmd_wink(req)
elif req.cmd == _CMD_CANCEL:
if __debug__:
log.debug(__name__, "_CMD_CANCEL")
dialog_mgr.result = _RESULT_CANCEL
dialog_mgr.reset()
return None
return cmd_cancel(req, dialog_mgr)
else:
if __debug__:
log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd)
Expand Down Expand Up @@ -1827,16 +1878,6 @@ 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(
client_data_hash: bytes, cred: Fido2Credential, user_verification: bool
) -> bytes:
Expand Down Expand Up @@ -1864,16 +1905,12 @@ def cbor_make_credential_sign(
+ extensions
)

if use_self_attestation(cred.rp_id_hash):
sig = cred.sign((authenticator_data, client_data_hash))
attestation_statement = {"alg": cred.algorithm, "sig": sig}
else:
sig = basic_attestation_sign((authenticator_data, client_data_hash))
attestation_statement = {
"alg": common.COSE_ALG_ES256,
"sig": sig,
"x5c": [_FIDO_ATT_CERT],
}
sig = basic_attestation_sign((authenticator_data, client_data_hash))
attestation_statement = {
"alg": common.COSE_ALG_ES256,
"sig": sig,
"x5c": [_FIDO_ATT_CERT],
}

# Encode the authenticatorMakeCredential response data.
return cbor.encode(
Expand Down
Loading
Loading