2019-10-01 14:01:56 +00:00
import uctypes
import ustruct
import utime
from micropython import const
2022-09-19 10:55:46 +00:00
from typing import TYPE_CHECKING
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
import storage . device as storage_device
2022-10-12 13:30:53 +00:00
from trezor import config , io , log , loop , utils , wire , workflow
2022-09-19 10:55:46 +00:00
from trezor . crypto import hashlib
2019-10-01 14:01:56 +00:00
from trezor . crypto . curve import nist256p1
2023-05-10 13:59:17 +00:00
from trezor . ui . layouts import show_error_popup
2019-10-01 14:01:56 +00:00
2020-04-29 19:03:55 +00:00
from apps . base import set_homescreen
2019-10-25 15:43:55 +00:00
from apps . common import cbor
2020-08-11 13:55:05 +00:00
from . import common
2022-09-19 10:55:46 +00:00
from . credential import Credential , Fido2Credential
if TYPE_CHECKING :
2022-10-12 13:30:53 +00:00
from typing import Any , Awaitable , Callable , Coroutine , Iterable , Iterator
2022-09-19 10:55:46 +00:00
from . credential import U2fCredential
HID = io . HID
2019-10-01 14:01:56 +00:00
2020-11-23 13:24:13 +00:00
_CID_BROADCAST = const ( 0xFFFF_FFFF ) # broadcast channel id
2019-10-01 14:01:56 +00:00
# types of frame
_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 )
2020-04-03 15:42:49 +00:00
_CMD_INIT_NONCE_SIZE = const ( 8 )
2019-10-01 14:01:56 +00:00
# types of cmd
_CMD_PING = const ( 0x81 ) # echo data through local processor only
_CMD_MSG = const ( 0x83 ) # send U2F message frame
_CMD_INIT = const ( 0x86 ) # channel initialization
_CMD_WINK = const ( 0x88 ) # send device identification wink
_CMD_CBOR = const ( 0x90 ) # send encapsulated CTAP CBOR encoded message
_CMD_CANCEL = const ( 0x91 ) # cancel any outstanding requests on this CID
_CMD_KEEPALIVE = const ( 0xBB ) # processing a message
_CMD_ERROR = const ( 0xBF ) # error response
# types for the msg cmd
_MSG_REGISTER = const ( 0x01 ) # registration command
_MSG_AUTHENTICATE = const ( 0x02 ) # authenticate/sign command
_MSG_VERSION = const ( 0x03 ) # read version string command
# types for the cbor cmd
_CBOR_MAKE_CREDENTIAL = const ( 0x01 ) # generate new credential command
_CBOR_GET_ASSERTION = const ( 0x02 ) # authenticate command
_CBOR_GET_INFO = const ( 0x04 ) # report AAGUID and device capabilities
_CBOR_CLIENT_PIN = const ( 0x06 ) # PIN and pinToken management
_CBOR_RESET = const ( 0x07 ) # factory reset, invalidating all generated credentials
_CBOR_GET_NEXT_ASSERTION = const ( 0x08 ) # obtain the next per-credential signature
# CBOR MakeCredential command parameter keys
_MAKECRED_CMD_CLIENT_DATA_HASH = const ( 0x01 ) # bytes, required
_MAKECRED_CMD_RP = const ( 0x02 ) # map, required
_MAKECRED_CMD_USER = const ( 0x03 ) # map, required
_MAKECRED_CMD_PUB_KEY_CRED_PARAMS = const ( 0x04 ) # array of maps, required
_MAKECRED_CMD_EXCLUDE_LIST = const ( 0x05 ) # array of maps, optional
_MAKECRED_CMD_EXTENSIONS = const ( 0x06 ) # map, optional
_MAKECRED_CMD_OPTIONS = const ( 0x07 ) # map, optional
_MAKECRED_CMD_PIN_AUTH = const ( 0x08 ) # bytes, optional
# CBOR MakeCredential response member keys
_MAKECRED_RESP_FMT = const ( 0x01 ) # str, required
_MAKECRED_RESP_AUTH_DATA = const ( 0x02 ) # bytes, required
_MAKECRED_RESP_ATT_STMT = const ( 0x03 ) # map, required
# CBOR GetAssertion command parameter keys
_GETASSERT_CMD_RP_ID = const ( 0x01 ) # str, required
_GETASSERT_CMD_CLIENT_DATA_HASH = const ( 0x02 ) # bytes, required
_GETASSERT_CMD_ALLOW_LIST = const ( 0x03 ) # array of maps, optional
_GETASSERT_CMD_EXTENSIONS = const ( 0x04 ) # map, optional
_GETASSERT_CMD_OPTIONS = const ( 0x05 ) # map, optional
_GETASSERT_CMD_PIN_AUTH = const ( 0x06 ) # bytes, optional
# CBOR GetAssertion response member keys
_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
# CBOR GetInfo response member keys
_GETINFO_RESP_VERSIONS = const ( 0x01 ) # array of str, required
_GETINFO_RESP_EXTENSIONS = const ( 0x02 ) # array of str, optional
_GETINFO_RESP_AAGUID = const ( 0x03 ) # bytes(16), required
_GETINFO_RESP_OPTIONS = const ( 0x04 ) # map, optional
_GETINFO_RESP_PIN_PROTOCOLS = const ( 0x06 ) # list of unsigned integers, optional
2020-03-14 19:20:59 +00:00
_GETINFO_RESP_MAX_CRED_COUNT_IN_LIST = const ( 0x07 ) # int, optional
_GETINFO_RESP_MAX_CRED_ID_LEN = const ( 0x08 ) # int, optional
2019-10-01 14:01:56 +00:00
# CBOR ClientPin command parameter keys
_CLIENTPIN_CMD_PIN_PROTOCOL = const ( 0x01 ) # unsigned int, required
_CLIENTPIN_CMD_SUBCOMMAND = const ( 0x02 ) # unsigned int, required
_CLIENTPIN_SUBCMD_GET_KEY_AGREEMENT = const ( 0x02 )
# CBOR ClientPin response member keys
_CLIENTPIN_RESP_KEY_AGREEMENT = const ( 0x01 ) # COSE_Key, optional
# status codes for the keepalive cmd
2019-12-03 09:32:01 +00:00
_KEEPALIVE_STATUS_NONE = const ( 0x00 )
2019-10-01 14:01:56 +00:00
_KEEPALIVE_STATUS_PROCESSING = const ( 0x01 ) # still processing the current request
_KEEPALIVE_STATUS_UP_NEEDED = const ( 0x02 ) # waiting for user presence
# time intervals and timeouts
_KEEPALIVE_INTERVAL_MS = const ( 80 ) # interval between keepalive commands
2019-12-03 09:32:01 +00:00
_CTAP_HID_TIMEOUT_MS = const (
500
) # maximum interval between CTAP HID continuation frames
_U2F_CONFIRM_TIMEOUT_MS = const (
3 * 1000
) # maximum U2F pollling interval, Chrome uses 200 ms
2019-10-01 14:01:56 +00:00
_FIDO2_CONFIRM_TIMEOUT_MS = const ( 60 * 1000 )
2019-11-20 14:10:55 +00:00
_POPUP_TIMEOUT_MS = const ( 4 * 1000 )
2020-05-01 14:03:40 +00:00
_UV_CACHE_TIME_MS = const ( 3 * 60 * 1000 ) # user verification cache time
2019-10-01 14:01:56 +00:00
# hid error codes
_ERR_NONE = const ( 0x00 ) # no error
_ERR_INVALID_CMD = const ( 0x01 ) # invalid command
_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_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
_ERR_KEY_STORE_FULL = const ( 0x28 ) # internal key storage is full
_ERR_UNSUPPORTED_OPTION = const ( 0x2B ) # unsupported option
_ERR_INVALID_OPTION = const ( 0x2C ) # not a valid option for current operation
_ERR_KEEPALIVE_CANCEL = const ( 0x2D ) # pending keep alive was cancelled
_ERR_NO_CREDENTIALS = const ( 0x2E ) # no valid credentials provided
_ERR_NOT_ALLOWED = const ( 0x30 ) # continuation command not allowed
_ERR_PIN_AUTH_INVALID = const ( 0x33 ) # pinAuth verification failed
_ERR_OTHER = const ( 0x7F ) # other unspecified error
_ERR_EXTENSION_FIRST = const ( 0xE0 ) # extension specific error
# command status responses
_SW_NO_ERROR = const ( 0x9000 )
_SW_WRONG_LENGTH = const ( 0x6700 )
_SW_CONDITIONS_NOT_SATISFIED = const ( 0x6985 )
_SW_WRONG_DATA = const ( 0x6A80 )
_SW_INS_NOT_SUPPORTED = const ( 0x6D00 )
_SW_CLA_NOT_SUPPORTED = const ( 0x6E00 )
# init response
_CAPFLAG_WINK = const ( 0x01 ) # device supports _CMD_WINK
_CAPFLAG_CBOR = const ( 0x04 ) # device supports _CMD_CBOR
_U2FHID_IF_VERSION = const ( 2 ) # interface version
# register response
_U2F_REGISTER_ID = const ( 0x05 ) # version 2 registration identifier
2020-04-02 14:21:19 +00:00
_FIDO_ATT_PRIV_KEY = b " q& \xac + \xf6 D \xdc a \x86 \xad \x83 \xef \x1f \xcd \xf1 *W \xb5 \xcf \xa2 \x00 \x0b \x8a \xd0 ' \xe9 V \xe8 T \xc5 \n \x8b "
_FIDO_ATT_CERT = b " 0 \x82 \x01 \xcd 0 \x82 \x01 s \xa0 \x03 \x02 \x01 \x02 \x02 \x04 \x03 E` \xc4 0 \n \x06 \x08 * \x86 H \xce = \x04 \x03 \x02 0.1,0* \x06 \x03 U \x04 \x03 \x0c #Trezor FIDO Root CA Serial 841513560 \x17 \r 200406100417Z \x18 \x0f 20500406100417Z0x1 \x0b 0 \t \x06 \x03 U \x04 \x06 \x13 \x02 CZ1 \x1c 0 \x1a \x06 \x03 U \x04 \n \x0c \x13 SatoshiLabs, s.r.o.1 \" 0 \x06 \x03 U \x04 \x0b \x0c \x19 Authenticator Attestation1 ' 0 % \x06 \x03 U \x04 \x03 \x0c \x1e Trezor FIDO EE Serial 548784040Y0 \x13 \x06 \x07 * \x86 H \xce = \x02 \x01 \x06 \x08 * \x86 H \xce = \x03 \x01 \x07 \x03 B \x00 \x04 \xd9 \x18 \xbd \xfa \x8a T \xac \x92 \xe9 \r \xa9 \x1f \xca z \xa2 dT \xc0 \xd1 s61M \xde \x83 \xa5 K \x86 \xb5 \xdf N \xf0 Re \x9a \x1d o \xfc \xb7 F \x7f \x1a \xcd \xdb \x8a 3 \x08 \x0b ^ \xed \x91 \x89 \x13 \xf4 C \xa5 & \x1b \xc7 { h`o \xc1 \xa3 3010! \x06 \x0b + \x06 \x01 \x04 \x01 \x82 \xe5 \x1c \x01 \x01 \x04 \x04 \x12 \x04 \x10 \xd6 \xd0 \xbd \xc3 b \xee \xc4 \xdb \xde \x8d zenJD \x87 0 \x0c \x06 \x03 U \x1d \x13 \x01 \x01 \xff \x04 \x02 0 \x00 0 \n \x06 \x08 * \x86 H \xce = \x04 \x03 \x02 \x03 H \x00 0E \x02 \x0b \xce \xc4 R \xc3 \n \x11 ' \xe5 \xd5 \xf5 \xfc \xf5 \xd6 Wy \x11 + \xe5 0 \xad \x9d -TXJ \xbe E \x86 \xda \x93 \xc6 \x02 ! \x00 \xaf \xca = \xcf \xd8 A \xb0 \xad z \x9e $} \x0f f \xf4 L, \x83 \xf9 T \xab \x95 O \x89 6 \xc1 5 \x08 \x7f X \xf1 \x95 "
2023-02-23 10:06:59 +00:00
_BOGUS_RP_ID = " .dummy "
2019-12-02 19:09:41 +00:00
_BOGUS_APPID_CHROME = b " A " * 32
_BOGUS_APPID_FIREFOX = b " \0 " * 32
_BOGUS_APPIDS = ( _BOGUS_APPID_CHROME , _BOGUS_APPID_FIREFOX )
2019-11-20 15:02:47 +00:00
_AAGUID = b " \xd6 \xd0 \xbd \xc3 b \xee \xc4 \xdb \xde \x8d zenJD \x87 " # First 16 bytes of SHA-256("TREZOR 2")
2019-10-01 14:01:56 +00:00
# authentication control byte
_AUTH_ENFORCE = const ( 0x03 ) # enforce user presence and sign
_AUTH_CHECK_ONLY = const ( 0x07 ) # check only
_AUTH_FLAG_UP = const ( 1 << 0 ) # user present
_AUTH_FLAG_UV = const ( 1 << 2 ) # user verified
_AUTH_FLAG_AT = const ( 1 << 6 ) # attested credential data included
_AUTH_FLAG_ED = const ( 1 << 7 ) # extension data included
# common raw message format (ISO7816-4:2005 mapping)
_APDU_CLA = const ( 0 ) # uint8_t cla; // Class - reserved
_APDU_INS = const ( 1 ) # uint8_t ins; // U2F instruction
_APDU_P1 = const ( 2 ) # uint8_t p1; // U2F parameter 1
_APDU_P2 = const ( 3 ) # uint8_t p2; // U2F parameter 2
_APDU_LC1 = const ( 4 ) # uint8_t lc1; // Length field, set to zero
_APDU_LC2 = const ( 5 ) # uint8_t lc2; // Length field, MSB
_APDU_LC3 = const ( 6 ) # uint8_t lc3; // Length field, LSB
_APDU_DATA = const ( 7 ) # uint8_t data[1]; // Data field
# Dialog results
_RESULT_NONE = const ( 0 )
_RESULT_CONFIRM = const ( 1 ) # User confirmed.
_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.
# FIDO2 configuration.
2022-09-19 10:55:46 +00:00
_ALLOW_FIDO2 = const ( 1 )
_ALLOW_RESIDENT_CREDENTIALS = const ( 1 )
_ALLOW_WINK = const ( 0 )
2019-10-01 14:01:56 +00:00
2019-12-11 10:47:02 +00:00
# The default attestation type to use in MakeCredential responses. If false, then basic attestation will be used by default.
2022-09-19 10:55:46 +00:00
_DEFAULT_USE_SELF_ATTESTATION = const ( 1 )
2019-12-11 10:47:02 +00:00
# The default value of the use_sign_count flag for newly created credentials.
2022-09-19 10:55:46 +00:00
_DEFAULT_USE_SIGN_COUNT = const ( 1 )
2019-10-01 14:01:56 +00:00
2020-03-14 19:20:59 +00:00
# The maximum number of credential IDs that can be supplied in the GetAssertion allow list.
_MAX_CRED_COUNT_IN_LIST = const ( 10 )
2019-12-02 11:04:49 +00:00
# The CID of the last WINK command. Used to ensure that we do only one WINK at a time on any given CID.
_last_wink_cid = 0
2023-02-23 10:06:59 +00:00
# Indicates whether the last U2F_AUTHENTICATE or CBOR_GET_ASSERTION had a valid key handle / credential ID.
2023-02-20 10:52:24 +00:00
_last_auth_valid = False
2019-12-02 19:09:41 +00:00
2019-10-01 14:01:56 +00:00
class CborError ( Exception ) :
def __init__ ( self , code : int ) :
2020-11-20 22:42:32 +00:00
super ( ) . __init__ ( )
2019-10-01 14:01:56 +00:00
self . code = code
2021-12-08 09:10:58 +00:00
def frame_init ( ) - > uctypes . StructDict :
2019-10-01 14:01:56 +00:00
# uint32_t cid; // Channel identifier
# uint8_t cmd; // Command - b7 set
# uint8_t bcnth; // Message byte count - high part
# uint8_t bcntl; // Message byte count - low part
# uint8_t data[HID_RPT_SIZE - 7]; // Data payload
return {
" cid " : 0 | uctypes . UINT32 ,
" cmd " : 4 | uctypes . UINT8 ,
" bcnt " : 5 | uctypes . UINT16 ,
" data " : ( 7 | uctypes . ARRAY , ( _HID_RPT_SIZE - 7 ) | uctypes . UINT8 ) ,
}
2021-12-08 09:10:58 +00:00
def frame_cont ( ) - > uctypes . StructDict :
2019-10-01 14:01:56 +00:00
# uint32_t cid; // Channel identifier
# uint8_t seq; // Sequence number - b7 cleared
# uint8_t data[HID_RPT_SIZE - 5]; // Data payload
return {
" cid " : 0 | uctypes . UINT32 ,
" seq " : 4 | uctypes . UINT8 ,
" data " : ( 5 | uctypes . ARRAY , ( _HID_RPT_SIZE - 5 ) | uctypes . UINT8 ) ,
}
2022-09-19 10:55:46 +00:00
def _resp_cmd_init ( ) - > uctypes . StructDict :
UINT8 = uctypes . UINT8 # local_cache_attribute
2019-10-01 14:01:56 +00:00
# uint8_t nonce[8]; // Client application nonce
# uint32_t cid; // Channel identifier
# uint8_t versionInterface; // Interface version
# uint8_t versionMajor; // Major version number
# uint8_t versionMinor; // Minor version number
# uint8_t versionBuild; // Build version number
# uint8_t capFlags; // Capabilities flags
return {
2022-09-19 10:55:46 +00:00
" nonce " : ( 0 | uctypes . ARRAY , 8 | UINT8 ) ,
2019-10-01 14:01:56 +00:00
" cid " : 8 | uctypes . UINT32 ,
2022-09-19 10:55:46 +00:00
" versionInterface " : 12 | UINT8 ,
" versionMajor " : 13 | UINT8 ,
" versionMinor " : 14 | UINT8 ,
" versionBuild " : 15 | UINT8 ,
" capFlags " : 16 | UINT8 ,
2019-10-01 14:01:56 +00:00
}
2022-09-19 10:55:46 +00:00
def _resp_cmd_register ( khlen : int , certlen : int , siglen : int ) - > dict :
2019-10-01 14:01:56 +00:00
cert_ofs = 67 + khlen
sig_ofs = cert_ofs + certlen
status_ofs = sig_ofs + siglen
2022-09-19 10:55:46 +00:00
UINT8 = uctypes . UINT8 # local_cache_attribute
ARRAY = uctypes . ARRAY # local_cache_attribute
2019-10-01 14:01:56 +00:00
# uint8_t registerId; // Registration identifier (U2F_REGISTER_ID)
# uint8_t pubKey[65]; // Generated public key
# uint8_t keyHandleLen; // Length of key handle
# uint8_t keyHandle[khlen]; // Key handle
# uint8_t cert[certlen]; // Attestation certificate
# uint8_t sig[siglen]; // Registration signature
# uint16_t status;
return {
2022-09-19 10:55:46 +00:00
" 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 ) ,
2019-10-01 14:01:56 +00:00
" status " : status_ofs | uctypes . UINT16 ,
}
# index of keyHandleLen in req_cmd_authenticate struct
_REQ_CMD_AUTHENTICATE_KHLEN = const ( 64 )
2022-09-19 10:55:46 +00:00
def _req_cmd_authenticate ( khlen : int ) - > uctypes . StructDict :
UINT8 = uctypes . UINT8 # local_cache_attribute
ARRAY = uctypes . ARRAY # local_cache_attribute
2019-10-01 14:01:56 +00:00
return {
2022-09-19 10:55:46 +00:00
" chal " : ( 0 | ARRAY , 32 | UINT8 ) ,
" appId " : ( 32 | ARRAY , 32 | UINT8 ) ,
" keyHandleLen " : 64 | UINT8 ,
" keyHandle " : ( 65 | ARRAY , khlen | UINT8 ) ,
2019-10-01 14:01:56 +00:00
}
2022-09-19 10:55:46 +00:00
def _resp_cmd_authenticate ( siglen : int ) - > uctypes . StructDict :
2019-10-01 14:01:56 +00:00
status_ofs = 5 + siglen
# uint8_t flags; // U2F_AUTH_FLAG_ values
# uint32_t ctr; // Counter field (big-endian)
# uint8_t sig[siglen]; // Signature
# uint16_t status;
return {
" flags " : 0 | uctypes . UINT8 ,
" ctr " : 1 | uctypes . UINT32 ,
" sig " : ( 5 | uctypes . ARRAY , siglen | uctypes . UINT8 ) ,
" status " : status_ofs | uctypes . UINT16 ,
}
2021-12-08 09:10:58 +00:00
def overlay_struct ( buf : bytearray , desc : uctypes . StructDict ) - > Any :
desc_size = uctypes . sizeof ( desc , uctypes . BIG_ENDIAN )
2019-10-01 14:01:56 +00:00
if desc_size > len ( buf ) :
2021-09-27 10:13:51 +00:00
raise ValueError ( f " desc is too big ( { desc_size } > { len ( buf ) } ) " )
2019-10-01 14:01:56 +00:00
return uctypes . struct ( uctypes . addressof ( buf ) , desc , uctypes . BIG_ENDIAN )
2021-12-08 09:10:58 +00:00
def make_struct ( desc : uctypes . StructDict ) - > tuple [ bytearray , Any ] :
desc_size = uctypes . sizeof ( desc , uctypes . BIG_ENDIAN )
2019-10-01 14:01:56 +00:00
buf = bytearray ( desc_size )
return buf , uctypes . struct ( uctypes . addressof ( buf ) , desc , uctypes . BIG_ENDIAN )
class Msg :
def __init__ (
self , cid : int , cla : int , ins : int , p1 : int , p2 : int , lc : int , data : bytes
) - > None :
self . cid = cid
self . cla = cla
self . ins = ins
self . p1 = p1
self . p2 = p2
self . lc = lc
self . data = data
class Cmd :
def __init__ ( self , cid : int , cmd : int , data : bytes ) - > None :
self . cid = cid
self . cmd = cmd
self . data = data
def to_msg ( self ) - > Msg :
2022-09-19 10:55:46 +00:00
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 ]
2019-10-01 14:01:56 +00:00
lc = (
2022-09-19 10:55:46 +00:00
( self_data [ _APDU_LC1 ] << 16 )
+ ( self_data [ _APDU_LC2 ] << 8 )
+ ( self_data [ _APDU_LC3 ] )
2019-10-01 14:01:56 +00:00
)
2022-09-19 10:55:46 +00:00
data = self_data [ _APDU_DATA : _APDU_DATA + lc ]
2019-10-01 14:01:56 +00:00
return Msg ( self . cid , cla , ins , p1 , p2 , lc , data )
2022-09-19 10:55:46 +00:00
async def _read_cmd ( iface : HID ) - > Cmd | None :
2019-10-01 14:01:56 +00:00
desc_init = frame_init ( )
desc_cont = frame_cont ( )
read = loop . wait ( iface . iface_num ( ) | io . POLL_READ )
buf = await read
while True :
2020-03-05 17:32:29 +00:00
ifrm = overlay_struct ( bytearray ( buf ) , desc_init )
2019-10-01 14:01:56 +00:00
bcnt = ifrm . bcnt
data = ifrm . data
datalen = len ( data )
seq = 0
2022-09-19 10:55:46 +00:00
ifrm_cid = ifrm . cid # local_cache_attribute
warning = log . warning # local_cache_attribute
2019-10-01 14:01:56 +00:00
if ifrm . cmd & _TYPE_MASK == _TYPE_CONT :
# unexpected cont packet, abort current msg
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _TYPE_CONT " )
2019-10-01 14:01:56 +00:00
return None
2022-09-19 10:55:46 +00:00
if ifrm_cid == 0 or ( ( ifrm_cid == _CID_BROADCAST ) and ( ifrm . cmd != _CMD_INIT ) ) :
2019-10-01 14:01:56 +00:00
# CID 0 is reserved for future use and _CID_BROADCAST is reserved for channel allocation
2022-09-19 10:55:46 +00:00
await send_cmd ( cmd_error ( ifrm_cid , _ERR_INVALID_CID ) , iface )
2019-10-01 14:01:56 +00:00
return None
if bcnt > _MAX_U2FHID_MSG_PAYLOAD_LEN :
# invalid payload length, abort current msg
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _MAX_U2FHID_MSG_PAYLOAD_LEN " )
await send_cmd ( cmd_error ( ifrm_cid , _ERR_INVALID_LEN ) , iface )
2019-10-01 14:01:56 +00:00
return None
if datalen < bcnt :
databuf = bytearray ( bcnt )
utils . memcpy ( databuf , 0 , data , 0 , bcnt )
data = databuf
else :
data = data [ : bcnt ]
while datalen < bcnt :
2020-06-02 09:02:06 +00:00
buf = await loop . race ( read , loop . sleep ( _CTAP_HID_TIMEOUT_MS ) )
2020-03-05 17:32:29 +00:00
if not isinstance ( buf , bytes ) :
2020-04-03 15:42:49 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _ERR_MSG_TIMEOUT " )
await send_cmd ( cmd_error ( ifrm_cid , _ERR_MSG_TIMEOUT ) , iface )
2019-10-01 14:01:56 +00:00
return None
2020-03-05 17:32:29 +00:00
cfrm = overlay_struct ( bytearray ( buf ) , desc_cont )
2022-09-19 10:55:46 +00:00
cfrm_cid = cfrm . cid # local_cache_attribute
2019-10-01 14:01:56 +00:00
if cfrm . seq == _CMD_INIT :
2022-09-19 10:55:46 +00:00
if cfrm_cid == ifrm_cid :
2020-04-03 15:42:49 +00:00
# _CMD_INIT command on current channel, abort current transaction.
if __debug__ :
2022-09-19 10:55:46 +00:00
warning (
2020-04-03 15:42:49 +00:00
__name__ ,
" U2FHID: received CMD_INIT command during active tran, aborting " ,
)
break
else :
# _CMD_INIT command on different channel, return synchronization response, but continue on current CID.
if __debug__ :
log . info (
__name__ ,
" U2FHID: received CMD_INIT command for different CID " ,
)
cfrm = overlay_struct ( bytearray ( buf ) , desc_init )
await send_cmd (
cmd_init (
2022-09-19 10:55:46 +00:00
Cmd ( cfrm_cid , cfrm . cmd , bytes ( cfrm . data [ : cfrm . bcnt ] ) )
2020-04-03 15:42:49 +00:00
) ,
iface ,
)
continue
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
if cfrm_cid != ifrm_cid :
2020-04-03 15:42:49 +00:00
# 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__ :
2022-09-19 10:55:46 +00:00
warning (
2020-04-03 15:42:49 +00:00
__name__ ,
" U2FHID: received init frame for different CID, _ERR_CHANNEL_BUSY " ,
)
2022-09-19 10:55:46 +00:00
await send_cmd ( cmd_error ( cfrm_cid , _ERR_CHANNEL_BUSY ) , iface )
2020-04-03 15:42:49 +00:00
else :
if __debug__ :
2022-09-19 10:55:46 +00:00
warning (
2020-04-03 15:42:49 +00:00
__name__ , " U2FHID: received cont frame for different CID "
)
2019-10-01 14:01:56 +00:00
continue
if cfrm . seq != seq :
# cont frame for this channel, but incorrect seq number, abort
# current msg
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _ERR_INVALID_SEQ " )
await send_cmd ( cmd_error ( cfrm_cid , _ERR_INVALID_SEQ ) , iface )
2019-10-01 14:01:56 +00:00
return None
datalen + = utils . memcpy ( data , datalen , cfrm . data , 0 , bcnt - datalen )
seq + = 1
else :
2022-09-19 10:55:46 +00:00
return Cmd ( ifrm_cid , ifrm . cmd , bytes ( data ) )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
async def send_cmd ( cmd : Cmd , iface : HID ) - > None :
2019-10-01 14:01:56 +00:00
init_desc = frame_init ( )
cont_desc = frame_cont ( )
offset = 0
seq = 0
datalen = len ( cmd . data )
buf , frm = make_struct ( init_desc )
frm . cid = cmd . cid
frm . cmd = cmd . cmd
frm . bcnt = datalen
offset + = utils . memcpy ( frm . data , 0 , cmd . data , offset , datalen )
iface . write ( buf )
if offset < datalen :
frm = overlay_struct ( buf , cont_desc )
write = loop . wait ( iface . iface_num ( ) | io . POLL_WRITE )
while offset < datalen :
frm . seq = seq
copied = utils . memcpy ( frm . data , 0 , cmd . data , offset , datalen )
offset + = copied
if copied < _FRAME_CONT_SIZE :
frm . data [ copied : ] = bytearray ( _FRAME_CONT_SIZE - copied )
while True :
2020-06-02 09:02:06 +00:00
ret = await loop . race ( write , loop . sleep ( _CTAP_HID_TIMEOUT_MS ) )
2020-01-11 13:33:24 +00:00
if ret is not None :
raise TimeoutError
2019-10-01 14:01:56 +00:00
if iface . write ( buf ) > 0 :
break
seq + = 1
2022-09-19 10:55:46 +00:00
def send_cmd_sync ( cmd : Cmd , iface : HID ) - > None :
2019-10-01 14:01:56 +00:00
init_desc = frame_init ( )
cont_desc = frame_cont ( )
offset = 0
seq = 0
datalen = len ( cmd . data )
buf , frm = make_struct ( init_desc )
frm . cid = cmd . cid
frm . cmd = cmd . cmd
frm . bcnt = datalen
offset + = utils . memcpy ( frm . data , 0 , cmd . data , offset , datalen )
iface . write ( buf )
if offset < datalen :
frm = overlay_struct ( buf , cont_desc )
while offset < datalen :
frm . seq = seq
copied = utils . memcpy ( frm . data , 0 , cmd . data , offset , datalen )
offset + = copied
if copied < _FRAME_CONT_SIZE :
frm . data [ copied : ] = bytearray ( _FRAME_CONT_SIZE - copied )
iface . write_blocking ( buf , 1000 )
seq + = 1
2022-09-19 10:55:46 +00:00
async def handle_reports ( iface : HID ) - > None :
2019-10-01 14:01:56 +00:00
dialog_mgr = DialogManager ( iface )
while True :
try :
2022-09-19 10:55:46 +00:00
req = await _read_cmd ( iface )
2019-10-01 14:01:56 +00:00
if req is None :
continue
2023-02-20 10:52:24 +00:00
if not dialog_mgr . allow_cid ( req . cid ) :
2021-03-18 09:48:50 +00:00
resp : Cmd | None = cmd_error ( req . cid , _ERR_CHANNEL_BUSY )
2019-10-01 14:01:56 +00:00
else :
2022-09-19 10:55:46 +00:00
resp = _dispatch_cmd ( req , dialog_mgr )
2019-10-01 14:01:56 +00:00
if resp is not None :
await send_cmd ( resp , iface )
except Exception as e :
log . exception ( __name__ , e )
class KeepaliveCallback :
2022-09-19 10:55:46 +00:00
def __init__ ( self , cid : int , iface : HID ) - > None :
2019-10-01 14:01:56 +00:00
self . cid = cid
self . iface = iface
def __call__ ( self ) - > None :
send_cmd_sync ( cmd_keepalive ( self . cid , _KEEPALIVE_STATUS_PROCESSING ) , self . iface )
async def verify_user ( keepalive_callback : KeepaliveCallback ) - > bool :
2020-04-21 12:31:24 +00:00
from trezor . wire import PinCancelled , PinInvalid
from apps . common . request_pin import verify_user_pin
2019-10-01 14:01:56 +00:00
import trezor . pin
try :
trezor . pin . keepalive_callback = keepalive_callback
2020-05-01 14:03:40 +00:00
await verify_user_pin ( cache_time_ms = _UV_CACHE_TIME_MS )
2019-10-01 14:01:56 +00:00
ret = True
except ( PinCancelled , PinInvalid ) :
ret = False
finally :
trezor . pin . keepalive_callback = None
return ret
2022-10-12 13:30:53 +00:00
def _confirm_fido_choose ( title : str , credentials : list [ Credential ] ) - > Awaitable [ int ] :
from trezor . ui . layouts . fido import confirm_fido
from . import knownapps
assert len ( credentials ) > 0
repr_credential = credentials [ 0 ]
if __debug__ :
for cred in credentials :
assert cred . rp_id_hash == repr_credential . rp_id_hash
app_name = repr_credential . app_name ( )
app = knownapps . by_rp_id_hash ( repr_credential . rp_id_hash )
icon_name = None if app is None else app . icon_name
return confirm_fido (
None , title , app_name , icon_name , [ c . account_name ( ) for c in credentials ]
)
async def _confirm_fido ( title : str , credential : Credential ) - > bool :
try :
await _confirm_fido_choose ( title , [ credential ] )
return True
except wire . ActionCancelled :
return False
2023-02-23 10:06:59 +00:00
async def _confirm_bogus_app ( title : str ) - > None :
if _last_auth_valid :
2023-05-10 13:59:17 +00:00
await show_error_popup (
2023-02-23 10:06:59 +00:00
title ,
" This device is already registered with this application. " ,
" Already registered. " ,
timeout_ms = _POPUP_TIMEOUT_MS ,
)
else :
2023-05-10 13:59:17 +00:00
await show_error_popup (
2023-02-23 10:06:59 +00:00
title ,
" This device is not registered with this application. " ,
" Not registered. " ,
timeout_ms = _POPUP_TIMEOUT_MS ,
)
2019-10-01 14:01:56 +00:00
class State :
2022-09-19 10:55:46 +00:00
def __init__ ( self , cid : int , iface : HID ) - > None :
2019-10-01 14:01:56 +00:00
self . cid = cid
self . iface = iface
2019-11-20 14:10:55 +00:00
self . finished = False
2019-10-01 14:01:56 +00:00
2023-02-20 10:52:24 +00:00
def allow_cid ( self , cid : int ) - > bool :
# Generally allow any CID, because Safari browser changes CID for every U2F message.
return True
2020-05-01 13:02:35 +00:00
def keepalive_status ( self ) - > int :
# Run the keepalive loop to check for timeout, but do not send any keepalive messages.
return _KEEPALIVE_STATUS_NONE
2019-10-01 14:01:56 +00:00
def timeout_ms ( self ) - > int :
raise NotImplementedError
2021-03-18 09:48:50 +00:00
async def confirm_dialog ( self ) - > bool | " State " :
2021-12-08 09:10:58 +00:00
raise NotImplementedError
2019-10-01 14:01:56 +00:00
async def on_confirm ( self ) - > None :
pass
async def on_decline ( self ) - > None :
pass
async def on_timeout ( self ) - > None :
pass
async def on_cancel ( self ) - > None :
pass
2022-10-12 13:30:53 +00:00
class U2fState ( State ) :
2022-09-19 10:55:46 +00:00
def __init__ ( self , cid : int , iface : HID , req_data : bytes , cred : Credential ) - > None :
2019-10-01 14:01:56 +00:00
State . __init__ ( self , cid , iface )
self . _cred = cred
self . _req_data = req_data
def timeout_ms ( self ) - > int :
return _U2F_CONFIRM_TIMEOUT_MS
def app_name ( self ) - > str :
return self . _cred . app_name ( )
2021-03-18 09:48:50 +00:00
def account_name ( self ) - > str | None :
2019-10-01 14:01:56 +00:00
return self . _cred . account_name ( )
class U2fConfirmRegister ( U2fState ) :
async def confirm_dialog ( self ) - > bool :
2019-12-02 19:09:41 +00:00
if self . _cred . rp_id_hash in _BOGUS_APPIDS :
2023-02-23 10:06:59 +00:00
await _confirm_bogus_app ( " U2F " )
2021-03-19 16:39:35 +00:00
return False
2019-10-01 14:01:56 +00:00
else :
2022-10-12 13:30:53 +00:00
return await _confirm_fido ( " U2F Register " , self . _cred )
2019-10-01 14:01:56 +00:00
def __eq__ ( self , other : object ) - > bool :
return (
2023-02-20 10:52:24 +00:00
isinstance ( other , U2fConfirmRegister ) and self . _req_data == other . _req_data
2019-10-01 14:01:56 +00:00
)
class U2fConfirmAuthenticate ( U2fState ) :
async def confirm_dialog ( self ) - > bool :
2022-10-12 13:30:53 +00:00
return await _confirm_fido ( " U2F Authenticate " , self . _cred )
2019-10-01 14:01:56 +00:00
def __eq__ ( self , other : object ) - > bool :
return (
isinstance ( other , U2fConfirmAuthenticate )
and self . _req_data == other . _req_data
)
2020-04-29 19:03:55 +00:00
class U2fUnlock ( State ) :
def timeout_ms ( self ) - > int :
return _U2F_CONFIRM_TIMEOUT_MS
async def confirm_dialog ( self ) - > bool :
from trezor . wire import PinCancelled , PinInvalid
from apps . common . request_pin import verify_user_pin
try :
await verify_user_pin ( )
set_homescreen ( )
except ( PinCancelled , PinInvalid ) :
return False
return True
def __eq__ ( self , other : object ) - > bool :
return isinstance ( other , U2fUnlock )
2019-10-01 14:01:56 +00:00
class Fido2State ( State ) :
2023-02-20 10:52:24 +00:00
def allow_cid ( self , cid : int ) - > bool :
# In FIDO2 lock out other channels until user interaction or timeout.
return cid == self . cid
2019-10-01 14:01:56 +00:00
def keepalive_status ( self ) - > int :
return _KEEPALIVE_STATUS_UP_NEEDED
def timeout_ms ( self ) - > int :
return _FIDO2_CONFIRM_TIMEOUT_MS
async def on_confirm ( self ) - > None :
cmd = cbor_error ( self . cid , _ERR_OPERATION_DENIED )
await send_cmd ( cmd , self . iface )
2019-11-20 14:10:55 +00:00
self . finished = True
2019-10-01 14:01:56 +00:00
async def on_decline ( self ) - > None :
cmd = cbor_error ( self . cid , _ERR_OPERATION_DENIED )
await send_cmd ( cmd , self . iface )
2019-11-20 14:10:55 +00:00
self . finished = True
2019-10-01 14:01:56 +00:00
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 )
2019-11-20 14:10:55 +00:00
self . finished = True
2019-10-01 14:01:56 +00:00
2020-05-01 13:03:02 +00:00
class Fido2Unlock ( Fido2State ) :
def __init__ (
self ,
2021-03-18 09:48:50 +00:00
process_func : Callable [ [ Cmd , " DialogManager " ] , State | Cmd ] ,
2020-05-01 13:03:02 +00:00
req : Cmd ,
dialog_mgr : " DialogManager " ,
) - > None :
super ( ) . __init__ ( req . cid , dialog_mgr . iface )
self . process_func = process_func
self . req = req
2021-03-18 09:48:50 +00:00
self . resp : Cmd | None = None
2020-05-01 13:03:02 +00:00
self . dialog_mgr = dialog_mgr
2021-03-18 09:48:50 +00:00
async def confirm_dialog ( self ) - > bool | " State " :
2020-05-01 13:03:02 +00:00
if not await verify_user ( KeepaliveCallback ( self . cid , self . iface ) ) :
return False
set_homescreen ( )
resp = self . process_func ( self . req , self . dialog_mgr )
if isinstance ( resp , State ) :
return resp
else :
self . resp = resp
return True
async def on_confirm ( self ) - > None :
if self . resp :
await send_cmd ( self . resp , self . iface )
2022-10-12 13:30:53 +00:00
class Fido2ConfirmMakeCredential ( Fido2State ) :
2019-10-01 14:01:56 +00:00
def __init__ (
self ,
cid : int ,
2022-09-19 10:55:46 +00:00
iface : HID ,
2019-10-01 14:01:56 +00:00
client_data_hash : bytes ,
cred : Fido2Credential ,
resident : bool ,
user_verification : bool ,
) - > None :
Fido2State . __init__ ( self , cid , iface )
self . _client_data_hash = client_data_hash
self . _cred = cred
self . _resident = resident
self . _user_verification = user_verification
async def confirm_dialog ( self ) - > bool :
2023-02-23 10:06:59 +00:00
if self . _cred . rp_id == _BOGUS_RP_ID :
await _confirm_bogus_app ( " FIDO2 " )
return True
2022-10-12 13:30:53 +00:00
if not await _confirm_fido ( " FIDO2 Register " , self . _cred ) :
2019-10-01 14:01:56 +00:00
return False
if self . _user_verification :
return await verify_user ( KeepaliveCallback ( self . cid , self . iface ) )
return True
async def on_confirm ( self ) - > None :
2022-09-19 10:55:46 +00:00
from . resident_credentials import store_resident_credential
cid = self . cid # local_cache_attribute
2019-10-01 14:01:56 +00:00
self . _cred . generate_id ( )
2022-09-19 10:55:46 +00:00
send_cmd_sync ( cmd_keepalive ( cid , _KEEPALIVE_STATUS_PROCESSING ) , self . iface )
response_data = _cbor_make_credential_sign (
2019-10-01 14:01:56 +00:00
self . _client_data_hash , self . _cred , self . _user_verification
)
2022-09-19 10:55:46 +00:00
cmd = Cmd ( cid , _CMD_CBOR , bytes ( [ _ERR_NONE ] ) + response_data )
2019-10-01 14:01:56 +00:00
if self . _resident :
2022-09-19 10:55:46 +00:00
send_cmd_sync ( cmd_keepalive ( cid , _KEEPALIVE_STATUS_PROCESSING ) , self . iface )
2019-10-01 14:01:56 +00:00
if not store_resident_credential ( self . _cred ) :
2022-09-19 10:55:46 +00:00
cmd = cbor_error ( cid , _ERR_KEY_STORE_FULL )
2019-10-01 14:01:56 +00:00
await send_cmd ( cmd , self . iface )
2019-11-20 14:10:55 +00:00
self . finished = True
2019-10-01 14:01:56 +00:00
class Fido2ConfirmExcluded ( Fido2ConfirmMakeCredential ) :
2022-09-19 10:55:46 +00:00
def __init__ ( self , cid : int , iface : HID , cred : Fido2Credential ) - > None :
2019-10-01 14:01:56 +00:00
super ( ) . __init__ ( cid , iface , b " " , cred , resident = False , user_verification = False )
async def on_confirm ( self ) - > None :
cmd = cbor_error ( self . cid , _ERR_CREDENTIAL_EXCLUDED )
await send_cmd ( cmd , self . iface )
2019-11-20 14:10:55 +00:00
self . finished = True
2019-10-01 14:01:56 +00:00
2023-05-10 13:59:17 +00:00
await show_error_popup (
2022-09-19 10:55:46 +00:00
" FIDO2 Register " ,
2023-01-20 13:57:33 +00:00
" This device is already registered with {} . " ,
2022-09-19 10:55:46 +00:00
" Already registered. " ,
self . _cred . rp_id , # description_param
2023-05-10 13:59:17 +00:00
timeout_ms = _POPUP_TIMEOUT_MS ,
2021-03-19 16:39:35 +00:00
)
2019-10-01 14:01:56 +00:00
2022-10-12 13:30:53 +00:00
class Fido2ConfirmGetAssertion ( Fido2State ) :
2019-10-01 14:01:56 +00:00
def __init__ (
self ,
cid : int ,
2022-09-19 10:55:46 +00:00
iface : HID ,
2019-10-01 14:01:56 +00:00
client_data_hash : bytes ,
2021-03-18 09:48:50 +00:00
creds : list [ Credential ] ,
hmac_secret : dict | None ,
2019-10-01 14:01:56 +00:00
resident : bool ,
user_verification : bool ,
) - > None :
Fido2State . __init__ ( self , cid , iface )
self . _client_data_hash = client_data_hash
self . _creds = creds
self . _hmac_secret = hmac_secret
self . _resident = resident
self . _user_verification = user_verification
2022-10-12 13:30:53 +00:00
self . _selected_cred : Credential | None = None
2019-10-01 14:01:56 +00:00
async def confirm_dialog ( self ) - > bool :
2022-10-12 13:30:53 +00:00
# There is a choice from more than one credential.
try :
index = await _confirm_fido_choose ( " FIDO2 Authenticate " , self . _creds )
except wire . ActionCancelled :
2019-10-01 14:01:56 +00:00
return False
2022-10-12 13:30:53 +00:00
self . _selected_cred = self . _creds [ index ]
2019-10-01 14:01:56 +00:00
if self . _user_verification :
return await verify_user ( KeepaliveCallback ( self . cid , self . iface ) )
return True
async def on_confirm ( self ) - > None :
2022-09-19 10:55:46 +00:00
cid = self . cid # local_cache_attribute
2022-10-12 13:30:53 +00:00
assert self . _selected_cred is not None
2019-10-01 14:01:56 +00:00
try :
2022-09-19 10:55:46 +00:00
send_cmd_sync ( cmd_keepalive ( cid , _KEEPALIVE_STATUS_PROCESSING ) , self . iface )
2019-10-01 14:01:56 +00:00
response_data = cbor_get_assertion_sign (
self . _client_data_hash ,
2022-10-12 13:30:53 +00:00
self . _selected_cred . rp_id_hash ,
self . _selected_cred ,
2019-10-01 14:01:56 +00:00
self . _hmac_secret ,
self . _resident ,
True ,
self . _user_verification ,
)
2022-09-19 10:55:46 +00:00
cmd = Cmd ( cid , _CMD_CBOR , bytes ( [ _ERR_NONE ] ) + response_data )
2019-10-01 14:01:56 +00:00
except CborError as e :
2022-09-19 10:55:46 +00:00
cmd = cbor_error ( cid , e . code )
2019-10-01 14:01:56 +00:00
except KeyError :
2022-09-19 10:55:46 +00:00
cmd = cbor_error ( cid , _ERR_MISSING_PARAMETER )
2020-04-09 20:17:19 +00:00
except Exception as e :
# Firmware error.
if __debug__ :
log . exception ( __name__ , e )
2022-09-19 10:55:46 +00:00
cmd = cbor_error ( cid , _ERR_OTHER )
2019-10-01 14:01:56 +00:00
await send_cmd ( cmd , self . iface )
2019-11-20 14:10:55 +00:00
self . finished = True
2019-10-01 14:01:56 +00:00
class Fido2ConfirmNoPin ( State ) :
2023-02-20 10:52:24 +00:00
def allow_cid ( self , cid : int ) - > bool :
# In FIDO2 lock out other channels until user interaction or timeout.
return cid == self . cid
2019-10-01 14:01:56 +00:00
def timeout_ms ( self ) - > int :
return _FIDO2_CONFIRM_TIMEOUT_MS
async def confirm_dialog ( self ) - > bool :
2020-05-01 13:03:02 +00:00
cmd = cbor_error ( self . cid , _ERR_UNSUPPORTED_OPTION )
await send_cmd ( cmd , self . iface )
self . finished = True
2023-05-10 13:59:17 +00:00
await show_error_popup (
2022-09-19 10:55:46 +00:00
" FIDO2 Verify User " ,
2023-01-20 13:57:33 +00:00
" Please enable PIN protection. " ,
2022-09-19 10:55:46 +00:00
" Unable to verify user. " ,
2021-03-19 16:39:35 +00:00
timeout_ms = _POPUP_TIMEOUT_MS ,
)
return False
2019-10-01 14:01:56 +00:00
class Fido2ConfirmNoCredentials ( Fido2ConfirmGetAssertion ) :
2022-09-19 10:55:46 +00:00
def __init__ ( self , cid : int , iface : HID , rp_id : str ) - > None :
2019-10-01 14:01:56 +00:00
cred = Fido2Credential ( )
cred . rp_id = rp_id
2023-02-24 17:49:23 +00:00
cred . rp_id_hash = hashlib . sha256 ( rp_id ) . digest ( )
2019-10-01 14:01:56 +00:00
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 )
await send_cmd ( cmd , self . iface )
2019-11-20 14:10:55 +00:00
self . finished = True
2019-10-01 14:01:56 +00:00
2023-05-10 13:59:17 +00:00
await show_error_popup (
2022-09-19 10:55:46 +00:00
" FIDO2 Authenticate " ,
2023-01-20 13:57:33 +00:00
" This device is not registered with \n {} . " ,
2022-09-19 10:55:46 +00:00
" Not registered. " ,
self . _creds [ 0 ] . app_name ( ) , # description_param
2023-05-10 13:59:17 +00:00
timeout_ms = _POPUP_TIMEOUT_MS ,
2019-10-01 14:01:56 +00:00
)
class Fido2ConfirmReset ( Fido2State ) :
async def confirm_dialog ( self ) - > bool :
2022-10-12 13:30:53 +00:00
from trezor . ui . layouts . fido import confirm_fido_reset
2022-09-19 10:55:46 +00:00
2022-10-12 13:30:53 +00:00
return await confirm_fido_reset ( )
2019-10-01 14:01:56 +00:00
async def on_confirm ( self ) - > None :
2022-09-19 10:55:46 +00:00
import storage . resident_credentials
2019-11-08 11:47:54 +00:00
storage . resident_credentials . delete_all ( )
2019-10-01 14:01:56 +00:00
cmd = Cmd ( self . cid , _CMD_CBOR , bytes ( [ _ERR_NONE ] ) )
await send_cmd ( cmd , self . iface )
2019-11-20 14:10:55 +00:00
self . finished = True
2019-10-01 14:01:56 +00:00
class DialogManager :
2022-09-19 10:55:46 +00:00
def __init__ ( self , iface : HID ) - > None :
2019-10-01 14:01:56 +00:00
self . iface = iface
self . _clear ( )
def _clear ( self ) - > None :
2021-03-18 09:48:50 +00:00
self . state : State | None = None
2019-10-01 14:01:56 +00:00
self . deadline = 0
self . result = _RESULT_NONE
2021-03-18 09:48:50 +00:00
self . workflow : loop . spawn | None = None
self . keepalive : Coroutine | None = None
2019-10-01 14:01:56 +00:00
2020-05-22 09:00:54 +00:00
def _workflow_is_running ( self ) - > bool :
return self . workflow is not None and not self . workflow . finished
2019-10-01 14:01:56 +00:00
def reset_timeout ( self ) - > None :
if self . state is not None :
self . deadline = utime . ticks_ms ( ) + self . state . timeout_ms ( )
def reset ( self ) - > None :
if self . workflow is not None :
2020-05-22 09:00:54 +00:00
self . workflow . close ( )
2019-10-01 14:01:56 +00:00
if self . keepalive is not None :
loop . close ( self . keepalive )
self . _clear ( )
2023-02-20 10:52:24 +00:00
def allow_cid ( self , cid : int ) - > bool :
return (
not self . is_busy ( )
or cid == _CID_BROADCAST
or ( self . state is not None and self . state . allow_cid ( cid ) )
)
2019-10-01 14:01:56 +00:00
def is_busy ( self ) - > bool :
if utime . ticks_ms ( ) > = self . deadline :
self . reset ( )
2019-11-20 18:58:39 +00:00
2020-05-22 09:00:54 +00:00
if not self . _workflow_is_running ( ) :
2019-11-20 18:58:39 +00:00
return bool ( workflow . tasks )
if self . state is None or self . state . finished :
self . reset ( )
return False
return True
2019-10-01 14:01:56 +00:00
def set_state ( self , state : State ) - > bool :
2020-04-29 19:03:55 +00:00
if self . state == state and utime . ticks_ms ( ) < self . deadline :
self . reset_timeout ( )
return True
2019-11-20 18:58:39 +00:00
if self . is_busy ( ) :
2019-10-01 14:01:56 +00:00
return False
self . state = state
self . reset_timeout ( )
self . result = _RESULT_NONE
2020-05-22 09:00:54 +00:00
self . keepalive = self . keepalive_loop ( ) # TODO: use loop.spawn here
2020-05-01 13:03:02 +00:00
loop . schedule ( self . keepalive )
2020-05-22 09:00:54 +00:00
self . workflow = workflow . spawn ( self . dialog_workflow ( ) )
2019-10-01 14:01:56 +00:00
return True
async def keepalive_loop ( self ) - > None :
2022-09-19 10:55:46 +00:00
state = self . state # local_cache_attribute
2019-10-09 13:18:25 +00:00
try :
2022-09-19 10:55:46 +00:00
if not state :
2019-10-09 13:18:25 +00:00
return
while utime . ticks_ms ( ) < self . deadline :
2022-09-19 10:55:46 +00:00
if state . keepalive_status ( ) != _KEEPALIVE_STATUS_NONE :
cmd = cmd_keepalive ( state . cid , state . keepalive_status ( ) )
2019-12-03 09:32:01 +00:00
await send_cmd ( cmd , self . iface )
2020-06-02 09:02:06 +00:00
await loop . sleep ( _KEEPALIVE_INTERVAL_MS )
2019-10-09 13:18:25 +00:00
finally :
self . keepalive = None
2019-10-01 14:01:56 +00:00
self . result = _RESULT_TIMEOUT
self . reset ( )
async def dialog_workflow ( self ) - > None :
2020-05-22 09:00:54 +00:00
if self . state is None :
2019-10-01 14:01:56 +00:00
return
try :
2020-05-22 09:00:54 +00:00
while self . result is _RESULT_NONE :
result = await self . state . confirm_dialog ( )
if isinstance ( result , State ) :
self . state = result
self . reset_timeout ( )
elif result is True :
self . result = _RESULT_CONFIRM
2019-11-20 14:10:55 +00:00
else :
2020-05-22 09:00:54 +00:00
self . result = _RESULT_DECLINE
2019-10-01 14:01:56 +00:00
finally :
2020-05-22 09:00:54 +00:00
if self . keepalive is not None :
loop . close ( self . keepalive )
2022-09-19 10:55:46 +00:00
result = self . result # local_cache_attribute
state = self . state # local_cache_attribute
if result == _RESULT_CONFIRM :
await state . on_confirm ( )
elif result == _RESULT_CANCEL :
await state . on_cancel ( )
elif result == _RESULT_TIMEOUT :
await state . on_timeout ( )
2020-05-22 09:00:54 +00:00
else :
2022-09-19 10:55:46 +00:00
await state . on_decline ( )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
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
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
if cmd == _CMD_MSG :
2019-10-01 14:01:56 +00:00
try :
m = req . to_msg ( )
except IndexError :
2022-09-19 10:55:46 +00:00
return cmd_error ( cid , _ERR_INVALID_LEN )
ins = m . ins # local_cache_attribute
2019-10-01 14:01:56 +00:00
if m . cla != 0 :
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _SW_CLA_NOT_SUPPORTED " )
return msg_error ( cid , _SW_CLA_NOT_SUPPORTED )
2019-10-01 14:01:56 +00:00
if m . lc + _APDU_DATA > len ( req . data ) :
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _SW_WRONG_LENGTH " )
return msg_error ( cid , _SW_WRONG_LENGTH )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
if ins == _MSG_REGISTER :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _MSG_REGISTER " )
return _msg_register ( m , dialog_mgr )
elif ins == _MSG_AUTHENTICATE :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _MSG_AUTHENTICATE " )
return _msg_authenticate ( m , dialog_mgr )
elif ins == _MSG_VERSION :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
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
2019-10-01 14:01:56 +00:00
else :
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _SW_INS_NOT_SUPPORTED: %d " , ins )
return msg_error ( cid , _SW_INS_NOT_SUPPORTED )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
elif cmd == _CMD_INIT :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CMD_INIT " )
2019-10-01 14:01:56 +00:00
return cmd_init ( req )
2022-09-19 10:55:46 +00:00
elif cmd == _CMD_PING :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CMD_PING " )
2019-10-01 14:01:56 +00:00
return req
2022-09-19 10:55:46 +00:00
elif cmd == _CMD_WINK and _ALLOW_WINK :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CMD_WINK " )
return _cmd_wink ( req )
elif cmd == _CMD_CBOR and _ALLOW_FIDO2 :
2019-10-01 14:01:56 +00:00
if not req . data :
2022-09-19 10:55:46 +00:00
return cmd_error ( cid , _ERR_INVALID_LEN )
req_data_first = req . data [ 0 ]
if req_data_first == _CBOR_MAKE_CREDENTIAL :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CBOR_MAKE_CREDENTIAL " )
return _cbor_make_credential ( req , dialog_mgr )
elif req_data_first == _CBOR_GET_ASSERTION :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CBOR_GET_ASSERTION " )
return _cbor_get_assertion ( req , dialog_mgr )
elif req_data_first == _CBOR_GET_INFO :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CBOR_GET_INFO " )
return _cbor_get_info ( req )
elif req_data_first == _CBOR_CLIENT_PIN :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CBOR_CLIENT_PIN " )
return _cbor_client_pin ( req )
elif req_data_first == _CBOR_RESET :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CBOR_RESET " )
return _cbor_reset ( req , dialog_mgr )
elif req_data_first == _CBOR_GET_NEXT_ASSERTION :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CBOR_GET_NEXT_ASSERTION " )
return cbor_error ( cid , _ERR_NOT_ALLOWED )
2019-10-01 14:01:56 +00:00
else :
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _ERR_INVALID_CMD _CMD_CBOR %d " , req_data_first )
return cbor_error ( cid , _ERR_INVALID_CMD )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
elif cmd == _CMD_CANCEL :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
debug ( __name__ , " _CMD_CANCEL " )
2019-10-01 14:01:56 +00:00
dialog_mgr . result = _RESULT_CANCEL
dialog_mgr . reset ( )
return None
else :
if __debug__ :
2022-09-19 10:55:46 +00:00
warning ( __name__ , " _ERR_INVALID_CMD: %d " , cmd )
return cmd_error ( cid , _ERR_INVALID_CMD )
2019-10-01 14:01:56 +00:00
def cmd_init ( req : Cmd ) - > Cmd :
2022-09-19 10:55:46 +00:00
from trezor . crypto import random
cid = req . cid # local_cache_attribute
if cid == _CID_BROADCAST :
2020-11-23 13:24:13 +00:00
# uint32_t except 0 and 0xffff_ffff
resp_cid = random . uniform ( 0xFFFF_FFFE ) + 1
2019-10-01 14:01:56 +00:00
else :
2022-09-19 10:55:46 +00:00
resp_cid = cid
2019-10-01 14:01:56 +00:00
2020-04-03 15:42:49 +00:00
if len ( req . data ) != _CMD_INIT_NONCE_SIZE :
2022-09-19 10:55:46 +00:00
return cmd_error ( cid , _ERR_INVALID_LEN )
2020-04-03 15:42:49 +00:00
2022-09-19 10:55:46 +00:00
buf , resp = make_struct ( _resp_cmd_init ( ) )
2019-10-01 14:01:56 +00:00
utils . memcpy ( resp . nonce , 0 , req . data , 0 , len ( req . data ) )
resp . cid = resp_cid
resp . versionInterface = _U2FHID_IF_VERSION
resp . versionMajor = 2
resp . versionMinor = 0
resp . versionBuild = 0
2020-03-16 10:33:41 +00:00
resp . capFlags = ( _CAPFLAG_WINK * _ALLOW_WINK ) | _CAPFLAG_CBOR
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
return Cmd ( cid , req . cmd , bytes ( buf ) )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
def _cmd_wink ( req : Cmd ) - > Cmd :
from trezor import ui
2019-12-02 11:04:49 +00:00
global _last_wink_cid
if _last_wink_cid != req . cid :
_last_wink_cid = req . cid
ui . alert ( )
return req
2022-09-19 10:55:46 +00:00
def _msg_register ( req : Msg , dialog_mgr : DialogManager ) - > Cmd :
from . credential import U2fCredential
cid = req . cid # local_cache_attribute
data = req . data # local_cache_attribute
2020-04-29 19:03:55 +00:00
if not config . is_unlocked ( ) :
2022-09-19 10:55:46 +00:00
new_state : State = U2fUnlock ( cid , dialog_mgr . iface )
2020-04-29 19:03:55 +00:00
dialog_mgr . set_state ( new_state )
2022-09-19 10:55:46 +00:00
return msg_error ( cid , _SW_CONDITIONS_NOT_SATISFIED )
2020-04-29 19:03:55 +00:00
2022-09-19 10:55:46 +00:00
if not storage_device . is_initialized ( ) :
2019-10-01 14:01:56 +00:00
if __debug__ :
log . warning ( __name__ , " not initialized " )
2020-04-09 20:17:19 +00:00
# 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.
2022-09-19 10:55:46 +00:00
return cmd_error ( cid , _ERR_CHANNEL_BUSY )
2019-10-01 14:01:56 +00:00
# check length of input data
2022-09-19 10:55:46 +00:00
if len ( data ) != 64 :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
log . warning ( __name__ , " _SW_WRONG_LENGTH req_data " )
return msg_error ( cid , _SW_WRONG_LENGTH )
2019-10-01 14:01:56 +00:00
# parse challenge and rp_id_hash
2022-09-19 10:55:46 +00:00
chal = data [ : 32 ]
2019-10-01 14:01:56 +00:00
cred = U2fCredential ( )
2022-09-19 10:55:46 +00:00
cred . rp_id_hash = data [ 32 : ]
2019-10-01 14:01:56 +00:00
cred . generate_key_handle ( )
# check equality with last request
2022-09-19 10:55:46 +00:00
new_state = U2fConfirmRegister ( cid , dialog_mgr . iface , data , cred )
2020-04-29 19:03:55 +00:00
if not dialog_mgr . set_state ( new_state ) :
2022-09-19 10:55:46 +00:00
return msg_error ( cid , _SW_CONDITIONS_NOT_SATISFIED )
2019-10-01 14:01:56 +00:00
# wait for a button or continue
2019-12-03 09:06:39 +00:00
if dialog_mgr . result == _RESULT_NONE :
2019-10-01 14:01:56 +00:00
if __debug__ :
log . info ( __name__ , " waiting for button " )
2022-09-19 10:55:46 +00:00
return msg_error ( cid , _SW_CONDITIONS_NOT_SATISFIED )
2019-10-01 14:01:56 +00:00
2019-12-03 09:06:39 +00:00
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.
2022-09-19 10:55:46 +00:00
return cmd_error ( cid , _ERR_CHANNEL_BUSY )
2019-12-03 09:06:39 +00:00
2019-10-01 14:01:56 +00:00
# sign the registration challenge and return
if __debug__ :
log . info ( __name__ , " signing register " )
2022-09-19 10:55:46 +00:00
buf = _msg_register_sign ( chal , cred )
2019-10-01 14:01:56 +00:00
dialog_mgr . reset ( )
2022-09-19 10:55:46 +00:00
return Cmd ( cid , _CMD_MSG , buf )
2019-10-01 14:01:56 +00:00
2020-02-27 17:58:21 +00:00
def basic_attestation_sign ( data : Iterable [ bytes ] ) - > bytes :
2022-09-19 10:55:46 +00:00
from trezor . crypto import der
2019-10-01 14:01:56 +00:00
dig = hashlib . sha256 ( )
2020-02-27 17:58:21 +00:00
for segment in data :
dig . update ( segment )
2020-04-02 14:21:19 +00:00
sig = nist256p1 . sign ( _FIDO_ATT_PRIV_KEY , dig . digest ( ) , False )
2020-02-27 17:58:21 +00:00
return der . encode_seq ( ( sig [ 1 : 33 ] , sig [ 33 : ] ) )
2022-09-19 10:55:46 +00:00
def _msg_register_sign ( challenge : bytes , cred : U2fCredential ) - > bytes :
memcpy = utils . memcpy # local_cache_attribute
id = cred . id # local_cache_attribute
2020-02-27 17:58:21 +00:00
pubkey = cred . public_key ( )
2022-09-19 10:55:46 +00:00
sig = basic_attestation_sign ( ( b " \x00 " , cred . rp_id_hash , challenge , id , pubkey ) )
2019-10-01 14:01:56 +00:00
# pack to a response
2022-09-19 10:55:46 +00:00
buf , resp = make_struct ( _resp_cmd_register ( len ( id ) , len ( _FIDO_ATT_CERT ) , len ( sig ) ) )
2019-10-01 14:01:56 +00:00
resp . registerId = _U2F_REGISTER_ID
2022-09-19 10:55:46 +00:00
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 ) )
2019-10-01 14:01:56 +00:00
resp . status = _SW_NO_ERROR
2020-03-05 17:32:29 +00:00
return bytes ( buf )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
def _msg_authenticate ( req : Msg , dialog_mgr : DialogManager ) - > Cmd :
2023-02-20 10:52:24 +00:00
global _last_auth_valid
_last_auth_valid = False
2022-09-19 10:55:46 +00:00
cid = req . cid # local_cache_attribute
data = req . data # local_cache_attribute
info = log . info # local_cache_attribute
2020-04-29 19:03:55 +00:00
if not config . is_unlocked ( ) :
2022-09-19 10:55:46 +00:00
new_state : State = U2fUnlock ( cid , dialog_mgr . iface )
2020-04-29 19:03:55 +00:00
dialog_mgr . set_state ( new_state )
2022-09-19 10:55:46 +00:00
return msg_error ( cid , _SW_CONDITIONS_NOT_SATISFIED )
2020-04-29 19:03:55 +00:00
2022-09-19 10:55:46 +00:00
if not storage_device . is_initialized ( ) :
2019-10-01 14:01:56 +00:00
if __debug__ :
log . warning ( __name__ , " not initialized " )
2020-04-09 20:17:19 +00:00
# Device is not registered with the RP.
2022-09-19 10:55:46 +00:00
return msg_error ( cid , _SW_WRONG_DATA )
2019-10-01 14:01:56 +00:00
# we need at least keyHandleLen
2022-09-19 10:55:46 +00:00
if len ( data ) < = _REQ_CMD_AUTHENTICATE_KHLEN :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
log . warning ( __name__ , " _SW_WRONG_LENGTH req_data " )
return msg_error ( cid , _SW_WRONG_LENGTH )
2019-10-01 14:01:56 +00:00
# check keyHandleLen
2022-09-19 10:55:46 +00:00
khlen = data [ _REQ_CMD_AUTHENTICATE_KHLEN ]
auth = overlay_struct ( bytearray ( data ) , _req_cmd_authenticate ( khlen ) )
2020-03-05 17:32:29 +00:00
challenge = bytes ( auth . chal )
rp_id_hash = bytes ( auth . appId )
key_handle = bytes ( auth . keyHandle )
2019-10-01 14:01:56 +00:00
2019-11-06 12:56:52 +00:00
try :
2020-03-05 17:32:29 +00:00
cred = Credential . from_bytes ( key_handle , rp_id_hash )
2019-11-06 12:56:52 +00:00
except Exception :
2020-04-09 20:17:19 +00:00
# specific error logged in _node_from_key_handle
2022-09-19 10:55:46 +00:00
return msg_error ( cid , _SW_WRONG_DATA )
2019-10-01 14:01:56 +00:00
2023-02-20 10:52:24 +00:00
_last_auth_valid = True
2019-10-01 14:01:56 +00:00
# if _AUTH_CHECK_ONLY is requested, return, because keyhandle has been checked already
if req . p1 == _AUTH_CHECK_ONLY :
if __debug__ :
2022-09-19 10:55:46 +00:00
info ( __name__ , " _AUTH_CHECK_ONLY " )
return msg_error ( cid , _SW_CONDITIONS_NOT_SATISFIED )
2019-10-01 14:01:56 +00:00
# from now on, only _AUTH_ENFORCE is supported
if req . p1 != _AUTH_ENFORCE :
if __debug__ :
2022-09-19 10:55:46 +00:00
info ( __name__ , " _AUTH_ENFORCE " )
return msg_error ( cid , _SW_WRONG_DATA )
2019-10-01 14:01:56 +00:00
# check equality with last request
2022-09-19 10:55:46 +00:00
new_state = U2fConfirmAuthenticate ( cid , dialog_mgr . iface , data , cred )
2020-04-29 19:03:55 +00:00
if not dialog_mgr . set_state ( new_state ) :
2022-09-19 10:55:46 +00:00
return msg_error ( cid , _SW_CONDITIONS_NOT_SATISFIED )
2019-10-01 14:01:56 +00:00
# wait for a button or continue
2019-12-03 09:06:39 +00:00
if dialog_mgr . result == _RESULT_NONE :
2019-10-01 14:01:56 +00:00
if __debug__ :
2022-09-19 10:55:46 +00:00
info ( __name__ , " waiting for button " )
return msg_error ( cid , _SW_CONDITIONS_NOT_SATISFIED )
2019-10-01 14:01:56 +00:00
2019-12-03 09:06:39 +00:00
if dialog_mgr . result != _RESULT_CONFIRM :
if __debug__ :
2022-09-19 10:55:46 +00:00
info ( __name__ , " request declined " )
2019-12-03 09:06:39 +00:00
# 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.
2022-09-19 10:55:46 +00:00
return cmd_error ( cid , _ERR_CHANNEL_BUSY )
2019-12-03 09:06:39 +00:00
2019-10-01 14:01:56 +00:00
# sign the authentication challenge and return
if __debug__ :
2022-09-19 10:55:46 +00:00
info ( __name__ , " signing authentication " )
buf = _msg_authenticate_sign ( challenge , rp_id_hash , cred )
2019-10-01 14:01:56 +00:00
dialog_mgr . reset ( )
2022-09-19 10:55:46 +00:00
return Cmd ( cid , _CMD_MSG , buf )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
def _msg_authenticate_sign (
2019-10-01 14:01:56 +00:00
challenge : bytes , rp_id_hash : bytes , cred : Credential
) - > bytes :
flags = bytes ( [ _AUTH_FLAG_UP ] )
# get next counter
ctr = cred . next_signature_counter ( )
ctrbuf = ustruct . pack ( " >L " , ctr )
2020-02-27 17:58:21 +00:00
# sign the input data together with counter
sig = cred . sign ( ( rp_id_hash , flags , ctrbuf , challenge ) )
2019-10-01 14:01:56 +00:00
# pack to a response
2022-09-19 10:55:46 +00:00
buf , resp = make_struct ( _resp_cmd_authenticate ( len ( sig ) ) )
2019-10-01 14:01:56 +00:00
resp . flags = flags [ 0 ]
resp . ctr = ctr
utils . memcpy ( resp . sig , 0 , sig , 0 , len ( sig ) )
resp . status = _SW_NO_ERROR
2020-03-05 17:32:29 +00:00
return bytes ( buf )
2019-10-01 14:01:56 +00:00
def msg_error ( cid : int , code : int ) - > Cmd :
return Cmd ( cid , _CMD_MSG , ustruct . pack ( " >H " , code ) )
def cmd_error ( cid : int , code : int ) - > Cmd :
return Cmd ( cid , _CMD_ERROR , ustruct . pack ( " >B " , code ) )
def cbor_error ( cid : int , code : int ) - > Cmd :
return Cmd ( cid , _CMD_CBOR , ustruct . pack ( " >B " , code ) )
def credentials_from_descriptor_list (
2021-03-18 09:48:50 +00:00
descriptor_list : list [ dict ] , rp_id_hash : bytes
2020-03-15 19:56:03 +00:00
) - > Iterator [ Credential ] :
2019-10-01 14:01:56 +00:00
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 " ]
2020-03-05 17:32:29 +00:00
if not isinstance ( credential_id , bytes ) :
2019-10-01 14:01:56 +00:00
raise TypeError
2019-11-06 12:56:52 +00:00
try :
cred = Credential . from_bytes ( credential_id , rp_id_hash )
except Exception :
2020-03-15 19:56:03 +00:00
continue
yield cred
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
def _distinguishable_cred_list ( credentials : Iterable [ Credential ] ) - > list [ Credential ] :
2020-03-15 19:56:03 +00:00
""" 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 . """
2021-03-18 09:48:50 +00:00
cred_list : list [ Credential ] = [ ]
2020-03-15 19:56:03 +00:00
for cred in credentials :
for i , prev_cred in enumerate ( cred_list ) :
if prev_cred . account_name ( ) == cred . account_name ( ) :
# Among indistinguishable FIDO2 credentials prefer the newest.
# Among U2F credentials prefer the first in the input.
if isinstance ( cred , Fido2Credential ) and cred < prev_cred :
cred_list [ i ] = cred
break
else :
cred_list . append ( cred )
2019-10-01 14:01:56 +00:00
return cred_list
2022-09-19 10:55:46 +00:00
def _algorithms_from_pub_key_cred_params ( pub_key_cred_params : list [ dict ] ) - > list [ int ] :
2019-10-01 14:01:56 +00:00
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
2022-09-19 10:55:46 +00:00
def _cbor_make_credential ( req : Cmd , dialog_mgr : DialogManager ) - > Cmd | None :
2020-05-01 13:03:02 +00:00
if config . is_unlocked ( ) :
2022-09-19 10:55:46 +00:00
resp = _cbor_make_credential_process ( req , dialog_mgr )
2020-05-01 13:03:02 +00:00
else :
2022-09-19 10:55:46 +00:00
resp = Fido2Unlock ( _cbor_make_credential_process , req , dialog_mgr )
2020-05-01 13:03:02 +00:00
if isinstance ( resp , State ) :
if dialog_mgr . set_state ( resp ) :
return None
else :
return cmd_error ( req . cid , _ERR_CHANNEL_BUSY )
else :
return resp
2022-09-19 10:55:46 +00:00
def _cbor_make_credential_process ( req : Cmd , dialog_mgr : DialogManager ) - > State | Cmd :
2020-08-11 13:55:05 +00:00
from . import knownapps
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
cid = req . cid # local_cache_attribute
if not storage_device . is_initialized ( ) :
2019-10-01 14:01:56 +00:00
if __debug__ :
log . warning ( __name__ , " not initialized " )
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_OTHER )
2019-10-01 14:01:56 +00:00
try :
2021-03-22 20:14:25 +00:00
param = cbor . decode ( req . data , offset = 1 )
2019-10-01 14:01:56 +00:00
rp = param [ _MAKECRED_CMD_RP ]
rp_id = rp [ " id " ]
rp_id_hash = hashlib . sha256 ( rp_id ) . digest ( )
# Prepare the new credential.
user = param [ _MAKECRED_CMD_USER ]
cred = Fido2Credential ( )
cred . rp_id = rp_id
cred . rp_id_hash = rp_id_hash
2023-03-10 13:52:50 +00:00
cred . rp_name = rp . get ( " name " , None )
2019-10-01 14:01:56 +00:00
cred . user_id = user [ " id " ]
cred . user_name = user . get ( " name " , None )
cred . user_display_name = user . get ( " displayName " , None )
2020-03-14 19:16:44 +00:00
cred . truncate_names ( )
2019-10-01 14:01:56 +00:00
# Check if any of the credential descriptors in the exclude list belong to this authenticator.
exclude_list = param . get ( _MAKECRED_CMD_EXCLUDE_LIST , [ ] )
2020-03-15 19:56:03 +00:00
excluded_creds = credentials_from_descriptor_list ( exclude_list , rp_id_hash )
if not utils . is_empty_iterator ( excluded_creds ) :
2019-10-01 14:01:56 +00:00
# This authenticator is already registered.
2022-09-19 10:55:46 +00:00
return Fido2ConfirmExcluded ( cid , dialog_mgr . iface , cred )
2019-10-01 14:01:56 +00:00
2020-02-27 17:58:21 +00:00
# Check that the relying party supports ECDSA with SHA-256 or EdDSA. We don't support any other algorithms.
2019-10-01 14:01:56 +00:00
pub_key_cred_params = param [ _MAKECRED_CMD_PUB_KEY_CRED_PARAMS ]
2022-09-19 10:55:46 +00:00
for alg in _algorithms_from_pub_key_cred_params ( pub_key_cred_params ) :
2020-02-27 17:58:21 +00:00
if alg == common . COSE_ALG_ES256 :
cred . algorithm = alg
cred . curve = common . COSE_CURVE_P256
break
elif alg == common . COSE_ALG_EDDSA :
cred . algorithm = alg
cred . curve = common . COSE_CURVE_ED25519
break
else :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_UNSUPPORTED_ALGORITHM )
2019-10-01 14:01:56 +00:00
# Get options.
options = param . get ( _MAKECRED_CMD_OPTIONS , { } )
resident_key = options . get ( " rk " , False )
user_verification = options . get ( " uv " , False )
# Get supported extensions.
cred . hmac_secret = param . get ( _MAKECRED_CMD_EXTENSIONS , { } ) . get (
" hmac-secret " , False
)
client_data_hash = param [ _MAKECRED_CMD_CLIENT_DATA_HASH ]
except TypeError :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_CBOR_UNEXPECTED_TYPE )
2019-10-01 14:01:56 +00:00
except KeyError :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_MISSING_PARAMETER )
2019-10-01 14:01:56 +00:00
except Exception :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_INVALID_CBOR )
2019-10-01 14:01:56 +00:00
2019-11-28 14:51:41 +00:00
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 :
2022-09-19 10:55:46 +00:00
cred . use_sign_count = bool ( _DEFAULT_USE_SIGN_COUNT )
2019-10-01 14:01:56 +00:00
2023-03-10 13:52:50 +00:00
if app is not None and app . use_compact :
# Remove unnecessary information.
# The user_id is mandatory only for resident credentials.
if not resident_key :
cred . user_id = None
# We prefer to show rp_id, so we don't need rp_name.
cred . rp_name = None
# We prefer to show user_name, so we don't need user_display_name if we have user_name.
if cred . user_name :
cred . user_display_name = None
2019-10-01 14:01:56 +00:00
# Check data types.
if (
not cred . check_data_types ( )
or not isinstance ( user . get ( " icon " , " " ) , str )
or not isinstance ( rp . get ( " icon " , " " ) , str )
2020-03-05 17:32:29 +00:00
or not isinstance ( client_data_hash , bytes )
2019-10-01 14:01:56 +00:00
or not isinstance ( resident_key , bool )
or not isinstance ( user_verification , bool )
) :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_CBOR_UNEXPECTED_TYPE )
2019-10-01 14:01:56 +00:00
# Check options.
if " up " in options :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_INVALID_OPTION )
2019-10-01 14:01:56 +00:00
if resident_key and not _ALLOW_RESIDENT_CREDENTIALS :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_UNSUPPORTED_OPTION )
2019-10-01 14:01:56 +00:00
if user_verification and not config . has_pin ( ) :
# User verification requested, but PIN is not enabled.
2022-09-19 10:55:46 +00:00
return Fido2ConfirmNoPin ( cid , dialog_mgr . iface )
2019-10-01 14:01:56 +00:00
# Check that the pinAuth parameter is absent. Client PIN is not supported.
if _MAKECRED_CMD_PIN_AUTH in param :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_PIN_AUTH_INVALID )
2019-10-01 14:01:56 +00:00
# Ask user to confirm registration.
2020-05-01 13:03:02 +00:00
return Fido2ConfirmMakeCredential (
2022-09-19 10:55:46 +00:00
cid ,
2020-05-01 13:03:02 +00:00
dialog_mgr . iface ,
client_data_hash ,
cred ,
resident_key ,
user_verification ,
2019-10-01 14:01:56 +00:00
)
2022-09-19 10:55:46 +00:00
def _cbor_make_credential_sign (
2020-02-27 17:58:21 +00:00
client_data_hash : bytes , cred : Fido2Credential , user_verification : bool
) - > bytes :
2022-09-19 10:55:46 +00:00
from . import knownapps
2019-10-01 14:01:56 +00:00
flags = _AUTH_FLAG_UP | _AUTH_FLAG_AT
if user_verification :
flags | = _AUTH_FLAG_UV
# Encode the authenticator data (Credential ID, its public key and extensions).
att_cred_data = (
2020-02-27 17:58:21 +00:00
_AAGUID + len ( cred . id ) . to_bytes ( 2 , " big " ) + cred . id + cred . public_key ( )
2019-10-01 14:01:56 +00:00
)
extensions = b " "
if cred . hmac_secret :
extensions = cbor . encode ( { " hmac-secret " : True } )
flags | = _AUTH_FLAG_ED
ctr = cred . next_signature_counter ( )
authenticator_data = (
cred . rp_id_hash
+ bytes ( [ flags ] )
+ ctr . to_bytes ( 4 , " big " )
+ att_cred_data
+ extensions
)
2022-09-19 10:55:46 +00:00
# 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 :
2020-02-27 17:58:21 +00:00
sig = cred . sign ( ( authenticator_data , client_data_hash ) )
attestation_statement = { " alg " : cred . algorithm , " sig " : sig }
2019-12-11 10:47:02 +00:00
else :
2020-02-27 17:58:21 +00:00
sig = basic_attestation_sign ( ( authenticator_data , client_data_hash ) )
attestation_statement = {
" alg " : common . COSE_ALG_ES256 ,
" sig " : sig ,
2020-04-02 14:21:19 +00:00
" x5c " : [ _FIDO_ATT_CERT ] ,
2020-02-27 17:58:21 +00:00
}
2019-10-01 14:01:56 +00:00
# Encode the authenticatorMakeCredential response data.
return cbor . encode (
{
_MAKECRED_RESP_FMT : " packed " ,
_MAKECRED_RESP_AUTH_DATA : authenticator_data ,
_MAKECRED_RESP_ATT_STMT : attestation_statement ,
}
)
2022-09-19 10:55:46 +00:00
def _cbor_get_assertion ( req : Cmd , dialog_mgr : DialogManager ) - > Cmd | None :
2020-05-01 13:03:02 +00:00
if config . is_unlocked ( ) :
2022-09-19 10:55:46 +00:00
resp = _cbor_get_assertion_process ( req , dialog_mgr )
2020-05-01 13:03:02 +00:00
else :
2022-09-19 10:55:46 +00:00
resp = Fido2Unlock ( _cbor_get_assertion_process , req , dialog_mgr )
2020-05-01 13:03:02 +00:00
if isinstance ( resp , State ) :
if dialog_mgr . set_state ( resp ) :
return None
else :
return cmd_error ( req . cid , _ERR_CHANNEL_BUSY )
else :
return resp
2022-09-19 10:55:46 +00:00
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 ( ) :
2019-10-01 14:01:56 +00:00
if __debug__ :
log . warning ( __name__ , " not initialized " )
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_OTHER )
2019-10-01 14:01:56 +00:00
try :
2021-03-22 20:14:25 +00:00
param = cbor . decode ( req . data , offset = 1 )
2019-10-01 14:01:56 +00:00
rp_id = param [ _GETASSERT_CMD_RP_ID ]
rp_id_hash = hashlib . sha256 ( rp_id ) . digest ( )
allow_list = param . get ( _GETASSERT_CMD_ALLOW_LIST , [ ] )
2021-12-08 09:10:58 +00:00
cred_list : list [ Credential ]
2019-10-01 14:01:56 +00:00
if allow_list :
# Get all credentials from the allow list that belong to this authenticator.
2020-03-15 19:56:03 +00:00
allowed_creds = credentials_from_descriptor_list ( allow_list , rp_id_hash )
2022-09-19 10:55:46 +00:00
cred_list = _distinguishable_cred_list ( allowed_creds )
2019-10-01 14:01:56 +00:00
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 :
2019-10-25 15:43:55 +00:00
cred_list = list ( find_by_rp_id_hash ( rp_id_hash ) )
2019-10-01 14:01:56 +00:00
else :
cred_list = [ ]
resident = True
# Sort credentials by time of creation.
cred_list . sort ( )
# Check that the pinAuth parameter is absent. Client PIN is not supported.
if _GETASSERT_CMD_PIN_AUTH in param :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_PIN_AUTH_INVALID )
2019-10-01 14:01:56 +00:00
# Get options.
options = param . get ( _GETASSERT_CMD_OPTIONS , { } )
user_presence = options . get ( " up " , True )
user_verification = options . get ( " uv " , False )
# Get supported extensions.
hmac_secret = param . get ( _GETASSERT_CMD_EXTENSIONS , { } ) . get ( " hmac-secret " , None )
client_data_hash = param [ _GETASSERT_CMD_CLIENT_DATA_HASH ]
except TypeError :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_CBOR_UNEXPECTED_TYPE )
2019-10-01 14:01:56 +00:00
except KeyError :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_MISSING_PARAMETER )
2019-10-01 14:01:56 +00:00
except Exception :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_INVALID_CBOR )
2019-10-01 14:01:56 +00:00
# Check data types.
if (
not isinstance ( hmac_secret , ( dict , type ( None ) ) )
2020-03-05 17:32:29 +00:00
or not isinstance ( client_data_hash , bytes )
2019-10-01 14:01:56 +00:00
or not isinstance ( user_presence , bool )
or not isinstance ( user_verification , bool )
) :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_CBOR_UNEXPECTED_TYPE )
2019-10-01 14:01:56 +00:00
# Check options.
if " rk " in options :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_INVALID_OPTION )
2019-10-01 14:01:56 +00:00
if user_verification and not config . has_pin ( ) :
# User verification requested, but PIN is not enabled.
2022-09-19 10:55:46 +00:00
return Fido2ConfirmNoPin ( cid , dialog_mgr . iface )
2019-10-01 14:01:56 +00:00
2023-02-23 10:06:59 +00:00
global _last_auth_valid
_last_auth_valid = bool ( cred_list )
2019-10-01 14:01:56 +00:00
if not cred_list :
# No credentials. This authenticator is not registered.
if user_presence :
2022-09-19 10:55:46 +00:00
return Fido2ConfirmNoCredentials ( cid , dialog_mgr . iface , rp_id )
2019-10-01 14:01:56 +00:00
else :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_NO_CREDENTIALS )
2019-10-01 14:01:56 +00:00
elif not user_presence and not user_verification :
# Silent authentication.
try :
response_data = cbor_get_assertion_sign (
client_data_hash ,
rp_id_hash ,
cred_list [ 0 ] ,
hmac_secret ,
resident ,
user_presence ,
user_verification ,
)
2022-09-19 10:55:46 +00:00
return Cmd ( cid , _CMD_CBOR , bytes ( [ _ERR_NONE ] ) + response_data )
2020-04-09 20:17:19 +00:00
except Exception as e :
# Firmware error.
if __debug__ :
log . exception ( __name__ , e )
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_OTHER )
2019-10-01 14:01:56 +00:00
else :
# Ask user to confirm one of the credentials.
2020-05-01 13:03:02 +00:00
return Fido2ConfirmGetAssertion (
2022-09-19 10:55:46 +00:00
cid ,
2020-05-01 13:03:02 +00:00
dialog_mgr . iface ,
client_data_hash ,
cred_list ,
hmac_secret ,
resident ,
user_verification ,
2019-10-01 14:01:56 +00:00
)
2022-09-19 10:55:46 +00:00
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
2019-10-01 14:01:56 +00:00
key_agreement = hmac_secret [ 1 ] # The public key of platform key agreement key.
2020-02-27 17:58:21 +00:00
# NOTE: We should check the key_agreement[COSE_KEY_ALG] here, but to avoid compatibility issues we don't,
2019-10-01 14:01:56 +00:00
# because there is currently no valid value which describes the actual key agreement algorithm.
if (
2020-02-27 17:58:21 +00:00
key_agreement [ common . COSE_KEY_KTY ] != common . COSE_KEYTYPE_EC2
or key_agreement [ common . COSE_KEY_CRV ] != common . COSE_CURVE_P256
2019-10-01 14:01:56 +00:00
) :
return None
2020-02-27 17:58:21 +00:00
x = key_agreement [ common . COSE_KEY_X ]
y = key_agreement [ common . COSE_KEY_Y ]
2019-10-01 14:01:56 +00:00
salt_enc = hmac_secret [ 2 ] # The encrypted salt.
salt_auth = hmac_secret [ 3 ] # The HMAC of the encrypted salt.
if (
len ( x ) != 32
or len ( y ) != 32
or len ( salt_auth ) != 16
or len ( salt_enc ) not in ( 32 , 64 )
) :
raise CborError ( _ERR_INVALID_LEN )
# Compute the ECDH shared secret.
2021-04-08 10:43:28 +00:00
ecdh_result = nist256p1 . multiply ( KEY_AGREEMENT_PRIVKEY , b " \04 " + x + y )
2019-10-01 14:01:56 +00:00
shared_secret = hashlib . sha256 ( ecdh_result [ 1 : 33 ] ) . digest ( )
# Check the authentication tag and decrypt the salt.
2020-10-12 14:33:13 +00:00
tag = hmac ( hmac . SHA256 , shared_secret , salt_enc ) . digest ( ) [ : 16 ]
2019-10-01 14:01:56 +00:00
if not utils . consteq ( tag , salt_auth ) :
raise CborError ( _ERR_EXTENSION_FIRST )
salt = aes ( aes . CBC , shared_secret ) . decrypt ( salt_enc )
# Get cred_random - a constant symmetric key associated with the credential.
cred_random = cred . hmac_secret_key ( )
if cred_random is None :
# The credential does not have the hmac-secret extension enabled.
return None
# Compute the hmac-secret output.
2020-10-12 14:33:13 +00:00
output = hmac ( hmac . SHA256 , cred_random , salt [ : 32 ] ) . digest ( )
2019-10-01 14:01:56 +00:00
if len ( salt ) == 64 :
2020-10-12 14:33:13 +00:00
output + = hmac ( hmac . SHA256 , cred_random , salt [ 32 : ] ) . digest ( )
2019-10-01 14:01:56 +00:00
# Encrypt the hmac-secret output.
return aes ( aes . CBC , shared_secret ) . encrypt ( output )
def cbor_get_assertion_sign (
client_data_hash : bytes ,
rp_id_hash : bytes ,
cred : Credential ,
2021-03-18 09:48:50 +00:00
hmac_secret : dict | None ,
2019-10-01 14:01:56 +00:00
resident : bool ,
user_presence : bool ,
user_verification : bool ,
) - > bytes :
# Process extensions
extensions = { }
# Spec deviation: Do not reveal hmac-secret during silent authentication.
if hmac_secret and user_presence :
2022-09-19 10:55:46 +00:00
encrypted_output = _cbor_get_assertion_hmac_secret ( cred , hmac_secret )
2019-10-01 14:01:56 +00:00
if encrypted_output is not None :
extensions [ " hmac-secret " ] = encrypted_output
# Encode the authenticator data.
flags = 0
if user_presence :
flags | = _AUTH_FLAG_UP
if user_verification :
flags | = _AUTH_FLAG_UV
encoded_extensions = b " "
if extensions :
flags | = _AUTH_FLAG_ED
encoded_extensions = cbor . encode ( extensions )
ctr = cred . next_signature_counter ( )
authenticator_data = (
rp_id_hash + bytes ( [ flags ] ) + ctr . to_bytes ( 4 , " big " ) + encoded_extensions
)
# Sign the authenticator data and the client data hash.
if user_presence :
2020-02-27 17:58:21 +00:00
sig = cred . sign ( ( authenticator_data , client_data_hash ) )
2019-10-01 14:01:56 +00:00
else :
2020-02-27 17:58:21 +00:00
# Spec deviation: Use a bogus signature during silent authentication.
sig = cred . bogus_signature ( )
2019-10-01 14:01:56 +00:00
# Encode the authenticatorGetAssertion response data.
response = {
_GETASSERT_RESP_CREDENTIAL : { " type " : " public-key " , " id " : cred . id } ,
_GETASSERT_RESP_AUTH_DATA : authenticator_data ,
_GETASSERT_RESP_SIGNATURE : sig ,
}
if resident and user_presence and cred . user_id is not None :
response [ _GETASSERT_RESP_USER ] = { " id " : cred . user_id }
return cbor . encode ( response )
2022-09-19 10:55:46 +00:00
def _cbor_get_info ( req : Cmd ) - > Cmd :
from . credential import CRED_ID_MAX_LENGTH
2019-12-02 19:18:46 +00:00
# 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.
2019-10-01 14:01:56 +00:00
response_data = {
_GETINFO_RESP_VERSIONS : [ " U2F_V2 " , " FIDO_2_0 " ] ,
_GETINFO_RESP_EXTENSIONS : [ " hmac-secret " ] ,
_GETINFO_RESP_AAGUID : _AAGUID ,
_GETINFO_RESP_OPTIONS : {
2023-02-22 22:53:38 +00:00
" rk " : bool ( _ALLOW_RESIDENT_CREDENTIALS ) ,
2019-10-01 14:01:56 +00:00
" up " : True ,
" uv " : True ,
} ,
_GETINFO_RESP_PIN_PROTOCOLS : [ 1 ] ,
2020-03-14 19:20:59 +00:00
_GETINFO_RESP_MAX_CRED_COUNT_IN_LIST : _MAX_CRED_COUNT_IN_LIST ,
_GETINFO_RESP_MAX_CRED_ID_LEN : CRED_ID_MAX_LENGTH ,
2019-10-01 14:01:56 +00:00
}
return Cmd ( req . cid , _CMD_CBOR , bytes ( [ _ERR_NONE ] ) + cbor . encode ( response_data ) )
2022-09-19 10:55:46 +00:00
def _cbor_client_pin ( req : Cmd ) - > Cmd :
from storage . fido2 import KEY_AGREEMENT_PUBKEY
cid = req . cid # local_cache_attribute
2019-10-01 14:01:56 +00:00
try :
2021-03-22 20:14:25 +00:00
param = cbor . decode ( req . data , offset = 1 )
2019-10-01 14:01:56 +00:00
pin_protocol = param [ _CLIENTPIN_CMD_PIN_PROTOCOL ]
subcommand = param [ _CLIENTPIN_CMD_SUBCOMMAND ]
except Exception :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_INVALID_CBOR )
2019-10-01 14:01:56 +00:00
if pin_protocol != 1 :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_PIN_AUTH_INVALID )
2019-10-01 14:01:56 +00:00
# We only support the get key agreement command which is required for the hmac-secret extension.
if subcommand != _CLIENTPIN_SUBCMD_GET_KEY_AGREEMENT :
2022-09-19 10:55:46 +00:00
return cbor_error ( cid , _ERR_UNSUPPORTED_OPTION )
2019-10-01 14:01:56 +00:00
# Encode the public key of the authenticator key agreement key.
2020-02-27 17:58:21 +00:00
# NOTE: There is currently no valid value for COSE_KEY_ALG which describes the actual
# key agreement algorithm as specified, but COSE_ALG_ECDH_ES_HKDF_256 is allegedly
2019-10-01 14:01:56 +00:00
# recommended by the latest draft of the CTAP2 spec.
response_data = {
_CLIENTPIN_RESP_KEY_AGREEMENT : {
2020-02-27 17:58:21 +00:00
common . COSE_KEY_ALG : common . COSE_ALG_ECDH_ES_HKDF_256 ,
common . COSE_KEY_KTY : common . COSE_KEYTYPE_EC2 ,
common . COSE_KEY_CRV : common . COSE_CURVE_P256 ,
2021-04-08 10:43:28 +00:00
common . COSE_KEY_X : KEY_AGREEMENT_PUBKEY [ 1 : 33 ] ,
common . COSE_KEY_Y : KEY_AGREEMENT_PUBKEY [ 33 : ] ,
2019-10-01 14:01:56 +00:00
}
}
2022-09-19 10:55:46 +00:00
return Cmd ( cid , _CMD_CBOR , bytes ( [ _ERR_NONE ] ) + cbor . encode ( response_data ) )
2019-10-01 14:01:56 +00:00
2022-09-19 10:55:46 +00:00
def _cbor_reset ( req : Cmd , dialog_mgr : DialogManager ) - > Cmd | None :
if not storage_device . is_initialized ( ) :
2019-10-01 14:01:56 +00:00
if __debug__ :
log . warning ( __name__ , " not initialized " )
2020-04-09 20:17:19 +00:00
# Return success, because the authenticator is already in factory default state.
return cbor_error ( req . cid , _ERR_NONE )
2019-10-01 14:01:56 +00:00
if not dialog_mgr . set_state ( Fido2ConfirmReset ( req . cid , dialog_mgr . iface ) ) :
return cmd_error ( req . cid , _ERR_CHANNEL_BUSY )
return None
def cmd_keepalive ( cid : int , status : int ) - > Cmd :
return Cmd ( cid , _CMD_KEEPALIVE , bytes ( [ status ] ) )