Skip to content

Reject key generation for id and label keys a device declines - #11866

Merged
padelsbach merged 2 commits into
wolfSSL:masterfrom
Frauschi:keygen-id-key-fallback
Oct 28, 2026
Merged

padelsbach merged 2 commits into
wolfSSL:masterfrom
Frauschi:keygen-id-key-fallback

Conversation

@Frauschi

Copy link
Copy Markdown
Member

Description

A key initialized with an id or label names a key held by a crypto callback device. If that device returned CRYPTOCB_UNAVAILABLE for key generation, make_key silently generated a software key pair instead. The id or label still pointed at the device key, so later private-key operations used a different key, and signatures did not verify against the key's own public key.

  • ECC, RSA, Ed25519, Ed448, ML-DSA, ML-KEM, SLH-DSA, Falcon, LMS and XMSS key generation now return NO_VALID_DEVID when the key has an id or label and its devId reaches a registered device that did not generate it. The check is WC_CRYPTOCB_IS_DEV_KEY() in cryptocb.h, which finds the device the same way dispatch does (through the WOLF_CRYPTO_CB_FIND callback if set, by devId otherwise).
  • If the devId reaches no device, later operations also run in software, so the fallback stays. This keeps generate-then-wc_Pkcs11StoreKey working, as well as callbacks that generate by clearing devId and calling make_key again (wolfcrypt/test/test.c, tests/swdev).
  • Only RNG-driven keygen is affected; seeded keygen and private-key import are unchanged.
  • Ed448 has no keygen callback on master (Add keygen crypto callback for Ed448 #11860 adds it), so its id and label keys on a registered device are always rejected. ECC behaves the same without HAVE_ECC_DHE.
  • The test_wc_CryptoCb TLS callback no longer labels the ECDH keys it declines, which broke resumed keygen under WOLFSSL_ASYNC_CRYPT_SW.

Testing

  • New test_*_make_key_id_dev per algorithm: id and label keys on a declining device get NO_VALID_DEVID. Unregistered devIds and plain keys still fall back to software. ECC also covers a device that calls make_key again on the caller's key, and a find callback routing an INVALID_DEVID id key to the declining device. All 10 tests fail without the fix.
  • testwolfcrypt and unit.test --api pass with --enable-cryptocb in these variants: plain, WOLF_CRYPTO_CB_FIND, --enable-swdev, --enable-pkcs11 (without ML-DSA, which does not build with PKCS#11 on macOS clang on master), WOLFSSL_ASYNC_CRYPT_SW, and swdev with WOLF_CRYPTO_CB_ONLY_ECC / _RSA.
  • NO_ECC_DHE (crypto-only, since TLS needs ECDHE on master): testwolfcrypt passes, and a small test program confirms the ECC rejection and fallback.

@Frauschi Frauschi self-assigned this Oct 22, 2026
Copilot AI balanced review requested due to automatic review settings October 22, 2026 18:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

A configuration-specific test regression remains, and callback compatibility across ten cryptographic algorithms warrants final human validation.

1 open issue
What changed in this PR

This PR prevents wolfCrypt from silently generating software keys when a registered device declines generation for an id- or label-bound key, avoiding mismatched public and private keys.

Changes:

  • Adds a shared device-routing check across ten key-generation algorithms while preserving fallback for unregistered devices.
  • Adds regression tests and adjusts the TLS test callback to stop labeling declined ECDH keys.
  • Documents the new NO_VALID_DEVID return condition.
File Description
wolfssl/​wolfcrypt/​cryptocb.h Declares the device-bound key check.
wolfcrypt/​src/​wc_xmss.c Rejects unsafe XMSS software fallback.
wolfcrypt/​src/​wc_slhdsa.c Rejects unsafe SLH-DSA software fallback.
wolfcrypt/​src/​wc_mlkem.c Rejects unsafe ML-KEM software fallback.
wolfcrypt/​src/​wc_mldsa.c Rejects unsafe ML-DSA software fallback.
wolfcrypt/​src/​wc_lms.c Rejects unsafe LMS software fallback.
wolfcrypt/​src/​rsa.c Rejects unsafe RSA software fallback.
wolfcrypt/​src/​falcon.c Rejects unsafe Falcon software fallback.
wolfcrypt/​src/​ed448.c Rejects device-bound Ed448 generation.
wolfcrypt/​src/​ed25519.c Rejects unsafe Ed25519 software fallback.
wolfcrypt/​src/​ecc.c Rejects unsafe ECC software fallback.
wolfcrypt/​src/​cryptocb.c Implements the device-routing lookup.
tests/​api/​test_slhdsa.h Registers the SLH-DSA regression test.
tests/​api/​test_slhdsa.c Tests SLH-DSA rejection and fallback.
tests/​api/​test_rsa.h Registers the RSA regression test.
tests/​api/​test_rsa.c Tests RSA rejection and fallback.
tests/​api/​test_mlkem.h Registers the ML-KEM regression test.
tests/​api/​test_mlkem.c Tests ML-KEM rejection and fallback.
tests/​api/​test_mldsa.h Registers the ML-DSA regression test.
tests/​api/​test_mldsa.c Tests ML-DSA rejection and fallback.
tests/​api/​test_lms_xmss.h Registers LMS and XMSS regression tests.
tests/​api/​test_lms_xmss.c Tests device-bound LMS and XMSS rejection.
tests/​api/​test_falcon.h Registers the Falcon regression test.
tests/​api/​test_falcon.c Tests Falcon rejection and fallback.
tests/​api/​test_ed448.h Registers the Ed448 regression test.
tests/​api/​test_ed448.c Tests Ed448 rejection and fallback.
tests/​api/​test_ed25519.h Registers the Ed25519 regression test.
tests/​api/​test_ed25519.c Tests Ed25519 rejection and fallback.
tests/​api/​test_ecc.h Registers the ECC regression test.
tests/​api/​test_ecc.c Tests ECC rejection, routing, and callback reentry.
tests/​api.c Stops labeling declined ECDH keys.
doc/​dox_comments/​header_files/​wc_xmss.h Documents XMSS device rejection.
doc/​dox_comments/​header_files/​wc_slhdsa.h Documents SLH-DSA device rejection.
doc/​dox_comments/​header_files/​wc_mlkem.h Documents ML-KEM device rejection.
doc/​dox_comments/​header_files/​wc_mldsa.h Documents ML-DSA device rejection.
doc/​dox_comments/​header_files/​wc_lms.h Documents LMS device rejection.
doc/​dox_comments/​header_files/​rsa.h Documents RSA device rejection.
doc/​dox_comments/​header_files/​falcon.h Documents Falcon device rejection.
doc/​dox_comments/​header_files/​ed448.h Documents Ed448 device rejection.
doc/​dox_comments/​header_files/​ed25519.h Documents Ed25519 device rejection.
doc/​dox_comments/​header_files/​ecc.h Documents ECC device rejection.

Review effort: Balanced

Comment thread tests/api/test_mlkem.c Outdated
@Frauschi
Frauschi force-pushed the keygen-id-key-fallback branch 2 times, most recently from ea0a251 to 016ac4a Compare October 22, 2026 18:44
@Frauschi

Copy link
Copy Markdown
Member Author

Jenkins retest this please

@Frauschi Frauschi assigned wolfSSL-Bot and unassigned Frauschi Oct 22, 2026
@Frauschi
Frauschi requested a review from padelsbach October 22, 2026 21:20
Comment thread wolfcrypt/src/ecc.c
Comment thread wolfcrypt/src/wc_lms.c Outdated
@Frauschi
Frauschi force-pushed the keygen-id-key-fallback branch from 016ac4a to c63d25b Compare October 27, 2026 16:43
@padelsbach

Copy link
Copy Markdown
Contributor

@Frauschi, looks good! Some conflicts arrived with the merge of #11860, can you please rebase?

@padelsbach padelsbach assigned Frauschi and unassigned wolfSSL-Bot Oct 27, 2026
A key initialized with an id or label names a key held by a crypto
callback device. When that device returned CRYPTOCB_UNAVAILABLE for key
generation, the make-key functions fell back to software and returned
success. The key then held a software key pair while its id or label
still named the device key, so the next private-key operation went to
the device and used a different key from the one in the key object. A
signature made that way does not verify against the key's own public
key.

ECC, RSA, Ed25519, Ed448, ML-DSA, ML-KEM, SLH-DSA, Falcon, LMS and XMSS
key generation now return NO_VALID_DEVID when the key has an id or label
and its devId names a registered device that did not generate it.
WC_CRYPTOCB_IS_DEV_KEY() in cryptocb.h holds that test. It resolves the
device through the WOLF_CRYPTO_CB_FIND callback, as dispatch does, so an
id key with INVALID_DEVID that the find callback routes to a device is
covered too.

The rule only applies to a registered device. When the devId reaches
no device, later operations also run in software with the same key, so
the software fallback stays: generating a key under an id and then
storing it with wc_Pkcs11StoreKey keeps working. A device
callback that generates the key by clearing devId and calling make-key
again on the same key, as wolfcrypt/test/test.c and tests/swdev do,
also keeps working, since the inner call sees no registered device.

The id, label and devId are read before the device is called, so a
callback that writes a label into a key it declines does not trip the
check on that call. The test_wc_CryptoCb TLS callback did this to mark
ephemeral ECDH keys, and under WOLFSSL_ASYNC_CRYPT_SW the resumed key
generation then saw the label and failed. It now recognizes those keys
by their software private key instead, since its TLS keys are loaded
public-only.

ECC id and label keys on a registered device always fail here when
HAVE_ECC_DHE is not defined, since its keygen callback is gated on it.
RSA keeps its hardware keygen paths unchanged.

Only RNG-driven key generation is affected. wc_MlDsaKey_MakeKeyFromSeed,
wc_MlKemKey_MakeKeyWithRandom, wc_SlhDsaKey_MakeKeyWithRandom and
private-key import take the key material from the caller and are
unchanged.

Each algorithm gains an API test: an id key and a label key on a
registered device that declines keygen get NO_VALID_DEVID, and where
software key generation exists, an id key on an unregistered devId and
a plain key on the declining device still fall back to software. The
ECC test also covers a device that generates the key by calling
wc_ecc_make_key_ex on the caller's key, and a find callback that routes
an INVALID_DEVID id key to a declining device.
A PKCS#11 token without a key generation mechanism declines keygen:
wc_Pkcs11 returns NOT_COMPILED_IN, which the crypto callback layer maps
to CRYPTOCB_UNAVAILABLE. The software key used to be stored on the
token afterwards with wc_Pkcs11StoreKey, and rejecting id and label
keys broke that flow. Defining WOLF_CRYPTO_CB_KEYGEN_SW_FALLBACK now
restores the software fallback for them. Without it, the flow still
works by initializing the id key with INVALID_DEVID, generating it and
then storing it, since wc_Pkcs11StoreKey takes the token directly. That
does not apply when a WOLF_CRYPTO_CB_FIND callback routes INVALID_DEVID
to the token, because the key then belongs to the device.

The check no longer looks up the device before every keygen on an id or
label key. Whether the key has an id or label, and its devId, are still
read before the device is called, but wc_CryptoCb_HasPkDevice() only
runs once the device has declined, so a key the device generates costs
no extra lookup. Reading devId up front keeps the check when a callback
clears key->devId and then declines. The cryptocb dispatchers cannot
make this check themselves: they get a void* key for the PQC types, and
CRYPTOCB_UNAVAILABLE from them means both no device and a declining
device.

WC_CRYPTOCB_KEYGEN_ID_CHECK in cryptocb.h gates the check, its tests and
wc_CryptoCb_HasPkDevice(). The opt-out is listed with the other crypto
callback build options in cryptocb.c, and the NO_VALID_DEVID doxygen
entries now also name a device picked by a WOLF_CRYPTO_CB_FIND callback.
Under the opt-out the ECC test checks that id and label keys are
generated in software again, and a new keygen-sw-fallback entry in the
cryptocb-only workflow builds that configuration.

Test changes: the ECC test adds a callback that clears devId before
declining. The RSA and SLH-DSA tests add a key without an id on the
declining device, which the other algorithms already covered. The RSA
test uses TEST_RSA_BITS instead of a 2048-bit software key, and the
SLH-DSA test uses the file's default parameter set, so it also runs in
SHA-2-only and 192/256-only builds. The RSA test guard drops
WOLFSSL_CRYPTOCELL, which master removed, and now runs in the
WOLFSSL_SE050_NO_RSA and WOLFSSL_SE050_ONLY_KEY_ID builds that keep the
software keygen path.
@Frauschi
Frauschi force-pushed the keygen-id-key-fallback branch from c63d25b to 85284bb Compare October 28, 2026 09:40
@Frauschi
Frauschi requested a review from padelsbach October 28, 2026 12:20
@Frauschi Frauschi assigned padelsbach and unassigned Frauschi Oct 28, 2026
@padelsbach
padelsbach merged commit 66bb163 into wolfSSL:master Oct 28, 2026
432 checks passed
@Frauschi
Frauschi deleted the keygen-id-key-fallback branch October 28, 2026 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants