|
|
|
@ -18,17 +18,17 @@ _TYPE_INIT = const(0x80) # initial frame identifier
|
|
|
|
|
_TYPE_CONT = const(0x00) # continuation frame identifier
|
|
|
|
|
|
|
|
|
|
# types of cmd
|
|
|
|
|
_CMD_PING = const(0x81) # echo data through local processor only
|
|
|
|
|
_CMD_MSG = const(0x83) # send U2F message frame
|
|
|
|
|
_CMD_LOCK = const(0x84) # send lock channel command
|
|
|
|
|
_CMD_INIT = const(0x86) # channel initialization
|
|
|
|
|
_CMD_WINK = const(0x88) # send device identification wink
|
|
|
|
|
_CMD_PING = const(0x81) # echo data through local processor only
|
|
|
|
|
_CMD_MSG = const(0x83) # send U2F message frame
|
|
|
|
|
_CMD_LOCK = const(0x84) # send lock channel command
|
|
|
|
|
_CMD_INIT = const(0x86) # channel initialization
|
|
|
|
|
_CMD_WINK = const(0x88) # send device identification wink
|
|
|
|
|
_CMD_ERROR = const(0xbf) # error response
|
|
|
|
|
|
|
|
|
|
# types for the msg cmd
|
|
|
|
|
_MSG_REGISTER = const(0x01) # registration command
|
|
|
|
|
_MSG_REGISTER = const(0x01) # registration command
|
|
|
|
|
_MSG_AUTHENTICATE = const(0x02) # authenticate/sign command
|
|
|
|
|
_MSG_VERSION = const(0x03) # read version string command
|
|
|
|
|
_MSG_VERSION = const(0x03) # read version string command
|
|
|
|
|
|
|
|
|
|
# hid error codes
|
|
|
|
|
_ERR_NONE = const(0x00) # no error
|
|
|
|
@ -52,8 +52,8 @@ _SW_INS_NOT_SUPPORTED = const(0x6d00)
|
|
|
|
|
_SW_CLA_NOT_SUPPORTED = const(0x6e00)
|
|
|
|
|
|
|
|
|
|
# init response
|
|
|
|
|
_CAPFLAG_WINK = const(0x01) # device supports _CMD_WINK
|
|
|
|
|
_U2FHID_IF_VERSION = const(2) # interface version
|
|
|
|
|
_CAPFLAG_WINK = const(0x01) # device supports _CMD_WINK
|
|
|
|
|
_U2FHID_IF_VERSION = const(2) # interface version
|
|
|
|
|
|
|
|
|
|
# register response
|
|
|
|
|
_U2F_KEY_PATH = const(0x80553246)
|
|
|
|
@ -63,18 +63,18 @@ _U2F_ATT_CERT = b"0\x82\x01\x180\x81\xc0\x02\t\x00\xb1\xd9\x8fBdr\xd3,0\n\x06\x0
|
|
|
|
|
_BOGUS_APPID = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
|
|
|
|
|
|
|
|
|
# authentication control byte
|
|
|
|
|
_AUTH_ENFORCE = const(0x03) # enforce user presence and sign
|
|
|
|
|
_AUTH_ENFORCE = const(0x03) # enforce user presence and sign
|
|
|
|
|
_AUTH_CHECK_ONLY = const(0x07) # check only
|
|
|
|
|
_AUTH_FLAG_TUP = const(0x01) # test of user presence set
|
|
|
|
|
_AUTH_FLAG_TUP = const(0x01) # test of user presence set
|
|
|
|
|
|
|
|
|
|
# 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_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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -85,10 +85,10 @@ def frame_init() -> dict:
|
|
|
|
|
# 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),
|
|
|
|
|
"cid": 0 | uctypes.UINT32,
|
|
|
|
|
"cmd": 4 | uctypes.UINT8,
|
|
|
|
|
"bcnt": 5 | uctypes.UINT16,
|
|
|
|
|
"data": (7 | uctypes.ARRAY, (_HID_RPT_SIZE - 7) | uctypes.UINT8),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -97,9 +97,9 @@ def frame_cont() -> dict:
|
|
|
|
|
# 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),
|
|
|
|
|
"cid": 0 | uctypes.UINT32,
|
|
|
|
|
"seq": 4 | uctypes.UINT8,
|
|
|
|
|
"data": (5 | uctypes.ARRAY, (_HID_RPT_SIZE - 5) | uctypes.UINT8),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -112,13 +112,13 @@ def resp_cmd_init() -> dict:
|
|
|
|
|
# uint8_t versionBuild; // Build version number
|
|
|
|
|
# uint8_t capFlags; // Capabilities flags
|
|
|
|
|
return {
|
|
|
|
|
'nonce': (0 | uctypes.ARRAY, 8 | uctypes.UINT8),
|
|
|
|
|
'cid': 8 | uctypes.UINT32,
|
|
|
|
|
'versionInterface': 12 | uctypes.UINT8,
|
|
|
|
|
'versionMajor': 13 | uctypes.UINT8,
|
|
|
|
|
'versionMinor': 14 | uctypes.UINT8,
|
|
|
|
|
'versionBuild': 15 | uctypes.UINT8,
|
|
|
|
|
'capFlags': 16 | uctypes.UINT8,
|
|
|
|
|
"nonce": (0 | uctypes.ARRAY, 8 | uctypes.UINT8),
|
|
|
|
|
"cid": 8 | uctypes.UINT32,
|
|
|
|
|
"versionInterface": 12 | uctypes.UINT8,
|
|
|
|
|
"versionMajor": 13 | uctypes.UINT8,
|
|
|
|
|
"versionMinor": 14 | uctypes.UINT8,
|
|
|
|
|
"versionBuild": 15 | uctypes.UINT8,
|
|
|
|
|
"capFlags": 16 | uctypes.UINT8,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -134,13 +134,13 @@ def resp_cmd_register(khlen: int, certlen: int, siglen: int) -> dict:
|
|
|
|
|
# uint8_t sig[siglen]; // Registration signature
|
|
|
|
|
# uint16_t status;
|
|
|
|
|
return {
|
|
|
|
|
'registerId': 0 | uctypes.UINT8,
|
|
|
|
|
'pubKey': (1 | uctypes.ARRAY, 65 | uctypes.UINT8),
|
|
|
|
|
'keyHandleLen': 66 | uctypes.UINT8,
|
|
|
|
|
'keyHandle': (67 | uctypes.ARRAY, khlen | uctypes.UINT8),
|
|
|
|
|
'cert': (cert_ofs | uctypes.ARRAY, certlen | uctypes.UINT8),
|
|
|
|
|
'sig': (sig_ofs | uctypes.ARRAY, siglen | uctypes.UINT8),
|
|
|
|
|
'status': status_ofs | uctypes.UINT16,
|
|
|
|
|
"registerId": 0 | uctypes.UINT8,
|
|
|
|
|
"pubKey": (1 | uctypes.ARRAY, 65 | uctypes.UINT8),
|
|
|
|
|
"keyHandleLen": 66 | uctypes.UINT8,
|
|
|
|
|
"keyHandle": (67 | uctypes.ARRAY, khlen | uctypes.UINT8),
|
|
|
|
|
"cert": (cert_ofs | uctypes.ARRAY, certlen | uctypes.UINT8),
|
|
|
|
|
"sig": (sig_ofs | uctypes.ARRAY, siglen | uctypes.UINT8),
|
|
|
|
|
"status": status_ofs | uctypes.UINT16,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -154,10 +154,10 @@ def req_cmd_authenticate(khlen: int) -> dict:
|
|
|
|
|
# uint8_t keyHandleLen; // Length of key handle
|
|
|
|
|
# uint8_t keyHandle[khlen]; // Key handle
|
|
|
|
|
return {
|
|
|
|
|
'chal': (0 | uctypes.ARRAY, 32 | uctypes.UINT8),
|
|
|
|
|
'appId': (32 | uctypes.ARRAY, 32 | uctypes.UINT8),
|
|
|
|
|
'keyHandleLen': 64 | uctypes.UINT8,
|
|
|
|
|
'keyHandle': (65 | uctypes.ARRAY, khlen | uctypes.UINT8),
|
|
|
|
|
"chal": (0 | uctypes.ARRAY, 32 | uctypes.UINT8),
|
|
|
|
|
"appId": (32 | uctypes.ARRAY, 32 | uctypes.UINT8),
|
|
|
|
|
"keyHandleLen": 64 | uctypes.UINT8,
|
|
|
|
|
"keyHandle": (65 | uctypes.ARRAY, khlen | uctypes.UINT8),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -168,17 +168,17 @@ def resp_cmd_authenticate(siglen: int) -> dict:
|
|
|
|
|
# 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,
|
|
|
|
|
"flags": 0 | uctypes.UINT8,
|
|
|
|
|
"ctr": 1 | uctypes.UINT32,
|
|
|
|
|
"sig": (5 | uctypes.ARRAY, siglen | uctypes.UINT8),
|
|
|
|
|
"status": status_ofs | uctypes.UINT16,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def overlay_struct(buf, desc):
|
|
|
|
|
desc_size = uctypes.sizeof(desc, uctypes.BIG_ENDIAN)
|
|
|
|
|
if desc_size > len(buf):
|
|
|
|
|
raise ValueError('desc is too big (%d > %d)' % (desc_size, len(buf)))
|
|
|
|
|
raise ValueError("desc is too big (%d > %d)" % (desc_size, len(buf)))
|
|
|
|
|
return uctypes.struct(uctypes.addressof(buf), desc, uctypes.BIG_ENDIAN)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -189,8 +189,9 @@ def make_struct(desc):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Msg:
|
|
|
|
|
|
|
|
|
|
def __init__(self, cid: int, cla: int, ins: int, p1: int, p2: int, lc: int, data: bytes) -> None:
|
|
|
|
|
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
|
|
|
|
@ -201,7 +202,6 @@ class Msg:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Cmd:
|
|
|
|
|
|
|
|
|
|
def __init__(self, cid: int, cmd: int, data: bytes) -> None:
|
|
|
|
|
self.cid = cid
|
|
|
|
|
self.cmd = cmd
|
|
|
|
@ -212,10 +212,12 @@ class Cmd:
|
|
|
|
|
ins = self.data[_APDU_INS]
|
|
|
|
|
p1 = self.data[_APDU_P1]
|
|
|
|
|
p2 = self.data[_APDU_P2]
|
|
|
|
|
lc = (self.data[_APDU_LC1] << 16) + \
|
|
|
|
|
(self.data[_APDU_LC2] << 8) + \
|
|
|
|
|
(self.data[_APDU_LC3])
|
|
|
|
|
data = self.data[_APDU_DATA:_APDU_DATA + lc]
|
|
|
|
|
lc = (
|
|
|
|
|
(self.data[_APDU_LC1] << 16)
|
|
|
|
|
+ (self.data[_APDU_LC2] << 8)
|
|
|
|
|
+ (self.data[_APDU_LC3])
|
|
|
|
|
)
|
|
|
|
|
data = self.data[_APDU_DATA : _APDU_DATA + lc]
|
|
|
|
|
return Msg(self.cid, cla, ins, p1, p2, lc, data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -235,7 +237,7 @@ async def read_cmd(iface: io.HID) -> Cmd:
|
|
|
|
|
if ifrm.cmd & _TYPE_MASK == _TYPE_CONT:
|
|
|
|
|
# unexpected cont packet, abort current msg
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, '_TYPE_CONT')
|
|
|
|
|
log.warning(__name__, "_TYPE_CONT")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
if datalen < bcnt:
|
|
|
|
@ -253,13 +255,13 @@ async def read_cmd(iface: io.HID) -> Cmd:
|
|
|
|
|
if cfrm.seq == _CMD_INIT:
|
|
|
|
|
# _CMD_INIT frame, cancels current channel
|
|
|
|
|
ifrm = overlay_struct(buf, desc_init)
|
|
|
|
|
data = ifrm.data[:ifrm.bcnt]
|
|
|
|
|
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')
|
|
|
|
|
log.warning(__name__, "_ERR_CHANNEL_BUSY")
|
|
|
|
|
await send_cmd(cmd_error(cfrm.cid, _ERR_CHANNEL_BUSY), iface)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
@ -267,7 +269,7 @@ async def read_cmd(iface: io.HID) -> Cmd:
|
|
|
|
|
# cont frame for this channel, but incorrect seq number, abort
|
|
|
|
|
# current msg
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, '_ERR_INVALID_SEQ')
|
|
|
|
|
log.warning(__name__, "_ERR_INVALID_SEQ")
|
|
|
|
|
await send_cmd(cmd_error(cfrm.cid, _ERR_INVALID_SEQ), iface)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
@ -330,7 +332,6 @@ _CONFIRM_TIMEOUT_MS = const(10 * 1000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConfirmState:
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
self.reset()
|
|
|
|
|
|
|
|
|
@ -382,19 +383,20 @@ class ConfirmState:
|
|
|
|
|
from trezor.ui.text import Text
|
|
|
|
|
|
|
|
|
|
if bytes(self.app_id) == _BOGUS_APPID:
|
|
|
|
|
text = Text('U2F mismatch', ui.ICON_WRONG, icon_color=ui.RED)
|
|
|
|
|
text.normal('Another U2F device', 'was used to register', 'in this application.')
|
|
|
|
|
text = Text("U2F mismatch", ui.ICON_WRONG, icon_color=ui.RED)
|
|
|
|
|
text.normal(
|
|
|
|
|
"Another U2F device", "was used to register", "in this application."
|
|
|
|
|
)
|
|
|
|
|
text.render()
|
|
|
|
|
await loop.sleep(3 * 1000 * 1000)
|
|
|
|
|
self.confirmed = True
|
|
|
|
|
else:
|
|
|
|
|
content = ConfirmContent(self.action, self.app_id)
|
|
|
|
|
dialog = ConfirmDialog(content, )
|
|
|
|
|
dialog = ConfirmDialog(content)
|
|
|
|
|
self.confirmed = await dialog == CONFIRMED
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConfirmContent(ui.Widget):
|
|
|
|
|
|
|
|
|
|
def __init__(self, action: int, app_id: bytes) -> None:
|
|
|
|
|
self.action = action
|
|
|
|
|
self.app_id = app_id
|
|
|
|
@ -411,25 +413,30 @@ class ConfirmContent(ui.Widget):
|
|
|
|
|
|
|
|
|
|
if app_id == _BOGUS_APPID:
|
|
|
|
|
# TODO: display a warning dialog for bogus app ids
|
|
|
|
|
name = 'Another U2F device'
|
|
|
|
|
icon = res.load('apps/fido_u2f/res/u2f_generic.toif') # TODO: warning icon
|
|
|
|
|
name = "Another U2F device"
|
|
|
|
|
icon = res.load("apps/fido_u2f/res/u2f_generic.toif") # TODO: warning icon
|
|
|
|
|
elif app_id in knownapps.knownapps:
|
|
|
|
|
name = knownapps.knownapps[app_id]
|
|
|
|
|
try:
|
|
|
|
|
icon = res.load('apps/fido_u2f/res/u2f_%s.toif' % name.lower().replace(' ', '_'))
|
|
|
|
|
icon = res.load(
|
|
|
|
|
"apps/fido_u2f/res/u2f_%s.toif" % name.lower().replace(" ", "_")
|
|
|
|
|
)
|
|
|
|
|
except Exception:
|
|
|
|
|
icon = res.load('apps/fido_u2f/res/u2f_generic.toif')
|
|
|
|
|
icon = res.load("apps/fido_u2f/res/u2f_generic.toif")
|
|
|
|
|
else:
|
|
|
|
|
name = '%s...%s' % (hexlify(app_id[:4]).decode(), hexlify(app_id[-4:]).decode())
|
|
|
|
|
icon = res.load('apps/fido_u2f/res/u2f_generic.toif')
|
|
|
|
|
name = "%s...%s" % (
|
|
|
|
|
hexlify(app_id[:4]).decode(),
|
|
|
|
|
hexlify(app_id[-4:]).decode(),
|
|
|
|
|
)
|
|
|
|
|
icon = res.load("apps/fido_u2f/res/u2f_generic.toif")
|
|
|
|
|
self.app_name = name
|
|
|
|
|
self.app_icon = icon
|
|
|
|
|
|
|
|
|
|
def render(self) -> None:
|
|
|
|
|
if self.action == _CONFIRM_REGISTER:
|
|
|
|
|
header = 'U2F Register'
|
|
|
|
|
header = "U2F Register"
|
|
|
|
|
else:
|
|
|
|
|
header = 'U2F Authenticate'
|
|
|
|
|
header = "U2F Authenticate"
|
|
|
|
|
ui.header(header, ui.ICON_DEFAULT, ui.GREEN, ui.BG, ui.GREEN)
|
|
|
|
|
ui.display.image((ui.WIDTH - 64) // 2, 64, self.app_icon)
|
|
|
|
|
ui.display.text_center(ui.WIDTH // 2, 168, self.app_name, ui.MONO, ui.FG, ui.BG)
|
|
|
|
@ -441,46 +448,46 @@ def dispatch_cmd(req: Cmd, state: ConfirmState) -> Cmd:
|
|
|
|
|
|
|
|
|
|
if m.cla != 0:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, '_SW_CLA_NOT_SUPPORTED')
|
|
|
|
|
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')
|
|
|
|
|
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')
|
|
|
|
|
log.debug(__name__, "_MSG_REGISTER")
|
|
|
|
|
return msg_register(m, state)
|
|
|
|
|
elif m.ins == _MSG_AUTHENTICATE:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.debug(__name__, '_MSG_AUTHENTICATE')
|
|
|
|
|
log.debug(__name__, "_MSG_AUTHENTICATE")
|
|
|
|
|
return msg_authenticate(m, state)
|
|
|
|
|
elif m.ins == _MSG_VERSION:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.debug(__name__, '_MSG_VERSION')
|
|
|
|
|
log.debug(__name__, "_MSG_VERSION")
|
|
|
|
|
return msg_version(m)
|
|
|
|
|
else:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, '_SW_INS_NOT_SUPPORTED: %d', m.ins)
|
|
|
|
|
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')
|
|
|
|
|
log.debug(__name__, "_CMD_INIT")
|
|
|
|
|
return cmd_init(req)
|
|
|
|
|
elif req.cmd == _CMD_PING:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.debug(__name__, '_CMD_PING')
|
|
|
|
|
log.debug(__name__, "_CMD_PING")
|
|
|
|
|
return req
|
|
|
|
|
elif req.cmd == _CMD_WINK:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.debug(__name__, '_CMD_WINK')
|
|
|
|
|
log.debug(__name__, "_CMD_WINK")
|
|
|
|
|
return req
|
|
|
|
|
else:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, '_ERR_INVALID_CMD: %d', req.cmd)
|
|
|
|
|
log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd)
|
|
|
|
|
return cmd_error(req.cid, _ERR_INVALID_CMD)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -510,13 +517,13 @@ def msg_register(req: Msg, state: ConfirmState) -> Cmd:
|
|
|
|
|
|
|
|
|
|
if not storage.is_initialized():
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, 'not 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:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, '_SW_WRONG_LENGTH req.data')
|
|
|
|
|
log.warning(__name__, "_SW_WRONG_LENGTH req.data")
|
|
|
|
|
return msg_error(req.cid, _SW_WRONG_LENGTH)
|
|
|
|
|
|
|
|
|
|
# parse challenge and app_id
|
|
|
|
@ -532,12 +539,12 @@ def msg_register(req: Msg, state: ConfirmState) -> Cmd:
|
|
|
|
|
# wait for a button or continue
|
|
|
|
|
if not state.confirmed:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.info(__name__, 'waiting for button')
|
|
|
|
|
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')
|
|
|
|
|
log.info(__name__, "signing register")
|
|
|
|
|
buf = msg_register_sign(chal, app_id)
|
|
|
|
|
|
|
|
|
|
state.reset()
|
|
|
|
@ -554,11 +561,11 @@ def msg_register_sign(challenge: bytes, app_id: bytes) -> bytes:
|
|
|
|
|
nodepath = [_U2F_KEY_PATH] + keypath
|
|
|
|
|
|
|
|
|
|
# prepare signing key from random path, compute decompressed public key
|
|
|
|
|
node = seed.derive_node_without_passphrase(nodepath, 'nist256p1')
|
|
|
|
|
node = seed.derive_node_without_passphrase(nodepath, "nist256p1")
|
|
|
|
|
pubkey = nist256p1.publickey(node.private_key(), False)
|
|
|
|
|
|
|
|
|
|
# first half of keyhandle is keypath
|
|
|
|
|
keybuf = ustruct.pack('>8L', *keypath)
|
|
|
|
|
keybuf = ustruct.pack(">8L", *keypath)
|
|
|
|
|
|
|
|
|
|
# second half of keyhandle is a hmac of app_id and keypath
|
|
|
|
|
keybase = hmac.Hmac(node.private_key(), app_id, hashlib.sha256)
|
|
|
|
@ -567,12 +574,12 @@ def msg_register_sign(challenge: bytes, app_id: bytes) -> bytes:
|
|
|
|
|
|
|
|
|
|
# hash the request data together with keyhandle and pubkey
|
|
|
|
|
dig = hashlib.sha256()
|
|
|
|
|
dig.update(b'\x00') # uint8_t reserved;
|
|
|
|
|
dig.update(app_id) # uint8_t appId[32];
|
|
|
|
|
dig.update(b"\x00") # uint8_t reserved;
|
|
|
|
|
dig.update(app_id) # uint8_t appId[32];
|
|
|
|
|
dig.update(challenge) # uint8_t chal[32];
|
|
|
|
|
dig.update(keybuf) # uint8_t keyHandle[64];
|
|
|
|
|
dig.update(keybuf) # uint8_t keyHandle[64];
|
|
|
|
|
dig.update(keybase)
|
|
|
|
|
dig.update(pubkey) # uint8_t pubKey[65];
|
|
|
|
|
dig.update(pubkey) # uint8_t pubKey[65];
|
|
|
|
|
dig = dig.digest()
|
|
|
|
|
|
|
|
|
|
# sign the digest and convert to der
|
|
|
|
@ -580,8 +587,9 @@ def msg_register_sign(challenge: bytes, app_id: bytes) -> bytes:
|
|
|
|
|
sig = der.encode_seq((sig[1:33], sig[33:]))
|
|
|
|
|
|
|
|
|
|
# pack to a response
|
|
|
|
|
buf, resp = make_struct(resp_cmd_register(
|
|
|
|
|
len(keybuf) + len(keybase), len(_U2F_ATT_CERT), len(sig)))
|
|
|
|
|
buf, resp = make_struct(
|
|
|
|
|
resp_cmd_register(len(keybuf) + len(keybase), len(_U2F_ATT_CERT), len(sig))
|
|
|
|
|
)
|
|
|
|
|
resp.registerId = _U2F_REGISTER_ID
|
|
|
|
|
utils.memcpy(resp.pubKey, 0, pubkey, 0, len(pubkey))
|
|
|
|
|
resp.keyHandleLen = len(keybuf) + len(keybase)
|
|
|
|
@ -599,20 +607,20 @@ def msg_authenticate(req: Msg, state: ConfirmState) -> Cmd:
|
|
|
|
|
|
|
|
|
|
if not storage.is_initialized():
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, 'not 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:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, '_SW_WRONG_LENGTH req.data')
|
|
|
|
|
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')
|
|
|
|
|
log.warning(__name__, "_SW_WRONG_LENGTH khlen")
|
|
|
|
|
return msg_error(req.cid, _SW_WRONG_LENGTH)
|
|
|
|
|
|
|
|
|
|
auth = overlay_struct(req.data, req_cmd_authenticate(khlen))
|
|
|
|
@ -626,13 +634,13 @@ def msg_authenticate(req: Msg, state: ConfirmState) -> Cmd:
|
|
|
|
|
# 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')
|
|
|
|
|
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')
|
|
|
|
|
log.info(__name__, "_AUTH_ENFORCE")
|
|
|
|
|
return msg_error(req.cid, _SW_WRONG_DATA)
|
|
|
|
|
|
|
|
|
|
# check equality with last request
|
|
|
|
@ -644,12 +652,12 @@ def msg_authenticate(req: Msg, state: ConfirmState) -> Cmd:
|
|
|
|
|
# wait for a button or continue
|
|
|
|
|
if not state.confirmed:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.info(__name__, 'waiting for button')
|
|
|
|
|
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')
|
|
|
|
|
log.info(__name__, "signing authentication")
|
|
|
|
|
buf = msg_authenticate_sign(auth.chal, auth.appId, node.private_key())
|
|
|
|
|
|
|
|
|
|
state.reset()
|
|
|
|
@ -662,18 +670,18 @@ def msg_authenticate_genkey(app_id: bytes, keyhandle: bytes):
|
|
|
|
|
|
|
|
|
|
# unpack the keypath from the first half of keyhandle
|
|
|
|
|
keybuf = keyhandle[:32]
|
|
|
|
|
keypath = ustruct.unpack('>8L', keybuf)
|
|
|
|
|
keypath = ustruct.unpack(">8L", keybuf)
|
|
|
|
|
|
|
|
|
|
# check high bit for hardened keys
|
|
|
|
|
for i in keypath:
|
|
|
|
|
if not i & HARDENED:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, 'invalid key path')
|
|
|
|
|
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')
|
|
|
|
|
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)
|
|
|
|
@ -683,7 +691,7 @@ def msg_authenticate_genkey(app_id: bytes, keyhandle: bytes):
|
|
|
|
|
# verify the hmac
|
|
|
|
|
if keybase != keyhandle[32:]:
|
|
|
|
|
if __debug__:
|
|
|
|
|
log.warning(__name__, 'invalid key handle')
|
|
|
|
|
log.warning(__name__, "invalid key handle")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
return node
|
|
|
|
@ -694,13 +702,13 @@ def msg_authenticate_sign(challenge: bytes, app_id: bytes, privkey: bytes) -> by
|
|
|
|
|
|
|
|
|
|
# get next counter
|
|
|
|
|
ctr = storage.next_u2f_counter()
|
|
|
|
|
ctrbuf = ustruct.pack('>L', ctr)
|
|
|
|
|
ctrbuf = ustruct.pack(">L", ctr)
|
|
|
|
|
|
|
|
|
|
# hash input data together with counter
|
|
|
|
|
dig = hashlib.sha256()
|
|
|
|
|
dig.update(app_id) # uint8_t appId[32];
|
|
|
|
|
dig.update(flags) # uint8_t flags;
|
|
|
|
|
dig.update(ctrbuf) # uint8_t ctr[4];
|
|
|
|
|
dig.update(app_id) # uint8_t appId[32];
|
|
|
|
|
dig.update(flags) # uint8_t flags;
|
|
|
|
|
dig.update(ctrbuf) # uint8_t ctr[4];
|
|
|
|
|
dig.update(challenge) # uint8_t chal[32];
|
|
|
|
|
dig = dig.digest()
|
|
|
|
|
|
|
|
|
@ -721,12 +729,12 @@ def msg_authenticate_sign(challenge: bytes, app_id: bytes, privkey: bytes) -> by
|
|
|
|
|
def msg_version(req: Msg) -> Cmd:
|
|
|
|
|
if req.data:
|
|
|
|
|
return msg_error(req.cid, _SW_WRONG_LENGTH)
|
|
|
|
|
return Cmd(req.cid, _CMD_MSG, b'U2F_V2\x90\x00') # includes _SW_NO_ERROR
|
|
|
|
|
return Cmd(req.cid, _CMD_MSG, b"U2F_V2\x90\x00") # includes _SW_NO_ERROR
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def msg_error(cid: int, code: int) -> Cmd:
|
|
|
|
|
return Cmd(cid, _CMD_MSG, ustruct.pack('>H', code))
|
|
|
|
|
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))
|
|
|
|
|
return Cmd(cid, _CMD_ERROR, ustruct.pack(">B", code))
|
|
|
|
|