FwSignDigestAndAppend and FwVerifySignatureCore sized their PKCS#1
v1.5 DigestInfo buffers as WC_MAX_DIGEST_SIZE + MAX_ENCODED_DIG_ASN_SZ,
which leaves out the outer SEQUENCE and the AlgorithmIdentifier. A
SHA-512 DigestInfo is 83 bytes. Size both buffers with
MAX_DER_DIGEST_SZ, the constant wolfCrypt uses for this encoding.
- Reject TPM2_Sign with TPM_RC_SIZE when the digest size does not
match the resolved scheme hashAlg (Part 3 Sec.20.2.1), matching
FwCmd_SignDigest.
- examples/pcr/policy_sign: size the DigestInfo buffer with
WOLFTPM_MAX_DER_DIGEST_SZ, since older wolfSSL does not export
MAX_DER_DIGEST_SZ.
- tests: add RSASSA-SHA512 Sign/VerifySignature and Sign digest-size
mismatch tests, run the SignDigest RSA roundtrip with SHA-512 too,
and move BuildRsa2048SignPrimary out of the WOLFTPM_V185 block so
builds without v1.85 can use it.
Cover the policyAuth branch: sign with a PolicyAuthValue-only ML-DSA
key whose policy session sits in slot 1, then assert the session
handle stays, its nonceTPM advances, and a second policy + sign on the
same session succeeds. The session is bound so the wrapper's
PolicyAuthValue HMAC key has a session key in front of the authValue.
When the TPM rejects SignSequenceStart, the sign helper sets sigSz to
zero and returns. The slot-1 session checks still ran, and with a
free slot 2 the nonce assertion failed because nothing advanced the
nonce, aborting the suite. Unload the key and return once the first
sign produces no signature.
The loop advanced both idx and the in pointer, so each check after the
first read two bytes past the intended one. An ECDSA R or S starting
00 <0x80 00 <0x80 lost a significant byte, and wolfTPM2_PK_EccSign then
DER-encoded a wrong signature that the TLS peer rejected (-330).
Only advance idx and return in + idx. Add a unit test using the S bytes
from the failing CI run.
The slot-1 session copied into slot 2 kept its Name. For a session
bound to that entity (name == bindName), TPM2_ParamEncBindKey() then
appended the bind authValue to the parameter-encryption key, but slot
2 authorizes no handle, so the TPM used the session key alone. The
command HMAC still validated, while the TPM decrypted the message to
other bytes and signed those.
Clear the Name in slot 2 and put the caller's Name back on restore,
unless the response retired the session. Add a bound-session
regression that verifies the signature over the original message.
- With MAX_SESSION_NUM=1 (--enable-smallstack --disable-wolfcrypt)
the slot-1 save and restore read and wrote past dev->session.
Compile them only when a second slot exists.
- A policy-auth key keeps its policy session in slot 1 and the
command advances its nonce; restoring the snapshot rolled that
nonce back. Skip the save and restore for policyAuth keys.
- With MAX_SESSION_NUM=2 the session cannot move to slot 2, so its
nonce does not roll. Check the rolled nonce only when slot 2
exists.
SignSequenceComplete and SignDigest signed with obj->privKey without
checking that a private seed was loaded. For a public-only ML-DSA or
Hash-ML-DSA key from TPM2_LoadExternal they returned success with a
signature from the all-zero seed, which does not verify against the
key's public area.
Return TPM_RC_KEY when privKeySize is not MAX_MLDSA_PRIV_SEED_SIZE,
matching FwSignAttestMldsa. For sequences the check is in Complete,
where the key is used: the object at keyHandle can differ from the one
given to Start while keeping the same Name.
wolfTPM2_SignSequenceComplete authorizes keyHandle with
wolfTPM2_SetAuthHandle(dev, 1, ...), which resets session slot 1 to
password auth. A caller's parameter-encryption HMAC session in slot 1
was silently dropped, so the next command (e.g. wolfTPM2_Decapsulate)
returned its secret unencrypted and the session's rolling nonce was
lost.
Save slot 1 before the command and restore it afterwards. When the
saved session is an HMAC session and slot 2 is free, run it from slot
2 for this command, where the TPM takes extra encrypt and audit
sessions, so it also protects the sign. Copy it back with the nonces
the command advanced.
Authorizing the key through the slot-1 session instead would fail
with TPM_RC_AUTH_FAIL for keys with an authValue, since an unbound
HMAC session does not carry the key's authValue.
Extend the ML-DSA key-auth sign test to sign with an HMAC session in
slot 1, check the session is kept and its nonce rolled, and verify the
signature through the same session.
tests/unit_tests.c skips a v1.85 test when the TPM answers TPM_RC_VALUE,
TPM_RC_SCHEME or TPM_RC_COMMAND_CODE, but the checks could not match.
tpm2_asn.h defines TPM_RC_VALUE as its deprecated ASN alias (-203) unless
WOLFTPM_NO_DEPRECATED_ASN_RC is set, so every TPM_RC_VALUE check in the
file compared against an ASN error. A TPM without v1.85 rejects an ML-DSA
or ML-KEM template with TPM_RC_TYPE, which no check accepted, and a
response code carrying parameter bits never matched exactly. A build with
v1.85 therefore aborted the suite at the first PQC test against such a
TPM.
The test file opts out of the ASN aliases, so the existing checks compare
against the response code. Where a v1.85 key is created,
TEST_V185_UNSUPPORTED skips only when the creation failed and the TPM does
not list the algorithm in TPM_CAP_ALGS, so on a TPM with v1.85 a creation
failure still fails the test; test_alg_supported moves up for that. The
checks that run after the key exists drop TPM_RC_VALUE, which only ever
matched the ASN alias there, and keep their other codes, so a failure of
an algorithm the TPM has shown it supports is not taken for a missing
feature. Against a fwTPM built without v1.85 the suite now runs to the
end; against one with v1.85 the results are unchanged.
wolfTPM2_CryptoDevCb ignored the id of a wolfCrypt key and always used the
single ECDSA, RSA or ML-DSA key configured in TpmCryptoDevCtx. An
application that names its keys by id, such as
wolfSSL_CTX_use_PrivateKey_Id or wolfCert's key_id, could not keep more
than one key per type in the TPM, and EC key generation with a slot
already loaded returned that key's public half instead of a new key.
An ECC, RSA or ML-DSA key whose id is 4 bytes and, read big-endian, a
persistent or transient handle now names that TPM key, so a key blob
loaded with wolfTPM2_LoadKey is usable through the handle the TPM gave it.
Key generation with an id creates a fixedTPM, fixedParent key under
storageKey with an empty auth value and noDA, since a key id carries no
auth. A persistent handle must be free, and the key is persisted there
with EvictControl, which needs an empty owner authorization. The new id
WOLFTPM2_KEYID_TRANSIENT (0x80000000) leaves the key loaded at the
transient handle the TPM picks and writes that handle back into the id;
the caller unloads it with wolfTPM2_UnloadHandle. Any other id, an RSA
size the export cannot hold, and a wolfCrypt key that already holds a key
fail key generation instead of falling back to the slot or to software,
and so does a curve shorter than its hash (P-192 and P-224 with SHA-256),
whose every signature the signing path would refuse.
The last one matters because the TPM may itself pick 0x80000000 for a
generated key, and a second keygen on that wolfCrypt key would then create
another one and leave the first unreferenced.
Signing, RSA private, public and get-size operations read the key at the
handle and fail when it does not fit the request: a different type, a
restricted key such as a storage key, an RSA key with a padding scheme
or without decrypt, an ECC key with a scheme other than ECDSA or on
another curve, an ML-DSA key of another parameter set, or a wolfCrypt
key holding a public key other than the TPM key's or only a private key.
An RSA public key must match in both modulus and exponent, with a TPM
exponent of 0 read as 65537. ECDSA signing also fails when the key's
fixed scheme hash is shorter than the input or longer than the curve,
because the TPM fits the input to that hash and the signature would not
verify. Otherwise an ML-DSA request naming a HashML-DSA key would return
that key's signature as success, and a transient handle the TPM handed
to another key would sign for it. A key with the NULL scheme signs ECDSA
with the curve's hash under the same check; the hash is set explicitly
because the parsed public area carries none for a NULL scheme.
wolfTPM2_ReadPublicKey now zeroes its output, so that field no longer
holds stack contents. A build without HAVE_ECC_KEY_EXPORT or
WOLFSSL_MLDSA_PUBLIC_KEY cannot compare the public key, so it refuses a
wolfCrypt key that holds one. Public RSA operations on an RsaKey without
a modulus run on the TPM key, since an RsaKey named by id has none;
wolfSSL needs them to check its own RSA signature in TLS. An RsaKey with
a modulus keeps using it. The doxygen note lists these requirements,
including that a key named by id is used with an empty authorization
value, that an id-named ML-DSA key needs wc_MlDsaKey_SetParams, and that
without a public key in the wolfCrypt key a transient id names whatever
key is loaded at the handle.
A 4-byte id that only reads as a handle in little-endian order fails
instead of reaching the slot. Ids of any other length or value keep the
slot behaviour for signing, so the 8-byte ML-DSA placeholder id in the TLS
example is unaffected. wolfSSL passes a seeded ML-DSA key generation as
WC_PK_TYPE_PQC_SIG_KEYGEN_SEED, which the callback leaves to software.
ML-DSA key generation by id needs a wolfSSL with ML-DSA key generation.
Failures on these paths reach wolfCrypt as WC_HW_E. A positive TPM_RC
would be taken as a signature length by wolfSSL's certificate signing.
test_wolfTPM2_CryptoDevCb_KeyId and test_wolfTPM2_CryptoDevCb_MlDsaKeyId
generate keys by id against the TPM, sign with id-only keys, verify in
software, and check that a missing handle, a missing storage key, an
occupied handle, a curve mismatch and an id naming a key of another type
or ML-DSA parameter set are refused. They and
test_wolfTPM2_CryptoDevCb_TransientKeyId also cover transient key
generation and its write-back, signing through a loaded key blob,
unloading, a public key mismatch for ECC, RSA and ML-DSA, a wolfCrypt key
holding only a private key for ECC and ML-DSA, RSA public operations with
the RsaKey's own modulus, refusal of byte-swapped, short and other
transient ids, and refusal of keygen into a key that already holds one.
test_wolfTPM2_CryptoDevCb_KeyIdRefused names a restricted RSA storage
key, an RSA key with the OAEP scheme, a sign-only RSA key, an ECDH key
and a P-256 key with ECDSA-SHA384 by id and checks that each is refused,
that an RSA key created with exponent 0 signs for its public key, that a
P-256 key with the NULL scheme signs a hash that verifies, that a P-384
key with ECDSA-SHA256 refuses a SHA-384 input and signs a SHA-256 one
that verifies, and that RSA key generation above
WOLFTPM2_WRAP_RSA_KEY_BITS leaves no transient key behind, as does P-224
key generation when wolfSSL has P-224. The transient test also refuses
an RsaKey holding the TPM modulus with another exponent. The ML-DSA key
id test needs wolfSSL ML-DSA key generation and level 65, and checks
level 87 only when built in. The ML-DSA test skips on a TPM without
ML-DSA, and the persistent handle tests skip with the Windows TBS
interface, which blocks EvictControl. The tests that create an RSA key
in the TPM skip on a small-stack fwTPM build, whose reduced response
buffer cannot hold an RSA Create response.
wolfTPM2_CryptoDevCb passed the whole message to SignSequenceComplete and
rejected anything longer than MAX_DIGEST_BUFFER with BAD_FUNC_ARG. That
blocks certificate and CSR signing with a TPM ML-DSA key, since the signed
body carries the 1.3 to 2.6 KB public key. Input beyond one buffer now goes
through SignSequenceUpdate before the final SignSequenceComplete; input that
fits is still sent in one Complete.
The ML-DSA sign test drops the over-size BAD_FUNC_ARG case and signs a
message of two buffers plus 100 bytes, verified with a TPM verify sequence.