Affected candidate
Draft PR #621 at 79c2bc853eaba0277d06afcf7a13df3c25eaec13 (firmware 7.16.0).
Finding
The new CTAP2 ClientPIN implementation creates authentication secrets with unchecked RNG calls, then retains the ECDH private key after a successful exchange.
Evidence in lib/firmware/ctap2.c:
generate_key_agreement() draws the P-256 private key with plain random_buffer(), ignores ecdsa_get_public_key65()'s failure result, and sets key_agreement.valid = true unconditionally.
setPIN and changePIN draw storage.pin_salt with plain random_buffer().
getPINToken draws the 32-byte pin_token with plain random_buffer().
- Successful set/change/token operations only assign
key_agreement.valid = false; they do not wipe the static private/public key.
- The
setPIN invalid-MAC path can derive shared_secret and return through the compound condition without wiping it.
include/keepkey/rand/rng_health.h explicitly states that plain random_buffer() is unchecked and that adding a new key-material draw does not inherit the boot-lifetime health gate. These 7.16 call sites are absent from its complete covered list.
Impact
High / release blocker. A source that the firmware health machinery would reject can still generate the ClientPIN ECDH private key and pin_token. A predictable token permits pinUvAuthParam forgery without the PIN; a predictable ECDH scalar exposes PIN-protocol traffic. An invalid/out-of-range P-256 draw is advertised as a valid all-zero public key because the conversion result is ignored. Successful exchanges also leave the private scalar resident until another key agreement or reset overwrites it.
This is independent of #613: fixing the hardware-error latch does not protect call sites that bypass random_buffer_checked().
Required remediation
Affected candidate
Draft PR #621 at
79c2bc853eaba0277d06afcf7a13df3c25eaec13(firmware 7.16.0).Finding
The new CTAP2 ClientPIN implementation creates authentication secrets with unchecked RNG calls, then retains the ECDH private key after a successful exchange.
Evidence in
lib/firmware/ctap2.c:generate_key_agreement()draws the P-256 private key with plainrandom_buffer(), ignoresecdsa_get_public_key65()'s failure result, and setskey_agreement.valid = trueunconditionally.setPINandchangePINdrawstorage.pin_saltwith plainrandom_buffer().getPINTokendraws the 32-bytepin_tokenwith plainrandom_buffer().key_agreement.valid = false; they do not wipe the static private/public key.setPINinvalid-MAC path can deriveshared_secretand return through the compound condition without wiping it.include/keepkey/rand/rng_health.hexplicitly states that plainrandom_buffer()is unchecked and that adding a new key-material draw does not inherit the boot-lifetime health gate. These 7.16 call sites are absent from its complete covered list.Impact
High / release blocker. A source that the firmware health machinery would reject can still generate the ClientPIN ECDH private key and
pin_token. A predictable token permitspinUvAuthParamforgery without the PIN; a predictable ECDH scalar exposes PIN-protocol traffic. An invalid/out-of-range P-256 draw is advertised as a valid all-zero public key because the conversion result is ignored. Successful exchanges also leave the private scalar resident until another key agreement or reset overwrites it.This is independent of #613: fixing the hardware-error latch does not protect call sites that bypass
random_buffer_checked().Required remediation
random_buffer_checked()and fail the command closed on a bad verdict.ecdsa_get_public_key65()before settingvalidor encoding a response.shared_secret, plaintext PIN material, verifiers, and authenticated-message buffers on every exit after population.