refactor(core/webauthn): make sure KEY_AGREEMENT_*KEY is generated once per power-up

This is what the spec recommends and it has been the case before
workflow-restarts, when `apps.webauthn.fido2` was imported exactly once
per lifetime.

With workflow-restarts, `fido2` is being imported repeatedly and the
keys regenerated. This does not seem to be a problem per the spec -- a
FIDO workflow will retain the same keys, and non-FIDO workflows can be
seen as unplugs/replugs as far as the FIDO functionality is concerned.

However, regenerating the keys is slow, which is a problem for the
hardware-based unit tests. We can avoid the slowness by returning to the
spec-mandated behavior and generating once per power-up.
pull/1610/head
matejcik 3 years ago committed by matejcik
parent f6f3c7ffcf
commit b1e4246b46

@ -50,6 +50,8 @@ storage.debug
import storage.debug import storage.debug
storage.device storage.device
import storage.device import storage.device
storage.fido2
import storage.fido2
storage.recovery storage.recovery
import storage.recovery import storage.recovery
storage.recovery_shares storage.recovery_shares

@ -5,6 +5,7 @@ from micropython import const
import storage import storage
import storage.resident_credentials import storage.resident_credentials
from storage.fido2 import KEY_AGREEMENT_PRIVKEY, KEY_AGREEMENT_PUBKEY
from trezor import config, io, log, loop, ui, utils, workflow from trezor import config, io, log, loop, ui, utils, workflow
from trezor.crypto import aes, der, hashlib, hmac, random from trezor.crypto import aes, der, hashlib, hmac, random
from trezor.crypto.curve import nist256p1 from trezor.crypto.curve import nist256p1
@ -211,10 +212,6 @@ _RESULT_DECLINE = const(2) # User declined.
_RESULT_CANCEL = const(3) # Request was cancelled by _CMD_CANCEL. _RESULT_CANCEL = const(3) # Request was cancelled by _CMD_CANCEL.
_RESULT_TIMEOUT = const(4) # Request exceeded _FIDO2_CONFIRM_TIMEOUT_MS. _RESULT_TIMEOUT = const(4) # Request exceeded _FIDO2_CONFIRM_TIMEOUT_MS.
# Generate the authenticatorKeyAgreementKey used for ECDH in authenticatorClientPIN getKeyAgreement.
_KEY_AGREEMENT_PRIVKEY = nist256p1.generate_secret()
_KEY_AGREEMENT_PUBKEY = nist256p1.publickey(_KEY_AGREEMENT_PRIVKEY, False)
# FIDO2 configuration. # FIDO2 configuration.
_ALLOW_FIDO2 = True _ALLOW_FIDO2 = True
_ALLOW_RESIDENT_CREDENTIALS = True _ALLOW_RESIDENT_CREDENTIALS = True
@ -1781,7 +1778,7 @@ def cbor_get_assertion_hmac_secret(cred: Credential, hmac_secret: dict) -> bytes
raise CborError(_ERR_INVALID_LEN) raise CborError(_ERR_INVALID_LEN)
# Compute the ECDH shared secret. # Compute the ECDH shared secret.
ecdh_result = nist256p1.multiply(_KEY_AGREEMENT_PRIVKEY, b"\04" + x + y) ecdh_result = nist256p1.multiply(KEY_AGREEMENT_PRIVKEY, b"\04" + x + y)
shared_secret = hashlib.sha256(ecdh_result[1:33]).digest() shared_secret = hashlib.sha256(ecdh_result[1:33]).digest()
# Check the authentication tag and decrypt the salt. # Check the authentication tag and decrypt the salt.
@ -1904,8 +1901,8 @@ def cbor_client_pin(req: Cmd) -> Cmd:
common.COSE_KEY_ALG: common.COSE_ALG_ECDH_ES_HKDF_256, common.COSE_KEY_ALG: common.COSE_ALG_ECDH_ES_HKDF_256,
common.COSE_KEY_KTY: common.COSE_KEYTYPE_EC2, common.COSE_KEY_KTY: common.COSE_KEYTYPE_EC2,
common.COSE_KEY_CRV: common.COSE_CURVE_P256, common.COSE_KEY_CRV: common.COSE_CURVE_P256,
common.COSE_KEY_X: _KEY_AGREEMENT_PUBKEY[1:33], common.COSE_KEY_X: KEY_AGREEMENT_PUBKEY[1:33],
common.COSE_KEY_Y: _KEY_AGREEMENT_PUBKEY[33:], common.COSE_KEY_Y: KEY_AGREEMENT_PUBKEY[33:],
} }
} }

@ -8,6 +8,9 @@ if __debug__:
import storage.debug import storage.debug
from trezor import config, pin, utils # noqa: F401 from trezor import config, pin, utils # noqa: F401
if not utils.BITCOIN_ONLY:
import storage.fido2 # noqa: F401
# Prepare the USB interfaces first. Do not connect to the host yet. # Prepare the USB interfaces first. Do not connect to the host yet.
import usb import usb

@ -0,0 +1,12 @@
# FIDO2 keys that should be generated once per power-up
# https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticator-power-up-configuration
# While we could safely generate these keys per-wipe and save them in sessionless cache,
# this drags down the performance of our test suite.
# We want to avoid importing `trezor.crypto.curve` because that would needlessly pollute
# our RAM space, while in the end importing the symbol from `trezorcrypto` directly anyway
from trezorcrypto import nist256p1
# the authenticatorKeyAgreementKey used for ECDH in authenticatorClientPIN getKeyAgreement.
KEY_AGREEMENT_PRIVKEY = nist256p1.generate_secret()
KEY_AGREEMENT_PUBKEY = nist256p1.publickey(KEY_AGREEMENT_PRIVKEY, False)
Loading…
Cancel
Save