diff --git a/core/src/apps/webauthn/__init__.py b/core/src/apps/webauthn/__init__.py index baad80879..370eb61b6 100644 --- a/core/src/apps/webauthn/__init__.py +++ b/core/src/apps/webauthn/__init__.py @@ -131,6 +131,7 @@ _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 @@ -1231,6 +1232,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 @@ -1241,7 +1279,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. @@ -1256,21 +1295,18 @@ 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: - # This authenticator is already registered. - if not dialog_mgr.set_state( - Fido2ConfirmExcluded(req.cid, dialog_mgr.iface, cred) - ): - return cmd_error(req.cid, _ERR_CHANNEL_BUSY) - return 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) + ): + return cmd_error(req.cid, _ERR_CHANNEL_BUSY) + return None # 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) @@ -1285,6 +1321,8 @@ 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: @@ -1295,11 +1333,13 @@ 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 resident_key and not _ALLOW_RESIDENT_CREDENTIALS: @@ -1410,23 +1450,20 @@ 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: - if cred.rp_id is None: - cred.rp_id = rp_id - cred_list.append(cred) + 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 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. @@ -1445,6 +1482,8 @@ 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: @@ -1457,7 +1496,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: