Skip to content

Conversation

@antoinelochet
Copy link
Contributor

@antoinelochet antoinelochet commented Aug 18, 2025

Adds ML-DSA support with OpenSSL >= 3.5

Summary by CodeRabbit

  • New Features

    • ML‑DSA support: key‑pair generation, signing/verification, parameter sets, mechanisms and attributes exposed to users.
  • Build & Configuration

    • New build option to enable ML‑DSA.
    • CI: added Ubuntu 24.04 job testing with OpenSSL 3.5.4.
  • Tests

    • New ML‑DSA test suite covering generation, serialization/PKCS#8, signing/verification and edge cases.
  • Documentation

    • README and CMake notes updated with ML‑DSA configuration option.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

📝 Walkthrough

Walkthrough

Adds ML‑DSA support across build systems and Autotools detection, new ML‑DSA crypto types and OpenSSL backend, PKCS#11 attributes/objects and SoftHSM routing, tests, build list updates, and a CI job for OpenSSL 3.5.4. (No public API removals.)

Changes

Cohort / File(s) Summary
Build & CI
/.github/workflows/ci.yml, CMakeLists.txt, CMAKE-NOTES.md, CMAKE-WIN-NOTES.md, README.md, config.h.in.cmake
Add ENABLE_MLDSA / --enable-mldsa option and config macro WITH_ML_DSA; docs updated; add linux_ossl_35 CI job installing OpenSSL 3.5.4.
Autotools & detection
m4/acx_crypto_backend.m4, m4/acx_openssl_mldsa.m4
Add --enable-mldsa option, ACX_OPENSSL_MLDSA probe, define WITH_ML_DSA, AM_CONDITIONAL and backend capability checks/errors.
Core ML‑DSA types
src/lib/crypto/MLDSAParameters.{h,cpp}, src/lib/crypto/MLDSAPublicKey.{h,cpp}, src/lib/crypto/MLDSAPrivateKey.{h,cpp}, src/lib/crypto/MLDSAUtil.{h,cpp}
New MLDSAParameters / MLDSAPublicKey / MLDSAPrivateKey classes with (de)serialization and PKCS#8 handling; MLDSAUtil helpers for key marshalling and context copying.
OpenSSL backend & wrappers
src/lib/crypto/OSSLMLDSA.{h,cpp}, src/lib/crypto/OSSLMLDSAKeyPair.{h,cpp}, src/lib/crypto/OSSLMLDSAPublicKey.{h,cpp}, src/lib/crypto/OSSLMLDSAPrivateKey.{h,cpp}, src/lib/crypto/OSSLUtil.{h,cpp}, src/lib/crypto/OSSLCryptoFactory.cpp
Add OpenSSL-backed ML‑DSA implementation (sign/verify/keygen/reconstruct), provider-name mapping, key wrappers and factory wiring; guarded by WITH_ML_DSA.
SoftHSM core & API
src/lib/SoftHSM.h, src/lib/SoftHSM.cpp, src/lib/crypto/AsymmetricAlgorithm.h, src/lib/crypto/CMakeLists.txt, src/lib/crypto/Makefile.am
Add MLDSA enum/mechanisms, SIGN_ADDITIONAL_CONTEXT/Hedge types, SoftHSM::generateMLDSA and mechanism exposure; include ML‑DSA sources in builds.
PKCS#11 objects & attributes
src/lib/P11Attributes.{h,cpp}, src/lib/P11Objects.{h,cpp}, src/lib/object_store/DBObject.cpp, src/bin/dump/tables.h
Add CKA_PARAMETER_SET and CKA_SEED attributes, new P11 ML‑DSA object classes, DB attribute-kind mapping, and dump table entries (CKM/CKK constants).
Key material helpers & OSSL util
src/lib/crypto/MLDSAUtil.{h,cpp}, src/lib/crypto/OSSLUtil.{h,cpp}
Helpers to load/store ML‑DSA keys (token encrypt/decrypt), copy SIGN_ADDITIONAL_CONTEXT, and map parameter set → OpenSSL algorithm name.
Tests & test integration
src/lib/crypto/test/MLDSATests.{h,cpp}, src/lib/crypto/test/CMakeLists.txt, src/lib/crypto/test/Makefile.am, src/lib/test/SignVerifyTests.{h,cpp}
Add CppUnit ML‑DSA tests (keygen, PKCS#8, sign/verify with hedge/deterministic/context variants), integrate into test targets, and extend sign test buffers.
Build lists & misc fixes
src/lib/crypto/Makefile.am, src/lib/crypto/test/CMakeLists.txt, src/lib/crypto/OSSLComp.cpp
Add ML‑DSA sources to Automake/CMake lists and a minor header-comment correction.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SoftHSM
    participant CryptoFactory
    participant OSSLMLDSA
    participant OpenSSL

    Client->>SoftHSM: C_GenerateKeyPair(CKM_ML_DSA_KEY_PAIR_GEN, params)
    SoftHSM->>SoftHSM: generateMLDSA(...)
    SoftHSM->>CryptoFactory: getAsymmetricAlgorithm(AsymAlgo::MLDSA)
    CryptoFactory->>OSSLMLDSA: instantiate OSSLMLDSA
    SoftHSM->>OSSLMLDSA: generateKeyPair(parameters)
    OSSLMLDSA->>OpenSSL: EVP_PKEY_CTX_new_from_name("ML-DSA-xx") / keygen
    OpenSSL-->>OSSLMLDSA: EVP_PKEY (key pair)
    OSSLMLDSA-->>SoftHSM: MLDSA key pair wrapper
    SoftHSM->>SoftHSM: create P11 MLDSA objects (CKA_PARAMETER_SET, CKA_VALUE, CKA_SEED)
    SoftHSM-->>Client: return object handles
Loading
sequenceDiagram
    participant Client
    participant SoftHSM
    participant Token
    participant OSSLMLDSA
    participant OpenSSL

    Client->>SoftHSM: C_SignInit(hKey, CKM_ML_DSA, pParameter)
    SoftHSM->>SoftHSM: copyMLDSAContext / AsymSignInit
    SoftHSM->>Token: retrieve/decrypt CKA_VALUE, CKA_SEED (if private)
    Token-->>SoftHSM: key bytes
    SoftHSM->>OSSLMLDSA: sign(privateKey, data, SIGN_ADDITIONAL_CONTEXT)
    OSSLMLDSA->>OpenSSL: EVP_PKEY_sign (with hedge/deterministic params)
    OpenSSL-->>OSSLMLDSA: signature bytes
    OSSLMLDSA-->>SoftHSM: signature
    SoftHSM-->>Client: return signature
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • jschlyter
  • bjosv

Poem

🐇 I nibbled headers, hopped through code,
planted seeds and params down the road.
OpenSSL hums while tests run fast,
CI will green and merge at last.
Hop, merge, and munch a carrot of code!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding ML-DSA support, which is the primary objective of this large changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@kalvdans kalvdans left a comment

Choose a reason for hiding this comment

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

The code is marked with "Copyright (c) 2010 SURFnet bv". Since you wrote it yourself, you own the copyright to the code, so remove the incorrect copyright statement.

More comments when you undraft :)

@antoinelochet antoinelochet marked this pull request as ready for review August 18, 2025 15:54
@antoinelochet antoinelochet requested a review from a team as a code owner August 18, 2025 15:54
@antoinelochet
Copy link
Contributor Author

Copyright banners on new code have been removed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 34

🔭 Outside diff range comments (3)
src/lib/SoftHSM.cpp (3)

6731-6736: Wrap/unwarp mechanism parity + DES3 key type check is always true

  • Parity: C_WrapKey allows AES_CBC/_PAD but C_UnwrapKey doesn’t allow AES_CBC at all. Either remove AES_CBC wrapping or add AES_CBC unwrapping. Same for DES3_CBC: WrapKeySym supports it but C_WrapKey rejects it due to a logic bug (see below) and C_UnwrapKey lacks a corresponding case.
  • Logic bug: The DES3 key-type check uses “!= DES2 || != DES3” which is tautologically true; it should be “&&”.
         case CKM_AES_CBC:
         case CKM_AES_CBC_PAD:
             if (pMechanism->pParameter == NULL_PTR ||
                 pMechanism->ulParameterLen != 16)
                 return CKR_ARGUMENTS_BAD;
             break;
