Conversation
Offload ML-DSA operations onto a PKCS#11 token via the cryptoCb interface: * Key generation * Signature generation * Signature verification * Key import Both the pure and pre-hash versions are supported. Not yet supported are the pre-hash versions that also offload the hashing onto the token. This also fixes casting errors introduces in wolfSSL#9780 due to usage of uintptr_t, which is unavailable without including stdint.h on some platforms. Use the wolfssl own wc_ptr_t instead.
0d9a00d to
ddf8b8a
Compare
There was a problem hiding this comment.
Pull request overview
This pull request adds ML-DSA (Module-Lattice-Based Digital Signature Algorithm, formerly known as Dilithium) signature support to the PKCS#11 interface, enabling hardware security module offloading for post-quantum cryptographic operations. The implementation follows the established patterns for RSA and ECC in the wolfSSL PKCS#11 integration, and also fixes casting errors introduced in PR #9780 by replacing uintptr_t with the portable wc_ptr_t type.
Changes:
- Added ML-DSA key type, mechanisms, and parameter structures to PKCS#11 header definitions
- Implemented ML-DSA key generation, signing, verification, import, and private key checking functions
- Fixed casting issues using
wc_ptr_tinstead ofuintptr_tfor better portability - Added new macro wrappers for ML-DSA functions to support upcoming Dilithium to ML-DSA naming transition
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| wolfssl/wolfcrypt/wc_pkcs11.h | Added PKCS11_KEY_TYPE_MLDSA enum value for ML-DSA key type |
| wolfssl/wolfcrypt/pkcs11.h | Added PKCS#11 constants (CKK_ML_DSA, CKM_ML_DSA, etc.) and structures for ML-DSA parameter sets and signing contexts |
| wolfssl/wolfcrypt/dilithium.h | Added macro wrappers for sign/verify context hash functions and key decode functions to support naming transition |
| wolfcrypt/src/wc_pkcs11.c | Core implementation: ML-DSA key creation, finding, generation, signing, verification, and checking functions; fixed casting from uintptr_t to wc_ptr_t |
| .wolfssl_known_macro_extras | Added NO_PKCS11_MLDSA macro for disabling ML-DSA PKCS#11 support |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| else { | ||
| /* Set the context data */ | ||
| if (info->pk.pqc_sign.context != NULL && | ||
| info->pk.pqc_sign.contextLen > 0) { | ||
| paramSet.pure.pContext = (byte*) info->pk.pqc_sign.context; | ||
| paramSet.pure.ulContextLen = info->pk.pqc_sign.contextLen; | ||
|
|
||
| paramSet.pure.hedgeVariant = CKH_HEDGE_REQUIRED; | ||
|
|
||
| mech.pParameter = ¶mSet.pure; | ||
| mech.ulParameterLen = sizeof(paramSet.pure); | ||
| } | ||
| } |
There was a problem hiding this comment.
The hedgeVariant field is not initialized when signing without preHash and no context is provided. The paramSet union is uninitialized, and when no context is provided in the pure case, the code doesn't set mech.pParameter or mech.ulParameterLen, but also doesn't set hedgeVariant. If the mechanism is used later, this could lead to undefined behavior. Consider initializing the union to zero or always setting hedgeVariant when the parameter structure is used.
| if (ret == 0) { | ||
| /* Prepare mechanism structure */ | ||
| mech.mechanism = CKM_ML_DSA; | ||
| mech.ulParameterLen = 0; | ||
| mech.pParameter = NULL; | ||
|
|
||
| if (info->pk.pqc_verify.preHashType != WC_HASH_TYPE_NONE) { | ||
| /* Set the preHash algorithm */ | ||
| ret = Pkcs11GetMldsaPreHash(info->pk.pqc_verify.preHashType, | ||
| ¶mSet.preHash.hash); | ||
| if (ret == 0) { | ||
| mech.mechanism = CKM_HASH_ML_DSA; | ||
| mech.pParameter = ¶mSet.preHash; | ||
| mech.ulParameterLen = sizeof(paramSet.preHash); | ||
| } | ||
|
|
||
| /* Set the context data */ | ||
| if (info->pk.pqc_verify.context != NULL && | ||
| info->pk.pqc_verify.contextLen > 0) { | ||
| paramSet.preHash.pContext = (byte*) info->pk.pqc_verify.context; | ||
| paramSet.preHash.ulContextLen = info->pk.pqc_verify.contextLen; | ||
| } | ||
| else { | ||
| paramSet.preHash.pContext = NULL; | ||
| paramSet.preHash.ulContextLen = 0; | ||
| } | ||
| } | ||
| else { | ||
| /* Set the context data */ | ||
| if (info->pk.pqc_verify.context != NULL && | ||
| info->pk.pqc_verify.contextLen > 0) { | ||
| paramSet.pure.pContext = (byte*) info->pk.pqc_verify.context; | ||
| paramSet.pure.ulContextLen = info->pk.pqc_verify.contextLen; | ||
|
|
||
| mech.pParameter = ¶mSet.pure; | ||
| mech.ulParameterLen = sizeof(paramSet.pure); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The hedgeVariant field is never set in the verification path, neither in the preHash case nor in the pure case. While verification might not require hedgeVariant to be set according to the PKCS#11 specification, this is inconsistent with the signing path where it's always set for preHash. Consider initializing the paramSet union to zero or explicitly setting hedgeVariant for consistency.
| if (ret == 0) | ||
| ret = Pkcs11GetMldsaPublicKey(key, session, pubKey); | ||
|
|
||
| if (ret != 0 && privKey != NULL_PTR) | ||
| session->func->C_DestroyObject(session->handle, privKey); |
There was a problem hiding this comment.
The public key object created by C_GenerateKeyPair should be destroyed after extracting its data, similar to how it's done in RSA (line 2890) and ECC (line 3137) key generation. Add cleanup for pubKey: if (pubKey != NULL_PTR) session->func->C_DestroyObject(session->handle, pubKey); before the private key cleanup.
|
|
||
| /** | ||
| * Performs the AES-GCM decryption operation. | ||
| * Convert the wolfCryp hash type to a digest mechanism. |
There was a problem hiding this comment.
Typo in comment: "wolfCryp" should be "wolfCrypt".
| * Convert the wolfCryp hash type to a digest mechanism. | |
| * Convert the wolfCrypt hash type to a digest mechanism. |
| } | ||
|
|
||
| /** | ||
| * Perform an PQC key generation operation. |
There was a problem hiding this comment.
Grammar issue in comment: "Perform an PQC" should be "Perform a PQC" (use "a" not "an" before "PQC" since PQC is pronounced with a consonant sound).
| * Perform an PQC key generation operation. | |
| * Perform a PQC key generation operation. |
| } | ||
|
|
||
| /** | ||
| * Perform an PQC key generation operation. |
There was a problem hiding this comment.
Grammar issue in comment: "Perform an PQC" should be "Perform a PQC" (use "a" not "an" before "PQC" since PQC is pronounced with a consonant sound).
| * Perform an PQC key generation operation. | |
| * Perform a PQC key generation operation. |
| { CKA_CLASS, &keyClass, sizeof(keyClass) }, | ||
| { CKA_KEY_TYPE, &mldsaKeyType, sizeof(ecKeyType) }, | ||
| { CKA_PARAMETER_SET, ¶m_set, sizeof(param_set) }, |
There was a problem hiding this comment.
The sizeof calculation should use mldsaKeyType instead of ecKeyType. This is a copy-paste error that causes the wrong size to be used for the ML-DSA key type attribute.
| { CKA_CLASS, &keyClass, sizeof(keyClass) }, | |
| { CKA_KEY_TYPE, &mldsaKeyType, sizeof(ecKeyType) }, | |
| { CKA_PARAMETER_SET, ¶m_set, sizeof(param_set) }, | |
| { CKA_CLASS, &keyClass, sizeof(keyClass) }, | |
| { CKA_KEY_TYPE, &mldsaKeyType, sizeof(mldsaKeyType) }, | |
| { CKA_PARAMETER_SET, ¶m_set, sizeof(param_set) }, |
| pubKeyTmpl[pubTmplCnt].pValue = key->label; | ||
| pubKeyTmpl[pubTmplCnt].ulValueLen = key->labelLen; |
There was a problem hiding this comment.
When setting the public key template for CKA_ID, the code incorrectly uses key->label and key->labelLen instead of key->id and key->idLen. This will cause the public key ID to be set incorrectly to the label value.
| pubKeyTmpl[pubTmplCnt].pValue = key->label; | |
| pubKeyTmpl[pubTmplCnt].ulValueLen = key->labelLen; | |
| pubKeyTmpl[pubTmplCnt].pValue = key->id; | |
| pubKeyTmpl[pubTmplCnt].ulValueLen = key->idLen; |
|
|
||
| #ifdef WOLFSSL_SMALL_STACK | ||
| if (ret == 0) { | ||
| pubKey = (MlDsaKey*)XMALLOC(sizeof(MlDsaKey), pubKey->heap, |
There was a problem hiding this comment.
The XMALLOC call attempts to use pubKey->heap when pubKey is still NULL. This will cause a null pointer dereference. The code should use privKey->heap instead, as done elsewhere in the function.
| pubKey = (MlDsaKey*)XMALLOC(sizeof(MlDsaKey), pubKey->heap, | |
| pubKey = (MlDsaKey*)XMALLOC(sizeof(MlDsaKey), privKey->heap, |
This PR adds ML-DSA signature support to the PKCS#11 interface.
It enables offloading the following ML-DSA operations onto a PKCS#11 token via the cryptoCb interface:
Both the pure and pre-hash versions of ML-DSA are supported. Not yet supported are the pre-hash versions that also offload the hashing onto the token.
This also fixes casting errors introduced in #9780 due to the usage of
uintptr_t, which is unavailable without includingstdint.hon some platforms. We now use the ownwc_ptr_tinstead.Within
wc_pkcs11.c, the newMlDsa_xxxfunctions are already used to simplify the upcoming name change from Dilithium to MlDsa. For that, some new macros have been added to cover all required functions.The new functionality has been tested successfully with wolfPKCS11 using wolfSSL/wolfPKCS11#161 as well as with a prototype PKCS#11 middleware for a new PQC-capable secure element from [redacted].