From 526ced13f362eaf4b5f5e172d249abc7a7bc48e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 24 Mar 2026 19:52:21 +0100 Subject: [PATCH 1/2] Add ML-KEM (FIPS 203) post-quantum KEM support Adds support for ML-KEM (Kyber, FIPS 203) key encapsulation mechanism, following the same patterns established by the ML-DSA integration. Disabled by default; enable with --enable-mlkem (autotools) or -DWOLFPKCS11_MLKEM=yes (CMake). Enabling ML-KEM automatically enables PKCS#11 v3.2 support. Capabilities added: - Key generation (CKM_ML_KEM_KEY_PAIR_GEN) for KEM-512/768/1024 - Key import/export via C_CreateObject / C_GetAttributeValue - Token persistence (WOLFPKCS11_STORE_MLKEMKEY_PRIV/PUB, 0x0E/0x0F) - Encapsulation (C_EncapsulateKey / CKM_ML_KEM) - Decapsulation (C_DecapsulateKey / CKM_ML_KEM) - New PKCS#11 constants: CKK_ML_KEM, CKM_ML_KEM_KEY_PAIR_GEN, CKM_ML_KEM, CKA_ENCAPSULATE, CKA_DECAPSULATE, CKF_ENCAPSULATE, CKF_DECAPSULATE, CKP_ML_KEM_512/768/1024 - New internal flags: WP11_FLAG_ENCAPSULATE, WP11_FLAG_DECAPSULATE Tests added to tests/pkcs11v3test.c (inside WOLFPKCS11_MLKEM guard): - Key generation in session and with ID - Token key persistence round-trip - Export/reimport round-trip (exercises import path) - Encapsulate/decapsulate shared-secret equality check - Wrong-key implicit-rejection test --- .github/workflows/clang-tidy.yml | 2 +- .github/workflows/cmake.yml | 2 +- .github/workflows/sanitizer-tests.yml | 2 +- .github/workflows/unit-test.yml | 4 + .gitignore | 5 + CMakeLists.txt | 32 +- README.md | 13 +- cmake/options.h.in | 2 + configure.ac | 22 + src/crypto.c | 299 ++++++- src/internal.c | 874 +++++++++++++++++++- src/slot.c | 22 + tests/pkcs11v3test.c | 1099 ++++++++++++++++++++++++- wolfpkcs11/internal.h | 15 + wolfpkcs11/pkcs11.h | 35 +- wolfpkcs11/store.h | 2 + 16 files changed, 2365 insertions(+), 65 deletions(-) diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 7544890e..5b5498a7 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -43,7 +43,7 @@ jobs: - name: "NSS+TPM Build" configure_flags: "--enable-nss --enable-tpm" - name: "PKCS#11 V3.2 PQC Build" - configure_flags: "--enable-pkcs11v32 --enable-mldsa" + configure_flags: "--enable-pkcs11v32 --enable-mldsa --enable-mlkem" steps: # Checkout wolfPKCS11 diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index a4004431..67ba3d29 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -62,7 +62,7 @@ jobs: -DWOLFPKCS11_AESKEYWRAP:BOOL=yes -DWOLFPKCS11_AESCTR:BOOL=yes -DWOLFPKCS11_AESCCM:BOOL=yes \ -DWOLFPKCS11_AESECB:BOOL=yes -DWOLFPKCS11_AESCTS:BOOL=yes -DWOLFPKCS11_AESCMAC:BOOL=yes \ -DWOLFPKCS11_PBKDF2:BOOL=yes -DWOLFPKCS11_SHA3:BOOL=yes -DWOLFPKCS11_PKCS11_V3_0:BOOL=yes \ - -DWOLFPKCS11_PKCS11_V3_2:BOOL=yes -DWOLFPKCS11_MLDSA:BOOL=yes \ + -DWOLFPKCS11_PKCS11_V3_2:BOOL=yes -DWOLFPKCS11_MLDSA:BOOL=yes -DWOLFPKCS11_MLKEM:BOOL=yes \ -DCMAKE_MODULE_PATH="$GITHUB_WORKSPACE/install/${CMAKE_INSTALL_LIBDIR}" \ .. cmake --build . diff --git a/.github/workflows/sanitizer-tests.yml b/.github/workflows/sanitizer-tests.yml index a4e81919..21f5fb1f 100644 --- a/.github/workflows/sanitizer-tests.yml +++ b/.github/workflows/sanitizer-tests.yml @@ -26,7 +26,7 @@ jobs: - name: "NSS+TPM Build" configure_flags: "--enable-nss --enable-tpm" - name: "PKCS#11 V3.2 PQC Build" - configure_flags: "--enable-pkcs11v32 --enable-mldsa" + configure_flags: "--enable-pkcs11v32 --enable-mldsa --enable-mlkem" steps: #pull wolfPKCS11 diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index fb787964..1400247c 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -106,6 +106,10 @@ jobs: uses: ./.github/workflows/build-workflow.yml with: config: --enable-mldsa + mlkem: + uses: ./.github/workflows/build-workflow.yml + with: + config: --enable-mlkem debug: uses: ./.github/workflows/build-workflow.yml with: diff --git a/.gitignore b/.gitignore index afd172af..d7b57e74 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,9 @@ tests/rsa_session_persistence_test tests/debug_test tests/token_path_test tests/pkcs11v3test +tests/aes_cbc_pad_padding_test +tests/ecb_check_value_error_test +tests/find_objects_null_template_test examples/add_aes_key examples/add_hmac_key examples/add_rsa_key @@ -54,8 +57,10 @@ examples/obj_list examples/slot_info examples/token_info store/wp11* +store/cbc_pad_padding_test store/debug store/empty_pin_test +store/find_null_test store/object store/pkcs11mtt store/pkcs11test diff --git a/CMakeLists.txt b/CMakeLists.txt index 65e505d0..a868a2f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -474,12 +474,42 @@ endif() if(WOLFPKCS11_MLDSA) if(NOT WOLFPKCS11_PKCS11_V3_2) - message(FATAL_ERROR "ML-DSA requires PKCS#11 Version 3.2 support (enable WOLFPKCS11_PKCS11_V3_2)") + message(STATUS "ML-DSA requires PKCS#11 v3.2 support — enabling WOLFPKCS11_PKCS11_V3_2 automatically") + override_cache(WOLFPKCS11_PKCS11_V3_2 "yes") + if(NOT WOLFPKCS11_PKCS11_V3_0) + override_cache(WOLFPKCS11_PKCS11_V3_0 "yes") + list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_PKCS11_V3_0") + endif() + list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_PKCS11_V3_2") endif() list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_MLDSA") endif() +# ML-KEM +add_option("WOLFPKCS11_MLKEM" + "Enable wolfPKCS11 ML-KEM support (default: disabled)" + "no" "yes;no" +) + +if(NOT WOLFPKCS11_SHA3) + override_cache(WOLFPKCS11_MLKEM "no") +endif() + +if(WOLFPKCS11_MLKEM) + if(NOT WOLFPKCS11_PKCS11_V3_2) + message(STATUS "ML-KEM requires PKCS#11 v3.2 support — enabling WOLFPKCS11_PKCS11_V3_2 automatically") + override_cache(WOLFPKCS11_PKCS11_V3_2 "yes") + if(NOT WOLFPKCS11_PKCS11_V3_0) + override_cache(WOLFPKCS11_PKCS11_V3_0 "yes") + list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_PKCS11_V3_0") + endif() + list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_PKCS11_V3_2") + endif() + list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_MLKEM") +endif() + + # If wolfpkcs11/options.h exists, delete it to avoid # a mixup with build/wolfpkcs11/options.h. if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/wolfpkcs11/options.h") diff --git a/README.md b/README.md index bb92d9ef..b81bc0c5 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,16 @@ As ML-DSA is a feature of PKCS#11 version 3.2, support for that is required, too. Hence, to enable all in wolfPKCS11, add `--enable-pkcs11v32 --enable-mldsa` during the configure step. +### Optional: PQC ML-KEM Support + +To have ML-KEM support in wolfPKCS11, configure wolfSSL with ML-KEM (FIPS 203) +support enabled, either by adding `--enable-mlkem` to `./configure` or by +setting `WOLFPKCS11_MLKEM` to `yes` in CMake. + +As ML-KEM is a feature of PKCS#11 version 3.2, support for that is required, +too. Hence, to enable all in wolfPKCS11, add `--enable-pkcs11v32 --enable-mlkem` +during the configure step. + ### Build options and defines #### Define WOLFPKCS11_TPM_STORE @@ -207,7 +217,8 @@ cmake -DCMAKE_PREFIX_PATH=/path/to/wolfssl/install .. | `WOLFPKCS11_NSS` | `no` | NSS-specific modifications | | `WOLFPKCS11_PKCS11_V3_0` | `yes` | PKCS#11 v3.0 support | | `WOLFPKCS11_PKCS11_V3_2` | `no` | PKCS#11 v3.2 support | -| `WOLFPKCS11_MLDSA` | `no`| ML-DSA support | +| `WOLFPKCS11_MLDSA` | `no` | ML-DSA support | +| `WOLFPKCS11_MLKEM` | `no` | ML-KEM support | | `WOLFPKCS11_EXAMPLES` | `yes` | Build examples | | `WOLFPKCS11_TESTS` | `yes` | Build and register tests | | `WOLFPKCS11_COVERAGE` | `no` | Code coverage support | diff --git a/cmake/options.h.in b/cmake/options.h.in index c54f7b7f..85137980 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -94,6 +94,8 @@ extern "C" { #cmakedefine WOLFSSL_SHA3 #undef WOLFPKCS11_MLDSA #cmakedefine WOLFPKCS11_MLDSA +#undef WOLFPKCS11_MLKEM +#cmakedefine WOLFPKCS11_MLKEM #undef WOLFPKCS11_TPM #cmakedefine WOLFPKCS11_TPM #undef WOLFPKCS11_NSS diff --git a/configure.ac b/configure.ac index 954380c2..937e7a95 100644 --- a/configure.ac +++ b/configure.ac @@ -537,6 +537,27 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFPKCS11_MLDSA" fi +AC_ARG_ENABLE([mlkem], + [AS_HELP_STRING([--enable-mlkem],[Enable ML-KEM (default: disabled)])], + [ ENABLED_MLKEM=$enableval ], + [ ENABLED_MLKEM=no ] + ) + +if test "$ENABLED_SHA3" = "no" +then + echo "ML-KEM requires SHA-3 support (disabled), disabling ML-KEM" + ENABLED_MLKEM=no +fi + +if test "$ENABLED_MLKEM" = "yes" +then + if test "$ENABLED_PKCS11V3_2" = "no"; then + ENABLED_PKCS11V3_2=yes + AM_CFLAGS="$AM_CFLAGS -DWOLFPKCS11_PKCS11_V3_2" + fi + AM_CFLAGS="$AM_CFLAGS -DWOLFPKCS11_MLKEM" +fi + AM_CONDITIONAL([BUILD_STATIC],[test "x$enable_shared" = "xno"]) @@ -725,6 +746,7 @@ echo " * DH: $ENABLED_DH" echo " * ECC: $ENABLED_ECC" echo " * HKDF: $ENABLED_HKDF" echo " * ML-DSA: $ENABLED_MLDSA" +echo " * ML-KEM: $ENABLED_MLKEM" echo " * NSS modifications: $ENABLED_NSS" echo " * Default token path: $WOLFPKCS11_DEFAULT_TOKEN_PATH" echo " * PKCS#11 Version 3.0: $ENABLED_PKCS11V3_0" diff --git a/src/crypto.c b/src/crypto.c index 0326e02c..021364e4 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -109,6 +109,17 @@ static CK_ATTRIBUTE_TYPE dhKeyParams[] = { #define DH_KEY_PARAMS_CNT (sizeof(dhKeyParams)/sizeof(*dhKeyParams)) #endif +#ifdef WOLFPKCS11_MLKEM +/* ML-KEM key data attributes. */ +static CK_ATTRIBUTE_TYPE mlKemKeyParams[] = { + CKA_PARAMETER_SET, + CKA_SEED, + CKA_VALUE +}; +/* Count of ML-KEM key data attributes. */ +#define MLKEM_KEY_PARAMS_CNT (sizeof(mlKemKeyParams)/sizeof(*mlKemKeyParams)) +#endif + /* Secret key data attributes. */ static CK_ATTRIBUTE_TYPE secretKeyParams[] = { CKA_VALUE_LEN, @@ -237,6 +248,8 @@ static AttributeType attrType[] = { { CKA_CHECK_VALUE, ATTR_TYPE_DATA }, { CKA_PARAMETER_SET, ATTR_TYPE_ULONG }, { CKA_SEED, ATTR_TYPE_DATA }, + { CKA_ENCAPSULATE, ATTR_TYPE_BOOL }, + { CKA_DECAPSULATE, ATTR_TYPE_BOOL }, #ifdef WOLFPKCS11_NSS { CKA_CERT_SHA1_HASH, ATTR_TYPE_DATA }, { CKA_CERT_MD5_HASH, ATTR_TYPE_DATA }, @@ -476,6 +489,14 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, wrap = CK_FALSE; sign = CK_TRUE; break; + case CKK_ML_KEM: + derive = CK_FALSE; + verify = CK_FALSE; + encrypt = CK_FALSE; + recover = CK_FALSE; + wrap = CK_FALSE; + sign = CK_FALSE; + break; } /* Defaults if not set */ @@ -495,6 +516,11 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_DERIVE, derive, pTemplate, ulCount); +#ifdef WOLFPKCS11_MLKEM + if (ret == CKR_OK && type == CKK_ML_KEM) + ret = SetIfNotFound(obj, CKA_ENCAPSULATE, trueVal, pTemplate, + ulCount); +#endif break; case CKO_SECRET_KEY: if (ret == CKR_OK) @@ -530,6 +556,11 @@ static CK_RV SetAttributeDefaults(WP11_Object* obj, CK_OBJECT_CLASS keyType, if (ret == CKR_OK) ret = SetIfNotFound(obj, CKA_DERIVE, derive, pTemplate, ulCount); +#ifdef WOLFPKCS11_MLKEM + if (ret == CKR_OK && type == CKK_ML_KEM) + ret = SetIfNotFound(obj, CKA_DECAPSULATE, trueVal, pTemplate, + ulCount); +#endif break; } @@ -635,6 +666,12 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, cnt = DH_KEY_PARAMS_CNT; break; #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + attrs = mlKemKeyParams; + cnt = MLKEM_KEY_PARAMS_CNT; + break; + #endif #ifdef WOLFPKCS11_HKDF case CKK_HKDF: #endif @@ -695,15 +732,20 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, break; #endif #ifdef WOLFPKCS11_MLDSA - case CKK_ML_DSA: - ret = WP11_Object_SetMldsaKey(obj, data, len); - break; + case CKK_ML_DSA: + ret = WP11_Object_SetMldsaKey(obj, data, len); + break; #endif #ifndef NO_DH case CKK_DH: ret = WP11_Object_SetDhKey(obj, data, len); break; #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + ret = WP11_Object_SetMlKemKey(obj, data, len); + break; + #endif #ifndef NO_AES case CKK_AES: #endif @@ -1077,7 +1119,8 @@ static CK_RV CreateObject(WP11_Session* session, CK_ATTRIBUTE_PTR pTemplate, if (objType != CKK_RSA && objType != CKK_EC && objType != CKK_DH && objType != CKK_AES && objType != CKK_HKDF && - objType != CKK_GENERIC_SECRET && objType != CKK_ML_DSA) { + objType != CKK_GENERIC_SECRET && objType != CKK_ML_DSA && + objType != CKK_ML_KEM) { return CKR_ATTRIBUTE_VALUE_INVALID; } } @@ -6969,6 +7012,96 @@ CK_RV C_GenerateKeyPair(CK_SESSION_HANDLE hSession, } break; #endif +#ifdef WOLFPKCS11_MLKEM + case CKM_ML_KEM_KEY_PAIR_GEN: + if (pMechanism->pParameter != NULL || + pMechanism->ulParameterLen != 0) { + return CKR_MECHANISM_PARAM_INVALID; + } + + *phPublicKey = *phPrivateKey = CK_INVALID_HANDLE; + + rv = NewObject(session, CKK_ML_KEM, CKO_PUBLIC_KEY, + pPublicKeyTemplate, ulPublicKeyAttributeCount, &pub); + if (rv == CKR_OK) { + CK_ATTRIBUTE_PTR privTmplCpy = NULL; + if (ulPrivateKeyAttributeCount > + (ULONG_MAX / sizeof(CK_ATTRIBUTE)) - 1) { + rv = CKR_ARGUMENTS_BAD; + } + else { + /* Copy the CKA_PARAMETER_SET attribute from the public key + * template to the private key one to properly initialize the + * private key in NewObject() below. */ + privTmplCpy = (CK_ATTRIBUTE_PTR)XMALLOC( + (ulPrivateKeyAttributeCount + 1) * sizeof(CK_ATTRIBUTE), + NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (privTmplCpy == NULL) { + rv = CKR_HOST_MEMORY; + } + } + if (rv == CKR_OK) { + unsigned int i; + unsigned int privParamIdx = + (unsigned int)ulPrivateKeyAttributeCount; + unsigned int pubParamIdx = + (unsigned int)ulPublicKeyAttributeCount; + CK_ULONG newAttrCount; + /* Copy existing attributes; note any existing + * CKA_PARAMETER_SET in the private template. */ + for (i = 0; i < ulPrivateKeyAttributeCount; i++) { + privTmplCpy[i] = pPrivateKeyTemplate[i]; + if (pPrivateKeyTemplate[i].type == CKA_PARAMETER_SET && + privParamIdx == ulPrivateKeyAttributeCount) { + privParamIdx = i; + } + } + /* Find CKA_PARAMETER_SET in the public key template. */ + for (i = 0; i < ulPublicKeyAttributeCount; i++) { + if (pPublicKeyTemplate[i].type == CKA_PARAMETER_SET) { + pubParamIdx = i; + break; + } + } + if (pubParamIdx == ulPublicKeyAttributeCount) { + /* CKA_PARAMETER_SET is not found in the public key + * template */ + rv = CKR_TEMPLATE_INCOMPLETE; + } + else { + /* Ensure exactly one CKA_PARAMETER_SET in the private + * template: overwrite existing entry if present, + * otherwise append. */ + if (privParamIdx < ulPrivateKeyAttributeCount) { + privTmplCpy[privParamIdx].pValue = + pPublicKeyTemplate[pubParamIdx].pValue; + privTmplCpy[privParamIdx].ulValueLen = + pPublicKeyTemplate[pubParamIdx].ulValueLen; + newAttrCount = ulPrivateKeyAttributeCount; + } + else { + privTmplCpy[ulPrivateKeyAttributeCount].type = + CKA_PARAMETER_SET; + privTmplCpy[ulPrivateKeyAttributeCount].pValue = + pPublicKeyTemplate[pubParamIdx].pValue; + privTmplCpy[ulPrivateKeyAttributeCount].ulValueLen = + pPublicKeyTemplate[pubParamIdx].ulValueLen; + newAttrCount = ulPrivateKeyAttributeCount + 1; + } + rv = NewObject(session, CKK_ML_KEM, CKO_PRIVATE_KEY, + privTmplCpy, newAttrCount, &priv); + } + } + XFREE(privTmplCpy, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + if (rv == CKR_OK) { + ret = WP11_MlKem_GenerateKeyPair(pub, priv, + WP11_Session_GetSlot(session)); + if (ret != 0) + rv = CKR_FUNCTION_FAILED; + } + break; +#endif #ifndef NO_DH case CKM_DH_PKCS_KEY_PAIR_GEN: if (pMechanism->pParameter != NULL || @@ -8570,12 +8703,87 @@ CK_RV C_MessageVerifyFinal(CK_SESSION_HANDLE hSession) CK_RV C_EncapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hPublicKey, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey, - CK_BYTE_PTR pCiphertext, CK_ULONG_PTR pulCiphertextLen) + CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, + CK_ULONG_PTR pulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey) { +#ifdef WOLFPKCS11_MLKEM + int ret; + CK_RV rv = CKR_OK; + WP11_Session* session; + WP11_Object* pubObj = NULL; + WP11_Object* secretObj = NULL; + byte* derivedKey = NULL; + word32 keyLen = 0; + unsigned char* secretKeyData[2] = { NULL, NULL }; + CK_ULONG secretKeyLen[2] = { 0, 0 }; +#endif + if (!WP11_Library_IsInitialized()) return CKR_CRYPTOKI_NOT_INITIALIZED; +#ifdef WOLFPKCS11_MLKEM + if (WP11_Session_Get(hSession, &session) != 0) + return CKR_SESSION_HANDLE_INVALID; + if (pMechanism == NULL || pTemplate == NULL || phKey == NULL || + pulCiphertextLen == NULL) + return CKR_ARGUMENTS_BAD; + ret = WP11_Object_Find(session, hPublicKey, &pubObj); + if (ret != 0) + return CKR_OBJECT_HANDLE_INVALID; + if (WP11_Object_GetClass(pubObj) != CKO_PUBLIC_KEY) + return CKR_KEY_HANDLE_INVALID; + + switch (pMechanism->mechanism) { + case CKM_ML_KEM: + if (pMechanism->pParameter != NULL || + pMechanism->ulParameterLen != 0) { + return CKR_MECHANISM_PARAM_INVALID; + } + ret = WP11_MlKem_Encapsulate(pubObj, &derivedKey, &keyLen, + pCiphertext, pulCiphertextLen); + if (ret < 0) + rv = CKR_FUNCTION_FAILED; + else if (ret > 0) + rv = (CK_RV)ret; + break; + default: + return CKR_MECHANISM_INVALID; + } + + /* If the user called with an empty pCiphertext to query the ciphertext + * length, WP11_MlKem_Encapsulate() sets the size to pulCiphertextLen and + * returns 0. In this case, we have to exit early. */ + if (rv == CKR_OK && pCiphertext == NULL) + return CKR_OK; + + if (rv == CKR_OK) { + rv = CreateObject(session, pTemplate, ulAttributeCount, &secretObj); + if (rv == CKR_OK) { + secretKeyData[1] = derivedKey; + secretKeyLen[1] = keyLen; + ret = WP11_Object_SetSecretKey(secretObj, secretKeyData, + secretKeyLen); + if (ret != 0) + rv = CKR_FUNCTION_FAILED; + if (rv == CKR_OK) + rv = AddObject(session, secretObj, pTemplate, ulAttributeCount, + phKey); + if (rv == CKR_OK) + rv = SetInitialStates(secretObj); + } + if (rv != CKR_OK && secretObj != NULL) { + WP11_Object_Free(secretObj); + secretObj = NULL; + } + } + + if (derivedKey != NULL) { + wc_ForceZero(derivedKey, keyLen); + XFREE(derivedKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + return rv; +#else (void)hSession; (void)pMechanism; (void)hPublicKey; @@ -8584,18 +8792,87 @@ CK_RV C_EncapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, (void)phKey; (void)pCiphertext; (void)pulCiphertextLen; - return CKR_FUNCTION_NOT_SUPPORTED; +#endif } CK_RV C_DecapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hPrivateKey, CK_BYTE_PTR pCiphertext, - CK_ULONG ulCiphertextLen, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey) + CK_OBJECT_HANDLE hPrivateKey, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, + CK_ULONG ulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey) { +#ifdef WOLFPKCS11_MLKEM + int ret; + CK_RV rv = CKR_OK; + WP11_Session* session; + WP11_Object* privObj = NULL; + WP11_Object* secretObj = NULL; + byte* derivedKey = NULL; + word32 keyLen = 0; + unsigned char* secretKeyData[2] = { NULL, NULL }; + CK_ULONG secretKeyLen[2] = { 0, 0 }; +#endif + if (!WP11_Library_IsInitialized()) return CKR_CRYPTOKI_NOT_INITIALIZED; +#ifdef WOLFPKCS11_MLKEM + if (WP11_Session_Get(hSession, &session) != 0) + return CKR_SESSION_HANDLE_INVALID; + if (pMechanism == NULL || pCiphertext == NULL || pTemplate == NULL || + phKey == NULL) + return CKR_ARGUMENTS_BAD; + + ret = WP11_Object_Find(session, hPrivateKey, &privObj); + if (ret != 0) + return CKR_OBJECT_HANDLE_INVALID; + if (WP11_Object_GetClass(privObj) != CKO_PRIVATE_KEY) + return CKR_KEY_HANDLE_INVALID; + + switch (pMechanism->mechanism) { + case CKM_ML_KEM: + if (pMechanism->pParameter != NULL || + pMechanism->ulParameterLen != 0) { + return CKR_MECHANISM_PARAM_INVALID; + } + ret = WP11_MlKem_Decapsulate(privObj, &derivedKey, &keyLen, + pCiphertext, ulCiphertextLen); + if (ret < 0) + rv = CKR_FUNCTION_FAILED; + else if (ret > 0) + rv = (CK_RV)ret; + break; + default: + return CKR_MECHANISM_INVALID; + } + if (rv == CKR_OK) { + rv = CreateObject(session, pTemplate, ulAttributeCount, &secretObj); + if (rv == CKR_OK) { + secretKeyData[1] = derivedKey; + secretKeyLen[1] = keyLen; + ret = WP11_Object_SetSecretKey(secretObj, secretKeyData, + secretKeyLen); + if (ret != 0) + rv = CKR_FUNCTION_FAILED; + if (rv == CKR_OK) + rv = AddObject(session, secretObj, pTemplate, ulAttributeCount, + phKey); + if (rv == CKR_OK) + rv = SetInitialStates(secretObj); + } + if (rv != CKR_OK && secretObj != NULL) { + WP11_Object_Free(secretObj); + secretObj = NULL; + } + } + + if (derivedKey != NULL) { + wc_ForceZero(derivedKey, keyLen); + XFREE(derivedKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + return rv; +#else (void)hSession; (void)pMechanism; (void)hPrivateKey; @@ -8604,8 +8881,8 @@ CK_RV C_DecapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, (void)pTemplate; (void)ulAttributeCount; (void)phKey; - return CKR_FUNCTION_NOT_SUPPORTED; +#endif } CK_RV C_VerifySignatureInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, diff --git a/src/internal.c b/src/internal.c index 883796c0..f7cb8f63 100644 --- a/src/internal.c +++ b/src/internal.c @@ -42,6 +42,9 @@ #include #include #include +#ifdef WOLFPKCS11_MLKEM +#include +#endif #if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_CUSTOM_STORE) /* OS-specific includes for directory creation */ @@ -259,6 +262,9 @@ struct WP11_Object { #endif #ifndef NO_DH WP11_DhKey* dhKey; /* DH parameters object */ + #endif + #ifdef WOLFPKCS11_MLKEM + MlKemKey* mlKemKey; /* ML-KEM key object */ #endif WP11_Data* symmKey; /* Symmetric key object */ WP11_GenericData genericData; /* Generic data object */ @@ -1403,6 +1409,16 @@ static int wolfPKCS11_Store_Name(int type, CK_ULONG id1, CK_ULONG id2, char* nam ret = XSNPRINTF(name, nameLen, "%s/wp11_mldsakey_pub_%016lx_%016lx", str, id1, id2); break; +#ifdef WOLFPKCS11_MLKEM + case WOLFPKCS11_STORE_MLKEMKEY_PRIV: + ret = XSNPRINTF(name, nameLen, "%s/wp11_mlkemkey_priv_%016lx_%016lx", + str, id1, id2); + break; + case WOLFPKCS11_STORE_MLKEMKEY_PUB: + ret = XSNPRINTF(name, nameLen, "%s/wp11_mlkemkey_pub_%016lx_%016lx", + str, id1, id2); + break; +#endif default: ret = -1; @@ -2487,6 +2503,20 @@ int wp11_Object_AllocateTypeData(WP11_Object* object) } break; #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + if (object->data.mlKemKey == NULL) { + object->data.mlKemKey = (MlKemKey*)XMALLOC( + sizeof(MlKemKey), NULL, DYNAMIC_TYPE_KEY); + if (object->data.mlKemKey == NULL) { + ret = MEMORY_E; + } + else { + XMEMSET(object->data.mlKemKey, 0, sizeof(MlKemKey)); + } + } + break; + #endif #ifndef NO_DH case CKK_DH: if (object->data.dhKey == NULL) { @@ -4728,6 +4758,233 @@ static int wp11_Object_Store_DhKey(WP11_Object* object, int tokenId, int objId) } #endif /* !NO_DH */ +#ifdef WOLFPKCS11_MLKEM +static int MlKemKeyTryDecode(MlKemKey* key, int level, byte* data, word32 len, + int devId, CK_OBJECT_CLASS objClass) +{ + int ret; + + ret = wc_MlKemKey_Init(key, level, NULL, devId); + if (ret == 0) { + if (objClass == CKO_PRIVATE_KEY) { + ret = wc_MlKemKey_DecodePrivateKey(key, data, len); + } + else { + ret = wc_MlKemKey_DecodePublicKey(key, data, len); + } + } + + if (ret != 0) { + wc_MlKemKey_Free(key); + } + + return ret; +} + +/** + * Decode the ML-KEM key. + * + * Encoded private keys are encrypted. + * + * @param [in, out] object ML-KEM key object. + * @return 0 on success. + * @return -ve on failure. + */ +static int wp11_Object_Decode_MlKemKey(WP11_Object* object) +{ + int ret = 0; + + if (object->objClass == CKO_PRIVATE_KEY) { + unsigned char* der; + int len; + + if (object->keyDataLen < AES_BLOCK_SIZE) + return BAD_FUNC_ARG; + len = object->keyDataLen - AES_BLOCK_SIZE; + + der = (unsigned char*)XMALLOC(len, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (der == NULL) { + ret = MEMORY_E; + } + if (ret == 0) { + ret = wp11_DecryptData(der, object->keyData, len, + object->slot->token.key, + sizeof(object->slot->token.key), object->iv, + sizeof(object->iv), object->devId); + } + if (ret == 0) { + ret = MlKemKeyTryDecode(object->data.mlKemKey, WC_ML_KEM_512, + der, len, object->devId, object->objClass); + if (ret != 0) { + ret = MlKemKeyTryDecode(object->data.mlKemKey, WC_ML_KEM_768, + der, len, object->devId, + object->objClass); + } + if (ret != 0) { + ret = MlKemKeyTryDecode(object->data.mlKemKey, WC_ML_KEM_1024, + der, len, object->devId, + object->objClass); + } + wc_ForceZero(der, len); + } + if (der != NULL) + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + else { + ret = MlKemKeyTryDecode(object->data.mlKemKey, WC_ML_KEM_512, + object->keyData, object->keyDataLen, + object->devId, object->objClass); + if (ret != 0) { + ret = MlKemKeyTryDecode(object->data.mlKemKey, WC_ML_KEM_768, + object->keyData, object->keyDataLen, + object->devId, object->objClass); + } + if (ret != 0) { + ret = MlKemKeyTryDecode(object->data.mlKemKey, WC_ML_KEM_1024, + object->keyData, object->keyDataLen, + object->devId, object->objClass); + } + } + object->encoded = (ret != 0); + + return ret; +} + +/** + * Encode the ML-KEM key. + * + * Private keys are encoded and then encrypted. + * + * @param [in, out] object ML-KEM key object. + * @return 0 on success. + * @return -ve on failure. + */ +static int wp11_Object_Encode_MlKemKey(WP11_Object* object) +{ + int ret; + word32 keyLen = 0; + + if (object->objClass == CKO_PRIVATE_KEY) { + ret = wc_MlKemKey_PrivateKeySize(object->data.mlKemKey, &keyLen); + if (ret == 0) { + object->keyDataLen = keyLen + AES_BLOCK_SIZE; + } + } + else { + ret = wc_MlKemKey_PublicKeySize(object->data.mlKemKey, &keyLen); + if (ret == 0) { + object->keyDataLen = keyLen; + } + } + + if (ret == 0) { + XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + object->keyData = (unsigned char*)XMALLOC(object->keyDataLen, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (object->keyData == NULL) + ret = MEMORY_E; + } + + if (ret == 0 && object->objClass == CKO_PRIVATE_KEY) { + ret = wc_MlKemKey_EncodePrivateKey(object->data.mlKemKey, + object->keyData, keyLen); + if (ret == 0) { + ret = wp11_EncryptData(object->keyData, object->keyData, keyLen, + object->slot->token.key, + sizeof(object->slot->token.key), object->iv, + sizeof(object->iv), object->devId); + } + } + else if (ret == 0 && object->objClass == CKO_PUBLIC_KEY) { + ret = wc_MlKemKey_EncodePublicKey(object->data.mlKemKey, + object->keyData, keyLen); + } + + if (ret != 0) { + if (object->keyData != NULL) + wc_ForceZero(object->keyData, object->keyDataLen); + XFREE(object->keyData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + object->keyData = NULL; + object->keyDataLen = 0; + } + + return ret; +} + +/** + * Load a ML-KEM key from storage. + * + * @param [in, out] object ML-KEM key object. + * @param [in] tokenId Id of token this key belongs to. + * @param [in] objId Id of object for token. + * @return 0 on success. + * @return MEMORY_E when dynamic memory allocation fails. + * @return BUFFER_E when loading fails. + * @return NOT_AVAILABLE_E when unable to locate data. + */ +static int wp11_Object_Load_MlKemKey(WP11_Object* object, int tokenId, + int objId) +{ + int ret; + void* storage = NULL; + int storeType; + + if (object->objClass == CKO_PRIVATE_KEY) + storeType = WOLFPKCS11_STORE_MLKEMKEY_PRIV; + else + storeType = WOLFPKCS11_STORE_MLKEMKEY_PUB; + + ret = wp11_storage_open_readonly(storeType, tokenId, objId, &storage); + if (ret == 0) { + ret = wp11_storage_read_alloc_array(storage, &object->keyData, + &object->keyDataLen); + wp11_storage_close(storage); + } + + return ret; +} + +/** + * Store a ML-KEM key to storage. + * + * @param [in] object ML-KEM key object. + * @param [in] tokenId Id of token this key belongs to. + * @param [in] objId Id of object for token. + * @return 0 on success. + * @return MEMORY_E when dynamic memory allocation fails. + * @return BUFFER_E when storing fails. + * @return NOT_AVAILABLE_E when unable to write data. + */ +static int wp11_Object_Store_MlKemKey(WP11_Object* object, int tokenId, + int objId) +{ + int ret = 0; + void* storage = NULL; + int storeType; + + if (object->keyData == NULL) { + ret = wp11_Object_Encode_MlKemKey(object); + if (ret != 0) + return ret; + } + + if (object->objClass == CKO_PRIVATE_KEY) + storeType = WOLFPKCS11_STORE_MLKEMKEY_PRIV; + else + storeType = WOLFPKCS11_STORE_MLKEMKEY_PUB; + + ret = wp11_storage_open(storeType, tokenId, objId, object->keyDataLen, + &storage); + if (ret == 0) { + ret = wp11_storage_write_array(storage, object->keyData, + object->keyDataLen); + wp11_storage_close(storage); + } + + return ret; +} +#endif /* WOLFPKCS11_MLKEM */ + /** * Decode the symmetric key - requires decryption. * @@ -5017,6 +5274,11 @@ static int wp11_Object_Load(WP11_Object* object, int tokenId, int objId) ret = wp11_Object_Load_DhKey(object, tokenId, objId); break; #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + ret = wp11_Object_Load_MlKemKey(object, tokenId, objId); + break; + #endif #ifndef NO_AES case CKK_AES: #endif @@ -5193,6 +5455,11 @@ static int wp11_Object_Store(WP11_Object* object, int tokenId, int objId) ret = wp11_Object_Store_DhKey(object, tokenId, objId); break; #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + ret = wp11_Object_Store_MlKemKey(object, tokenId, objId); + break; + #endif #ifndef NO_AES case CKK_AES: #endif @@ -5258,6 +5525,11 @@ static int wp11_Object_Decode(WP11_Object* object) ret = wp11_Object_Decode_DhKey(object); break; #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + ret = wp11_Object_Decode_MlKemKey(object); + break; + #endif #ifndef NO_AES case CKK_AES: #endif @@ -5336,6 +5608,15 @@ static int wp11_Object_Encode(WP11_Object* object, int protect) } break; #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + ret = wp11_Object_Encode_MlKemKey(object); + if (protect && ret == 0 && object->objClass == CKO_PRIVATE_KEY) { + wc_MlKemKey_Free(object->data.mlKemKey); + object->encoded = 1; + } + break; + #endif #ifndef NO_AES case CKK_AES: #endif @@ -5425,6 +5706,14 @@ static int wp11_Object_Unstore(WP11_Object* object, int tokenId, int objId) storeObjType = WOLFPKCS11_STORE_DHKEY_PUB; break; #endif + #ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + if (object->objClass == CKO_PRIVATE_KEY) + storeObjType = WOLFPKCS11_STORE_MLKEMKEY_PRIV; + else + storeObjType = WOLFPKCS11_STORE_MLKEMKEY_PUB; + break; + #endif #ifndef NO_AES case CKK_AES: #endif @@ -8284,6 +8573,14 @@ void WP11_Object_Free(WP11_Object* object) XFREE(object->data.dhKey, NULL, DYNAMIC_TYPE_DH); object->data.dhKey = NULL; } + #endif + #ifdef WOLFPKCS11_MLKEM + if (object->type == CKK_ML_KEM && object->data.mlKemKey != NULL) { + if (!object->encoded) + wc_MlKemKey_Free(object->data.mlKemKey); + XFREE(object->data.mlKemKey, NULL, DYNAMIC_TYPE_KEY); + object->data.mlKemKey = NULL; + } #endif if ((object->type == CKK_AES || object->type == CKK_GENERIC_SECRET || object->type == CKK_HKDF) && object->data.symmKey != NULL) { @@ -8708,10 +9005,10 @@ int WP11_Object_SetMldsaKey(WP11_Object* object, unsigned char** data, if (seedUsed == 0) { /* Import given public/private key data */ if (object->objClass == CKO_PUBLIC_KEY) { - ret = wc_MlDsaKey_ImportPubRaw(key, data[2], len[2]); + ret = wc_MlDsaKey_ImportPubRaw(key, data[2], (word32)len[2]); } else { - ret = wc_MlDsaKey_ImportPrivRaw(key, data[2], len[2]); + ret = wc_MlDsaKey_ImportPrivRaw(key, data[2], (word32)len[2]); } } else { @@ -8808,56 +9105,168 @@ int WP11_Object_SetDhKey(WP11_Object* object, unsigned char** data, } #endif +#ifdef WOLFPKCS11_MLKEM /** - * Set the DH key data into the object. + * Set the ML-KEM key data into the object. + * Store the data in the wolfCrypt data structure. * * @param object [in] Object object. - * @param data [in] Array of byte arrays. + * @param data [in] Array of byte arrays (data[0]=CKA_PARAMETER_SET, + * data[1]=optional CKA_SEED, + * data[2]=optional key bytes). * @param len [in] Array of lengths of byte arrays. - * @return BAD_FUNC_ARG when key length data is not size of CK_ULONG or - * key length is not a valid size for the key type. - * BUFFER_E when key data length is less than key length data value. - * Other -ve on failure. + * @return -ve on failure. * 0 on success. */ -int WP11_Object_SetSecretKey(WP11_Object* object, unsigned char** data, - CK_ULONG* len) +int WP11_Object_SetMlKemKey(WP11_Object* object, unsigned char** data, + CK_ULONG* len) { int ret = 0; - WP11_Data* key; + MlKemKey* key = object->data.mlKemKey; + CK_ML_KEM_PARAMETER_SET_TYPE* params = NULL; + int seedUsed = 0; + + if (data[0] == NULL) + return BAD_FUNC_ARG; + + if (len[0] != sizeof(CK_ML_KEM_PARAMETER_SET_TYPE)) + return BUFFER_E; if (object->onToken) WP11_Lock_LockRW(object->lock); - key = object->data.symmKey; - key->len = 0; - XMEMSET(key->data, 0, sizeof(key->data)); + params = (CK_ML_KEM_PARAMETER_SET_TYPE*)data[0]; - /* First item is the key's length. */ - if (ret == 0 && data[0] != NULL && len[0] != (int)sizeof(CK_ULONG)) - ret = BAD_FUNC_ARG; -#if !defined(NO_AES) && !defined(WOLFPKCS11_NSS) - if (ret == 0 && object->type == CKK_AES && data[0] != NULL) { - if (*(CK_ULONG*)data[0] != AES_128_KEY_SIZE && - *(CK_ULONG*)data[0] != AES_192_KEY_SIZE && - *(CK_ULONG*)data[0] != AES_256_KEY_SIZE) { - ret = BAD_FUNC_ARG; - } + switch (*params) { + case CKP_ML_KEM_512: + ret = wc_MlKemKey_Init(key, WC_ML_KEM_512, NULL, + object->devId); + break; + case CKP_ML_KEM_768: + ret = wc_MlKemKey_Init(key, WC_ML_KEM_768, NULL, + object->devId); + break; + case CKP_ML_KEM_1024: + ret = wc_MlKemKey_Init(key, WC_ML_KEM_1024, NULL, + object->devId); + break; + default: + ret = ASN_PARSE_E; } -#endif - if (ret == 0 && data[0] != NULL) - key->len = (word32)*(CK_ULONG*)data[0]; - /* Second item is the key data. */ + /* Set seed (only for private keys). */ if (ret == 0 && data[1] != NULL) { - if (key->len == 0) - key->len = (word32)len[1]; - else if (len[1] != (CK_ULONG)key->len) - ret = BUFFER_E; + if (object->objClass != CKO_PRIVATE_KEY) { + ret = BAD_FUNC_ARG; + } + else if (len[1] != WC_ML_KEM_MAKEKEY_RAND_SZ) { + ret = BAD_FUNC_ARG; + } + else { + ret = wc_MlKemKey_MakeKeyWithRandom(key, data[1], (int)len[1]); + seedUsed = 1; + } } - if (ret == 0 && key->len > WP11_MAX_SYM_KEY_SZ) - ret = BUFFER_E; - if (ret == 0 && data[1] != NULL) + + /* Set key data. */ + if (ret == 0 && data[2] != NULL) { + if (object->objClass == CKO_PUBLIC_KEY) { + ret = wc_MlKemKey_DecodePublicKey(key, data[2], (word32)len[2]); + } + else if (seedUsed == 0) { + ret = wc_MlKemKey_DecodePrivateKey(key, data[2], (word32)len[2]); + } + else { + byte* expandedKey = NULL; + word32 expandedKeyLen = 0; + + ret = wc_MlKemKey_PrivateKeySize(key, &expandedKeyLen); + if (ret == 0 && expandedKeyLen != len[2]) { + ret = BAD_FUNC_ARG; + } + if (ret == 0) { + expandedKey = XMALLOC(expandedKeyLen, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (expandedKey == NULL) + ret = MEMORY_E; + } + if (ret == 0) { + ret = wc_MlKemKey_EncodePrivateKey(key, expandedKey, + expandedKeyLen); + } + if (ret == 0) { + if (WP11_ConstantCompare(expandedKey, data[2], + (int)expandedKeyLen) != 1) { + ret = BAD_FUNC_ARG; + } + } + if (expandedKey != NULL) { + wc_ForceZero(expandedKey, expandedKeyLen); + XFREE(expandedKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + } + } + + if (ret != 0) + wc_MlKemKey_Free(key); + + if (object->onToken) + WP11_Lock_UnlockRW(object->lock); + + return ret; +} +#endif /* WOLFPKCS11_MLKEM */ + +/** + * Set the DH key data into the object. + * + * @param object [in] Object object. + * @param data [in] Array of byte arrays. + * @param len [in] Array of lengths of byte arrays. + * @return BAD_FUNC_ARG when key length data is not size of CK_ULONG or + * key length is not a valid size for the key type. + * BUFFER_E when key data length is less than key length data value. + * Other -ve on failure. + * 0 on success. + */ +int WP11_Object_SetSecretKey(WP11_Object* object, unsigned char** data, + CK_ULONG* len) +{ + int ret = 0; + WP11_Data* key; + + if (object->onToken) + WP11_Lock_LockRW(object->lock); + + key = object->data.symmKey; + key->len = 0; + XMEMSET(key->data, 0, sizeof(key->data)); + + /* First item is the key's length. */ + if (ret == 0 && data[0] != NULL && len[0] != (int)sizeof(CK_ULONG)) + ret = BAD_FUNC_ARG; +#if !defined(NO_AES) && !defined(WOLFPKCS11_NSS) + if (ret == 0 && object->type == CKK_AES && data[0] != NULL) { + if (*(CK_ULONG*)data[0] != AES_128_KEY_SIZE && + *(CK_ULONG*)data[0] != AES_192_KEY_SIZE && + *(CK_ULONG*)data[0] != AES_256_KEY_SIZE) { + ret = BAD_FUNC_ARG; + } + } +#endif + if (ret == 0 && data[0] != NULL) + key->len = (word32)*(CK_ULONG*)data[0]; + + /* Second item is the key data. */ + if (ret == 0 && data[1] != NULL) { + if (key->len == 0) + key->len = (word32)len[1]; + else if (len[1] != (CK_ULONG)key->len) + ret = BUFFER_E; + } + if (ret == 0 && key->len > WP11_MAX_SYM_KEY_SZ) + ret = BUFFER_E; + if (ret == 0 && data[1] != NULL) XMEMCPY(key->data, data[1], key->len); if (object->onToken) @@ -9790,6 +10199,139 @@ static int DhObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, } #endif /* !NO_DH */ +#ifdef WOLFPKCS11_MLKEM +static int GetMlKemParams(MlKemKey* key, byte* data, CK_ULONG* len) +{ + CK_ML_KEM_PARAMETER_SET_TYPE params; + + if (len == NULL) + return BUFFER_E; + + switch (key->type) { + case WC_ML_KEM_512: + params = CKP_ML_KEM_512; + break; + case WC_ML_KEM_768: + params = CKP_ML_KEM_768; + break; + case WC_ML_KEM_1024: + params = CKP_ML_KEM_1024; + break; + default: + return ASN_PARSE_E; + } + + if (data == NULL) + *len = sizeof(CK_ML_KEM_PARAMETER_SET_TYPE); + else if (*len < sizeof(CK_ML_KEM_PARAMETER_SET_TYPE)) + return BUFFER_E; + else { + XMEMCPY(data, ¶ms, sizeof(CK_ML_KEM_PARAMETER_SET_TYPE)); + *len = sizeof(CK_ML_KEM_PARAMETER_SET_TYPE); + } + + return 0; +} + +static int GetMlKemPublicKey(MlKemKey* key, byte* data, CK_ULONG* len) +{ + int ret = 0; + word32 dataLen = 0; + + ret = wc_MlKemKey_PublicKeySize(key, &dataLen); + if (ret != 0) + return ret; + + if (data == NULL) + *len = dataLen; + else if (*len < dataLen) + ret = BUFFER_E; + else { + ret = wc_MlKemKey_EncodePublicKey(key, data, dataLen); + if (ret == 0) + *len = dataLen; + } + + return ret; +} + +static int GetMlKemPrivateKey(MlKemKey* key, byte* data, CK_ULONG* len) +{ + int ret = 0; + word32 dataLen = 0; + + ret = wc_MlKemKey_PrivateKeySize(key, &dataLen); + if (ret != 0) + return ret; + + if (data == NULL) + *len = dataLen; + else if (*len < dataLen) + ret = BUFFER_E; + else { + ret = wc_MlKemKey_EncodePrivateKey(key, data, dataLen); + if (ret == 0) + *len = dataLen; + } + + return ret; +} + +/** + * Get a ML-KEM object's data as an attribute. + * + * @param object [in] Object object. + * @param type [in] Attribute type. + * @param data [in] Attribute data buffer. + * @param len [in,out] On in, length of attribute data buffer in bytes. + * On out, length of attribute data in bytes. + * @return BUFFER_E when buffer is too small for data. + * NOT_AVAILABLE_E when attribute type is not supported. + * 0 on success. + */ +static int MlKemObject_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, + byte* data, CK_ULONG* len) +{ + int ret = 0; + int noPriv = (((object->opFlag & WP11_FLAG_SENSITIVE) != 0) || + ((object->opFlag & WP11_FLAG_EXTRACTABLE) == 0)); + int noPub = 0; + + if (!(object->data.mlKemKey->flags & MLKEM_FLAG_PRIV_SET)) + noPriv = 1; + if (!(object->data.mlKemKey->flags & MLKEM_FLAG_PUB_SET)) + noPub = 1; + + switch (type) { + case CKA_PARAMETER_SET: + ret = GetMlKemParams(object->data.mlKemKey, data, len); + break; + case CKA_SEED: + *len = CK_UNAVAILABLE_INFORMATION; + break; + case CKA_VALUE: + if (object->objClass == CKO_PRIVATE_KEY) { + if (noPriv) + *len = CK_UNAVAILABLE_INFORMATION; + else + ret = GetMlKemPrivateKey(object->data.mlKemKey, data, len); + } + else if (object->objClass == CKO_PUBLIC_KEY) { + if (noPub) + *len = CK_UNAVAILABLE_INFORMATION; + else + ret = GetMlKemPublicKey(object->data.mlKemKey, data, len); + } + break; + default: + ret = NOT_AVAILABLE_E; + break; + } + + return ret; +} +#endif /* WOLFPKCS11_MLKEM */ + int WP11_Generic_SerializeKey(WP11_Object* object, byte* output, word32* poutsz) { if (object == NULL || poutsz == NULL) @@ -10082,6 +10624,14 @@ int WP11_Object_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, case CKA_DERIVE: ret = GetOpFlagBool(object->opFlag, WP11_FLAG_DERIVE, data, len); break; + case CKA_ENCAPSULATE: + ret = GetOpFlagBool(object->opFlag, WP11_FLAG_ENCAPSULATE, data, + len); + break; + case CKA_DECAPSULATE: + ret = GetOpFlagBool(object->opFlag, WP11_FLAG_DECAPSULATE, data, + len); + break; case CKA_CERTIFICATE_TYPE: if (object->objClass == CKO_CERTIFICATE) ret = GetULong(object->data.cert.type, data, len); @@ -10140,6 +10690,11 @@ int WP11_Object_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, ret = DhObject_GetAttr(object, type, data, len); break; #endif +#ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + ret = MlKemObject_GetAttr(object, type, data, len); + break; +#endif #ifndef NO_AES case CKK_AES: #endif @@ -10459,6 +11014,9 @@ int WP11_Object_SetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, #ifndef NO_DH case CKK_DH: #endif +#ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: +#endif #ifndef NO_AES case CKK_AES: #endif @@ -10542,13 +11100,61 @@ int WP11_Object_SetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, } break; case CKA_PARAMETER_SET: - case CKA_SEED: + switch (object->type) { #ifdef WOLFPKCS11_MLDSA - if (object->type != CKK_ML_DSA) + case CKK_ML_DSA: + break; #endif +#ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + break; +#endif + default: ret = BAD_FUNC_ARG; + break; + } + break; + case CKA_SEED: + switch (object->type) { +#ifdef WOLFPKCS11_MLDSA + case CKK_ML_DSA: + break; +#endif +#ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + break; +#endif + default: + ret = BAD_FUNC_ARG; + break; + } + break; + case CKA_ENCAPSULATE: + switch (object->type) { +#ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + WP11_Object_SetOpFlag(object, WP11_FLAG_ENCAPSULATE, + *(CK_BBOOL*)data); + break; +#endif + default: + ret = BAD_FUNC_ARG; + break; + } + break; + case CKA_DECAPSULATE: + switch (object->type) { +#ifdef WOLFPKCS11_MLKEM + case CKK_ML_KEM: + WP11_Object_SetOpFlag(object, WP11_FLAG_DECAPSULATE, + *(CK_BBOOL*)data); + break; +#endif + default: + ret = BAD_FUNC_ARG; + break; + } break; - case CKA_WOLFSSL_DEVID: object->devId = (int)(*(CK_ULONG*)data); break; @@ -12445,6 +13051,198 @@ int WP11_Dh_Derive(unsigned char* pub, word32 pubLen, unsigned char* key, } #endif /* !NO_DH */ +#ifdef WOLFPKCS11_MLKEM +/** + * Generate an ML-KEM key pair. + * + * @param pub [in] Public key object (already initialized with parameter set + * via WP11_Object_SetMlKemKey). + * @param priv [in] Private key object (already initialized with parameter + * set via WP11_Object_SetMlKemKey). + * @param slot [in] Slot object for RNG access. + * @return 0 on success. + * @return -ve on failure. + */ +int WP11_MlKem_GenerateKeyPair(WP11_Object* pub, WP11_Object* priv, + WP11_Slot* slot) +{ + int ret = 0; + byte* pubKeyBytes = NULL; + word32 pubKeyLen = 0; + WC_RNG rng; + + /* Both public and private key are already initialized. */ + + if (ret == 0) { + ret = Rng_New(&slot->token.rng, &slot->token.rngLock, &rng); + } + if (ret == 0) { + ret = wc_MlKemKey_MakeKey(priv->data.mlKemKey, &rng); + Rng_Free(&rng); + } + if (ret == 0) { + ret = wc_MlKemKey_PublicKeySize(priv->data.mlKemKey, &pubKeyLen); + } + if (ret == 0) { + pubKeyBytes = (byte*)XMALLOC(pubKeyLen, NULL, DYNAMIC_TYPE_PUBLIC_KEY); + if (pubKeyBytes == NULL) + ret = MEMORY_E; + } + if (ret == 0) { + ret = wc_MlKemKey_EncodePublicKey(priv->data.mlKemKey, pubKeyBytes, + pubKeyLen); + } + if (ret == 0) { + ret = wc_MlKemKey_DecodePublicKey(pub->data.mlKemKey, pubKeyBytes, + pubKeyLen); + } + if (ret == 0) { + priv->local = 1; + pub->local = 1; + priv->keyGenMech = CKM_ML_KEM_KEY_PAIR_GEN; + pub->keyGenMech = CKM_ML_KEM_KEY_PAIR_GEN; + } + + if (pubKeyBytes != NULL) + XFREE(pubKeyBytes, NULL, DYNAMIC_TYPE_PUBLIC_KEY); + + return ret; +} + +/** + * Encapsulate: generate a shared secret and ciphertext using the public key. + * + * @param pub [in] Public key object. + * @param sharedSecret [out] Allocated buffer with shared secret. + * @param ssLen [out] Length of shared secret in bytes. + * @param pCiphertext [out] Buffer to hold ciphertext. + * @param pulCiphertextLen [in,out] On in, size of buffer. On out, ciphertext + * length. + * @return 0 on success. + * @return -ve on failure. + */ +int WP11_MlKem_Encapsulate(WP11_Object* pub, unsigned char** sharedSecret, + word32* ssLen, CK_BYTE_PTR pCiphertext, + CK_ULONG_PTR pulCiphertextLen) +{ + int ret; + int rngInit = 0; + WC_RNG rng; + MlKemKey* mlKemKey = pub->data.mlKemKey; + word32 ctLen = 0; + + *sharedSecret = NULL; + + if (WP11_Object_GetType(pub) != CKK_ML_KEM) + return CKR_KEY_TYPE_INCONSISTENT; + if ((pub->opFlag & WP11_FLAG_ENCAPSULATE) == 0) + return CKR_KEY_FUNCTION_NOT_PERMITTED; + + if (pub->onToken) + WP11_Lock_LockRO(pub->lock); + + ret = wc_MlKemKey_CipherTextSize(mlKemKey, &ctLen); + if (ret == 0) { + if (pCiphertext == NULL) { + *pulCiphertextLen = ctLen; + if (pub->onToken) + WP11_Lock_UnlockRO(pub->lock); + return CKR_OK; + } + else if (*pulCiphertextLen < ctLen) { + *pulCiphertextLen = ctLen; + if (pub->onToken) + WP11_Lock_UnlockRO(pub->lock); + return CKR_BUFFER_TOO_SMALL; + } + *pulCiphertextLen = ctLen; + ret = Rng_New(&pub->slot->token.rng, &pub->slot->token.rngLock, &rng); + if (ret == 0) + rngInit = 1; + } + if (ret == 0) { + ret = wc_MlKemKey_SharedSecretSize(mlKemKey, ssLen); + } + if (ret == 0) { + *sharedSecret = (unsigned char*)XMALLOC(*ssLen, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (*sharedSecret == NULL) + ret = MEMORY_E; + } + if (ret == 0) { + ret = wc_MlKemKey_Encapsulate(mlKemKey, pCiphertext, *sharedSecret, + &rng); + } + + if (rngInit) + Rng_Free(&rng); + + if (ret != 0 && *sharedSecret != NULL) { + wc_ForceZero(*sharedSecret, *ssLen); + XFREE(*sharedSecret, NULL, DYNAMIC_TYPE_TMP_BUFFER); + *sharedSecret = NULL; + } + + if (pub->onToken) + WP11_Lock_UnlockRO(pub->lock); + + return ret; +} + +/** + * Decapsulate: recover the shared secret from the ciphertext using the private + * key. + * + * @param priv [in] Private key object. + * @param sharedSecret [out] Allocated buffer with shared secret. + * @param ssLen [out] Length of shared secret in bytes. + * @param pCiphertext [in] Ciphertext buffer. + * @param ulCiphertextLen [in] Length of ciphertext in bytes. + * @return 0 on success. + * @return -ve on failure. + */ +int WP11_MlKem_Decapsulate(WP11_Object* priv, unsigned char** sharedSecret, + word32* ssLen, CK_BYTE_PTR pCiphertext, + CK_ULONG ulCiphertextLen) +{ + int ret; + MlKemKey* mlKemKey = priv->data.mlKemKey; + + *sharedSecret = NULL; + + if (WP11_Object_GetType(priv) != CKK_ML_KEM) + return CKR_KEY_TYPE_INCONSISTENT; + if ((priv->opFlag & WP11_FLAG_DECAPSULATE) == 0) + return CKR_KEY_FUNCTION_NOT_PERMITTED; + + if (priv->onToken) + WP11_Lock_LockRO(priv->lock); + + ret = wc_MlKemKey_SharedSecretSize(mlKemKey, ssLen); + if (ret == 0) { + *sharedSecret = (unsigned char*)XMALLOC(*ssLen, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (*sharedSecret == NULL) + ret = MEMORY_E; + } + if (ret == 0) { + ret = wc_MlKemKey_Decapsulate(mlKemKey, *sharedSecret, pCiphertext, + (word32)ulCiphertextLen); + } + + if (ret != 0 && *sharedSecret != NULL) { + wc_ForceZero(*sharedSecret, *ssLen); + XFREE(*sharedSecret, NULL, DYNAMIC_TYPE_TMP_BUFFER); + *sharedSecret = NULL; + } + + if (priv->onToken) + WP11_Lock_UnlockRO(priv->lock); + + return ret; +} +#endif /* WOLFPKCS11_MLKEM */ + #ifndef NO_AES #ifdef HAVE_AES_CBC diff --git a/src/slot.c b/src/slot.c index bf99f8f0..ae9005e9 100644 --- a/src/slot.c +++ b/src/slot.c @@ -378,6 +378,10 @@ static CK_MECHANISM_TYPE mechanismList[] = { CKM_DH_PKCS_KEY_PAIR_GEN, CKM_DH_PKCS_DERIVE, #endif +#ifdef WOLFPKCS11_MLKEM + CKM_ML_KEM_KEY_PAIR_GEN, + CKM_ML_KEM, +#endif #ifndef NO_AES CKM_AES_KEY_GEN, #ifdef HAVE_AES_KEY_WRAP @@ -657,6 +661,16 @@ static CK_MECHANISM_INFO dhPkcsMechInfo = { 1024, 4096, CKF_DERIVE }; #endif +#ifdef WOLFPKCS11_MLKEM +/* Info on ML-KEM key generation mechanism. */ +static CK_MECHANISM_INFO mlKemKgMechInfo = { + 800, 1568, CKF_GENERATE_KEY_PAIR +}; +/* Info on ML-KEM mechanism. */ +static CK_MECHANISM_INFO mlKemMechInfo = { + 800, 1568, CKF_ENCAPSULATE | CKF_DECAPSULATE +}; +#endif #ifndef NO_KDF static CK_MECHANISM_INFO tls12MasterKeyDeriveDhInfo = { 8, 128, CKF_DERIVE @@ -1003,6 +1017,14 @@ CK_RV C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, XMEMCPY(pInfo, &dhPkcsMechInfo, sizeof(CK_MECHANISM_INFO)); break; #endif +#ifdef WOLFPKCS11_MLKEM + case CKM_ML_KEM_KEY_PAIR_GEN: + XMEMCPY(pInfo, &mlKemKgMechInfo, sizeof(CK_MECHANISM_INFO)); + break; + case CKM_ML_KEM: + XMEMCPY(pInfo, &mlKemMechInfo, sizeof(CK_MECHANISM_INFO)); + break; +#endif #ifndef NO_AES case CKM_AES_KEY_GEN: XMEMCPY(pInfo, &aesKeyGenMechInfo, sizeof(CK_MECHANISM_INFO)); diff --git a/tests/pkcs11v3test.c b/tests/pkcs11v3test.c index f6148195..9566e6c6 100644 --- a/tests/pkcs11v3test.c +++ b/tests/pkcs11v3test.c @@ -28,6 +28,9 @@ #endif #include #include +#ifdef WOLFPKCS11_MLKEM + #include +#endif #ifndef WOLFPKCS11_USER_SETTINGS #include @@ -84,11 +87,15 @@ static byte* userPin = (byte*)"wolfpkcs11-test"; static int userPinLen; #ifdef WOLFPKCS11_PKCS11_V3_2 -#ifdef WOLFPKCS11_MLDSA +#if defined(WOLFPKCS11_MLDSA) || defined(WOLFPKCS11_MLKEM) static CK_BBOOL ckTrue = CK_TRUE; static CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY; static CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY; + +#endif /* WOLFPKCS11_MLDSA || WOLFPKCS11_MLKEM */ +#ifdef WOLFPKCS11_MLDSA + static CK_KEY_TYPE mldsaKeyType = CKK_ML_DSA; static CK_RV gen_mldsa_keys(CK_SESSION_HANDLE session, @@ -926,6 +933,1080 @@ static CK_RV test_mldsa_fixed_keys_both(void* args) return ret; } #endif /* WOLFPKCS11_MLDSA */ + +#ifdef WOLFPKCS11_MLKEM + +static CK_KEY_TYPE mlkemKeyType = CKK_ML_KEM; +static unsigned char mlkem_512_seed[WC_ML_KEM_MAKEKEY_RAND_SZ] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f +}; +static unsigned char mlkem_512_seed_alt[WC_ML_KEM_MAKEKEY_RAND_SZ] = { + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf +}; + +static CK_RV mlkem_keypair_from_seed(CK_ML_KEM_PARAMETER_SET_TYPE paramSet, + unsigned char* seed, CK_ULONG seedLen, + unsigned char** privKeyData, + CK_ULONG* privKeyLen, + unsigned char** pubKeyData, + CK_ULONG* pubKeyLen) +{ + CK_RV rv = CKR_OK; + int ret = 0; + int keyInited = 0; + int level = 0; + MlKemKey key; + word32 privLen = 0; + word32 pubLen = 0; + unsigned char* privData = NULL; + unsigned char* pubData = NULL; + + if (seed == NULL || privKeyData == NULL || privKeyLen == NULL || + pubKeyData == NULL || pubKeyLen == NULL) { + return CKR_ARGUMENTS_BAD; + } + + switch (paramSet) { + case CKP_ML_KEM_512: + level = WC_ML_KEM_512; + break; + case CKP_ML_KEM_768: + level = WC_ML_KEM_768; + break; + case CKP_ML_KEM_1024: + level = WC_ML_KEM_1024; + break; + default: + return CKR_ARGUMENTS_BAD; + } + + *privKeyData = NULL; + *pubKeyData = NULL; + *privKeyLen = 0; + *pubKeyLen = 0; + + ret = wc_MlKemKey_Init(&key, level, NULL, INVALID_DEVID); + if (ret == 0) { + keyInited = 1; + ret = wc_MlKemKey_MakeKeyWithRandom(&key, seed, (int)seedLen); + } + if (ret == 0) + ret = wc_MlKemKey_PrivateKeySize(&key, &privLen); + if (ret == 0) + ret = wc_MlKemKey_PublicKeySize(&key, &pubLen); + if (ret == 0) { + privData = (unsigned char*)malloc(privLen); + pubData = (unsigned char*)malloc(pubLen); + if (privData == NULL || pubData == NULL) + rv = CKR_HOST_MEMORY; + } + if (ret == 0 && rv == CKR_OK) + ret = wc_MlKemKey_EncodePrivateKey(&key, privData, privLen); + if (ret == 0 && rv == CKR_OK) + ret = wc_MlKemKey_EncodePublicKey(&key, pubData, pubLen); + + if (ret != 0 && rv == CKR_OK) + rv = CKR_FUNCTION_FAILED; + + if (keyInited) + wc_MlKemKey_Free(&key); + + if (rv != CKR_OK) { + if (privData != NULL) + free(privData); + if (pubData != NULL) + free(pubData); + } + else { + *privKeyData = privData; + *pubKeyData = pubData; + *privKeyLen = privLen; + *pubKeyLen = pubLen; + } + + return rv; +} + +static CK_RV gen_mlkem_keys(CK_SESSION_HANDLE session, + CK_ML_KEM_PARAMETER_SET_TYPE paramSet, + CK_OBJECT_HANDLE* pubKey, CK_OBJECT_HANDLE* privKey, + unsigned char* privId, int privIdLen, + unsigned char* pubId, int pubIdLen, int onToken) +{ + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_MECHANISM mech; + CK_BBOOL token = (CK_BBOOL)onToken; + CK_ATTRIBUTE pubKeyTmpl[] = { + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_ENCAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &token, sizeof(token) }, + { CKA_ID, pubId, pubIdLen }, + }; + CK_ULONG pubTmplCnt = sizeof(pubKeyTmpl) / sizeof(*pubKeyTmpl); + CK_ATTRIBUTE privKeyTmpl[] = { + { CKA_DECAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_TOKEN, &token, sizeof(token) }, + { CKA_ID, privId, privIdLen }, + }; + CK_ULONG privTmplCnt = sizeof(privKeyTmpl) / sizeof(*privKeyTmpl); + + if (pubId == NULL) + pubTmplCnt--; + if (privId == NULL) + privTmplCnt--; + + mech.mechanism = CKM_ML_KEM_KEY_PAIR_GEN; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + ret = funcList->C_GenerateKeyPair(session, &mech, pubKeyTmpl, pubTmplCnt, + privKeyTmpl, privTmplCnt, &pub, &priv); + CHECK_CKR(ret, "ML-KEM Key Generation"); + if (ret == CKR_OK && pubKey != NULL) + *pubKey = pub; + if (ret == CKR_OK && privKey != NULL) + *privKey = priv; + + return ret; +} + +static CK_RV find_mlkem_key(CK_SESSION_HANDLE session, CK_OBJECT_CLASS objClass, + CK_OBJECT_HANDLE* key, unsigned char* id, int idLen) +{ + CK_RV ret = CKR_OK; + CK_ULONG count = 0; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &objClass, sizeof(objClass) }, + { CKA_KEY_TYPE, &mlkemKeyType, sizeof(mlkemKeyType) }, + { CKA_ID, id, idLen }, + }; + + ret = funcList->C_FindObjectsInit(session, tmpl, sizeof(tmpl)/sizeof(*tmpl)); + CHECK_CKR(ret, "ML-KEM Find Objects Init"); + if (ret == CKR_OK) { + ret = funcList->C_FindObjects(session, key, 1, &count); + CHECK_CKR(ret, "ML-KEM Find Objects"); + } + if (ret == CKR_OK) { + ret = funcList->C_FindObjectsFinal(session); + CHECK_CKR(ret, "ML-KEM Find Objects Final"); + } + if (ret == CKR_OK) { + CHECK_COND(count == 1, ret, "ML-KEM Find Objects count"); + } + + return ret; +} + +static CK_RV find_mlkem_priv_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key, + unsigned char* id, int idLen) +{ + return find_mlkem_key(session, privKeyClass, key, id, idLen); +} + +static CK_RV find_mlkem_pub_key(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE* key, + unsigned char* id, int idLen) +{ + return find_mlkem_key(session, pubKeyClass, key, id, idLen); +} + +static CK_RV import_mlkem_priv_key(CK_SESSION_HANDLE session, + CK_ML_KEM_PARAMETER_SET_TYPE paramSet, + unsigned char* privKeyData, + CK_ULONG privKeyLen, CK_OBJECT_HANDLE* obj) +{ + CK_RV ret; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &mlkemKeyType, sizeof(mlkemKeyType) }, + { CKA_DECAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_VALUE, privKeyData, privKeyLen }, + }; + int cnt = sizeof(tmpl)/sizeof(*tmpl); + + ret = funcList->C_CreateObject(session, tmpl, cnt, obj); + CHECK_CKR(ret, "ML-KEM Priv Key CreateObject"); + + return ret; +} + +static CK_RV import_mlkem_priv_key_from_seed(CK_SESSION_HANDLE session, + CK_ML_KEM_PARAMETER_SET_TYPE paramSet, + unsigned char* seed, + CK_ULONG seedLen, + CK_OBJECT_HANDLE* obj) +{ + CK_RV ret; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &mlkemKeyType, sizeof(mlkemKeyType) }, + { CKA_DECAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_SEED, seed, seedLen }, + }; + int cnt = sizeof(tmpl)/sizeof(*tmpl); + + ret = funcList->C_CreateObject(session, tmpl, cnt, obj); + CHECK_CKR(ret, "ML-KEM Priv Key CreateObject from seed"); + + return ret; +} + +static CK_RV import_mlkem_priv_key_and_seed(CK_SESSION_HANDLE session, + CK_ML_KEM_PARAMETER_SET_TYPE paramSet, + unsigned char* seed, + CK_ULONG seedLen, + unsigned char* privKeyData, + CK_ULONG privKeyLen, + CK_OBJECT_HANDLE* obj) +{ + CK_RV ret; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &mlkemKeyType, sizeof(mlkemKeyType) }, + { CKA_DECAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_SEED, seed, seedLen }, + { CKA_VALUE, privKeyData, privKeyLen }, + }; + int cnt = sizeof(tmpl)/sizeof(*tmpl); + + ret = funcList->C_CreateObject(session, tmpl, cnt, obj); + CHECK_CKR(ret, "ML-KEM Priv Key CreateObject from seed and key"); + + return ret; +} + +static CK_RV import_mlkem_pub_key(CK_SESSION_HANDLE session, + CK_ML_KEM_PARAMETER_SET_TYPE paramSet, + unsigned char* pubKeyData, CK_ULONG pubKeyLen, + CK_OBJECT_HANDLE* obj) +{ + CK_RV ret; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass) }, + { CKA_KEY_TYPE, &mlkemKeyType, sizeof(mlkemKeyType) }, + { CKA_ENCAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_VALUE, pubKeyData, pubKeyLen }, + }; + int cnt = sizeof(tmpl)/sizeof(*tmpl); + + ret = funcList->C_CreateObject(session, tmpl, cnt, obj); + CHECK_CKR(ret, "ML-KEM Pub Key CreateObject"); + + return ret; +} + +/* Perform encapsulate/decapsulate and verify that shared secrets match. */ +static CK_RV mlkem_encap_decap(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE pubKey, + CK_OBJECT_HANDLE privKey) +{ + CK_RV ret = CKR_OK; + CK_FUNCTION_LIST_3_2* funcListExt = (CK_FUNCTION_LIST_3_2*)funcList; + CK_MECHANISM mech; + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL extractable = CK_TRUE; + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_EXTRACTABLE, &extractable, sizeof(extractable) }, + }; + CK_ULONG secretTmplCnt = sizeof(secretTmpl) / sizeof(*secretTmpl); + CK_OBJECT_HANDLE encapKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE decapKey = CK_INVALID_HANDLE; + CK_BYTE* ciphertext = NULL; + CK_ULONG ctLen = 0; + CK_BYTE ss1[64]; + CK_BYTE ss2[64]; + CK_ULONG ss1Len = sizeof(ss1); + CK_ULONG ss2Len = sizeof(ss2); + CK_ATTRIBUTE getValueTmpl[] = { { CKA_VALUE, NULL, 0 } }; + + mech.mechanism = CKM_ML_KEM; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + /* First call with ctLen=0 to discover ciphertext size. */ + ret = funcListExt->C_EncapsulateKey(session, &mech, pubKey, secretTmpl, + secretTmplCnt, NULL, &ctLen, &encapKey); + CHECK_CKR(ret, "ML-KEM Encapsulate size query"); + + if (ret == CKR_OK) { + ciphertext = (CK_BYTE*)malloc(ctLen); + if (ciphertext == NULL) + ret = CKR_HOST_MEMORY; + } + + /* Encapsulate: generates ciphertext and encapsulated shared secret. */ + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, pubKey, secretTmpl, + secretTmplCnt, ciphertext, &ctLen, + &encapKey); + CHECK_CKR(ret, "ML-KEM Encapsulate"); + } + + /* Decapsulate: recover shared secret from ciphertext. */ + if (ret == CKR_OK) { + ret = funcListExt->C_DecapsulateKey(session, &mech, privKey, secretTmpl, + secretTmplCnt, ciphertext, ctLen, + &decapKey); + CHECK_CKR(ret, "ML-KEM Decapsulate"); + } + + /* Compare shared secrets — they must be identical. */ + if (ret == CKR_OK) { + getValueTmpl[0].pValue = ss1; + getValueTmpl[0].ulValueLen = ss1Len; + ret = funcList->C_GetAttributeValue(session, encapKey, getValueTmpl, 1); + CHECK_CKR(ret, "ML-KEM Get encap shared secret"); + if (ret == CKR_OK) + ss1Len = getValueTmpl[0].ulValueLen; + } + if (ret == CKR_OK) { + getValueTmpl[0].pValue = ss2; + getValueTmpl[0].ulValueLen = ss2Len; + ret = funcList->C_GetAttributeValue(session, decapKey, getValueTmpl, 1); + CHECK_CKR(ret, "ML-KEM Get decap shared secret"); + if (ret == CKR_OK) + ss2Len = getValueTmpl[0].ulValueLen; + } + if (ret == CKR_OK) { + CHECK_COND(ss1Len == ss2Len && XMEMCMP(ss1, ss2, ss1Len) == 0, + ret, "ML-KEM Shared secrets match"); + } + + if (ciphertext != NULL) + free(ciphertext); + if (encapKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, encapKey); + if (decapKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, decapKey); + + return ret; +} + +static CK_RV test_mlkem_gen_keys(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pub, &priv, NULL, 0, + NULL, 0, 0); + if (ret == CKR_OK) + ret = mlkem_encap_decap(session, pub, priv); + + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + + return ret; +} + +static CK_RV test_mlkem_gen_keys_id(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + unsigned char* privId = (unsigned char*)"mlkem-priv-id"; + int privIdLen = (int)XSTRLEN((const char*)privId); + + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pub, NULL, privId, privIdLen, + NULL, 0, 0); + if (ret == CKR_OK) + ret = find_mlkem_priv_key(session, &priv, privId, privIdLen); + if (ret == CKR_OK) + ret = mlkem_encap_decap(session, pub, priv); + + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + + return ret; +} + +static CK_RV test_mlkem_gen_keys_token(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + unsigned char* privId = (unsigned char*)"mlkem-priv-token"; + unsigned char* pubId = (unsigned char*)"mlkem-pub-token"; + int privIdLen = (int)XSTRLEN((const char*)privId); + int pubIdLen = (int)XSTRLEN((const char*)pubId); + + return gen_mlkem_keys(session, CKP_ML_KEM_512, NULL, NULL, privId, privIdLen, + pubId, pubIdLen, 1); +} + +static CK_RV test_mlkem_token_keys(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + unsigned char* privId = (unsigned char*)"mlkem-priv-token"; + unsigned char* pubId = (unsigned char*)"mlkem-pub-token"; + int privIdLen = (int)XSTRLEN((const char*)privId); + int pubIdLen = (int)XSTRLEN((const char*)pubId); + + ret = find_mlkem_priv_key(session, &priv, privId, privIdLen); + if (ret == CKR_OK) + ret = find_mlkem_pub_key(session, &pub, pubId, pubIdLen); + if (ret == CKR_OK) + ret = mlkem_encap_decap(session, pub, priv); + + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + + return ret; +} + +/* Generate a key pair, export the raw key material, destroy, re-import, and + * verify that encapsulate/decapsulate still works — exercises the import path. */ +static CK_RV test_mlkem_fixed_keys(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub2 = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv2 = CK_INVALID_HANDLE; + CK_BYTE* privKeyData = NULL; + CK_BYTE* pubKeyData = NULL; + CK_ULONG privKeyLen = 0; + CK_ULONG pubKeyLen = 0; + CK_ATTRIBUTE getPriv[] = { { CKA_VALUE, NULL, 0 } }; + CK_ATTRIBUTE getPub[] = { { CKA_VALUE, NULL, 0 } }; + + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pub, &priv, NULL, 0, + NULL, 0, 0); + + /* Query sizes. */ + if (ret == CKR_OK) { + ret = funcList->C_GetAttributeValue(session, priv, getPriv, 1); + CHECK_CKR(ret, "ML-KEM get priv key size"); + if (ret == CKR_OK) + privKeyLen = getPriv[0].ulValueLen; + } + if (ret == CKR_OK) { + ret = funcList->C_GetAttributeValue(session, pub, getPub, 1); + CHECK_CKR(ret, "ML-KEM get pub key size"); + if (ret == CKR_OK) + pubKeyLen = getPub[0].ulValueLen; + } + + /* Allocate and fetch key data. */ + if (ret == CKR_OK) { + privKeyData = (CK_BYTE*)malloc(privKeyLen); + pubKeyData = (CK_BYTE*)malloc(pubKeyLen); + if (privKeyData == NULL || pubKeyData == NULL) + ret = CKR_HOST_MEMORY; + } + if (ret == CKR_OK) { + getPriv[0].pValue = privKeyData; + ret = funcList->C_GetAttributeValue(session, priv, getPriv, 1); + CHECK_CKR(ret, "ML-KEM get priv key data"); + } + if (ret == CKR_OK) { + getPub[0].pValue = pubKeyData; + ret = funcList->C_GetAttributeValue(session, pub, getPub, 1); + CHECK_CKR(ret, "ML-KEM get pub key data"); + } + + /* Destroy originals so the import is the only active copy. */ + if (priv != CK_INVALID_HANDLE) { + funcList->C_DestroyObject(session, priv); + priv = CK_INVALID_HANDLE; + } + if (pub != CK_INVALID_HANDLE) { + funcList->C_DestroyObject(session, pub); + pub = CK_INVALID_HANDLE; + } + + /* Re-import and verify. */ + if (ret == CKR_OK) + ret = import_mlkem_priv_key(session, CKP_ML_KEM_512, privKeyData, + privKeyLen, &priv2); + if (ret == CKR_OK) + ret = import_mlkem_pub_key(session, CKP_ML_KEM_512, pubKeyData, + pubKeyLen, &pub2); + if (ret == CKR_OK) + ret = mlkem_encap_decap(session, pub2, priv2); + + if (priv2 != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv2); + if (pub2 != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub2); + if (privKeyData != NULL) + free(privKeyData); + if (pubKeyData != NULL) + free(pubKeyData); + + return ret; +} + +static CK_RV test_mlkem_fixed_keys_seed(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_BYTE* privKeyData = NULL; + CK_BYTE* pubKeyData = NULL; + CK_ULONG privKeyLen = 0; + CK_ULONG pubKeyLen = 0; + CK_ML_KEM_PARAMETER_SET_TYPE paramSet = CKP_ML_KEM_512; + + ret = mlkem_keypair_from_seed(paramSet, mlkem_512_seed, sizeof(mlkem_512_seed), + &privKeyData, &privKeyLen, &pubKeyData, + &pubKeyLen); + CHECK_CKR(ret, "ML-KEM keypair from seed"); + if (ret == CKR_OK) + ret = import_mlkem_priv_key_from_seed(session, paramSet, mlkem_512_seed, + sizeof(mlkem_512_seed), &priv); + if (ret == CKR_OK) + ret = import_mlkem_pub_key(session, paramSet, pubKeyData, pubKeyLen, + &pub); + if (ret == CKR_OK) + ret = mlkem_encap_decap(session, pub, priv); + + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + if (privKeyData != NULL) + free(privKeyData); + if (pubKeyData != NULL) + free(pubKeyData); + + return ret; +} + +static CK_RV test_mlkem_fixed_keys_seed_both(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_BYTE* privKeyData = NULL; + CK_BYTE* pubKeyData = NULL; + CK_ULONG privKeyLen = 0; + CK_ULONG pubKeyLen = 0; + CK_ML_KEM_PARAMETER_SET_TYPE paramSet = CKP_ML_KEM_512; + + ret = mlkem_keypair_from_seed(paramSet, mlkem_512_seed, sizeof(mlkem_512_seed), + &privKeyData, &privKeyLen, &pubKeyData, + &pubKeyLen); + CHECK_CKR(ret, "ML-KEM keypair from seed"); + if (ret == CKR_OK) { + ret = import_mlkem_priv_key_and_seed(session, paramSet, mlkem_512_seed, + sizeof(mlkem_512_seed), privKeyData, + privKeyLen, &priv); + } + if (ret == CKR_OK) + ret = import_mlkem_pub_key(session, paramSet, pubKeyData, pubKeyLen, + &pub); + if (ret == CKR_OK) + ret = mlkem_encap_decap(session, pub, priv); + + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + if (privKeyData != NULL) + free(privKeyData); + if (pubKeyData != NULL) + free(pubKeyData); + + return ret; +} + +static CK_RV test_mlkem_seed_invalid(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_ML_KEM_PARAMETER_SET_TYPE paramSet = CKP_ML_KEM_512; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_BYTE* privKeyDataAlt = NULL; + CK_BYTE* pubKeyDataAlt = NULL; + CK_ULONG privKeyLenAlt = 0; + CK_ULONG pubKeyLenAlt = 0; + + /* CKA_SEED is private-key only. */ + if (ret == CKR_OK) { + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass) }, + { CKA_KEY_TYPE, &mlkemKeyType, sizeof(mlkemKeyType) }, + { CKA_ENCAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_SEED, mlkem_512_seed, sizeof(mlkem_512_seed) }, + }; + int cnt = sizeof(tmpl)/sizeof(*tmpl); + ret = funcList->C_CreateObject(session, tmpl, cnt, &obj); + CHECK_CKR_FAIL(ret, CKR_FUNCTION_FAILED, + "ML-KEM Pub Key CreateObject from seed must fail"); + obj = CK_INVALID_HANDLE; + } + + /* Invalid seed length must fail. */ + if (ret == CKR_OK) { + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &mlkemKeyType, sizeof(mlkemKeyType) }, + { CKA_DECAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_SEED, mlkem_512_seed, sizeof(mlkem_512_seed) - 1 }, + }; + int cnt = sizeof(tmpl)/sizeof(*tmpl); + ret = funcList->C_CreateObject(session, tmpl, cnt, &obj); + CHECK_CKR_FAIL(ret, CKR_FUNCTION_FAILED, + "ML-KEM Priv Key CreateObject bad seed length"); + obj = CK_INVALID_HANDLE; + } + + /* Mismatched seed and expanded key must fail. */ + if (ret == CKR_OK) { + ret = mlkem_keypair_from_seed(paramSet, mlkem_512_seed_alt, + sizeof(mlkem_512_seed_alt), + &privKeyDataAlt, &privKeyLenAlt, + &pubKeyDataAlt, &pubKeyLenAlt); + CHECK_CKR(ret, "ML-KEM keypair from alternate seed"); + } + if (ret == CKR_OK) { + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &privKeyClass, sizeof(privKeyClass) }, + { CKA_KEY_TYPE, &mlkemKeyType, sizeof(mlkemKeyType) }, + { CKA_DECAPSULATE, &ckTrue, sizeof(ckTrue) }, + { CKA_PARAMETER_SET, ¶mSet, sizeof(paramSet) }, + { CKA_SEED, mlkem_512_seed, sizeof(mlkem_512_seed) }, + { CKA_VALUE, privKeyDataAlt, privKeyLenAlt }, + }; + int cnt = sizeof(tmpl)/sizeof(*tmpl); + ret = funcList->C_CreateObject(session, tmpl, cnt, &obj); + CHECK_CKR_FAIL(ret, CKR_FUNCTION_FAILED, + "ML-KEM Priv Key CreateObject mismatch seed and key"); + } + + if (obj != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, obj); + if (privKeyDataAlt != NULL) + free(privKeyDataAlt); + if (pubKeyDataAlt != NULL) + free(pubKeyDataAlt); + + return ret; +} + +/* Verify that decapsulating ciphertext with the wrong private key produces a + * different shared secret (ML-KEM uses implicit rejection, so the call + * succeeds but yields a pseudo-random value). */ +static CK_RV test_mlkem_encap_decap_fail(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_FUNCTION_LIST_3_2* funcListExt = (CK_FUNCTION_LIST_3_2*)funcList; + CK_OBJECT_HANDLE pubA = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE privA = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pubB = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE privB = CK_INVALID_HANDLE; + CK_MECHANISM mech; + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL extractable = CK_TRUE; + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_EXTRACTABLE, &extractable, sizeof(extractable) }, + }; + CK_ULONG secretTmplCnt = sizeof(secretTmpl) / sizeof(*secretTmpl); + CK_OBJECT_HANDLE encapKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE decapKey = CK_INVALID_HANDLE; + CK_BYTE* ciphertext = NULL; + CK_ULONG ctLen = 0; + CK_BYTE ss1[64]; + CK_BYTE ss2[64]; + CK_ULONG ss1Len = sizeof(ss1); + CK_ULONG ss2Len = sizeof(ss2); + CK_ATTRIBUTE getValueTmpl[] = { { CKA_VALUE, NULL, 0 } }; + + /* Generate two independent key pairs. */ + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pubA, &privA, NULL, 0, + NULL, 0, 0); + if (ret == CKR_OK) + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pubB, &privB, NULL, 0, + NULL, 0, 0); + + mech.mechanism = CKM_ML_KEM; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + /* Discover ciphertext size. */ + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, pubA, secretTmpl, + secretTmplCnt, NULL, &ctLen, + &encapKey); + CHECK_CKR(ret, "ML-KEM Encapsulate size query"); + } + if (ret == CKR_OK) { + ciphertext = (CK_BYTE*)malloc(ctLen); + if (ciphertext == NULL) + ret = CKR_HOST_MEMORY; + } + + /* Encapsulate with pubA. */ + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, pubA, secretTmpl, + secretTmplCnt, ciphertext, &ctLen, + &encapKey); + CHECK_CKR(ret, "ML-KEM Encapsulate (fail test)"); + } + + /* Decapsulate with wrong key (privB instead of privA). ML-KEM implicit + * rejection means this succeeds but yields a different shared secret. */ + if (ret == CKR_OK) { + ret = funcListExt->C_DecapsulateKey(session, &mech, privB, secretTmpl, + secretTmplCnt, ciphertext, ctLen, + &decapKey); + CHECK_CKR(ret, "ML-KEM Decapsulate wrong key"); + } + + /* Retrieve both shared secrets. */ + if (ret == CKR_OK) { + getValueTmpl[0].pValue = ss1; + getValueTmpl[0].ulValueLen = ss1Len; + ret = funcList->C_GetAttributeValue(session, encapKey, getValueTmpl, 1); + CHECK_CKR(ret, "ML-KEM Get encap shared secret (fail test)"); + if (ret == CKR_OK) + ss1Len = getValueTmpl[0].ulValueLen; + } + if (ret == CKR_OK) { + getValueTmpl[0].pValue = ss2; + getValueTmpl[0].ulValueLen = ss2Len; + ret = funcList->C_GetAttributeValue(session, decapKey, getValueTmpl, 1); + CHECK_CKR(ret, "ML-KEM Get decap shared secret (fail test)"); + if (ret == CKR_OK) + ss2Len = getValueTmpl[0].ulValueLen; + } + /* Shared secrets must differ when wrong key was used. */ + if (ret == CKR_OK) { + CHECK_COND(ss1Len == ss2Len && XMEMCMP(ss1, ss2, ss1Len) != 0, + ret, "ML-KEM Shared secrets must differ for wrong key"); + } + + if (ciphertext != NULL) + free(ciphertext); + if (encapKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, encapKey); + if (decapKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, decapKey); + if (privA != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, privA); + if (pubA != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pubA); + if (privB != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, privB); + if (pubB != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pubB); + + return ret; +} + +static CK_RV test_mlkem_bad_mech_params(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_FUNCTION_LIST_3_2* funcListExt = (CK_FUNCTION_LIST_3_2*)funcList; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE secret = CK_INVALID_HANDLE; + CK_MECHANISM mech; + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL extractable = CK_TRUE; + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_EXTRACTABLE, &extractable, sizeof(extractable) }, + }; + CK_ULONG secretTmplCnt = sizeof(secretTmpl) / sizeof(*secretTmpl); + CK_ULONG ctLen = 0; + CK_BYTE dummyCt[1] = { 0 }; + CK_BYTE badParam = 0; + + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pub, &priv, NULL, 0, + NULL, 0, 0); + + mech.mechanism = CKM_ML_KEM; + mech.pParameter = &badParam; + mech.ulParameterLen = sizeof(badParam); + + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, pub, secretTmpl, + secretTmplCnt, NULL, &ctLen, + &secret); + CHECK_CKR_FAIL(ret, CKR_MECHANISM_PARAM_INVALID, + "ML-KEM Encapsulate bad mechanism parameter"); + } + if (ret == CKR_OK) { + ret = funcListExt->C_DecapsulateKey(session, &mech, priv, secretTmpl, + secretTmplCnt, dummyCt, + sizeof(dummyCt), &secret); + CHECK_CKR_FAIL(ret, CKR_MECHANISM_PARAM_INVALID, + "ML-KEM Decapsulate bad mechanism parameter"); + } + + mech.pParameter = NULL; + mech.ulParameterLen = 1; + + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, pub, secretTmpl, + secretTmplCnt, NULL, &ctLen, + &secret); + CHECK_CKR_FAIL(ret, CKR_MECHANISM_PARAM_INVALID, + "ML-KEM Encapsulate bad mechanism parameter length"); + } + if (ret == CKR_OK) { + ret = funcListExt->C_DecapsulateKey(session, &mech, priv, secretTmpl, + secretTmplCnt, dummyCt, + sizeof(dummyCt), &secret); + CHECK_CKR_FAIL(ret, CKR_MECHANISM_PARAM_INVALID, + "ML-KEM Decapsulate bad mechanism parameter length"); + } + + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + if (secret != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, secret); + + return ret; +} + +static CK_RV test_mlkem_key_validation(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_FUNCTION_LIST_3_2* funcListExt = (CK_FUNCTION_LIST_3_2*)funcList; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE secret = CK_INVALID_HANDLE; + CK_MECHANISM mech; + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL extractable = CK_TRUE; + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_EXTRACTABLE, &extractable, sizeof(extractable) }, + }; + CK_ULONG secretTmplCnt = sizeof(secretTmpl) / sizeof(*secretTmpl); + CK_ULONG ctLen = 0; + CK_BYTE dummyCt[1] = { 0 }; +#ifdef WOLFPKCS11_MLDSA + CK_OBJECT_HANDLE mldsaPub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE mldsaPriv = CK_INVALID_HANDLE; +#endif + + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pub, &priv, NULL, 0, + NULL, 0, 0); + + mech.mechanism = CKM_ML_KEM; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, priv, secretTmpl, + secretTmplCnt, NULL, &ctLen, + &secret); + CHECK_CKR_FAIL(ret, CKR_KEY_HANDLE_INVALID, + "ML-KEM Encapsulate wrong key class"); + } + if (ret == CKR_OK) { + ret = funcListExt->C_DecapsulateKey(session, &mech, pub, secretTmpl, + secretTmplCnt, dummyCt, + sizeof(dummyCt), &secret); + CHECK_CKR_FAIL(ret, CKR_KEY_HANDLE_INVALID, + "ML-KEM Decapsulate wrong key class"); + } + +#ifdef WOLFPKCS11_MLDSA + if (ret == CKR_OK) { + ret = gen_mldsa_keys(session, CKP_ML_DSA_44, &mldsaPub, &mldsaPriv, + NULL, 0, NULL, 0, 0); + } + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, mldsaPub, + secretTmpl, secretTmplCnt, NULL, + &ctLen, &secret); + CHECK_CKR_FAIL(ret, CKR_KEY_TYPE_INCONSISTENT, + "ML-KEM Encapsulate wrong key type"); + } + if (ret == CKR_OK) { + ret = funcListExt->C_DecapsulateKey(session, &mech, mldsaPriv, + secretTmpl, secretTmplCnt, dummyCt, + sizeof(dummyCt), &secret); + CHECK_CKR_FAIL(ret, CKR_KEY_TYPE_INCONSISTENT, + "ML-KEM Decapsulate wrong key type"); + } +#endif + + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + if (secret != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, secret); +#ifdef WOLFPKCS11_MLDSA + if (mldsaPriv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, mldsaPriv); + if (mldsaPub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, mldsaPub); +#endif + + return ret; +} + +static CK_RV test_mlkem_initial_states(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_FUNCTION_LIST_3_2* funcListExt = (CK_FUNCTION_LIST_3_2*)funcList; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE encapKey = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE decapKey = CK_INVALID_HANDLE; + CK_MECHANISM mech; + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL sensitive = CK_TRUE; + CK_BBOOL extractable = CK_FALSE; + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_SENSITIVE, &sensitive, sizeof(sensitive) }, + { CKA_EXTRACTABLE, &extractable, sizeof(extractable) }, + }; + CK_ULONG secretTmplCnt = sizeof(secretTmpl) / sizeof(*secretTmpl); + CK_BYTE* ciphertext = NULL; + CK_ULONG ctLen = 0; + CK_BBOOL encapSensitive = CK_FALSE; + CK_BBOOL encapExtractable = CK_TRUE; + CK_BBOOL encapAlwaysSensitive = CK_FALSE; + CK_BBOOL encapNeverExtractable = CK_FALSE; + CK_BBOOL decapSensitive = CK_FALSE; + CK_BBOOL decapExtractable = CK_TRUE; + CK_BBOOL decapAlwaysSensitive = CK_FALSE; + CK_BBOOL decapNeverExtractable = CK_FALSE; + CK_ATTRIBUTE encapAttr[] = { + { CKA_SENSITIVE, &encapSensitive, sizeof(encapSensitive) }, + { CKA_EXTRACTABLE, &encapExtractable, sizeof(encapExtractable) }, + { CKA_ALWAYS_SENSITIVE, &encapAlwaysSensitive, sizeof(encapAlwaysSensitive) }, + { CKA_NEVER_EXTRACTABLE, &encapNeverExtractable, sizeof(encapNeverExtractable) }, + }; + CK_ATTRIBUTE decapAttr[] = { + { CKA_SENSITIVE, &decapSensitive, sizeof(decapSensitive) }, + { CKA_EXTRACTABLE, &decapExtractable, sizeof(decapExtractable) }, + { CKA_ALWAYS_SENSITIVE, &decapAlwaysSensitive, sizeof(decapAlwaysSensitive) }, + { CKA_NEVER_EXTRACTABLE, &decapNeverExtractable, sizeof(decapNeverExtractable) }, + }; + + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pub, &priv, NULL, 0, + NULL, 0, 0); + + mech.mechanism = CKM_ML_KEM; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, pub, secretTmpl, + secretTmplCnt, NULL, &ctLen, + &encapKey); + CHECK_CKR(ret, "ML-KEM Encapsulate size query (states)"); + } + if (ret == CKR_OK) { + ciphertext = (CK_BYTE*)malloc(ctLen); + if (ciphertext == NULL) + ret = CKR_HOST_MEMORY; + } + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, pub, secretTmpl, + secretTmplCnt, ciphertext, &ctLen, + &encapKey); + CHECK_CKR(ret, "ML-KEM Encapsulate (states)"); + } + if (ret == CKR_OK) { + ret = funcListExt->C_DecapsulateKey(session, &mech, priv, secretTmpl, + secretTmplCnt, ciphertext, ctLen, + &decapKey); + CHECK_CKR(ret, "ML-KEM Decapsulate (states)"); + } + if (ret == CKR_OK) { + ret = funcList->C_GetAttributeValue(session, encapKey, encapAttr, + sizeof(encapAttr) / sizeof(*encapAttr)); + CHECK_CKR(ret, "ML-KEM Get initial states (encap)"); + } + if (ret == CKR_OK) { + ret = funcList->C_GetAttributeValue(session, decapKey, decapAttr, + sizeof(decapAttr) / sizeof(*decapAttr)); + CHECK_CKR(ret, "ML-KEM Get initial states (decap)"); + } + if (ret == CKR_OK) { + CHECK_COND(encapSensitive == CK_TRUE && + encapExtractable == CK_FALSE && + encapAlwaysSensitive == CK_TRUE && + encapNeverExtractable == CK_TRUE, ret, + "ML-KEM Encap key initial states"); + } + if (ret == CKR_OK) { + CHECK_COND(decapSensitive == CK_TRUE && + decapExtractable == CK_FALSE && + decapAlwaysSensitive == CK_TRUE && + decapNeverExtractable == CK_TRUE, ret, + "ML-KEM Decap key initial states"); + } + + if (ciphertext != NULL) + free(ciphertext); + if (decapKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, decapKey); + if (encapKey != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, encapKey); + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + + return ret; +} +#endif /* WOLFPKCS11_MLKEM */ #endif /* WOLFPKCS11_PKCS11_V3_2 */ static CK_RV test_get_interface_list(void* args) @@ -1367,6 +2448,7 @@ static CK_RV test_function_not_supported(void* args) } #ifdef WOLFPKCS11_PKCS11_V3_2 +#ifndef WOLFPKCS11_MLKEM if (ret == CKR_OK) { ret = funcListExt->C_EncapsulateKey(session, NULL, 0, NULL, 0, NULL, NULL, 0); @@ -1377,6 +2459,7 @@ static CK_RV test_function_not_supported(void* args) 0, NULL); CHECK_CKR_FAIL(ret, CKR_FUNCTION_NOT_SUPPORTED, "DecapsulateKey"); } +#endif /* !WOLFPKCS11_MLKEM */ if (ret == CKR_OK) { ret = funcListExt->C_VerifySignatureInit(session, NULL, 0, NULL, 0); CHECK_CKR_FAIL(ret, CKR_FUNCTION_NOT_SUPPORTED, "VerifySignatureInit"); @@ -1523,6 +2606,20 @@ static TEST_FUNC testFunc[] = { PKCS11TEST_FUNC_SESS_DECL(test_mldsa_fixed_keys_seed), PKCS11TEST_FUNC_SESS_DECL(test_mldsa_fixed_keys_both), #endif +#ifdef WOLFPKCS11_MLKEM + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_gen_keys), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_gen_keys_id), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_gen_keys_token), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_token_keys), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_fixed_keys), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_fixed_keys_seed), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_fixed_keys_seed_both), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_seed_invalid), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_encap_decap_fail), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_bad_mech_params), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_key_validation), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_initial_states), +#endif #endif }; static int testFuncCnt = sizeof(testFunc) / sizeof(*testFunc); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index f7494dde..08a1919a 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -212,6 +212,8 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" #define WP11_FLAG_UNWRAP 0x00010000 #define WP11_FLAG_WRAP 0x00020000 #define WP11_FLAG_DERIVE 0x00040000 +#define WP11_FLAG_ENCAPSULATE 0x00080000 +#define WP11_FLAG_DECAPSULATE 0x00100000 /* Flags for token. */ #define WP11_TOKEN_FLAG_USER_PIN_SET 0x00000001 @@ -531,6 +533,19 @@ WP11_LOCAL int WP11_Dh_GenerateKeyPair(WP11_Object* pub, WP11_Object* priv, WP11_LOCAL int WP11_Dh_Derive(unsigned char* pub, word32 pubLen, unsigned char* key, word32* keyLen, WP11_Object* priv); +#ifdef WOLFPKCS11_MLKEM +WP11_LOCAL int WP11_Object_SetMlKemKey(WP11_Object* object, unsigned char** data, + CK_ULONG* len); +WP11_LOCAL int WP11_MlKem_GenerateKeyPair(WP11_Object* pub, WP11_Object* priv, + WP11_Slot* slot); +WP11_LOCAL int WP11_MlKem_Encapsulate(WP11_Object* pub, unsigned char** sharedSecret, + word32* ssLen, CK_BYTE_PTR pCiphertext, + CK_ULONG_PTR pulCiphertextLen); +WP11_LOCAL int WP11_MlKem_Decapsulate(WP11_Object* priv, unsigned char** sharedSecret, + word32* ssLen, CK_BYTE_PTR pCiphertext, + CK_ULONG ulCiphertextLen); +#endif + WP11_LOCAL int WP11_GenerateRandomKey(WP11_Object* secret, WP11_Slot* slot); WP11_LOCAL int WP11_KDF_Derive(WP11_Session* session, CK_HKDF_PARAMS_PTR params, diff --git a/wolfpkcs11/pkcs11.h b/wolfpkcs11/pkcs11.h index 20b2aa19..925f957b 100644 --- a/wolfpkcs11/pkcs11.h +++ b/wolfpkcs11/pkcs11.h @@ -118,6 +118,9 @@ extern "C" { #define CKF_EC_UNCOMPRESS 0x01000000UL #define CKF_EC_COMPRESS 0x02000000UL +#define CKF_ENCAPSULATE 0x10000000UL +#define CKF_DECAPSULATE 0x20000000UL + #define CKF_LIBRARY_CANT_CREATE_OS_THREADS 0x00000001UL #define CKF_OS_LOCKING_OK 0x00000002UL @@ -173,6 +176,7 @@ extern "C" { #define CKK_AES 0x0000001FUL #define CKK_DES3 0x00000015UL /* not supported */ #define CKK_HKDF 0x00000042UL +#define CKK_ML_KEM 0x00000049UL #define CKK_ML_DSA 0x0000004AUL #ifdef WOLFPKCS11_NSS @@ -255,6 +259,9 @@ extern "C" { /* new post-quantum (general) */ #define CKA_PARAMETER_SET 0x0000061DUL #define CKA_SEED 0x00000637UL +/* KEM */ +#define CKA_ENCAPSULATE 0x00000633UL +#define CKA_DECAPSULATE 0x00000634UL #ifdef WOLFPKCS11_NSS #define CKA_NSS_EMAIL (CKA_NSS + 2) @@ -353,6 +360,8 @@ extern "C" { #define CKM_HKDF_DERIVE 0x0000402AUL #define CKM_HKDF_DATA 0x0000402BUL #define CKM_HKDF_KEY_GEN 0x0000402CUL +#define CKM_ML_KEM_KEY_PAIR_GEN 0x0000000FUL +#define CKM_ML_KEM 0x00000017UL #define CKM_ML_DSA_KEY_PAIR_GEN 0x0000001CUL #define CKM_ML_DSA 0x0000001DUL #define CKM_HASH_ML_DSA 0x0000001FUL @@ -862,6 +871,12 @@ typedef CK_ULONG CK_ML_DSA_PARAMETER_SET_TYPE; #define CKP_ML_DSA_65 0x00000002UL #define CKP_ML_DSA_87 0x00000003UL +/* ML-KEM values for CKA_PARAMETER_SET */ +typedef CK_ULONG CK_ML_KEM_PARAMETER_SET_TYPE; +#define CKP_ML_KEM_512 0x00000001UL +#define CKP_ML_KEM_768 0x00000002UL +#define CKP_ML_KEM_1024 0x00000003UL + /* Function list types. */ typedef struct CK_FUNCTION_LIST CK_FUNCTION_LIST; @@ -1145,12 +1160,12 @@ CK_RV C_MessageVerifyFinal(CK_SESSION_HANDLE hSession); /* PKCS#11 V 3.2 functions */ CK_RV C_EncapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hPublicKey, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey, - CK_BYTE_PTR pCiphertext, CK_ULONG_PTR pulCiphertextLen); + CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, + CK_ULONG_PTR pulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey); CK_RV C_DecapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hPrivateKey, CK_BYTE_PTR pCiphertext, - CK_ULONG ulCiphertextLen, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey); + CK_OBJECT_HANDLE hPrivateKey, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, + CK_ULONG ulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey); CK_RV C_VerifySignatureInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey, CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen); @@ -1856,12 +1871,12 @@ struct CK_FUNCTION_LIST_3_2 { /* PKCS#11 V 3.2 functions */ CK_RV (*C_EncapsulateKey)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hPublicKey, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey, - CK_BYTE_PTR pCiphertext, CK_ULONG_PTR pulCiphertextLen); + CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, + CK_ULONG_PTR pulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey); CK_RV (*C_DecapsulateKey)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, - CK_OBJECT_HANDLE hPrivateKey, CK_BYTE_PTR pCiphertext, - CK_ULONG ulCiphertextLen, CK_ATTRIBUTE_PTR pTemplate, - CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey); + CK_OBJECT_HANDLE hPrivateKey, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, + CK_ULONG ulCiphertextLen, CK_OBJECT_HANDLE_PTR phKey); CK_RV (*C_VerifySignatureInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey, CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen); diff --git a/wolfpkcs11/store.h b/wolfpkcs11/store.h index 2ba49a5f..12bf120e 100644 --- a/wolfpkcs11/store.h +++ b/wolfpkcs11/store.h @@ -38,6 +38,8 @@ #define WOLFPKCS11_STORE_DATA 0x0B #define WOLFPKCS11_STORE_MLDSAKEY_PRIV 0x0C #define WOLFPKCS11_STORE_MLDSAKEY_PUB 0x0D +#define WOLFPKCS11_STORE_MLKEMKEY_PRIV 0x0E +#define WOLFPKCS11_STORE_MLKEMKEY_PUB 0x0F /* * Opens access to location to read/write token data. From 411a317d1bcbfe13d5836348d7c1ef0d23e8639d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 25 Mar 2026 16:09:25 +0100 Subject: [PATCH 2/2] Fix ML-DSA context buffer: replace heap pointer with fixed-size array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the dynamically-allocated `byte* ctx` pointer in WP11_MldsaParams with an inline `byte ctx[256]` array. PKCS#11 v3.2 (§2.3.12) caps the ML-DSA context length at 255 bytes, so heap allocation is unnecessary and introduced several memory-management hazards: - ctx was freed at the end of WP11_Mldsa_Sign/Verify before session teardown, leaving a dangling pointer if the session was reused - the cleanup in wp11_Session_Final checked the wrong mechanism set, meaning it could free ctx a second time - WP11_Session_SetMldsaParams freed ctx before re-initialising, which was safe only if the pointer was always valid (it wasn't on first call) Embedding the buffer in the struct eliminates all manual lifetime tracking. --- src/internal.c | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/internal.c b/src/internal.c index f7cb8f63..0b2b6491 100644 --- a/src/internal.c +++ b/src/internal.c @@ -356,7 +356,7 @@ typedef struct WP11_PssParams { typedef struct WP11_MldsaParams { enum wc_HashType preHashType; word32 hedgeType; - byte* ctx; + byte ctx[256]; byte ctxSz; } WP11_MldsaParams; #endif @@ -911,14 +911,6 @@ static void wp11_Session_Final(WP11_Session* session) session->params.oaep.label = NULL; } #endif -#ifdef WOLFPKCS11_MLDSA - if ((session->mechanism == CKM_ML_DSA || - session->mechanism == CKM_HASH_ML_DSA) && - session->params.mldsa.ctx != NULL) { - XFREE(session->params.mldsa.ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); - session->params.mldsa.ctx = NULL; - } -#endif #ifndef NO_AES #ifdef HAVE_AES_CBC if ((session->mechanism == CKM_AES_CBC || @@ -7851,7 +7843,6 @@ int WP11_Session_SetMldsaParams(WP11_Session* session, CK_VOID_PTR params, int ret = 0; WP11_MldsaParams* mldsa = &session->params.mldsa; - XFREE(mldsa->ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); XMEMSET(mldsa, 0, sizeof(*mldsa)); if (params != NULL) { @@ -7865,19 +7856,12 @@ int WP11_Session_SetMldsaParams(WP11_Session* session, CK_VOID_PTR params, if (ctx->ulContextLen > 255) { ret = BAD_FUNC_ARG; } - if (ret == 0) { - mldsa->ctx = (byte*)XMALLOC(ctx->ulContextLen, NULL, - DYNAMIC_TYPE_TMP_BUFFER); - if (mldsa->ctx == NULL) - ret = MEMORY_E; - } if (ret == 0) { XMEMCPY(mldsa->ctx, ctx->pContext, ctx->ulContextLen); mldsa->ctxSz = ctx->ulContextLen; } } else { - mldsa->ctx = NULL; mldsa->ctxSz = 0; } @@ -7894,19 +7878,12 @@ int WP11_Session_SetMldsaParams(WP11_Session* session, CK_VOID_PTR params, if (ctx->ulContextLen > 255) { ret = BAD_FUNC_ARG; } - if (ret == 0) { - mldsa->ctx = (byte*)XMALLOC(ctx->ulContextLen, NULL, - DYNAMIC_TYPE_TMP_BUFFER); - if (mldsa->ctx == NULL) - ret = MEMORY_E; - } if (ret == 0) { XMEMCPY(mldsa->ctx, ctx->pContext, ctx->ulContextLen); mldsa->ctxSz = ctx->ulContextLen; } } else { - mldsa->ctx = NULL; mldsa->ctxSz = 0; } @@ -7923,7 +7900,6 @@ int WP11_Session_SetMldsaParams(WP11_Session* session, CK_VOID_PTR params, else { mldsa->preHashType = WC_HASH_TYPE_NONE; mldsa->hedgeType = CKH_HEDGE_PREFERRED; - mldsa->ctx = NULL; mldsa->ctxSz = 0; } @@ -12806,8 +12782,6 @@ int WP11_Mldsa_Sign(unsigned char* data, word32 dataLen, unsigned char* sig, Rng_Free(&rng); } - XFREE(params->ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); - params->ctx = NULL; params->ctxSz = 0; if (priv->onToken) @@ -12856,8 +12830,6 @@ int WP11_Mldsa_Verify(unsigned char* sig, word32 sigLen, unsigned char* data, } } - XFREE(params->ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); - params->ctx = NULL; params->ctxSz = 0; if (pub->onToken)