def dispatch(report, open_callback, close_callback, unknown_callback): ''' Dispatches payloads of reports adhering to one of the wire codecs. ''' if codec_v1.detect(report): marker, session_id, report_data = codec_v1.parse_report(report) else: marker, session_id, report_data = codec_v2.parse_report(report) if marker == codec_v2.REP_MARKER_OPEN: log.debug(__name__, 'request for new session') open_callback() return elif marker == codec_v2.REP_MARKER_CLOSE: log.debug(__name__, 'request for closing session %x', session_id) close_callback(session_id) return if session_id not in readers: log.warning(__name__, 'report on unknown session %x', session_id) unknown_callback(session_id, report_data) return log.debug(__name__, 'report on session %x', session_id) reader = readers[session_id] try: reader.send(report_data) except StopIteration: readers.pop(session_id) except Exception as e: log.exception(__name__, e)
def msg_authenticate_genkey(app_id: bytes, keyhandle: bytes): from apps.common import seed # unpack the keypath from the first half of keyhandle keybuf = keyhandle[:32] keypath = ustruct.unpack('>8L', keybuf) # check high bit for hardened keys for i in keypath: if not i & 0x80000000: log.warning(__name__, 'invalid key path') return None # derive the signing key nodepath = [_U2F_KEY_PATH] + list(keypath) node = seed.get_root_without_passphrase('nist256p1') node.derive_path(nodepath) # second half of keyhandle is a hmac of app_id and keypath keybase = hmac.Hmac(node.private_key(), app_id, hashlib.sha256) keybase.update(keybuf) keybase = keybase.digest() # verify the hmac if keybase != keyhandle[32:]: log.warning(__name__, 'invalid key handle') return None return node
def msg_authenticate_genkey(app_id: bytes, keyhandle: bytes, pathformat: str): from apps.common import seed # unpack the keypath from the first half of keyhandle keybuf = keyhandle[:32] keypath = ustruct.unpack(pathformat, keybuf) # check high bit for hardened keys for i in keypath: if not i & HARDENED: if __debug__: log.warning(__name__, "invalid key path") return None # derive the signing key nodepath = [_U2F_KEY_PATH] + list(keypath) node = seed.derive_node_without_passphrase(nodepath, "nist256p1") # second half of keyhandle is a hmac of app_id and keypath keybase = hmac.Hmac(node.private_key(), app_id, hashlib.sha256) keybase.update(keybuf) keybase = keybase.digest() # verify the hmac if keybase != keyhandle[32:]: if __debug__: log.warning(__name__, "invalid key handle") return None return node
def _node_from_key_handle(rp_id_hash: bytes, keyhandle: bytes, pathformat: str) -> bip32.HDNode | None: # unpack the keypath from the first half of keyhandle keypath = keyhandle[:32] path = ustruct.unpack(pathformat, keypath) # check high bit for hardened keys for i in path: if not i & HARDENED: if __debug__: log.warning(__name__, "invalid key path") return None # derive the signing key nodepath = [_U2F_KEY_PATH] + list(path) node = seed.derive_node_without_passphrase(nodepath, "nist256p1") # second half of keyhandle is a hmac of rp_id_hash and keypath mac = hmac(hmac.SHA256, node.private_key(), rp_id_hash) mac.update(keypath) # verify the hmac if not utils.consteq(mac.digest(), keyhandle[32:]): if __debug__: log.warning(__name__, "invalid key handle") return None return node
async def dispatch_DebugLinkDecision( ctx: wire.Context, msg: DebugLinkDecision ) -> None: if debuglink_decision_chan.putters: log.warning(__name__, "DebugLinkDecision queue is not empty") debuglink_decision_chan.publish(msg)
def _finalize_default(task: loop.Task, value: Any) -> None: global default_task if default_task is task: if __debug__: log.debug(__name__, "default closed") default_task = None else: if __debug__: log.warning( __name__, "default task does not match: task=%s, default_task=%s", task, default_task, )
async def dispatch_DebugLinkDecision(ctx: wire.Context, msg: DebugLinkDecision) -> None: if debuglink_decision_chan.putters: log.warning(__name__, "DebugLinkDecision queue is not empty") if msg.x is not None: evt_down = io.TOUCH_START, msg.x, msg.y evt_up = io.TOUCH_END, msg.x, msg.y loop.synthetic_events.append((io.TOUCH, evt_down)) loop.synthetic_events.append((io.TOUCH, evt_up)) else: debuglink_decision_chan.publish(msg) if msg.wait: loop.schedule(return_layout_change(ctx))
def _handle_unexpected(session_id, msg_type, data_len): log.warning(__name__, 'session %x: skip type %d, len %d', session_id, msg_type, data_len) # read the message in full try: while True: yield except EOFError: pass # respond with an unknown message error from trezor.messages.Failure import Failure from trezor.messages.FailureType import UnexpectedMessage failure = Failure(code=UnexpectedMessage, message='Unexpected message') failure = Failure.dumps(failure) sessions.get_codec(session_id).encode(session_id, Failure.MESSAGE_WIRE_TYPE, failure, _write_report)
def msg_register(req: Msg) -> Cmd: global _state global _lastreq from apps.common import storage if not storage.is_initialized(): log.warning(__name__, 'not initialized') return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # check length of input data if len(req.data) != 64: log.warning(__name__, '_SW_WRONG_LENGTH req.data') return msg_error(req.cid, _SW_WRONG_LENGTH) # parse challenge and app_id chal = req.data[:32] app_id = req.data[32:] # check equality with last request if _lastreq is None or _lastreq.__dict__ != req.__dict__: if _state is not None: _state.kill() _state = None _lastreq = req # wait for a button or continue if _state is not None and utime.ticks_ms() > _state.deadline_ms: _state.kill() _state = None if _state is None: _state = ConfirmState(_CONFIRM_REGISTER, app_id) _state.fork() if _state.confirmed is None: log.info(__name__, 'waiting for button') return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) _state = None buf = msg_register_sign(chal, app_id) return Cmd(req.cid, _CMD_MSG, buf)
async def dispatch_DebugLinkDecision(ctx: wire.Context, msg: DebugLinkDecision) -> None: from trezor import io if debuglink_decision_chan.putters: log.warning(__name__, "DebugLinkDecision queue is not empty") if msg.x is not None and msg.y is not None: evt_down = io.TOUCH_START, msg.x, msg.y evt_up = io.TOUCH_END, msg.x, msg.y loop.synthetic_events.append((io.TOUCH, evt_down)) if msg.hold_ms is not None: loop.schedule(touch_hold(msg.x, msg.y, msg.hold_ms)) else: loop.synthetic_events.append((io.TOUCH, evt_up)) else: debuglink_decision_chan.publish(msg) if msg.wait: storage.layout_watcher = LAYOUT_WATCHER_LAYOUT loop.schedule(return_layout_change())
def msg_register(req: Msg, state: ConfirmState) -> Cmd: from apps.common import storage if not storage.is_initialized(): if __debug__: log.warning(__name__, "not initialized") return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # check length of input data if len(req.data) != 64: if __debug__: log.warning(__name__, "_SW_WRONG_LENGTH req.data") return msg_error(req.cid, _SW_WRONG_LENGTH) # parse challenge and app_id chal = req.data[:32] app_id = req.data[32:] # check equality with last request if not state.compare(_CONFIRM_REGISTER, req.data): if not state.setup(_CONFIRM_REGISTER, req.data, app_id): return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) state.keepalive() # wait for a button or continue if not state.confirmed: if __debug__: log.info(__name__, "waiting for button") return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # sign the registration challenge and return if __debug__: log.info(__name__, "signing register") buf = msg_register_sign(chal, app_id) state.reset() return Cmd(req.cid, _CMD_MSG, buf)
async def session_handler(iface: WireInterface, sid: int) -> None: reader = None ctx = Context(iface, sid) while True: try: # wait for new message, if needed, and find handler if not reader: reader = ctx.make_reader() await reader.aopen() try: handler, args = workflow_handlers[reader.type] except KeyError: handler, args = unexpected_msg, () m = utils.unimport_begin() w = handler(ctx, reader, *args) try: workflow.onstart(w) await w finally: workflow.onclose(w) utils.unimport_end(m) except UnexpectedMessageError as exc: # retry with opened reader from the exception reader = exc.reader continue except Error as exc: # we log wire.Error as warning, not as exception if __debug__: log.warning(__name__, "failure: %s", exc.message) except Exception as exc: # sessions are never closed by raised exceptions if __debug__: log.exception(__name__, exc) # read new message in next iteration reader = None
def dispatch_cmd(req: Cmd, state: ConfirmState) -> Cmd: if req.cmd == _CMD_MSG: m = req.to_msg() if m.cla != 0: if __debug__: log.warning(__name__, "_SW_CLA_NOT_SUPPORTED") return msg_error(req.cid, _SW_CLA_NOT_SUPPORTED) if m.lc + _APDU_DATA > len(req.data): if __debug__: log.warning(__name__, "_SW_WRONG_LENGTH") return msg_error(req.cid, _SW_WRONG_LENGTH) if m.ins == _MSG_REGISTER: if __debug__: log.debug(__name__, "_MSG_REGISTER") return msg_register(m, state) elif m.ins == _MSG_AUTHENTICATE: if __debug__: log.debug(__name__, "_MSG_AUTHENTICATE") return msg_authenticate(m, state) elif m.ins == _MSG_VERSION: if __debug__: log.debug(__name__, "_MSG_VERSION") return msg_version(m) else: if __debug__: log.warning(__name__, "_SW_INS_NOT_SUPPORTED: %d", m.ins) return msg_error(req.cid, _SW_INS_NOT_SUPPORTED) elif req.cmd == _CMD_INIT: if __debug__: log.debug(__name__, "_CMD_INIT") return cmd_init(req) elif req.cmd == _CMD_PING: if __debug__: log.debug(__name__, "_CMD_PING") return req elif req.cmd == _CMD_WINK: if __debug__: log.debug(__name__, "_CMD_WINK") return req else: if __debug__: log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd) return cmd_error(req.cid, _ERR_INVALID_CMD)
async def read_cmd(iface: io.HID) -> Cmd: desc_init = frame_init() desc_cont = frame_cont() read = loop.wait(iface.iface_num() | io.POLL_READ) buf = await read ifrm = overlay_struct(buf, desc_init) bcnt = ifrm.bcnt data = ifrm.data datalen = len(data) seq = 0 if ifrm.cmd & _TYPE_MASK == _TYPE_CONT: # unexpected cont packet, abort current msg if __debug__: log.warning(__name__, "_TYPE_CONT") return None if datalen < bcnt: databuf = bytearray(bcnt) utils.memcpy(databuf, 0, data, 0, bcnt) data = databuf else: data = data[:bcnt] while datalen < bcnt: buf = await read cfrm = overlay_struct(buf, desc_cont) if cfrm.seq == _CMD_INIT: # _CMD_INIT frame, cancels current channel ifrm = overlay_struct(buf, desc_init) data = ifrm.data[: ifrm.bcnt] break if cfrm.cid != ifrm.cid: # cont frame for a different channel, reply with BUSY and skip if __debug__: log.warning(__name__, "_ERR_CHANNEL_BUSY") await send_cmd(cmd_error(cfrm.cid, _ERR_CHANNEL_BUSY), iface) continue if cfrm.seq != seq: # cont frame for this channel, but incorrect seq number, abort # current msg if __debug__: log.warning(__name__, "_ERR_INVALID_SEQ") await send_cmd(cmd_error(cfrm.cid, _ERR_INVALID_SEQ), iface) return None datalen += utils.memcpy(data, datalen, cfrm.data, 0, bcnt - datalen) seq += 1 return Cmd(ifrm.cid, ifrm.cmd, data)
def dispatch_cmd(req: Cmd) -> Cmd: if req.cmd == _CMD_MSG: m = req.to_msg() if m.cla != 0: log.warning(__name__, '_SW_CLA_NOT_SUPPORTED') return msg_error(req.cid, _SW_CLA_NOT_SUPPORTED) if m.lc + _APDU_DATA > len(req.data): log.warning(__name__, '_SW_WRONG_LENGTH') return msg_error(req.cid, _SW_WRONG_LENGTH) if m.ins == _MSG_REGISTER: log.debug(__name__, '_MSG_REGISTER') return msg_register(m) elif m.ins == _MSG_AUTHENTICATE: log.debug(__name__, '_MSG_AUTHENTICATE') return msg_authenticate(m) elif m.ins == _MSG_VERSION: log.debug(__name__, '_MSG_VERSION') return msg_version(m) else: log.warning(__name__, '_SW_INS_NOT_SUPPORTED: %d', m.ins) return msg_error(req.cid, _SW_INS_NOT_SUPPORTED) elif req.cmd == _CMD_INIT: log.debug(__name__, '_CMD_INIT') return cmd_init(req) elif req.cmd == _CMD_PING: log.debug(__name__, '_CMD_PING') return req elif req.cmd == _CMD_WINK: log.debug(__name__, '_CMD_WINK') return req else: log.warning(__name__, '_ERR_INVALID_CMD: %d', req.cmd) return cmd_error(req.cid, _ERR_INVALID_CMD)
def _session_unknown(session_id, report_data): log.warning(__name__, 'report on unknown session %x', session_id)
def msg_authenticate(req: Msg, state: ConfirmState) -> Cmd: from apps.common import storage if not storage.is_initialized(): if __debug__: log.warning(__name__, "not initialized") return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # we need at least keyHandleLen if len(req.data) <= _REQ_CMD_AUTHENTICATE_KHLEN: if __debug__: log.warning(__name__, "_SW_WRONG_LENGTH req.data") return msg_error(req.cid, _SW_WRONG_LENGTH) # check keyHandleLen khlen = req.data[_REQ_CMD_AUTHENTICATE_KHLEN] if khlen != 64: if __debug__: log.warning(__name__, "_SW_WRONG_LENGTH khlen") return msg_error(req.cid, _SW_WRONG_LENGTH) auth = overlay_struct(req.data, req_cmd_authenticate(khlen)) # check the keyHandle and generate the signing key node = msg_authenticate_genkey(auth.appId, auth.keyHandle, "<8L") if node is None: # prior to firmware version 2.0.8, keypath was serialized in a # big-endian manner, instead of little endian, like in trezor-mcu. # try to parse it as big-endian now and check the HMAC. node = msg_authenticate_genkey(auth.appId, auth.keyHandle, ">8L") if node is None: # specific error logged in msg_authenticate_genkey return msg_error(req.cid, _SW_WRONG_DATA) # if _AUTH_CHECK_ONLY is requested, return, because keyhandle has been checked already if req.p1 == _AUTH_CHECK_ONLY: if __debug__: log.info(__name__, "_AUTH_CHECK_ONLY") return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # from now on, only _AUTH_ENFORCE is supported if req.p1 != _AUTH_ENFORCE: if __debug__: log.info(__name__, "_AUTH_ENFORCE") return msg_error(req.cid, _SW_WRONG_DATA) # check equality with last request if not state.compare(_CONFIRM_AUTHENTICATE, req.data): if not state.setup(_CONFIRM_AUTHENTICATE, req.data, auth.appId): return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) state.keepalive() # wait for a button or continue if not state.confirmed: if __debug__: log.info(__name__, "waiting for button") return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # sign the authentication challenge and return if __debug__: log.info(__name__, "signing authentication") buf = msg_authenticate_sign(auth.chal, auth.appId, node.private_key()) state.reset() return Cmd(req.cid, _CMD_MSG, buf)
def msg_authenticate(req: Msg) -> Cmd: global _state global _lastreq from apps.common import storage if not storage.is_initialized(): log.warning(__name__, 'not initialized') return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # we need at least keyHandleLen if len(req.data) <= _REQ_CMD_AUTHENTICATE_KHLEN: log.warning(__name__, '_SW_WRONG_LENGTH req.data') return msg_error(req.cid, _SW_WRONG_LENGTH) # check keyHandleLen khlen = req.data[_REQ_CMD_AUTHENTICATE_KHLEN] if khlen != 64: log.warning(__name__, '_SW_WRONG_LENGTH khlen') return msg_error(req.cid, _SW_WRONG_LENGTH) auth = overlay_struct(req.data, req_cmd_authenticate(khlen)) # check the keyHandle and generate the signing key node = msg_authenticate_genkey(auth.appId, auth.keyHandle) if node is None: # specific error logged in msg_authenticate_genkey return msg_error(req.cid, _SW_WRONG_DATA) # if _AUTH_CHECK_ONLY is requested, return, because keyhandle has been checked already if req.p1 == _AUTH_CHECK_ONLY: log.info(__name__, '_AUTH_CHECK_ONLY') return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # from now on, only _AUTH_ENFORCE is supported if req.p1 != _AUTH_ENFORCE: log.info(__name__, '_AUTH_ENFORCE') return msg_error(req.cid, _SW_WRONG_DATA) # check equality with last request if _lastreq is None or _lastreq.__dict__ != req.__dict__: if _state is not None: _state.kill() _state = None _lastreq = req # wait for a button or continue if _state is not None and utime.ticks_ms() > _state.deadline_ms: _state.kill() _state = None if _state is None: _state = ConfirmState(_CONFIRM_AUTHENTICATE, auth.appId) _state.fork() if _state.confirmed is None: log.info(__name__, 'waiting for button') return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) _state = None buf = msg_authenticate_sign(auth.chal, auth.appId, node.private_key()) return Cmd(req.cid, _CMD_MSG, buf)
def msg_authenticate(req: Msg, state: ConfirmState) -> Cmd: from apps.common import storage if not storage.is_initialized(): if __debug__: log.warning(__name__, 'not initialized') return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # we need at least keyHandleLen if len(req.data) <= _REQ_CMD_AUTHENTICATE_KHLEN: if __debug__: log.warning(__name__, '_SW_WRONG_LENGTH req.data') return msg_error(req.cid, _SW_WRONG_LENGTH) # check keyHandleLen khlen = req.data[_REQ_CMD_AUTHENTICATE_KHLEN] if khlen != 64: if __debug__: log.warning(__name__, '_SW_WRONG_LENGTH khlen') return msg_error(req.cid, _SW_WRONG_LENGTH) auth = overlay_struct(req.data, req_cmd_authenticate(khlen)) # check the keyHandle and generate the signing key node = msg_authenticate_genkey(auth.appId, auth.keyHandle) if node is None: # specific error logged in msg_authenticate_genkey return msg_error(req.cid, _SW_WRONG_DATA) # if _AUTH_CHECK_ONLY is requested, return, because keyhandle has been checked already if req.p1 == _AUTH_CHECK_ONLY: if __debug__: log.info(__name__, '_AUTH_CHECK_ONLY') return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # from now on, only _AUTH_ENFORCE is supported if req.p1 != _AUTH_ENFORCE: if __debug__: log.info(__name__, '_AUTH_ENFORCE') return msg_error(req.cid, _SW_WRONG_DATA) # check equality with last request if not state.compare(_CONFIRM_AUTHENTICATE, req.data): if not state.setup(_CONFIRM_AUTHENTICATE, req.data, auth.appId): return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) state.keepalive() # wait for a button or continue if not state.confirmed: if __debug__: log.info(__name__, 'waiting for button') return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # sign the authentication challenge and return if __debug__: log.info(__name__, 'signing authentication') buf = msg_authenticate_sign(auth.chal, auth.appId, node.private_key()) state.reset() return Cmd(req.cid, _CMD_MSG, buf)