1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-16 03:18:09 +00:00

Merge pull request #576 from trezor/andrewkozlik/fido2-device-test

FIDO2 fixes for device tests and knownapps improvements
This commit is contained in:
Pavol Rusnak 2019-09-27 14:21:45 +02:00 committed by GitHub
commit 8524ff9832
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 369 additions and 182 deletions

View File

@ -1,5 +1,5 @@
{
"label": "Binance",
"webauthn": "www.binance.com",
"webauthn": ["www.binance.com"],
"use_sign_count": false
}

View File

@ -1,4 +1,4 @@
{
"label": "Bitbucket",
"u2f": "https://bitbucket.org"
"u2f": ["12743b921297b77f1135e41fdedd4a846afe82e1f36932a9912f3b0d8dfb7d0e"]
}

View File

@ -1,4 +1,4 @@
{
"label": "Bitfinex",
"u2f": "https://www.bitfinex.com"
"u2f": ["302fd5b4492a07b9febb30e73269eca501205ccfe0c20bf7b472fa2d31e21e63"]
}

View File

@ -1,4 +1,4 @@
{
"label": "Bitwarden",
"u2f": "https://vault.bitwarden.com/app-id.json"
"u2f": ["a34d309ffa28c12414b8ba6c07ee1efae1a85e8a04614859a67c0493b6956190"]
}

View File

@ -1,4 +1,4 @@
{
"label": "Dashlane",
"u2f": "https://www.dashlane.com"
"u2f": ["68201915d74cb42af5b3cc5c95b9553e3e3a83b4d2a93b45fbadaa8469ff8e6e"]
}

View File

@ -1,5 +1,5 @@
{
"label": "Dropbox",
"u2f": "https://www.dropbox.com/u2f-app-id.json",
"webauthn": "www.dropbox.com"
"u2f": ["c50f8a7b708e92f82e7a50e2bdc55d8fd91a22fe6b29c0cdf7805530842af581"],
"webauthn": ["www.dropbox.com"]
}

View File

@ -1,4 +1,4 @@
{
"label": "Duo",
"u2f": "https://api-9dcf9b83.duosecurity.com"
"u2f": ["f3e2042f94607da0a9c1f3b95e0d2f2bb2e069c5bb4fa764affa647d847b7ed6"]
}

View File

@ -1,4 +1,4 @@
{
"label": "FastMail",
"u2f": "https://www.fastmail.com"
"u2f": ["6966abe3674ea2f53079eb710197848c9be6f363992fd029e9898447cb9f0084"]
}

View File

@ -1,4 +1,4 @@
{
"label": "Fedora",
"u2f": "https://id.fedoraproject.org/u2f-origins.json"
"u2f": ["9d61442f5ce133bd46544fc42f0a6d54c0deb88840cac2b6aefa6514f89349e9"]
}

View File

@ -1,4 +1,5 @@
{
"label": "Gandi",
"u2f": "https://account.gandi.net/api/u2f/trusted_facets.json"
"u2f": ["a4e22dcafea7e90e128950113989fc45978dc9fb87767560516c1c69dfdfd196"],
"webauthn": ["gandi.net"]
}

View File

@ -1,6 +1,6 @@
{
"label": "GitHub",
"u2f": "https://github.com/u2f/trusted_facets",
"webauthn": "github.com",
"u2f": ["70617dfed065863af47c15556c91798880828cc407fdf70ae85011569465a075"],
"webauthn": ["github.com"],
"use_sign_count": true
}

View File

@ -1,4 +1,4 @@
{
"label": "GitLab",
"u2f": "https://gitlab.com"
"u2f": ["e7be96a51bd0192a72840d2e5909f72ba82a2fe93faa624f03396b30e494c804"]
}

View File

@ -1,5 +1,5 @@
{
"label": "Google",
"u2f": "https://www.gstatic.com/securitykey/origins.json",
"webauthn": "google.com"
"u2f": ["a54672b222c4cf95e151ed8d4d3c767a6cc349435943794e884f3d023a8229fd"],
"webauthn": ["google.com"]
}

View File

@ -1,4 +1,7 @@
{
"label": "Keeper",
"u2f": "https://keepersecurity.com"
"u2f": [
"53a15ba42a7c0325b8dbee289634a48f58aea3246645d5ff418f9bb8819885a9",
"d65f005ef4dea9320c9973053c95ff6020115d5fec1b7fee41a578e18df9ca8c"
]
}

View File

@ -1,4 +1,4 @@
{
"label": "LastPass",
"u2f": "https://lastpass.com"
"u2f": ["d755c527a86bf78445c282e713dcb86d46ff8b3cafcfb73b2e8cbe6c0884cb24"]
}

View File

@ -1,4 +1,4 @@
{
"label": "login.gov",
"webauthn": "secure.login.gov"
"webauthn": ["secure.login.gov"]
}

View File

@ -1,5 +1,5 @@
{
"label": "Microsoft",
"webauthn": "login.microsoft.com",
"webauthn": ["login.microsoft.com"],
"use_sign_count": false
}

View File

@ -1,4 +1,7 @@
{
"label": "Slush Pool",
"u2f": "https://slushpool.com/static/security/u2f.json"
"u2f": [
"08b2a3d41939aa31668493cb36cdcc4f16c4d9b4c8238b73c2f672c033007197",
"38804f2eff74f228b74151c201aa82e7e8eefcacfecf23fa146b13a37666314f"
]
}

View File

@ -1,4 +1,4 @@
{
"label": "Stripe",
"u2f": "https://dashboard.stripe.com"
"u2f": ["2ac6ad09a6d0772c44da73a6072f9d240fc6854a70d79c1024ff7c7559593292"]
}

View File

