mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 06:48:16 +00:00
core/webauthn: Implement CTAP2 protocol for FIDO2 support.
This commit is contained in:
parent
6366f3ac0d
commit
70fe14e84c
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@ if False:
|
||||
# Credential ID values
|
||||
_CRED_ID_VERSION = b"\xf1\xd0\x02\x00"
|
||||
_CRED_ID_MIN_LENGTH = const(33)
|
||||
_KEY_HANDLE_LENGTH = const(64)
|
||||
|
||||
# Credential ID keys
|
||||
_CRED_ID_RP_ID = const(0x01)
|
||||
@ -23,6 +24,9 @@ _CRED_ID_USER_DISPLAY_NAME = const(0x05)
|
||||
_CRED_ID_CREATION_TIME = const(0x06)
|
||||
_CRED_ID_HMAC_SECRET = const(0x07)
|
||||
|
||||
# Key paths
|
||||
_U2F_KEY_PATH = const(0x80553246)
|
||||
|
||||
|
||||
class Credential:
|
||||
def __init__(self) -> None:
|
||||
@ -194,3 +198,102 @@ class Fido2Credential(Credential):
|
||||
)
|
||||
|
||||
return node.key()
|
||||
|
||||
|
||||
class U2fCredential(Credential):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.node = None # type: Optional[bip32.HDNode]
|
||||
|
||||
def __lt__(self, other: "Credential") -> bool:
|
||||
# Sort U2F credentials after FIDO2 credentials.
|
||||
if isinstance(other, Fido2Credential):
|
||||
return False
|
||||
|
||||
# Sort U2F credentials lexicographically amongst each other.
|
||||
return self.id < other.id
|
||||
|
||||
def private_key(self) -> bytes:
|
||||
if self.node is None:
|
||||
return b""
|
||||
return self.node.private_key()
|
||||
|
||||
def generate_key_handle(self) -> None:
|
||||
# derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r'
|
||||
path = [HARDENED | random.uniform(0xF0000000) for _ in range(0, 8)]
|
||||
nodepath = [_U2F_KEY_PATH] + path
|
||||
|
||||
# prepare signing key from random path, compute decompressed public key
|
||||
self.node = seed.derive_node_without_passphrase(nodepath, "nist256p1")
|
||||
|
||||
# first half of keyhandle is keypath
|
||||
keypath = ustruct.pack("<8L", *path)
|
||||
|
||||
# second half of keyhandle is a hmac of rp_id_hash and keypath
|
||||
mac = hmac.Hmac(self.node.private_key(), self.rp_id_hash, hashlib.sha256)
|
||||
mac.update(keypath)
|
||||
|
||||
self.id = keypath + mac.digest()
|
||||
|
||||
def app_name(self) -> str:
|
||||
from apps.webauthn import knownapps
|
||||
|
||||
app_name = knownapps.knownapps.get(self.rp_id_hash, None)
|
||||
if app_name is None:
|
||||
app_name = "%s...%s" % (
|
||||
hexlify(self.rp_id_hash[:4]).decode(),
|
||||
hexlify(self.rp_id_hash[-4:]).decode(),
|
||||
)
|
||||
return app_name
|
||||
|
||||
@staticmethod
|
||||
def from_key_handle(
|
||||
key_handle: bytes, rp_id_hash: bytes
|
||||
) -> Optional["U2fCredential"]:
|
||||
# check the keyHandle and generate the signing key
|
||||
node = U2fCredential._node_from_key_handle(rp_id_hash, key_handle, "<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 = U2fCredential._node_from_key_handle(rp_id_hash, key_handle, ">8L")
|
||||
if node is None:
|
||||
# specific error logged in msg_authenticate_genkey
|
||||
return None
|
||||
|
||||
cred = U2fCredential()
|
||||
cred.id = key_handle
|
||||
cred.rp_id_hash = rp_id_hash
|
||||
cred.node = node
|
||||
return cred
|
||||
|
||||
@staticmethod
|
||||
def _node_from_key_handle(
|
||||
rp_id_hash: bytes, keyhandle: bytes, pathformat: str
|
||||
) -> Optional[bip32.HDNode]:
|
||||
# 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(node.private_key(), rp_id_hash, hashlib.sha256)
|
||||
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
|
||||
|
@ -1,5 +1,8 @@
|
||||
from trezor import ui
|
||||
|
||||
if False:
|
||||
from typing import Any
|
||||
|
||||
|
||||
def pin_to_int(pin: str) -> int:
|
||||
return int("1" + pin)
|
||||
@ -7,10 +10,15 @@ def pin_to_int(pin: str) -> int:
|
||||
|
||||
_previous_progress = None
|
||||
|
||||
keepalive_callback = None # type: Any
|
||||
|
||||
|
||||
def show_pin_timeout(seconds: int, progress: int, message: str) -> bool:
|
||||
global _previous_progress
|
||||
|
||||
if callable(keepalive_callback):
|
||||
keepalive_callback()
|
||||
|
||||
if progress == 0:
|
||||
if progress != _previous_progress:
|
||||
# avoid overdraw in case of repeated progress calls
|
||||
|
Loading…
Reference in New Issue
Block a user