diff --git a/src/bin/util/softhsm2-util-botan.cpp b/src/bin/util/softhsm2-util-botan.cpp index b0cc5a1da..d80763beb 100644 --- a/src/bin/util/softhsm2-util-botan.cpp +++ b/src/bin/util/softhsm2-util-botan.cpp @@ -837,7 +837,7 @@ eddsa_key_material_t* crypto_malloc_eddsa return NULL; } - eddsa_key_material_t* keyMat = (eddsa_key_material_t*)malloc(sizeof(eddsa_key_material_t)); + eddsa_key_material_t* keyMat = (eddsa_key_material_t*)calloc(1, sizeof(eddsa_key_material_t)); if (keyMat == NULL) { return NULL; @@ -848,13 +848,13 @@ eddsa_key_material_t* crypto_malloc_eddsa if (ed25519) oid = Botan::OIDS::lookup("Ed25519"); if (oid.empty()) { + crypto_free_eddsa(keyMat); return NULL; } Botan::secure_vector derOID; derOID = Botan::DER_Encoder().encode(oid).get_contents(); - memset(keyMat, 0, sizeof(*keyMat)); keyMat->sizeOID = derOID.size(); keyMat->derOID = (CK_VOID_PTR)malloc(keyMat->sizeOID); diff --git a/src/lib/SoftHSM.cpp b/src/lib/SoftHSM.cpp index e5cd0cab5..e6627cd43 100644 --- a/src/lib/SoftHSM.cpp +++ b/src/lib/SoftHSM.cpp @@ -58,6 +58,10 @@ #include "ECParameters.h" #include "EDPublicKey.h" #include "EDPrivateKey.h" +#ifdef WITH_EDDSA +#include "EDDSAMechanismParam.h" +#include "EDDSAUtil.h" +#endif #include "DHParameters.h" #include "DHPublicKey.h" #include "DHPrivateKey.h" @@ -4320,6 +4324,7 @@ CK_RV SoftHSM::AsymSignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechan #endif #ifdef WITH_EDDSA bool isEDDSA = false; + EDDSAMechanismParam eddsaParam; #endif #ifdef WITH_ML_DSA bool isMLDSA = false; @@ -4582,10 +4587,17 @@ CK_RV SoftHSM::AsymSignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechan #endif #ifdef WITH_EDDSA case CKM_EDDSA: + { mechanism = AsymMech::EDDSA; bAllowMultiPartOp = false; isEDDSA = true; + CK_RV eddsaRv = EDDSAUtil::getEddsaParam(pMechanism, eddsaParam, &mechanismParam); + if (eddsaRv != CKR_OK) + { + return eddsaRv; + } break; + } #endif #ifdef WITH_ML_DSA case CKM_ML_DSA: @@ -5410,6 +5422,7 @@ CK_RV SoftHSM::AsymVerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMech #endif #ifdef WITH_EDDSA bool isEDDSA = false; + EDDSAMechanismParam eddsaParam; #endif #ifdef WITH_ML_DSA bool isMLDSA = false; @@ -5671,10 +5684,17 @@ CK_RV SoftHSM::AsymVerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMech #endif #ifdef WITH_EDDSA case CKM_EDDSA: + { mechanism = AsymMech::EDDSA; bAllowMultiPartOp = false; isEDDSA = true; + CK_RV eddsaRv = EDDSAUtil::getEddsaParam(pMechanism, eddsaParam, &mechanismParam); + if (eddsaRv != CKR_OK) + { + return eddsaRv; + } break; + } #endif #ifdef WITH_ML_DSA case CKM_ML_DSA: diff --git a/src/lib/crypto/BotanEDDSA.cpp b/src/lib/crypto/BotanEDDSA.cpp index f10a07877..4a164f76d 100644 --- a/src/lib/crypto/BotanEDDSA.cpp +++ b/src/lib/crypto/BotanEDDSA.cpp @@ -38,6 +38,7 @@ #include "CryptoFactory.h" #include "BotanCryptoFactory.h" #include "ECParameters.h" +#include "EDDSAMechanismParam.h" #include "BotanEDKeyPair.h" #include "BotanUtil.h" #include @@ -62,22 +63,56 @@ BotanEDDSA::~BotanEDDSA() delete verifier; } +// Select the Botan EMSA for the requested EdDSA instance +bool BotanEDDSA::selectEmsa(std::string& emsa, size_t orderLength, const MechanismParam* mechanismParam) +{ + emsa = "Pure"; + + if (mechanismParam == NULL || !mechanismParam->isOfType(EDDSAMechanismParam::type)) + { + return true; + } + + const EDDSAMechanismParam* eddsaParam = (const EDDSAMechanismParam*) mechanismParam; + + if (eddsaParam->contextData.size() > 0) + { + ERROR_MSG("EDDSA: Context data is not supported in the Botan 2 EDDSA implementation"); + return false; + } + + if (!eddsaParam->flag) + { + return true; + } + + // Ed25519: orderLength = 32, Ed448: orderLength = 57 + if (orderLength == 32) + { + emsa = "Ed25519ph"; + return true; + } + + if (orderLength == 57) + { + ERROR_MSG("EDDSA: Ed448 is not supported in the Botan 2 EDDSA implementation"); + return false; + } + + ERROR_MSG("EDDSA: Unknown EdDSA key size: %lu", (unsigned long) orderLength); + return false; +} + // Signing functions bool BotanEDDSA::sign(PrivateKey* privateKey, const ByteString& dataToSign, ByteString& signature, const AsymMech::Type mechanism, - const MechanismParam* /* mechanismParam */) + const MechanismParam* mechanismParam) { - std::string emsa; - - if (mechanism == AsymMech::EDDSA) + if (mechanism != AsymMech::EDDSA) { - emsa = "Pure"; - } - else - { ERROR_MSG("Invalid mechanism supplied (%i)", mechanism); return false; - } + } // Check if the private key is the right type if (!privateKey->isOfType(BotanEDPrivateKey::type)) @@ -87,16 +122,23 @@ bool BotanEDDSA::sign(PrivateKey* privateKey, const ByteString& dataToSign, return false; } - BotanEDPrivateKey* pk = (BotanEDPrivateKey*) privateKey; - Botan::Ed25519_PrivateKey* botanKey = dynamic_cast(pk->getBotanKey()); + BotanEDPrivateKey* pk = (BotanEDPrivateKey*) privateKey; + Botan::Ed25519_PrivateKey* botanKey = dynamic_cast(pk->getBotanKey()); - if (botanKey == NULL) - { + if (botanKey == NULL) + { ERROR_MSG("Could not get the Botan private key"); return false; } + std::string emsa; + if (!selectEmsa(emsa, pk->getOrderLength(), mechanismParam)) + { + ERROR_MSG("Could not select the EMSA for the Botan 2 EDDSA implementation"); + return false; + } + try { BotanRNG* rng = (BotanRNG*)BotanCryptoFactory::i()->getRNG(); @@ -104,7 +146,7 @@ bool BotanEDDSA::sign(PrivateKey* privateKey, const ByteString& dataToSign, } catch (...) { - ERROR_MSG("Could not create the signer token"); + ERROR_MSG("Could not create the signer token. emsa: %s", emsa.c_str()); return false; } @@ -162,16 +204,10 @@ bool BotanEDDSA::signFinal(ByteString& /*signature*/) // Verification functions bool BotanEDDSA::verify(PublicKey* publicKey, const ByteString& originalData, const ByteString& signature, const AsymMech::Type mechanism, - const MechanismParam* /* mechanismParam */) + const MechanismParam* mechanismParam) { - std::string emsa; - - if (mechanism == AsymMech::EDDSA) + if (mechanism != AsymMech::EDDSA) { - emsa = "Pure"; - } - else - { ERROR_MSG("Invalid mechanism supplied (%i)", mechanism); return false; @@ -195,13 +231,20 @@ bool BotanEDDSA::verify(PublicKey* publicKey, const ByteString& originalData, return false; } + std::string emsa; + if (!selectEmsa(emsa, pk->getOrderLength(), mechanismParam)) + { + ERROR_MSG("Could not select the EMSA for the Botan 2 EDDSA implementation"); + return false; + } + try { verifier = new Botan::PK_Verifier(*botanKey, emsa); } catch (...) { - ERROR_MSG("Could not create the verifier token"); + ERROR_MSG("Could not create the verifier token. emsa: %s", emsa.c_str()); return false; } diff --git a/src/lib/crypto/BotanEDDSA.h b/src/lib/crypto/BotanEDDSA.h index de7af0c7f..82b864a33 100644 --- a/src/lib/crypto/BotanEDDSA.h +++ b/src/lib/crypto/BotanEDDSA.h @@ -80,6 +80,9 @@ class BotanEDDSA : public AsymmetricAlgorithm virtual AsymmetricParameters* newParameters(); private: + // Derive the Botan EMSA from the key size and the pre-hash flag; context data is not supported + static bool selectEmsa(std::string& emsa, size_t orderLength, const MechanismParam* mechanismParam); + Botan::PK_Signer* signer; Botan::PK_Verifier* verifier; }; diff --git a/src/lib/crypto/CMakeLists.txt b/src/lib/crypto/CMakeLists.txt index fe0d47f7b..113fcabc2 100644 --- a/src/lib/crypto/CMakeLists.txt +++ b/src/lib/crypto/CMakeLists.txt @@ -26,6 +26,8 @@ set(SOURCES AESKey.cpp ECPublicKey.cpp EDPrivateKey.cpp EDPublicKey.cpp + EDDSAMechanismParam.cpp + EDDSAUtil.cpp GOSTPrivateKey.cpp GOSTPublicKey.cpp HashAlgorithm.cpp @@ -156,7 +158,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") list(APPEND INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/../win32) ENDIF() -if(WITH_BOTAN) +if(WITH_BOTAN AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") # mute botan specific warnings # https://github.com/randombit/botan/issues/486 list(APPEND COMPILE_OPTIONS "/wd4250;/wd4251;/wd4275;/wd4127;/wd4273") diff --git a/src/lib/crypto/EDDSAMechanismParam.cpp b/src/lib/crypto/EDDSAMechanismParam.cpp new file mode 100644 index 000000000..cf250e9c6 --- /dev/null +++ b/src/lib/crypto/EDDSAMechanismParam.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2026 SoftHSMv2 contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ +/***************************************************************************** + EDDSAMechanismParam.cpp + + EdDSA mechanism parameters used for signing/verifying operations + *****************************************************************************/ + +#include "config.h" +#ifdef WITH_EDDSA +#include "ByteString.h" +#include "MechanismParam.h" +#include "EDDSAMechanismParam.h" +#include + +EDDSAMechanismParam::EDDSAMechanismParam() +{ + flag = false; +} + +// Set the type +/*static*/ const char* EDDSAMechanismParam::type = "EdDSA Signature param"; + +EDDSAMechanismParam* EDDSAMechanismParam::clone() const +{ + return new EDDSAMechanismParam(*this); +} + +// Check if the parameter is of the given type +bool EDDSAMechanismParam::isOfType(const char* inType) const +{ + return !strcmp(type, inType); +} +#endif diff --git a/src/lib/crypto/EDDSAMechanismParam.h b/src/lib/crypto/EDDSAMechanismParam.h new file mode 100644 index 000000000..1ebae5243 --- /dev/null +++ b/src/lib/crypto/EDDSAMechanismParam.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2026 SoftHSMv2 contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ +/***************************************************************************** + EDDSAMechanismParam.h + + EdDSA mechanism parameters used for signing/verifying operations + *****************************************************************************/ + +#ifndef _SOFTHSM_V2_EDDSAMECHANISMPARAM_H +#define _SOFTHSM_V2_EDDSAMECHANISMPARAM_H + +#include "config.h" +#ifdef WITH_EDDSA +#include "ByteString.h" +#include "MechanismParam.h" + +class EDDSAMechanismParam : public MechanismParam +{ +public: + + // EdDSA parameters from ck_eddsa_params + bool flag; // false = no pre-hash (pure), true = with pre-hash (ph) + ByteString contextData; + + // The type + static const char* type; + + EDDSAMechanismParam(); + + virtual EDDSAMechanismParam* clone() const; + + // Check if the mechanism param is of the given type + virtual bool isOfType(const char* inType) const; +}; + +#endif // WITH_EDDSA +#endif // !_SOFTHSM_V2_EDDSAMECHANISMPARAM_H diff --git a/src/lib/crypto/EDDSAUtil.cpp b/src/lib/crypto/EDDSAUtil.cpp new file mode 100644 index 000000000..32f931b92 --- /dev/null +++ b/src/lib/crypto/EDDSAUtil.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2026 SoftHSMv2 contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ +/***************************************************************************** + EDDSAUtil.cpp + + EdDSA convenience functions + *****************************************************************************/ + +#include "config.h" +#ifdef WITH_EDDSA +#include "EDDSAUtil.h" +#include "log.h" + +// Maximum context data length allowed by CK_EDDSA_PARAMS +static const CK_ULONG maxEddsaContextDataLen = 255; + +/*static*/ CK_RV EDDSAUtil::getEddsaParam(CK_MECHANISM_PTR pMechanism, EDDSAMechanismParam& eddsaParam, MechanismParam** mechanismParam) +{ + if (pMechanism->pParameter == NULL_PTR) + { + if (pMechanism->ulParameterLen != 0) + { + ERROR_MSG("EDDSA: No parameters supplied but ulParameterLen is %lu", (unsigned long) pMechanism->ulParameterLen); + return CKR_ARGUMENTS_BAD; + } + + // No parameters means the pure variant without context data + return CKR_OK; + } + + if (pMechanism->ulParameterLen != sizeof(CK_EDDSA_PARAMS)) + { + ERROR_MSG("EDDSA: ulParameterLen is %lu, expected %lu", (unsigned long) pMechanism->ulParameterLen, (unsigned long) sizeof(CK_EDDSA_PARAMS)); + return CKR_ARGUMENTS_BAD; + } + + CK_EDDSA_PARAMS* ckEddsaParams = (CK_EDDSA_PARAMS*) pMechanism->pParameter; + + if (ckEddsaParams->ulContextDataLen > maxEddsaContextDataLen) + { + ERROR_MSG("EDDSA: ulContextDataLen is %lu, maximum is %lu", (unsigned long) ckEddsaParams->ulContextDataLen, (unsigned long) maxEddsaContextDataLen); + return CKR_ARGUMENTS_BAD; + } + + if (ckEddsaParams->ulContextDataLen > 0 && ckEddsaParams->pContextData == NULL_PTR) + { + ERROR_MSG("EDDSA: ulContextDataLen is %lu but pContextData is NULL", (unsigned long) ckEddsaParams->ulContextDataLen); + return CKR_ARGUMENTS_BAD; + } + + eddsaParam.flag = (ckEddsaParams->phFlag != CK_FALSE); + if (ckEddsaParams->ulContextDataLen > 0) + { + eddsaParam.contextData = ByteString(ckEddsaParams->pContextData, ckEddsaParams->ulContextDataLen); + } + + DEBUG_MSG("EDDSA: preHash=%s, contextDataLen=%lu", eddsaParam.flag ? "true" : "false", (unsigned long) eddsaParam.contextData.size()); + + *mechanismParam = &eddsaParam; + + return CKR_OK; +} +#endif diff --git a/src/lib/crypto/EDDSAUtil.h b/src/lib/crypto/EDDSAUtil.h new file mode 100644 index 000000000..c13697945 --- /dev/null +++ b/src/lib/crypto/EDDSAUtil.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2026 SoftHSMv2 contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ +/***************************************************************************** + EDDSAUtil.h + + EdDSA convenience functions + *****************************************************************************/ + +#ifndef _SOFTHSM_V2_EDDSAUTIL_H +#define _SOFTHSM_V2_EDDSAUTIL_H + +#include "config.h" +#ifdef WITH_EDDSA +#include "EDDSAMechanismParam.h" +#include "MechanismParam.h" +#include "cryptoki.h" + +class EDDSAUtil +{ +public: + EDDSAUtil() = delete; + + // Translate the CKM_EDDSA mechanism parameters, if any, into an EDDSAMechanismParam + static CK_RV getEddsaParam(CK_MECHANISM_PTR pMechanism, EDDSAMechanismParam& eddsaParam, MechanismParam** mechanismParam); +}; + +#endif // !WITH_EDDSA +#endif // !_SOFTHSM_V2_EDDSAUTIL_H diff --git a/src/lib/crypto/Makefile.am b/src/lib/crypto/Makefile.am index 781375be0..75d82fce0 100644 --- a/src/lib/crypto/Makefile.am +++ b/src/lib/crypto/Makefile.am @@ -26,6 +26,8 @@ libsofthsm_crypto_la_SOURCES = AESKey.cpp \ ECPrivateKey.cpp \ EDPublicKey.cpp \ EDPrivateKey.cpp \ + EDDSAMechanismParam.cpp \ + EDDSAUtil.cpp \ GOSTPublicKey.cpp \ GOSTPrivateKey.cpp \ HashAlgorithm.cpp \ diff --git a/src/lib/crypto/OSSLEDDSA.cpp b/src/lib/crypto/OSSLEDDSA.cpp index 58cf7aebd..6096a0709 100644 --- a/src/lib/crypto/OSSLEDDSA.cpp +++ b/src/lib/crypto/OSSLEDDSA.cpp @@ -36,6 +36,7 @@ #include "OSSLEDDSA.h" #include "CryptoFactory.h" #include "ECParameters.h" +#include "EDDSAMechanismParam.h" #include "OSSLEDKeyPair.h" #include "OSSLComp.h" #include "OSSLUtil.h" @@ -43,23 +44,80 @@ #include #include #include +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#include +#include +#endif #include +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +bool OSSLEDDSA::buildInstanceParams(OSSL_PARAM* params, size_t orderLength, bool preHash, const ByteString& contextData, const char* operation) +{ + // Select the instance based on key type and pre-hash flag + // Ed25519: orderLength = 32, Ed448: orderLength = 57 + const char* instance = NULL; + + DEBUG_MSG("%s with key of order length %zu, contextData.size(): %zu, keySize: %zu, preHash: %s", operation, orderLength, contextData.size(), orderLength, preHash ? "true" : "false"); + + if (orderLength == 32) + { + if (preHash) + { + instance = "Ed25519ph"; + } + else if (contextData.size() > 0) + { + instance = "Ed25519ctx"; + } + } + else if (orderLength == 57) + { + if (preHash) + { + instance = "Ed448ph"; + } + else + { + instance = "Ed448"; + } + } + else + { + ERROR_MSG("EDDSA: Unknown EdDSA key size: %zu", orderLength); + return false; + } + + size_t i = 0; + if (instance != NULL) + { + DEBUG_MSG("Setting instance for %s: %s", operation, instance); + params[i++] = OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_INSTANCE, (char*) instance, 0); + } + if (contextData.size() > 0) + { + params[i++] = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, (void*) contextData.const_byte_str(), contextData.size()); + } + params[i] = OSSL_PARAM_construct_end(); + + return true; +} +#endif + // Signing functions bool OSSLEDDSA::sign(PrivateKey* privateKey, const ByteString& dataToSign, ByteString& signature, const AsymMech::Type mechanism, - const MechanismParam* /* mechanismParam */) + const MechanismParam* mechanismParam) { if (mechanism != AsymMech::EDDSA) { - ERROR_MSG("Invalid mechanism supplied (%i)", mechanism); + ERROR_MSG("EDDSA: Invalid mechanism supplied (%i)", mechanism); return false; } // Check if the private key is the right type if (!privateKey->isOfType(OSSLEDPrivateKey::type)) { - ERROR_MSG("Invalid key type supplied"); + ERROR_MSG("EDDSA: Invalid key type supplied"); return false; } @@ -69,11 +127,28 @@ bool OSSLEDDSA::sign(PrivateKey* privateKey, const ByteString& dataToSign, if (pkey == NULL) { - ERROR_MSG("Could not get the OpenSSL private key"); + ERROR_MSG("EDDSA: Could not get the OpenSSL private key"); return false; } +#if OPENSSL_VERSION_NUMBER >= 0x30200000L + // Extract context parameters if provided + bool preHash = false; + ByteString contextData; +#endif + if (mechanismParam != NULL && mechanismParam->isOfType(EDDSAMechanismParam::type)) + { +#if OPENSSL_VERSION_NUMBER >= 0x30200000L + EDDSAMechanismParam* eddsaParam = (EDDSAMechanismParam*) mechanismParam; + preHash = eddsaParam->flag; + contextData = eddsaParam->contextData; +#else + ERROR_MSG("Context data is not supported in OpenSSL < 3.2"); + return false; +#endif + } + // Perform the signature operation size_t len = pk->getOrderLength(); if (len == 0) @@ -85,18 +160,34 @@ bool OSSLEDDSA::sign(PrivateKey* privateKey, const ByteString& dataToSign, signature.resize(len); memset(&signature[0], 0, len); EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + +#if OPENSSL_VERSION_NUMBER >= 0x30200000L + OSSL_PARAM params[3]; + if (!buildInstanceParams(params, pk->getOrderLength(), preHash, contextData, "Signing")) + { + EVP_MD_CTX_free(ctx); + return false; + } + + if (!EVP_DigestSignInit_ex(ctx, NULL, NULL, NULL, NULL, pkey, params)) +#else if (!EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey)) +#endif { - ERROR_MSG("EDDSA sign init failed (0x%08X)", ERR_get_error()); + long err = ERR_get_error(); + ERROR_MSG("EDDSA sign init failed (0x%08X): %s", err, ERR_error_string(err, NULL)); EVP_MD_CTX_free(ctx); return false; } + if (!EVP_DigestSign(ctx, &signature[0], &len, dataToSign.const_byte_str(), dataToSign.size())) { - ERROR_MSG("EDDSA sign failed (0x%08X)", ERR_get_error()); + long err = ERR_get_error(); + ERROR_MSG("EDDSA sign failed (0x%08X): %s", err, ERR_error_string(err, NULL)); EVP_MD_CTX_free(ctx); return false; } + DEBUG_MSG("Signature length: %zu, signature: %s", len, signature.hex_str().c_str()); EVP_MD_CTX_free(ctx); return true; } @@ -126,7 +217,7 @@ bool OSSLEDDSA::signFinal(ByteString& /*signature*/) // Verification functions bool OSSLEDDSA::verify(PublicKey* publicKey, const ByteString& originalData, const ByteString& signature, const AsymMech::Type mechanism, - const MechanismParam* /* mechanismParam */) + const MechanismParam* mechanismParam) { if (mechanism != AsymMech::EDDSA) { @@ -152,6 +243,23 @@ bool OSSLEDDSA::verify(PublicKey* publicKey, const ByteString& originalData, return false; } +#if OPENSSL_VERSION_NUMBER >= 0x30200000L + // Extract context parameters if provided + bool preHash = false; + ByteString contextData; +#endif + if (mechanismParam != NULL && mechanismParam->isOfType(EDDSAMechanismParam::type)) + { +#if OPENSSL_VERSION_NUMBER >= 0x30200000L + EDDSAMechanismParam* eddsaParam = (EDDSAMechanismParam*) mechanismParam; + preHash = eddsaParam->flag; + contextData = eddsaParam->contextData; +#else + ERROR_MSG("Context data is not supported in OpenSSL < 3.2"); + return false; +#endif + } + // Perform the verify operation size_t len = pk->getOrderLength(); if (len == 0) @@ -166,17 +274,37 @@ bool OSSLEDDSA::verify(PublicKey* publicKey, const ByteString& originalData, return false; } EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + +#if OPENSSL_VERSION_NUMBER >= 0x30200000L + OSSL_PARAM params[3]; + if (!buildInstanceParams(params, pk->getOrderLength(), preHash, contextData, "Verifying")) + { + EVP_MD_CTX_free(ctx); + return false; + } + + if (!EVP_DigestVerifyInit_ex(ctx, NULL, NULL, NULL, NULL, pkey, params)) +#else if (!EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey)) +#endif { - ERROR_MSG("EDDSA verify init failed (0x%08X)", ERR_get_error()); + long err = ERR_get_error(); + ERROR_MSG("EDDSA verify init failed (0x%08X): %s", err, ERR_error_string(err, NULL)); EVP_MD_CTX_free(ctx); return false; } + + DEBUG_MSG("Verifying signature of length: %zu, signature: %s", len, signature.hex_str().c_str()); + int ret = EVP_DigestVerify(ctx, signature.const_byte_str(), len, originalData.const_byte_str(), originalData.size()); + DEBUG_MSG("Verify result: %d", ret); if (ret != 1) { if (ret < 0) - ERROR_MSG("EDDSA verify failed (0x%08X)", ERR_get_error()); + { + long err = ERR_get_error(); + ERROR_MSG("EDDSA verify failed (0x%08X): %s", err, ERR_error_string(err, NULL)); + } EVP_MD_CTX_free(ctx); return false; } @@ -256,14 +384,16 @@ bool OSSLEDDSA::generateKeyPair(AsymmetricKeyPair** ppKeyPair, AsymmetricParamet int ret = EVP_PKEY_keygen_init(ctx); if (ret != 1) { - ERROR_MSG("EDDSA key generation init failed (0x%08X)", ERR_get_error()); + long err = ERR_get_error(); + ERROR_MSG("EDDSA key generation init failed (0x%08X): %s", err, ERR_error_string(err, NULL)); EVP_PKEY_CTX_free(ctx); return false; } ret = EVP_PKEY_keygen(ctx, &pkey); if (ret != 1) { - ERROR_MSG("EDDSA key generation failed (0x%08X)", ERR_get_error()); + long err = ERR_get_error(); + ERROR_MSG("EDDSA key generation failed (0x%08X): %s", err, ERR_error_string(err, NULL)); EVP_PKEY_CTX_free(ctx); return false; } diff --git a/src/lib/crypto/OSSLEDDSA.h b/src/lib/crypto/OSSLEDDSA.h index 7fbb60309..6bac80dee 100644 --- a/src/lib/crypto/OSSLEDDSA.h +++ b/src/lib/crypto/OSSLEDDSA.h @@ -77,6 +77,11 @@ class OSSLEDDSA : public AsymmetricAlgorithm virtual AsymmetricParameters* newParameters(); private: +#if OPENSSL_VERSION_NUMBER >= 0x30200000L + // Derive the EdDSA instance from the key size and pre-hash flag, and build the params to pass to the digest init + // params must hold at least 3 entries; contextData must outlive the init call + static bool buildInstanceParams(OSSL_PARAM* params, size_t orderLength, bool preHash, const ByteString& contextData, const char* operation); +#endif }; #endif // !_SOFTHSM_V2_OSSLEDDSA_H diff --git a/src/lib/crypto/test/EDDSATests.cpp b/src/lib/crypto/test/EDDSATests.cpp index 8fae76499..d7653d06b 100644 --- a/src/lib/crypto/test/EDDSATests.cpp +++ b/src/lib/crypto/test/EDDSATests.cpp @@ -43,6 +43,33 @@ #include "ECParameters.h" #include "EDPublicKey.h" #include "EDPrivateKey.h" +#include "EDDSAMechanismParam.h" +#ifdef WITH_OPENSSL +#include +#endif + +// Contexts and pre-hashing need OpenSSL 3.2 or later; Botan 2.X has no context support at all +#if defined(WITH_OPENSSL) && OPENSSL_VERSION_NUMBER < 0x30200000L + #define EDDSA_HAVE_CONTEXT 0 + #define EDDSA_HAVE_PREHASH 0 +#elif defined(WITH_BOTAN) + #define EDDSA_HAVE_CONTEXT 0 + #define EDDSA_HAVE_PREHASH 1 +#else + #define EDDSA_HAVE_CONTEXT 1 + #define EDDSA_HAVE_PREHASH 1 +#endif + +// Botan 2.X only supports Ed25519 +#ifdef WITH_BOTAN + #define EDDSA_HAVE_ED448 0 +#else + #define EDDSA_HAVE_ED448 1 +#endif + +// Curve identifiers used by the test vectors below +#define ED25519_CURVE "06032b6570" +#define ED448_CURVE "130a65647761726473343438" CPPUNIT_TEST_SUITE_REGISTRATION(EDDSATests); @@ -400,6 +427,199 @@ void EDDSATests::testSignVerifyKnownVectorEd448() #endif } +void EDDSATests::checkKnownVector(const char* ec, const char* k, const char* a, const char* msg, + const char* ctx, bool preHash, const char* sig) +{ + EDPublicKey* pubKey = (EDPublicKey*) eddsa->newPublicKey(); + EDPrivateKey* privKey = (EDPrivateKey*) eddsa->newPrivateKey(); + CPPUNIT_ASSERT(pubKey != NULL); + CPPUNIT_ASSERT(privKey != NULL); + + ByteString curve = ec; + pubKey->setEC(curve); + pubKey->setA(ByteString(a)); + privKey->setEC(curve); + privKey->setK(ByteString(k)); + + ByteString data; + if (msg != NULL) data = ByteString(msg); + + EDDSAMechanismParam param; + param.flag = preHash; + if (ctx != NULL) param.contextData = ByteString(ctx); + + ByteString expected = sig; + + // EdDSA is deterministic, so the signature must match the test vector octet for octet + ByteString signature; + CPPUNIT_ASSERT(eddsa->sign(privKey, data, signature, AsymMech::EDDSA, ¶m)); + CPPUNIT_ASSERT(signature == expected); + + CPPUNIT_ASSERT(eddsa->verify(pubKey, data, expected, AsymMech::EDDSA, ¶m)); + + // Flipping a single bit of the signature must break verification + ByteString badSignature = expected; + badSignature[0] ^= 0x01; + CPPUNIT_ASSERT(!eddsa->verify(pubKey, data, badSignature, AsymMech::EDDSA, ¶m)); + + eddsa->recyclePublicKey(pubKey); + eddsa->recyclePrivateKey(privKey); +} + +void EDDSATests::testSignVerifyKnownVectorEd25519ctx() +{ +#if EDDSA_HAVE_CONTEXT + // Test vectors from RFC 8032 section 7.2 + + const char* k1 = "0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6"; + const char* a1 = "0420dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292"; + const char* msg1 = "f726936d19c800494e3fdaff20b276a8"; + const char* ctxFoo = "666f6f"; // "foo" + const char* ctxBar = "626172"; // "bar" + + // foo + checkKnownVector(ED25519_CURVE, k1, a1, msg1, ctxFoo, false, + "55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a" + "8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d"); + + // bar: same key and message, different context + checkKnownVector(ED25519_CURVE, k1, a1, msg1, ctxBar, false, + "fc60d5872fc46b3aa69f8b5b4351d5808f92bcc044606db097abab6dbcb1aee3" + "216c48e8b3b66431b5b186d1d28f8ee15a5ca2df6668346291c2043d4eb3e90d"); + + // foo2: same key and context, different message + checkKnownVector(ED25519_CURVE, k1, a1, "508e9e6882b979fea900f62adceaca35", ctxFoo, false, + "8b70c1cc8310e1de20ac53ce28ae6e7207f33c3295e03bb5c0732a1d20dc6490" + "8922a8b052cf99b7c4fe107a5abb5b2c4085ae75890d02df26269d8945f84b0b"); + + // foo3: different key + checkKnownVector(ED25519_CURVE, + "ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560", + "04200f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772", + msg1, ctxFoo, false, + "21655b5f1aa965996b3f97b3c849eafba922a0a62992f73b3d1b73106a84ad85" + "e9b86a7b6005ea868337ff2d20a7f5fbd4cd10b0be49a68da2b2e0dc0ad8960f"); + + // The "foo" signature must not verify under the "bar" context + EDPublicKey* pubKey = (EDPublicKey*) eddsa->newPublicKey(); + ByteString curve = ED25519_CURVE; + pubKey->setEC(curve); + pubKey->setA(ByteString(a1)); + + ByteString data = msg1; + ByteString fooSignature = + "55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a" + "8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d"; + + EDDSAMechanismParam barParam; + barParam.flag = false; + barParam.contextData = ByteString(ctxBar); + CPPUNIT_ASSERT(!eddsa->verify(pubKey, data, fooSignature, AsymMech::EDDSA, &barParam)); + + // Nor without any context at all + EDDSAMechanismParam pureParam; + pureParam.flag = false; + CPPUNIT_ASSERT(!eddsa->verify(pubKey, data, fooSignature, AsymMech::EDDSA, &pureParam)); + + eddsa->recyclePublicKey(pubKey); +#endif +} + +void EDDSATests::testSignVerifyKnownVectorEd25519ph() +{ +#if EDDSA_HAVE_PREHASH + // Test vector from RFC 8032 section 7.3 + + const char* k = "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42"; + const char* a = "0420ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"; + const char* msg = "616263"; // "abc" + const char* sig = + "98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae41" + "31f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406"; + + checkKnownVector(ED25519_CURVE, k, a, msg, NULL, true, sig); + + // The pre-hashed signature must not verify as a pure Ed25519 signature + EDPublicKey* pubKey = (EDPublicKey*) eddsa->newPublicKey(); + ByteString curve = ED25519_CURVE; + pubKey->setEC(curve); + pubKey->setA(ByteString(a)); + + ByteString data = msg; + ByteString signature = sig; + + EDDSAMechanismParam pureParam; + pureParam.flag = false; + CPPUNIT_ASSERT(!eddsa->verify(pubKey, data, signature, AsymMech::EDDSA, &pureParam)); + + eddsa->recyclePublicKey(pubKey); +#endif +} + +void EDDSATests::testSignVerifyKnownVectorEd448ctx() +{ +#if EDDSA_HAVE_ED448 && EDDSA_HAVE_CONTEXT + // Test vector "1 octet (with context)" from RFC 8032 section 7.4 + + const char* k = "c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463a" + "fbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e"; + const char* a = "0439" + "43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c086" + "6aea01eb00742802b8438ea4cb82169c235160627b4c3a9480"; + const char* msg = "03"; + const char* sig = + "d4f8f6131770dd46f40867d6fd5d5055de43541f8c5e35abbcd001b32a89f7d2" + "151f7647f11d8ca2ae279fb842d607217fce6e042f6815ea000c85741de5c8da" + "1144a6a1aba7f96de42505d7a7298524fda538fccbbb754f578c1cad10d54d0d" + "5428407e85dcbc98a49155c13764e66c3c00"; + + checkKnownVector(ED448_CURVE, k, a, msg, "666f6f", false, sig); + + // The same signature must not verify without the context + EDPublicKey* pubKey = (EDPublicKey*) eddsa->newPublicKey(); + ByteString curve = ED448_CURVE; + pubKey->setEC(curve); + pubKey->setA(ByteString(a)); + + ByteString data = msg; + ByteString signature = sig; + + EDDSAMechanismParam pureParam; + pureParam.flag = false; + CPPUNIT_ASSERT(!eddsa->verify(pubKey, data, signature, AsymMech::EDDSA, &pureParam)); + + eddsa->recyclePublicKey(pubKey); +#endif +} + +void EDDSATests::testSignVerifyKnownVectorEd448ph() +{ +#if EDDSA_HAVE_ED448 && EDDSA_HAVE_PREHASH + // Test vectors from RFC 8032 section 7.5 + + const char* k = "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42" + "ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49"; + const char* a = "0439" + "259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743" + "c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880"; + const char* msg = "616263"; // "abc" + + checkKnownVector(ED448_CURVE, k, a, msg, NULL, true, + "822f6901f7480f3d5f562c592994d9693602875614483256505600bbc281ae38" + "1f54d6bce2ea911574932f52a4e6cadd78769375ec3ffd1b801a0d9b3f4030cd" + "433964b6457ea39476511214f97469b57dd32dbc560a9a94d00bff07620464a3" + "ad203df7dc7ce360c3cd3696d9d9fab90f00"); + +#if EDDSA_HAVE_CONTEXT + checkKnownVector(ED448_CURVE, k, a, msg, "666f6f", true, + "c32299d46ec8ff02b54540982814dce9a05812f81962b649d528095916a2aa48" + "1065b1580423ef927ecf0af5888f90da0f6a9a85ad5dc3f280d91224ba9911a3" + "653d00e484e2ce232521481c8658df304bb7745a73514cdb9bf3e15784ab7128" + "4f8d0704a608c54a6b62d97beb511d132100"); +#endif +#endif +} + void EDDSATests::testDerivation() { for (const ByteString& c : montCurves) diff --git a/src/lib/crypto/test/EDDSATests.h b/src/lib/crypto/test/EDDSATests.h index 888d23b5a..aaaf4f236 100644 --- a/src/lib/crypto/test/EDDSATests.h +++ b/src/lib/crypto/test/EDDSATests.h @@ -44,7 +44,11 @@ class EDDSATests : public CppUnit::TestFixture CPPUNIT_TEST(testPKCS8); CPPUNIT_TEST(testSigningVerifying); CPPUNIT_TEST(testSignVerifyKnownVectorEd25519); + CPPUNIT_TEST(testSignVerifyKnownVectorEd25519ctx); + CPPUNIT_TEST(testSignVerifyKnownVectorEd25519ph); CPPUNIT_TEST(testSignVerifyKnownVectorEd448); + CPPUNIT_TEST(testSignVerifyKnownVectorEd448ctx); + CPPUNIT_TEST(testSignVerifyKnownVectorEd448ph); CPPUNIT_TEST(testDerivation); CPPUNIT_TEST(testDeriveKnownVectorX25519); CPPUNIT_TEST(testDeriveKnownVectorX448); @@ -56,7 +60,11 @@ class EDDSATests : public CppUnit::TestFixture void testPKCS8(); void testSigningVerifying(); void testSignVerifyKnownVectorEd25519(); + void testSignVerifyKnownVectorEd25519ctx(); + void testSignVerifyKnownVectorEd25519ph(); void testSignVerifyKnownVectorEd448(); + void testSignVerifyKnownVectorEd448ctx(); + void testSignVerifyKnownVectorEd448ph(); void testDerivation(); void testDeriveKnownVectorX25519(); void testDeriveKnownVectorX448(); @@ -65,6 +73,10 @@ class EDDSATests : public CppUnit::TestFixture void tearDown(); private: + // Sign and verify an RFC 8032 test vector; ec, k, a, msg and sig are hex, ctx may be NULL + void checkKnownVector(const char* ec, const char* k, const char* a, const char* msg, + const char* ctx, bool preHash, const char* sig); + // EDDSA instance AsymmetricAlgorithm* eddsa; }; diff --git a/src/lib/test/SignVerifyTests.cpp b/src/lib/test/SignVerifyTests.cpp index 222d79b67..ed0f07de4 100644 --- a/src/lib/test/SignVerifyTests.cpp +++ b/src/lib/test/SignVerifyTests.cpp @@ -305,15 +305,15 @@ void SignVerifyTests::signVerifySingleData(size_t dataSize, CK_MECHANISM_TYPE me { CK_RV rv; CK_MECHANISM mechanism = { mechanismType, param, paramLen }; - CK_BYTE *data = (CK_BYTE*)malloc(dataSize); CK_BYTE signature[1024]; CK_ULONG ulSignatureLen = 0; - unsigned i; + CPPUNIT_ASSERT(dataSize > 0); + CK_BYTE *data = (CK_BYTE*)malloc(dataSize); CPPUNIT_ASSERT(data != NULL); - for (i=0;i 0); + CPPUNIT_ASSERT(data != NULL); + + rv = CRYPTOKI_F_PTR( C_SignInit(hSession,&mechanism,hPrivateKey) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + ulSignatureLen = sizeof(signature); + rv = CRYPTOKI_F_PTR( C_Sign(hSession,data,dataSize,signature,&ulSignatureLen) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + rv = CRYPTOKI_F_PTR( C_VerifyInit(hSession,&mechanism,hPublicKey) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + rv = CRYPTOKI_F_PTR( C_Verify(hSession,data,dataSize,signature,ulSignatureLen) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + // verify again, but now change the input that is being signed. + rv = CRYPTOKI_F_PTR( C_VerifyInit(hSession,&mechanism,hPublicKey) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + CK_BYTE origByte = data[0]; + data[0] = 0xff; + rv = CRYPTOKI_F_PTR( C_Verify(hSession,data,dataSize,signature,ulSignatureLen) ); + CPPUNIT_ASSERT(rv==CKR_SIGNATURE_INVALID); + + // the caller owns the buffer and may reuse it, so leave it untouched + data[0] = origByte; +} + void SignVerifyTests::signVerifyMulti(CK_MECHANISM_TYPE mechanismType, CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hPublicKey, CK_OBJECT_HANDLE hPrivateKey, CK_VOID_PTR param /* = NULL_PTR */, CK_ULONG paramLen /* = 0 */) { CK_RV rv; @@ -784,6 +820,304 @@ void SignVerifyTests::testEdSignVerify(const char* curve) CPPUNIT_ASSERT(rv == CKR_OK); signVerifySingle(CKM_EDDSA, hSessionRO, hPuk,hPrk); } + +void SignVerifyTests::testEdSignVerifyWithContext(const char* curve) +{ + bool contextSupported = true; + +#ifdef WITH_BOTAN + // Botan 2.X has no context support + contextSupported = false; +#endif +#if defined(WITH_OPENSSL) && OPENSSL_VERSION_NUMBER < 0x30200000L + contextSupported = false; +#endif + + if (!contextSupported) + { + fprintf(stdout, "EdDSA context is not supported. Skipping testEdSignVerifyWithContext.\n"); + return; + } + + CK_RV rv; + CK_SESSION_HANDLE hSessionRO; + CK_SESSION_HANDLE hSessionRW; + + // 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; + + // Test EdDSA signature with context data + // Create EdDSA parameters with context + CK_BYTE contextData[] = "context-data"; + CK_ULONG dataSize = (CK_ULONG)(sizeof(contextData) - 1); // exclude trailing NULL + CK_EDDSA_PARAMS params = + { + CK_FALSE, // phFlag = 0 (no pre-hash) + dataSize, // context_data_len + contextData // context_data + }; + + // Public Session keys with context + rv = generateED(curve, hSessionRW,IN_SESSION,IS_PUBLIC,IN_SESSION,IS_PUBLIC,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingle(CKM_EDDSA, hSessionRO, hPuk, hPrk, ¶ms, sizeof(params)); + + // Private Session Keys with context + rv = generateED(curve, hSessionRW,IN_SESSION,IS_PRIVATE,IN_SESSION,IS_PRIVATE,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingle(CKM_EDDSA, hSessionRO, hPuk, hPrk, ¶ms, sizeof(params)); + + // Test with different context data + CK_BYTE anotherContext[] = "Bob"; + dataSize = (CK_ULONG)(sizeof(anotherContext) - 1); // exclude trailing NULL + CK_EDDSA_PARAMS eddsaParams2 = { + CK_FALSE, // phFlag = 0 + dataSize, // context_data_len + anotherContext // context_data + }; + + // Public Token Keys with different context + rv = generateED(curve, hSessionRW,ON_TOKEN,IS_PUBLIC,ON_TOKEN,IS_PUBLIC,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingle(CKM_EDDSA, hSessionRO, hPuk, hPrk, &eddsaParams2, sizeof(eddsaParams2)); + + // Private Token Keys with different context + rv = generateED(curve, hSessionRW,ON_TOKEN,IS_PRIVATE,ON_TOKEN,IS_PRIVATE,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingle(CKM_EDDSA, hSessionRO, hPuk, hPrk, &eddsaParams2, sizeof(eddsaParams2)); + + // Test with empty context + CK_EDDSA_PARAMS eddsaParamsNoCtx = { + CK_FALSE, // phFlag = 0 + 0, // context_data_len = 0 + NULL // context_data = NULL + }; + + // Generate new keys for empty context test + rv = generateED(curve, hSessionRW,IN_SESSION,IS_PUBLIC,IN_SESSION,IS_PUBLIC,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingle(CKM_EDDSA, hSessionRO, hPuk, hPrk, &eddsaParamsNoCtx, sizeof(CK_EDDSA_PARAMS)); +} + +void SignVerifyTests::testEdSignVerifyWithContextPreHashed(const char* curve) +{ +#ifdef WITH_BOTAN + // Botan 2.X does not support Ed448 curve, so we skip this test for now. + if (strcmp(curve, "Ed448") == 0) + { + fprintf(stdout, "Botan 2.X does not support Ed448. Skipping testEdSignVerifyWithContextPreHashed for Ed448.\n"); + return; + } +#endif +#if defined(WITH_OPENSSL) && OPENSSL_VERSION_NUMBER < 0x30200000L + fprintf(stdout, "OpenSSL 3.2.0 or later is required for EDDSA with context support. Skipping testEdSignVerifyWithContextPreHashed.\n"); + return; +#endif + + CK_RV rv; + CK_SESSION_HANDLE hSessionRO; + CK_SESSION_HANDLE hSessionRW; + + // 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; + + // With phFlag set the token does the pre-hashing, so this is just the message to be signed + CK_BYTE message[] = { 0x11, 0x79, 0x06, 0xd8, 0xd7, 0xd9, 0x4d, 0xbc, 0x02, 0x01, 0x93, 0x2f, 0xbb, 0xab, 0x01, 0xed, 0x5b, 0x9e, 0xbc, 0x0f, 0x3c, 0x85, 0xef, 0xa0, 0x78, 0x61, 0xa1, 0xfa, 0xff, 0xb4, 0xee, 0xb2, 0x33, 0xdf, 0x44, 0xaa, 0xa0, 0x2d, 0x7b, 0x8f, 0xf2, 0x50, 0x7f, 0x98, 0x42, 0x7e, 0x3e, 0x1f, 0x5c, 0x7b, 0x9d, 0x6a, 0x2e, 0x4c, 0x8f, 0x1a, 0x3b, 0x5d, 0x6e, 0x7f, 0x8a, 0x9b }; + CK_EDDSA_PARAMS eddsaParams = { + CK_TRUE, // phFlag = 1 (pre-hash) + 0, // context_data_len = 0 + NULL // context_data = NULL + }; + + // Public Session keys + rv = generateED(curve, hSessionRW,IN_SESSION,IS_PUBLIC,IN_SESSION,IS_PUBLIC,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingleData(message, sizeof(message), CKM_EDDSA, hSessionRO, hPuk, hPrk, &eddsaParams, sizeof(CK_EDDSA_PARAMS)); + + // Private Session Keys + rv = generateED(curve, hSessionRW,IN_SESSION,IS_PRIVATE,IN_SESSION,IS_PRIVATE,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingleData(message, sizeof(message), CKM_EDDSA, hSessionRO, hPuk, hPrk, &eddsaParams, sizeof(CK_EDDSA_PARAMS)); + + // Test with a different message + CK_BYTE anotherMessage[] = { 0x13, 0xe8, 0x06, 0x38, 0x7a, 0xdc, 0x2d, 0x73, 0x3c, 0x85, 0xa9, 0xd0, 0x81, 0x91, 0xfa, 0xa0, 0xcd, 0xeb, 0x11, 0xca, 0x4d, 0x1c, 0x2a, 0x05, 0x7c, 0x27, 0xf3, 0x6c, 0xeb, 0xc4, 0xdf, 0x88, 0x8a, 0x45, 0x6a, 0xc5, 0xc0, 0x91, 0x69, 0x31, 0x4e, 0xb0, 0x49, 0xe7, 0xdf, 0xdc, 0xf8, 0x68, 0x67, 0x21, 0xf6, 0xda, 0x13, 0x46, 0x1c, 0x57, 0x5c, 0x6e, 0x78, 0x36, 0x91, 0xc4, 0x2d, 0x09 }; + CK_EDDSA_PARAMS eddsaParams2 = { + CK_TRUE, // phFlag = 1 (pre-hash) + 0, // context_data_len = 0 + NULL // context_data = NULL + }; + + // Public Token Keys + rv = generateED(curve, hSessionRW,ON_TOKEN,IS_PUBLIC,ON_TOKEN,IS_PUBLIC,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingleData(anotherMessage, sizeof(anotherMessage), CKM_EDDSA, hSessionRO, hPuk, hPrk, &eddsaParams2, sizeof(CK_EDDSA_PARAMS)); + + // Private Token Keys + rv = generateED(curve, hSessionRW,ON_TOKEN,IS_PRIVATE,ON_TOKEN,IS_PRIVATE,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingleData(anotherMessage, sizeof(anotherMessage), CKM_EDDSA, hSessionRO, hPuk, hPrk, &eddsaParams2, sizeof(CK_EDDSA_PARAMS)); + + // Test with empty context + CK_EDDSA_PARAMS eddsaParamsNoCtx = { + CK_TRUE, // phFlag = 1 (pre-hash) + 0, // context_data_len = 0 + NULL // context_data = NULL + }; + + // Generate new keys for empty context test + rv = generateED(curve, hSessionRW,IN_SESSION,IS_PUBLIC,IN_SESSION,IS_PUBLIC,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + signVerifySingleData(64, CKM_EDDSA, hSessionRO, hPuk, hPrk, &eddsaParamsNoCtx, sizeof(CK_EDDSA_PARAMS)); +} + +void SignVerifyTests::signVerifyMismatchedParams(CK_BYTE_PTR data, size_t dataSize, CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hPublicKey, CK_OBJECT_HANDLE hPrivateKey, CK_VOID_PTR signParam, CK_ULONG signParamLen, CK_VOID_PTR verifyParam, CK_ULONG verifyParamLen) +{ + CK_RV rv; + CK_MECHANISM signMechanism = { CKM_EDDSA, signParam, signParamLen }; + CK_MECHANISM verifyMechanism = { CKM_EDDSA, verifyParam, verifyParamLen }; + CK_BYTE signature[1024]; + CK_ULONG ulSignatureLen = sizeof(signature); + + rv = CRYPTOKI_F_PTR( C_SignInit(hSession,&signMechanism,hPrivateKey) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + rv = CRYPTOKI_F_PTR( C_Sign(hSession,data,dataSize,signature,&ulSignatureLen) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + rv = CRYPTOKI_F_PTR( C_VerifyInit(hSession,&verifyMechanism,hPublicKey) ); + CPPUNIT_ASSERT(rv==CKR_OK); + + rv = CRYPTOKI_F_PTR( C_Verify(hSession,data,dataSize,signature,ulSignatureLen) ); + CPPUNIT_ASSERT(rv==CKR_SIGNATURE_INVALID); +} + +// A signature made with one set of EdDSA parameters must not verify under another +void SignVerifyTests::testEdSignVerifyMismatchedParams(const char* curve) +{ + bool contextSupported = true; + bool preHashSupported = true; + +#ifdef WITH_BOTAN + // Botan 2.X has no context support and only pre-hashes Ed25519 + contextSupported = false; + if (strcmp(curve, "Ed448") == 0) + preHashSupported = false; +#endif +#if defined(WITH_OPENSSL) && OPENSSL_VERSION_NUMBER < 0x30200000L + contextSupported = false; + preHashSupported = false; +#endif + + if (!contextSupported && !preHashSupported) + { + fprintf(stdout, "Neither EdDSA context nor pre-hash is supported. Skipping testEdSignVerifyMismatchedParams.\n"); + return; + } + + CK_RV rv; + CK_SESSION_HANDLE hSessionRO; + CK_SESSION_HANDLE hSessionRW; + + // Just make sure that we finalize any previous tests + CRYPTOKI_F_PTR( C_Finalize(NULL_PTR) ); + + rv = CRYPTOKI_F_PTR( C_Initialize(NULL_PTR) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &hSessionRO) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + rv = CRYPTOKI_F_PTR( C_OpenSession(m_initializedTokenSlotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSessionRW) ); + CPPUNIT_ASSERT(rv == CKR_OK); + + 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; + + rv = generateED(curve, hSessionRW,IN_SESSION,IS_PUBLIC,IN_SESSION,IS_PUBLIC,hPuk,hPrk); + CPPUNIT_ASSERT(rv == CKR_OK); + + CK_BYTE data[64]; + for (size_t i = 0; i < sizeof(data); i++) + data[i] = (CK_BYTE) i; + + CK_BYTE contextAlice[] = "Alice"; + CK_BYTE contextBob[] = "Bob"; + CK_EDDSA_PARAMS aliceParams = { CK_FALSE, (CK_ULONG)(sizeof(contextAlice) - 1), contextAlice }; + CK_EDDSA_PARAMS bobParams = { CK_FALSE, (CK_ULONG)(sizeof(contextBob) - 1), contextBob }; + CK_EDDSA_PARAMS pureParams = { CK_FALSE, 0, NULL }; + CK_EDDSA_PARAMS preHashParams = { CK_TRUE, 0, NULL }; + + if (contextSupported) + { + // Different context data + signVerifyMismatchedParams(data, sizeof(data), hSessionRO, hPuk, hPrk, + &aliceParams, sizeof(aliceParams), &bobParams, sizeof(bobParams)); + + // Context data on signing only + signVerifyMismatchedParams(data, sizeof(data), hSessionRO, hPuk, hPrk, + &aliceParams, sizeof(aliceParams), &pureParams, sizeof(pureParams)); + + // Context data dropped by omitting the parameters altogether + signVerifyMismatchedParams(data, sizeof(data), hSessionRO, hPuk, hPrk, + &aliceParams, sizeof(aliceParams), NULL_PTR, 0); + } + + if (preHashSupported) + { + // Pre-hash on signing only + signVerifyMismatchedParams(data, sizeof(data), hSessionRO, hPuk, hPrk, + &preHashParams, sizeof(preHashParams), &pureParams, sizeof(pureParams)); + } +} #endif #ifdef WITH_ML_DSA diff --git a/src/lib/test/SignVerifyTests.h b/src/lib/test/SignVerifyTests.h index a7c22e083..fcc2ecc04 100644 --- a/src/lib/test/SignVerifyTests.h +++ b/src/lib/test/SignVerifyTests.h @@ -37,6 +37,9 @@ #include "config.h" #include "TestsBase.h" #include +#ifdef WITH_OPENSSL + #include +#endif class SignVerifyTests : public TestsBase { @@ -47,6 +50,9 @@ class SignVerifyTests : public TestsBase #endif #ifdef WITH_EDDSA CPPUNIT_TEST_PARAMETERIZED(testEdSignVerify, {"Ed25519", "Ed448"}); + CPPUNIT_TEST_PARAMETERIZED(testEdSignVerifyWithContext, {"Ed25519", "Ed448"}); + CPPUNIT_TEST_PARAMETERIZED(testEdSignVerifyWithContextPreHashed, {"Ed25519", "Ed448"}); + CPPUNIT_TEST_PARAMETERIZED(testEdSignVerifyMismatchedParams, {"Ed25519", "Ed448"}); #endif CPPUNIT_TEST(testMacSignVerify); #ifdef WITH_ML_DSA @@ -63,6 +69,9 @@ class SignVerifyTests : public TestsBase #endif #ifdef WITH_EDDSA void testEdSignVerify(const char* curve); + void testEdSignVerifyWithContext(const char* curve); + void testEdSignVerifyWithContextPreHashed(const char* curve); + void testEdSignVerifyMismatchedParams(const char* curve); #endif void testMacSignVerify(); #ifdef WITH_ML_DSA @@ -84,6 +93,10 @@ class SignVerifyTests : public TestsBase #endif void signVerifySingle(CK_MECHANISM_TYPE mechanismType, CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hPublicKey, CK_OBJECT_HANDLE hPrivateKey, CK_VOID_PTR param = NULL_PTR, CK_ULONG paramLen = 0); void signVerifySingleData(size_t dataSize, CK_MECHANISM_TYPE mechanismType, CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hPublicKey, CK_OBJECT_HANDLE hPrivateKey, CK_VOID_PTR param = NULL_PTR, CK_ULONG paramLen = 0); + void signVerifySingleData(CK_BYTE_PTR data, size_t dataSize, CK_MECHANISM_TYPE mechanismType, CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hPublicKey, CK_OBJECT_HANDLE hPrivateKey, CK_VOID_PTR param = NULL_PTR, CK_ULONG paramLen = 0); +#ifdef WITH_EDDSA + void signVerifyMismatchedParams(CK_BYTE_PTR data, size_t dataSize, CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hPublicKey, CK_OBJECT_HANDLE hPrivateKey, CK_VOID_PTR signParam, CK_ULONG signParamLen, CK_VOID_PTR verifyParam, CK_ULONG verifyParamLen); +#endif void signVerifyMulti(CK_MECHANISM_TYPE mechanismType, CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hPublicKey, CK_OBJECT_HANDLE hPrivateKey, CK_VOID_PTR param = NULL_PTR, CK_ULONG paramLen = 0); CK_RV generateKey(CK_SESSION_HANDLE hSession, CK_KEY_TYPE keyType, CK_BBOOL bToken, CK_BBOOL bPrivate, CK_OBJECT_HANDLE &hKey); CK_RV generateDes2Key(CK_SESSION_HANDLE hSession, CK_BBOOL bToken, CK_BBOOL bPrivate, CK_OBJECT_HANDLE &hKey);