@ -1,4 +1,4 @@
{
"label": "u2f.bin.coffee",
"u2f": "https://u2f.bin.coffee"
"u2f": ["1b3c16dd2f7c46e2b4c289dc16746bcc60dfcf0fb818e13215526e1408e7f468"]
}

View File

@ -1,4 +1,4 @@
{
"label": "webauthn.bin.coffee",
"webauthn": "webauthn.bin.coffee"
"webauthn": ["webauthn.bin.coffee"]
}

View File

@ -1,4 +1,4 @@
{
"label": "WebAuthn.io",
"webauthn": "webauthn.io"
"webauthn": ["webauthn.io"]
}

View File

@ -1,4 +1,4 @@
{
"label": "WebAuthn.me",
"webauthn": "webauthn.me"
"webauthn": ["webauthn.me"]
}

View File

@ -1,4 +1,4 @@
{
"label": "demo.yubico.com",
"webauthn": "demo.yubico.com"
"webauthn": ["demo.yubico.com"]
}

View File

@ -16,41 +16,41 @@ def c_bytes(h):
def gen_core(data):
print("_knownapps = {")
print("# contents generated via script in")
print("# trezor-common/defs/webauthn/gen.py")
print("# do not edit manually")
print()
print("knownapps = {")
print(" # U2F")
for d in data:
if "u2f" in d:
url, label = d["u2f"], d["label"]
print(' "%s": {"label": "%s", "use_sign_count": True},' % (url, label))
for appid in d.get("u2f", []):
label = d["label"]
h = bytes.fromhex(appid)
print(" %s: {" % h)
print(' "label": "%s",' % label)
print(' "use_sign_count": True,')
print(" },")
print(" # WebAuthn")
for d in data:
if "webauthn" in d:
origin, label, use_sign_count = (
d["webauthn"],
d["label"],
d.get("use_sign_count", None),
)
if use_sign_count is None:
print(' "%s": {"label": "%s"},' % (origin, label))
else:
print(
' "%s": {"label": "%s", "use_sign_count": %s},'
% (origin, label, use_sign_count)
)
for origin in d.get("webauthn", []):
h = sha256(origin.encode()).digest()
label, use_sign_count = (d["label"], d.get("use_sign_count", None))
print(" %s: {" % h)
print(' "label": "%s",' % label)
if use_sign_count is not None:
print(' "use_sign_count": %s,' % use_sign_count)
print(" },")
print("}")
def gen_mcu(data):
for d in data:
if "u2f" in d:
url, label = d["u2f"], d["label"]
h = sha256(url.encode()).digest()
print(
'\t{\n\t\t// U2F: %s\n\t\t%s,\n\t\t"%s"\n\t},'
% (url, c_bytes(h), label)
)
if "webauthn" in d:
origin, label = d["webauthn"], d["label"]
for appid in d.get("u2f", []):
label = d["label"]
h = bytes.fromhex(appid)
print('\t{\n\t\t// U2F\n\t\t%s,\n\t\t"%s"\n\t},' % (c_bytes(h), label))
for origin in d.get("webauthn", []):
label = d["label"]
h = sha256(origin.encode()).digest()
print(
'\t{\n\t\t// WebAuthn: %s\n\t\t%s,\n\t\t"%s"\n\t},'

View File

@ -25,7 +25,6 @@ if __debug__:
if False:
from typing import Any, Coroutine, List, Optional
_HID_RPT_SIZE = const(64)
_CID_BROADCAST = const(0xFFFFFFFF) # broadcast channel id
# types of frame
@ -33,6 +32,12 @@ _TYPE_MASK = const(0x80) # frame type mask
_TYPE_INIT = const(0x80) # initial frame identifier
_TYPE_CONT = const(0x00) # continuation frame identifier
# U2F HID sizes
_HID_RPT_SIZE = const(64)
_FRAME_INIT_SIZE = const(57)
_FRAME_CONT_SIZE = const(59)
_MAX_U2FHID_MSG_PAYLOAD_LEN = const(_FRAME_INIT_SIZE + 128 * _FRAME_CONT_SIZE)
# types of cmd
_CMD_PING = const(0x81) # echo data through local processor only
_CMD_MSG = const(0x83) # send U2F message frame
@ -108,6 +113,7 @@ _KEEPALIVE_STATUS_UP_NEEDED = const(0x02) # waiting for user presence
# time intervals and timeouts
_KEEPALIVE_INTERVAL_MS = const(80) # interval between keepalive commands
_CTAP_HID_TIMEOUT_MS = const(500)
_U2F_CONFIRM_TIMEOUT_MS = const(10 * 1000)
_FIDO2_CONFIRM_TIMEOUT_MS = const(60 * 1000)
@ -131,7 +137,9 @@ _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
_ERR_MISSING_PARAMETER = const(0x14) # missing non-optional parameter
_ERR_CREDENTIAL_EXCLUDED = const(0x19) # valid credential found in the exclude list
_ERR_UNSUPPORTED_ALGORITHM = const(0x26) # requested COSE algorithm not supported
_ERR_OPERATION_DENIED = const(0x27) # user declined or timed out
@ -193,9 +201,6 @@ _RESULT_DECLINE = const(2) # User declined.
_RESULT_CANCEL = const(3) # Request was cancelled by _CMD_CANCEL.
_RESULT_TIMEOUT = const(4) # Request exceeded _FIDO2_CONFIRM_TIMEOUT_MS.
_FRAME_INIT_SIZE = 57
_FRAME_CONT_SIZE = 59
# Generate the authenticatorKeyAgreementKey used for ECDH in authenticatorClientPIN getKeyAgreement.
_KEY_AGREEMENT_PRIVKEY = nist256p1.generate_secret()
_KEY_AGREEMENT_PUBKEY = nist256p1.publickey(_KEY_AGREEMENT_PRIVKEY, False)
@ -207,6 +212,8 @@ _ALLOW_RESIDENT_CREDENTIALS = True
# The attestation type to use in MakeCredential responses. If false, then self attestation will be used.
_USE_BASIC_ATTESTATION = False
_AUTOCONFIRM = False
def frame_init() -> dict:
# uint32_t cid; // Channel identifier
@ -357,7 +364,7 @@ async def read_cmd(iface: io.HID) -> Optional[Cmd]:
read = loop.wait(iface.iface_num() | io.POLL_READ)
buf = await read
while True:
ifrm = overlay_struct(buf, desc_init)
bcnt = ifrm.bcnt
data = ifrm.data
@ -370,6 +377,18 @@ async def read_cmd(iface: io.HID) -> Optional[Cmd]:
log.warning(__name__, "_TYPE_CONT")
return None
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)
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)
return None
if datalen < bcnt:
databuf = bytearray(bcnt)
utils.memcpy(databuf, 0, data, 0, bcnt)
@ -378,18 +397,19 @@ async def read_cmd(iface: io.HID) -> Optional[Cmd]:
data = data[:bcnt]
while datalen < bcnt:
buf = await read
buf = await loop.race(read, loop.sleep(_CTAP_HID_TIMEOUT_MS * 1000))
if not isinstance(buf, (bytes, bytearray)):
await send_cmd(cmd_error(ifrm.cid, _ERR_MSG_TIMEOUT), iface)
return None
cfrm = overlay_struct(buf, desc_cont)
if cfrm.seq == _CMD_INIT:
# _CMD_INIT frame, cancels current channel
ifrm = overlay_struct(buf, desc_init)
data = ifrm.data[: ifrm.bcnt]
break
if cfrm.cid != ifrm.cid:
# cont frame for a different channel, reply with BUSY and skip
# cont frame for a different channel, reply with BUSY and abort
if __debug__:
log.warning(__name__, "_ERR_CHANNEL_BUSY")
await send_cmd(cmd_error(cfrm.cid, _ERR_CHANNEL_BUSY), iface)
@ -405,7 +425,7 @@ async def read_cmd(iface: io.HID) -> Optional[Cmd]:
datalen += utils.memcpy(data, datalen, cfrm.data, 0, bcnt - datalen)
seq += 1
else:
return Cmd(ifrm.cid, ifrm.cmd, data)
@ -533,6 +553,8 @@ async def verify_user(keepalive_callback: KeepaliveCallback) -> bool:
async def confirm(*args: Any, **kwargs: Any) -> bool:
if _AUTOCONFIRM:
return True
dialog = Confirm(*args, **kwargs)
if __debug__:
return await loop.race(dialog, confirm_signal()) is CONFIRMED
@ -540,6 +562,16 @@ async def confirm(*args: Any, **kwargs: Any) -> bool:
return await dialog is CONFIRMED
async def confirm_pageable(*args: Any, **kwargs: Any) -> bool:
if _AUTOCONFIRM:
return True
dialog = ConfirmPageable(*args, **kwargs)
if __debug__:
return await loop.race(dialog, confirm_signal()) is CONFIRMED
else:
return await dialog is CONFIRMED
class State:
def __init__(self, cid: int, iface: io.HID) -> None:
self.cid = cid
@ -735,6 +767,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
client_data_hash: bytes,
creds: List[Credential],
hmac_secret: Optional[dict],
resident: bool,
user_verification: bool,
) -> None:
Fido2State.__init__(self, cid, iface)
@ -743,6 +776,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
self._client_data_hash = client_data_hash
self._creds = creds
self._hmac_secret = hmac_secret
self._resident = resident
self._user_verification = user_verification
self.load_icon(self._creds[0].rp_id_hash)
@ -760,7 +794,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
async def confirm_dialog(self) -> bool:
content = ConfirmContent(self)
if await ConfirmPageable(self, content) is not CONFIRMED:
if not await confirm_pageable(self, content):
return False
if self._user_verification:
return await verify_user(KeepaliveCallback(self.cid, self.iface))
@ -777,6 +811,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
cred.rp_id_hash,
cred,
self._hmac_secret,
self._resident,
True,
self._user_verification,
)
@ -804,7 +839,9 @@ class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion):
def __init__(self, cid: int, iface: io.HID, rp_id: str) -> None:
cred = Fido2Credential()
cred.rp_id = rp_id
super().__init__(cid, iface, b"", [cred], {}, user_verification=False)
super().__init__(
cid, iface, b"", [cred], {}, resident=False, user_verification=False
)
async def on_confirm(self) -> None:
cmd = cbor_error(self.cid, _ERR_NO_CREDENTIALS)
@ -931,7 +968,10 @@ class DialogManager:
def dispatch_cmd(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
if req.cmd == _CMD_MSG:
try:
m = req.to_msg()
except IndexError:
return cmd_error(req.cid, _ERR_INVALID_LEN)
if m.cla != 0:
if __debug__:
@ -974,6 +1014,8 @@ def dispatch_cmd(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
loop.schedule(ui.alert())
return req
elif req.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:
if __debug__:
log.debug(__name__, "_CBOR_MAKE_CREDENTIAL")
@ -1016,9 +1058,7 @@ def dispatch_cmd(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
def cmd_init(req: Cmd) -> Cmd:
if req.cid == 0:
return cmd_error(req.cid, _ERR_INVALID_CID)
elif req.cid == _CID_BROADCAST:
if req.cid == _CID_BROADCAST:
# uint32_t except 0 and 0xffffffff
resp_cid = random.uniform(0xFFFFFFFE) + 1
else:
@ -1211,6 +1251,43 @@ def cbor_error(cid: int, code: int) -> Cmd:
return Cmd(cid, _CMD_CBOR, ustruct.pack(">B", code))
def credentials_from_descriptor_list(
descriptor_list: List[dict], rp_id_hash: bytes
) -> List[Credential]:
cred_list = []
for credential_descriptor in descriptor_list:
credential_type = credential_descriptor["type"]
if not isinstance(credential_type, str):
raise TypeError
if credential_type != "public-key":
continue
credential_id = credential_descriptor["id"]
if not isinstance(credential_id, (bytes, bytearray)):
raise TypeError
cred = Credential.from_bytes(credential_id, rp_id_hash)
if cred is not None:
cred_list.append(cred)
return cred_list
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"]
if not isinstance(pub_key_cred_type, str):
raise TypeError
if pub_key_cred_type != "public-key":
continue
pub_key_cred_alg = pkcp["alg"]
if not isinstance(pub_key_cred_alg, int):
raise TypeError
alg_list.append(pub_key_cred_alg)
return alg_list
def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
from apps.webauthn.knownapps import knownapps
@ -1221,7 +1298,8 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
try:
param = cbor.decode(req.data[1:])
rp_id = param[_MAKECRED_CMD_RP]["id"]
rp = param[_MAKECRED_CMD_RP]
rp_id = rp["id"]
rp_id_hash = hashlib.sha256(rp_id).digest()
# Prepare the new credential.
@ -1236,9 +1314,7 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
# Check if any of the credential descriptors in the exclude list belong to this authenticator.
exclude_list = param.get(_MAKECRED_CMD_EXCLUDE_LIST, [])
for credential_descriptor in exclude_list:
excl_cred = Credential.from_bytes(credential_descriptor["id"], rp_id_hash)
if credential_descriptor["type"] == "public-key" and excl_cred is not None:
if credentials_from_descriptor_list(exclude_list, rp_id_hash):
# This authenticator is already registered.
if not dialog_mgr.set_state(
Fido2ConfirmExcluded(req.cid, dialog_mgr.iface, cred)
@ -1248,9 +1324,8 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
# Check that the relying party supports ECDSA P-256 with SHA-256. We don't support any other algorithms.
pub_key_cred_params = param[_MAKECRED_CMD_PUB_KEY_CRED_PARAMS]
if ("public-key", _COSE_ALG_ES256) not in (
(pkcp.get("type", None), pkcp.get("alg", None))
for pkcp in pub_key_cred_params
if _COSE_ALG_ES256 not in algorithms_from_pub_key_cred_params(
pub_key_cred_params
):
return cbor_error(req.cid, _ERR_UNSUPPORTED_ALGORITHM)
@ -1265,6 +1340,10 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
)
client_data_hash = param[_MAKECRED_CMD_CLIENT_DATA_HASH]
except TypeError:
return cbor_error(req.cid, _ERR_CBOR_UNEXPECTED_TYPE)
except KeyError:
return cbor_error(req.cid, _ERR_MISSING_PARAMETER)
except Exception:
return cbor_error(req.cid, _ERR_INVALID_CBOR)
@ -1273,13 +1352,18 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
# Check data types.
if (
not cred.check_data_types()
or not isinstance(user.get("icon", ""), str)
or not isinstance(rp.get("icon", ""), str)
or not isinstance(client_data_hash, (bytes, bytearray))
or not isinstance(resident_key, bool)
or not isinstance(user_verification, bool)
):
return cbor_error(req.cid, _ERR_INVALID_CBOR)
return cbor_error(req.cid, _ERR_CBOR_UNEXPECTED_TYPE)
# Check options.
if "up" in options:
return cbor_error(req.cid, _ERR_INVALID_OPTION)
if resident_key and not _ALLOW_RESIDENT_CREDENTIALS:
return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION)
@ -1388,22 +1472,21 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
rp_id = param[_GETASSERT_CMD_RP_ID]
rp_id_hash = hashlib.sha256(rp_id).digest()
cred_list = []
allow_list = param.get(_GETASSERT_CMD_ALLOW_LIST, [])
if allow_list:
# Get all credentials from the allow list that belong to this authenticator.
for credential_descriptor in allow_list:
if credential_descriptor["type"] != "public-key":
continue
cred = Credential.from_bytes(credential_descriptor["id"], rp_id_hash)
if cred is not None:
cred_list = credentials_from_descriptor_list(allow_list, rp_id_hash)
for cred in cred_list:
if cred.rp_id is None:
cred.rp_id = rp_id
cred_list.append(cred)
resident = False
else:
# Allow list is empty. Get resident credentials.
if _ALLOW_RESIDENT_CREDENTIALS:
cred_list = get_resident_credentials(rp_id_hash)
else:
cred_list = []
resident = True
# Sort credentials by time of creation.
cred_list.sort()
@ -1421,6 +1504,10 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
hmac_secret = param.get(_GETASSERT_CMD_EXTENSIONS, {}).get("hmac-secret", None)
client_data_hash = param[_GETASSERT_CMD_CLIENT_DATA_HASH]
except TypeError:
return cbor_error(req.cid, _ERR_CBOR_UNEXPECTED_TYPE)
except KeyError:
return cbor_error(req.cid, _ERR_MISSING_PARAMETER)
except Exception:
return cbor_error(req.cid, _ERR_INVALID_CBOR)
@ -1431,7 +1518,7 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
or not isinstance(user_presence, bool)
or not isinstance(user_verification, bool)
):
return cbor_error(req.cid, _ERR_INVALID_CBOR)
return cbor_error(req.cid, _ERR_CBOR_UNEXPECTED_TYPE)
# Check options.
if "rk" in options:
@ -1461,6 +1548,7 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
rp_id_hash,
cred_list[0],
hmac_secret,
resident,
user_presence,
user_verification,
)
@ -1476,6 +1564,7 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
client_data_hash,
cred_list,
hmac_secret,
resident,
user_verification,
)
)
@ -1536,6 +1625,7 @@ def cbor_get_assertion_sign(
rp_id_hash: bytes,
cred: Credential,
hmac_secret: Optional[dict],
resident: bool,
user_presence: bool,
user_verification: bool,
) -> bytes:
@ -1585,7 +1675,7 @@ def cbor_get_assertion_sign(
_GETASSERT_RESP_SIGNATURE: sig,
}
if user_presence and cred.user_id is not None:
if resident and user_presence and cred.user_id is not None:
response[_GETASSERT_RESP_USER] = {"id": cred.user_id}
return cbor.encode(response)

