Skip to content

Add support for ML-DSA in PKCS#11#9836

Open
Frauschi wants to merge 1 commit intowolfSSL:masterfrom
Frauschi:pkcs11_dilithium
Open

Add support for ML-DSA in PKCS#11#9836
Frauschi wants to merge 1 commit intowolfSSL:masterfrom
Frauschi:pkcs11_dilithium

Conversation

@Frauschi
Copy link
Contributor

@Frauschi Frauschi commented Feb 26, 2026

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:

  • Key generation
  • Signature generation
  • Signature verification
  • Key import
  • Private key checking

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 including stdint.h on some platforms. We now use the own wc_ptr_t instead.

Within wc_pkcs11.c, the new MlDsa_xxx functions 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].

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.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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_t instead of uintptr_t for 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.

Comment on lines +4324 to +4336
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 = &paramSet.pure;
mech.ulParameterLen = sizeof(paramSet.pure);
}
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +4428 to +4466
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,
&paramSet.preHash.hash);
if (ret == 0) {
mech.mechanism = CKM_HASH_ML_DSA;
mech.pParameter = &paramSet.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 = &paramSet.pure;
mech.ulParameterLen = sizeof(paramSet.pure);
}
}
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +4228 to +4232
if (ret == 0)
ret = Pkcs11GetMldsaPublicKey(key, session, pubKey);

if (ret != 0 && privKey != NULL_PTR)
session->func->C_DestroyObject(session->handle, privKey);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.

/**
* Performs the AES-GCM decryption operation.
* Convert the wolfCryp hash type to a digest mechanism.
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Typo in comment: "wolfCryp" should be "wolfCrypt".

Suggested change
* Convert the wolfCryp hash type to a digest mechanism.
* Convert the wolfCrypt hash type to a digest mechanism.

Copilot uses AI. Check for mistakes.
}

/**
* Perform an PQC key generation operation.
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
* Perform an PQC key generation operation.
* Perform a PQC key generation operation.

Copilot uses AI. Check for mistakes.
}

/**
* Perform an PQC key generation operation.
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
* Perform an PQC key generation operation.
* Perform a PQC key generation operation.

Copilot uses AI. Check for mistakes.
Comment on lines +3970 to +3972
{ CKA_CLASS, &keyClass, sizeof(keyClass) },
{ CKA_KEY_TYPE, &mldsaKeyType, sizeof(ecKeyType) },
{ CKA_PARAMETER_SET, &param_set, sizeof(param_set) },
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
{ CKA_CLASS, &keyClass, sizeof(keyClass) },
{ CKA_KEY_TYPE, &mldsaKeyType, sizeof(ecKeyType) },
{ CKA_PARAMETER_SET, &param_set, sizeof(param_set) },
{ CKA_CLASS, &keyClass, sizeof(keyClass) },
{ CKA_KEY_TYPE, &mldsaKeyType, sizeof(mldsaKeyType) },
{ CKA_PARAMETER_SET, &param_set, sizeof(param_set) },

Copilot uses AI. Check for mistakes.
Comment on lines +4206 to +4207
pubKeyTmpl[pubTmplCnt].pValue = key->label;
pubKeyTmpl[pubTmplCnt].ulValueLen = key->labelLen;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
pubKeyTmpl[pubTmplCnt].pValue = key->label;
pubKeyTmpl[pubTmplCnt].ulValueLen = key->labelLen;
pubKeyTmpl[pubTmplCnt].pValue = key->id;
pubKeyTmpl[pubTmplCnt].ulValueLen = key->idLen;

Copilot uses AI. Check for mistakes.

#ifdef WOLFSSL_SMALL_STACK
if (ret == 0) {
pubKey = (MlDsaKey*)XMALLOC(sizeof(MlDsaKey), pubKey->heap,
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
pubKey = (MlDsaKey*)XMALLOC(sizeof(MlDsaKey), pubKey->heap,
pubKey = (MlDsaKey*)XMALLOC(sizeof(MlDsaKey), privKey->heap,

Copilot uses AI. Check for mistakes.
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.

3 participants