wc_XmssKey_ExportPub_ex and wc_XmssKey_ExportPubRaw checked only for NULL
arguments. Parameters are populated by wc_XmssKey_SetParamStr, well before
wc_XmssKey_MakeKey, wc_XmssKey_Reload or an import produces a public key, so
a caller that had merely selected a parameter set received a zeroed public
key and a success return. The export path also marked the destination
verify-only, and wc_XmssKey_PublicKeyToDer encoded the result as a valid
RFC 9802 SubjectPublicKeyInfo.
Require the OK, VERIFYONLY or NOSIGS state in both exporters, and check it
before the destination key is zeroized so a rejected call leaves it
untouched. wc_XmssKey_GetPubLen keeps its parameter-only contract, since
sizing a buffer ahead of key generation is what callers use it for. Cover
the parameter-only case in the XMSS decision coverage test and in the
white-box export guards.
Fixes F-8151.
wc_LmsKey_ExportPubRaw only required key->params to be set. Parameters
are populated by wc_LmsKey_SetParameters, well before wc_LmsKey_MakeKey
or an import produces a public key, so a caller that had merely selected
a parameter set received a buffer full of zeros and a success return.
Require the same OK, VERIFYONLY or NOSIGS state that
wc_LmsKey_ExportPub_ex already enforces, and cover the parameter-only
case in the LMS decision coverage test.
Fixes F-8150.
Derive is the only key store operation that creates a key without any way to
say how big it should be. Every other one has an answer: ImportPlain has the
plaintext's length, ImportWrapped reads it from the blob, the exports and
GetInfo report a property of a key that already exists. Derive has keyType,
which is a purpose rather than a length - AES is 128 or 256 either way -
derivSz, which measures the input rather than the output, and attrs, which is
three flag bits. So the device picks, the caller cannot ask, and GetInfo then
reports a size the API gave no way to request.
Add keySz, in bytes, beside the keyType it qualifies. Zero keeps the current
behaviour and leaves the choice to the device, which is the right answer for a
key store whose derivation has only one output size.
PKCS#11 carries this in C_DeriveKey's template as CKA_VALUE_LEN and PSA in the
attributes passed to psa_key_derivation_output_key(); this is the same
parameter under a name that matches wc_KeyStore_ImportPlain().
The key store API reached master without a ChangeLog entry. Add one here, since
this is the last change to that API and the entry can describe it as it ships.
wc_ed25519_make_public() and wc_ed448_make_public() take pubKeySz by
value, but every doxygen example passed its address. Copying the example
gives "incompatible pointer to integer conversion", which is an error
rather than a warning on current compilers.
wc_ed25519_make_public() has kept the derived public key in the key object
since it started doing so, but its documentation still described a pure
derivation into the caller's buffer. Mark the key argument [in,out] and
say what it stores, matching the Ed448 wording.
test_wc_SignCRL_ed25519() still set pubKeySet by hand after calling
wc_ed25519_make_public(), which has set it itself since the same change.
wc_ed448_make_public() wrote the derived point only into the caller's
output buffer while unconditionally setting key->pubKeySet, leaving
key->p all zeros. wc_ed448_sign_msg() gates on pubKeySet alone and hashes
key->p into the challenge, so a key that arrived without a public half -
a PKCS#8 v1 PrivateKeyInfo carries no public-key field - would sign over
an all-zero public key once make_public had been called on it. No
verifier accepts those signatures and nothing reports an error. Before
the make_public call the same key fails loudly with ECC_PRIV_KEY_E, so
deriving the public key turned a hard failure into silent corruption.
Adopt the derived key into the object when it did not already carry one,
matching wc_ed25519_make_public(). wc_ed448_check_key() is unaffected: it
requires pubKeySet before calling make_public, so its private-vs-public
comparison still runs against the stored key. wc_ed448_make_key() clears
pubKeySet before deriving, so make_public now does the copy into key->p
and key->k that make_key used to do itself.
Document the side effect in the Ed448 doxygen, in both the English and the
Japanese copy: the key argument is [in,out] now. Route
wc_ed448_import_private_key_ex() through the same helper, so the key buffer
layout has one definition, and record at the helper why it never overwrites
a public key the caller already set. test_wc_SignCRL_ed448() no longer sets
pubKeySet by hand.
The regression test exports the private key to check the public half
mirrored into key->k; without that check, dropping the copy left the test
green even though wc_ed448_export_private() would hand back a zeroed public
half. Only its signing round trip is gated on HAVE_ED448_SIGN and
HAVE_ED448_VERIFY, so the storage checks still run in a sign-less build.
A hardware key store holds keys with a lifetime of their own, and none of the
existing callbacks can manage them. WC_ALGO_TYPE_SETKEY and
WC_ALGO_TYPE_EXPORT_KEY are bound to a wolfCrypt key object and carry material
for that object's own use: neither can name a stored key, ask for one to be
created exportable, or make one outlive the object that used it. Wrapped keys
add a second reason, since a wrapped blob never becomes plaintext on this side
of the boundary at all.
Add a dedicated algorithm type covering the operations that manage a stored
key, whether or not they cross that boundary:
WC_KEYSTORE_IMPORT_PLAIN place plaintext key material into a slot
WC_KEYSTORE_EXPORT_PLAIN read a stored key back as plaintext
WC_KEYSTORE_IMPORT_WRAPPED unwrap a blob directly into a slot
WC_KEYSTORE_EXPORT_WRAPPED wrap a stored key back out
WC_KEYSTORE_DERIVE derive slot to slot without touching RAM
WC_KEYSTORE_DELETE destroy a stored key
WC_KEYSTORE_GET_INFO query what a slot holds
The plaintext pair is what most key stores outside the secure-element class
offer, and it is the shape wc_Pkcs11StoreKey() already implements privately:
that function takes a Pkcs11Token* rather than a devId, so an application using
it is bound to PKCS#11 even though the operation is generic. Devices that hold
keys which may never appear in plaintext decline the pair and offer only the
wrapped form.
Delete is deliberately separate from WC_ALGO_TYPE_FREE: freeing a wolfCrypt key
object must never destroy the hardware key it refers to.
A key reference is not a new naming scheme. It is the identifier
WOLF_PRIVATE_KEY_ID already uses, so the bytes that name a key here are the
bytes wc_ecc_init_id() or wc_AesInit_Id() take to bind an object to that key,
and the bytes read back from key->id afterwards. wolfCrypt copies them through
without inspection.
Two details come from building the NXP EdgeLock port against this surface,
which is what a design like this needs before it is fixed in place.
Every operation that creates a key takes keyType, because a key store has to
know what a key is for before it can set its permissions, and the material
does not always say. Raw bytes carry no metadata, and neither does a bare
RFC 3394 wrap, which is a pure data transformation. A vendor container that
carries its own property word stays authoritative over what it holds and
keyType is then a cross-check, which catches the right blob going into the
wrong kind of slot; a device must refuse a mismatch rather than silently
prefer one source. WC_KEYSTORE_KEY_NONE leaves the choice to the device. The
export operations take no keyType, since the key already exists and GET_INFO
reports it.
attrs travels with every creating operation for the same reason, but as a
source only. A format carrying no attributes of its own leaves attrs as the
device's only word on what the new key may do. A container that carries them
wins and attrs is ignored, and it cannot usefully be cross-checked there:
a container's attributes are inside the wrap and are not known until the key
exists, whereas keyType is checkable up front against the reference being
imported into. wc_KeyStore_GetInfo() is how a caller confirms what an import
produced.
keyType also fixes how plaintext material is encoded, so no further argument
is needed to say. A symmetric type takes the raw key bytes; an asymmetric type
takes DER, a private key as PKCS#8 PrivateKeyInfo and a public key as
SubjectPublicKeyInfo. Stating it matters: WC_KEYSTORE_KEY_ECC_SIGN and a byte
string do not otherwise say whether the bytes are a scalar, SEC1 or PKCS#8,
and two devices could each pick differently and both be defensible.
Keys that act on other keys need naming. Without WC_KEYSTORE_KEY_WRAP and
WC_KEYSTORE_KEY_DERIVE a device asked about a wrapping key can only answer
NONE, which is indistinguishable from an empty slot and defeats the point of
GET_INFO. Measured on hardware, a 256-bit wrapping key reported type 0 with
256 bits, which tells a caller nothing about what it may do with it. Both are
listed ahead of the algorithm types, since they name what a key acts on rather
than which algorithm it serves.
The algorithm types cover what the two other backends this was checked against
actually store. wc_Pkcs11StoreKey() handles RSA, ECC and ML-KEM today, and
wolfHSM's WH_KEY_ALGO_ENUM names RSA, ECC, Curve25519, Ed25519, ML-DSA, ML-KEM,
LMS and XMSS, so a shorter vocabulary would have left the facility unusable to
both for anything but symmetric keys.
An algorithm serving two purposes a key store grants separately is split, since
one key doing both is the key-separation problem and a caller has to be able to
ask for the narrower key. RSA and ECC are the only two: PKCS#11 carries
CKA_SIGN and CKA_DECRYPT independently for RSA, and CKA_SIGN against CKA_DERIVE
for ECC. wolfCrypt's own PKCS#11 layer honours that for ECC, selecting from the
ecc_key flags, but sets both CKA_DECRYPT and CKA_SIGN unconditionally for RSA
because no equivalent RsaKey flag exists; expressing the distinction here is
what would let that be tightened later. The remaining types serve one purpose
each and are not split. Signature use is listed before agreement or transport
use throughout.
Argument order follows one rule: two interchangeable control words are never
left adjacent, because a transposition between them compiles cleanly and
surfaces much later as an unrelated-looking failure. An earlier arrangement had
attrs and kdfType separated only by a pointer and a length, and swapping them
silently created a key without the exportable attribute, whose export then
failed two operations away from the mistake. Beyond that each control word sits
beside what it describes. wc_KeyStore_ImportPlain is the one place the two aims
collide and the first wins: attrs sits at the tail of every creating operation
rather than beside keyType, because WC_KEYSTORE_ATTR_EXPORTABLE and
WC_KEYSTORE_KEY_WRAP are both 1 and a swap would be silent. All three creating
operations therefore read keyRef, keyRefSz, keyType, then what they draw the
key from, then attrs.
kdfType refers to enum wc_KdfType, and WC_KDF_TYPE_NONE asks for the device's
own derivation, which is all many key stores offer.
Attributes are fixed when a key is created; hardware generally burns them in,
so there is no operation here to change them afterwards. They round-trip: a
device that can represent one must report it back through GET_INFO, so a caller
can ask whether an export is permitted rather than attempting one and
interpreting the error. Attributes the device cannot represent read as absent,
and out parameters it does not fill are cleared rather than left holding the
caller's stack.
Gated behind WOLF_CRYPTO_CB_KEYSTORE, with tests in wolfcrypt/test and
tests/api that assert each operation reaches the device carrying the arguments
the caller passed, not merely that the call returned zero.
wc_CryptoCb_Curve25519MakePub took no devId and resolved its device with a
lookup for INVALID_DEVID, falling back to the first registered device. Every
route to an X25519 public point derivation went through it, so a key bound to
one device had its private scalar offered to whichever device happened to be
registered first, with no way to opt out.
Give both wc_CryptoCb_Curve25519MakePub and wc_CryptoCb_Curve25519Generic a
devId parameter and let only a caller that selected no device settle for the
first registered one, matching the curve448 siblings. wc_curve25519_make_key
and wc_curve25519_export_public_ex now derive the point through a devId
carrying helper that reaches the callback only when the key names a device.
The keyless public API has no key to take a devId from, so it offers the
scalar to wc_CryptoCb_DefaultDevID() and keeps its old behaviour, while
honouring WC_NO_DEFAULT_DEVID.
Under WOLFSSL_CURVE25519_BLINDING both wc_curve25519_make_pub and
wc_curve25519_generic fall through to their blinded variant after the
callback declined, which dispatched the same private scalar to the same
device a second time. The fall-through now calls the software half of the
blinded derivation directly, so a declining device is offered the scalar
once.
Under WOLF_CRYPTO_CB_ONLY_CURVE25519 an unbound key passed to
wc_curve25519_export_public_ex now returns NO_VALID_DEVID rather than
reaching the first registered device. Such builds must create keys with
wc_curve25519_init_ex and a real devId.
RFC 9846, 5.5
CheckTLS13AEADSendLimit(): at the AEAD limit while sending early data,
return TOO_MUCH_EARLY_DATA instead of calling Tls13UpdateKeys(). A KeyUpdate
there would go out pre-handshake.
RFC 9846, 6.1
Three alert sites in DoAlert() / DoProcessAlertRecord(): TLS 1.3
user_canceled is now exempt from teardown and session invalidation at any
AlertLevel, not just warning. TLS 1.2 unchanged.
RFC 9846, 4.7.3
New Tls13KeyUpdateLimitReached() helper shared by send and receive
paths. At the 2^48-1 cap, DoTls13KeyUpdate() drops a peer's update_requested
and continues, rather than failing the connection. App-initiated
wolfSSL_update_keys() still returns BAD_STATE_E.
RFC 9846, 4.3
TranslateErrorToAlert() maps BUFFER_E to decode_error as well as
BUFFER_ERROR — one case label covering 27 malformed-extension sites that
previously aborted with no alert sent.
Tests added.
- ISOTP_Receive() waits for the first frame in a loop that exits only
on a non-zero result, and now handles just the negative case; the
WOLFSSL_CBIO_ERR_TIMEOUT return is gone and a comment records that
nothing times out between messages.
- The wolfSSL_SetIO_ISOTP() doxygen block states what recv_fn returns
for a frame, for no frame within timeout, and for an error.
Issue: F-12563
- iotsafe_init(): free the mutex on the ATE0 and drain failure paths
so a retried init never double-initializes it
- guard every exported API entry (wc_iotsafe_*, wolfIoTSafe_GetCert_ex,
wolfIoTSafe_GetRandom) against locking an uninitialized mutex via
iotsafe_ensure_init(), which lazy-initializes the port before locking
- doxygen: wolfIoTSafe_SetCSIM_write_cb param rf -> wf
All IoT-Safe operations share the file-scope static command/response
buffers on a single CSIM channel. Concurrent operations (e.g. a random
generation and an ECDSA sign callback from different TLS sessions)
could overwrite each other's buffers and consume the wrong modem
reply, corrupting cryptographic output or leaving the applet in a
persistent error state.
Add a port-level mutex (active in multi-threaded builds, no-op when
SINGLE_THREADED) held across each APDU transaction. The seven
single-transaction operations get thin serialized wrappers. GetRandom
locks after its lazy-init check, and the ECDH callback locks its APDU
branch, calling the locked impls directly since it already holds the
mutex (the software fallback never takes the lock). The mutex is
created in iotsafe_init(); document the single-threaded init contract
in the doxygen of wolfSSL_CTX_iotsafe_enable() and the CSIM callback
setters.
Fixes F-10045.
Mirror curve25519: keygen, shared secret, make pub and generic
callbacks, wc_curve448_init_ex/new/delete, new wc_curve448_generic
API with scalar clamp checks, WOLF_CRYPTO_CB_ONLY_CURVE448 mode,
TLS devId plumbing, tests (test.c, api unit tests, swdev),
benchmark devId, CI entries.