1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-14 03:30:02 +00:00

Merge pull request #736 from trezor/andrewkozlik/731

U2F UX improvements
This commit is contained in:
Andrew Kozlik 2019-12-04 13:11:38 +01:00 committed by GitHub
commit 20cda137b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -110,13 +110,18 @@ _CLIENTPIN_SUBCMD_GET_KEY_AGREEMENT = const(0x02)
_CLIENTPIN_RESP_KEY_AGREEMENT = const(0x01) # COSE_Key, optional
# status codes for the keepalive cmd
_KEEPALIVE_STATUS_NONE = const(0x00)
_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
_CTAP_HID_TIMEOUT_MS = const(500)
_U2F_CONFIRM_TIMEOUT_MS = const(10 * 1000)
_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
_FIDO2_CONFIRM_TIMEOUT_MS = const(60 * 1000)
_POPUP_TIMEOUT_MS = const(4 * 1000)
@ -175,7 +180,9 @@ _U2FHID_IF_VERSION = const(2) # interface version
_U2F_REGISTER_ID = const(0x05) # version 2 registration identifier
_U2F_ATT_PRIV_KEY = b"q&\xac+\xf6D\xdca\x86\xad\x83\xef\x1f\xcd\xf1*W\xb5\xcf\xa2\x00\x0b\x8a\xd0'\xe9V\xe8T\xc5\n\x8b"
_U2F_ATT_CERT = b"0\x82\x01\x180\x81\xc0\x02\t\x00\xb1\xd9\x8fBdr\xd3,0\n\x06\x08*\x86H\xce=\x04\x03\x020\x151\x130\x11\x06\x03U\x04\x03\x0c\nTrezor U2F0\x1e\x17\r160429133153Z\x17\r260427133153Z0\x151\x130\x11\x06\x03U\x04\x03\x0c\nTrezor U2F0Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x04\xd9\x18\xbd\xfa\x8aT\xac\x92\xe9\r\xa9\x1f\xcaz\xa2dT\xc0\xd1s61M\xde\x83\xa5K\x86\xb5\xdfN\xf0Re\x9a\x1do\xfc\xb7F\x7f\x1a\xcd\xdb\x8a3\x08\x0b^\xed\x91\x89\x13\xf4C\xa5&\x1b\xc7{h`o\xc10\n\x06\x08*\x86H\xce=\x04\x03\x02\x03G\x000D\x02 $\x1e\x81\xff\xd2\xe5\xe6\x156\x94\xc3U.\x8f\xeb\xd7\x1e\x895\x92\x1c\xb4\x83ACq\x1cv\xea\xee\xf3\x95\x02 _\x80\xeb\x10\xf2\\\xcc9\x8b<\xa8\xa9\xad\xa4\x02\x7f\x93\x13 w\xb7\xab\xcewFZ'\xf5=3\xa1\x1d"
_BOGUS_APPID = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
_BOGUS_APPID_CHROME = b"A" * 32
_BOGUS_APPID_FIREFOX = b"\0" * 32
_BOGUS_APPIDS = (_BOGUS_APPID_CHROME, _BOGUS_APPID_FIREFOX)
_AAGUID = b"\xd6\xd0\xbd\xc3b\xee\xc4\xdb\xde\x8dzenJD\x87" # First 16 bytes of SHA-256("TREZOR 2")
_BOGUS_PRIV_KEY = b"\xAA" * 32
@ -218,6 +225,9 @@ _USE_BASIC_ATTESTATION = False
# 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
# The CID of the last successful U2F_AUTHENTICATE check-only request.
_last_good_auth_check_cid = 0
class CborError(Exception):
def __init__(self, code: int):
@ -598,6 +608,10 @@ class U2fState(State, ConfirmInfo):
self._req_data = req_data
self.load_icon(self._cred.rp_id_hash)
def keepalive_status(self) -> int:
# Run the keepalive loop to check for timeout, but do not send any keepalive messages.
return _KEEPALIVE_STATUS_NONE
def timeout_ms(self) -> int:
return _U2F_CONFIRM_TIMEOUT_MS
@ -615,13 +629,20 @@ class U2fConfirmRegister(U2fState):
super().__init__(cid, iface, req_data, cred)
async def confirm_dialog(self) -> bool:
if self._cred.rp_id_hash == _BOGUS_APPID:
if self._cred.rp_id_hash in _BOGUS_APPIDS:
text = Text("U2F", ui.ICON_WRONG, ui.RED)
text.bold("Not registered.")
text.br_half()
text.normal(
"Another U2F device", "was used to register", "in this application."
)
if self.cid == _last_good_auth_check_cid:
text.bold("Already registered.")
text.br_half()
text.normal(
"This device is already", "registered with this", "application."
)
else:
text.bold("Not registered.")
text.br_half()
text.normal(
"This device is not", "registered with this", "application."
)
return await Popup(text, _POPUP_TIMEOUT_MS)
else:
content = ConfirmContent(self)
@ -945,11 +966,12 @@ class DialogManager:
async def keepalive_loop(self) -> None:
try:
if not isinstance(self.state, Fido2State):
if not isinstance(self.state, (U2fState, Fido2State)):
return
while utime.ticks_ms() < self.deadline:
cmd = cmd_keepalive(self.state.cid, self.state.keepalive_status())
await send_cmd(cmd, self.iface)
if self.state.keepalive_status() != _KEEPALIVE_STATUS_NONE:
cmd = cmd_keepalive(self.state.cid, self.state.keepalive_status())
await send_cmd(cmd, self.iface)
await loop.sleep(_KEEPALIVE_INTERVAL_MS * 1000)
finally:
self.keepalive = None
@ -1128,11 +1150,18 @@ def msg_register(req: Msg, dialog_mgr: DialogManager) -> Cmd:
dialog_mgr.reset_timeout()
# wait for a button or continue
if dialog_mgr.result != _RESULT_CONFIRM:
if dialog_mgr.result == _RESULT_NONE:
if __debug__:
log.info(__name__, "waiting for button")
return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED)
if dialog_mgr.result != _RESULT_CONFIRM:
if __debug__:
log.info(__name__, "request declined")
# There is no standard way to decline a U2F request, but responding with ERR_CHANNEL_BUSY
# doesn't seem to violate the protocol and at least stops Chrome from polling.
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
# sign the registration challenge and return
if __debug__:
log.info(__name__, "signing register")
@ -1199,6 +1228,8 @@ def msg_authenticate(req: Msg, dialog_mgr: DialogManager) -> Cmd:
if req.p1 == _AUTH_CHECK_ONLY:
if __debug__:
log.info(__name__, "_AUTH_CHECK_ONLY")
global _last_good_auth_check_cid
_last_good_auth_check_cid = req.cid
return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED)
# from now on, only _AUTH_ENFORCE is supported
@ -1215,11 +1246,18 @@ def msg_authenticate(req: Msg, dialog_mgr: DialogManager) -> Cmd:
dialog_mgr.reset_timeout()
# wait for a button or continue
if dialog_mgr.result != _RESULT_CONFIRM:
if dialog_mgr.result == _RESULT_NONE:
if __debug__:
log.info(__name__, "waiting for button")
return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED)
if dialog_mgr.result != _RESULT_CONFIRM:
if __debug__:
log.info(__name__, "request declined")
# There is no standard way to decline a U2F request, but responding with ERR_CHANNEL_BUSY
# doesn't seem to violate the protocol and at least stops Chrome from polling.
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
# sign the authentication challenge and return
if __debug__:
log.info(__name__, "signing authentication")
@ -1400,7 +1438,9 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
# User verification requested, but PIN is not enabled.
state_set = dialog_mgr.set_state(Fido2ConfirmNoPin(req.cid, dialog_mgr.iface))
if state_set:
return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION)
# We should return _ERR_UNSUPPORTED_OPTION, but since we claim in GetInfo that the PIN
# is set even when it's not, it makes more sense to return _ERR_OPERATION_DENIED.
return cbor_error(req.cid, _ERR_OPERATION_DENIED)
else:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
@ -1557,7 +1597,9 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
# User verification requested, but PIN is not enabled.
state_set = dialog_mgr.set_state(Fido2ConfirmNoPin(req.cid, dialog_mgr.iface))
if state_set:
return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION)
# We should return _ERR_UNSUPPORTED_OPTION, but since we claim in GetInfo that the PIN
# is set even when it's not, it makes more sense to return _ERR_OPERATION_DENIED.
return cbor_error(req.cid, _ERR_OPERATION_DENIED)
else:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
@ -1715,6 +1757,8 @@ def cbor_get_assertion_sign(
def cbor_get_info(req: Cmd) -> Cmd:
# Note: We claim that the PIN is set even when it's not, because otherwise
# login.live.com shows an error, but doesn't instruct the user to set a PIN.
response_data = {
_GETINFO_RESP_VERSIONS: ["U2F_V2", "FIDO_2_0"],
_GETINFO_RESP_EXTENSIONS: ["hmac-secret"],