+        case CKM_DES3_CBC:
+        case CKM_DES3_CBC_PAD:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != 8)
+                return CKR_ARGUMENTS_BAD;
+            break;
@@
-    if (pMechanism->mechanism == CKM_DES3_CBC && (wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 ||
-        wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
+    if (pMechanism->mechanism == CKM_DES3_CBC &&
+        (wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 &&
+         wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
         return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;

Follow-up: see UnwrapKeySym and C_UnwrapKey changes below to restore symmetry.

Also applies to: 6776-6779


7015-7024: Add AES_CBC/DES3_CBC support in UnwrapKeySym to match wrapping

Currently only PAD variants are supported. Either drop non-PAD wrapping or add non-PAD unwrapping. Here is the latter:

         case CKM_AES_CBC_PAD:
             algo = SymAlgo::AES;
             blocksize = 16;
             break;
+        case CKM_AES_CBC:
+            algo = SymAlgo::AES;
+            blocksize = 16;
+            break;
@@
         case CKM_DES3_CBC_PAD:
             algo = SymAlgo::DES3;
             blocksize = 8;
             break;
+        case CKM_DES3_CBC:
+            algo = SymAlgo::DES3;
+            blocksize = 8;
+            break;
@@
-    case CKM_AES_CBC_PAD:
-    case CKM_DES3_CBC_PAD:
+    case CKM_AES_CBC_PAD:
+    case CKM_DES3_CBC_PAD:
     {
         iv.resize(blocksize);
         memcpy(&iv[0], pMechanism->pParameter, blocksize);
@@
         keydata += decryptedFinal;
 
         if(!RFC5652Unpad(keydata,blocksize))
         {
             cipher->recycleKey(unwrappingkey);
             CryptoFactory::i()->recycleSymmetricAlgorithm(cipher);
             return CKR_GENERAL_ERROR; // TODO should be another error
         }
         break;
+    }
+    case CKM_AES_CBC:
+    case CKM_DES3_CBC:
+    {
+        iv.resize(blocksize);
+        memcpy(&iv[0], pMechanism->pParameter, blocksize);
+
+        if (!cipher->decryptInit(unwrappingkey, SymMode::CBC, iv, false))
+        {
+            cipher->recycleKey(unwrappingkey);
+            CryptoFactory::i()->recycleSymmetricAlgorithm(cipher);
+            return CKR_MECHANISM_INVALID;
+        }
+        if (!cipher->decryptUpdate(wrapped, keydata))
+        {
+            cipher->recycleKey(unwrappingkey);
+            CryptoFactory::i()->recycleSymmetricAlgorithm(cipher);
+            return CKR_GENERAL_ERROR;
+        }
+        // Finalize decryption
+        if (!cipher->decryptFinal(decryptedFinal))
+        {
+            cipher->recycleKey(unwrappingkey);
+            CryptoFactory::i()->recycleSymmetricAlgorithm(cipher);
+            return CKR_GENERAL_ERROR;
+        }
+        keydata += decryptedFinal;
+        break;
     }

Also applies to: 7051-7083


7361-7374: C_UnwrapKey parity with wrapping + fix DES3 key-type check

Allow non-PAD AES_CBC/DES3_CBC with proper IV length checks and fix the DES3 key-type check.

         case CKM_RSA_AES_KEY_WRAP:
             rv = MechParamCheckRSAAESKEYWRAP(pMechanism);
             if (rv != CKR_OK)
                 return rv;
             break;
         case CKM_AES_CBC:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != 16)
+                return CKR_ARGUMENTS_BAD;
+            break;
+        case CKM_DES3_CBC:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != 8)
+                return CKR_ARGUMENTS_BAD;
+            break;
         case CKM_AES_CBC_PAD:
             // TODO check block length
             if (pMechanism->pParameter == NULL_PTR ||
                 pMechanism->ulParameterLen != 16)
                 return CKR_ARGUMENTS_BAD;
             break;
@@
-    if (pMechanism->mechanism == CKM_DES3_CBC && (unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 ||
-        unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
+    if (pMechanism->mechanism == CKM_DES3_CBC &&
+        (unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 &&
+         unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
         return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;

Also applies to: 7415-7417

♻️ Duplicate comments (1)
src/lib/SoftHSM.cpp (1)

13004-13005: Re: make helper methods static or move them out of the class (still applicable)

The previous remark about marking these helpers static or moving them out applies here as well; the implicit ‘this’ is not used.

If you want, I can prepare a follow-up patch to make all get/set*Key helpers static free functions for consistency.

🧹 Nitpick comments (60)
src/lib/crypto/test/CMakeLists.txt (1)

25-25: Guard MLDSATests.cpp behind ENABLE_MLDSA

ML-DSA tests are currently always compiled, even when the feature is disabled; wrapping them in the existing ENABLE_MLDSA option prevents needless builds and keeps backend-only configurations working.

• File: src/lib/crypto/test/CMakeLists.txt (around line 25)

Apply this diff to remove the unconditional entry and append it only when ENABLE_MLDSA is on:

--- a/src/lib/crypto/test/CMakeLists.txt
+++ b/src/lib/crypto/test/CMakeLists.txt
@@ –22,7 +22,10 @@ set(SOURCES
     PKCS11ModuleTests.cpp
     PKCS11SessionTests.cpp
     PKCS11ObjectTests.cpp
-    MLDSATests.cpp
 )
 
+# Only build ML-DSA tests when the feature is enabled
+if(ENABLE_MLDSA)
+    list(APPEND SOURCES MLDSATests.cpp)
+endif()
README.md (2)

85-85: Replace hard tab with spaces (MD010) on the new option line

markdownlint flagged a hard tab on this newly added line. Use spaces for indentation to satisfy MD010.

Apply this diff:

-	--enable-mldsa		Enable support for ML-DSA (default detect)
+    --enable-mldsa      Enable support for ML-DSA (default detect)

85-85: Clarify minimum OpenSSL version required for ML-DSA

Since ML-DSA support is only available with OpenSSL >= 3.5 (per the PR description), it would help users if we document this requirement alongside the flag or in the Dependencies section.

Proposed wording to add under “Dependencies” or as a parenthetical on the option line:

  • “ML-DSA requires OpenSSL 3.5 or newer when using the OpenSSL backend.”
src/lib/crypto/CMakeLists.txt (2)

75-78: Gate ML-DSA sources on ENABLE_MLDSA in CMake

To avoid compiling empty or unsupported ML-DSA wrappers when the feature is disabled (or OpenSSL lacks ML-DSA), wrap the four OSSLMLDSA source files in the existing WITH_OPENSSL block under an ENABLE_MLDSA guard, and define WITH_ML_DSA when enabled.

• File: src/lib/crypto/CMakeLists.txt (around lines 75–78)

 if(WITH_OPENSSL)
     list(APPEND SOURCES
         OSSLCryptoFactory.cpp
         OSSLEDDSA.cpp
-          OSSLMLDSA.cpp
-          OSSLMLDSAKeyPair.cpp
-          OSSLMLDSAPrivateKey.cpp
-          OSSLMLDSAPublicKey.cpp
     )
+
+      if(ENABLE_MLDSA)
+        list(APPEND SOURCES
+          OSSLMLDSA.cpp
+          OSSLMLDSAKeyPair.cpp
+          OSSLMLDSAPrivateKey.cpp
+          OSSLMLDSAPublicKey.cpp
+        )
+        target_compile_definitions(${PROJECT_NAME} PRIVATE WITH_ML_DSA)
+      endif()
 endif()

• This matches the existing #ifdef WITH_ML_DSA usage throughout the code (e.g., in OSSLMLDSA*.cpp and SoftHSM.cpp), ensuring ML-DSA files are only built—and the macro only defined—when the feature is explicitly enabled.


31-33: Only compile ML-DSA sources when ENABLE_MLDSA is ON

Currently, the three ML-DSA files are unconditionally appended to the SOURCES list in src/lib/crypto/CMakeLists.txt, which defeats the purpose of the ENABLE_MLDSA option defined at the project root (CMakeLists.txt:11). Wrapping them in an if(ENABLE_MLDSA) both avoids unnecessary compile costs and keeps the build graph consistent with other algorithm flags.

Location
• src/lib/crypto/CMakeLists.txt: lines 31–33

Suggested diff

-            MLDSAParameters.cpp
-            MLDSAPrivateKey.cpp
-            MLDSAPublicKey.cpp

Insert after the main list(APPEND SOURCES …) block:

# ML-DSA core sources
if(ENABLE_MLDSA)
    list(APPEND SOURCES
        MLDSAParameters.cpp
        MLDSAPrivateKey.cpp
        MLDSAPublicKey.cpp
    )
endif()

Verify that ENABLE_MLDSA is declared (found in root CMakeLists.txt at line 11).

.github/workflows/ci.yml (3)

90-101: Make OpenSSL 3.5.2 discoverable at runtime; avoid brittle symlink hacks.

Relying on a lib64→lib symlink is fragile. Prefer registering the library path with ldconfig, or export LD_LIBRARY_PATH for the build/test steps.

For a robust setup, append after “sudo make install”:

           sudo make install > install.log
-          cd ${{ env.OPENSSL_INSTALL_DIR }}
-          sudo ln -s lib64 lib
+          echo "${{ env.OPENSSL_INSTALL_DIR }}/lib64" | sudo tee /etc/ld.so.conf.d/openssl-3.5.conf
+          sudo ldconfig

Alternatively (if avoiding system config changes), export LD_LIBRARY_PATH in Build/Test steps:

       - name: Build
         env:
           # Once all OpenSSL deprecations fixed, uncomment this
           #   CXXFLAGS: -Werror
           OPENSSL_INSTALL_DIR: /usr/local/openssl-3.5
-          LDFLAGS: "-Wl,-R/usr/local/openssl-3.5/lib64 -L/usr/local/openssl-3.5/lib64"
+          LDFLAGS: "-Wl,-rpath,/usr/local/openssl-3.5/lib64 -L/usr/local/openssl-3.5/lib64"
+          LD_LIBRARY_PATH: "/usr/local/openssl-3.5/lib64:${LD_LIBRARY_PATH}"

97-99: Surface build/install logs on failure for faster debugging.

Currently build/install logs are redirected to files and lost on failure.

Emit the tail of the logs on failure:

-          make -j$(nproc) > build.log
-          sudo make install > install.log
+          set -e
+          make -j"$(nproc)" > build.log || { echo '--- build.log (tail) ---'; tail -n +1 build.log; exit 1; }
+          sudo make install > install.log || { echo '--- install.log (tail) ---'; tail -n +1 install.log; exit 1; }

91-91: Trim unnecessary packages to speed up CI.

checkinstall and sudo are not needed on GitHub-hosted Ubuntu runners for this build. Consider dropping them.

-          sudo apt-get install -y libcppunit-dev p11-kit build-essential checkinstall zlib1g-dev sudo autoconf libtool git
+          sudo apt-get install -y libcppunit-dev p11-kit zlib1g-dev autoconf libtool git
src/lib/pkcs11/pkcs11.h (1)

1706-1751: PQ context structs and parameter-set enums: align comment and ensure ABI expectations.

  • Minor: Comments say “CKA_PARAMETER_SETS” while the attribute is singular CKA_PARAMETER_SET.
  • Struct field naming matches PKCS #11 style. Ensure no packing/alignment directives are needed beyond the existing header guarantees.

Apply this cosmetic comment fix:

-/* ML-DSA values for CKA_PARAMETER_SETS */
+/* ML-DSA values for CKA_PARAMETER_SET */
...
-/* SLH-DSA values for CKA_PARAMETER_SETS */
+/* SLH-DSA values for CKA_PARAMETER_SET */
...
-/* ML-KEM values for CKA_PARAMETER_SETS */
+/* ML-KEM values for CKA_PARAMETER_SET */
src/bin/dump/tables.h (2)

483-485: Add mappings for ML-DSA mechanisms: good start; consider extending to hash variants.

CKM_ML_DSA[_KEY_PAIR_GEN] are mapped. For completeness, you may also add the CKM_HASH_ML_DSA* entries so dumps display those by name when encountered.

I can prepare a patch adding the CKM_HASH_ML_DSA* identifiers if you want to include them now.


551-551: CKK_ML_DSA mapping is fine; optionally add CKK_ML_KEM while you're here.

Even if KEM isn’t enabled yet, mapping the constant improves dump readability.

For example:

  t[CKK_EC_EDWARDS] = "CKK_EC_EDWARDS";
+ t[CKK_ML_KEM] = "CKK_ML_KEM";
  t[CKK_ML_DSA] = "CKK_ML_DSA";
CMAKE-WIN-NOTES.md (1)

55-55: Replace hard tab with spaces to satisfy markdownlint (MD010).

The linter flags a hard tab on this line.

-	-DENABLE_MLDSA=ON               Enable support for ML-DSA
+    -DENABLE_MLDSA=ON               Enable support for ML-DSA
src/lib/crypto/test/Makefile.am (1)

27-27: Ensure MLDSATests.cpp is gated when ML-DSA is disabled/backends lack support.

If MLDSATests.cpp compiles conditionally with #if defined(WITH_ML_DSA), you’re fine. If not, the OpenSSL 1.1.1/Botan jobs may fail to build tests.

Option A (preferred, if configure.ac defines AM_CONDITIONAL WITH_ML_DSA):

-                MLDSATests.cpp \
+if WITH_ML_DSA
+                MLDSATests.cpp \
+endif

Option B: Keep file listed but guard its contents with preprocessor in MLDSATests.cpp:

#if defined(WITH_ML_DSA)
// existing tests
#else
// compile an empty test suite or nothing
#endif

Would you like me to prepare the corresponding configure.ac change to define WITH_ML_DSA for Automake?

src/lib/crypto/Makefile.am (1)

31-33: Gate ML-DSA sources behind WITH_ML_DSA

The Autotools setup already provides AC_ARG_ENABLE(mldsa) and AM_CONDITIONAL([WITH_ML_DSA], …), so you can mirror the CMake ENABLE_MLDSA flag and avoid unconditionally compiling the ML-DSA .cpp files.

Locations to update:

  • src/lib/crypto/Makefile.am (around lines 31–33)

Suggested diff:

--- a/src/lib/crypto/Makefile.am
+++ b/src/lib/crypto/Makefile.am
@@ -28,10 +28,6 @@ libsofthsm_crypto_la_SOURCES = \
                 <other sources> \
-                MLDSAParameters.cpp \
-                MLDSAPrivateKey.cpp \
-                MLDSAPublicKey.cpp
-
-# (previous unconditional inclusion)
+# ML-DSA core sources are added only when enabled
 if WITH_ML_DSA
 libsofthsm_crypto_la_SOURCES += \
                 MLDSAParameters.cpp \
                 MLDSAPrivateKey.cpp \
                 MLDSAPublicKey.cpp
 endif
CMAKE-NOTES.md (1)

14-14: Fix markdownlint: replace hard tab with spaces; optionally note OpenSSL ≥ 3.5 for ML-DSA.

The linter flags a hard tab on this newly added line. Replace with spaces. Consider documenting the OpenSSL ≥ 3.5 requirement for ML-DSA builds.

Apply this diff:

-	-DENABLE_MLDSA=ON		Enable support for ML-DSA
+    -DENABLE_MLDSA=ON              Enable support for ML-DSA

Optionally append a note under Configure:

Note: ML-DSA requires OpenSSL 3.5 or newer when building with the OpenSSL backend.
src/lib/crypto/OSSLUtil.cpp (2)

42-49: Make the parameter-set mapping const and file-local.

The mapping is immutable; marking it const avoids accidental mutation and enables better optimization.

Apply this diff:

-#ifdef WITH_ML_DSA
-static std::map<unsigned long, const char*> mldsaAlgNameFromParameterSet {
+#ifdef WITH_ML_DSA
+static const std::map<unsigned long, const char*> mldsaAlgNameFromParameterSet{
 	{MLDSAParameters::ML_DSA_44_PARAMETER_SET, "ML-DSA-44"},
 	{MLDSAParameters::ML_DSA_65_PARAMETER_SET, "ML-DSA-65"},
 	{MLDSAParameters::ML_DSA_87_PARAMETER_SET, "ML-DSA-87"}
 };
 #endif

223-231: Use const auto (or const_iterator) for the map lookup

No mutation occurs; tightening the iterator type improves safety if mldsaAlgNameFromParameterSet ever becomes const.

  • Verified that mldsaParameterSet2Name is declared under #ifdef WITH_ML_DSA in OSSLUtil.h, matching the definition.

Apply this diff:

-const char* OSSL::mldsaParameterSet2Name(unsigned long parameterSet) {
-
-	std::map<unsigned long, const char*>::iterator it = mldsaAlgNameFromParameterSet.find(parameterSet);
-
-	if (it != mldsaAlgNameFromParameterSet.end()) {
-		return it->second;
-	}
+const char* OSSL::mldsaParameterSet2Name(unsigned long parameterSet)
+{
+	const auto it = mldsaAlgNameFromParameterSet.find(parameterSet);
+	if (it != mldsaAlgNameFromParameterSet.end())
+	{
+		return it->second;
+	}
 
 	return NULL;
 }
src/lib/crypto/MLDSAPrivateKey.cpp (3)

27-29: Fix misleading comment.

The comment says “Get the bit length” but this function returns the parameter set.

Apply this diff:

-// Get the bit length
+// Get the ML-DSA parameter set from the private key length

41-42: Tighten wording in comment.

Minor nit on phrasing.

Apply this diff:

-// Get the signatureLength length
+// Get the signature length

82-97: Avoid shadowing and optionally validate the private key size on deserialisation.

  • Local variables ‘seed’ and ‘value’ shadow members; rename for clarity.
  • Optionally, reject deserialised keys with unknown private-key length to prevent invalid state early.

Apply this diff:

-bool MLDSAPrivateKey::deserialise(ByteString& serialised)
-{
-	ByteString seed = ByteString::chainDeserialise(serialised);
-	ByteString value = ByteString::chainDeserialise(serialised);
-
-	if ((seed.size() == 0) ||
-	    (value.size() == 0))
-	{
-		return false;
-	}
-
-	setSeed(seed);
-	setValue(value);
-	
-	return true;
-}
+bool MLDSAPrivateKey::deserialise(ByteString& serialised)
+{
+	ByteString seedBS  = ByteString::chainDeserialise(serialised);
+	ByteString valueBS = ByteString::chainDeserialise(serialised);
+
+	if (seedBS.size() == 0 || valueBS.size() == 0)
+	{
+		return false;
+	}
+
+	// Optional: ensure private key length matches a known ML-DSA parameter set
+	switch (valueBS.size())
+	{
+		case MLDSAParameters::ML_DSA_44_PRIV_LENGTH:
+		case MLDSAParameters::ML_DSA_65_PRIV_LENGTH:
+		case MLDSAParameters::ML_DSA_87_PRIV_LENGTH:
+			break;
+		default:
+			return false;
+	}
+
+	setSeed(seedBS);
+	setValue(valueBS);
+	return true;
+}
src/lib/crypto/test/MLDSATests.h (3)

7-12: Consider guarding the test with WITH_ML_DSA to avoid build issues when disabled.

If the test sources are compiled unconditionally, include guards prevent build failures when ML-DSA is not enabled.

Apply this diff:

+#ifdef WITH_ML_DSA
 #include <cppunit/extensions/HelperMacros.h>
 #include "AsymmetricAlgorithm.h"
+// Forward declarations if desired:
+// class AsymmetricAlgorithm;

And add at file end (see separate comment) a matching #endif.


---

`31-34`: **Member layout is fine; prefer forward-declare AsymmetricAlgorithm in headers to reduce coupling.**

This avoids pulling extra headers into test compilations.


Example:

```diff
-#include "AsymmetricAlgorithm.h"
+class AsymmetricAlgorithm;

---

`36-36`: **Fix incorrect header-guard end comment and optionally close the WITH_ML_DSA.**

The comment says EDDSATESTS while this is MLDSATests. Also close the optional WITH_ML_DSA guard.


```diff
-#endif // !_SOFTHSM_V2_EDDSATESTS_H
+#endif // !_SOFTHSM_V2_MLDSATESTS_H
+#ifdef WITH_ML_DSA
+#endif

If you adopt the earlier WITH_ML_DSA guard, ensure exactly one #endif closes it here.


</blockquote></details>
<details>
<summary>src/lib/P11Attributes.h (1)</summary><blockquote>

`1151-1163`: **P11AttrParameterSet checks: consider requiring or allowing during GenerateKeyPair.**

For key generation, parameter set typically must be provided or derived from templates/params. Using only ck1 (CreateObject) may be too permissive/ambiguous for GenerateKeyPair. Consider including ck3 if you want to enforce it at generation time, or ensure setDefault() derives it reliably.



Would you like me to align this with existing parameter attributes (e.g., EC params) and update setDefault()/updateAttr() in P11Attributes.cpp accordingly?

</blockquote></details>
<details>
<summary>m4/acx_crypto_backend.m4 (2)</summary><blockquote>

`113-121`: **OpenSSL backend check wires correctly; ensure macro is available in all configure paths.**

The flow matches ECC/EDDSA. Make sure m4/acx_openssl_mldsa.m4 is included by aclocal/ACLOCAL_AMFLAGS so the macro is found at autoconf time.



If not already, add it to EXTRA_DIST or ensure m4/ is scanned.

---

`261-273`: **Consider disallowing ML-DSA in FIPS mode (policy alignment with GOST).**

You already block GOST when FIPS=yes. Given ML-DSA is not FIPS-approved, you may want to error when enable_fips=yes and enable_mldsa=yes to avoid misconfigured builds.


Example:

```diff
 AC_MSG_CHECKING(for ML-DSA support)
 if test "x${enable_mldsa}" = "xyes"; then
+    if test "x${enable_fips}" = "xyes"; then
+        AC_MSG_ERROR([ML-DSA is not FIPS approved])
+    fi
     AC_MSG_RESULT(yes)
     AC_DEFINE_UNQUOTED(
         [WITH_ML_DSA],
         [],
         [Compile with ML-DSA support]
     )
src/lib/crypto/OSSLMLDSAPublicKey.h (3)

15-23: Constructors/destructor are fine; ensure pkey lifetime semantics are documented.

No changes needed; destructor frees pkey. If getOSSLKey hands out the internal pointer for use by OpenSSL calls, document that callers must not free it.

Add a short note in the header comment for getOSSLKey().


37-39: getOSSLKey() returns non-const EVP_PKEY; consider const-correctness if mutation isn’t intended.*

If callers should not modify pkey, return const EVP_PKEY* (and add a non-const overload only where needed).

-EVP_PKEY* getOSSLKey();
+const EVP_PKEY* getOSSLKey() const;

And adjust the .cpp accordingly.


48-48: Fix the trailing header-guard comment.

The guard is _SOFTHSM_V2_OSSLMLDSAPUBLICKEY_H but the comment says OSSLDSAPUBLICKEY.

-#endif // !_SOFTHSM_V2_OSSLDSAPUBLICKEY_H
+#endif // !_SOFTHSM_V2_OSSLMLDSAPUBLICKEY_H
src/lib/crypto/test/MLDSATests.cpp (3)

1-6: Fix copy/paste header: EDDSA → MLDSA

The file banner still references EDDSA and EDDSATests.cpp. It should reflect MLDSA to avoid confusion.

Apply:

 /*****************************************************************************
- EDDSATests.cpp
+ MLDSATests.cpp
 
- Contains test cases to test the EDDSA class
+ Contains test cases to test the MLDSA class
  *****************************************************************************/

33-35: Comment nit: “EDDSA object” → “MLDSA object”

Align the inline comment with the class under test.

-	// Check the EDDSA object
+	// Check the MLDSA object

164-196: Signing/verification test is effective; consider adding a negative check

End-to-end sign/verify with random data is covered. Consider an additional negative test (e.g., verify with a different public key or modified data) to ensure failure paths behave correctly.

src/lib/crypto/MLDSAParameters.cpp (3)

28-31: Minor: areOfType could be const (if interface allows)

If AsymmetricParameters::areOfType permits const, marking this method const improves const-correctness. Skip if the base interface is non-const.


39-50: Optional validation: reject unsupported parameter sets on deserialise

Currently any unsigned long is accepted. If only {1,2,3} are supported, reject others early to avoid propagating invalid state.

 bool MLDSAParameters::deserialise(ByteString& serialised)
 {
-	if (serialised.size() == 0)
+	if (serialised.size() == 0)
 	{
 		return false;
 	}
 
-	setParameterSet(serialised.long_val());
+	const unsigned long ps = serialised.long_val();
+	switch (ps)
+	{
+		case 1UL:
+		case 2UL:
+		case 3UL:
+			break;
+		default:
+			return false;
+	}
+	setParameterSet(ps);
 
 	return true;
 }

7-11: Nit: unused include

log.h appears unused in this TU. Consider dropping it to keep includes minimal.

src/lib/crypto/OSSLMLDSAKeyPair.h (1)

24-31: Optional: mark overrides explicitly

Adding override clarifies intent and helps catch signature mismatches.

-	virtual PublicKey* getPublicKey();
-	virtual const PublicKey* getConstPublicKey() const;
+	virtual PublicKey* getPublicKey() override;
+	virtual const PublicKey* getConstPublicKey() const override;
@@
-	virtual PrivateKey* getPrivateKey();
-	virtual const PrivateKey* getConstPrivateKey() const;
+	virtual PrivateKey* getPrivateKey() override;
+	virtual const PrivateKey* getConstPrivateKey() const override;
src/lib/crypto/OSSLMLDSAKeyPair.cpp (1)

9-11: Remove unused include

log.h is not used here.

Apply this diff:

-#include "log.h"
 #include "OSSLMLDSAKeyPair.h"
src/lib/crypto/OSSLMLDSAPrivateKey.h (3)

13-14: Drop unnecessary header from a public header to reduce coupling

<openssl/bn.h> is not used in this header. Remove it to keep the header lean.

Apply this diff:

-#include <openssl/bn.h>
 #include <openssl/evp.h>

58-59: Fix header guard comment

Trailing comment references a different class name.

Apply this diff:

-#endif // !_SOFTHSM_V2_OSSLECPRIVATEKEY_H
+#endif // !_SOFTHSM_V2_OSSLMLDSAPRIVATEKEY_H

20-25: Consider defining safe copy semantics (optional)

Given this class owns an EVP_PKEY*, the default copy/assignment will shallow-copy the pointer and double-free on destruction. Either delete copy/assignment or implement them to duplicate/reset pkey based on ByteString value. Not strictly required if all call sites avoid copying, but it’s safer.

I can provide a patch to delete copy/assignment or to implement them by rebuilding pkey from getValue().

src/lib/crypto/MLDSAPublicKey.cpp (3)

8-12: Remove unused include

log.h isn’t used in this unit.

Apply this diff:

-#include "log.h"
 #include "MLDSAParameters.h"
 #include "MLDSAPublicKey.h"

27-39: Comment mismatch

The block header says “Get the bit length” but this method returns the parameter set. Update the comment for clarity.

Apply this diff:

-// Get the bit length
+// Get the parameter set

41-53: Typo in comment

“Get the signatureLength length” → “Get the signature length”.

Apply this diff:

-// Get the signatureLength length
+// Get the signature length
src/lib/crypto/MLDSAPublicKey.h (1)

22-27: Fix comment labels

These comment headers are misleading. Adjust to match the methods’ purpose.

Apply this diff:

-// Get the bit length
+// Get the parameter set
 virtual unsigned long getParameterSet() const;

-// Get the signature length
+// Get the signature length
 virtual unsigned long getOutputLength() const;
src/lib/P11Attributes.cpp (1)

2564-2597: Optional: validate and protect CKA_SEED more strictly

  • Consider enforcing expected lengths when applicable (e.g., ML-DSA seeds are typically 32 bytes).
  • Ensure P11AttrSeed has ck7 in its checks to block retrieval when sensitive/unextractable, aligning with base retrieve() semantics.

Can you confirm P11AttrSeed’s checks include ck7 in P11Attributes.h so that retrieval is blocked when appropriate?

src/lib/crypto/MLDSAParameters.h (2)

69-69: Initialize parameterSet to a safe default

Avoid using an uninitialized value if getParameterSet() is called before setParameterSet(). Initialize to 0.

-	unsigned long parameterSet;
+	unsigned long parameterSet = 0UL;

10-11: Remove unused includes

and are not used in this header; dropping them reduces compile time and dependencies.

-#include <tuple>
-#include <stdexcept>
src/lib/crypto/MLDSAPrivateKey.h (1)

15-21: Add a virtual destructor

PrivateKey already has a virtual destructor, but explicitly providing one here improves clarity and helps prevent potential slicing/warnings when extending further.

 public:
 	// The type
 	static const char* type;
 
+	// Destructor
+	virtual ~MLDSAPrivateKey() { }
+
 	// Check if the key is of the given type
 	virtual bool isOfType(const char* inType);
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (3)

151-167: Guard against unsupported/unknown parameter set names

If mldsaParameterSet2Name() returns NULL (e.g., unexpected key length), creating the context will fail. Add an explicit check with a clear error.

 	const char* name = OSSL::mldsaParameterSet2Name(getParameterSet());
 
 	int selection = 0;
     EVP_PKEY_CTX *ctx = NULL;
     OSSL_PARAM params[3], *p = params;
 
+	if (name == NULL) {
+		ERROR_MSG("Unsupported ML-DSA parameter set (value: %lu)", getParameterSet());
+		return;
+	}

13-15: Remove unused include and add cleansing support (optional)

<openssl/bn.h> isn’t used here. If you choose to cleanse stack buffers, include <openssl/crypto.h>.

-#include <openssl/bn.h>
 #include <openssl/core_names.h>
 #include <openssl/x509.h>
+#include <openssl/crypto.h>

46-69: Optional: cleanse sensitive stack buffers

Seed and private key material temporarily reside on the stack. Cleanse after use to reduce exposure.

 	if(rv && seed_len == 32) {
 		// seed is not mandatory for OSSL key reconstruction
 		ByteString seedBS = ByteString(seed, seed_len);
 		setSeed(seedBS);
 	}
+	OPENSSL_cleanse(seed, sizeof(seed));
@@
 	ByteString valueBS = ByteString(priv, priv_len);
 
 	setValue(valueBS);
+	OPENSSL_cleanse(priv, sizeof(priv));
src/lib/crypto/OSSLMLDSAPublicKey.cpp (4)

52-54: Fix log message: it’s a public key

The error message incorrectly mentions “private key”. Make it clear this is extracting the public key.

-		ERROR_MSG("Could not get private key private, rv: %d", rv);
+		ERROR_MSG("Could not get public key data, rv: %d", rv);

95-111: Guard against unknown parameter set name

Add an explicit check for NULL algorithm name with a clear error log.

 	const char* name = OSSL::mldsaParameterSet2Name(getParameterSet());
 
 	int selection = 0;
     EVP_PKEY_CTX *ctx = NULL;
     OSSL_PARAM params[3], *p = params;
 
+	if (name == NULL) {
+		ERROR_MSG("Unsupported ML-DSA parameter set (value: %lu)", getParameterSet());
+		return;
+	}

13-16: Optional: add cleansing support for pubkey buffer

If you prefer to cleanse stack buffers, include <openssl/crypto.h>.

 #include <openssl/evp.h>
 #include <openssl/core_names.h>
 #include <string.h>
+#include <openssl/crypto.h>

57-60: Optional: cleanse temporary pubkey buffer

The public key is not secret in most contexts, but if you want consistent hygiene:

 	ByteString pubBS = ByteString(localPub, pub_len);
 	setValue(pubBS);
-	
+	OPENSSL_cleanse(localPub, sizeof(localPub));
src/lib/crypto/OSSLMLDSA.h (1)

10-50: Consider guarding the header with WITH_ML_DSA

Implementations are compiled under WITH_ML_DSA; exposing this header unconditionally can lead to accidental references when ML-DSA is disabled. Guarding the declarations as well reduces accidental link/ODR issues.

src/lib/SoftHSM.cpp (1)

13539-13556: Remove unused variable

‘parameterSet’ local ByteString is declared but never used in setMLDSAPrivateKey.

-    ByteString parameterSet;
     ByteString seed;
     ByteString value;
src/lib/crypto/OSSLMLDSA.cpp (3)

67-69: Right-size the signature buffer after signing

EVP_DigestSign updates the out length. If it’s ever shorter than the pre-sized buffer, the trailing bytes remain zeroed. Resize to the actual produced length.

Apply this diff:

-    signature.resize(len);
-    memset(&signature[0], 0, len);
+    signature.resize(len);
+    memset(&signature[0], 0, len);
@@
-    if (!EVP_DigestSign(ctx, &signature[0], &len, dataToSign.const_byte_str(), dataToSign.size()))
+    if (!EVP_DigestSign(ctx, &signature[0], &len, dataToSign.const_byte_str(), dataToSign.size()))
     {
         ERROR_MSG("ML-DSA sign failed (0x%08X)", ERR_get_error());
         EVP_MD_CTX_free(ctx);
         return false;
     }
+    // Trim to the actual signature size (defensive)
+    signature.resize(len);

Also applies to: 77-84


138-141: Fix comment typo and duplication

The block has duplicate/mismatched comments (“Perform the verify operation” and “Perform the signature operation”). Clean this up for clarity.

Apply this diff:

-    // Perform the verify operation
-    // Perform the signature operation
+    // Perform the verify operation

73-74: Use correct printf specifier for ERR_get_error() (unsigned long)

ERR_get_error() returns unsigned long. Using %08X may truncate on LP64 platforms. Prefer %08lX (or cast to unsigned int with care).

Example fixes:

-        ERROR_MSG("ML-DSA sign init failed (0x%08X)", ERR_get_error());
+        ERROR_MSG("ML-DSA sign init failed (0x%08lX)", ERR_get_error());

Apply similarly to other ERR_get_error() logs in this file.

Also applies to: 79-81, 156-157, 164-166, 171-175, 181-183, 272-279, 284-286

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (2)
src/lib/SoftHSM.cpp (2)

7001-7024: UnwrapKeySym lacks AES_CBC/DES3_CBC (non-PAD) support → cannot unwrap what WrapKeySym can wrap

WrapKeySym supports CKM_AES_CBC and CKM_DES3_CBC, but UnwrapKeySym only supports the PAD variants. This asymmetry prevents unwrapping CBC-wrapped blobs (non-PAD).

Apply:

 switch(pMechanism->mechanism) {
@@
+#ifdef HAVE_AES_KEY_WRAP
   case CKM_AES_KEY_WRAP:
@@
 #endif
 #ifdef HAVE_AES_KEY_WRAP_PAD
   case CKM_AES_KEY_WRAP_PAD:
@@
 #endif
+  case CKM_AES_CBC:
+      algo = SymAlgo::AES;
+      blocksize = 16;
+      break;
   case CKM_AES_CBC_PAD:
       algo = SymAlgo::AES;
       blocksize = 16;
       break;
+  case CKM_DES3_CBC:
+      algo = SymAlgo::DES3;
+      blocksize = 8;
+      break;
   case CKM_DES3_CBC_PAD:
       algo = SymAlgo::DES3;
       blocksize = 8;
       break;
@@
-  case CKM_AES_CBC_PAD:
-  case CKM_DES3_CBC_PAD:
+  case CKM_AES_CBC:
+  case CKM_AES_CBC_PAD:
+  case CKM_DES3_CBC:
+  case CKM_DES3_CBC_PAD:
       iv.resize(blocksize);
       memcpy(&iv[0], pMechanism->pParameter, blocksize);

Also ensure the C_UnwrapKey mechanism validation accepts CKM_AES_CBC (IV len 16) and CKM_DES3_CBC (IV len 8); see next comment.

Also applies to: 7047-7055


7361-7374: Accept non-PAD CBC in C_UnwrapKey and fix IV length checks

Currently only CKM_AES_CBC_PAD is accepted; add CKM_AES_CBC and CKM_DES3_CBC to keep the unwrap path aligned with WrapKeySym/UnwrapKeySym.

Apply:

-        case CKM_AES_CBC_PAD:
-            // TODO check block length
-            if (pMechanism->pParameter == NULL_PTR ||
-                            pMechanism->ulParameterLen != 16)
-                return CKR_ARGUMENTS_BAD;
-            break;
+        case CKM_AES_CBC:
+        case CKM_AES_CBC_PAD:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != 16)
+                return CKR_ARGUMENTS_BAD;
+            break;
@@
-        case CKM_DES3_CBC_PAD:
-            // TODO check block length
-            if (pMechanism->pParameter == NULL_PTR ||
-                            pMechanism->ulParameterLen != 8)
-                return CKR_ARGUMENTS_BAD;
-            break;
+        case CKM_DES3_CBC:
+        case CKM_DES3_CBC_PAD:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != 8)
+                return CKR_ARGUMENTS_BAD;
+            break;

Additionally, in the “Check unwrapping key class and type” section just below (Lines 7413–7417), the error code uses CKR_WRAPPING_KEY_TYPE_INCONSISTENT for unwrap — change to CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT. Same applies to the DES3 clause. Example:

  • return CKR_WRAPPING_KEY_TYPE_INCONSISTENT → return CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT
♻️ Duplicate comments (10)
src/lib/crypto/MLDSAPublicKey.h (1)

10-12: Resolved: explicit ByteString dependency included

Including ByteString.h directly removes reliance on transitive includes and avoids brittle builds. Good fix.

src/lib/P11Attributes.cpp (1)

2230-2260: CKA_PARAMETER_SET default type and update path look correct

  • Default is now CK_ULONG (0) instead of ByteString. Matches the update path and OSAttribute usage.
  • Update restricted to GENERATE/CREATE and enforces sizeof(CK_ULONG). Solid.

Optional: consider validating values against the allowed PKCS#11 enumerants (e.g., CKP_ML_DSA_44/65/87), rejecting unknown non-zero sets. This catches template errors early.

CMakeLists.txt (1)

11-11: Wire ENABLE_MLDSA → WITH_ML_DSA and enforce OpenSSL ≥ 3.5

The ENABLE_MLDSA option is currently inert: it neither defines the WITH_ML_DSA macro nor checks your OpenSSL version, so all #ifdef WITH_ML_DSA blocks remain disabled. To activate ML-DSA support only when OpenSSL ≥ 3.5:

• In CMakeLists.txt (around the existing option(ENABLE_MLDSA…) on line 11):

  • Add a find_package(OpenSSL REQUIRED)
  • Gate on OpenSSL_VERSION_MAJOR/MINOR to set WITH_ML_DSA from ENABLE_MLDSA or force it OFF with a warning
  • Propagate WITH_ML_DSA via add_compile_definitions

• In your CMake config-header template (e.g. config.h.in.cmake or equivalent):

  • Add
    #cmakedefine WITH_ML_DSA @WITH_ML_DSA@

Suggested patch:

 option(ENABLE_EDDSA "Enable support for EDDSA" ON)
 option(ENABLE_MLDSA "Enable support for ML-DSA" OFF)
 option(ENABLE_GOST "Enable support for GOST" OFF)

+# Locate OpenSSL and enforce version gate for ML-DSA
+find_package(OpenSSL REQUIRED)
+if(OPENSSL_VERSION_MAJOR GREATER 3
+    OR (OPENSSL_VERSION_MAJOR EQUAL 3 AND OPENSSL_VERSION_MINOR GREATER_EQUAL 5))
+  set(WITH_ML_DSA ${ENABLE_MLDSA}
+      CACHE BOOL "Enable ML-DSA support (OpenSSL ≥ 3.5)" FORCE)
+else()
+  set(WITH_ML_DSA OFF
+      CACHE BOOL "ML-DSA disabled: requires OpenSSL ≥ 3.5" FORCE)
+  if(ENABLE_MLDSA)
+    message(WARNING
+      "ENABLE_MLDSA requested but OpenSSL ${OPENSSL_VERSION} < 3.5; ML-DSA disabled")
+  endif()
+endif()

+# Define macro for sources
+if(WITH_ML_DSA)
+  add_compile_definitions(WITH_ML_DSA)
+endif()

And in your config header template:

#cmakedefine WITH_ML_DSA @WITH_ML_DSA@
src/lib/crypto/OSSLUtil.cpp (1)

37-39: Resolved: added missing includes

Including MLDSAParameters.h and fixes the prior compile issue on stricter toolchains when using std::map.

src/lib/crypto/OSSLMLDSAPrivateKey.cpp (1)

120-133: Resolved: EVP_PKEY leak fixed in PKCS8Decode()

Freeing localPKey after setFromOSSL avoids a leak. Thanks for addressing this.

src/lib/crypto/OSSLMLDSA.cpp (1)

48-56: Remove redundant NULL-check on pk after a non-NULL check of privateKey

pk is a direct cast of privateKey, which you already validated as non-NULL (Line 34). The pk == NULL branch is dead code; drop it.

  OSSLMLDSAPrivateKey *pk = (OSSLMLDSAPrivateKey *)privateKey;
-	
-	if (pk == NULL)
-	{
-		ERROR_MSG("Could not get the SoftHSM private key");
-
-		return false;
-	}
src/lib/SoftHSM.cpp (4)

13001-13006: These helpers don’t use ‘this’ — consider making them static methods or free functions

getMLDSAPrivateKey, getMLDSAPublicKey, and setMLDSAPrivateKey can be marked static (or moved out of the class) to reduce coupling, consistent with prior review discussion.

Also applies to: 13033-13038, 13518-13522


6414-6422: AES_CBC/DES3_CBC blocksize remains 0 in WrapKeySym (IV becomes empty) — set blocksize for non-PAD modes

For non-PAD CBC modes, blocksize is left as 0, so iv.resize(0) and memcpy with 0 bytes occurs; encryptInit then runs with an empty IV. This is a functional bug that breaks wrapping and is inconsistent with PAD cases.

Apply:

 case CKM_AES_CBC:
-    algo = SymAlgo::AES;
+    algo = SymAlgo::AES;
+    blocksize = 16;
     break;
@@
 case CKM_DES3_CBC:
-    algo = SymAlgo::DES3;
+    algo = SymAlgo::DES3;
+    blocksize = 8;
     break;
@@
-            iv.resize(blocksize);
-            memcpy(&iv[0], pMechanism->pParameter, blocksize);
+            iv.resize(blocksize);
+            memcpy(&iv[0], pMechanism->pParameter, blocksize);

Optional hardening (recommended): also validate pMechanism->ulParameterLen == blocksize before memcpy to fail fast on malformed IV.

Also applies to: 6423-6431, 6460-6463


13027-13031: Populate parameter set on ML‑DSA private key (required for backend context)

getMLDSAPrivateKey sets value/seed but not the parameter set; the backend may need it to sign correctly.

Apply:

     privateKey->setValue(value);
     privateKey->setSeed(seed);
+    // Ensure parameter set is conveyed to the runtime key
+    unsigned long parameterSet = key->getUnsignedLongValue(CKA_PARAMETER_SET, 0);
+    privateKey->setParameterSet(parameterSet);

13056-13058: Populate parameter set on ML‑DSA public key

Same rationale as private: set the parameter set on the runtime object.

Apply:

     publicKey->setValue(value);
+    unsigned long parameterSet = key->getUnsignedLongValue(CKA_PARAMETER_SET, 0);
+    publicKey->setParameterSet(parameterSet);
🧹 Nitpick comments (15)
m4/acx_botan_mldsa.m4 (2)

2-2: Drop AC_MSG_CHECKING when using AC_CACHE_CHECK

AC_CACHE_CHECK emits both the checking message and the final result. Keeping AC_MSG_CHECKING here will duplicate or misalign output.

-	AC_MSG_CHECKING(for Botan ML-DSA support)
+	# AC_CACHE_CHECK will print the check and result

5-8: Nit: LIBS mangling is unnecessary for a compile-only probe

AC_COMPILE_IFELSE doesn’t link, so LIBS changes are unused. Dropping them reduces side effects in configure logs.

-	tmp_LIBS=$LIBS
...
-	LIBS="$CRYPTO_LIBS $LIBS"
...
-	LIBS=$tmp_LIBS

Also applies to: 30-31

src/lib/crypto/MLDSAPublicKey.h (1)

23-31: Fix mislabeled comment for getParameterSet()

The comment above getParameterSet() says "Get the bit length". It should describe the parameter set. Minor, but prevents confusion when skimming headers.

Apply:

-	// Get the bit length
+	// Get the parameter set
 	virtual unsigned long getParameterSet() const;
 
-	// Get the signature length
+	// Get the signature length
 	virtual unsigned long getOutputLength() const;
 
 	// Get the bit length
 	virtual unsigned long getBitLength() const;
src/lib/crypto/OSSLUtil.cpp (1)

43-49: Prefer a simple switch over a global std::map for 3 constants

A switch avoids static initialization, reduces code size, and removes an otherwise unnecessary dependency. Functionally identical here.

Apply:

-#ifdef WITH_ML_DSA
-static std::map<unsigned long, const char*> mldsaAlgNameFromParameterSet {
-	{MLDSAParameters::ML_DSA_44_PARAMETER_SET, "ML-DSA-44"},
-	{MLDSAParameters::ML_DSA_65_PARAMETER_SET, "ML-DSA-65"},
-	{MLDSAParameters::ML_DSA_87_PARAMETER_SET, "ML-DSA-87"}
-};
-#endif
+#ifdef WITH_ML_DSA
+#endif

And rewrite the function:

 #ifdef WITH_ML_DSA
 const char* OSSL::mldsaParameterSet2Name(unsigned long parameterSet) {
-
-	std::map<unsigned long, const char*>::iterator it = mldsaAlgNameFromParameterSet.find(parameterSet);
-
-	if (it != mldsaAlgNameFromParameterSet.end()) {
-		return it->second;
-	}
-
-	return NULL;
+	switch (parameterSet) {
+		case MLDSAParameters::ML_DSA_44_PARAMETER_SET: return "ML-DSA-44";
+		case MLDSAParameters::ML_DSA_65_PARAMETER_SET: return "ML-DSA-65";
+		case MLDSAParameters::ML_DSA_87_PARAMETER_SET: return "ML-DSA-87";
+		default: return NULL;
+	}
 }
 #endif

If you take this, you can also drop the include.

Also applies to: 223-234

src/lib/crypto/OSSLMLDSAPrivateKey.cpp (2)

7-16: Include <stdint.h> for uint8_t

uint8_t is used; include <stdint.h>/ explicitly to avoid relying on transitive includes.

 #include "OSSLUtil.h"
+#include <stdint.h>
 #include <openssl/bn.h>
 #include <openssl/core_names.h>
 #include <openssl/x509.h>

60-63: Minor: clarify error message

Typo/redundant wording in the log.

-		ERROR_MSG("Could not get private key private, rv: %d", rv);
+		ERROR_MSG("Could not get private key value, rv: %d", rv);
src/lib/crypto/OSSLMLDSA.h (4)

10-13: Avoid exposing OpenSSL in the public header; include cstddef for size_t

This header doesn’t use any OpenSSL types/macros directly; only the .cpp needs <openssl/evp.h>. Also, this header uses size_t in method signatures; relying on transitive includes for size_t can be brittle.

  • Remove <openssl/evp.h> from the header to reduce compile dependencies.
  • Add so size_t is always defined here.

Apply:

 #include "config.h"
 #include "AsymmetricAlgorithm.h"
-#include <openssl/evp.h>
+#include <cstddef>

And ensure the implementation file includes OpenSSL:

// In src/lib/crypto/OSSLMLDSA.cpp
#include <openssl/evp.h>

16-50: Add override specifiers to catch signature drift; modernize destructor

Marking overrides explicitly improves safety (compiler checks exact signature matches) and readability. It would also immediately flag any mismatch with the base AsymmetricAlgorithm interface (e.g., missing const qualifiers or differing parameter lists).

Apply:

 	// Destructor
-	virtual ~OSSLMLDSA() { }
+	virtual ~OSSLMLDSA() override = default;

 	// Signing functions
-	virtual bool sign(PrivateKey* privateKey, const ByteString& dataToSign, ByteString& signature, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0);
-	virtual bool signInit(PrivateKey* privateKey, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0);
-	virtual bool signUpdate(const ByteString& dataToSign);
-	virtual bool signFinal(ByteString& signature);
+	virtual bool sign(PrivateKey* privateKey, const ByteString& dataToSign, ByteString& signature, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0) override;
+	virtual bool signInit(PrivateKey* privateKey, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0) override;
+	virtual bool signUpdate(const ByteString& dataToSign) override;
+	virtual bool signFinal(ByteString& signature) override;

 	// Verification functions
-	virtual bool verify(PublicKey* publicKey, const ByteString& originalData, const ByteString& signature, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0);
-	virtual bool verifyInit(PublicKey* publicKey, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0);
-	virtual bool verifyUpdate(const ByteString& originalData);
-	virtual bool verifyFinal(const ByteString& signature);
+	virtual bool verify(PublicKey* publicKey, const ByteString& originalData, const ByteString& signature, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0) override;
+	virtual bool verifyInit(PublicKey* publicKey, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0) override;
+	virtual bool verifyUpdate(const ByteString& originalData) override;
+	virtual bool verifyFinal(const ByteString& signature) override;

 	// Encryption functions
-	virtual bool encrypt(PublicKey* publicKey, const ByteString& data, ByteString& encryptedData, const AsymMech::Type padding);
+	virtual bool encrypt(PublicKey* publicKey, const ByteString& data, ByteString& encryptedData, const AsymMech::Type padding) override;

 	// Decryption functions
-	virtual bool checkEncryptedDataSize(PrivateKey* privateKey, const ByteString& encryptedData, int* errorCode);
-	virtual bool decrypt(PrivateKey* privateKey, const ByteString& encryptedData, ByteString& data, const AsymMech::Type padding);
-	virtual unsigned long getMinKeySize();
-	virtual unsigned long getMaxKeySize();
+	virtual bool checkEncryptedDataSize(PrivateKey* privateKey, const ByteString& encryptedData, int* errorCode) override;
+	virtual bool decrypt(PrivateKey* privateKey, const ByteString& encryptedData, ByteString& data, const AsymMech::Type padding) override;
+	virtual unsigned long getMinKeySize() override;
+	virtual unsigned long getMaxKeySize() override;

 	// Key factory
-	virtual bool generateKeyPair(AsymmetricKeyPair** ppKeyPair, AsymmetricParameters* parameters, RNG* rng = NULL);
-	virtual bool reconstructKeyPair(AsymmetricKeyPair** ppKeyPair, ByteString& serialisedData);
-	virtual bool reconstructPublicKey(PublicKey** ppPublicKey, ByteString& serialisedData);
-	virtual bool reconstructPrivateKey(PrivateKey** ppPrivateKey, ByteString& serialisedData);
-	virtual bool reconstructParameters(AsymmetricParameters** ppParams, ByteString& serialisedData);
-	virtual PublicKey* newPublicKey();
-	virtual PrivateKey* newPrivateKey();
-	virtual AsymmetricParameters* newParameters();
+	virtual bool generateKeyPair(AsymmetricKeyPair** ppKeyPair, AsymmetricParameters* parameters, RNG* rng = NULL) override;
+	virtual bool reconstructKeyPair(AsymmetricKeyPair** ppKeyPair, ByteString& serialisedData) override;
+	virtual bool reconstructPublicKey(PublicKey** ppPublicKey, ByteString& serialisedData) override;
+	virtual bool reconstructPrivateKey(PrivateKey** ppPrivateKey, ByteString& serialisedData) override;
+	virtual bool reconstructParameters(AsymmetricParameters** ppParams, ByteString& serialisedData) override;
+	virtual PublicKey* newPublicKey() override;
+	virtual PrivateKey* newPrivateKey() override;
+	virtual AsymmetricParameters* newParameters() override;

Note: If the base class marks any of these as const (e.g., getMinKeySize() const), the compiler will help you align signatures once override is added.


32-37: Clarify that ML-DSA does not support encryption/decryption

These methods purposely return false in the implementation. A short note in the header helps prevent confusion for future readers.

Apply:

-	// Encryption functions
+	// Encryption functions (ML-DSA does not support encryption; methods return false)
 	virtual bool encrypt(PublicKey* publicKey, const ByteString& data, ByteString& encryptedData, const AsymMech::Type padding);

-	// Decryption functions
+	// Decryption functions (ML-DSA does not support decryption; methods return false)
 	virtual bool checkEncryptedDataSize(PrivateKey* privateKey, const ByteString& encryptedData, int* errorCode);
 	virtual bool decrypt(PrivateKey* privateKey, const ByteString& encryptedData, ByteString& data, const AsymMech::Type padding);

51-52: Optional: remove empty private section

There are no private members. Consider removing the empty private: block to reduce noise, unless you plan to add members imminently.

src/lib/crypto/OSSLMLDSA.cpp (2)

138-141: Fix comment: it’s a public key type check, not private

Small clarity nit.

-	// Check if the private key is the right type
+	// Check if the public key is the right type

164-168: Improve error message with expected vs actual signature lengths

This aids operators and test triage.

-		ERROR_MSG("Invalid buffer length");
+		ERROR_MSG("Invalid signature length: got %zu, expected %zu", signature.size(), len);
src/lib/SoftHSM.cpp (3)

10064-10067: Prefer standard error code and fix format specifier for paramSet diagnostics

  • Use CKR_MECHANISM_PARAM_INVALID instead of CKR_PARAMETER_SET_NOT_SUPPORTED (unless you explicitly want to expose that code).
  • paramSet is CK_ULONG; use %lu and cast to unsigned long for portability.

Apply:

-    if (paramSet != 1UL && paramSet != 2UL && paramSet != 3UL) {
-        INFO_MSG("Wrong parameterSet: %ld", paramSet);
-        return CKR_PARAMETER_SET_NOT_SUPPORTED;
+    if (paramSet != 1UL && paramSet != 2UL && paramSet != 3UL) {
+        INFO_MSG("Unsupported parameter set: %lu", (unsigned long)paramSet);
+        return CKR_MECHANISM_PARAM_INVALID;
     }

Nit: if your PKCS#11 headers define CKP_ML_DSA_* constants for parameter sets, prefer those symbolic constants over raw numerics to align with project conventions.


10215-10217: Remove unused variable ‘parameterSet’

Local ‘parameterSet’ is never used.

Apply:

-                // MLDSA Private Key Attributes
-                ByteString parameterSet;
-                ByteString value;
+                // MLDSA Private Key Attributes
+                ByteString value;
                 ByteString seed;

13536-13539: Remove unused ‘parameterSet’ variable in setMLDSAPrivateKey

It’s not used; the attribute is set directly from priv->getParameterSet().

Apply:

-    // ML-DSA Private Key Attributes
-    ByteString parameterSet;
+    // ML-DSA Private Key Attributes
     ByteString seed;
     ByteString value;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 42e76f8 and af30be5.

📒 Files selected for processing (14)
  • .github/workflows/ci.yml (1 hunks)
  • CMakeLists.txt (1 hunks)
  • m4/acx_botan_mldsa.m4 (1 hunks)
  • src/lib/P11Attributes.cpp (2 hunks)
  • src/lib/SoftHSM.cpp (35 hunks)
  • src/lib/SoftHSM.h (4 hunks)
  • src/lib/crypto/MLDSAPublicKey.h (1 hunks)
  • src/lib/crypto/OSSLMLDSA.cpp (1 hunks)
  • src/lib/crypto/OSSLMLDSA.h (1 hunks)
  • src/lib/crypto/OSSLMLDSAKeyPair.cpp (1 hunks)
  • src/lib/crypto/OSSLMLDSAKeyPair.h (1 hunks)
  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp (1 hunks)
  • src/lib/crypto/OSSLMLDSAPublicKey.cpp (1 hunks)
  • src/lib/crypto/OSSLUtil.cpp (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/lib/crypto/OSSLMLDSAPublicKey.cpp
  • src/lib/crypto/OSSLMLDSAKeyPair.cpp
  • src/lib/crypto/OSSLMLDSAKeyPair.h
  • src/lib/SoftHSM.h
  • .github/workflows/ci.yml
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-18T17:54:37.601Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.601Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.

Applied to files:

  • m4/acx_botan_mldsa.m4
📚 Learning: 2025-08-18T17:59:30.642Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.642Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.h
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/OSSLUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAPublicKey.h
🧬 Code Graph Analysis (4)
src/lib/crypto/OSSLMLDSA.h (6)
src/lib/crypto/AsymmetricAlgorithm.h (2)
  • AsymmetricAlgorithm (116-185)
  • AsymmetricAlgorithm (123-123)
src/lib/crypto/OSSLMLDSA.cpp (42)
  • sign (24-97)
  • sign (24-26)
  • signInit (99-105)
  • signInit (99-100)
  • signUpdate (107-112)
  • signUpdate (107-107)
  • signFinal (114-119)
  • signFinal (114-114)
  • verify (122-222)
  • verify (122-124)
  • verifyInit (224-230)
  • verifyInit (224-225)
  • verifyUpdate (232-237)
  • verifyUpdate (232-232)
  • verifyFinal (239-244)
  • verifyFinal (239-239)
  • encrypt (247-253)
  • encrypt (247-248)
  • checkEncryptedDataSize (274-279)
  • checkEncryptedDataSize (274-274)
  • decrypt (256-262)
  • decrypt (256-257)
  • getMinKeySize (264-267)
  • getMinKeySize (264-264)
  • getMaxKeySize (269-272)
  • getMaxKeySize (269-269)
  • generateKeyPair (282-345)
  • generateKeyPair (282-282)
  • reconstructKeyPair (347-383)
  • reconstructKeyPair (347-347)
  • reconstructPublicKey (385-406)
  • reconstructPublicKey (385-385)
  • reconstructPrivateKey (408-429)
  • reconstructPrivateKey (408-408)
  • reconstructParameters (446-466)
  • reconstructParameters (446-446)
  • newPublicKey (431-434)
  • newPublicKey (431-431)
  • newPrivateKey (436-439)
  • newPrivateKey (436-436)
  • newParameters (441-444)
  • newParameters (441-441)
src/lib/crypto/AsymmetricKeyPair.h (1)
  • AsymmetricKeyPair (42-55)
src/lib/crypto/AsymmetricParameters.h (1)
  • AsymmetricParameters (40-45)
src/lib/crypto/PublicKey.h (1)
  • PublicKey (40-54)
src/lib/crypto/PrivateKey.h (1)
  • PrivateKey (41-58)
src/lib/crypto/OSSLMLDSA.cpp (1)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (224-233)
  • mldsaParameterSet2Name (224-224)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (4)
src/lib/crypto/OSSLMLDSAPrivateKey.h (1)
  • OSSLMLDSAPrivateKey (16-56)
src/lib/crypto/MLDSAPrivateKey.cpp (10)
  • setSeed (65-68)
  • setSeed (65-65)
  • setValue (55-58)
  • setValue (55-55)
  • isOfType (17-20)
  • isOfType (17-17)
  • getValue (60-63)
  • getValue (60-60)
  • getParameterSet (28-39)
  • getParameterSet (28-28)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (224-233)
  • mldsaParameterSet2Name (224-224)
src/lib/crypto/MLDSAParameters.cpp (2)
  • getParameterSet (22-25)
  • getParameterSet (22-22)
src/lib/crypto/MLDSAPublicKey.h (4)
src/lib/crypto/PublicKey.h (1)
  • PublicKey (40-54)
src/lib/crypto/OSSLMLDSAPublicKey.cpp (4)
  • isOfType (63-66)
  • isOfType (63-63)
  • setValue (68-76)
  • setValue (68-68)
src/lib/crypto/MLDSAPublicKey.cpp (16)
  • isOfType (17-20)
  • isOfType (17-17)
  • getParameterSet (28-39)
  • getParameterSet (28-28)
  • getOutputLength (42-53)
  • getOutputLength (42-42)
  • getBitLength (22-25)
  • getBitLength (22-22)
  • setValue (60-63)
  • setValue (60-60)
  • getValue (55-58)
  • getValue (55-55)
  • serialise (66-69)
  • serialise (66-66)
  • deserialise (71-83)
  • deserialise (71-71)
src/lib/crypto/MLDSAParameters.cpp (6)
  • getParameterSet (22-25)
  • getParameterSet (22-22)
  • serialise (34-37)
  • serialise (34-34)
  • deserialise (39-50)
  • deserialise (39-39)
🔇 Additional comments (16)
m4/acx_botan_mldsa.m4 (2)

4-11: Good hygiene: language push/pop and CPPFLAGS save/restore

The macro correctly switches to C++ and restores CPPFLAGS/LIBS afterward, and exposes have_lib_botan_mldsa_support for downstream logic.

Also applies to: 28-33


32-33: ML-DSA detection is properly propagated

The have_lib_botan_mldsa_support variable is indeed consumed downstream in m4/acx_crypto_backend.m4, influencing the build via the case dispatch:

  • m4/acx_crypto_backend.m4:191-194 –
    case "${enable_mldsa}-${have_lib_botan_mldsa_support}" in
      yes-no)   AC_MSG_ERROR([Botan library has no ML-DSA support]);;
      detect-*) enable_mldsa="${have_lib_botan_mldsa_support}";;
    esac

No further AC_SUBST or AM_CONDITIONAL is required, as the m4 dispatch already covers propagation.

src/lib/crypto/MLDSAPublicKey.h (1)

18-21: Static ‘type’ definition confirmed

  • Definition found once in src/lib/crypto/MLDSAPublicKey.cpp:14:
    /*static*/ const char* MLDSAPublicKey::type = "Abstract ML-DSA public key";

No further action required.

src/lib/P11Attributes.cpp (1)

2558-2596: CKA_SEED handling is secure and consistent

  • No plaintext logging; encryption pathways mirror existing sensitive attributes.
  • Accepts 0-length with NULL pointer via upstream checks. Stores as-is for public objects, encrypted for private ones.

Looks good.

src/lib/crypto/OSSLMLDSA.h (2)

7-9: Include guard mismatch is resolved — LGTM

The opening and closing guards consistently use _SOFTHSM_V2_OSSLMLDSA_H now. Thanks for fixing the earlier mismatch.

Also applies to: 54-54


21-40: All OSSLMLDSA method signatures match the AsymmetricAlgorithm base class – you can safely add override.

I’ve confirmed that for each of these methods in src/lib/crypto/OSSLMLDSA.h, the parameter lists and return types are identical to the declarations in src/lib/crypto/AsymmetricAlgorithm.h:

  • sign, signInit, signUpdate, signFinal
  • verify, verifyInit, verifyUpdate, verifyFinal
  • encrypt, decrypt
  • checkEncryptedDataSize
  • getMinKeySize, getMaxKeySize

Feel free to annotate each with override to prevent any silent non-override errors in the future.

src/lib/crypto/OSSLMLDSA.cpp (7)

76-82: Good: EVP_MD_CTX_new() NULL-checked

Prevents a potential crash on allocation failure. Nicely handled.


181-186: Good: explicit guard for unknown/unsupported parameter sets

Fail-fast with a clear diagnostic and clean-up. This improves debuggability significantly.


205-217: Good: distinguish invalid signatures from OpenSSL errors

Correct handling: 0 => invalid signature; <0 => provider error via ERR_get_error(). Clean and accurate.


302-307: Good: keygen explicitly rejects unknown parameter sets

Nice early error with a clear message before context creation.


331-342: Good: proper EVP_PKEY refcounting to avoid UAF/double-free

Using EVP_PKEY_up_ref before handing the same pkey to both wrappers and freeing the original reference is the correct ownership model.


246-279: Clear unsupported-operation messaging (encrypt/decrypt/checkEncryptedDataSize)

Explicit “not supported” errors are fine and consistent with signature-only algorithms.


264-272: Units confirmed as bits—no change needed

The constants ML_DSA_44_PRIV_LENGTH (2560) and ML_DSA_87_PRIV_LENGTH (4896) in MLDSAParameters.h represent key lengths in bits, matching the bit‐based values returned by all other getMinKeySize/getMaxKeySize implementations (e.g., RSA: 1024–4096, ECDSA: 112–521). Your use of these named constants is correct and requires no adjustment.

src/lib/SoftHSM.cpp (3)

146-148: ML‑DSA P11 object wiring (public key) looks correct

newP11Object correctly recognizes CKK_ML_DSA and instantiates P11MLDSAPublicKeyObj.


165-167: ML‑DSA P11 object wiring (private key) looks correct

newP11Object correctly recognizes CKK_ML_DSA and instantiates P11MLDSAPrivateKeyObj.


830-833: Mechanism table and info for ML‑DSA: LGTM

  • CKM_ML_DSA[_KEY_PAIR_GEN] added under WITH_ML_DSA.
  • Min/max sizes obtained from CryptoFactory and exposed in C_GetMechanismInfo.
  • Flags CKF_GENERATE_KEY_PAIR and CKF_SIGN|CKF_VERIFY are consistent.

No issues spotted.

Also applies to: 938-941, 1038-1049, 1336-1346

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 Outside diff range comments (2)
src/lib/SoftHSM.cpp (2)

7005-7026: Unwrap path does not support non-PAD CBC and has two critical bugs

  • Unwrap only handles CKM_AES_CBC_PAD/CKM_DES3_CBC_PAD; the wrap path now accepts non-PAD CKM_AES_CBC/CKM_DES3_CBC (once fixed above). Add symmetric support here.
  • Wrong return code and logic in key-type checks:
    • Returning CKR_WRAPPING_KEY_TYPE_INCONSISTENT in Unwrap; should be CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT.
    • The DES3 check uses “!= DES2 || != DES3” which is always true. Use &&.

Apply to UnwrapKeySym to accept non-PAD CBC:

     switch(pMechanism->mechanism) {
@@
-        case CKM_AES_CBC_PAD:
+        case CKM_AES_CBC:
+            algo = SymAlgo::AES;
+            blocksize = 16;
+            break;
+        case CKM_AES_CBC_PAD:
             algo = SymAlgo::AES;
             blocksize = 16;
             break;
@@
-        case CKM_DES3_CBC_PAD:
+        case CKM_DES3_CBC:
+            algo = SymAlgo::DES3;
+            blocksize = 8;
+            break;
+        case CKM_DES3_CBC_PAD:
             algo = SymAlgo::DES3;
             blocksize = 8;
             break;

Apply to C_UnwrapKey mechanism validation to accept non-PAD CBC with IV length checks:

-        case CKM_AES_CBC_PAD:
+        case CKM_AES_CBC:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != 16)
+                return CKR_ARGUMENTS_BAD;
+            break;
+        case CKM_AES_CBC_PAD:
             // TODO check block length
             if (pMechanism->pParameter == NULL_PTR ||
                 pMechanism->ulParameterLen != 16)
                 return CKR_ARGUMENTS_BAD;
             break;
@@
-        case CKM_DES3_CBC_PAD:
+        case CKM_DES3_CBC:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != 8)
+                return CKR_ARGUMENTS_BAD;
+            break;
+        case CKM_DES3_CBC_PAD:
             // TODO check block length
             if (pMechanism->pParameter == NULL_PTR ||
                 pMechanism->ulParameterLen != 8)
                 return CKR_ARGUMENTS_BAD;
             break;

Fix the unwrapping key-type checks:

-    if ((pMechanism->mechanism == CKM_AES_CBC || pMechanism->mechanism == CKM_AES_CBC_PAD) && unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_AES)
-        return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
+    if ((pMechanism->mechanism == CKM_AES_CBC || pMechanism->mechanism == CKM_AES_CBC_PAD) &&
+        unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_AES)
+        return CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT;
@@
-    if (pMechanism->mechanism == CKM_DES3_CBC && (unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 ||
-        unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
-        return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
+    if (pMechanism->mechanism == CKM_DES3_CBC &&
+        (unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 &&
+         unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
+        return CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT;

Note: Consider making the analogous DES3 check fix in C_WrapKey too (same ‘||’ issue) for consistency.

Also applies to: 7051-7086, 7364-7377, 7416-7421


7416-7421: Fix two typos in Unwrap key-type check (return code and logic)

  • Use UNWRAPPING in the error code for Unwrap path.
  • Correct the boolean logic so the check does not always fail.

Apply:

-    if ((pMechanism->mechanism == CKM_AES_CBC || pMechanism->mechanism == CKM_AES_CBC_PAD) && unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_AES)
-        return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
+    if ((pMechanism->mechanism == CKM_AES_CBC || pMechanism->mechanism == CKM_AES_CBC_PAD) &&
+        unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_AES)
+        return CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT;
@@
-    if (pMechanism->mechanism == CKM_DES3_CBC && (unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 ||
-        unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
-        return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
+    if (pMechanism->mechanism == CKM_DES3_CBC &&
+        (unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 &&
+         unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
+        return CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT;
♻️ Duplicate comments (1)
src/lib/SoftHSM.cpp (1)

4190-4191: Populate ML-DSA parameter set on runtime key objects

The runtime MLDSA{Private,Public}Key instances created via MLDSAUtil currently only set value/seed. Parameter set is required for the backend to know which ML-DSA variant to use.

Please extend MLDSAUtil::{getMLDSAPrivateKey,getMLDSAPublicKey} to set the parameter set read from the OSObject:

Outside this file (src/lib/crypto/MLDSAUtil.cpp), apply:

--- a/src/lib/crypto/MLDSAUtil.cpp
+++ b/src/lib/crypto/MLDSAUtil.cpp
@@
 CK_RV MLDSAUtil::getMLDSAPrivateKey(MLDSAPrivateKey* privateKey, Token* token, OSObject* key)
@@
-    privateKey->setValue(value);
-    privateKey->setSeed(seed);
+    privateKey->setValue(value);
+    privateKey->setSeed(seed);
+    // Parameter set is required
+    CK_ULONG paramSet = key->getUnsignedLongValue(CKA_PARAMETER_SET, 0);
+    privateKey->setParameterSet(paramSet);
@@
 CK_RV MLDSAUtil::getMLDSAPublicKey(MLDSAPublicKey* publicKey, Token* token, OSObject* key)
@@
-    publicKey->setValue(value);
+    publicKey->setValue(value);
+    CK_ULONG paramSet = key->getUnsignedLongValue(CKA_PARAMETER_SET, 0);
+    publicKey->setParameterSet(paramSet);

Would you like me to open a follow-up PR for this?

Also applies to: 4434-4439, 4527-4546, 5199-5200, 5441-5446, 5534-5553

🧹 Nitpick comments (6)
src/lib/crypto/MLDSAUtil.h (2)

29-30: Fix mismatched/end-if comments for clarity

The nesting is correct (#ifdef WITH_ML_DSA closes before the include guard), but the comments are swapped and confusing. Align comments with the blocks they close.

-#endif // !_SOFTHSM_V2_MLDSAUTIL_H
-#endif
+#endif // WITH_ML_DSA
+#endif // _SOFTHSM_V2_MLDSAUTIL_H

14-15: Trim unused includes from the header to reduce transitive dependencies

AsymmetricAlgorithm.h and CryptoFactory.h are not needed for the declarations in this header (they’re only used in the .cpp). Removing them will reduce rebuilds and header coupling.

-#include "AsymmetricAlgorithm.h"
-#include "CryptoFactory.h"

Add them in the implementation file instead (see suggested change in MLDSAUtil.cpp).

src/lib/crypto/MLDSAUtil.cpp (3)

52-54: Fix copy/paste comment (EC → ML-DSA)

Minor correctness/clarity tweak.

-    // EC Public Key Attributes
+    // ML-DSA Public Key Attributes

89-89: Remove unused variable

parameterSet is declared but never used; drop it to avoid warnings.

-    ByteString parameterSet;

7-10: Move algorithm/factory includes from header into the implementation

To support the header cleanup, include what you use here.

 #include "config.h"
 #ifdef WITH_ML_DSA
 #include "MLDSAUtil.h"
+#include "AsymmetricAlgorithm.h"
+#include "CryptoFactory.h"
src/lib/SoftHSM.cpp (1)

6734-6739: Symmetry: consider allowing DES3_CBC in C_WrapKey

You support CKM_DES3_CBC in WrapKeySym but the top-level C_WrapKey switch does not accept CKM_DES3_CBC. Add a branch with IV length 8 for consistency with the unwrap side.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between af30be5 and 8609c5a.

📒 Files selected for processing (7)
  • src/lib/SoftHSM.cpp (34 hunks)
  • src/lib/SoftHSM.h (2 hunks)
  • src/lib/crypto/CMakeLists.txt (2 hunks)
  • src/lib/crypto/MLDSAUtil.cpp (1 hunks)
  • src/lib/crypto/MLDSAUtil.h (1 hunks)
  • src/lib/crypto/Makefile.am (3 hunks)
  • src/lib/crypto/OSSLComp.cpp (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/lib/crypto/OSSLComp.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/crypto/CMakeLists.txt
  • src/lib/crypto/Makefile.am
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T17:59:30.642Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.642Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/SoftHSM.h
🧬 Code Graph Analysis (2)
src/lib/crypto/MLDSAUtil.h (1)
src/lib/crypto/MLDSAUtil.cpp (6)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-111)
  • setMLDSAPrivateKey (71-71)
src/lib/SoftHSM.cpp (2)
src/lib/crypto/MLDSAUtil.cpp (6)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-111)
  • setMLDSAPrivateKey (71-71)
src/lib/P11Objects.cpp (2)
  • isPrivate (303-311)
  • isPrivate (303-303)
🔇 Additional comments (11)
src/lib/crypto/MLDSAUtil.h (1)

23-27: API shape looks good and consistent

The utility provides a minimal and focused surface for ML-DSA key get/set, taking Token and OSObject as inputs and respecting privacy. Signature choices (CK_RV for getters, bool for setter) match existing patterns.

src/lib/SoftHSM.h (2)

55-58: Good: MLDSA includes correctly gated

Wrapping MLDSA key includes with WITH_ML_DSA prevents builds from breaking when ML-DSA is disabled or OpenSSL < 3.5.


378-393: Good: MLDSA generator declaration properly feature-gated

The generateMLDSA() declaration matches the conditional compilation used elsewhere, avoiding undefined references when ML-DSA is not enabled.

src/lib/crypto/MLDSAUtil.cpp (2)

94-105: I’ve confirmed that Token::encrypt returns a bool and that the MLDSAUtil method itself returns a bool status. Integrating encryption failures into the overall success flag and switching to static_cast is both safe and consistent with existing patterns in P11Attributes.cpp and SoftHSM.cpp.

Refactor MLDSAUtil to check encrypt() results and use static_cast

• In src/lib/crypto/MLDSAUtil.cpp, lines 93–105:
– Wrap the two token->encrypt(...) calls in a boolean check (e.g. encOK)
– On failure, recycle the private key and algorithm, then return false immediately
– Replace all C–style casts on priv with static_cast<MLDSAPrivateKey*>

• Downstream code already combines bOK from setAttribute; no change needed there beyond using the safer cast for getParameterSet().

Revised snippet:

-    if (isPrivate)
-    {
-        token->encrypt(((MLDSAPrivateKey*)priv)->getSeed(), seed);
-        token->encrypt(((MLDSAPrivateKey*)priv)->getValue(), value);
-    }
+    if (isPrivate)
+    {
+        bool encOK = true;
+        auto mlpriv = static_cast<MLDSAPrivateKey*>(priv);
+        encOK = encOK && token->encrypt(mlpriv->getSeed(), seed);
+        encOK = encOK && token->encrypt(mlpriv->getValue(), value);
+        if (!encOK)
+        {
+            mldsa->recyclePrivateKey(priv);
+            CryptoFactory::i()->recycleAsymmetricAlgorithm(mldsa);
+            return false;
+        }
+    }
     else
     {
-        seed = ((MLDSAPrivateKey*)priv)->getSeed();
-        value = ((MLDSAPrivateKey*)priv)->getValue();
+        auto mlpriv = static_cast<MLDSAPrivateKey*>(priv);
+        seed = mlpriv->getSeed();
+        value = mlpriv->getValue();
     }
     bool bOK = true;
-    bOK = bOK && key->setAttribute(CKA_PARAMETER_SET, ((MLDSAPrivateKey*)priv)->getParameterSet());
+    bOK = bOK && key->setAttribute(CKA_PARAMETER_SET,
+                                   static_cast<MLDSAPrivateKey*>(priv)->getParameterSet());
     bOK = bOK && key->setAttribute(CKA_SEED, seed);
     bOK = bOK && key->setAttribute(CKA_VALUE, value);

This aligns MLDSAUtil with existing encrypt()–error–handling patterns and uses modern C++ casts.


71-111: Ignore the parameter-set setter suggestion

After reviewing the code:

  • MLDSAUtil::setMLDSAPrivateKey already writes CKA_PARAMETER_SET to the token object.
  • MLDSAPrivateKey and MLDSAPublicKey only expose getParameterSet(), which computes the domain parameters from the key’s value size—there is no setParameterSet() on those classes.
  • A manual setter on the in-memory key isn’t available or needed.

No changes required.

Likely an incorrect or invalid review comment.

src/lib/SoftHSM.cpp (6)

65-70: ML-DSA headers are correctly gated

Includes for ML-DSA are properly hidden behind WITH_ML_DSA. No concerns here.


832-836: Mechanism table extended for ML-DSA

CKM_ML_DSA_KEY_PAIR_GEN and CKM_ML_DSA added and gated. Looks good.


941-944: Mechanism info for ML-DSA is consistent and gated

  • Min/max sizes are queried via CryptoFactory behind WITH_ML_DSA.
  • CKM_ML_DSA and KEY_PAIR_GEN expose sensible flags (SIGN/VERIFY and GENERATE_KEY_PAIR).

LGTM.

Also applies to: 1041-1052, 1339-1349


6167-6170: Key-pair generation flow and ML-DSA routing

C_GenerateKeyPair integration (mechanism dispatch and routing to generateMLDSA) looks correct and gated via WITH_ML_DSA. No issues.

Also applies to: 6198-6200, 6224-6225, 6298-6305


5672-5673: Minor: Debug output is fine

Extra DEBUG_MSG with sizes only (no sensitive data) is acceptable.

Also applies to: 5856-5856


149-151: Wrap ML-DSA support in WITH_ML_DSA guards

The ML-DSA key-object classes and their use in SoftHSM.cpp aren’t currently wrapped in #ifdef WITH_ML_DSA, so they’ll always be compiled—even when ML-DSA support is disabled. We need to guard both the class definitions and the CKK_ML_DSA branches.

Locations to fix:

  • src/lib/P11Objects.h
    • Line 201: class P11MLDSAPublicKeyObj
    • Line 303: class P11MLDSAPrivateKeyObj
      Wrap each definition in:
    #ifdef WITH_ML_DSA
    class P11MLDSAPublicKeyObj : public P11PublicKeyObj { … };
    //
    class P11MLDSAPrivateKeyObj : public P11PrivateKeyObj { … };
    #endif // WITH_ML_DSA
  • src/lib/SoftHSM.cpp
    • Lines 149–151 and 169–170: the else if (keyType == CKK_ML_DSA) branches
      Wrap these cases in the same guard, for example:
     #ifdef WITH_ML_DSA
  • else if (keyType == CKK_ML_DSA)
  •  *p11object = new P11MLDSAPublicKeyObj();
    
  • else if (keyType == CKK_ML_DSA) {
  •  *p11object = new P11MLDSAPublicKeyObj();
    
  • }
    #endif // WITH_ML_DSA



<details>
<summary>⛔ Skipped due to learnings</summary>

Learnt from: antoinelochet
PR: #809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.642Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.


</details>

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@antoinelochet antoinelochet force-pushed the ml-dsa-pr branch 2 times, most recently from 3bf81b0 to 54e8ee0 Compare August 18, 2025 19:44
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/lib/SoftHSM.cpp (1)

5445-5556: ML-DSA verify path: propagate parameter set to MLDSAPublicKey

After calling

MLDSAUtil::getMLDSAPublicKey((MLDSAPublicKey*)publicKey, token, key)

in the CKM_ML_DSA branch (src/lib/SoftHSM.cpp around lines 5445–5556), you must also read the CKA_PARAMETER_SET from the OSObject and apply it to the in-memory public key so the backend has the full context for verification. For example:

Suggested diff (around line 5550):

     if (MLDSAUtil::getMLDSAPublicKey((MLDSAPublicKey*)publicKey, token, key) != CKR_OK)
     {
         asymCrypto->recyclePublicKey(publicKey);
         CryptoFactory::i()->recycleAsymmetricAlgorithm(asymCrypto);
         return CKR_GENERAL_ERROR;
     }
+    // Apply parameter set for ML-DSA verify context
+    {
+        CK_ULONG paramSet = key->getUnsignedLongValue(CKA_PARAMETER_SET);
+        ((MLDSAPublicKey*)publicKey)->setParameterSet(paramSet);
+    }

This mirrors the sign path and ensures the verifier knows which ML-DSA parameters to use.

♻️ Duplicate comments (2)
src/lib/SoftHSM.cpp (2)

10020-10277: generateMLDSA: overall solid, but remove an unused variable and consider return code convention

  • The flow for extracting CKA_PARAMETER_SET, parameter validation, keypair generation, and attribute population is consistent with the project style (using CKP_ML_DSA_* and returning CKR_PARAMETER_SET_NOT_SUPPORTED). Keeping that is fine given the current repository convention.

  • There’s an unused variable in the private key block:

    • ByteString parameterSet; is declared but never used.

Apply this small cleanup:

@@
-                // MLDSA Private Key Attributes
-                ByteString parameterSet;
-                ByteString value;
-                ByteString seed;
+                // MLDSA Private Key Attributes
+                ByteString value;
+                ByteString seed;

6421-6439: CBC wrap bug: empty IV used for non-PAD modes (AES_CBC/DES3_CBC) and missing input size checks

For CKM_AES_CBC and CKM_DES3_CBC you never set blocksize. Consequently:

  • iv.resize(blocksize) yields a 0-length IV
  • memcpy copies 0 bytes
  • encryptInit gets an empty IV for CBC, which will fail or be unsafe

Additionally, for non-PAD modes the input must be a multiple of the block size.

Fix by setting blocksize, validating IV length, and (recommended) enforcing block-aligned input when no padding:

@@
-        case CKM_AES_CBC:
-            algo = SymAlgo::AES;
-            break;
+        case CKM_AES_CBC:
+            algo = SymAlgo::AES;
+            blocksize = 16;
+            if (wrappedlen % blocksize != 0) return CKR_KEY_SIZE_RANGE;
+            break;
@@
-        case CKM_DES3_CBC:
-            algo = SymAlgo::DES3;
-            break;
+        case CKM_DES3_CBC:
+            algo = SymAlgo::DES3;
+            blocksize = 8;
+            if (wrappedlen % blocksize != 0) return CKR_KEY_SIZE_RANGE;
+            break;
@@
-        case CKM_AES_CBC:
-        case CKM_AES_CBC_PAD:
-        case CKM_DES3_CBC:
-        case CKM_DES3_CBC_PAD:
-            iv.resize(blocksize);
-            memcpy(&iv[0], pMechanism->pParameter, blocksize);
+        case CKM_AES_CBC:
+        case CKM_AES_CBC_PAD:
+        case CKM_DES3_CBC:
+        case CKM_DES3_CBC_PAD:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != blocksize) {
+                cipher->recycleKey(wrappingkey);
+                CryptoFactory::i()->recycleSymmetricAlgorithm(cipher);
+                return CKR_ARGUMENTS_BAD;
+            }
+            iv.resize(blocksize);
+            memcpy(&iv[0], pMechanism->pParameter, blocksize);

This mirrors the already-correct PAD paths and prevents empty-IV CBC usage.

Also applies to: 6463-6470

🧹 Nitpick comments (5)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (5)

61-63: Fix typo in error message

"Could not get private key private" → "Could not get private key".

Apply this diff:

-		ERROR_MSG("Could not get private key private, rv: %d", rv);
+		ERROR_MSG("Could not get private key, rv: %d", rv);

127-132: Harden PKCS#8 decode by checking the key algorithm is ML-DSA

Guard against mis-decoding a different algorithm into this class.

Apply this diff:

 	EVP_PKEY* localPKey = EVP_PKCS82PKEY(p8);
 	PKCS8_PRIV_KEY_INFO_free(p8);
 	if (localPKey == NULL) return false;
+	// Ensure the decoded key is ML-DSA
+	if (!EVP_PKEY_is_a(localPKey, "ML-DSA")) {
+		EVP_PKEY_free(localPKey);
+		return false;
+	}
 	setFromOSSL(localPKey);
 	EVP_PKEY_free(localPKey);
 	return true;

122-126: Avoid potential size truncation when converting BER length

ber.size() is size_t; storing into int risks truncation for very large inputs.

Apply this diff:

-	int len = ber.size();
-	if (len <= 0) return false;
+	if (ber.size() == 0) return false;
+	if (ber.size() > LONG_MAX) return false;
+	long len = static_cast<long>(ber.size());

Additionally, ensure LONG_MAX is available by including climits at the top of the file:

// add near other includes
#include <climits>

13-15: Remove unused include to keep build surface minimal

openssl/bn.h isn't used here.

Apply this diff:

-#include <openssl/bn.h>

44-69: Zeroize stack buffers holding sensitive material before return

Seed and private-key buffers remain on the stack; zeroizing is a small hardening win. Consider wiping in both success and error paths.

Apply this diff to wipe on the success path:

 ByteString valueBS = ByteString(priv, priv_len);

 setValue(valueBS);
+OPENSSL_cleanse(priv, sizeof(priv));
+OPENSSL_cleanse(seed, sizeof(seed));

And in the error branch before returning:

-	if(!rv) {
-		ERROR_MSG("Could not get private key, rv: %d", rv);
-		return;
-	}
+	if(!rv) {
+		ERROR_MSG("Could not get private key, rv: %d", rv);
+		OPENSSL_cleanse(priv, sizeof(priv));
+		OPENSSL_cleanse(seed, sizeof(seed));
+		return;
+	}

Add the necessary include:

// near other includes
#include <openssl/crypto.h> // for OPENSSL_cleanse
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 98ba23a and 54e8ee0.

📒 Files selected for processing (2)
  • src/lib/SoftHSM.cpp (34 hunks)
  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-18T17:59:30.642Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.642Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp
📚 Learning: 2025-08-18T19:46:46.645Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.645Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp
🧬 Code Graph Analysis (2)
src/lib/SoftHSM.cpp (1)
src/lib/crypto/MLDSAUtil.cpp (6)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-111)
  • setMLDSAPrivateKey (71-71)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (3)
src/lib/crypto/OSSLMLDSAPublicKey.cpp (10)
  • setFromOSSL (44-60)
  • setFromOSSL (44-44)
  • setValue (68-76)
  • setValue (68-68)
  • isOfType (63-66)
  • isOfType (63-63)
  • getOSSLKey (79-84)
  • getOSSLKey (79-79)
  • createOSSLKey (87-125)
  • createOSSLKey (87-87)
src/lib/crypto/MLDSAPrivateKey.cpp (10)
  • setSeed (65-68)
  • setSeed (65-65)
  • setValue (55-58)
  • setValue (55-55)
  • isOfType (17-20)
  • isOfType (17-17)
  • getValue (60-63)
  • getValue (60-60)
  • getParameterSet (28-39)
  • getParameterSet (28-28)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (224-233)
  • mldsaParameterSet2Name (224-224)
🔇 Additional comments (12)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (6)

31-38: Proper pkey lifecycle management in destructor — LGTM

Destructor correctly frees pkey and nulls it, preventing leaks/double-frees.


44-55: Seed retrieval handled correctly — LGTM

Optional seed is read and applied only when present and of expected length.


98-117: PKCS#8 encode path frees resources on all branches — LGTM

Happy path and error paths release PKCS8_PRIV_KEY_INFO correctly; der consistency check is good.


120-133: Memory leak from earlier revision is resolved — LGTM

EVP_PKEY from PKCS#8 gets freed after setFromOSSL(localPKey); good catch and fix.


150-160: Good defensive checks for unknown parameter set and empty key

Fail-fast with clear logs prevents undefined behavior when building EVP_PKEY.


172-191: Provider-based key creation path looks correct — LGTM

No pre-allocation of pkey before EVP_PKEY_fromdata, proper ctx cleanup on all paths.

src/lib/SoftHSM.cpp (6)

65-70: Good encapsulation: ML-DSA headers are properly compiled behind WITH_ML_DSA

The conditional includes keep non-ML builds unaffected. No issues.


149-152: P11 ML-DSA objects are correctly gated with WITH_ML_DSA

Adding P11MLDSAPublicKeyObj and P11MLDSAPrivateKeyObj under the compile guard prevents build breaks on non-ML configurations. Looks good.

Also applies to: 171-173


837-840: Mechanism registry updated for ML-DSA

CKM_ML_DSA_KEY_PAIR_GEN and CKM_ML_DSA are registered under WITH_ML_DSA. This aligns with the overall gating strategy.


945-947: Mechanism info: ML-DSA min/max sizes wired through CryptoFactory

Retrieval and propagation of key sizes for CKM_ML_DSA(_KEY_PAIR_GEN) are consistent with the rest of the codebase and guarded behind WITH_ML_DSA. Flags are appropriate (generate/sign/verify).

Also applies to: 1045-1056, 1343-1353


6171-6174: Keygen dispatch and template validation for ML-DSA

  • CKM_ML_DSA_KEY_PAIR_GEN is correctly integrated into dispatch.
  • Template consistency checks mirror other mechanisms.
  • Dispatch to generateMLDSA is straightforward.

All good.

Also applies to: 6202-6204, 6227-6229, 6302-6309


6899-6902: Wrap/unwrap integration for ML-DSA private keys looks correct

  • Wrap path maps CKK_ML_DSA to AsymAlgo::MLDSA and uses MLDSAUtil::getMLDSAPrivateKey before PKCS8 encoding; good.
  • Unwrap path stores ML-DSA private key with MLDSAUtil::setMLDSAPrivateKey; good.

Assuming MLDSAUtil is updated to also carry the parameter set on runtime key objects (see other comments), this integration is sound.

Also applies to: 6943-6946, 7624-7627

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/SoftHSM.cpp (1)

12423-12454: Bug: CKA_NEVER_EXTRACTABLE assignment uses CKA_ALWAYS_SENSITIVE

In the CKM_CONCATENATE_BASE_AND_KEY derivation path, the computed bNeverExtractable is assigned to CKA_ALWAYS_SENSITIVE, which is incorrect. It should set CKA_NEVER_EXTRACTABLE.

Apply:

-                bool bNeverExtractable = baseKey->getBooleanValue(CKA_NEVER_EXTRACTABLE, false) &&
-                                         otherKey->getBooleanValue(CKA_NEVER_EXTRACTABLE, false);
-                bOK = bOK && osobject->setAttribute(CKA_ALWAYS_SENSITIVE, bNeverExtractable);
+                bool bNeverExtractable = baseKey->getBooleanValue(CKA_NEVER_EXTRACTABLE, false) &&
+                                         otherKey->getBooleanValue(CKA_NEVER_EXTRACTABLE, false);
+                bOK = bOK && osobject->setAttribute(CKA_NEVER_EXTRACTABLE, bNeverExtractable);
🧹 Nitpick comments (1)
src/lib/SoftHSM.cpp (1)

10223-10241: Remove unused local ‘parameterSet’ in ML-DSA private-key attributes

‘ByteString parameterSet;’ is unused in this block.

Apply:

-                // MLDSA Private Key Attributes
-                ByteString parameterSet;
-                ByteString value;
-                ByteString seed;
+                // MLDSA Private Key Attributes
+                ByteString value;
+                ByteString seed;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7dbc7 and 5fac462.

📒 Files selected for processing (1)
  • src/lib/SoftHSM.cpp (34 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.386Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.
📚 Learning: 2025-08-19T09:23:25.386Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.386Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2025-08-18T19:46:46.645Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.645Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2025-08-18T17:59:30.642Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.642Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/SoftHSM.cpp
🧬 Code Graph Analysis (1)
src/lib/SoftHSM.cpp (2)
src/lib/crypto/CryptoFactory.cpp (4)
  • i (43-46)
  • i (43-43)
  • i (59-62)
  • i (59-59)
src/lib/crypto/MLDSAUtil.cpp (6)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-111)
  • setMLDSAPrivateKey (71-71)
🔇 Additional comments (11)
src/lib/SoftHSM.cpp (11)

65-70: LGTM: ML-DSA headers are correctly feature-gated

Including ML-DSA headers under WITH_ML_DSA keeps non-ML builds clean.


149-152: LGTM: P11MLDSA object creation is behind WITH_ML_DSA*

The new P11 object branches for CKK_ML_DSA are correctly wrapped with the feature guard.

Also applies to: 171-173


836-840: LGTM: Mechanism map extended with ML-DSA

CKM_ML_DSA_KEY_PAIR_GEN and CKM_ML_DSA added consistently with existing style.


945-948: LGTM: Mechanism info for ML-DSA added

  • Min/max key sizes are fetched via CryptoFactory and recycled properly.
  • Mechanism flags for keygen and sign/verify look correct.

Also applies to: 1045-1056, 1343-1353


1147-1147: Confirm reduced CKM_GENERIC_SECRET_KEY_GEN max size (0x8000000)

You’ve reduced ulMaxKeySize from 0x80000000 to 0x8000000. This matches the check in generateGeneric (CKA_VALUE_LEN upper bound). Please confirm this change is intentional and won’t break existing clients depending on larger generic secret sizes.


4194-4443: LGTM: ML-DSA sign path integration

  • Proper mechanism flagging, bAllowMultiPartOp=false, and use of MLDSAUtil for runtime key hydration.
  • Parameter-set inference via key material length (no explicit setter) is consistent with the ML-DSA key types design.

Also applies to: 4531-4550


5203-5450: LGTM: ML-DSA verify path integration

  • Mechanism mapping and object instantiation are consistent with the sign path.
  • Uses MLDSAUtil for public key material.

Also applies to: 5538-5556


6739-6743: Wrap supports AES_CBC, but unwrap only supports AES_CBC_PAD — clarify intent

C_WrapKey permits CKM_AES_CBC while UnwrapKeySym only implements PAD modes (CKM_AES_CBC_PAD/CKM_DES3_CBC_PAD). This asymmetry may surprise users.

  • Either disallow CKM_AES_CBC in C_WrapKey, or add the corresponding non-PAD CBC path in UnwrapKeySym (with strict block/IV validation).

Also applies to: 7009-7035


10049-10076: LGTM: CKA_PARAMETER_SET parsing and validation

  • Length check against sizeof(CK_ULONG) is correct.
  • Using CKP_ML_DSA_44/65/87 constants improves clarity.
  • Return code on unsupported set is CKR_PARAMETER_SET_NOT_SUPPORTED (assumed intentionally defined).

6890-6903: LGTM: ML-DSA integrated in wrap/unwrap flows

  • Correct keyType routing to MLDSA for wrapping/unwrapping private keys.
  • Uses MLDSAUtil for extracting/setting key material.

Also applies to: 6944-6947, 7625-7628


10099-10109: LGTM: ML-DSA key objects persist parameter set + seed/value

  • Public key stores CKA_PARAMETER_SET and CKA_VALUE (optionally encrypted).
  • Private key stores CKA_PARAMETER_SET, CKA_VALUE, and CKA_SEED (optionally encrypted).
    No secret material is logged — good.

Also applies to: 10156-10166, 10200-10210, 10238-10241

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/lib/SoftHSM.cpp (1)

6421-6440: CBC wrap uses zero-length IV for non‑PAD modes — functional bug and potential UB

For CKM_AES_CBC and CKM_DES3_CBC, blocksize isn’t set, but you later do iv.resize(blocksize) and memcpy(..., blocksize). This yields a zero-length IV and memcpy with 0 bytes; accessing &iv[0] when size() == 0 is undefined behavior. Result: encryption init may use an empty IV or crash.

Fix by:

  • Setting blocksize for non‑PAD modes (16 for AES, 8 for 3DES).
  • Validating pParameter is non‑NULL and ulParameterLen equals blocksize for all CBC cases before memcpy/encryptInit.

Apply:

@@
-        case CKM_AES_CBC:
-            algo = SymAlgo::AES;
-            break;
+        case CKM_AES_CBC:
+            algo = SymAlgo::AES;
+            blocksize = 16;
+            break;
@@
-        case CKM_DES3_CBC:
-            algo = SymAlgo::DES3;
-            break;
+        case CKM_DES3_CBC:
+            algo = SymAlgo::DES3;
+            blocksize = 8;
+            break;
@@
-        case CKM_AES_CBC:
-        case CKM_AES_CBC_PAD:
-        case CKM_DES3_CBC:
-        case CKM_DES3_CBC_PAD:
-            iv.resize(blocksize);
-            memcpy(&iv[0], pMechanism->pParameter, blocksize);
+        case CKM_AES_CBC:
+        case CKM_AES_CBC_PAD:
+        case CKM_DES3_CBC:
+        case CKM_DES3_CBC_PAD:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != blocksize) {
+                cipher->recycleKey(wrappingkey);
+                CryptoFactory::i()->recycleSymmetricAlgorithm(cipher);
+                return CKR_ARGUMENTS_BAD;
+            }
+            iv.resize(blocksize);
+            memcpy(&iv[0], pMechanism->pParameter, blocksize);

Optional (recommended): for non‑PAD modes also check keydata.size() is a multiple of blocksize before encrypting.

This is a correctness issue that can break CBC wrapping at runtime.

Also applies to: 6464-6470

🧹 Nitpick comments (2)
src/lib/SoftHSM.cpp (2)

10021-10279: Consider adding targeted tests for ML‑DSA generation/sign/verify

New surface area merits basic tests to catch regressions:

  • C_GenerateKeyPair with CKA_PARAMETER_SET = CKP_ML_DSA_44/65/87
  • C_SignInit/C_Sign and C_VerifyInit/C_Verify with CKM_ML_DSA
  • Attribute persistence: CKA_PARAMETER_SET, CKA_SEED, CKA_VALUE (private), CKA_VALUE (public)

I can draft unit/integration tests for these flows if helpful.


10223-10227: Remove unused local variable ‘parameterSet’

‘ByteString parameterSet;’ is declared but never used in generateMLDSA’s private-key block. Clean it up to avoid warnings.

-                // MLDSA Private Key Attributes
-                ByteString parameterSet;
-                ByteString value;
-                ByteString seed;
+                // MLDSA Private Key Attributes
+                ByteString value;
+                ByteString seed;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5fac462 and c0ed069.

📒 Files selected for processing (2)
  • src/lib/SoftHSM.cpp (34 hunks)
  • src/lib/crypto/OSSLUtil.cpp (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/crypto/OSSLUtil.cpp
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.386Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.
📚 Learning: 2025-08-19T09:23:25.386Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.386Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2025-08-18T17:59:30.642Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.642Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2025-08-18T19:46:46.645Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.645Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/SoftHSM.cpp
🧬 Code Graph Analysis (1)
src/lib/SoftHSM.cpp (1)
src/lib/crypto/MLDSAUtil.cpp (6)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-111)
  • setMLDSAPrivateKey (71-71)
🔇 Additional comments (8)
src/lib/SoftHSM.cpp (8)

65-70: Correctly gated ML‑DSA headers

Including ML‑DSA headers behind WITH_ML_DSA is consistent with the project’s feature-gating approach. LGTM.


149-152: P11 ML‑DSA object creation properly feature‑gated

The new CKK_ML_DSA branches for P11 public/private objects are correctly wrapped in WITH_ML_DSA. Prevents non-ML builds from referencing unknown symbols. LGTM.

Also applies to: 171-173


837-840: Mechanism registration for ML‑DSA added

CKM_ML_DSA_KEY_PAIR_GEN and CKM_ML_DSA are registered under WITH_ML_DSA. Good integration with supportedMechanisms and config filtering.


945-947: Mechanism info wiring for ML‑DSA is consistent

  • Min/max key sizes fetched via CryptoFactory (mirrors other algos).
  • Flags for keypair gen and sign/verify are set appropriately.
    LGTM.

Also applies to: 1044-1056, 1343-1353


4438-4443: Sign path: ML‑DSA integration via MLDSAUtil

CKM_ML_DSA routed to AsymMech::MLDSA; key material loaded using MLDSAUtil. Parameter set is inferred from key length in MLDSA key classes, so no explicit setParameterSet is needed. Looks correct.

Also applies to: 4531-4550


5445-5450: Verify path: ML‑DSA integration via MLDSAUtil

CKM_ML_DSA routed to AsymMech::MLDSA; MLDSAPublicKey populated via MLDSAUtil. Parameter set inference by key size keeps this path simple. LGTM.

Also applies to: 5538-5556


6171-6174: Key-pair generation: ML‑DSA routing and template checks

  • CKM_ML_DSA_KEY_PAIR_GEN mapped to CKK_ML_DSA in both public/private template checks.
  • generateMLDSA() invoked under WITH_ML_DSA.
    Template consistency handling is aligned with other key types. LGTM.

Also applies to: 6202-6204, 6228-6229, 6303-6311


10049-10076: CKA_PARAMETER_SET handling: length check and constants are correct

  • Validates ulValueLen == sizeof(CK_ULONG) before reading.
  • Uses CKP_ML_DSA_44/65/87 constants (consistent with project preference).
  • Returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets (as per current codebase semantics).
    LGTM.

Copy link
Member

@bukka bukka left a comment

Choose a reason for hiding this comment

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

It looks pretty good from the initial look. Just few things but I haven't checked to logic too deeply and haven't tested it either.

@jschlyter It would be great if the workflow could be enabled so pipeline can run.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/SoftHSM.cpp (2)

7070-7092: If keeping non-PAD CBC wrap, add the matching unwrap path

Currently only PAD cases are implemented. Add CKM_AES_CBC and CKM_DES3_CBC here with blocksize set and padding=false, plus IV length validation, to make wrap/unwrap symmetric.

Happy to provide a full patch if you choose Option B (support non-PAD).

Also applies to: 7116-7151


6799-6804: Align AES‑CBC wrap/unwrap: disallow CKM_AES_CBC (non‑PAD) or implement non‑PAD unwrap

C_WrapKey currently accepts CKM_AES_CBC but UnwrapKeySym only implements CKM_AES_CBC_PAD — pick one approach:

  • Option A (restrict wrap to PAD): change C_WrapKey to accept only CKM_AES_CBC_PAD (src/lib/SoftHSM.cpp around lines 6753–6804).
  • Option B (implement non‑PAD unwrap): add CKM_AES_CBC (no‑pad) handling inside UnwrapKeySym (implementation starts ~7054; current cases at 7083/7118/7429).

Option A (recommended small-scope fix):

-        case CKM_AES_CBC:
-        case CKM_AES_CBC_PAD:
+        case CKM_AES_CBC_PAD:
             if (pMechanism->pParameter == NULL_PTR ||
                 pMechanism->ulParameterLen != 16)
                 return CKR_ARGUMENTS_BAD;
             break;
♻️ Duplicate comments (4)
src/lib/crypto/OSSLMLDSAPublicKey.h (1)

29-31: Polymorphic type check LGTM.

The .cpp now returns true for both this class’s type and MLDSAPublicKey::type. This resolves the earlier concern.

src/lib/crypto/OSSLMLDSAPrivateKey.cpp (1)

119-133: PKCS#8 decode leak fixed

EVP_PKEY is now freed after setFromOSSL(); good catch.

src/lib/crypto/OSSLMLDSA.h (1)

7-9: Include guard consistency: fixed

Guard now matches; all good.

Also applies to: 54-54

src/lib/SoftHSM.cpp (1)

6481-6500: CBC wrap bug: zero-length IV used for AES_CBC/DES3_CBC (non-PAD)

blocksize remains 0 for CKM_AES_CBC and CKM_DES3_CBC; you then copy a 0-byte IV and call encryptInit with an empty IV. This will fail or be incorrect. Please either set blocksize and validate IV length, or reject non-PAD modes.

Minimal fix:

-        case CKM_AES_CBC:
-            algo = SymAlgo::AES;
-            break;
+        case CKM_AES_CBC:
+            algo = SymAlgo::AES;
+            blocksize = 16;
+            break;
@@
-        case CKM_DES3_CBC:
-            algo = SymAlgo::DES3;
-            break;
+        case CKM_DES3_CBC:
+            algo = SymAlgo::DES3;
+            blocksize = 8;
+            break;
@@
-        case CKM_AES_CBC:
-        case CKM_AES_CBC_PAD:
-        case CKM_DES3_CBC:
-        case CKM_DES3_CBC_PAD:
-            iv.resize(blocksize);
-            memcpy(&iv[0], pMechanism->pParameter, blocksize);
+        case CKM_AES_CBC:
+        case CKM_AES_CBC_PAD:
+        case CKM_DES3_CBC:
+        case CKM_DES3_CBC_PAD:
+            if (pMechanism->pParameter == NULL_PTR ||
+                pMechanism->ulParameterLen != blocksize) {
+                cipher->recycleKey(wrappingkey);
+                CryptoFactory::i()->recycleSymmetricAlgorithm(cipher);
+                return CKR_ARGUMENTS_BAD;
+            }
+            iv.resize(blocksize);
+            memcpy(&iv[0], pMechanism->pParameter, blocksize);

Optional: enforce wrappedlen % blocksize == 0 for non-PAD modes before encrypting.

Also applies to: 6524-6531

🧹 Nitpick comments (15)
src/lib/crypto/OSSLMLDSAPublicKey.h (5)

21-21: Parameter name consistency (+make ctor explicit).

Align with the .cpp and avoid implicit conversions.

-    OSSLMLDSAPublicKey(const EVP_PKEY* inMLDSAKEY);
+    explicit OSSLMLDSAPublicKey(const EVP_PKEY* inEVPPKEY);

Mirror the rename in OSSLMLDSAPublicKey.cpp constructor signature. Also consider renaming the setFromOSSL parameter for consistency (see below).


29-33: Add override specifiers for safer vtable checks.

Marking overrides helps catch signature drift at compile time.

-    virtual bool isOfType(const char* inType);
+    virtual bool isOfType(const char* inType) override;
 
-    virtual void setValue(const ByteString& value);
+    virtual void setValue(const ByteString& value) override;

34-36: Rename param to match OpenSSL type; fix error message in .cpp.

Minor consistency: use inEVPPKEY here, matching the type and the ctor.

Also, in OSSLMLDSAPublicKey.cpp inside setFromOSSL(), the error string says “private key private” while fetching OSSL_PKEY_PARAM_PUB_KEY; change to “public key”.

Header tweak:

-    virtual void setFromOSSL(const EVP_PKEY* inMLDSAKEY);
+    virtual void setFromOSSL(const EVP_PKEY* inEVPPKEY);

CPP tweak (not in this hunk):

-    ERROR_MSG("Could not get private key private, rv: %d", rv);
+    ERROR_MSG("Could not get public key, rv: %d", rv);

13-13: Reduce header coupling: forward-declare EVP_PKEY.

To limit transitive includes from a widely included header, forward-declare EVP_PKEY here and include <openssl/evp.h> in the .cpp.

-#include <openssl/evp.h>
+// Forward declaration to decouple header from OpenSSL headers
+typedef struct evp_pkey_st EVP_PKEY;

Ensure OSSLMLDSAPublicKey.cpp retains #include <openssl/evp.h>.


48-49: Fix include-guard trailing comment typo.

The guard name includes “ML”. Update the trailing comment to match.

-#endif // !_SOFTHSM_V2_OSSLDSAPUBLICKEY_H
+#endif // !_SOFTHSM_V2_OSSLMLDSAPUBLICKEY_H
README.md (1)

85-85: Documented option looks good; fix hard tab to satisfy markdownlint (MD010).

Replace the tab(s) on this new line with spaces, or fence the whole options block to silence the linter.

-	--enable-mldsa		Enable support for ML-DSA (default detect)
+  --enable-mldsa      Enable support for ML-DSA (default detect)
src/lib/crypto/OSSLMLDSAPrivateKey.h (2)

13-15: Trim header includes; forward-declare EVP_PKEY to reduce compile surface.

-#include <openssl/bn.h>
-#include <openssl/evp.h>
+// Forward declaration; include <openssl/evp.h> in the .cpp
+typedef struct evp_pkey_st EVP_PKEY;

58-58: Fix end-guard comment typo.

-#endif // !_SOFTHSM_V2_OSSLECPRIVATEKEY_H
+#endif // !_SOFTHSM_V2_OSSLMLDSAPRIVATEKEY_H
src/lib/crypto/OSSLUtil.cpp (1)

43-49: Optional: replace the map with a switch to drop and constexpr-initialize.

-#ifdef WITH_ML_DSA
-static const std::map<unsigned long, const char*> mldsaAlgNameFromParameterSet {
-	{MLDSAParameters::ML_DSA_44_PARAMETER_SET, "ML-DSA-44"},
-	{MLDSAParameters::ML_DSA_65_PARAMETER_SET, "ML-DSA-65"},
-	{MLDSAParameters::ML_DSA_87_PARAMETER_SET, "ML-DSA-87"}
-};
-#endif
+#ifdef WITH_ML_DSA
+#include <openssl/core_names.h>
+#endif

And:

-const char* OSSL::mldsaParameterSet2Name(unsigned long parameterSet) {
-
-	std::map<unsigned long, const char*>::const_iterator it = mldsaAlgNameFromParameterSet.find(parameterSet);
-
-	if (it != mldsaAlgNameFromParameterSet.end()) {
-		return it->second;
-	}
-
-	return NULL;
-}
+const char* OSSL::mldsaParameterSet2Name(unsigned long parameterSet) {
+	switch (parameterSet) {
+	case MLDSAParameters::ML_DSA_44_PARAMETER_SET: return "ML-DSA-44";
+	case MLDSAParameters::ML_DSA_65_PARAMETER_SET: return "ML-DSA-65";
+	case MLDSAParameters::ML_DSA_87_PARAMETER_SET: return "ML-DSA-87";
+	default: return NULL;
+	}
+}
m4/acx_crypto_backend.m4 (1)

188-191: Handle --enable-mldsa=detect on Botan path explicitly

For symmetry with ECC/EDDSA handling, set detect→no on Botan to make logs deterministic and avoid leaving "detect" dangling.

Apply:

-        if test  "x${enable_mldsa}" = "xyes"; then
-            AC_MSG_ERROR([Botan does not support ML-DSA])
-        fi
+        case "${enable_mldsa}" in
+            yes) AC_MSG_ERROR([Botan does not support ML-DSA]);;
+            detect) enable_mldsa="no";;
+        esac
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (3)

23-29: Guard against NULL EVP_PKEY in ctor-from-OSSL

Defensive check avoids accidental nullptr deref if misused.

 OSSLMLDSAPrivateKey::OSSLMLDSAPrivateKey(const EVP_PKEY* inMLDSAKEY)
 {
     pkey = NULL;
 
-    setFromOSSL(inMLDSAKEY);
+    if (inMLDSAKEY == NULL) {
+        ERROR_MSG("NULL EVP_PKEY passed to OSSLMLDSAPrivateKey ctor");
+        return;
+    }
+    setFromOSSL(inMLDSAKEY);
 }

46-54: Zeroise temporary key buffers after use

Wipe seed/priv stack buffers after copying to ByteString to reduce leakage surface.

 #include "OSSLUtil.h"
 #include <openssl/bn.h>
 #include <openssl/core_names.h>
+#include <openssl/crypto.h>
 #include <openssl/x509.h>
@@
     if(rv && seed_len == 32) {
         // seed is not mandatory for OSSL key reconstruction
         ByteString seedBS = ByteString(seed, seed_len);
         setSeed(seedBS);
     }
+    OPENSSL_cleanse(seed, sizeof(seed));
@@
     ByteString valueBS = ByteString(priv, priv_len);
 
     setValue(valueBS);
+    OPENSSL_cleanse(priv, sizeof(priv));

Also applies to: 56-69


59-65: Fix log message typo

“private key private” → “private key value”.

-    if(!rv) {
-        ERROR_MSG("Could not get private key private, rv: %d", rv);
+    if(!rv) {
+        ERROR_MSG("Could not get private key value, rv: %d", rv);
         return;
     }
src/lib/crypto/OSSLMLDSA.h (1)

10-13: Gate header contents under WITH_ML_DSA to avoid OpenSSL header requirements in non-ML-DSA builds

Prevents accidental inclusion on Botan-only builds.

 #ifndef _SOFTHSM_V2_OSSLMLDSA_H
 #define _SOFTHSM_V2_OSSLMLDSA_H
 
 #include "config.h"
-#include "AsymmetricAlgorithm.h"
-#include <openssl/evp.h>
+#ifdef WITH_ML_DSA
+#include "AsymmetricAlgorithm.h"
+#include <openssl/evp.h>
@@
 private:
 };
 
-#endif // !_SOFTHSM_V2_OSSLMLDSA_H
+#endif // WITH_ML_DSA
+#endif // !_SOFTHSM_V2_OSSLMLDSA_H

Also applies to: 14-52

src/lib/SoftHSM.cpp (1)

10284-10286: Remove unused local variable in ML-DSA private-key block

ByteString parameterSet is unused; trim to avoid warnings.

-                // MLDSA Private Key Attributes
-                ByteString parameterSet;
-                ByteString value;
-                ByteString seed;
+                // MLDSA Private Key Attributes
+                ByteString value;
+                ByteString seed;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0ed069 and 71b03bc.

📒 Files selected for processing (44)
  • .github/workflows/ci.yml (1 hunks)
  • CMAKE-NOTES.md (1 hunks)
  • CMAKE-WIN-NOTES.md (1 hunks)
  • CMakeLists.txt (1 hunks)
  • README.md (1 hunks)
  • config.h.in.cmake (1 hunks)
  • m4/acx_crypto_backend.m4 (4 hunks)
  • m4/acx_openssl_mldsa.m4 (1 hunks)
  • src/bin/dump/tables.h (3 hunks)
  • src/lib/P11Attributes.cpp (2 hunks)
  • src/lib/P11Attributes.h (2 hunks)
  • src/lib/P11Objects.cpp (2 hunks)
  • src/lib/P11Objects.h (2 hunks)
  • src/lib/SoftHSM.cpp (34 hunks)
  • src/lib/SoftHSM.h (2 hunks)
  • src/lib/crypto/AsymmetricAlgorithm.h (2 hunks)
  • src/lib/crypto/CMakeLists.txt (2 hunks)
  • src/lib/crypto/MLDSAParameters.cpp (1 hunks)
  • src/lib/crypto/MLDSAParameters.h (1 hunks)
  • src/lib/crypto/MLDSAPrivateKey.cpp (1 hunks)
  • src/lib/crypto/MLDSAPrivateKey.h (1 hunks)
  • src/lib/crypto/MLDSAPublicKey.cpp (1 hunks)
  • src/lib/crypto/MLDSAPublicKey.h (1 hunks)
  • src/lib/crypto/MLDSAUtil.cpp (1 hunks)
  • src/lib/crypto/MLDSAUtil.h (1 hunks)
  • src/lib/crypto/Makefile.am (3 hunks)
  • src/lib/crypto/OSSLComp.cpp (1 hunks)
  • src/lib/crypto/OSSLCryptoFactory.cpp (2 hunks)
  • src/lib/crypto/OSSLMLDSA.cpp (1 hunks)
  • src/lib/crypto/OSSLMLDSA.h (1 hunks)
  • src/lib/crypto/OSSLMLDSAKeyPair.cpp (1 hunks)
  • src/lib/crypto/OSSLMLDSAKeyPair.h (1 hunks)
  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp (1 hunks)
  • src/lib/crypto/OSSLMLDSAPrivateKey.h (1 hunks)
  • src/lib/crypto/OSSLMLDSAPublicKey.cpp (1 hunks)
  • src/lib/crypto/OSSLMLDSAPublicKey.h (1 hunks)
  • src/lib/crypto/OSSLUtil.cpp (2 hunks)
  • src/lib/crypto/OSSLUtil.h (1 hunks)
  • src/lib/crypto/test/CMakeLists.txt (1 hunks)
  • src/lib/crypto/test/MLDSATests.cpp (1 hunks)
  • src/lib/crypto/test/MLDSATests.h (1 hunks)
  • src/lib/crypto/test/Makefile.am (1 hunks)
  • src/lib/object_store/DBObject.cpp (1 hunks)
  • src/lib/pkcs11/pkcs11.h (5 hunks)
✅ Files skipped from review due to trivial changes (3)
  • CMAKE-NOTES.md
  • CMAKE-WIN-NOTES.md
  • src/lib/crypto/OSSLComp.cpp
🚧 Files skipped from review as they are similar to previous changes (31)
  • src/lib/crypto/OSSLUtil.h
  • src/lib/crypto/test/CMakeLists.txt
  • CMakeLists.txt
  • src/lib/crypto/MLDSAPrivateKey.h
  • m4/acx_openssl_mldsa.m4
  • src/lib/crypto/MLDSAPublicKey.h
  • src/lib/crypto/test/MLDSATests.cpp
  • src/lib/crypto/test/MLDSATests.h
  • src/lib/crypto/test/Makefile.am
  • src/lib/crypto/MLDSAParameters.h
  • src/lib/crypto/MLDSAParameters.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/OSSLCryptoFactory.cpp
  • src/bin/dump/tables.h
  • src/lib/crypto/CMakeLists.txt
  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/P11Attributes.h
  • src/lib/crypto/MLDSAPublicKey.cpp
  • src/lib/P11Objects.h
  • src/lib/crypto/Makefile.am
  • src/lib/P11Objects.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/OSSLMLDSAKeyPair.h
  • src/lib/P11Attributes.cpp
  • src/lib/crypto/OSSLMLDSAKeyPair.cpp
  • config.h.in.cmake
  • src/lib/crypto/OSSLMLDSAPublicKey.cpp
  • src/lib/crypto/MLDSAUtil.h
  • .github/workflows/ci.yml
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/pkcs11/pkcs11.h
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: bukka
PR: softhsm/SoftHSMv2#815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.347Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.
📚 Learning: 2025-08-19T09:23:25.434Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/crypto/OSSLMLDSAPublicKey.h
  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp
  • src/lib/crypto/OSSLMLDSA.h
  • src/lib/crypto/OSSLMLDSAPrivateKey.h
  • src/lib/crypto/OSSLUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/SoftHSM.h
📚 Learning: 2025-08-18T17:59:30.693Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/OSSLMLDSAPublicKey.h
  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp
  • src/lib/crypto/OSSLMLDSA.h
  • src/lib/crypto/OSSLMLDSAPrivateKey.h
  • src/lib/crypto/OSSLUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/SoftHSM.h
📚 Learning: 2025-08-18T19:46:46.659Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp
  • src/lib/crypto/OSSLUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/SoftHSM.h
📚 Learning: 2025-09-11T16:54:00.347Z
Learnt from: bukka
PR: softhsm/SoftHSMv2#815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.347Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.

Applied to files:

  • src/lib/crypto/OSSLMLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/SoftHSM.h
📚 Learning: 2025-08-18T17:54:37.645Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.

Applied to files:

  • m4/acx_crypto_backend.m4
🧬 Code graph analysis (6)
src/lib/crypto/OSSLMLDSAPublicKey.h (3)
src/lib/crypto/OSSLMLDSAPublicKey.cpp (13)
  • OSSLMLDSAPublicKey (18-21)
  • OSSLMLDSAPublicKey (23-28)
  • OSSLMLDSAPublicKey (31-38)
  • isOfType (63-66)
  • isOfType (63-63)
  • setValue (68-76)
  • setValue (68-68)
  • setFromOSSL (44-60)
  • setFromOSSL (44-44)
  • getOSSLKey (79-84)
  • getOSSLKey (79-79)
  • createOSSLKey (87-125)
  • createOSSLKey (87-87)
src/lib/crypto/MLDSAPublicKey.h (1)
  • MLDSAPublicKey (14-45)
src/lib/crypto/MLDSAPublicKey.cpp (4)
  • isOfType (17-20)
  • isOfType (17-17)
  • setValue (60-63)
  • setValue (60-60)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (4)
src/lib/crypto/OSSLMLDSAPrivateKey.h (1)
  • OSSLMLDSAPrivateKey (16-56)
src/lib/crypto/OSSLMLDSAPublicKey.cpp (10)
  • setFromOSSL (44-60)
  • setFromOSSL (44-44)
  • setValue (68-76)
  • setValue (68-68)
  • isOfType (63-66)
  • isOfType (63-63)
  • getOSSLKey (79-84)
  • getOSSLKey (79-79)
  • createOSSLKey (87-125)
  • createOSSLKey (87-87)
src/lib/crypto/MLDSAPrivateKey.cpp (10)
  • setSeed (65-68)
  • setSeed (65-65)
  • setValue (55-58)
  • setValue (55-55)
  • isOfType (17-20)
  • isOfType (17-17)
  • getValue (60-63)
  • getValue (60-60)
  • getParameterSet (28-39)
  • getParameterSet (28-28)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (224-233)
  • mldsaParameterSet2Name (224-224)
src/lib/crypto/OSSLMLDSA.h (2)
src/lib/crypto/AsymmetricAlgorithm.h (2)
  • AsymmetricAlgorithm (121-190)
  • AsymmetricAlgorithm (128-128)
src/lib/crypto/OSSLMLDSA.cpp (40)
  • sign (24-97)
  • sign (24-26)
  • signInit (99-105)
  • signInit (99-100)
  • signUpdate (107-112)
  • signUpdate (107-107)
  • signFinal (114-119)
  • signFinal (114-114)
  • verify (122-222)
  • verify (122-124)
  • verifyInit (224-230)
  • verifyInit (224-225)
  • verifyUpdate (232-237)
  • verifyUpdate (232-232)
  • verifyFinal (239-244)
  • verifyFinal (239-239)
  • encrypt (247-253)
  • encrypt (247-248)
  • decrypt (256-262)
  • decrypt (256-257)
  • getMinKeySize (264-267)
  • getMinKeySize (264-264)
  • getMaxKeySize (269-272)
  • getMaxKeySize (269-269)
  • generateKeyPair (282-345)
  • generateKeyPair (282-282)
  • reconstructKeyPair (347-383)
  • reconstructKeyPair (347-347)
  • reconstructPublicKey (385-406)
  • reconstructPublicKey (385-385)
  • reconstructPrivateKey (408-429)
  • reconstructPrivateKey (408-408)
  • reconstructParameters (446-466)
  • reconstructParameters (446-446)
  • newPublicKey (431-434)
  • newPublicKey (431-431)
  • newPrivateKey (436-439)
  • newPrivateKey (436-436)
  • newParameters (441-444)
  • newParameters (441-441)
src/lib/crypto/OSSLMLDSAPrivateKey.h (3)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (19)
  • OSSLMLDSAPrivateKey (18-21)
  • OSSLMLDSAPrivateKey (23-28)
  • OSSLMLDSAPrivateKey (31-38)
  • isOfType (72-75)
  • isOfType (72-72)
  • setValue (77-85)
  • setValue (77-77)
  • setSeed (87-95)
  • setSeed (87-87)
  • PKCS8Encode (98-117)
  • PKCS8Encode (98-98)
  • PKCS8Decode (120-133)
  • PKCS8Decode (120-120)
  • setFromOSSL (44-69)
  • setFromOSSL (44-44)
  • getOSSLKey (136-141)
  • getOSSLKey (136-136)
  • createOSSLKey (144-192)
  • createOSSLKey (144-144)
src/lib/crypto/MLDSAPrivateKey.h (1)
  • MLDSAPrivateKey (13-47)
src/lib/crypto/MLDSAPrivateKey.cpp (6)
  • isOfType (17-20)
  • isOfType (17-17)
  • setValue (55-58)
  • setValue (55-55)
  • setSeed (65-68)
  • setSeed (65-65)
src/lib/SoftHSM.cpp (1)
src/lib/crypto/MLDSAUtil.cpp (6)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-111)
  • setMLDSAPrivateKey (71-71)
src/lib/SoftHSM.h (1)
src/lib/SoftHSM.cpp (2)
  • generateMLDSA (10083-10339)
  • generateMLDSA (10083-10094)
🪛 markdownlint-cli2 (0.17.2)
README.md

85-85: Hard tabs
Column: 1

(MD010, no-hard-tabs)


85-85: Hard tabs
Column: 16

(MD010, no-hard-tabs)

🔇 Additional comments (17)
src/lib/SoftHSM.h (2)

55-58: Good: ML-DSA headers are correctly feature-gated.


378-393: Good: generateMLDSA() is declared under WITH_ML_DSA, matching the implementation.

src/lib/crypto/OSSLUtil.cpp (1)

223-234: Declaration exists and is correctly guarded.
Header src/lib/crypto/OSSLUtil.h already declares const char* mldsaParameterSet2Name(unsigned long) inside #ifdef WITH_ML_DSA (lines ~76–78).

m4/acx_crypto_backend.m4 (3)

31-39: ML-DSA configure flag: LGTM

Option wiring and help text look consistent with ECC/EDDSA.


257-269: WITH_ML_DSA define/conditional: LGTM

Consistent with existing feature toggles; no issues.


113-121: Resolve: ACX_OPENSSL_MLDSA uses capability-based detection

Confirmed — the macro performs a functional check calling EVP_PKEY_CTX_new_from_name("ML-DSA-44") in m4/acx_openssl_mldsa.m4; no version-gate change required.

src/lib/crypto/OSSLMLDSAPrivateKey.cpp (1)

150-160: Early guards on param-set/name and empty key: LGTM

Prevents undefined provider behavior on bad inputs.

src/lib/crypto/OSSLMLDSA.h (1)

32-39: Resolved — ML-DSA encrypt/decrypt/checkEncryptedDataSize are already stubbed

Implementations in src/lib/crypto/OSSLMLDSA.cpp (≈ lines 247–276) log "ML-DSA does not support encryption" and act as stubs — no change required.

src/lib/SoftHSM.cpp (9)

65-70: ML-DSA includes and guard look good

Headers are properly gated under WITH_ML_DSA.


149-152: Correct feature-gating for ML-DSA P11 objects

newP11Object branches are properly wrapped; avoids non-ML builds breaking.

Also applies to: 171-173


842-846: Mechanism registration: OK

CKM_ML_DSA and KEY_PAIR_GEN added as expected.


950-953: C_GetMechanismInfo wiring for ML-DSA: OK

Min/max are pulled from CryptoFactory and flags are set appropriately.

Also applies to: 1049-1061, 1353-1363


4204-4205: Sign init path for ML-DSA: OK

Using MLDSAUtil and relying on parameter-set inference is consistent with project preference.

Also applies to: 4473-4478, 4566-4584


5238-5239: Verify init path for ML-DSA: OK

Mirrors sign path; no explicit setParameterSet required.

Also applies to: 5505-5510, 5598-5616


6231-6234: Key-pair gen plumbing for ML-DSA: OK

Mechanism switch, template checks, and dispatch to generateMLDSA are sound.

Also applies to: 6262-6264, 6288-6289, 6362-6371


10099-10121: generateMLDSA: robust template parsing and validation

CKA_PARAMETER_SET length checked, CKP_ML_DSA_* constants used, and “unsupported” path logged consistently.

Also applies to: 10133-10136


1152-1152: Confirm CKM_GENERIC_SECRET_KEY_GEN max size change

ulMaxKeySize = 0x08000000 (134,217,728) — intentional? It differs from other “unbounded” caps used elsewhere.

@primetomas
Copy link

Very cool. Did some manual test.

I checked out this branch and tested with our p11ng-cli from EJBCA (Java code using Bouncy Castle). Generating keys, listing keys, signing (and verifying the signatures with Bouncy Castle code). I have PKCS#11v3.2 draft implemented and it worked out-of-the-box. Works well with all variants ML-DSA-44, ML-DSA-65 and ML-DSA-87.

@antoinelochet
Copy link
Contributor Author

Very cool. Did some manual test.

I checked out this branch and tested with our p11ng-cli from EJBCA (Java code using Bouncy Castle). Generating keys, listing keys, signing (and verifying the signatures with Bouncy Castle code). I have PKCS#11v3.2 draft implemented and it worked out-of-the-box. Works well with all variants ML-DSA-44, ML-DSA-65 and ML-DSA-87.

Thank you very much !
It works very well with our PKI too :)

@jschlyter jschlyter requested a review from bjosv October 4, 2025 08:53
@jschlyter
Copy link
Contributor

Nice work! I'll ask @bjosv to review before merge.

Copy link
Contributor

@jschlyter jschlyter left a comment

Choose a reason for hiding this comment

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

Please address and resolve all comments. If you believe the rabbit is out of line, do tell it.

@kingcdavid
Copy link

Not sure if it's helpful, but have an implementation of it working here:
https://github.com/kingcdavid/pkcs11-mldsa

@antoinelochet
Copy link
Contributor Author

Please address and resolve all comments. If you believe the rabbit is out of line, do tell it.

All resolved :)
I was not sure of what to do about this comment: #809 (comment) since I just copied what was done in other OSSL*PublicKey classes.
But upon application, all tests are still ok so I guess RabbitAI was right.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71b03bc and 1820021.

📒 Files selected for processing (4)
  • src/lib/crypto/OSSLMLDSAPublicKey.cpp (1 hunks)
  • src/lib/crypto/OSSLMLDSAPublicKey.h (1 hunks)
  • src/lib/crypto/OSSLUtil.cpp (2 hunks)
  • src/lib/object_store/DBObject.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/object_store/DBObject.cpp
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-19T09:23:25.434Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/crypto/OSSLUtil.cpp
  • src/lib/crypto/OSSLMLDSAPublicKey.h
  • src/lib/crypto/OSSLMLDSAPublicKey.cpp
📚 Learning: 2025-08-18T17:59:30.693Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/OSSLUtil.cpp
📚 Learning: 2025-08-18T19:46:46.659Z
Learnt from: antoinelochet
PR: softhsm/SoftHSMv2#809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/crypto/OSSLUtil.cpp
🧬 Code graph analysis (2)
src/lib/crypto/OSSLMLDSAPublicKey.h (3)
src/lib/crypto/OSSLMLDSAPublicKey.cpp (14)
  • OSSLMLDSAPublicKey (18-21)
  • OSSLMLDSAPublicKey (23-28)
  • OSSLMLDSAPublicKey (31-38)
  • OSSLMLDSAPublicKey (40-44)
  • isOfType (84-87)
  • isOfType (84-84)
  • setValue (89-97)
  • setValue (89-89)
  • setFromOSSL (65-81)
  • setFromOSSL (65-65)
  • getOSSLKey (100-105)
  • getOSSLKey (100-100)
  • createOSSLKey (108-146)
  • createOSSLKey (108-108)
src/lib/crypto/MLDSAPublicKey.h (1)
  • MLDSAPublicKey (14-45)
src/lib/crypto/MLDSAPublicKey.cpp (4)
  • isOfType (17-20)
  • isOfType (17-17)
  • setValue (60-63)
  • setValue (60-60)
src/lib/crypto/OSSLMLDSAPublicKey.cpp (4)
src/lib/crypto/OSSLMLDSAPublicKey.h (1)
  • OSSLMLDSAPublicKey (15-54)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (10)
  • setFromOSSL (44-69)
  • setFromOSSL (44-44)
  • setValue (77-85)
  • setValue (77-77)
  • isOfType (72-75)
  • isOfType (72-72)
  • getOSSLKey (136-141)
  • getOSSLKey (136-136)
  • createOSSLKey (144-192)
  • createOSSLKey (144-144)
src/lib/crypto/MLDSAPublicKey.cpp (8)
  • setValue (60-63)
  • setValue (60-60)
  • isOfType (17-20)
  • isOfType (17-17)
  • getValue (55-58)
  • getValue (55-55)
  • getParameterSet (28-39)
  • getParameterSet (28-28)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (226-235)
  • mldsaParameterSet2Name (226-226)

@primetomas
Copy link

primetomas commented Oct 26, 2025

I found an issue. It does not handle signing with a provided ML-DSA context. I.e. PKCS11v3.2 section 6.67.5.

It has an optional parameter CK_SIGN_ADDITIONAL_CONTEXT. If no parameter is supplied the hedgeVariant will be CKH_HEDGE_PREFERRED, ulContextLen will be zero and pContext will be NULL. On signing, if hedgeVariant is set to CKH_HEDGE_PREFERRED, the token may create either a hedged signature or a deterministic signature as specified in [FIPS 204]. If hedgeVariant is set to CKH_HEDGE_REQUIRED, the token must produce a hedged signature or fail. If the hedgeVariant is set to CKH_DETERMINISTIC_REQUIRED, the token must produce a deterministic signature or fail. On verification the hedgeVariant parameter is ignored.

Referring to section 6.67.1:

CK_SIGN_ADDITIONAL_CONTEXT is used in the mechanism parameters to supply a NIST defined context string in signature scheme.
typedef struct CK_SIGN_ADDITIONAL_CONTEXT {
  CK_HEDGE_TYPE  hedgeVariant;
  CK_BYTE_PTR    pContext;
  CK_ULONG       ulContextLen;
} CK_SIGN_ADDITIONAL_CONTEXT;
;

This is needed for Composite certificates as a domain separator is used as specified in composite draft 12 section 7.

@antoinelochet
Copy link
Contributor Author

I found an issue. It does not handle signing with a provided ML-DSA context. I.e. PKCS11v3.2 section 6.67.5.

It has an optional parameter CK_SIGN_ADDITIONAL_CONTEXT. If no parameter is supplied the hedgeVariant will be CKH_HEDGE_PREFERRED, ulContextLen will be zero and pContext will be NULL. On signing, if hedgeVariant is set to CKH_HEDGE_PREFERRED, the token may create either a hedged signature or a deterministic signature as specified in [FIPS 204]. If hedgeVariant is set to CKH_HEDGE_REQUIRED, the token must produce a hedged signature or fail. If the hedgeVariant is set to CKH_DETERMINISTIC_REQUIRED, the token must produce a deterministic signature or fail. On verification the hedgeVariant parameter is ignored.

Referring to section 6.67.1:

CK_SIGN_ADDITIONAL_CONTEXT is used in the mechanism parameters to supply a NIST defined context string in signature scheme.
typedef struct CK_SIGN_ADDITIONAL_CONTEXT {
  CK_HEDGE_TYPE  hedgeVariant;
  CK_BYTE_PTR    pContext;
  CK_ULONG       ulContextLen;
} CK_SIGN_ADDITIONAL_CONTEXT;
;

This is needed for Composite certificates as a domain separator is used as specified in composite draft 12 section 7.

I think there is (was?) an issue with OpenSSL. I will have to check.
Do you happen to have a test case ?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/SoftHSM.cpp (2)

6519-6571: Fix UB + missing IV handling in WrapKeySym() for CKM_AES_CBC / CKM_DES3_CBC.

blocksize is only set for *_CBC_PAD cases, but the CBC encrypt path unconditionally does:

  • iv.resize(blocksize);
  • memcpy(&iv[0], ..., blocksize);

If blocksize == 0 (e.g., CKM_AES_CBC or CKM_DES3_CBC), &iv[0] is undefined behavior, and the IV won’t be provided to the cipher.

Proposed fix
 	switch(pMechanism->mechanism) {
 #ifdef HAVE_AES_KEY_WRAP
 		case CKM_AES_KEY_WRAP:
 			wrappedlen = RFC3394Pad(keydata);
 			if ((wrappedlen < 16) || ((wrappedlen % 8) != 0))
 				return CKR_KEY_SIZE_RANGE;
 			algo = SymAlgo::AES;
 			mode = SymWrap::AES_KEYWRAP;
 			break;
 #endif
 #ifdef HAVE_AES_KEY_WRAP_PAD
 		case CKM_AES_KEY_WRAP_PAD:
 			algo = SymAlgo::AES;
 			mode = SymWrap::AES_KEYWRAP_PAD;
 			break;
 #endif
 		case CKM_AES_CBC:
 			algo = SymAlgo::AES;
+			blocksize = 16;
 			break;
 
 		case CKM_AES_CBC_PAD:
 			blocksize = 16;
 			wrappedlen = RFC5652Pad(keydata, blocksize);
 			algo = SymAlgo::AES;
 			break;
 
 		case CKM_DES3_CBC:
 			algo = SymAlgo::DES3;
+			blocksize = 8;
 			break;
 
 		case CKM_DES3_CBC_PAD:
 			blocksize = 8;
 			wrappedlen = RFC5652Pad(keydata, blocksize);
 			algo = SymAlgo::DES3;
 			break;
 
 		default:
 			return CKR_MECHANISM_INVALID;
 	}
@@
 	switch(pMechanism->mechanism) {
 
 		case CKM_AES_CBC:
 	        case CKM_AES_CBC_PAD:
 		case CKM_DES3_CBC:
 	        case CKM_DES3_CBC_PAD:
+			if (pMechanism->pParameter == NULL_PTR || pMechanism->ulParameterLen != blocksize)
+				return CKR_ARGUMENTS_BAD;
 			iv.resize(blocksize);
 			memcpy(&iv[0], pMechanism->pParameter, blocksize);
 
 			if (!cipher->encryptInit(wrappingkey, SymMode::CBC, iv, false))

Also, if CKM_DES3_CBC(_PAD) wrapping is intended to be supported, C_WrapKey’s mechanism allowlist should include it (currently only AES_CBC(_PAD) is accepted).


949-1059: Add comment clarifying ML-DSA key sizes are in bytes, not bits.

The implementation correctly returns ML-DSA key sizes in bytes (2560, 4032, 4896 for ML-DSA-44/65/87) as required by PKCS#11 v3.2 §6.67.4, as documented in MLDSAParameters.h. However, lines 949–1059 in SoftHSM.cpp lack an explicit comment distinguishing this from other algorithms (RSA, DSA, ECDSA, ECDH, EdDSA) which use bits. Add a brief comment above the ML-DSA section to document that these values are bytes per PKCS#11 v3.2, preventing accidental regressions.

This also applies to lines 1351–1361.

🤖 Fix all issues with AI agents
In @src/lib/crypto/MLDSAUtil.cpp:
- Around line 173-188: The code allocates a single contiguous blob into mem but
then separately mallocs sac->contextData and sets ownsContextMemory=false,
leaking the second allocation; instead, set sac->contextData to point into the
contiguous blob (offset after the SIGN_ADDITIONAL_CONTEXT structure) and copy
contextAsChar there, remove the extra malloc so ownsContextMemory remains false
and only the outer mem (sac) must be freed; update the allocation to use total =
sizeof(SIGN_ADDITIONAL_CONTEXT) + contextLength, assign sac =
reinterpret_cast<SIGN_ADDITIONAL_CONTEXT*>(mem), set sac->contextData = mem +
sizeof(SIGN_ADDITIONAL_CONTEXT) when contextLength>0, then memcpy from
contextAsChar into sac->contextData and do not call malloc for contextData.

In @src/lib/SoftHSM.cpp:
- Around line 5252-5258: In AsymVerifyInit, add a strict validation for the
CKM_ML_DSA branch to ensure pMechanism->pParameter and
pMechanism->ulParameterLen are consistent: require (pMechanism->pParameter ==
NULL_PTR) if and only if (pMechanism->ulParameterLen == 0); if this invariant
fails, return CKR_ARGUMENTS_BAD. Locate the CKM_ML_DSA handling in
AsymVerifyInit and perform this check before any use of the parameter (and
before constructing or calling MLDSAAdditionalContextGuard/setParameters()),
keeping the existing path for non-NULL parameters unchanged.
🧹 Nitpick comments (5)
src/lib/crypto/AsymmetricAlgorithm.h (1)

133-162: Conditional allocation in constructor silently degrades on OOM.

When std::malloc fails in the convenience constructor (Line 148-150), contextLen is set to 0 and ownsContextMemory to false, silently discarding the requested context. The caller has no way to detect this failure.

Consider throwing std::bad_alloc or providing a factory method that returns an error code, consistent with how copyMLDSAContext returns CKR_HOST_MEMORY.

src/lib/crypto/MLDSAUtil.cpp (1)

141-156: Consider validating ulContextLen == 0 with non-null pContext.

The code only validates that pContext != NULL when ulContextLen > 0. While not strictly an error, it may be worth logging a debug message if a caller passes pContext != NULL with ulContextLen == 0, as this could indicate a caller bug.

src/lib/SoftHSM.cpp (2)

5770-5774: Extra debug on signature length mismatch is OK, but ensure it’s not too noisy in typical failure cases.

Logging size and ulSignatureLen behind DEBUG_MSG is generally fine; just worth confirming it doesn’t spam logs in common negative-test flows.

Also applies to: 5954-5958


10116-10374: generateMLDSA(): avoid using paramSet==0 as “missing” sentinel; track presence explicitly.

Relying on paramSet == 0 to mean “not provided” is brittle (even if today’s constants are non-zero). Prefer a bool hasParamSet gate and keep paramSet purely as a value.

Proposed refactor
-	CK_ULONG paramSet = 0;
+	CK_ULONG paramSet = 0;
+	bool hasParamSet = false;
 	for (CK_ULONG i = 0; i < ulPublicKeyAttributeCount; i++)
 	{
 		switch (pPublicKeyTemplate[i].type)
 		{
 			case CKA_PARAMETER_SET:
 				if (pPublicKeyTemplate[i].ulValueLen != sizeof(CK_ULONG)) {
                     INFO_MSG("CKA_PARAMETER_SET must be sizeof(CK_ULONG)");
                     return CKR_ATTRIBUTE_VALUE_INVALID;
                 }
                 paramSet = *(CK_ULONG*)pPublicKeyTemplate[i].pValue;
+				hasParamSet = true;
 				break;
 			default:
 				break;
 		}
 	}
 
 	// The parameters must be specified to be able to generate a key pair.
-	if (paramSet == 0) {
+	if (!hasParamSet) {
 		INFO_MSG("Missing parameter(s) CKA_PARAMETER_SET in pPublicKeyTemplate");
 		return CKR_TEMPLATE_INCOMPLETE;
 	}

Based on learnings, using CKP_ML_DSA_* constants is preferred (and you already do).

src/lib/crypto/OSSLMLDSA.cpp (1)

83-83: Consider removing or guarding sensitive context logging.

This INFO_MSG logs the ML-DSA context bytes (which may include domain separators for composite certificates). While INFO-level logs are typically debug-only, logging raw context data could expose application-specific sensitive information in debug scenarios.

Consider removing this log or using a lower log level (DEBUG_MSG) if the project has one.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8e987f and cdac7b0.

📒 Files selected for processing (6)
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAUtil.h
  • src/lib/crypto/OSSLMLDSA.cpp
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:373-381
Timestamp: 2026-01-11T16:15:14.696Z
Learning: In PKCS#11 v3.2, ML-DSA key sizes (returned by getMinKeySize() and getMaxKeySize()) are specified in bytes, not bits. This is different from other algorithms like RSA, DSA, ECDSA, ECDH, and EdDSA which use bits. This is defined in chapter 6.67.4 of the PKCS#11 3.2 specification.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.
📚 Learning: 2025-10-27T16:44:57.865Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T17:59:30.693Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-19T09:23:25.434Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-10-13T09:42:31.270Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 818
File: src/lib/SoftHSM.cpp:3238-3266
Timestamp: 2025-10-13T09:42:31.270Z
Learning: In src/lib/SoftHSM.cpp, SoftHSM::AsymDecryptInit should use MechParamCheckRSAPKCSOAEP for CKM_RSA_PKCS_OAEP to keep OAEP parameter validation identical to AsymEncryptInit, and avoid redundant manual checks for pMechanism->pParameter and MGF allowlists.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-11-11T19:42:46.886Z
Learnt from: JoaoHenrique12
Repo: softhsm/SoftHSMv2 PR: 823
File: src/lib/SoftHSM.cpp:58-58
Timestamp: 2025-11-11T19:42:46.886Z
Learning: SoftHSM include policy: Algorithm key/parameter headers (e.g., SLHParameters, SLH*Key) are included unconditionally in SoftHSM.cpp/SoftHSM.h; feature gating is handled in mechanism tables and CryptoFactory (WITH_*). Avoid wrapping such includes in WITH_* guards.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2026-01-11T16:15:14.696Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:373-381
Timestamp: 2026-01-11T16:15:14.696Z
Learning: In PKCS#11 v3.2, ML-DSA key sizes (returned by getMinKeySize() and getMaxKeySize()) are specified in bytes, not bits. This is different from other algorithms like RSA, DSA, ECDSA, ECDH, and EdDSA which use bits. This is defined in chapter 6.67.4 of the PKCS#11 3.2 specification.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T19:46:46.659Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-09-11T16:54:00.370Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.

Applied to files:

  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-11T16:42:23.012Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 806
File: src/lib/test/p11test.cpp:81-84
Timestamp: 2025-08-11T16:42:23.012Z
Learning: In SoftHSM test files (like p11test.cpp and cryptotest.cpp), error handling for OSSL_PROVIDER_load() calls for "legacy" and "default" providers is intentionally omitted because these providers won't fail in practice and the test code is kept deliberately simple.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2026-01-10T15:07:10.701Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 837
File: src/lib/common/log.cpp:192-194
Timestamp: 2026-01-10T15:07:10.701Z
Learning: In SoftHSM, setLogFile and closeLogFile are designed to be called only during C_Initialize and C_Finalize respectively. Per PKCS#11 spec, C_Finalize behavior is undefined if called while other threads are making Cryptoki calls, so concurrent access between closeLogFile and softHSMLog would be API misuse. The implementation relies on these PKCS#11 threading guarantees rather than defensive locking in open/close operations.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2025-08-18T19:35:33.938Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:195-204
Timestamp: 2025-08-18T19:35:33.938Z
Learning: In OpenSSL, EVP_PKEY_verify_message does not exist. EVP_PKEY_verify_message_init is designed for streaming verification and should be paired with EVP_PKEY_CTX_set_signature, EVP_PKEY_verify_message_update, and EVP_PKEY_verify_message_final - not with EVP_PKEY_verify.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
📚 Learning: 2025-08-18T17:54:37.645Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
🧬 Code graph analysis (6)
src/lib/crypto/AsymmetricAlgorithm.h (1)
src/lib/test/TestsNoPINInitBase.cpp (1)
  • ptr (60-60)
src/lib/crypto/MLDSAUtil.cpp (1)
src/lib/SoftHSM.cpp (2)
  • i (395-416)
  • i (395-395)
src/lib/crypto/MLDSAPrivateKey.cpp (3)
src/lib/crypto/MLDSAPublicKey.cpp (12)
  • isOfType (17-20)
  • isOfType (17-17)
  • getParameterSet (28-39)
  • getParameterSet (28-28)
  • getOutputLength (42-53)
  • getOutputLength (42-42)
  • setValue (60-63)
  • setValue (60-60)
  • serialise (66-69)
  • serialise (66-66)
  • deserialise (71-83)
  • deserialise (71-71)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (6)
  • isOfType (90-93)
  • isOfType (90-90)
  • setValue (95-103)
  • setValue (95-95)
  • setSeed (105-113)
  • setSeed (105-105)
src/lib/crypto/MLDSAParameters.cpp (6)
  • getParameterSet (22-25)
  • getParameterSet (22-22)
  • serialise (34-37)
  • serialise (34-34)
  • deserialise (39-50)
  • deserialise (39-39)
src/lib/SoftHSM.cpp (1)
src/lib/crypto/MLDSAUtil.cpp (8)
  • copyMLDSAContext (130-191)
  • copyMLDSAContext (130-130)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
src/lib/crypto/OSSLMLDSA.cpp (1)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (226-235)
  • mldsaParameterSet2Name (226-226)
src/lib/crypto/MLDSAUtil.h (1)
src/lib/crypto/MLDSAUtil.cpp (8)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
  • copyMLDSAContext (130-191)
  • copyMLDSAContext (130-130)
🪛 Clang (14.0.6)
src/lib/crypto/AsymmetricAlgorithm.h

[error] 36-36: 'cstdlib' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAPrivateKey.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/OSSLMLDSA.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.h

[error] 10-10: 'config.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Linux with OpenSSL 1.1.1
  • GitHub Check: Windows (x64, botan)
  • GitHub Check: Linux with OpenSSL 3.5.4
  • GitHub Check: macOS (botan)
  • GitHub Check: Windows (x86, openssl)
  • GitHub Check: macOS (openssl)
  • GitHub Check: Windows (x64, openssl)
  • GitHub Check: Linux with OpenSSL 3.0
  • GitHub Check: Linux with Botan
🔇 Additional comments (27)
src/lib/crypto/AsymmetricAlgorithm.h (3)

36-37: LGTM!

Standard C library headers included for malloc/free/memcpy used by the new memory-managed context structures.


58-59: LGTM!

MLDSA type additions to AsymAlgo and AsymMech enums correctly extend the existing algorithm enumeration pattern for ML-DSA support.

Also applies to: 98-99


123-131: LGTM!

The Hedge enum correctly maps to the PKCS#11 v3.2 CK_HEDGE_TYPE variants (CKH_HEDGE_PREFERRED, CKH_HEDGE_REQUIRED, CKH_DETERMINISTIC_REQUIRED).

src/lib/crypto/MLDSAUtil.h (1)

1-32: LGTM!

Well-structured utility class declaration with proper include guards and WITH_ML_DSA feature gating. The static methods provide a clean API for ML-DSA key material extraction and context copying, consistent with similar utility patterns in the codebase (e.g., EdDSA utilities).

src/lib/crypto/MLDSAUtil.cpp (3)

11-41: LGTM!

Proper null-pointer validation, conditional decryption based on CKA_PRIVATE attribute, and consistent error handling pattern matching other key utility implementations.


43-69: LGTM!

Implementation mirrors getMLDSAPrivateKey appropriately for public key retrieval with proper token decryption handling.


71-128: LGTM!

Proper resource cleanup on all error paths via recyclePrivateKey and recycleAsymmetricAlgorithm. The PKCS#8 decode, encryption, and attribute setting flow is consistent with other key utility patterns.

src/lib/crypto/MLDSAPrivateKey.cpp (4)

13-24: LGTM!

Standard type identification pattern consistent with other key classes in the codebase.


26-57: LGTM!

Parameter set and output length inference from key material size follows the established design pattern. Based on learnings, this inference approach (getParameterSet() mapping key length to CKP_ML_DSA_44/65/87) is intentional, and runtime keys rely on this without explicit parameter-set assignment.


59-77: LGTM!

Standard accessor/mutator implementations for value and seed members.


79-100: LGTM!

Serialization concatenates seed then value; deserialization correctly uses chainDeserialise in the same order and validates both fields are non-empty before assignment. This mirrors the pattern used in MLDSAPublicKey.

src/lib/SoftHSM.cpp (7)

65-68: ML-DSA includes look consistent with the project’s include policy.

Not wrapping algorithm headers in WITH_* guards matches prior guidance (avoids include-level feature gating). Based on learnings, keep an eye on ensuring these headers are always present in non-ML-DSA builds (so non-WITH_ML_DSA builds still compile cleanly).


148-172: Correct object instantiation for CKK_ML_DSA under WITH_ML_DSA.

The newP11Object(...) routing for P11MLDSAPublicKeyObj / P11MLDSAPrivateKeyObj is straightforward and properly gated.


840-845: Mechanism exposure for CKM_ML_DSA is OK; exception handling change is fine.*

Adding CKM_ML_DSA_KEY_PAIR_GEN and CKM_ML_DSA to the supported-mechanism map under WITH_ML_DSA looks correct; the std::out_of_range catch style change is also fine.

Also applies to: 886-889


1150-1152: Generic secret key max size change should be kept consistent with generation limits.

0x8000000 matches the generation-side upper bound later in generateGeneric(), so the mechanism-info change is coherent.


6265-6269: ML-DSA keypair-gen routing is correctly wired into C_GenerateKeyPair.

Mechanism dispatch to generateMLDSA(...) and the template consistency checks look consistent with the rest of the function’s pattern.

Also applies to: 6297-6299, 6322-6324, 6397-6406


7118-7127: UnwrapKeySym(): CBC_PAD IV handling looks consistent, but keep parameter checks aligned with WrapKeySym().

The blocksize is set before IV extraction in the *_CBC_PAD cases, so the memcpy(&iv[0], ...) usage is safe here assuming pMechanism->ulParameterLen is enforced upstream (it is in C_UnwrapKey).

Also applies to: 7153-7160


6994-6998: Wrap/unwrap support for CKK_ML_DSA keys looks consistent.

Routing CKK_ML_DSA to AsymAlgo::MLDSA and using MLDSAUtil::{getMLDSAPrivateKey,setMLDSAPrivateKey} matches the existing patterns for other asymmetric types.

Also applies to: 7039-7042, 7720-7724

src/lib/crypto/OSSLMLDSA.cpp (9)

1-24: LGTM!

Header structure, includes, and static constant definitions look correct. The config.h include is standard for autotools projects (generated at build time).


176-311: LGTM!

The verification implementation is well-structured with proper validation of signature length before verification and correct handling of OpenSSL's verification return values (distinguishing failure from error). Resource cleanup is handled correctly on all paths.


154-174: LGTM!

Multi-part signing stubs correctly reject operations per PKCS#11 3.2 specification (ML-DSA is single-part only).


335-351: LGTM!

Correctly rejects encryption/decryption operations since ML-DSA is a signature-only algorithm.


353-368: LGTM!

Key size functions correctly return ML-DSA private key lengths in bytes, which aligns with PKCS#11 v3.2 §6.67.4 specification (ML-DSA uses bytes unlike RSA/DSA/ECDSA which use bits). Based on learnings, this is the expected behavior.


433-469: LGTM!

Key pair reconstruction correctly uses chain deserialization and properly cleans up the allocated key pair on failure.


471-515: LGTM!

Public and private key reconstruction functions follow the standard project pattern with proper cleanup on deserialization failure.


517-552: LGTM!

Factory methods follow the standard project pattern for creating new key/parameter objects and reconstructing from serialized data.


418-428: No issues found. The code correctly handles the pkey reference. Both setFromOSSL methods extract key material bytes using EVP_PKEY_get_octet_string_param and store only the extracted data in ByteString members—they do not retain a reference to the input EVP_PKEY pointer. Each key object manages its own separate EVP_PKEY instance created on-demand via createOSSLKey(). Freeing the temporary pkey at line 428 is safe and correct.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/SoftHSM.cpp (1)

6533-6602: Fix CBC wrapping/unwrapping: blocksize/DES3 bitlen are wrong for some paths (can break CKM_AES_CBC / CKM_DES3_CBC).*
WrapKeySym() uses iv.resize(blocksize) / memcpy(..., blocksize) for CBC modes, but blocksize is never set for CKM_AES_CBC and CKM_DES3_CBC. Also, DES3 should likely use bb = 7 (consistent with other DES(3) codepaths) before setBitLen().

Proposed fix (ensure blocksize + DES3 bb are set, and enforce alignment for non-PAD CBC)
--- a/src/lib/SoftHSM.cpp
+++ b/src/lib/SoftHSM.cpp
@@
 	// Get the symmetric algorithm matching the mechanism
 	SymAlgo::Type algo = SymAlgo::Unknown;
 	SymWrap::Type mode = SymWrap::Unknown;
 	size_t bb = 8;
 	size_t blocksize = 0;
 	auto wrappedlen = keydata.size();
@@
 		case CKM_AES_CBC:
+			blocksize = 16;
 			algo = SymAlgo::AES;
+			if ((wrappedlen % blocksize) != 0)
+				return CKR_KEY_SIZE_RANGE;
 			break;
 
 		case CKM_AES_CBC_PAD:
 			blocksize = 16;
 			wrappedlen = RFC5652Pad(keydata, blocksize);
 			algo = SymAlgo::AES;
 			break;
@@
 		case CKM_DES3_CBC:
+			blocksize = 8;
+			bb = 7;
 			algo = SymAlgo::DES3;
+			if ((wrappedlen % blocksize) != 0)
+				return CKR_KEY_SIZE_RANGE;
 			break;
 
 		case CKM_DES3_CBC_PAD:
 			blocksize = 8;
+			bb = 7;
 			wrappedlen = RFC5652Pad(keydata, blocksize);
 			algo = SymAlgo::DES3;
 			break;
@@
 	// Internal: Unwrap blob using symmetric key
@@
 	// Get the symmetric algorithm matching the mechanism
 	SymAlgo::Type algo = SymAlgo::Unknown;
 	SymWrap::Type mode = SymWrap::Unknown;
 	size_t bb = 8;
 	size_t blocksize = 0;
@@
 	        case CKM_DES3_CBC_PAD:
 			algo = SymAlgo::DES3;
 			blocksize = 8;
+			bb = 7;
 		        break;

Also applies to: 7134-7201

🧹 Nitpick comments (4)
src/lib/crypto/AsymmetricAlgorithm.h (1)

144-152: Silent allocation failure in convenience constructor.

When std::malloc fails (returns NULL), the constructor silently sets contextLen = 0 and ownsContextMemory = false. This is a valid fallback, but callers have no way to detect the allocation failure. Consider whether signaling this failure is important for unit test scenarios where this constructor is used.

src/lib/crypto/OSSLMLDSA.cpp (1)

236-236: Remove debug INFO_MSG.

This INFO_MSG("contextSize=%d", contextSize) appears to be leftover debug logging. Consider removing it before merging.

Proposed fix
		if (contextSize > 0) {
-			INFO_MSG("contextSize=%d", contextSize);
			if (contextSize > 255) {
src/lib/SoftHSM.cpp (2)

949-951: Add a clarifying comment: ML‑DSA min/max sizes are bytes (PKCS#11 v3.2).
Since most other mechanisms treat ulMinKeySize/ulMaxKeySize as “bits”, a short comment here would prevent future accidental “*8” conversions.

Also applies to: 1051-1059, 1351-1361


10132-10187: ML‑DSA keygen path looks solid; consider checking conflicting CKA_PARAMETER_SET in templates.
You correctly enforce CKP_ML_DSA_{44,65,87} and persist CKA_PARAMETER_SET/CKA_VALUE(/CKA_SEED) transactionally. Optional hardening: if CKA_PARAMETER_SET is present in the private template too, reject mismatches early (instead of silently overriding during persistence).

Also applies to: 10189-10206, 10257-10269, 10334-10351

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdac7b0 and 07d0768.

📒 Files selected for processing (7)
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAUtil.h
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/session_mgr/Session.cpp
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:373-381
Timestamp: 2026-01-11T16:15:14.696Z
Learning: In PKCS#11 v3.2, ML-DSA key sizes (returned by getMinKeySize() and getMaxKeySize()) are specified in bytes, not bits. This is different from other algorithms like RSA, DSA, ECDSA, ECDH, and EdDSA which use bits. This is defined in chapter 6.67.4 of the PKCS#11 3.2 specification.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.
📚 Learning: 2025-08-18T17:59:30.693Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/session_mgr/Session.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
📚 Learning: 2025-08-19T09:23:25.434Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/session_mgr/Session.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
📚 Learning: 2025-10-27T16:44:57.865Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/session_mgr/Session.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
📚 Learning: 2025-08-18T19:46:46.659Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/session_mgr/Session.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
📚 Learning: 2026-01-11T16:15:14.696Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:373-381
Timestamp: 2026-01-11T16:15:14.696Z
Learning: In PKCS#11 v3.2, ML-DSA key sizes (returned by getMinKeySize() and getMaxKeySize()) are specified in bytes, not bits. This is different from other algorithms like RSA, DSA, ECDSA, ECDH, and EdDSA which use bits. This is defined in chapter 6.67.4 of the PKCS#11 3.2 specification.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/session_mgr/Session.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
📚 Learning: 2025-11-11T19:42:46.886Z
Learnt from: JoaoHenrique12
Repo: softhsm/SoftHSMv2 PR: 823
File: src/lib/SoftHSM.cpp:58-58
Timestamp: 2025-11-11T19:42:46.886Z
Learning: SoftHSM include policy: Algorithm key/parameter headers (e.g., SLHParameters, SLH*Key) are included unconditionally in SoftHSM.cpp/SoftHSM.h; feature gating is handled in mechanism tables and CryptoFactory (WITH_*). Avoid wrapping such includes in WITH_* guards.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
📚 Learning: 2025-10-13T09:42:31.270Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 818
File: src/lib/SoftHSM.cpp:3238-3266
Timestamp: 2025-10-13T09:42:31.270Z
Learning: In src/lib/SoftHSM.cpp, SoftHSM::AsymDecryptInit should use MechParamCheckRSAPKCSOAEP for CKM_RSA_PKCS_OAEP to keep OAEP parameter validation identical to AsymEncryptInit, and avoid redundant manual checks for pMechanism->pParameter and MGF allowlists.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/session_mgr/Session.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
📚 Learning: 2025-09-11T16:54:00.370Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.

Applied to files:

  • src/lib/crypto/MLDSAUtil.h
  • src/lib/session_mgr/Session.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
📚 Learning: 2025-08-11T16:42:23.012Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 806
File: src/lib/test/p11test.cpp:81-84
Timestamp: 2025-08-11T16:42:23.012Z
Learning: In SoftHSM test files (like p11test.cpp and cryptotest.cpp), error handling for OSSL_PROVIDER_load() calls for "legacy" and "default" providers is intentionally omitted because these providers won't fail in practice and the test code is kept deliberately simple.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2026-01-10T15:07:10.701Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 837
File: src/lib/common/log.cpp:192-194
Timestamp: 2026-01-10T15:07:10.701Z
Learning: In SoftHSM, setLogFile and closeLogFile are designed to be called only during C_Initialize and C_Finalize respectively. Per PKCS#11 spec, C_Finalize behavior is undefined if called while other threads are making Cryptoki calls, so concurrent access between closeLogFile and softHSMLog would be API misuse. The implementation relies on these PKCS#11 threading guarantees rather than defensive locking in open/close operations.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2025-08-18T19:35:33.938Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:195-204
Timestamp: 2025-08-18T19:35:33.938Z
Learning: In OpenSSL, EVP_PKEY_verify_message does not exist. EVP_PKEY_verify_message_init is designed for streaming verification and should be paired with EVP_PKEY_CTX_set_signature, EVP_PKEY_verify_message_update, and EVP_PKEY_verify_message_final - not with EVP_PKEY_verify.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
📚 Learning: 2025-08-18T17:54:37.645Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
🧬 Code graph analysis (4)
src/lib/crypto/MLDSAUtil.h (1)
src/lib/crypto/MLDSAUtil.cpp (8)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
  • copyMLDSAContext (130-191)
  • copyMLDSAContext (130-130)
src/lib/SoftHSM.cpp (1)
src/lib/crypto/MLDSAUtil.cpp (8)
  • copyMLDSAContext (130-191)
  • copyMLDSAContext (130-130)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
src/lib/crypto/OSSLMLDSA.cpp (1)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (226-235)
  • mldsaParameterSet2Name (226-226)
src/lib/crypto/AsymmetricAlgorithm.h (1)
src/lib/test/TestsNoPINInitBase.cpp (1)
  • ptr (60-60)
🪛 Clang (14.0.6)
src/lib/crypto/MLDSAUtil.h

[error] 10-10: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAPrivateKey.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/OSSLMLDSA.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/AsymmetricAlgorithm.h

[error] 36-36: 'cstdlib' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Windows (x64, openssl)
  • GitHub Check: macOS (openssl)
  • GitHub Check: Linux with OpenSSL 1.1.1
  • GitHub Check: Windows (x64, botan)
  • GitHub Check: macOS (botan)
  • GitHub Check: Linux with Botan
  • GitHub Check: Windows (x86, openssl)
  • GitHub Check: Linux with OpenSSL 3.5.4
  • GitHub Check: Linux with OpenSSL 3.0
🔇 Additional comments (26)
src/lib/session_mgr/Session.cpp (1)

360-371: LGTM! The pointer fixup logic is correct.

The deep-copy approach with pointer adjustment follows the agreed design. The calculation reinterpret_cast<unsigned char*>(sac + 1) correctly points to the context bytes immediately following the struct in the newly copied buffer. After memcpy, the contextData pointer becomes stale and requires adjustment to point into the new allocation—the fixup properly handles this.

The condition inParamLen > sizeof(SIGN_ADDITIONAL_CONTEXT) ensures context bytes exist, and the inner check guards against fixing up when there's no actual context data. The invariant that contextLen <= inParamLen - sizeof(SIGN_ADDITIONAL_CONTEXT) is guaranteed by the caller (SoftHSM.cpp), which constructs the parameter as sizeof(struct) + contextLen and validates contextLen <= 255, so a defensive check is not required.

src/lib/crypto/AsymmetricAlgorithm.h (5)

36-37: LGTM on includes.

The <cstdlib> and <cstring> includes are correctly added to support std::malloc, std::free, and std::memcpy used in the new structs below. The static analysis hint about cstdlib not found is a false positive—this is a standard C++ header that will be available during actual compilation.


58-59: LGTM on enum additions.

The MLDSA entries are correctly added to both AsymAlgo::Type and AsymMech::Type enums, following the existing pattern for other algorithms.

Also applies to: 98-99


123-131: LGTM on Hedge struct.

The Hedge::Type enum correctly models the three hedge variants from PKCS#11 v3.2 (CKH_HEDGE_PREFERRED, CKH_HEDGE_REQUIRED, CKH_DETERMINISTIC_REQUIRED).


157-162: LGTM on deleted copy/move semantics.

Properly prevents accidental double-free by deleting copy and move constructors/assignments.


164-175: Guard destructor correctly handles placement-new'd objects.

The explicit destructor call followed by std::free is necessary because copyMLDSAContext uses reinterpret_cast on malloc'd memory rather than new. Since ownsContextMemory is false for these cases, the ~SIGN_ADDITIONAL_CONTEXT() destructor won't double-free the context data.

src/lib/crypto/MLDSAPrivateKey.cpp (4)

17-24: LGTM on isOfType implementation.

Correctly handles NULL input and uses strcmp consistently with the pattern in MLDSAPublicKey::isOfType.


32-43: LGTM on parameter set and output length inference.

The switch statements correctly use ML_DSA_*_PRIV_LENGTH constants to infer parameter sets and signature lengths from private key material size. This aligns with the project's design where parameter sets are inferred from key length rather than explicitly stored. Based on learnings, this is the intended approach.

Also applies to: 46-57


80-84: LGTM on serialization.

Serialization correctly concatenates seed.serialise() followed by value.serialise(), matching the expected deserialization order.


86-100: LGTM on deserialization.

The chainDeserialise pattern correctly reads seed then value in order, and properly validates that both components are non-empty before accepting the key.

src/lib/crypto/OSSLMLDSA.cpp (5)

27-151: LGTM on sign() implementation.

The signing flow correctly:

  • Validates mechanism and key types
  • Handles SIGN_ADDITIONAL_CONTEXT with proper bounds checking (context length ≤ 255)
  • Constructs OSSL_PARAM array for context string and deterministic mode
  • Uses EVP_PKEY_sign_message_init + EVP_PKEY_sign workflow
  • Properly frees EVP_SIGNATURE and EVP_PKEY_CTX on all paths

153-173: LGTM on multi-part operation stubs.

Correctly reject multi-part signing/verification with appropriate error messages. Per PKCS#11 v3.2 and PR comments, ML-DSA is single-part only.

Also applies to: 311-331


351-359: LGTM on key size methods.

Returning private key lengths in bytes aligns with PKCS#11 v3.2 §6.67.4 which specifies ML-DSA key sizes in bytes (unlike RSA/DSA/EC which use bits). Based on learnings, this is the correct approach.


431-467: LGTM on reconstructKeyPair.

Correctly deserializes public and private keys from serialized data, with proper cleanup on failure.


415-428: No reference counting issue detected.

The setFromOSSL methods in both OSSLMLDSAPrivateKey and OSSLMLDSAPublicKey extract key material using EVP_PKEY_get_octet_string_param and store the data as ByteString values; they do not store or increment references to the EVP_PKEY pointer. Therefore, calling EVP_PKEY_free(pkey) after both setFromOSSL calls is correct and required.

src/lib/crypto/MLDSAUtil.h (1)

20-29: LGTM on MLDSAUtil class declaration.

Clean utility class with static methods for:

  • Key material extraction (getMLDSAPrivateKey, getMLDSAPublicKey)
  • Key attribute setting (setMLDSAPrivateKey)
  • Context copying (copyMLDSAContext)

The API is consistent with similar utility classes in the codebase.

src/lib/crypto/MLDSAUtil.cpp (4)

11-41: LGTM on getMLDSAPrivateKey.

Correctly:

  • Validates all input pointers
  • Checks CKA_PRIVATE to determine if decryption is needed
  • Reads both CKA_VALUE and CKA_SEED attributes
  • Returns appropriate error codes (CKR_ARGUMENTS_BAD, CKR_GENERAL_ERROR)

43-69: LGTM on getMLDSAPublicKey.

Follows the same pattern as getMLDSAPrivateKey, appropriately handling only CKA_VALUE for public keys.


71-128: LGTM on setMLDSAPrivateKey.

The implementation correctly:

  • Acquires and releases the asymmetric algorithm via CryptoFactory
  • Decodes PKCS#8 data using PKCS8Decode
  • Encrypts seed and value when isPrivate is true
  • Sets all three attributes (CKA_PARAMETER_SET, CKA_SEED, CKA_VALUE)
  • Cleans up resources on all exit paths

130-191: LGTM on copyMLDSAContext.

The implementation correctly:

  • Validates inputs including context length bounds (≤ 255)
  • Maps CKH_* hedge variants to internal Hedge::Type
  • Allocates a single contiguous blob for struct + context bytes
  • Sets ownsContextMemory = false since the entire blob is freed together

Per learnings, this design avoids transient malloc in SoftHSM and lets Session::resetOp free the entire m_pParameters blob.

src/lib/SoftHSM.cpp (6)

65-69: Unconditional ML‑DSA includes match project include policy.
Good to see these not gated by WITH_* macros; feature gating should happen in mechanism tables / CryptoFactory. As per coding guidelines, keep it this way.


148-172: ML‑DSA P11 object instantiation is correctly gated.
P11MLDSAPublicKeyObj / P11MLDSAPrivateKeyObj wiring under #ifdef WITH_ML_DSA looks consistent with other key types.


841-844: Mechanism exposure for CKM_ML_DSA(_KEY_PAIR_GEN) looks correct.
Matches the expected pattern in prepareSupportedMechanisms().


5787-5788: Extra debug logging on signature-length mismatch is useful and low risk.
This should help diagnose interop issues without changing functional behavior.

Also applies to: 5971-5972


6281-6285: CKM_ML_DSA_KEY_PAIR_GEN routing into generateMLDSA is consistent.
Template consistency checks + gated dispatch look aligned with existing RSA/EC/EDDSA patterns.

Also applies to: 6313-6315, 6338-6340, 6413-6422


4200-4205: The implementation is correct and does not require changes. Session::setParameters() properly deep-copies the transient SIGN_ADDITIONAL_CONTEXT blob (lines 355–358 in Session.cpp), includes the appended context bytes in the copy, and reconstructs the internal pointer after the copy (line 368). The copied buffer is owned by Session and freed by Session::resetOp(). The MLDSAAdditionalContextGuard RAII object is safely destroyed at the end of the function scope after passing ownership to Session via setParameters(). The inline comment is slightly imprecise but not incorrect—it conveys that the guard becomes irrelevant after setParameters() transfers ownership to Session.

@antoinelochet antoinelochet force-pushed the ml-dsa-pr branch 4 times, most recently from 107776b to 0009185 Compare January 12, 2026 12:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/SoftHSM.cpp (1)

6537-6614: Fix blocksize initialization and DES3 mechanism support in wrap/unwrap functions.

Three correctness bugs exist:

  1. Blocksize uninitialized for CBC variants: CKM_AES_CBC and CKM_DES3_CBC cases in WrapKeySym do not set blocksize, leaving it at 0. Later code uses this to size and copy the IV, resulting in a zero-length IV.

  2. DES3 key-type check always fails: Both C_WrapKey (line ~6894) and C_UnwrapKey (line ~7536) use (keytype != CKK_DES2 || keytype != CKK_DES3) with OR operator. This always evaluates to true for any keytype, unconditionally rejecting all DES3 wrapping operations.

  3. Mechanism whitelist missing DES3 support: C_WrapKey and C_UnwrapKey only whitelist CKM_AES_CBC(_PAD), not CKM_DES3_CBC(_PAD). Yet WrapKeySym supports all four mechanisms, creating dead/unreachable code.

🛠️ Proposed fix
--- a/src/lib/SoftHSM.cpp
+++ b/src/lib/SoftHSM.cpp
@@ -6539,6 @@ CK_RV SoftHSM::WrapKeySym
 		case CKM_AES_CBC:
+			blocksize = 16;
 			algo = SymAlgo::AES;
 			break;
 
@@ -6549,6 +6550,7 @@ CK_RV SoftHSM::WrapKeySym
 		case CKM_DES3_CBC:
+			blocksize = 8;
 			algo = SymAlgo::DES3;
 			break;
 
@@ -6583,6 +6585,12 @@ CK_RV SoftHSM::WrapKeySym
 	        case CKM_AES_CBC_PAD:
 		case CKM_DES3_CBC:
 	        case CKM_DES3_CBC_PAD:
+			// Defensive check: blocksize must be set for CBC variants.
+			if (blocksize == 0) {
+				cipher->recycleKey(wrappingkey);
+				CryptoFactory::i()->recycleSymmetricAlgorithm(cipher);
+				return CKR_MECHANISM_INVALID;
+			}
 			iv.resize(blocksize);
 			memcpy(&iv[0], pMechanism->pParameter, blocksize);
 
@@ -6843,8 +6851,9 @@ CK_RV SoftHSM::C_WrapKey
 		case CKM_AES_CBC:
 	        case CKM_AES_CBC_PAD:
+		case CKM_DES3_CBC:
+	        case CKM_DES3_CBC_PAD:
-			if (pMechanism->pParameter == NULL_PTR ||
-                            pMechanism->ulParameterLen != 16)
+			if (pMechanism->pParameter == NULL_PTR)
                                 return CKR_ARGUMENTS_BAD;
                         break;
 		default:
@@ -6894,8 +6903,9 @@ CK_RV SoftHSM::C_WrapKey
 	if ((pMechanism->mechanism == CKM_AES_CBC || pMechanism->mechanism == CKM_AES_CBC_PAD) && wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_AES)
 		return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
-	if (pMechanism->mechanism == CKM_DES3_CBC && (wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 ||
-		wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
+	if ((pMechanism->mechanism == CKM_DES3_CBC || pMechanism->mechanism == CKM_DES3_CBC_PAD) &&
+	    (wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 &&
+	     wrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
 		return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
 
@@ -7536,8 +7545,9 @@ CK_RV SoftHSM::C_UnwrapKey
 	if ((pMechanism->mechanism == CKM_AES_CBC || pMechanism->mechanism == CKM_AES_CBC_PAD) && unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_AES)
 		return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
-	if (pMechanism->mechanism == CKM_DES3_CBC && (unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 ||
-		unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
+	if ((pMechanism->mechanism == CKM_DES3_CBC || pMechanism->mechanism == CKM_DES3_CBC_PAD) &&
+	    (unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES2 &&
+	     unwrapKey->getUnsignedLongValue(CKA_KEY_TYPE, CKK_VENDOR_DEFINED) != CKK_DES3))
 		return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
🧹 Nitpick comments (2)
src/lib/crypto/MLDSAPrivateKey.cpp (1)

32-43: Consider logging unknown parameter set cases.

The function returns 0UL for unrecognized key sizes, which is correct. However, callers should be aware that an unknown size will silently return 0. Consider adding a debug log for unmatched sizes to aid troubleshooting invalid key material.

💡 Optional: Add debug logging for unknown sizes
 unsigned long MLDSAPrivateKey::getParameterSet() const
 {
 	switch(value.size()) {
 		case MLDSAParameters::ML_DSA_44_PRIV_LENGTH:
 			return MLDSAParameters::ML_DSA_44_PARAMETER_SET;
 		case MLDSAParameters::ML_DSA_65_PRIV_LENGTH:
 			return MLDSAParameters::ML_DSA_65_PARAMETER_SET;
 		case MLDSAParameters::ML_DSA_87_PRIV_LENGTH:
 			return MLDSAParameters::ML_DSA_87_PARAMETER_SET;
+		default:
+			DEBUG_MSG("Unknown ML-DSA private key size: %zu", value.size());
+			break;
 	}
 	return 0UL;
 }
src/lib/crypto/MLDSAUtil.cpp (1)

71-128: Consider extracting the cast to a local variable.

The repeated ((MLDSAPrivateKey*)priv) casts work correctly but add visual noise. Consider storing the cast result in a local variable for clarity.

♻️ Optional: Extract cast to local variable
 	if (!priv->PKCS8Decode(ber))
 	{
 		mldsa->recyclePrivateKey(priv);
 		CryptoFactory::i()->recycleAsymmetricAlgorithm(mldsa);
 		return false;
 	}
+	MLDSAPrivateKey* mldsaPriv = static_cast<MLDSAPrivateKey*>(priv);
 	// ML-DSA Private Key Attributes
 	ByteString seed;
 	ByteString value;
 	if (isPrivate)
 	{
 		if (token == NULL)
 		{
 			mldsa->recyclePrivateKey(priv);
 			CryptoFactory::i()->recycleAsymmetricAlgorithm(mldsa);
 			return false;
 		}
-		if (!token->encrypt(((MLDSAPrivateKey*)priv)->getSeed(), seed))
+		if (!token->encrypt(mldsaPriv->getSeed(), seed))
 		{
 			mldsa->recyclePrivateKey(priv);
 			CryptoFactory::i()->recycleAsymmetricAlgorithm(mldsa);
 			return false;
 		}
-		if (!token->encrypt(((MLDSAPrivateKey*)priv)->getValue(), value))
+		if (!token->encrypt(mldsaPriv->getValue(), value))
 		{
 			mldsa->recyclePrivateKey(priv);
 			CryptoFactory::i()->recycleAsymmetricAlgorithm(mldsa);
 			return false;
 		}
 	}
 	else
 	{
-		seed = ((MLDSAPrivateKey*)priv)->getSeed();
-		value = ((MLDSAPrivateKey*)priv)->getValue();
+		seed = mldsaPriv->getSeed();
+		value = mldsaPriv->getValue();
 	}
 	bool bOK = true;
-	bOK = bOK && key->setAttribute(CKA_PARAMETER_SET, ((MLDSAPrivateKey*)priv)->getParameterSet());
+	bOK = bOK && key->setAttribute(CKA_PARAMETER_SET, mldsaPriv->getParameterSet());
 	bOK = bOK && key->setAttribute(CKA_SEED, seed);
 	bOK = bOK && key->setAttribute(CKA_VALUE, value);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07d0768 and 7f8a851.

📒 Files selected for processing (7)
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAUtil.h
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/session_mgr/Session.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/session_mgr/Session.cpp
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 818
File: src/lib/SoftHSM.cpp:3238-3266
Timestamp: 2025-10-13T09:42:31.270Z
Learning: In src/lib/SoftHSM.cpp, SoftHSM::AsymDecryptInit should use MechParamCheckRSAPKCSOAEP for CKM_RSA_PKCS_OAEP to keep OAEP parameter validation identical to AsymEncryptInit, and avoid redundant manual checks for pMechanism->pParameter and MGF allowlists.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.
📚 Learning: 2025-10-27T16:44:57.865Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T17:59:30.693Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-19T09:23:25.434Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-10-13T09:42:31.270Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 818
File: src/lib/SoftHSM.cpp:3238-3266
Timestamp: 2025-10-13T09:42:31.270Z
Learning: In src/lib/SoftHSM.cpp, SoftHSM::AsymDecryptInit should use MechParamCheckRSAPKCSOAEP for CKM_RSA_PKCS_OAEP to keep OAEP parameter validation identical to AsymEncryptInit, and avoid redundant manual checks for pMechanism->pParameter and MGF allowlists.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-11-11T19:42:46.886Z
Learnt from: JoaoHenrique12
Repo: softhsm/SoftHSMv2 PR: 823
File: src/lib/SoftHSM.cpp:58-58
Timestamp: 2025-11-11T19:42:46.886Z
Learning: SoftHSM include policy: Algorithm key/parameter headers (e.g., SLHParameters, SLH*Key) are included unconditionally in SoftHSM.cpp/SoftHSM.h; feature gating is handled in mechanism tables and CryptoFactory (WITH_*). Avoid wrapping such includes in WITH_* guards.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2026-01-11T16:15:14.696Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:373-381
Timestamp: 2026-01-11T16:15:14.696Z
Learning: In PKCS#11 v3.2, ML-DSA key sizes (returned by getMinKeySize() and getMaxKeySize()) are specified in bytes, not bits. This is different from other algorithms like RSA, DSA, ECDSA, ECDH, and EdDSA which use bits. This is defined in chapter 6.67.4 of the PKCS#11 3.2 specification.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T19:46:46.659Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-09-11T16:54:00.370Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.

Applied to files:

  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-11T16:42:23.012Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 806
File: src/lib/test/p11test.cpp:81-84
Timestamp: 2025-08-11T16:42:23.012Z
Learning: In SoftHSM test files (like p11test.cpp and cryptotest.cpp), error handling for OSSL_PROVIDER_load() calls for "legacy" and "default" providers is intentionally omitted because these providers won't fail in practice and the test code is kept deliberately simple.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2026-01-10T15:07:10.701Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 837
File: src/lib/common/log.cpp:192-194
Timestamp: 2026-01-10T15:07:10.701Z
Learning: In SoftHSM, setLogFile and closeLogFile are designed to be called only during C_Initialize and C_Finalize respectively. Per PKCS#11 spec, C_Finalize behavior is undefined if called while other threads are making Cryptoki calls, so concurrent access between closeLogFile and softHSMLog would be API misuse. The implementation relies on these PKCS#11 threading guarantees rather than defensive locking in open/close operations.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2025-08-18T19:35:33.938Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:195-204
Timestamp: 2025-08-18T19:35:33.938Z
Learning: In OpenSSL, EVP_PKEY_verify_message does not exist. EVP_PKEY_verify_message_init is designed for streaming verification and should be paired with EVP_PKEY_CTX_set_signature, EVP_PKEY_verify_message_update, and EVP_PKEY_verify_message_final - not with EVP_PKEY_verify.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
📚 Learning: 2025-08-18T17:54:37.645Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
🧬 Code graph analysis (5)
src/lib/crypto/AsymmetricAlgorithm.h (1)
src/lib/test/TestsNoPINInitBase.cpp (1)
  • ptr (60-60)
src/lib/SoftHSM.cpp (1)
src/lib/crypto/MLDSAUtil.cpp (8)
  • copyMLDSAContext (130-191)
  • copyMLDSAContext (130-130)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
src/lib/crypto/OSSLMLDSA.cpp (1)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (226-235)
  • mldsaParameterSet2Name (226-226)
src/lib/crypto/MLDSAPrivateKey.cpp (1)
src/lib/crypto/MLDSAParameters.cpp (6)
  • getParameterSet (22-25)
  • getParameterSet (22-22)
  • serialise (34-37)
  • serialise (34-34)
  • deserialise (39-50)
  • deserialise (39-39)
src/lib/crypto/MLDSAUtil.h (1)
src/lib/crypto/MLDSAUtil.cpp (8)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
  • copyMLDSAContext (130-191)
  • copyMLDSAContext (130-130)
🪛 Clang (14.0.6)
src/lib/crypto/AsymmetricAlgorithm.h

[error] 36-36: 'cstdlib' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/OSSLMLDSA.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAPrivateKey.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.h

[error] 10-10: 'config.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: macOS (openssl)
  • GitHub Check: Linux with OpenSSL 3.5.4
  • GitHub Check: macOS (botan)
  • GitHub Check: Windows (x86, openssl)
  • GitHub Check: Linux with OpenSSL 1.1.1
  • GitHub Check: Windows (x64, botan)
  • GitHub Check: Windows (x64, openssl)
  • GitHub Check: Linux with OpenSSL 3.0
  • GitHub Check: Linux with Botan
🔇 Additional comments (28)
src/lib/crypto/MLDSAPrivateKey.cpp (3)

17-24: LGTM!

The type checking implementation correctly handles NULL input and uses strcmp for string comparison.


46-57: LGTM!

The signature length mapping follows the same established pattern as getParameterSet() and correctly maps private key sizes to their corresponding signature lengths.


86-100: LGTM with minor observation.

The deserialization correctly validates both fields are non-empty before assignment. The implementation follows the expected serialization order (seed then value) established in serialise().

src/lib/crypto/MLDSAUtil.h (1)

20-29: LGTM!

The utility class provides a clean static interface for ML-DSA key operations and context handling. The function signatures appropriately use the internal SIGN_ADDITIONAL_CONTEXT type for the copied context output, aligning with the deep-copy strategy described in the learnings.

src/lib/crypto/MLDSAUtil.cpp (3)

11-41: LGTM!

The function correctly handles both encrypted (CKA_PRIVATE=true) and unencrypted key material, with appropriate error handling for decryption failures.


43-69: LGTM!

Follows the same secure pattern as getMLDSAPrivateKey, appropriately handling only the value attribute for public keys.


130-191: LGTM - well-designed single-allocation pattern.

The function correctly implements the single-blob allocation strategy for SIGN_ADDITIONAL_CONTEXT plus context bytes, as discussed in the learnings. The ownsContextMemory = false flag is intentional since the entire blob is freed via free(sac) rather than separately freeing contextData.

The context length validation (max 255) aligns with the ML-DSA specification.

src/lib/crypto/AsymmetricAlgorithm.h (4)

36-37: LGTM!

The <cstdlib> and <cstring> includes are necessary for std::malloc, std::free, and std::memcpy used in the new structures below.


58-59: LGTM!

The MLDSA entries extend the existing algorithm and mechanism enums cleanly, following the established pattern.

Also applies to: 98-99


123-131: LGTM!

The Hedge::Type enum provides an internal representation for the PKCS#11 CK_HEDGE_TYPE values, cleanly abstracting the PKCS#11 constants.


164-175: LGTM with usage caveat.

The guard correctly handles the single-blob allocation pattern from copyMLDSAContext() by calling the destructor (which is a no-op when ownsContextMemory=false) then free().

Note: This guard is designed specifically for malloc-allocated SIGN_ADDITIONAL_CONTEXT pointers. Objects created via new or on the stack should not use this guard.

src/lib/SoftHSM.cpp (8)

65-69: Unconditional ML-DSA includes look consistent with SoftHSM policy.
This matches the project’s “include headers unconditionally; gate via mechanisms/CryptoFactory” approach. Based on learnings, this is preferred.


148-172: ML-DSA object routing via WITH_ML_DSA looks correct.
The CKK_ML_DSA → P11MLDSA{Public,Private}KeyObj wiring is appropriately guarded and mirrors existing key-type routing.


841-845: Mechanism exposure for ML-DSA is wired correctly.
Adding CKM_ML_DSA_KEY_PAIR_GEN and CKM_ML_DSA behind WITH_ML_DSA is the right place to surface them for configuration and mechanism lists.

Also applies to: 886-889


949-952: ML-DSA mechanism info: confirm “key sizes in bytes” semantics + clean up minor nits.
The comment/handling indicates ML-DSA min/max are bytes (unlike most others). Please ensure CryptoFactory/AsymAlgo::MLDSA returns min/max in bytes as expected, and consider fixing the spelling/indentation for readability. Based on learnings, bytes-vs-bits is intentional for ML-DSA.

Also applies to: 1051-1061, 1352-1363


1151-1154: CKM_GENERIC_SECRET_KEY_GEN max size change aligns with generateGeneric checks.
Setting ulMaxKeySize to 0x8000000 matches later enforcement (keyLen > 0x8000000), so the API surface and implementation stay consistent.


5786-5792: Debug logging on signature length mismatch is fine; keep it non-sensitive.
Logging only lengths is typically OK; just ensure DEBUG logs won’t be enabled in sensitive deployments and the format types match CK_ULONG on all supported platforms.

Also applies to: 5969-5976


6283-6424: ML-DSA keypair generation path looks coherent; parameter-set validation is solid.
Template consistency checks + generateMLDSA enforcing CKP_ML_DSA_{44,65,87} and returning CKR_PARAMETER_SET_NOT_SUPPORTED looks correct, and the object attribute persistence (CKA_PARAMETER_SET/CKA_VALUE/CKA_SEED) is consistent.

Also applies to: 6315-6317, 6340-6342, 10134-10392


4202-4505: The ML-DSA additional context ownership is handled correctly. The code allocates a temporary context via MLDSAUtil::copyMLDSAContext(), which MLDSAAdditionalContextGuard (RAII) holds safely. Session::setParameters() deep-copies this into session-owned storage via malloc() and memcpy(), then fixes the internal contextData pointer to reference the new copied buffer. When the guard scope ends, the temporary is freed via the destructor; when the session resets, Session::resetOp() frees the session-owned copy. No UAF or double-free risk.

src/lib/crypto/OSSLMLDSA.cpp (9)

1-25: LGTM!

The includes are appropriate for the OpenSSL EVP-based ML-DSA implementation. The static OSSL_RANDOM and OSSL_DETERMINISTIC members are correctly defined as int variables (not constants) since OSSL_PARAM_construct_int requires a pointer to a mutable int.

Note: The static analysis warning about config.h not found is a false positive—this file is generated by the autoconf build system.


26-151: LGTM!

The signing implementation correctly:

  • Validates the mechanism and key types
  • Handles SIGN_ADDITIONAL_CONTEXT parameters for hedged/deterministic variants
  • Properly terminates the OSSL_PARAM array in all code paths
  • Cleans up OpenSSL resources (EVP_PKEY_CTX, EVP_SIGNATURE) on all error paths

153-173: LGTM!

Multi-part signing is correctly unsupported for ML-DSA per PKCS#11 3.2 specification. The stubs appropriately return false with descriptive error messages.


175-308: LGTM!

The verification implementation correctly:

  • Validates signature length against expected output before calling OpenSSL
  • Handles SIGN_ADDITIONAL_CONTEXT parameters consistently with the sign function
  • Properly distinguishes between verification failure (verifyRV == 0) and actual errors (verifyRV < 0), only logging errors for the latter

310-330: LGTM!

Multi-part verification stubs correctly reject unsupported operations, consistent with ML-DSA single-part requirement.


332-365: LGTM!

  • Encryption/decryption stubs correctly reject operations since ML-DSA is a signature-only algorithm
  • Key size functions return byte values (not bits), which aligns with PKCS#11 3.2 §6.67.4 for ML-DSA

430-512: LGTM!

The key reconstruction functions follow the established SoftHSM pattern:

  • Validate input parameters
  • Create new key object
  • Attempt deserialization
  • Clean up on failure

The reconstructKeyPair function could theoretically short-circuit after the first deserialization failure, but the current approach is correct and matches patterns used elsewhere in the codebase.


514-549: LGTM!

The factory methods and reconstructParameters follow the established patterns in the codebase. Clean and straightforward implementations.


414-425: No action required — the code is safe.

setFromOSSL extracts and deep-copies the key material using EVP_PKEY_get_octet_string_param(), storing only the extracted seed/private/public key bytes. The EVP_PKEY pointer itself is not retained, making the EVP_PKEY_free(pkey) call at line 425 safe.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/lib/crypto/AsymmetricAlgorithm.h (1)

144-152: Silent allocation failure could mask errors in tests.

When std::malloc fails on line 148, the constructor silently sets contextLen = 0 and ownsContextMemory = false, making the object appear valid but with no context data. Since this constructor is noted as being "used in unit tests," consider adding an isValid() method or throwing an exception to help detect allocation failures during testing.

💡 Optional: Add validity check
+    bool isValid() const {
+        // Valid if no context was requested, or if context was successfully allocated
+        return (contextLen == 0 && contextData == NULL) ||
+               (contextLen > 0 && contextData != NULL);
+    }
src/lib/SoftHSM.cpp (2)

840-844: Mechanism exposure for ML-DSA is straightforward; consider config token UX.

Adding CKM_ML_DSA_KEY_PAIR_GEN and CKM_ML_DSA to prepareSupportedMechanisms() is fine. One small UX thought: if slots.mechanisms is configured, users must now explicitly allow/deny these tokens—worth ensuring the docs/tests cover this new config surface.

Also applies to: 886-889


10135-10393: generateMLDSA: solid parameter-set validation; consider template consistency checks.

The CKA_PARAMETER_SET validation and CKR_PARAMETER_SET_NOT_SUPPORTED behavior match project conventions (using CKP_ML_DSA_*). Based on learnings, keeping CKP_ML_DSA_* constants is preferred.

One follow-up that may prevent confusing callers: if CKA_PARAMETER_SET is also provided in the private template (or both templates), consider rejecting mismatches with CKR_TEMPLATE_INCONSISTENT rather than ignoring one side.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f8a851 and 0009185.

📒 Files selected for processing (7)
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAUtil.h
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/session_mgr/Session.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/session_mgr/Session.cpp
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 818
File: src/lib/SoftHSM.cpp:3238-3266
Timestamp: 2025-10-13T09:42:31.270Z
Learning: In src/lib/SoftHSM.cpp, SoftHSM::AsymDecryptInit should use MechParamCheckRSAPKCSOAEP for CKM_RSA_PKCS_OAEP to keep OAEP parameter validation identical to AsymEncryptInit, and avoid redundant manual checks for pMechanism->pParameter and MGF allowlists.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.
📚 Learning: 2025-10-27T16:44:57.865Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T17:59:30.693Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-19T09:23:25.434Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-10-13T09:42:31.270Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 818
File: src/lib/SoftHSM.cpp:3238-3266
Timestamp: 2025-10-13T09:42:31.270Z
Learning: In src/lib/SoftHSM.cpp, SoftHSM::AsymDecryptInit should use MechParamCheckRSAPKCSOAEP for CKM_RSA_PKCS_OAEP to keep OAEP parameter validation identical to AsymEncryptInit, and avoid redundant manual checks for pMechanism->pParameter and MGF allowlists.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-11-11T19:42:46.886Z
Learnt from: JoaoHenrique12
Repo: softhsm/SoftHSMv2 PR: 823
File: src/lib/SoftHSM.cpp:58-58
Timestamp: 2025-11-11T19:42:46.886Z
Learning: SoftHSM include policy: Algorithm key/parameter headers (e.g., SLHParameters, SLH*Key) are included unconditionally in SoftHSM.cpp/SoftHSM.h; feature gating is handled in mechanism tables and CryptoFactory (WITH_*). Avoid wrapping such includes in WITH_* guards.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T19:46:46.659Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2026-01-11T16:15:14.696Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:373-381
Timestamp: 2026-01-11T16:15:14.696Z
Learning: In PKCS#11 v3.2, ML-DSA key sizes (returned by getMinKeySize() and getMaxKeySize()) are specified in bytes, not bits. This is different from other algorithms like RSA, DSA, ECDSA, ECDH, and EdDSA which use bits. This is defined in chapter 6.67.4 of the PKCS#11 3.2 specification.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-09-11T16:54:00.370Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.

Applied to files:

  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T19:35:33.938Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:195-204
Timestamp: 2025-08-18T19:35:33.938Z
Learning: In OpenSSL, EVP_PKEY_verify_message does not exist. EVP_PKEY_verify_message_init is designed for streaming verification and should be paired with EVP_PKEY_CTX_set_signature, EVP_PKEY_verify_message_update, and EVP_PKEY_verify_message_final - not with EVP_PKEY_verify.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
📚 Learning: 2025-08-18T17:54:37.645Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
📚 Learning: 2025-08-11T16:42:23.012Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 806
File: src/lib/test/p11test.cpp:81-84
Timestamp: 2025-08-11T16:42:23.012Z
Learning: In SoftHSM test files (like p11test.cpp and cryptotest.cpp), error handling for OSSL_PROVIDER_load() calls for "legacy" and "default" providers is intentionally omitted because these providers won't fail in practice and the test code is kept deliberately simple.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2026-01-10T15:07:10.701Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 837
File: src/lib/common/log.cpp:192-194
Timestamp: 2026-01-10T15:07:10.701Z
Learning: In SoftHSM, setLogFile and closeLogFile are designed to be called only during C_Initialize and C_Finalize respectively. Per PKCS#11 spec, C_Finalize behavior is undefined if called while other threads are making Cryptoki calls, so concurrent access between closeLogFile and softHSMLog would be API misuse. The implementation relies on these PKCS#11 threading guarantees rather than defensive locking in open/close operations.

Applied to files:

  • src/lib/SoftHSM.cpp
🧬 Code graph analysis (4)
src/lib/crypto/AsymmetricAlgorithm.h (1)
src/lib/test/TestsNoPINInitBase.cpp (1)
  • ptr (60-60)
src/lib/crypto/MLDSAPrivateKey.cpp (3)
src/lib/crypto/MLDSAPublicKey.cpp (12)
  • isOfType (17-20)
  • isOfType (17-17)
  • getParameterSet (28-39)
  • getParameterSet (28-28)
  • getOutputLength (42-53)
  • getOutputLength (42-42)
  • setValue (60-63)
  • setValue (60-60)
  • serialise (66-69)
  • serialise (66-66)
  • deserialise (71-83)
  • deserialise (71-71)
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (4)
  • isOfType (90-93)
  • isOfType (90-90)
  • setValue (95-103)
  • setValue (95-95)
src/lib/crypto/MLDSAParameters.cpp (6)
  • getParameterSet (22-25)
  • getParameterSet (22-22)
  • serialise (34-37)
  • serialise (34-34)
  • deserialise (39-50)
  • deserialise (39-39)
src/lib/SoftHSM.cpp (1)
src/lib/crypto/MLDSAUtil.cpp (6)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
src/lib/crypto/MLDSAUtil.h (1)
src/lib/crypto/MLDSAUtil.cpp (8)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
  • copyMLDSAContext (130-191)
  • copyMLDSAContext (130-130)
🪛 Clang (14.0.6)
src/lib/crypto/AsymmetricAlgorithm.h

[error] 36-36: 'cstdlib' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAPrivateKey.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/OSSLMLDSA.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.h

[error] 10-10: 'config.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Windows (x64, botan)
  • GitHub Check: Linux with OpenSSL 3.5.4
  • GitHub Check: Windows (x86, openssl)
  • GitHub Check: Windows (x64, openssl)
🔇 Additional comments (26)
src/lib/crypto/AsymmetricAlgorithm.h (4)

36-37: LGTM!

The inclusion of <cstdlib> and <cstring> is appropriate for the std::malloc, std::free, and std::memcpy usage in the new structs below. The static analysis warning about 'cstdlib' not found is a false positive due to project-specific include paths.


58-59: LGTM!

The MLDSA enum values are correctly added to both AsymAlgo::Type and AsymMech::Type, following the existing pattern established by other algorithms like EDDSA.

Also applies to: 98-99


123-131: LGTM!

The Hedge struct cleanly encapsulates the PKCS#11 3.2 hedge variant options for ML-DSA signing operations.


164-175: LGTM!

The MLDSAAdditionalContextGuard correctly implements RAII cleanup for the contiguous blob allocation pattern. The explicit destructor call before std::free() is appropriate since SIGN_ADDITIONAL_CONTEXT has a non-trivial destructor, and the ownsContextMemory = false flag in blob allocations prevents double-free of the context data. Based on learnings, this aligns with the Session::setParameters deep-copy pattern.

src/lib/crypto/MLDSAUtil.h (1)

1-32: LGTM!

The header file cleanly declares the MLDSAUtil utility class with appropriate static methods for ML-DSA key handling and context copying. The WITH_ML_DSA guard ensures the code is only compiled when ML-DSA support is enabled. The static analysis warning about 'config.h' not found is a false positive.

src/lib/crypto/MLDSAUtil.cpp (4)

11-41: LGTM!

The getMLDSAPrivateKey function correctly handles both private and non-private keys, with proper input validation and error handling for decryption failures.


43-69: LGTM!

The getMLDSAPublicKey function correctly handles public key extraction with the same validation and decryption pattern as the private key counterpart.


71-128: LGTM!

The setMLDSAPrivateKey function correctly handles PKCS#8 decoding, optional encryption for private keys, and attribute setting. The cleanup pattern, while repetitive, is consistent with the existing codebase style. Based on learnings, the parameter set is correctly inferred from key material length via getParameterSet().


130-191: LGTM!

The copyMLDSAContext function correctly implements the contiguous blob allocation pattern per the retrieved learnings. It properly validates the context length (max 255 bytes per PKCS#11 3.2 §6.67.5), maps all hedge variants including the default case, and handles memory allocation failures with CKR_HOST_MEMORY. The ownsContextMemory = false flag ensures the blob is freed as a unit via free(sac).

src/lib/crypto/MLDSAPrivateKey.cpp (3)

13-24: LGTM!

The isOfType implementation correctly handles NULL input and follows the established pattern in the codebase.


31-57: LGTM!

The getParameterSet and getOutputLength methods correctly infer the ML-DSA parameter set from the private key material length, using the appropriate ML_DSA_*_PRIV_LENGTH constants. This aligns with the established pattern in MLDSAPublicKey and the retrieved learnings about parameter set inference.


79-100: LGTM!

The serialization correctly concatenates seed and value, while deserialization properly validates both components are non-empty before populating the key. The use of chainDeserialise ensures correct parsing of the concatenated data.

src/lib/crypto/OSSLMLDSA.cpp (8)

96-151: LGTM!

The signing implementation correctly handles all error paths with proper resource cleanup for both EVP_PKEY_CTX and EVP_SIGNATURE. The two-phase signing (size query followed by actual sign) follows OpenSSL best practices.


176-308: LGTM!

The verification implementation correctly uses EVP_PKEY_verify_message_init paired with EVP_PKEY_verify per the retrieved learnings. The signature length validation, parameter handling, and error/resource cleanup mirror the signing flow appropriately.


153-173: LGTM!

The stub functions correctly reject unsupported operations (multi-part signing/verification and encryption/decryption) with appropriate error messages. This aligns with PKCS#11 3.2 which specifies ML-DSA as single-part only.

Also applies to: 310-365


350-358: LGTM!

The key size functions correctly return ML-DSA private key lengths in bytes, which aligns with PKCS#11 v3.2 chapter 6.67.4 (per retrieved learnings, ML-DSA key sizes are specified in bytes unlike other algorithms that use bits).


430-512: LGTM!

The key reconstruction functions follow the established pattern in the codebase, with proper deserialization, validation, and cleanup on failure.


514-549: LGTM!

The factory methods correctly instantiate the appropriate OpenSSL-backed key and parameter types.


414-428: No issue with EVP_PKEY_free call on line 425.

setFromOSSL extracts and copies key material into the key objects using EVP_PKEY_get_octet_string_param(), not storing the incoming pkey pointer. The EVP_PKEY is safely freed after both setFromOSSL calls.


72-94: No issue. The const-cast on line 83 is safe and necessary for OpenSSL API compatibility. OSSL_PARAM_construct_octet_string does not modify the input data—it only stores a pointer to it. The data lifetime is correctly maintained (the contextData pointer from the additionalContext parameter remains valid through the signing operation), and the non-const signature is a quirk of OpenSSL's API design.

src/lib/SoftHSM.cpp (6)

65-69: Unconditional ML-DSA includes align with SoftHSM include policy.

Keeping MLDSA* headers unconditional here is consistent with the project approach (feature gating via mechanism tables / WITH_*, not includes). Based on learnings, the include policy is intentional.


148-152: ML-DSA object instantiation wiring looks correct.

CKK_ML_DSAP11MLDSAPublicKeyObj / P11MLDSAPrivateKeyObj under WITH_ML_DSA matches the existing pattern for other key types.

Also applies to: 169-172


949-952: ML-DSA mechanism info: bytes-vs-bits handling is correct; keep it consistent end-to-end.

The comment + plumbing for ML-DSA min/max sizes being bytes (not bits) is correct for PKCS#11 v3.2 ML-DSA. Based on learnings, OSSLMLDSA::{getMinKeySize,getMaxKeySize} are in bytes, so this aligns.

Also applies to: 1051-1061, 1353-1364


5790-5791: Debug logging is safe and useful here.

Logging only (expected size, provided size) on signature-length mismatch helps diagnose interop issues without leaking sensitive material.

Also applies to: 5974-5975


6284-6288: Keypair-gen routing for ML-DSA looks consistent.

CKM_ML_DSA_KEY_PAIR_GEN routing to generateMLDSA() and the template mismatch checks follow existing patterns.

Also applies to: 6316-6318, 6341-6343, 6416-6425


4204-4208: The CK_SIGN_ADDITIONAL_CONTEXT ownership semantics are correct; no UAF/double-free risk.

The code is safe as implemented:

  • MLDSAUtil::copyMLDSAContext() allocates a buffer (buffer A) and stores it in mldsaCtxGuard.ptr
  • session->setParameters(param, paramLen) allocates its own independent buffer (buffer B) and deep-copies the data from buffer A into it, then fixes up internal pointers in buffer B
  • The guard's destructor frees buffer A when scope exits
  • session->resetOp() later frees buffer B

No transfer of ownership is needed, and the current approach avoids the complexity of transient buffer passing. The design is sound.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/SoftHSM.cpp (1)

6502-6619: Bug: WrapKeySym uses blocksize for CBC IV but never sets it for CKM_AES_CBC / CKM_DES3_CBC. This makes iv.resize(blocksize) zero-length and will likely break AES-CBC wrapping (and DES3-CBC if reachable).

Proposed fix
--- a/src/lib/SoftHSM.cpp
+++ b/src/lib/SoftHSM.cpp
@@
 	switch(pMechanism->mechanism) {
@@
 		case CKM_AES_CBC:
 			algo = SymAlgo::AES;
+			blocksize = 16;
 			break;
 
 		case CKM_AES_CBC_PAD:
 			blocksize = 16;
 			wrappedlen = RFC5652Pad(keydata, blocksize);
 			algo = SymAlgo::AES;
 			break;
@@
 		case CKM_DES3_CBC:
 			algo = SymAlgo::DES3;
+			blocksize = 8;
 			break;
 
 		case CKM_DES3_CBC_PAD:
 			blocksize = 8;
 			wrappedlen = RFC5652Pad(keydata, blocksize);
 			algo = SymAlgo::DES3;
 			break;
🧹 Nitpick comments (2)
src/lib/SoftHSM.cpp (2)

4204-4208: ML-DSA CK_SIGN_ADDITIONAL_CONTEXT handling + deep-copy approach looks solid. The MLDSAUtil::copyMLDSAContext() usage + passing a contiguous SIGN_ADDITIONAL_CONTEXT blob into Session::setParameters() aligns with the “Session owns a deep copy” approach (based on learnings).

Also applies to: 4475-4505, 4593-4612, 5266-5269, 5535-5564, 5651-5670


7137-7146: CBC_PAD unwrap path looks coherent (manual unpad after decrypt). Consider using a more specific CKR_* than CKR_GENERAL_ERROR for padding failures when you have time.

Also applies to: 7176-7204

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0009185 and 52c360c.

📒 Files selected for processing (7)
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAUtil.h
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/session_mgr/Session.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/session_mgr/Session.cpp
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 818
File: src/lib/SoftHSM.cpp:3238-3266
Timestamp: 2025-10-13T09:42:31.270Z
Learning: In src/lib/SoftHSM.cpp, SoftHSM::AsymDecryptInit should use MechParamCheckRSAPKCSOAEP for CKM_RSA_PKCS_OAEP to keep OAEP parameter validation identical to AsymEncryptInit, and avoid redundant manual checks for pMechanism->pParameter and MGF allowlists.
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.
📚 Learning: 2025-10-27T16:44:57.865Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:5550-5596
Timestamp: 2025-10-27T16:44:57.865Z
Learning: SoftHSM ML‑DSA additional context: avoid transient malloc in SoftHSM::{AsymSignInit, AsymVerifyInit}; let Session::setParameters deep‑copy SIGN_ADDITIONAL_CONTEXT by allocating one buffer for struct + context bytes and fixing the stored pointer; Session::resetOp frees m_pParameters so both struct and context are released.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T17:59:30.693Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/MLDSAParameters.h:42-44
Timestamp: 2025-08-18T17:59:30.693Z
Learning: In MLDSAParameters.h, the author prefers to keep PKCS#11 constant references (CKP_ML_DSA_*) rather than explicit numeric values for better code clarity and maintainability.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-19T09:23:25.434Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:4193-4196
Timestamp: 2025-08-19T09:23:25.434Z
Learning: MLDSA parameter set inference: In src/lib/crypto/MLDSAPrivateKey.{h,cpp} and MLDSAPublicKey.{h,cpp}, getParameterSet() maps key material length to CKP_ML_DSA_44/65/87, and there is no setParameterSet(). Runtime MLDSA keys used by SoftHSM (e.g., in C_SignInit/C_VerifyInit via MLDSAUtil) rely on this inference; no explicit parameter-set assignment is required.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-10-13T09:42:31.270Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 818
File: src/lib/SoftHSM.cpp:3238-3266
Timestamp: 2025-10-13T09:42:31.270Z
Learning: In src/lib/SoftHSM.cpp, SoftHSM::AsymDecryptInit should use MechParamCheckRSAPKCSOAEP for CKM_RSA_PKCS_OAEP to keep OAEP parameter validation identical to AsymEncryptInit, and avoid redundant manual checks for pMechanism->pParameter and MGF allowlists.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-11-11T19:42:46.886Z
Learnt from: JoaoHenrique12
Repo: softhsm/SoftHSMv2 PR: 823
File: src/lib/SoftHSM.cpp:58-58
Timestamp: 2025-11-11T19:42:46.886Z
Learning: SoftHSM include policy: Algorithm key/parameter headers (e.g., SLHParameters, SLH*Key) are included unconditionally in SoftHSM.cpp/SoftHSM.h; feature gating is handled in mechanism tables and CryptoFactory (WITH_*). Avoid wrapping such includes in WITH_* guards.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-18T19:46:46.659Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/SoftHSM.cpp:10044-10055
Timestamp: 2025-08-18T19:46:46.659Z
Learning: In SoftHSM::generateMLDSA (src/lib/SoftHSM.cpp), the project prefers using CKP_ML_DSA_* constants for parameter set checks and logs "Unsupported parameter set: %lu". The code currently returns CKR_PARAMETER_SET_NOT_SUPPORTED for unsupported sets.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2026-01-11T16:15:14.696Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:373-381
Timestamp: 2026-01-11T16:15:14.696Z
Learning: In PKCS#11 v3.2, ML-DSA key sizes (returned by getMinKeySize() and getMaxKeySize()) are specified in bytes, not bits. This is different from other algorithms like RSA, DSA, ECDSA, ECDH, and EdDSA which use bits. This is defined in chapter 6.67.4 of the PKCS#11 3.2 specification.

Applied to files:

  • src/lib/crypto/AsymmetricAlgorithm.h
  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/crypto/MLDSAPrivateKey.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-09-11T16:54:00.370Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 815
File: src/lib/SoftHSM.cpp:6543-6546
Timestamp: 2025-09-11T16:54:00.370Z
Learning: SoftHSM PR #815 (pkcs11.h 3.1.0 update, CK_RSA_AES_KEY_WRAP_PARAMS fix): Maintainer bukka prefers to keep scope minimal (header sync and param rename only); broader OAEP parameter enforcement or backend plumbing is out of scope for this PR and should be handled in a follow-up issue.

Applied to files:

  • src/lib/crypto/MLDSAUtil.cpp
  • src/lib/SoftHSM.cpp
  • src/lib/crypto/OSSLMLDSA.cpp
  • src/lib/crypto/MLDSAUtil.h
📚 Learning: 2025-08-11T16:42:23.012Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 806
File: src/lib/test/p11test.cpp:81-84
Timestamp: 2025-08-11T16:42:23.012Z
Learning: In SoftHSM test files (like p11test.cpp and cryptotest.cpp), error handling for OSSL_PROVIDER_load() calls for "legacy" and "default" providers is intentionally omitted because these providers won't fail in practice and the test code is kept deliberately simple.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2026-01-10T15:07:10.701Z
Learnt from: bukka
Repo: softhsm/SoftHSMv2 PR: 837
File: src/lib/common/log.cpp:192-194
Timestamp: 2026-01-10T15:07:10.701Z
Learning: In SoftHSM, setLogFile and closeLogFile are designed to be called only during C_Initialize and C_Finalize respectively. Per PKCS#11 spec, C_Finalize behavior is undefined if called while other threads are making Cryptoki calls, so concurrent access between closeLogFile and softHSMLog would be API misuse. The implementation relies on these PKCS#11 threading guarantees rather than defensive locking in open/close operations.

Applied to files:

  • src/lib/SoftHSM.cpp
📚 Learning: 2025-08-18T19:35:33.938Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: src/lib/crypto/OSSLMLDSA.cpp:195-204
Timestamp: 2025-08-18T19:35:33.938Z
Learning: In OpenSSL, EVP_PKEY_verify_message does not exist. EVP_PKEY_verify_message_init is designed for streaming verification and should be paired with EVP_PKEY_CTX_set_signature, EVP_PKEY_verify_message_update, and EVP_PKEY_verify_message_final - not with EVP_PKEY_verify.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
📚 Learning: 2025-08-18T17:54:37.645Z
Learnt from: antoinelochet
Repo: softhsm/SoftHSMv2 PR: 809
File: m4/acx_openssl_mldsa.m4:10-35
Timestamp: 2025-08-18T17:54:37.645Z
Learning: For OpenSSL feature detection in autoconf macros, prefer functional tests (like EVP_PKEY_CTX_new_from_name) over version number checks, as the functional approach directly tests the actual capability and is more reliable across different OpenSSL builds where features may be backported or disabled.

Applied to files:

  • src/lib/crypto/OSSLMLDSA.cpp
🧬 Code graph analysis (4)
src/lib/crypto/MLDSAUtil.cpp (1)
src/lib/crypto/AsymmetricAlgorithm.h (1)
  • SIGN_ADDITIONAL_CONTEXT (133-157)
src/lib/crypto/MLDSAPrivateKey.cpp (2)
src/lib/crypto/MLDSAPublicKey.cpp (10)
  • getParameterSet (28-39)
  • getParameterSet (28-28)
  • getOutputLength (42-53)
  • getOutputLength (42-42)
  • setValue (60-63)
  • setValue (60-60)
  • serialise (66-69)
  • serialise (66-66)
  • deserialise (71-83)
  • deserialise (71-71)
src/lib/crypto/MLDSAParameters.cpp (6)
  • getParameterSet (22-25)
  • getParameterSet (22-22)
  • serialise (34-37)
  • serialise (34-34)
  • deserialise (39-50)
  • deserialise (39-39)
src/lib/SoftHSM.cpp (1)
src/lib/crypto/MLDSAUtil.cpp (8)
  • copyMLDSAContext (130-191)
  • copyMLDSAContext (130-130)
  • getMLDSAPrivateKey (11-41)
  • getMLDSAPrivateKey (11-11)
  • getMLDSAPublicKey (43-69)
  • getMLDSAPublicKey (43-43)
  • setMLDSAPrivateKey (71-128)
  • setMLDSAPrivateKey (71-71)
src/lib/crypto/OSSLMLDSA.cpp (2)
src/lib/crypto/AsymmetricAlgorithm.h (1)
  • SIGN_ADDITIONAL_CONTEXT (133-157)
src/lib/crypto/OSSLUtil.cpp (2)
  • mldsaParameterSet2Name (226-235)
  • mldsaParameterSet2Name (226-226)
🪛 Clang (14.0.6)
src/lib/crypto/AsymmetricAlgorithm.h

[error] 36-36: 'cstdlib' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAPrivateKey.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/OSSLMLDSA.cpp

[error] 7-7: 'config.h' file not found

(clang-diagnostic-error)

src/lib/crypto/MLDSAUtil.h

[error] 10-10: 'config.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Windows (x64, openssl)
  • GitHub Check: Linux with OpenSSL 1.1.1
  • GitHub Check: Linux with OpenSSL 3.5.4
  • GitHub Check: Linux with Botan
  • GitHub Check: macOS (openssl)
  • GitHub Check: Windows (x86, openssl)
  • GitHub Check: Windows (x64, botan)
  • GitHub Check: Linux with OpenSSL 3.0
  • GitHub Check: macOS (botan)
🔇 Additional comments (25)
src/lib/crypto/AsymmetricAlgorithm.h (3)

36-37: LGTM - Standard library includes for memory management.

The <cstdlib> and <cstring> includes are appropriate for the std::malloc, std::free, and std::memcpy operations used in the new SIGN_ADDITIONAL_CONTEXT struct.

Note: The static analysis hint about 'cstdlib' file not found is a false positive - this is a standard C++ header and builds correctly per the CI reports.


133-169: Well-designed SIGN_ADDITIONAL_CONTEXT with proper ownership semantics.

The struct correctly implements:

  • Ownership tracking via ownsContextMemory flag
  • Safe destructor that only frees when the struct owns the memory
  • Non-copyable/non-movable to prevent double-free issues
  • Exception safety by throwing std::bad_alloc on allocation failure

Based on learnings, the ownsContextMemory=true path is for unit test convenience, while Session::setParameters uses the single-blob allocation pattern with ownsContextMemory=false.


171-182: Correct RAII guard for contiguous blob allocation.

The destructor pattern of calling ptr->~SIGN_ADDITIONAL_CONTEXT() followed by std::free(ptr) is correct for the single-blob allocation used in copyMLDSAContext. Since ownsContextMemory=false for blob-allocated contexts, the destructor won't double-free the context data.

src/lib/crypto/MLDSAPrivateKey.cpp (3)

17-29: LGTM - Type checking and bit length calculation.

The isOfType correctly handles NULL input and getBitLength properly delegates to the ByteString::bits() method.


31-57: LGTM - Parameter set inference from key length.

These methods correctly infer the ML-DSA parameter set from the private key length, consistent with the MLDSAPublicKey pattern. Based on learnings, this inference approach is intentional - there's no setParameterSet() as the parameter set is determined from key material length.


79-100: LGTM - Serialization with proper ordering.

The serialise and deserialise methods maintain consistent ordering (seed then value) and correctly require both components to be present for a valid private key. The use of chainDeserialise for sequential extraction matches the pattern in MLDSAPublicKey.

src/lib/crypto/OSSLMLDSA.cpp (6)

72-94: OSSL_PARAM construction looks correct, but the const cast is unavoidable here.

The OSSL_PARAM_construct_octet_string API requires a non-const pointer even though it shouldn't modify the data. The cast on line 83 is a necessary workaround for the OpenSSL API design.

The handling of Hedge::Type correctly maps DETERMINISTIC_REQUIRED to OSSL_DETERMINISTIC and all other cases (including HEDGE_PREFERRED and HEDGE_REQUIRED) to OSSL_RANDOM, which aligns with the PKCS#11 v3.2 specification where hedging means randomized signatures.


289-308: Correct verification flow with proper error handling.

The verification correctly distinguishes between signature mismatch (verifyRV == 0, returns false silently) and actual errors (verifyRV != 0 && verifyRV != 1, logs error then returns false). Based on learnings, the EVP_PKEY_verify_message_init + EVP_PKEY_verify pairing is the correct OpenSSL pattern for ML-DSA.


153-173: Correct stubs for unsupported multi-part operations.

Per PKCS#11 v3.2, ML-DSA is single-part only. These stubs correctly reject multi-part signing operations with clear error messages.


350-358: LGTM - Key sizes in bytes per PKCS#11 v3.2.

Based on learnings, ML-DSA key sizes are specified in bytes (not bits like RSA/DSA/ECDSA), as defined in PKCS#11 3.2 chapter 6.67.4. The use of ML_DSA_*_PRIV_LENGTH constants is correct.


430-466: LGTM - Proper cleanup on deserialisation failure.

The reconstructKeyPair correctly handles the case where either public or private key fails to deserialise by deleting the allocated key pair before returning false.


414-428: No changes needed. The reference counting is correct.

setFromOSSL does not store a reference to pkey. Instead, it extracts the raw key material (seed, private key, or public key) using EVP_PKEY_get_octet_string_param and stores only the extracted ByteString data. Therefore, freeing pkey immediately after calling setFromOSSL is safe and does not require EVP_PKEY_up_ref.

Likely an incorrect or invalid review comment.

src/lib/crypto/MLDSAUtil.h (1)

20-29: LGTM - Clean utility class declaration.

The MLDSAUtil class provides a well-organized set of static utility functions for ML-DSA key and context management. The method signatures are appropriate for their purposes.

src/lib/crypto/MLDSAUtil.cpp (3)

11-69: LGTM - Key retrieval with proper encryption handling.

Both functions correctly handle the CKA_PRIVATE attribute to determine whether decryption is needed. The private key function appropriately retrieves both CKA_VALUE and CKA_SEED, while the public key function only needs CKA_VALUE.


71-128: LGTM - Proper PKCS#8 import with encryption handling.

The function correctly:

  • Validates that a token is available when the key is private
  • Encrypts both seed and value before storing for private keys
  • Uses getParameterSet() which infers the parameter set from key material length (per learnings)
  • Cleans up resources on all code paths

173-191: Well-implemented single-blob allocation pattern.

The contiguous allocation [SIGN_ADDITIONAL_CONTEXT][context bytes] with ownsContextMemory=false correctly follows the pattern described in learnings, where Session::resetOp frees m_pParameters to release the entire blob.

The pointer arithmetic sac + 1 correctly points past the struct header to the context data area, and standard struct alignment should ensure proper alignment for the unsigned char array that follows.

src/lib/SoftHSM.cpp (9)

65-68: Unconditional ML-DSA header includes look correct (matches SoftHSM include policy). As per learnings, keep feature gating out of includes and in mechanism tables/CryptoFactory.


148-172: ML-DSA object instantiation via newP11Object(...) is wired correctly under WITH_ML_DSA.


840-844: Mechanism string registration for CKM_ML_DSA(_KEY_PAIR_GEN) looks good. (The std::out_of_range catch tweak is fine too.)

Also applies to: 886-889


949-952: C_GetMechanismInfo: ML-DSA min/max sizing + flags look consistent with “sizes are bytes” intent. Please double-check the AsymAlgo::MLDSA backend returns min/max in bytes as expected.

Also applies to: 1052-1061, 1352-1364


1151-1154: Generic secret key max size change is consistent with generateGeneric() validation.


5789-5793: Extra debug logging on signature length mismatch is fine, but consider verbosity. This will be helpful for ML-DSA bring-up; just ensure debug logs don’t become too noisy in common failure paths.

Also applies to: 5973-5977


6284-6288: C_GenerateKeyPair ML-DSA routing looks correct (type checks + generateMLDSA).

Also applies to: 6316-6318, 6341-6343, 6416-6425


7013-7017: ML-DSA wrap/unwrap integration via MLDSAUtil::{get,set}*Key looks consistent. Please verify the PKCS#8 encode/decode roundtrip for ML-DSA works across the supported OpenSSL provider setup.

Also applies to: 7058-7061, 7738-7743


10135-10393: generateMLDSA(...) implementation looks consistent with existing keygen patterns (param-set validation, transactional writes, cleanup). Nice use of CKP_ML_DSA_* constants and CKR_PARAMETER_SET_NOT_SUPPORTED (matches project preference per learnings).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@CMAKE-NOTES.md`:
- Around line 11-15: The MD010 lint failure is caused by hard tabs in the
CMAKE-NOTES.md lines listing cmake flags (e.g., the lines starting with
-DBUILD_TESTS=ON, -DDISABLE_NON_PAGED_MEMORY=ON, -DENABLE_EDDSA=ON,
-DENABLE_MLDSA=ON, -DWITH_MIGRATE=ON); replace each hard tab character with
spaces (use consistent spacing, e.g., one space between flag and description or
align columns with spaces) so the file no longer contains tabs and satisfies
markdownlint MD010.

In `@CMAKE-WIN-NOTES.md`:
- Line 55: The markdown line containing the CMake option "-DENABLE_MLDSA=ON     
Enable support for ML-DSA" uses a hard tab that triggers MD010; replace the hard
tab with spaces so the option and its description are separated by spaces (e.g.,
align with spaces) to satisfy markdownlint, updating the line that references
ENABLE_MLDSA accordingly.

In `@README.md`:
- Line 85: Replace the hard tab characters in the README line containing the CLI
option "--enable-mldsa" so it uses spaces instead (e.g., align the description
with spaces instead of a tab) to satisfy MD010; update the line that currently
reads "--enable-mldsa\t\tEnable support for ML-DSA (default detect)" to use
space characters between the flag and its description.

In `@src/lib/crypto/MLDSAParameters.h`:
- Around line 68-69: The private member `parameterSet` in class MLDSAParameters
is uninitialized and may be read by getParameterSet() before setParameterSet()
is called; initialize `parameterSet` to a sensible default (e.g., 0) in the
MLDSAParameters constructor(s) so all constructors set this field, or initialize
it inline where declared, and ensure getParameterSet() then returns a
well-defined value.

In `@src/lib/session_mgr/Session.cpp`:
- Around line 360-369: When handling MLDSA in Session::… (mechanism ==
AsymMech::Type::MLDSA) validate the embedded SIGN_ADDITIONAL_CONTEXT before
adjusting pointers: after casting to SIGN_ADDITIONAL_CONTEXT* sac, ensure
sac->contextLen is non-negative and that sizeof(SIGN_ADDITIONAL_CONTEXT) +
sac->contextLen <= inParamLen; only then set sac->contextData =
reinterpret_cast<unsigned char*>(sac + 1). If the check fails treat the input as
invalid (return an error/abort processing) instead of repointing to avoid
out-of-bounds reads from param.
♻️ Duplicate comments (4)
.github/workflows/ci.yml (1)

97-98: Avoid hiding build/install errors in CI logs (Line 97-98).

Redirecting output to files only makes failures harder to diagnose in Actions logs.

src/lib/crypto/OSSLMLDSAPrivateKey.h (1)

67-68: Fix include-guard closing comment typo.

The closing comment still references the wrong guard name.

🛠️ Proposed fix
-#endif // !_SOFTHSM_V2_OSSLECPRIVATEKEY_H
+#endif // !_SOFTHSM_V2_OSSLMLDSAPRIVATEKEY_H
src/lib/crypto/OSSLMLDSA.cpp (2)

118-141: Fix OpenSSL sign/verify API pairing (message vs one‑shot).

EVP_PKEY_sign_message_init / EVP_PKEY_verify_message_init are streaming APIs but are paired here with EVP_PKEY_sign / EVP_PKEY_verify. That mix is undefined and can ignore parameters or fail unexpectedly. Either switch to the streaming update/final sequence or use the one‑shot init APIs (*_init_ex) that match EVP_PKEY_sign/verify. Based on learnings, ...

🔧 Minimal one‑shot pairing fix (use *_init_ex)
-    initRv = EVP_PKEY_sign_message_init(sctx, sig_alg, params);
+    initRv = EVP_PKEY_sign_init_ex(sctx, sig_alg, params);
 ...
-    initRv = EVP_PKEY_sign_message_init(sctx, sig_alg, NULL);
+    initRv = EVP_PKEY_sign_init_ex(sctx, sig_alg, NULL);
-    initRv = EVP_PKEY_verify_message_init(vctx, sig_alg, params);
+    initRv = EVP_PKEY_verify_init_ex(vctx, sig_alg, params);
 ...
-    initRv = EVP_PKEY_verify_message_init(vctx, sig_alg, NULL);
+    initRv = EVP_PKEY_verify_init_ex(vctx, sig_alg, NULL);

Also applies to: 280-296


74-83: Guard contextData when contextLen > 0.

If contextLen is non‑zero but contextData is NULL, OSSL_PARAM_construct_octet_string will dereference a NULL pointer. Add an explicit NULL check in both sign and verify.

🩹 Suggested guard (apply in both blocks)
-        if (contextSize > 0) {
+        if (contextSize > 0) {
+            if (additionalContext->contextData == NULL) {
+                ERROR_MSG("Invalid parameters, context pointer is NULL");
+                return false;
+            }
             if (contextSize > 255) {
                 ERROR_MSG("Invalid parameters, context length > 255");
                 return false;
             }

Also applies to: 232-241

🧹 Nitpick comments (5)
.github/workflows/ci.yml (1)

96-100: Verify OpenSSL libdir matches the hardcoded lib64 paths.

./config doesn’t explicitly set --libdir, yet LDFLAGS/PKG_CONFIG_PATH target lib64. If OpenSSL installs into lib by default, ln -sf lib64 lib won’t create /usr/local/openssl-3.5/lib64, and the build may fail to locate libs. Please confirm the actual install layout on ubuntu-24.04 and align it explicitly (e.g., set --libdir=lib64, or switch paths to lib and link lib64 -> lib).

🔧 Example alignment (explicit lib64)
-          ./config shared zlib no-ssl3 no-weak-ssl-ciphers --prefix=${{ env.OPENSSL_INSTALL_DIR }} --openssldir=${{ env.OPENSSL_INSTALL_DIR }}
+          ./config shared zlib no-ssl3 no-weak-ssl-ciphers --prefix=${{ env.OPENSSL_INSTALL_DIR }} --openssldir=${{ env.OPENSSL_INSTALL_DIR }} --libdir=lib64
src/lib/crypto/OSSLMLDSAKeyPair.cpp (1)

9-9: Consider removing unused include.

log.h is included but no logging macros (ERROR_MSG, DEBUG_MSG, etc.) are used in this file.

Suggested fix
 `#include` "config.h"
 `#ifdef` WITH_ML_DSA
-#include "log.h"
 `#include` "OSSLMLDSAKeyPair.h"
src/lib/crypto/OSSLMLDSAPrivateKey.cpp (1)

180-182: Minor: Inconsistent indentation.

Lines 181-182 use spaces while surrounding code uses tabs. Consider aligning with project style.

Suggested fix
 	int selection = 0;
-    EVP_PKEY_CTX *ctx = NULL;
-    OSSL_PARAM params[3], *p = params;
+	EVP_PKEY_CTX *ctx = NULL;
+	OSSL_PARAM params[3], *p = params;
src/lib/crypto/MLDSAParameters.h (1)

10-11: Consider removing unused includes.

<tuple> and <stdexcept> appear unused in this header. If they're needed in the implementation file, include them there instead.

src/lib/crypto/OSSLMLDSA.h (1)

20-24: Minor indentation inconsistency.

Lines 21-22 use spaces while other lines use tabs. Consider aligning for consistency.

Proposed fix
 	// Signing functions
-    virtual bool sign(PrivateKey *privateKey, const ByteString &dataToSign, ByteString &signature, const AsymMech::Type mechanism, const void *param = NULL, const size_t paramLen = 0);
-    virtual bool signInit(PrivateKey* privateKey, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0);
+	virtual bool sign(PrivateKey *privateKey, const ByteString &dataToSign, ByteString &signature, const AsymMech::Type mechanism, const void *param = NULL, const size_t paramLen = 0);
+	virtual bool signInit(PrivateKey* privateKey, const AsymMech::Type mechanism, const void* param = NULL, const size_t paramLen = 0);
 	virtual bool signUpdate(const ByteString& dataToSign);

Comment on lines 11 to 15
-DBUILD_TESTS=ON Compile tests along with libraries
-DDISABLE_NON_PAGED_MEMORY=ON Disable non-paged memory for secure storage
-DENABLE_EDDSA=ON Enable support for EDDSA
-DENABLE_MLDSA=ON Enable support for ML-DSA
-DWITH_MIGRATE=ON Build migration tool
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace hard tabs to satisfy MD010.

Static analysis flags hard tabs on the new line. Please switch to spaces to keep markdownlint clean.

🧹 Suggested fix
-	-DENABLE_MLDSA=ON		Enable support for ML-DSA
+    -DENABLE_MLDSA=ON        Enable support for ML-DSA
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

11-11: Hard tabs
Column: 1

(MD010, no-hard-tabs)


11-11: Hard tabs
Column: 18

(MD010, no-hard-tabs)


12-12: Hard tabs
Column: 1

(MD010, no-hard-tabs)


12-12: Hard tabs
Column: 31

(MD010, no-hard-tabs)


13-13: Hard tabs
Column: 1

(MD010, no-hard-tabs)


13-13: Hard tabs
Column: 19

(MD010, no-hard-tabs)


14-14: Hard tabs
Column: 1

(MD010, no-hard-tabs)


14-14: Hard tabs
Column: 19

(MD010, no-hard-tabs)


15-15: Hard tabs
Column: 1

(MD010, no-hard-tabs)


15-15: Hard tabs
Column: 19

(MD010, no-hard-tabs)

🤖 Prompt for AI Agents
In `@CMAKE-NOTES.md` around lines 11 - 15, The MD010 lint failure is caused by
hard tabs in the CMAKE-NOTES.md lines listing cmake flags (e.g., the lines
starting with -DBUILD_TESTS=ON, -DDISABLE_NON_PAGED_MEMORY=ON,
-DENABLE_EDDSA=ON, -DENABLE_MLDSA=ON, -DWITH_MIGRATE=ON); replace each hard tab
character with spaces (use consistent spacing, e.g., one space between flag and
description or align columns with spaces) so the file no longer contains tabs
and satisfies markdownlint MD010.

Comment on lines +360 to +369
#ifdef WITH_ML_DSA
// Fix up SIGN_ADDITIONAL_CONTEXT pointer after deep copy
if (mechanism == AsymMech::Type::MLDSA && inParamLen > sizeof(SIGN_ADDITIONAL_CONTEXT))
{
SIGN_ADDITIONAL_CONTEXT* sac = reinterpret_cast<SIGN_ADDITIONAL_CONTEXT*>(param);
if (sac->contextLen > 0 && sac->contextData != NULL)
{
// Adjust contextData to point into the new copied blob
sac->contextData = reinterpret_cast<unsigned char*>(sac + 1);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate contextLen against inParamLen before repointing.

If contextLen exceeds the copied blob size, downstream reads can run past param. Add a bounds check and treat mismatched lengths as invalid input.

🔧 Proposed fix
 `#ifdef` WITH_ML_DSA
 		// Fix up SIGN_ADDITIONAL_CONTEXT pointer after deep copy
-		if (mechanism == AsymMech::Type::MLDSA && inParamLen > sizeof(SIGN_ADDITIONAL_CONTEXT))
+		if (mechanism == AsymMech::Type::MLDSA && inParamLen >= sizeof(SIGN_ADDITIONAL_CONTEXT))
 		{
 			SIGN_ADDITIONAL_CONTEXT* sac = reinterpret_cast<SIGN_ADDITIONAL_CONTEXT*>(param);
-			if (sac->contextLen > 0 && sac->contextData != NULL)
+			if (sac->contextLen > 0)
 			{
+				const size_t expected = sizeof(SIGN_ADDITIONAL_CONTEXT) + sac->contextLen;
+				if (inParamLen < expected) {
+					free(param);
+					param = NULL;
+					paramLen = 0;
+					return;
+				}
 				// Adjust contextData to point into the new copied blob
 				sac->contextData = reinterpret_cast<unsigned char*>(sac + 1);
+			}
+			else
+			{
+				sac->contextData = NULL;
 			}
 		}
 `#endif`
🤖 Prompt for AI Agents
In `@src/lib/session_mgr/Session.cpp` around lines 360 - 369, When handling MLDSA
in Session::… (mechanism == AsymMech::Type::MLDSA) validate the embedded
SIGN_ADDITIONAL_CONTEXT before adjusting pointers: after casting to
SIGN_ADDITIONAL_CONTEXT* sac, ensure sac->contextLen is non-negative and that
sizeof(SIGN_ADDITIONAL_CONTEXT) + sac->contextLen <= inParamLen; only then set
sac->contextData = reinterpret_cast<unsigned char*>(sac + 1). If the check fails
treat the input as invalid (return an error/abort processing) instead of
repointing to avoid out-of-bounds reads from param.

Applied RabbitAI suggested fixes
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/lib/crypto/AsymmetricAlgorithm.h`:
- Around line 144-157: The constructor SIGN_ADDITIONAL_CONTEXT throws
std::bad_alloc but the header doesn't include the declaration; add an `#include`
<new> to the top of the AsymmetricAlgorithm.h header so std::bad_alloc is
declared (ensure the include is added alongside the other standard includes
before the SIGN_ADDITIONAL_CONTEXT definition).
♻️ Duplicate comments (7)
README.md (1)

85-85: Hard tabs still present (duplicate issue).

This line was previously flagged for MD010 violations (hard tabs). The issue remains unresolved. Please replace the hard tabs with spaces as suggested in the earlier review.

src/lib/session_mgr/Session.cpp (1)

345-369: Validate ML‑DSA context length against copied buffer size.
Without a bounds check, a crafted contextLen can cause downstream reads past param. Based on learnings, add a length guard before repointing.

🔧 Suggested fix
 `#ifdef` WITH_ML_DSA
 		// Fix up SIGN_ADDITIONAL_CONTEXT pointer after deep copy
-		if (mechanism == AsymMech::Type::MLDSA && inParamLen > sizeof(SIGN_ADDITIONAL_CONTEXT))
+		if (mechanism == AsymMech::Type::MLDSA && inParamLen >= sizeof(SIGN_ADDITIONAL_CONTEXT))
 		{
 			SIGN_ADDITIONAL_CONTEXT* sac = reinterpret_cast<SIGN_ADDITIONAL_CONTEXT*>(param);
-			if (sac->contextLen > 0 && sac->contextData != NULL)
+			if (sac->contextLen > 0)
 			{
+				const size_t expected = sizeof(SIGN_ADDITIONAL_CONTEXT) + sac->contextLen;
+				if (inParamLen < expected) {
+					free(param);
+					param = NULL;
+					paramLen = 0;
+					return;
+				}
 				// Adjust contextData to point into the new copied blob
 				sac->contextData = reinterpret_cast<unsigned char*>(sac + 1);
+			}
+			else
+			{
+				sac->contextData = NULL;
 			}
 		}
 `#endif`
src/lib/crypto/OSSLMLDSA.cpp (2)

72-84: Guard against NULL contextData when contextLen > 0.

If contextLen > 0 but contextData is NULL, OSSL_PARAM_construct_octet_string will dereference a NULL pointer. Add a guard in both sign and verify paths.

🐛 Suggested fix
 	if (additionalContext != NULL) {
 		Hedge::Type type = additionalContext->hedgeType;
 		size_t contextSize = additionalContext->contextLen;
 		if (contextSize > 0) {
+			if (additionalContext->contextData == NULL) {
+				ERROR_MSG("Invalid parameters, context pointer is NULL");
+				return false;
+			}
 			if (contextSize > 255) {
 				ERROR_MSG("Invalid parameters, context length > 255");
 				return false;
 			}
 			const unsigned char* contextAsChars = additionalContext->contextData;
 			*p++ = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, (unsigned char*)contextAsChars, contextSize);
 		}
 	if (additionalContext != NULL) {
 		Hedge::Type type = additionalContext->hedgeType;
 		size_t contextSize = additionalContext->contextLen;
 		if (contextSize > 0) {
+			if (additionalContext->contextData == NULL) {
+				ERROR_MSG("Invalid parameters, context pointer is NULL");
+				return false;
+			}
 			if (contextSize > 255) {
 				ERROR_MSG("Invalid parameters, context length > 255");
 				return false;
 			}
 			const unsigned char* contextAsChars = additionalContext->contextData;
 			*p++ = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, (unsigned char*)contextAsChars, contextSize);
 		}

Also applies to: 230-242


118-141: Don’t mix message-init with one‑shot sign/verify APIs.

EVP_PKEY_sign_message_init/EVP_PKEY_verify_message_init must be paired with the message update/final API. Using EVP_PKEY_sign/EVP_PKEY_verify after message-init is undefined and can ignore the ML‑DSA context parameters.

🛠️ Suggested fix (message update/final flow)
-    /* Calculate the required size for the signature by passing a NULL buffer. */
-    if (EVP_PKEY_sign(sctx, NULL, &len, dataToSign.const_byte_str(), dataToSign.size()) <= 0) {
-		ERROR_MSG("ML-DSA sign size query failed (0x%08lX)", ERR_get_error());
-		EVP_SIGNATURE_free(sig_alg);
-		EVP_PKEY_CTX_free(sctx);
-		return false;
-	}
-	signature.resize(len);
-    if (EVP_PKEY_sign(sctx, &signature[0], &len, dataToSign.const_byte_str(), dataToSign.size()) <= 0) {
+    if (EVP_PKEY_sign_message_update(sctx, dataToSign.const_byte_str(), dataToSign.size()) <= 0) {
+		ERROR_MSG("ML-DSA sign update failed (0x%08lX)", ERR_get_error());
+		EVP_SIGNATURE_free(sig_alg);
+		EVP_PKEY_CTX_free(sctx);
+		return false;
+	}
+    if (EVP_PKEY_sign_message_final(sctx, NULL, &len) <= 0) {
+		ERROR_MSG("ML-DSA sign final size query failed (0x%08lX)", ERR_get_error());
+		EVP_SIGNATURE_free(sig_alg);
+		EVP_PKEY_CTX_free(sctx);
+		return false;
+	}
+	signature.resize(len);
+    if (EVP_PKEY_sign_message_final(sctx, &signature[0], &len) <= 0) {
 		ERROR_MSG("ML-DSA sign failed (0x%08lX)", ERR_get_error());
 		EVP_SIGNATURE_free(sig_alg);
 		EVP_PKEY_CTX_free(sctx);
 		return false;
 	}
+	signature.resize(len);
-	int verifyRV = EVP_PKEY_verify(vctx, signature.const_byte_str(), signature.size(),
-                                            originalData.const_byte_str(), originalData.size());
+	if (EVP_PKEY_CTX_set_signature(vctx, signature.const_byte_str(), signature.size()) <= 0) {
+		ERROR_MSG("ML-DSA set_signature failed (0x%08lX)", ERR_get_error());
+		EVP_PKEY_CTX_free(vctx);
+		EVP_SIGNATURE_free(sig_alg);
+		return false;
+	}
+	if (EVP_PKEY_verify_message_update(vctx, originalData.const_byte_str(), originalData.size()) <= 0) {
+		ERROR_MSG("ML-DSA verify update failed (0x%08lX)", ERR_get_error());
+		EVP_PKEY_CTX_free(vctx);
+		EVP_SIGNATURE_free(sig_alg);
+		return false;
+	}
+	int verifyRV = EVP_PKEY_verify_message_final(vctx);
 	EVP_PKEY_CTX_free(vctx);
 	EVP_SIGNATURE_free(sig_alg);
OpenSSL 3.5 EVP_PKEY_sign_message_init usage with EVP_PKEY_sign_message_update and EVP_PKEY_sign_message_final

Based on learnings, the message-init APIs must be paired with update/final, not the one‑shot sign/verify calls.

Also applies to: 280-296

src/lib/SoftHSM.cpp (3)

5524-5553: Same paramLen/Session::setParameters coupling as sign‑init.
See the sign‑init comment about verifying pointer rebasing for ML‑DSA params.


6526-6575: CBC wrap still uses blocksize == 0 for CKM_AES_CBC / CKM_DES3_CBC.
Non‑PAD CBC cases leave blocksize unset, so IV copy uses a 0‑byte IV and CBC init is incorrect. This was previously flagged.

🐛 Suggested fix (same as prior)
case CKM_AES_CBC:
-    algo = SymAlgo::AES;
+    algo = SymAlgo::AES;
+    blocksize = 16;
     break;
...
case CKM_DES3_CBC:
-    algo = SymAlgo::DES3;
+    algo = SymAlgo::DES3;
+    blocksize = 8;
     break;

10154-10383: Guard CKA_PARAMETER_SET pointer before dereference.
pPublicKeyTemplate[i].pValue is dereferenced without a NULL check. A malformed template can still hit this path even if ulValueLen matches, causing a NULL deref. This has been raised previously.

🛠️ Proposed fix
case CKA_PARAMETER_SET:
-    if (pPublicKeyTemplate[i].ulValueLen != sizeof(CK_ULONG)) {
+    if (pPublicKeyTemplate[i].pValue == NULL_PTR ||
+        pPublicKeyTemplate[i].ulValueLen != sizeof(CK_ULONG)) {
         INFO_MSG("CKA_PARAMETER_SET must be sizeof(CK_ULONG)");
         return CKR_ATTRIBUTE_VALUE_INVALID;
     }
     paramSet = *(CK_ULONG*)pPublicKeyTemplate[i].pValue;
     break;

Comment on lines +144 to +157
SIGN_ADDITIONAL_CONTEXT(Hedge::Type ht, const unsigned char* buf, size_t len)
: hedgeType(ht), contextLen(0), contextData(NULL), ownsContextMemory(false)
{
if (len > 0) {
contextData = static_cast<unsigned char*>(std::malloc(len));
if (contextData) {
std::memcpy(contextData, buf, len);
contextLen = len;
ownsContextMemory = true;
}
else
{
throw std::bad_alloc();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file to see current includes and the code in question
cat -n src/lib/crypto/AsymmetricAlgorithm.h | head -160

Repository: softhsm/SoftHSMv2

Length of output: 5151


Add <new> to declare std::bad_alloc.
This header throws std::bad_alloc (line 156) but doesn't include <new>. Relying on transitive includes can break builds.

🔧 Suggested fix
 `#include` <cstdlib>
 `#include` <cstring>
+#include <new>
🤖 Prompt for AI Agents
In `@src/lib/crypto/AsymmetricAlgorithm.h` around lines 144 - 157, The constructor
SIGN_ADDITIONAL_CONTEXT throws std::bad_alloc but the header doesn't include the
declaration; add an `#include` <new> to the top of the AsymmetricAlgorithm.h
header so std::bad_alloc is declared (ensure the include is added alongside the
other standard includes before the SIGN_ADDITIONAL_CONTEXT definition).

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.

7 participants