View File

@ -1,60 +1,120 @@
from trezor.crypto.hashlib import sha256
# contents generated via script in
# trezor-common/defs/webauthn/gen.py
# do not edit manually
_knownapps = {
knownapps = {
# U2F
"https://bitbucket.org": {"label": "Bitbucket", "use_sign_count": True},
"https://www.bitfinex.com": {"label": "Bitfinex", "use_sign_count": True},
"https://vault.bitwarden.com/app-id.json": {
b"\x12t;\x92\x12\x97\xb7\x7f\x115\xe4\x1f\xde\xddJ\x84j\xfe\x82\xe1\xf3i2\xa9\x91/;\r\x8d\xfb}\x0e": {
"label": "Bitbucket",
"use_sign_count": True,
},
b"0/\xd5\xb4I*\x07\xb9\xfe\xbb0\xe72i\xec\xa5\x01 \\\xcf\xe0\xc2\x0b\xf7\xb4r\xfa-1\xe2\x1ec": {
"label": "Bitfinex",
"use_sign_count": True,
},
b"\xa3M0\x9f\xfa(\xc1$\x14\xb8\xbal\x07\xee\x1e\xfa\xe1\xa8^\x8a\x04aHY\xa6|\x04\x93\xb6\x95a\x90": {
"label": "Bitwarden",
"use_sign_count": True,
},
"https://www.dashlane.com": {"label": "Dashlane", "use_sign_count": True},
"https://www.dropbox.com/u2f-app-id.json": {
b"h \x19\x15\xd7L\xb4*\xf5\xb3\xcc\\\x95\xb9U>>:\x83\xb4\xd2\xa9;E\xfb\xad\xaa\x84i\xff\x8en": {
"label": "Dashlane",
"use_sign_count": True,
},
b'\xc5\x0f\x8a{p\x8e\x92\xf8.zP\xe2\xbd\xc5]\x8f\xd9\x1a"\xfek)\xc0\xcd\xf7\x80U0\x84*\xf5\x81': {
"label": "Dropbox",
"use_sign_count": True,
},
"https://api-9dcf9b83.duosecurity.com": {"label": "Duo", "use_sign_count": True},
"https://www.fastmail.com": {"label": "FastMail", "use_sign_count": True},
"https://id.fedoraproject.org/u2f-origins.json": {
b"\xf3\xe2\x04/\x94`}\xa0\xa9\xc1\xf3\xb9^\r/+\xb2\xe0i\xc5\xbbO\xa7d\xaf\xfad}\x84{~\xd6": {
"label": "Duo",
"use_sign_count": True,
},
b"if\xab\xe3gN\xa2\xf50y\xebq\x01\x97\x84\x8c\x9b\xe6\xf3c\x99/\xd0)\xe9\x89\x84G\xcb\x9f\x00\x84": {
"label": "FastMail",
"use_sign_count": True,
},
b"\x9daD/\\\xe13\xbdFTO\xc4/\nmT\xc0\xde\xb8\x88@\xca\xc2\xb6\xae\xfae\x14\xf8\x93I\xe9": {
"label": "Fedora",
"use_sign_count": True,
},
"https://account.gandi.net/api/u2f/trusted_facets.json": {
b"\xa4\xe2-\xca\xfe\xa7\xe9\x0e\x12\x89P\x119\x89\xfcE\x97\x8d\xc9\xfb\x87vu`Ql\x1ci\xdf\xdf\xd1\x96": {
"label": "Gandi",
"use_sign_count": True,
},
"https://github.com/u2f/trusted_facets": {
b"pa}\xfe\xd0e\x86:\xf4|\x15Ul\x91y\x88\x80\x82\x8c\xc4\x07\xfd\xf7\n\xe8P\x11V\x94e\xa0u": {
"label": "GitHub",
"use_sign_count": True,
},
"https://gitlab.com": {"label": "GitLab", "use_sign_count": True},
"https://www.gstatic.com/securitykey/origins.json": {
b"\xe7\xbe\x96\xa5\x1b\xd0\x19*r\x84\r.Y\t\xf7+\xa8*/\xe9?\xaabO\x039k0\xe4\x94\xc8\x04": {
"label": "GitLab",
"use_sign_count": True,
},
b'\xa5Fr\xb2"\xc4\xcf\x95\xe1Q\xed\x8dM<vzl\xc3ICYCyN\x88O=\x02:\x82)\xfd': {
"label": "Google",
"use_sign_count": True,
},
"https://keepersecurity.com": {"label": "Keeper", "use_sign_count": True},
"https://lastpass.com": {"label": "LastPass", "use_sign_count": True},
"https://slushpool.com/static/security/u2f.json": {
b"S\xa1[\xa4*|\x03%\xb8\xdb\xee(\x964\xa4\x8fX\xae\xa3$fE\xd5\xffA\x8f\x9b\xb8\x81\x98\x85\xa9": {
"label": "Keeper",
"use_sign_count": True,
},
b"\xd6_\x00^\xf4\xde\xa92\x0c\x99s\x05<\x95\xff` \x11]_\xec\x1b\x7f\xeeA\xa5x\xe1\x8d\xf9\xca\x8c": {
"label": "Keeper",
"use_sign_count": True,
},
b"\xd7U\xc5'\xa8k\xf7\x84E\xc2\x82\xe7\x13\xdc\xb8mF\xff\x8b<\xaf\xcf\xb7;.\x8c\xbel\x08\x84\xcb$": {
"label": "LastPass",
"use_sign_count": True,
},
b"\x08\xb2\xa3\xd4\x199\xaa1f\x84\x93\xcb6\xcd\xccO\x16\xc4\xd9\xb4\xc8#\x8bs\xc2\xf6r\xc03\x00q\x97": {
"label": "Slush Pool",
"use_sign_count": True,
},
"https://dashboard.stripe.com": {"label": "Stripe", "use_sign_count": True},
"https://u2f.bin.coffee": {"label": "u2f.bin.coffee", "use_sign_count": True},
b"8\x80O.\xfft\xf2(\xb7AQ\xc2\x01\xaa\x82\xe7\xe8\xee\xfc\xac\xfe\xcf#\xfa\x14k\x13\xa3vf1O": {
"label": "Slush Pool",
"use_sign_count": True,
},
b"*\xc6\xad\t\xa6\xd0w,D\xdas\xa6\x07/\x9d$\x0f\xc6\x85Jp\xd7\x9c\x10$\xff|uYY2\x92": {
"label": "Stripe",
"use_sign_count": True,
},
b"\x1b<\x16\xdd/|F\xe2\xb4\xc2\x89\xdc\x16tk\xcc`\xdf\xcf\x0f\xb8\x18\xe12\x15Rn\x14\x08\xe7\xf4h": {
"label": "u2f.bin.coffee",
"use_sign_count": True,
},
# WebAuthn
"www.binance.com": {"label": "Binance", "use_sign_count": False},
"www.dropbox.com": {"label": "Dropbox"},
"github.com": {"label": "GitHub", "use_sign_count": True},
"google.com": {"label": "Google"},
"secure.login.gov": {"label": "login.gov"},
"login.microsoft.com": {"label": "Microsoft", "use_sign_count": False},
"webauthn.bin.coffee": {"label": "webauthn.bin.coffee"},
"webauthn.io": {"label": "WebAuthn.io"},
"webauthn.me": {"label": "WebAuthn.me"},
"demo.yubico.com": {"label": "demo.yubico.com"},
b"\xc3@\x8c\x04G\x88\xae\xa5\xb3\xdf0\x89R\xfd\x8c\xa3\xc7\x0e!\xfe\xf4\xf6\xc1\xc27L\xaa\x1d\xf9\xb2\x8d\xdd": {
"label": "Binance",
"use_sign_count": False,
},
b"\x82\xf4\xa8\xc9_\xec\x94\xb2k\xaf\x9e7%\x0e\x95c\xd9\xa3f\xc7\xbe&\x1c\xa4\xdd\x01\x01\xf4\xd5\xef\xcb\x83": {
"label": "Dropbox"
},
b"T\xcee\x1e\xd7\x15\xb4\xaa\xa7U\xee\xce\xbdN\xa0\x95\x08\x15\xb34\xbd\x07\xd1\t\x89>\x960\x18\xcd\xdb\xd9": {
"label": "Gandi"
},
b":\xeb\x00$`8\x1co%\x8e\x83\x95\xd3\x02oW\x1f\r\x9avH\x8d\xcd\x83v9\xb1:\xed1e`": {
"label": "GitHub",
"use_sign_count": True,
},
b"\xd4\xc9\xd9\x02s&'\x1a\x89\xceQ\xfc\xaf2\x8e\xd6s\xf1{\xe34i\xff\x97\x9e\x8a\xb8\xddP\x1efO": {
"label": "Google"
},
b"\xf8?\xc3\xa1\xb2\x89\xa0\xde\xc5\xc1\xc8\xaa\x07\xe9\xb5\xdd\x9c\xbbv\xf6\xb2\xf5``\x17frh\xe5\xb9\xc4^": {
"label": "login.gov"
},
b"5l\x9e\xd4\xa0\x93!\xb9i_\x1e\xaf\x91\x82\x03\xf1\xb5_h\x9d\xa6\x1f\xbc\x96\x18L\x15}\xdah\x0c\x81": {
"label": "Microsoft",
"use_sign_count": False,
},
b"\xa6B\xd2\x1b|mU\xe1\xce#\xc59\x98(\xd2\xc7I\xbfjn\xf2\xfe\x03\xcc\x9e\x10\xcd\xf4\xedS\x08\x8b": {
"label": "webauthn.bin.coffee"
},
b"t\xa6\xea\x92\x13\xc9\x9c/t\xb2$\x92\xb3 \xcf@&*\x94\xc1\xa9P\xa09\x7f)%\x0b`\x84\x1e\xf0": {
"label": "WebAuthn.io"
},
b"\xf9[\xc78(\xee!\x0f\x9f\xd3\xbb\xe7-\x97\x90\x80\x13\xb0\xa3u\x9e\x9a\xea=\n\xe3\x18vl\xd2\xe1\xad": {
"label": "WebAuthn.me"
},
b"\xc4l\xef\x82\xad\x1bTdwY\x1d\x00\x8b\x08u\x9e\xc3\xe6\xd2\xec\xb4\xf3\x94t\xbf\xeaii\x92]\x03\xb7": {
"label": "demo.yubico.com"
},
}
knownapps = {sha256(k.encode()).digest(): v for (k, v) in _knownapps.items()}

View File

@ -40,27 +40,27 @@ static const U2FWellKnown u2f_well_known[] = {
"Binance"
},
{
// U2F: https://bitbucket.org
// U2F
{ 0x12, 0x74, 0x3b, 0x92, 0x12, 0x97, 0xb7, 0x7f, 0x11, 0x35, 0xe4, 0x1f, 0xde, 0xdd, 0x4a, 0x84, 0x6a, 0xfe, 0x82, 0xe1, 0xf3, 0x69, 0x32, 0xa9, 0x91, 0x2f, 0x3b, 0x0d, 0x8d, 0xfb, 0x7d, 0x0e },
"Bitbucket"
},
{
// U2F: https://www.bitfinex.com
// U2F
{ 0x30, 0x2f, 0xd5, 0xb4, 0x49, 0x2a, 0x07, 0xb9, 0xfe, 0xbb, 0x30, 0xe7, 0x32, 0x69, 0xec, 0xa5, 0x01, 0x20, 0x5c, 0xcf, 0xe0, 0xc2, 0x0b, 0xf7, 0xb4, 0x72, 0xfa, 0x2d, 0x31, 0xe2, 0x1e, 0x63 },
"Bitfinex"
},
{
// U2F: https://vault.bitwarden.com/app-id.json
// U2F
{ 0xa3, 0x4d, 0x30, 0x9f, 0xfa, 0x28, 0xc1, 0x24, 0x14, 0xb8, 0xba, 0x6c, 0x07, 0xee, 0x1e, 0xfa, 0xe1, 0xa8, 0x5e, 0x8a, 0x04, 0x61, 0x48, 0x59, 0xa6, 0x7c, 0x04, 0x93, 0xb6, 0x95, 0x61, 0x90 },
"Bitwarden"
},
{
// U2F: https://www.dashlane.com
// U2F
{ 0x68, 0x20, 0x19, 0x15, 0xd7, 0x4c, 0xb4, 0x2a, 0xf5, 0xb3, 0xcc, 0x5c, 0x95, 0xb9, 0x55, 0x3e, 0x3e, 0x3a, 0x83, 0xb4, 0xd2, 0xa9, 0x3b, 0x45, 0xfb, 0xad, 0xaa, 0x84, 0x69, 0xff, 0x8e, 0x6e },
"Dashlane"
},
{
// U2F: https://www.dropbox.com/u2f-app-id.json
// U2F
{ 0xc5, 0x0f, 0x8a, 0x7b, 0x70, 0x8e, 0x92, 0xf8, 0x2e, 0x7a, 0x50, 0xe2, 0xbd, 0xc5, 0x5d, 0x8f, 0xd9, 0x1a, 0x22, 0xfe, 0x6b, 0x29, 0xc0, 0xcd, 0xf7, 0x80, 0x55, 0x30, 0x84, 0x2a, 0xf5, 0x81 },
"Dropbox"
},
@ -70,47 +70,67 @@ static const U2FWellKnown u2f_well_known[] = {
"Dropbox"
},
{
// U2F: https://api-9dcf9b83.duosecurity.com
// U2F
{ 0xf3, 0xe2, 0x04, 0x2f, 0x94, 0x60, 0x7d, 0xa0, 0xa9, 0xc1, 0xf3, 0xb9, 0x5e, 0x0d, 0x2f, 0x2b, 0xb2, 0xe0, 0x69, 0xc5, 0xbb, 0x4f, 0xa7, 0x64, 0xaf, 0xfa, 0x64, 0x7d, 0x84, 0x7b, 0x7e, 0xd6 },
"Duo"
},
{
// U2F: https://www.fastmail.com
// U2F
{ 0x69, 0x66, 0xab, 0xe3, 0x67, 0x4e, 0xa2, 0xf5, 0x30, 0x79, 0xeb, 0x71, 0x01, 0x97, 0x84, 0x8c, 0x9b, 0xe6, 0xf3, 0x63, 0x99, 0x2f, 0xd0, 0x29, 0xe9, 0x89, 0x84, 0x47, 0xcb, 0x9f, 0x00, 0x84 },
"FastMail"
},
{
// U2F: https://id.fedoraproject.org/u2f-origins.json
// U2F
{ 0x9d, 0x61, 0x44, 0x2f, 0x5c, 0xe1, 0x33, 0xbd, 0x46, 0x54, 0x4f, 0xc4, 0x2f, 0x0a, 0x6d, 0x54, 0xc0, 0xde, 0xb8, 0x88, 0x40, 0xca, 0xc2, 0xb6, 0xae, 0xfa, 0x65, 0x14, 0xf8, 0x93, 0x49, 0xe9 },
"Fedora"
},
{
// U2F: https://account.gandi.net/api/u2f/trusted_facets.json
// U2F
{ 0xa4, 0xe2, 0x2d, 0xca, 0xfe, 0xa7, 0xe9, 0x0e, 0x12, 0x89, 0x50, 0x11, 0x39, 0x89, 0xfc, 0x45, 0x97, 0x8d, 0xc9, 0xfb, 0x87, 0x76, 0x75, 0x60, 0x51, 0x6c, 0x1c, 0x69, 0xdf, 0xdf, 0xd1, 0x96 },
"Gandi"
},
{
// U2F: https://github.com/u2f/trusted_facets
// WebAuthn: gandi.net
{ 0x54, 0xce, 0x65, 0x1e, 0xd7, 0x15, 0xb4, 0xaa, 0xa7, 0x55, 0xee, 0xce, 0xbd, 0x4e, 0xa0, 0x95, 0x08, 0x15, 0xb3, 0x34, 0xbd, 0x07, 0xd1, 0x09, 0x89, 0x3e, 0x96, 0x30, 0x18, 0xcd, 0xdb, 0xd9 },
"Gandi"
},
{
// U2F
{ 0x70, 0x61, 0x7d, 0xfe, 0xd0, 0x65, 0x86, 0x3a, 0xf4, 0x7c, 0x15, 0x55, 0x6c, 0x91, 0x79, 0x88, 0x80, 0x82, 0x8c, 0xc4, 0x07, 0xfd, 0xf7, 0x0a, 0xe8, 0x50, 0x11, 0x56, 0x94, 0x65, 0xa0, 0x75 },
"GitHub"
},
{
// U2F: https://gitlab.com
// WebAuthn: github.com
{ 0x3a, 0xeb, 0x00, 0x24, 0x60, 0x38, 0x1c, 0x6f, 0x25, 0x8e, 0x83, 0x95, 0xd3, 0x02, 0x6f, 0x57, 0x1f, 0x0d, 0x9a, 0x76, 0x48, 0x8d, 0xcd, 0x83, 0x76, 0x39, 0xb1, 0x3a, 0xed, 0x31, 0x65, 0x60 },
"GitHub"
},
{
// U2F
{ 0xe7, 0xbe, 0x96, 0xa5, 0x1b, 0xd0, 0x19, 0x2a, 0x72, 0x84, 0x0d, 0x2e, 0x59, 0x09, 0xf7, 0x2b, 0xa8, 0x2a, 0x2f, 0xe9, 0x3f, 0xaa, 0x62, 0x4f, 0x03, 0x39, 0x6b, 0x30, 0xe4, 0x94, 0xc8, 0x04 },
"GitLab"
},
{
// U2F: https://www.gstatic.com/securitykey/origins.json
// U2F
{ 0xa5, 0x46, 0x72, 0xb2, 0x22, 0xc4, 0xcf, 0x95, 0xe1, 0x51, 0xed, 0x8d, 0x4d, 0x3c, 0x76, 0x7a, 0x6c, 0xc3, 0x49, 0x43, 0x59, 0x43, 0x79, 0x4e, 0x88, 0x4f, 0x3d, 0x02, 0x3a, 0x82, 0x29, 0xfd },
"Google"
},
{
// U2F: https://keepersecurity.com
// WebAuthn: google.com
{ 0xd4, 0xc9, 0xd9, 0x02, 0x73, 0x26, 0x27, 0x1a, 0x89, 0xce, 0x51, 0xfc, 0xaf, 0x32, 0x8e, 0xd6, 0x73, 0xf1, 0x7b, 0xe3, 0x34, 0x69, 0xff, 0x97, 0x9e, 0x8a, 0xb8, 0xdd, 0x50, 0x1e, 0x66, 0x4f },
"Google"
},
{
// U2F
{ 0x53, 0xa1, 0x5b, 0xa4, 0x2a, 0x7c, 0x03, 0x25, 0xb8, 0xdb, 0xee, 0x28, 0x96, 0x34, 0xa4, 0x8f, 0x58, 0xae, 0xa3, 0x24, 0x66, 0x45, 0xd5, 0xff, 0x41, 0x8f, 0x9b, 0xb8, 0x81, 0x98, 0x85, 0xa9 },
"Keeper"
},
{
// U2F: https://lastpass.com
// U2F
{ 0xd6, 0x5f, 0x00, 0x5e, 0xf4, 0xde, 0xa9, 0x32, 0x0c, 0x99, 0x73, 0x05, 0x3c, 0x95, 0xff, 0x60, 0x20, 0x11, 0x5d, 0x5f, 0xec, 0x1b, 0x7f, 0xee, 0x41, 0xa5, 0x78, 0xe1, 0x8d, 0xf9, 0xca, 0x8c },
"Keeper"
},
{
// U2F
{ 0xd7, 0x55, 0xc5, 0x27, 0xa8, 0x6b, 0xf7, 0x84, 0x45, 0xc2, 0x82, 0xe7, 0x13, 0xdc, 0xb8, 0x6d, 0x46, 0xff, 0x8b, 0x3c, 0xaf, 0xcf, 0xb7, 0x3b, 0x2e, 0x8c, 0xbe, 0x6c, 0x08, 0x84, 0xcb, 0x24 },
"LastPass"
},
@ -120,17 +140,27 @@ static const U2FWellKnown u2f_well_known[] = {
"login.gov"
},
{
// U2F: https://slushpool.com/static/security/u2f.json
// WebAuthn: login.microsoft.com
{ 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 },
"Microsoft"
},
{
// U2F
{ 0x08, 0xb2, 0xa3, 0xd4, 0x19, 0x39, 0xaa, 0x31, 0x66, 0x84, 0x93, 0xcb, 0x36, 0xcd, 0xcc, 0x4f, 0x16, 0xc4, 0xd9, 0xb4, 0xc8, 0x23, 0x8b, 0x73, 0xc2, 0xf6, 0x72, 0xc0, 0x33, 0x00, 0x71, 0x97 },
"Slush Pool"
},
{
// U2F: https://dashboard.stripe.com
// U2F
{ 0x38, 0x80, 0x4f, 0x2e, 0xff, 0x74, 0xf2, 0x28, 0xb7, 0x41, 0x51, 0xc2, 0x01, 0xaa, 0x82, 0xe7, 0xe8, 0xee, 0xfc, 0xac, 0xfe, 0xcf, 0x23, 0xfa, 0x14, 0x6b, 0x13, 0xa3, 0x76, 0x66, 0x31, 0x4f },
"Slush Pool"
},
{
// U2F
{ 0x2a, 0xc6, 0xad, 0x09, 0xa6, 0xd0, 0x77, 0x2c, 0x44, 0xda, 0x73, 0xa6, 0x07, 0x2f, 0x9d, 0x24, 0x0f, 0xc6, 0x85, 0x4a, 0x70, 0xd7, 0x9c, 0x10, 0x24, 0xff, 0x7c, 0x75, 0x59, 0x59, 0x32, 0x92 },
"Stripe"
},
{
// U2F: https://u2f.bin.coffee
// U2F
{ 0x1b, 0x3c, 0x16, 0xdd, 0x2f, 0x7c, 0x46, 0xe2, 0xb4, 0xc2, 0x89, 0xdc, 0x16, 0x74, 0x6b, 0xcc, 0x60, 0xdf, 0xcf, 0x0f, 0xb8, 0x18, 0xe1, 0x32, 0x15, 0x52, 0x6e, 0x14, 0x08, 0xe7, 0xf4, 0x68 },
"u2f.bin.coffee"
},