1
0
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:
Andrew Kozlik 2019-08-08 16:27:49 +02:00
parent 6366f3ac0d
commit 70fe14e84c
3 changed files with 1228 additions and 206 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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