From aa79f93395709d0056c4c6a966b01c8af6b1e465 Mon Sep 17 00:00:00 2001 From: Antoine Lochet <30625353+antoinelochet@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:05:32 +0200 Subject: [PATCH 1/2] Added possibility to set CKA_VALUE_LEN on C_Encapsulate/C_Decapsulate --- src/lib/P11Attributes.cpp | 10 +- src/lib/P11Attributes.h | 15 +- src/lib/P11Objects.cpp | 3 +- src/lib/SoftHSM.cpp | 40 +++- src/lib/crypto/MLKEMUtil.h | 1 + src/lib/test/CMakeLists.txt | 1 + src/lib/test/EncapDecapTests.cpp | 351 +++++++++++++++++++++++++++++++ src/lib/test/EncapDecapTests.h | 41 ++++ src/lib/test/Makefile.am | 1 + 9 files changed, 447 insertions(+), 16 deletions(-) create mode 100644 src/lib/test/EncapDecapTests.cpp create mode 100644 src/lib/test/EncapDecapTests.h diff --git a/src/lib/P11Attributes.cpp b/src/lib/P11Attributes.cpp index a3e407039..29d0ac50a 100644 --- a/src/lib/P11Attributes.cpp +++ b/src/lib/P11Attributes.cpp @@ -411,13 +411,13 @@ CK_RV P11Attribute::update(Token* token, bool isPrivate, CK_VOID_PTR pValue, CK_ // given non-Cryptoki attribute is read-only is obviously outside the scope of Cryptoki. // Attributes cannot be changed if CKA_MODIFIABLE is set to false - if (!isModifiable() && op != OBJECT_OP_GENERATE && op != OBJECT_OP_CREATE && op != OBJECT_OP_UNWRAP) { + if (!isModifiable() && op != OBJECT_OP_GENERATE && op != OBJECT_OP_CREATE && op != OBJECT_OP_UNWRAP && op != OBJECT_OP_DECAPSULATE) { ERROR_MSG("An object is with CKA_MODIFIABLE set to false is not modifiable"); return CKR_ATTRIBUTE_READ_ONLY; } // Attributes cannot be modified if CKA_TRUSTED is true on a certificate object. - if (isTrusted() && op != OBJECT_OP_GENERATE && op != OBJECT_OP_CREATE && op != OBJECT_OP_UNWRAP) { + if (isTrusted() && op != OBJECT_OP_GENERATE && op != OBJECT_OP_CREATE && op != OBJECT_OP_UNWRAP && op != OBJECT_OP_DECAPSULATE) { if (osobject->getUnsignedLongValue(CKA_CLASS, CKO_VENDOR_DEFINED) == CKO_CERTIFICATE) { ERROR_MSG("A trusted certificate cannot be modified"); @@ -487,7 +487,7 @@ CK_RV P11Attribute::update(Token* token, bool isPrivate, CK_VOID_PTR pValue, CK_ // For attributes that have not been explicitly excluded from modification // during create/derive/generate/unwrap, we allow them to be modified. - if (OBJECT_OP_CREATE==op || OBJECT_OP_DERIVE==op || OBJECT_OP_GENERATE==op || OBJECT_OP_UNWRAP==op) + if (OBJECT_OP_CREATE==op || OBJECT_OP_DERIVE==op || OBJECT_OP_GENERATE==op || OBJECT_OP_UNWRAP==op || OBJECT_OP_DECAPSULATE==op) { return updateAttr(token, isPrivate, pValue, ulValueLen, op); } @@ -967,7 +967,7 @@ CK_RV P11AttrValue::updateAttr(Token *token, bool isPrivate, CK_VOID_PTR pValue, // Set the size during C_CreateObject and C_UnwrapKey. - if (op == OBJECT_OP_CREATE || op == OBJECT_OP_UNWRAP) + if (op == OBJECT_OP_CREATE || op == OBJECT_OP_UNWRAP || op == OBJECT_OP_DECAPSULATE) { // Set the CKA_VALUE_LEN if (osobject->attributeExists(CKA_VALUE_LEN)) @@ -2308,7 +2308,7 @@ CK_RV P11AttrValueLen::updateAttr(Token* /*token*/, bool /*isPrivate*/, CK_VOID_ { // Attribute specific checks - if (op != OBJECT_OP_GENERATE && op != OBJECT_OP_DERIVE) + if (op != OBJECT_OP_GENERATE && op != OBJECT_OP_DERIVE && op != OBJECT_OP_DECAPSULATE) { return CKR_ATTRIBUTE_READ_ONLY; } diff --git a/src/lib/P11Attributes.h b/src/lib/P11Attributes.h index 0c8bb7c85..00460df0a 100644 --- a/src/lib/P11Attributes.h +++ b/src/lib/P11Attributes.h @@ -38,13 +38,14 @@ #include "Token.h" // The operation types -#define OBJECT_OP_NONE 0x0 -#define OBJECT_OP_COPY 0x1 -#define OBJECT_OP_CREATE 0x2 -#define OBJECT_OP_DERIVE 0x3 -#define OBJECT_OP_GENERATE 0x4 -#define OBJECT_OP_SET 0x5 -#define OBJECT_OP_UNWRAP 0x6 +#define OBJECT_OP_NONE 0x0 +#define OBJECT_OP_COPY 0x1 +#define OBJECT_OP_CREATE 0x2 +#define OBJECT_OP_DERIVE 0x3 +#define OBJECT_OP_GENERATE 0x4 +#define OBJECT_OP_SET 0x5 +#define OBJECT_OP_UNWRAP 0x6 +#define OBJECT_OP_DECAPSULATE 0x7 class P11Attribute { diff --git a/src/lib/P11Objects.cpp b/src/lib/P11Objects.cpp index bc028416f..82228f4de 100644 --- a/src/lib/P11Objects.cpp +++ b/src/lib/P11Objects.cpp @@ -256,7 +256,8 @@ CK_RV P11Object::saveTemplate(Token *token, bool isPrivate, CK_ATTRIBUTE_PTR pTe // ck5 MUST be specified when object is unwrapped with C_UnwrapKey. if (((checks & P11Attribute::ck1) == P11Attribute::ck1 && op == OBJECT_OP_CREATE) || ((checks & P11Attribute::ck3) == P11Attribute::ck3 && op == OBJECT_OP_GENERATE) || - ((checks & P11Attribute::ck5) == P11Attribute::ck5 && op == OBJECT_OP_UNWRAP)) + ((checks & P11Attribute::ck5) == P11Attribute::ck5 && op == OBJECT_OP_UNWRAP) || + ((checks & P11Attribute::ck5) == P11Attribute::ck5 && op == OBJECT_OP_DECAPSULATE)) { bool isSpecified = false; diff --git a/src/lib/SoftHSM.cpp b/src/lib/SoftHSM.cpp index 9e7a2cb87..50c291ca6 100644 --- a/src/lib/SoftHSM.cpp +++ b/src/lib/SoftHSM.cpp @@ -8487,6 +8487,23 @@ CK_RV SoftHSM::C_EncapsulateKey INFO_MSG("CKA_VALUE must not be included"); rv = CKR_ATTRIBUTE_READ_ONLY; break; + case CKA_VALUE_LEN: + byteLen = *(CK_ULONG*)pTemplate[i].pValue; + if (byteLen > 32) + { + INFO_MSG("CKA_VALUE_LEN must be less than or equal to 32"); + return CKR_WRAPPED_KEY_LEN_RANGE; + } + if (keyType == CKK_AES) + { + if (byteLen != 16 && byteLen != 24 && byteLen != 32) + { + INFO_MSG("CKA_VALUE_LEN must be 16, 24 or 32 for AES"); + return CKR_ATTRIBUTE_VALUE_INVALID; + } + break; + } + break; case CKA_CHECK_VALUE: if (pTemplate[i].ulValueLen > 0) { @@ -8650,7 +8667,7 @@ CK_RV SoftHSM::C_DecapsulateKey CK_BBOOL isDecapsulationKeyOnToken = decapsulationKey->getBooleanValue(CKA_TOKEN, false); CK_BBOOL isDecapsulationKeyPrivate = decapsulationKey->getBooleanValue(CKA_PRIVATE, true); - // Check user credentials for the wrapping key + // Check user credentials for the decapsulation key rv = haveRead(session->getState(), isDecapsulationKeyOnToken, isDecapsulationKeyPrivate); if (rv != CKR_OK) { @@ -8661,7 +8678,7 @@ CK_RV SoftHSM::C_DecapsulateKey } // Check if the wrapping key can be used for decapsulation - if (decapsulationKey->getBooleanValue(CKA_DECAPSULATE, false) == false) + if (!decapsulationKey->getBooleanValue(CKA_DECAPSULATE, false)) return CKR_KEY_FUNCTION_NOT_PERMITTED; // Check if the specified mechanism is allowed for the wrapping key @@ -8762,7 +8779,7 @@ CK_RV SoftHSM::C_DecapsulateKey } } - rv = this->CreateObject(hSession, secretAttribs, secretAttribsCount, phKey, OBJECT_OP_UNWRAP); + rv = this->CreateObject(hSession, secretAttribs, secretAttribsCount, phKey, OBJECT_OP_DECAPSULATE); // Store the attributes that are being supplied if (rv == CKR_OK) @@ -8780,6 +8797,23 @@ CK_RV SoftHSM::C_DecapsulateKey case CKA_VALUE: INFO_MSG("CKA_VALUE must not be included"); return CKR_ATTRIBUTE_READ_ONLY; + case CKA_VALUE_LEN: + byteLen = *(CK_ULONG*)pTemplate[i].pValue; + if (byteLen > 32) + { + INFO_MSG("CKA_VALUE_LEN must be less than or equal to 32"); + return CKR_WRAPPED_KEY_LEN_RANGE; + } + if (keyType == CKK_AES) + { + if (byteLen != 16 && byteLen != 24 && byteLen != 32) + { + INFO_MSG("CKA_VALUE_LEN must be 16, 24 or 32 for AES"); + return CKR_ATTRIBUTE_VALUE_INVALID; + } + break; + } + break; case CKA_CHECK_VALUE: if (pTemplate[i].ulValueLen > 0) { diff --git a/src/lib/crypto/MLKEMUtil.h b/src/lib/crypto/MLKEMUtil.h index cfe864cd4..602e7ba08 100644 --- a/src/lib/crypto/MLKEMUtil.h +++ b/src/lib/crypto/MLKEMUtil.h @@ -20,6 +20,7 @@ class MLKEMUtil { public: + MLKEMUtil() = delete; static CK_RV getMLKEMPrivateKey(MLKEMPrivateKey* privateKey, Token* token, OSObject* key); static CK_RV getMLKEMPublicKey(MLKEMPublicKey* publicKey, Token* token, OSObject* key); diff --git a/src/lib/test/CMakeLists.txt b/src/lib/test/CMakeLists.txt index 77bb1a025..9028e6128 100644 --- a/src/lib/test/CMakeLists.txt +++ b/src/lib/test/CMakeLists.txt @@ -21,6 +21,7 @@ set(SOURCES p11test.cpp UserTests.cpp ObjectTests.cpp DeriveTests.cpp + EncapDecapTests.cpp SignVerifyTests.cpp AsymEncryptDecryptTests.cpp AsymWrapUnwrapTests.cpp diff --git a/src/lib/test/EncapDecapTests.cpp b/src/lib/test/EncapDecapTests.cpp new file mode 100644 index 000000000..20dd46978 --- /dev/null +++ b/src/lib/test/EncapDecapTests.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2026 SoftHSMv2 contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ +/***************************************************************************** + EncapDecapTests.cpp + + Contains test cases for C_EncapsulateKey and C_DecapsulateKey + *****************************************************************************/ + +#include +#include +#include +#include "EncapDecapTests.h" + +// CKA_TOKEN +const CK_BBOOL ON_TOKEN = CK_TRUE; +const CK_BBOOL IN_SESSION = CK_FALSE; + +// CKA_PRIVATE +const CK_BBOOL IS_PRIVATE = CK_TRUE; +const CK_BBOOL IS_PUBLIC = CK_FALSE; + + +CPPUNIT_TEST_SUITE_REGISTRATION(EncapDecapTests); + +#ifdef WITH_ML_KEM +CK_RV EncapDecapTests::generateMLKEM(CK_ULONG parameterSet, CK_SESSION_HANDLE hSession, CK_BBOOL bTokenPuk, CK_BBOOL bPrivatePuk, CK_BBOOL bTokenPrk, CK_BBOOL bPrivatePrk, CK_OBJECT_HANDLE &hPuk, CK_OBJECT_HANDLE &hPrk) +{ + CK_MECHANISM mechanism = { CKM_ML_KEM_KEY_PAIR_GEN, NULL_PTR, 0 }; + CK_KEY_TYPE keyType = CKK_ML_KEM; + CK_BYTE label[] = { 0x12, 0x34 }; // dummy + CK_BYTE id[] = { 123 } ; // dummy + CK_BBOOL bFalse = CK_FALSE; + CK_BBOOL bTrue = CK_TRUE; + + CK_ATTRIBUTE pukAttribs[] = { + { CKA_PARAMETER_SET, ¶meterSet, sizeof(parameterSet) }, + { CKA_LABEL, &label[0], sizeof(label) }, + { CKA_ID, &id[0], sizeof(id) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ENCAPSULATE, &bTrue, sizeof(bTrue) }, + { CKA_TOKEN, &bTokenPuk, sizeof(bTokenPuk) }, + { CKA_PRIVATE, &bPrivatePuk, sizeof(bPrivatePuk) } + }; + CK_ATTRIBUTE prkAttribs[] = { + { CKA_LABEL, &label[0], sizeof(label) }, + { CKA_ID, &id[0], sizeof(id) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_DECAPSULATE, &bTrue, sizeof(bFalse) }, + { CKA_SENSITIVE, &bTrue, sizeof(bTrue) }, + { CKA_TOKEN, &bTokenPrk, sizeof(bTokenPrk) }, + { CKA_PRIVATE, &bPrivatePrk, sizeof(bPrivatePrk) }, + { CKA_EXTRACTABLE, &bFalse, sizeof(bFalse) } + }; + + hPuk = CK_INVALID_HANDLE; + hPrk = CK_INVALID_HANDLE; + return CRYPTOKI_F_PTR( C_GenerateKeyPair(hSession, &mechanism, + pukAttribs, sizeof(pukAttribs)/sizeof(CK_ATTRIBUTE), + prkAttribs, sizeof(prkAttribs)/sizeof(CK_ATTRIBUTE), + &hPuk, &hPrk) ); +} + +void EncapDecapTests::testMLKEMEncapsulationDecapsulation(CK_ULONG parameterSet) +{ + CK_RV rv; + CK_SESSION_HANDLE hSessionRO; + CK_SESSION_HANDLE hSessionRW; + CK_MECHANISM mechanism = { CKM_ML_KEM, NULL_PTR, 0 }; + CK_BBOOL bTrue = CK_TRUE; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + + // Just make sure that we finalize any previous tests + CRYPTOKI_F_PTR( C_Finalize(NULL_PTR) ); + + // Open read-only session on when the token is not initialized should fail + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &hSessionRO) ); + CPPUNIT_ASSERT(rv == CKR_CRYPTOKI_NOT_INITIALIZED); + + // Initialize the library and start the test. + rv = CRYPTOKI_F_PTR( C_Initialize(NULL_PTR) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Open read-only session + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &hSessionRO) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Open read-write session + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSessionRW) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Login USER into the sessions so we can create a private objects + rv = CRYPTOKI_F_PTR( C_Login(hSessionRO,CKU_USER,m_userPin1,m_userPin1Length) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + CK_OBJECT_HANDLE hPuk = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE hPrk = CK_INVALID_HANDLE; + + // Generate ML-KEM key pair + rv = generateMLKEM(parameterSet, hSessionRW, IN_SESSION, IS_PUBLIC, IN_SESSION, IS_PRIVATE, hPuk, hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hPuk != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(hPrk != CK_INVALID_HANDLE); + + // Test encapsulation - create a new AES key through encapsulation + CK_ATTRIBUTE keyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + CK_OBJECT_HANDLE hEncapKey = CK_INVALID_HANDLE; + CK_BYTE cipherText[2048]; + CK_ULONG ulCipherTextLen = sizeof(cipherText); + memset(cipherText, 0, sizeof(cipherText)); + + rv = CRYPTOKI_F_PTR(C_EncapsulateKey( + hSessionRW, + &mechanism, + hPuk, + keyAttribs, + sizeof(keyAttribs) / sizeof(CK_ATTRIBUTE), + cipherText, + &ulCipherTextLen, + &hEncapKey + )); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hEncapKey != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(ulCipherTextLen > 0); + + CK_BYTE encapKCV[64]; + CK_ULONG encapKCVLen = sizeof(encapKCV); + CK_ATTRIBUTE getEncapKCVAttrib = { CKA_CHECK_VALUE, encapKCV, encapKCVLen }; + + rv = CRYPTOKI_F_PTR(C_GetAttributeValue(hSessionRW, hEncapKey, &getEncapKCVAttrib, 1)); + CPPUNIT_ASSERT(rv == CKR_OK); + encapKCVLen = getEncapKCVAttrib.ulValueLen; + + // Test decapsulation - recover the shared secret using the ciphertext + CK_OBJECT_HANDLE hDecapKey = CK_INVALID_HANDLE; + + CK_ATTRIBUTE decapKeyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + rv = CRYPTOKI_F_PTR(C_DecapsulateKey( + hSessionRW, + &mechanism, + hPrk, + decapKeyAttribs, + sizeof(decapKeyAttribs) / sizeof(CK_ATTRIBUTE), + cipherText, + ulCipherTextLen, + &hDecapKey + )); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hDecapKey != CK_INVALID_HANDLE); + + // Verify that the decapsulated key has the same value as the encapsulated key + CK_BYTE decapKCV[64]; + CK_ULONG decapKCVLen = sizeof(decapKCV); + CK_ATTRIBUTE getDecapKCVAttrib = { CKA_CHECK_VALUE, decapKCV, decapKCVLen }; + + rv = CRYPTOKI_F_PTR(C_GetAttributeValue(hSessionRW, hDecapKey, &getDecapKCVAttrib, 1)); + CPPUNIT_ASSERT(rv == CKR_OK); + decapKCVLen = getDecapKCVAttrib.ulValueLen; + + CPPUNIT_ASSERT(encapKCVLen == decapKCVLen); + CPPUNIT_ASSERT(memcmp(encapKCV, decapKCV, encapKCVLen) == 0); + + // Clean up + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hEncapKey)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hDecapKey)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPuk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPrk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Close the session + rv = CRYPTOKI_F_PTR(C_CloseSession(hSessionRW)); + CPPUNIT_ASSERT(rv == CKR_OK); +} + +void EncapDecapTests::testMLKEMEncapsulationDecapsulationWithValueLen(CK_ULONG ulValueLen) +{ + CK_RV rv; + CK_SESSION_HANDLE hSessionRO; + CK_SESSION_HANDLE hSessionRW; + CK_MECHANISM mechanism = { CKM_ML_KEM, NULL_PTR, 0 }; + CK_BBOOL bTrue = CK_TRUE; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_ULONG parameterSet = CKP_ML_KEM_512; + + // Just make sure that we finalize any previous tests + CRYPTOKI_F_PTR( C_Finalize(NULL_PTR) ); + + // Open read-only session on when the token is not initialized should fail + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &hSessionRO) ); + CPPUNIT_ASSERT(rv == CKR_CRYPTOKI_NOT_INITIALIZED); + + // Initialize the library and start the test. + rv = CRYPTOKI_F_PTR( C_Initialize(NULL_PTR) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Open read-only session + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &hSessionRO) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Open read-write session + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSessionRW) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Login USER into the sessions so we can create a private objects + rv = CRYPTOKI_F_PTR( C_Login(hSessionRO,CKU_USER,m_userPin1,m_userPin1Length) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + CK_OBJECT_HANDLE hPuk = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE hPrk = CK_INVALID_HANDLE; + + // Generate ML-KEM key pair + rv = generateMLKEM(parameterSet, hSessionRW, IN_SESSION, IS_PUBLIC, IN_SESSION, IS_PRIVATE, hPuk, hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hPuk != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(hPrk != CK_INVALID_HANDLE); + + // Test encapsulation with specific key length + CK_ATTRIBUTE keyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_VALUE_LEN, &ulValueLen, sizeof(ulValueLen) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + CK_OBJECT_HANDLE hEncapKey = CK_INVALID_HANDLE; + CK_BYTE cipherText[2048]; + CK_ULONG ulCipherTextLen = sizeof(cipherText); + memset(cipherText, 0, sizeof(cipherText)); + + rv = CRYPTOKI_F_PTR(C_EncapsulateKey( + hSessionRW, + &mechanism, + hPuk, + keyAttribs, + sizeof(keyAttribs) / sizeof(CK_ATTRIBUTE), + cipherText, + &ulCipherTextLen, + &hEncapKey + )); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hEncapKey != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(ulCipherTextLen > 0); + + // Verify the decapsulated key has the correct length + CK_BYTE encapKeyLength[64]; + CK_ULONG encapKeyLengthLen = sizeof(encapKeyLength); + CK_ATTRIBUTE getEncapKeyValueAttrib = { CKA_VALUE_LEN, encapKeyLength, encapKeyLengthLen }; + + rv = CRYPTOKI_F_PTR(C_GetAttributeValue(hSessionRW, hEncapKey, &getEncapKeyValueAttrib, 1)); + CPPUNIT_ASSERT(rv == CKR_OK); + encapKeyLengthLen = *(CK_ULONG*)getEncapKeyValueAttrib.pValue; + CPPUNIT_ASSERT(encapKeyLengthLen == ulValueLen); + + // Get key check value for encapsulated key + CK_BYTE encapKCV[64]; + CK_ULONG encapKCVLen = sizeof(encapKCV); + CK_ATTRIBUTE getEncapKCVAttrib = { CKA_CHECK_VALUE, encapKCV, encapKCVLen }; + + rv = CRYPTOKI_F_PTR(C_GetAttributeValue(hSessionRW, hEncapKey, &getEncapKCVAttrib, 1)); + CPPUNIT_ASSERT(rv == CKR_OK); + encapKCVLen = getEncapKCVAttrib.ulValueLen; + + // Test decapsulation with specific key length + CK_OBJECT_HANDLE hDecapKey = CK_INVALID_HANDLE; + + CK_ATTRIBUTE decapKeyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_VALUE_LEN, &ulValueLen, sizeof(ulValueLen) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + rv = CRYPTOKI_F_PTR(C_DecapsulateKey( + hSessionRW, + &mechanism, + hPrk, + decapKeyAttribs, + sizeof(decapKeyAttribs) / sizeof(CK_ATTRIBUTE), + cipherText, + ulCipherTextLen, + &hDecapKey + )); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hDecapKey != CK_INVALID_HANDLE); + + // Verify the decapsulated key has the correct length + CK_BYTE decapKeyLength[64]; + CK_ULONG decapKeyLengthLen = sizeof(decapKeyLength); + CK_ATTRIBUTE getDecapKeyValueAttrib = { CKA_VALUE_LEN, decapKeyLength, decapKeyLengthLen }; + + rv = CRYPTOKI_F_PTR(C_GetAttributeValue(hSessionRW, hDecapKey, &getDecapKeyValueAttrib, 1)); + CPPUNIT_ASSERT(rv == CKR_OK); + decapKeyLengthLen = *(CK_ULONG*)getDecapKeyValueAttrib.pValue; + CPPUNIT_ASSERT(decapKeyLengthLen == ulValueLen); + + // Get key check value for decapsulated key + CK_BYTE decapKCV[64]; + CK_ULONG decapKCVLen = sizeof(decapKCV); + CK_ATTRIBUTE getDecapKCVAttrib = { CKA_CHECK_VALUE, decapKCV, decapKCVLen }; + + rv = CRYPTOKI_F_PTR(C_GetAttributeValue(hSessionRW, hDecapKey, &getDecapKCVAttrib, 1)); + CPPUNIT_ASSERT(rv == CKR_OK); + decapKCVLen = getDecapKCVAttrib.ulValueLen; + + // Verify both keys have the same check values + CPPUNIT_ASSERT(encapKCVLen == decapKCVLen); + CPPUNIT_ASSERT(memcmp(encapKCV, decapKCV, encapKCVLen) == 0); + + // Clean up + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hEncapKey)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hDecapKey)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPuk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPrk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Close the session + rv = CRYPTOKI_F_PTR(C_CloseSession(hSessionRW)); + CPPUNIT_ASSERT(rv == CKR_OK); +} + +#endif diff --git a/src/lib/test/EncapDecapTests.h b/src/lib/test/EncapDecapTests.h new file mode 100644 index 000000000..e923511b9 --- /dev/null +++ b/src/lib/test/EncapDecapTests.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 SoftHSMv2 contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ +/***************************************************************************** + EncapDecapTests.h + + Contains test cases to C_EncapsulateKey and C_DecapsulateKey + *****************************************************************************/ + +#ifndef _SOFTHSM_V2_ENCAPDECAPTESTS_H +#define _SOFTHSM_V2_ENCAPDECAPTESTS_H + +#include "config.h" +#include "TestsBase.h" +#include + +class EncapDecapTests : public TestsBase +{ + CPPUNIT_TEST_SUITE(EncapDecapTests); +#ifdef WITH_ML_KEM + CPPUNIT_TEST_PARAMETERIZED(testMLKEMEncapsulationDecapsulation, {CKP_ML_KEM_512, CKP_ML_KEM_768, CKP_ML_KEM_1024}); + CPPUNIT_TEST_PARAMETERIZED(testMLKEMEncapsulationDecapsulationWithValueLen, {16, 24, 32}); +#endif + CPPUNIT_TEST_SUITE_END(); + +public: +#ifdef WITH_ML_KEM + void testMLKEMEncapsulationDecapsulation(CK_ULONG parameterSet); + void testMLKEMEncapsulationDecapsulationWithValueLen(CK_ULONG ulValueLen); +#endif + +protected: +#ifdef WITH_ML_KEM + CK_RV generateMLKEM(CK_ULONG parameterSet, CK_SESSION_HANDLE hSession, CK_BBOOL bTokenPuk, CK_BBOOL bPrivatePuk, CK_BBOOL bTokenPrk, CK_BBOOL bPrivatePrk, CK_OBJECT_HANDLE &hPuk, CK_OBJECT_HANDLE &hPrk); +#endif + +}; + +#endif // !_SOFTHSM_V2_ENCAPDECAPTESTS_H diff --git a/src/lib/test/Makefile.am b/src/lib/test/Makefile.am index ea11df373..53b979ca4 100644 --- a/src/lib/test/Makefile.am +++ b/src/lib/test/Makefile.am @@ -21,6 +21,7 @@ p11test_SOURCES = p11test.cpp \ UserTests.cpp \ ObjectTests.cpp \ DeriveTests.cpp \ + EncapDecapTests.cpp \ SignVerifyTests.cpp \ AsymEncryptDecryptTests.cpp \ AsymWrapUnwrapTests.cpp \ From deef15c008bf238c5a9c660001dc322a7bc0a307 Mon Sep 17 00:00:00 2001 From: Antoine Lochet <30625353+antoinelochet@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:51:53 +0200 Subject: [PATCH 2/2] Fixed issue 889 by better handling error cases Co-Authored-By: Matthias Valvekens --- src/lib/SoftHSM.cpp | 132 ++++++----- src/lib/test/EncapDecapTests.cpp | 371 +++++++++++++++++++++++++++++-- src/lib/test/EncapDecapTests.h | 8 + 3 files changed, 435 insertions(+), 76 deletions(-) diff --git a/src/lib/SoftHSM.cpp b/src/lib/SoftHSM.cpp index 50c291ca6..90f8d4256 100644 --- a/src/lib/SoftHSM.cpp +++ b/src/lib/SoftHSM.cpp @@ -8435,6 +8435,16 @@ CK_RV SoftHSM::C_EncapsulateKey return CKR_GENERAL_ERROR; } + if (pCipherText == NULL_PTR) + { + unsigned long ctLen = ((MLKEMPublicKey*)publicKey)->getOutputLength(); + cipher->recyclePublicKey(publicKey); + CryptoFactory::i()->recycleAsymmetricAlgorithm(cipher); + if (ctLen == 0) return CKR_GENERAL_ERROR; + *pulCipherTextLen = ctLen; + return CKR_OK; + } + SymmetricKey *secret = NULL; if (cipher->encapsulate(publicKey, cipherText, &secret, keyType, mech)) @@ -8489,17 +8499,19 @@ CK_RV SoftHSM::C_EncapsulateKey break; case CKA_VALUE_LEN: byteLen = *(CK_ULONG*)pTemplate[i].pValue; - if (byteLen > 32) + if (byteLen > 32 || byteLen == 0) { INFO_MSG("CKA_VALUE_LEN must be less than or equal to 32"); - return CKR_WRAPPED_KEY_LEN_RANGE; + *pulCipherTextLen = 0; + rv = CKR_WRAPPED_KEY_LEN_RANGE; } if (keyType == CKK_AES) { if (byteLen != 16 && byteLen != 24 && byteLen != 32) { INFO_MSG("CKA_VALUE_LEN must be 16, 24 or 32 for AES"); - return CKR_ATTRIBUTE_VALUE_INVALID; + *pulCipherTextLen = 0; + rv = CKR_ATTRIBUTE_VALUE_INVALID; } break; } @@ -8734,6 +8746,12 @@ CK_RV SoftHSM::C_DecapsulateKey AsymmetricAlgorithm* cipher = CryptoFactory::i()->getAsymmetricAlgorithm(algo); if (cipher == NULL) return CKR_MECHANISM_INVALID; + if (ulCipherTextLen <= 0 || pCipherText == NULL_PTR) + { + CryptoFactory::i()->recycleAsymmetricAlgorithm(cipher); + return CKR_ATTRIBUTE_VALUE_INVALID; + } + PrivateKey* privateKey = cipher->newPrivateKey(); if (MLKEMUtil::getMLKEMPrivateKey((MLKEMPrivateKey*)privateKey, token, decapsulationKey) != CKR_OK) @@ -8796,20 +8814,22 @@ CK_RV SoftHSM::C_DecapsulateKey { case CKA_VALUE: INFO_MSG("CKA_VALUE must not be included"); - return CKR_ATTRIBUTE_READ_ONLY; + rv = CKR_ATTRIBUTE_READ_ONLY; + break; case CKA_VALUE_LEN: byteLen = *(CK_ULONG*)pTemplate[i].pValue; - if (byteLen > 32) + if (byteLen > 32 || byteLen == 0) { INFO_MSG("CKA_VALUE_LEN must be less than or equal to 32"); - return CKR_WRAPPED_KEY_LEN_RANGE; + rv = CKR_WRAPPED_KEY_LEN_RANGE; + break; } if (keyType == CKK_AES) { if (byteLen != 16 && byteLen != 24 && byteLen != 32) { INFO_MSG("CKA_VALUE_LEN must be 16, 24 or 32 for AES"); - return CKR_ATTRIBUTE_VALUE_INVALID; + rv = CKR_ATTRIBUTE_VALUE_INVALID; } break; } @@ -8818,7 +8838,7 @@ CK_RV SoftHSM::C_DecapsulateKey if (pTemplate[i].ulValueLen > 0) { INFO_MSG("CKA_CHECK_VALUE must be a no-value (0 length) entry"); - return CKR_ATTRIBUTE_VALUE_INVALID; + rv = CKR_ATTRIBUTE_VALUE_INVALID; } checkValue = false; break; @@ -8829,55 +8849,59 @@ CK_RV SoftHSM::C_DecapsulateKey bool bOK = true; - // Common Attributes - bOK = bOK && osobject->setAttribute(CKA_LOCAL,false); - bOK = bOK && osobject->setAttribute(CKA_ALWAYS_SENSITIVE,false); - bOK = bOK && osobject->setAttribute(CKA_NEVER_EXTRACTABLE,false); + if (rv == CKR_OK) + { - // Secret Attributes - ByteString secretValue = secret->getKeyBits(); - ByteString value; - ByteString plainKCV; - ByteString kcv; + // Common Attributes + bOK = bOK && osobject->setAttribute(CKA_LOCAL,false); + bOK = bOK && osobject->setAttribute(CKA_ALWAYS_SENSITIVE,false); + bOK = bOK && osobject->setAttribute(CKA_NEVER_EXTRACTABLE,false); - // Truncate value when requested, remove from the leading end - if (byteLen < secretValue.size()) - secretValue.resize(byteLen); - // Get the KCV - switch (keyType) - { - case CKK_GENERIC_SECRET: - secret->setBitLen(byteLen * 8); - plainKCV = secret->getKeyCheckValue(); - break; - case CKK_AES: - secret->setBitLen(byteLen * 8); - plainKCV = ((AESKey*)secret)->getKeyCheckValue(); - break; - default: - bOK = false; - break; - } + // Secret Attributes + ByteString secretValue = secret->getKeyBits(); + ByteString value; + ByteString plainKCV; + ByteString kcv; - if (isPrivate) - { - token->encrypt(secretValue, value); - token->encrypt(plainKCV, kcv); - } - else - { - value = secretValue; - kcv = plainKCV; - } - bOK = bOK && osobject->setAttribute(CKA_VALUE, value); - if (checkValue) - bOK = bOK && osobject->setAttribute(CKA_CHECK_VALUE, kcv); - if (bOK) { - bOK = osobject->commitTransaction(); - DEBUG_MSG("phKey: %lx", phKey); - } - else { - osobject->abortTransaction(); + // Truncate value when requested, remove from the leading end + if (byteLen < secretValue.size()) + secretValue.resize(byteLen); + // Get the KCV + switch (keyType) + { + case CKK_GENERIC_SECRET: + secret->setBitLen(byteLen * 8); + plainKCV = secret->getKeyCheckValue(); + break; + case CKK_AES: + secret->setBitLen(byteLen * 8); + plainKCV = ((AESKey*)secret)->getKeyCheckValue(); + break; + default: + bOK = false; + break; + } + + if (isPrivate) + { + token->encrypt(secretValue, value); + token->encrypt(plainKCV, kcv); + } + else + { + value = secretValue; + kcv = plainKCV; + } + bOK = bOK && osobject->setAttribute(CKA_VALUE, value); + if (checkValue) + bOK = bOK && osobject->setAttribute(CKA_CHECK_VALUE, kcv); + if (bOK) { + bOK = osobject->commitTransaction(); + DEBUG_MSG("phKey: %lx", phKey); + } + else { + osobject->abortTransaction(); + } } if (!bOK) diff --git a/src/lib/test/EncapDecapTests.cpp b/src/lib/test/EncapDecapTests.cpp index 20dd46978..b19986592 100644 --- a/src/lib/test/EncapDecapTests.cpp +++ b/src/lib/test/EncapDecapTests.cpp @@ -14,14 +14,12 @@ #include #include "EncapDecapTests.h" -// CKA_TOKEN -const CK_BBOOL ON_TOKEN = CK_TRUE; +#ifdef WITH_ML_KEM const CK_BBOOL IN_SESSION = CK_FALSE; -// CKA_PRIVATE const CK_BBOOL IS_PRIVATE = CK_TRUE; const CK_BBOOL IS_PUBLIC = CK_FALSE; - +#endif CPPUNIT_TEST_SUITE_REGISTRATION(EncapDecapTests); @@ -66,7 +64,6 @@ CK_RV EncapDecapTests::generateMLKEM(CK_ULONG parameterSet, CK_SESSION_HANDLE hS void EncapDecapTests::testMLKEMEncapsulationDecapsulation(CK_ULONG parameterSet) { CK_RV rv; - CK_SESSION_HANDLE hSessionRO; CK_SESSION_HANDLE hSessionRW; CK_MECHANISM mechanism = { CKM_ML_KEM, NULL_PTR, 0 }; CK_BBOOL bTrue = CK_TRUE; @@ -76,24 +73,16 @@ void EncapDecapTests::testMLKEMEncapsulationDecapsulation(CK_ULONG parameterSet) // Just make sure that we finalize any previous tests CRYPTOKI_F_PTR( C_Finalize(NULL_PTR) ); - // Open read-only session on when the token is not initialized should fail - rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &hSessionRO) ); - CPPUNIT_ASSERT(rv == CKR_CRYPTOKI_NOT_INITIALIZED); - // Initialize the library and start the test. rv = CRYPTOKI_F_PTR( C_Initialize(NULL_PTR) ); CPPUNIT_ASSERT(rv == CKR_OK); - // Open read-only session - rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &hSessionRO) ); - CPPUNIT_ASSERT(rv == CKR_OK); - // Open read-write session rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSessionRW) ); CPPUNIT_ASSERT(rv == CKR_OK); // Login USER into the sessions so we can create a private objects - rv = CRYPTOKI_F_PTR( C_Login(hSessionRO,CKU_USER,m_userPin1,m_userPin1Length) ); + rv = CRYPTOKI_F_PTR( C_Login(hSessionRW,CKU_USER,m_userPin1,m_userPin1Length) ); CPPUNIT_ASSERT(rv==CKR_OK); CK_OBJECT_HANDLE hPuk = CK_INVALID_HANDLE; @@ -193,6 +182,272 @@ void EncapDecapTests::testMLKEMEncapsulationDecapsulation(CK_ULONG parameterSet) CPPUNIT_ASSERT(rv == CKR_OK); } +void EncapDecapTests::testMLKEMEncapsulationCiphertextIsNull() +{ + CK_RV rv; + CK_SESSION_HANDLE hSessionRW; + CK_MECHANISM mechanism = { CKM_ML_KEM, NULL_PTR, 0 }; + CK_BBOOL bTrue = CK_TRUE; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_ULONG parameterSet = CKP_ML_KEM_512; + + // Just make sure that we finalize any previous tests + CRYPTOKI_F_PTR( C_Finalize(NULL_PTR) ); + + // Initialize the library and start the test. + rv = CRYPTOKI_F_PTR( C_Initialize(NULL_PTR) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Open read-write session + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSessionRW) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Login USER into the sessions so we can create a private objects + rv = CRYPTOKI_F_PTR( C_Login(hSessionRW,CKU_USER,m_userPin1,m_userPin1Length) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + CK_OBJECT_HANDLE hPuk = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE hPrk = CK_INVALID_HANDLE; + + // Generate ML-KEM key pair + rv = generateMLKEM(parameterSet, hSessionRW, IN_SESSION, IS_PUBLIC, IN_SESSION, IS_PRIVATE, hPuk, hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hPuk != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(hPrk != CK_INVALID_HANDLE); + + // Test encapsulation - create a new AES key through encapsulation + CK_ATTRIBUTE keyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + CK_OBJECT_HANDLE hEncapKey = CK_INVALID_HANDLE; + CK_ULONG ulCipherTextLen = sizeof(CK_ULONG); + + rv = CRYPTOKI_F_PTR(C_EncapsulateKey( + hSessionRW, + &mechanism, + hPuk, + keyAttribs, + sizeof(keyAttribs) / sizeof(CK_ATTRIBUTE), + NULL_PTR, // Passing NULL_PTR for ciphertext to test behavior + &ulCipherTextLen, + &hEncapKey + )); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hEncapKey == CK_INVALID_HANDLE); + CPPUNIT_ASSERT(ulCipherTextLen > 0); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPuk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPrk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Close the session + rv = CRYPTOKI_F_PTR(C_CloseSession(hSessionRW)); + CPPUNIT_ASSERT(rv == CKR_OK); +} + +void EncapDecapTests::testMLKEMDecapsulationCiphertextLengthIsZero() +{ + CK_RV rv; + CK_SESSION_HANDLE hSessionRW; + CK_MECHANISM mechanism = { CKM_ML_KEM, NULL_PTR, 0 }; + CK_BBOOL bTrue = CK_TRUE; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_ULONG parameterSet = CKP_ML_KEM_512; + + // Just make sure that we finalize any previous tests + CRYPTOKI_F_PTR( C_Finalize(NULL_PTR) ); + + // Initialize the library and start the test. + rv = CRYPTOKI_F_PTR( C_Initialize(NULL_PTR) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Open read-write session + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSessionRW) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Login USER into the sessions so we can create a private objects + rv = CRYPTOKI_F_PTR( C_Login(hSessionRW,CKU_USER,m_userPin1,m_userPin1Length) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + CK_OBJECT_HANDLE hPuk = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE hPrk = CK_INVALID_HANDLE; + + // Generate ML-KEM key pair + rv = generateMLKEM(parameterSet, hSessionRW, IN_SESSION, IS_PUBLIC, IN_SESSION, IS_PRIVATE, hPuk, hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hPuk != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(hPrk != CK_INVALID_HANDLE); + + // Test encapsulation - create a new AES key through encapsulation + CK_ATTRIBUTE keyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + CK_OBJECT_HANDLE hEncapKey = CK_INVALID_HANDLE; + CK_BYTE cipherText[2048]; + CK_ULONG ulCipherTextLen = sizeof(cipherText); + memset(cipherText, 0, sizeof(cipherText)); + + rv = CRYPTOKI_F_PTR(C_EncapsulateKey( + hSessionRW, + &mechanism, + hPuk, + keyAttribs, + sizeof(keyAttribs) / sizeof(CK_ATTRIBUTE), + cipherText, + &ulCipherTextLen, + &hEncapKey + )); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hEncapKey != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(ulCipherTextLen > 0); + + // Test decapsulation - recover the shared secret using the ciphertext + CK_OBJECT_HANDLE hDecapKey = CK_INVALID_HANDLE; + + CK_ATTRIBUTE decapKeyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + rv = CRYPTOKI_F_PTR(C_DecapsulateKey( + hSessionRW, + &mechanism, + hPrk, + decapKeyAttribs, + sizeof(decapKeyAttribs) / sizeof(CK_ATTRIBUTE), + cipherText, + 0, // Passing 0 for ciphertext length to test behavior + &hDecapKey + )); + CPPUNIT_ASSERT(rv == CKR_ATTRIBUTE_VALUE_INVALID); + + // Clean up + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hEncapKey)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPuk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPrk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Close the session + rv = CRYPTOKI_F_PTR(C_CloseSession(hSessionRW)); + CPPUNIT_ASSERT(rv == CKR_OK); +} + +void EncapDecapTests::testMLKEMDecapsulationCiphertextIsNull() +{ + CK_RV rv; + CK_SESSION_HANDLE hSessionRW; + CK_MECHANISM mechanism = { CKM_ML_KEM, NULL_PTR, 0 }; + CK_BBOOL bTrue = CK_TRUE; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_ULONG parameterSet = CKP_ML_KEM_512; + + // Just make sure that we finalize any previous tests + CRYPTOKI_F_PTR( C_Finalize(NULL_PTR) ); + + // Initialize the library and start the test. + rv = CRYPTOKI_F_PTR( C_Initialize(NULL_PTR) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Open read-write session + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSessionRW) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Login USER into the sessions so we can create a private objects + rv = CRYPTOKI_F_PTR( C_Login(hSessionRW,CKU_USER,m_userPin1,m_userPin1Length) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + CK_OBJECT_HANDLE hPuk = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE hPrk = CK_INVALID_HANDLE; + + // Generate ML-KEM key pair + rv = generateMLKEM(parameterSet, hSessionRW, IN_SESSION, IS_PUBLIC, IN_SESSION, IS_PRIVATE, hPuk, hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hPuk != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(hPrk != CK_INVALID_HANDLE); + + // Test encapsulation - create a new AES key through encapsulation + CK_ATTRIBUTE keyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + CK_OBJECT_HANDLE hEncapKey = CK_INVALID_HANDLE; + CK_BYTE cipherText[2048]; + CK_ULONG ulCipherTextLen = sizeof(cipherText); + memset(cipherText, 0, sizeof(cipherText)); + + rv = CRYPTOKI_F_PTR(C_EncapsulateKey( + hSessionRW, + &mechanism, + hPuk, + keyAttribs, + sizeof(keyAttribs) / sizeof(CK_ATTRIBUTE), + cipherText, + &ulCipherTextLen, + &hEncapKey + )); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hEncapKey != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(ulCipherTextLen > 0); + + // Test decapsulation - recover the shared secret using the ciphertext + CK_OBJECT_HANDLE hDecapKey = CK_INVALID_HANDLE; + + CK_ATTRIBUTE decapKeyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + rv = CRYPTOKI_F_PTR(C_DecapsulateKey( + hSessionRW, + &mechanism, + hPrk, + decapKeyAttribs, + sizeof(decapKeyAttribs) / sizeof(CK_ATTRIBUTE), + NULL_PTR, // Passing NULL_PTR for ciphertext to test behavior + ulCipherTextLen, + &hDecapKey + )); + CPPUNIT_ASSERT(rv == CKR_ATTRIBUTE_VALUE_INVALID); + + // Clean up + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hEncapKey)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPuk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPrk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Close the session + rv = CRYPTOKI_F_PTR(C_CloseSession(hSessionRW)); + CPPUNIT_ASSERT(rv == CKR_OK); +} + void EncapDecapTests::testMLKEMEncapsulationDecapsulationWithValueLen(CK_ULONG ulValueLen) { CK_RV rv; @@ -265,14 +520,13 @@ void EncapDecapTests::testMLKEMEncapsulationDecapsulationWithValueLen(CK_ULONG u CPPUNIT_ASSERT(ulCipherTextLen > 0); // Verify the decapsulated key has the correct length - CK_BYTE encapKeyLength[64]; + CK_ULONG encapKeyLength = 0; CK_ULONG encapKeyLengthLen = sizeof(encapKeyLength); - CK_ATTRIBUTE getEncapKeyValueAttrib = { CKA_VALUE_LEN, encapKeyLength, encapKeyLengthLen }; + CK_ATTRIBUTE getEncapKeyValueAttrib = { CKA_VALUE_LEN, &encapKeyLength, encapKeyLengthLen }; rv = CRYPTOKI_F_PTR(C_GetAttributeValue(hSessionRW, hEncapKey, &getEncapKeyValueAttrib, 1)); CPPUNIT_ASSERT(rv == CKR_OK); - encapKeyLengthLen = *(CK_ULONG*)getEncapKeyValueAttrib.pValue; - CPPUNIT_ASSERT(encapKeyLengthLen == ulValueLen); + CPPUNIT_ASSERT(encapKeyLength == ulValueLen); // Get key check value for encapsulated key CK_BYTE encapKCV[64]; @@ -308,14 +562,13 @@ void EncapDecapTests::testMLKEMEncapsulationDecapsulationWithValueLen(CK_ULONG u CPPUNIT_ASSERT(hDecapKey != CK_INVALID_HANDLE); // Verify the decapsulated key has the correct length - CK_BYTE decapKeyLength[64]; + CK_ULONG decapKeyLength = 0; CK_ULONG decapKeyLengthLen = sizeof(decapKeyLength); - CK_ATTRIBUTE getDecapKeyValueAttrib = { CKA_VALUE_LEN, decapKeyLength, decapKeyLengthLen }; + CK_ATTRIBUTE getDecapKeyValueAttrib = { CKA_VALUE_LEN, &decapKeyLength, decapKeyLengthLen }; rv = CRYPTOKI_F_PTR(C_GetAttributeValue(hSessionRW, hDecapKey, &getDecapKeyValueAttrib, 1)); CPPUNIT_ASSERT(rv == CKR_OK); - decapKeyLengthLen = *(CK_ULONG*)getDecapKeyValueAttrib.pValue; - CPPUNIT_ASSERT(decapKeyLengthLen == ulValueLen); + CPPUNIT_ASSERT(decapKeyLength == ulValueLen); // Get key check value for decapsulated key CK_BYTE decapKCV[64]; @@ -348,4 +601,78 @@ void EncapDecapTests::testMLKEMEncapsulationDecapsulationWithValueLen(CK_ULONG u CPPUNIT_ASSERT(rv == CKR_OK); } +void EncapDecapTests::testMLKEMEncapsulationDecapsulationWithValueLenWrong() +{ + CK_RV rv; + CK_SESSION_HANDLE hSessionRW; + CK_MECHANISM mechanism = { CKM_ML_KEM, NULL_PTR, 0 }; + CK_BBOOL bTrue = CK_TRUE; + CK_KEY_TYPE keyType = CKK_AES; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_ULONG parameterSet = CKP_ML_KEM_512; + CK_ULONG ulValueLen = 10; // Intentionally incorrect value length for testing + + // Just make sure that we finalize any previous tests + CRYPTOKI_F_PTR( C_Finalize(NULL_PTR) ); + + // Initialize the library and start the test. + rv = CRYPTOKI_F_PTR( C_Initialize(NULL_PTR) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Open read-write session + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSessionRW) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Login USER into the sessions so we can create a private objects + rv = CRYPTOKI_F_PTR( C_Login(hSessionRW,CKU_USER,m_userPin1,m_userPin1Length) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + CK_OBJECT_HANDLE hPuk = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE hPrk = CK_INVALID_HANDLE; + + // Generate ML-KEM key pair + rv = generateMLKEM(parameterSet, hSessionRW, IN_SESSION, IS_PUBLIC, IN_SESSION, IS_PRIVATE, hPuk, hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + CPPUNIT_ASSERT(hPuk != CK_INVALID_HANDLE); + CPPUNIT_ASSERT(hPrk != CK_INVALID_HANDLE); + + // Test encapsulation with specific key length + CK_ATTRIBUTE keyAttribs[] = { + { CKA_CLASS, &keyClass, sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_VALUE_LEN, &ulValueLen, sizeof(ulValueLen) }, + { CKA_ENCRYPT, &bTrue, sizeof(bTrue) }, + { CKA_DECRYPT, &bTrue, sizeof(bTrue) } + }; + + CK_OBJECT_HANDLE hEncapKey = CK_INVALID_HANDLE; + CK_BYTE cipherText[2048]; + CK_ULONG ulCipherTextLen = sizeof(cipherText); + memset(cipherText, 0, sizeof(cipherText)); + + rv = CRYPTOKI_F_PTR(C_EncapsulateKey( + hSessionRW, + &mechanism, + hPuk, + keyAttribs, + sizeof(keyAttribs) / sizeof(CK_ATTRIBUTE), + cipherText, + &ulCipherTextLen, + &hEncapKey + )); + CPPUNIT_ASSERT(rv == CKR_ATTRIBUTE_VALUE_INVALID); + CPPUNIT_ASSERT(hEncapKey == CK_INVALID_HANDLE); + CPPUNIT_ASSERT(ulCipherTextLen == 0); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPuk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR(C_DestroyObject(hSessionRW, hPrk)); + CPPUNIT_ASSERT(rv == CKR_OK); + + // Close the session + rv = CRYPTOKI_F_PTR(C_CloseSession(hSessionRW)); + CPPUNIT_ASSERT(rv == CKR_OK); +} + #endif diff --git a/src/lib/test/EncapDecapTests.h b/src/lib/test/EncapDecapTests.h index e923511b9..d9888580f 100644 --- a/src/lib/test/EncapDecapTests.h +++ b/src/lib/test/EncapDecapTests.h @@ -22,6 +22,10 @@ class EncapDecapTests : public TestsBase #ifdef WITH_ML_KEM CPPUNIT_TEST_PARAMETERIZED(testMLKEMEncapsulationDecapsulation, {CKP_ML_KEM_512, CKP_ML_KEM_768, CKP_ML_KEM_1024}); CPPUNIT_TEST_PARAMETERIZED(testMLKEMEncapsulationDecapsulationWithValueLen, {16, 24, 32}); + CPPUNIT_TEST(testMLKEMEncapsulationDecapsulationWithValueLenWrong); + CPPUNIT_TEST(testMLKEMEncapsulationCiphertextIsNull); + CPPUNIT_TEST(testMLKEMDecapsulationCiphertextIsNull); + CPPUNIT_TEST(testMLKEMDecapsulationCiphertextLengthIsZero); #endif CPPUNIT_TEST_SUITE_END(); @@ -29,6 +33,10 @@ class EncapDecapTests : public TestsBase #ifdef WITH_ML_KEM void testMLKEMEncapsulationDecapsulation(CK_ULONG parameterSet); void testMLKEMEncapsulationDecapsulationWithValueLen(CK_ULONG ulValueLen); + void testMLKEMEncapsulationDecapsulationWithValueLenWrong(); + void testMLKEMEncapsulationCiphertextIsNull(); + void testMLKEMDecapsulationCiphertextIsNull(); + void testMLKEMDecapsulationCiphertextLengthIsZero(); #endif protected: