Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/lib/SoftHSM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
#include "ECParameters.h"
#include "EDPublicKey.h"
#include "EDPrivateKey.h"
#ifdef WITH_EDDSA
#include "EDDSAMechanismParam.h"
#endif
#include "DHParameters.h"
#include "DHPublicKey.h"
#include "DHPrivateKey.h"
Expand Down Expand Up @@ -4320,6 +4323,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;
Expand Down Expand Up @@ -4585,6 +4589,37 @@ CK_RV SoftHSM::AsymSignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechan
mechanism = AsymMech::EDDSA;
bAllowMultiPartOp = false;
isEDDSA = true;
if (pMechanism->pParameter == NULL_PTR)
{
if (pMechanism->ulParameterLen != 0)
{
ERROR_MSG("Invalid parameters");
return CKR_ARGUMENTS_BAD;
}
// Default EdDSA parameters
eddsaParam.flag = false;
}
else
{
if (pMechanism->ulParameterLen != sizeof(CK_EDDSA_PARAMS))
{
ERROR_MSG("Invalid parameters");
return CKR_ARGUMENTS_BAD;
}
CK_EDDSA_PARAMS* ckEddsaParams = (CK_EDDSA_PARAMS*) pMechanism->pParameter;
eddsaParam.flag = (ckEddsaParams->phFlag != 0x00);
if (ckEddsaParams->ulContextDataLen > 0)
{
if (ckEddsaParams->pContextData == NULL_PTR)
{
ERROR_MSG("Invalid parameters");
return CKR_ARGUMENTS_BAD;
}
eddsaParam.contextData = ByteString(ckEddsaParams->pContextData, ckEddsaParams->ulContextDataLen);
DEBUG_MSG("Sign EdDSA flag=%s, contextDataLen=%lu", eddsaParam.flag ? "true" : "false", (unsigned long)eddsaParam.contextData.size());
}
mechanismParam = &eddsaParam;
}
break;
#endif
#ifdef WITH_ML_DSA
Expand Down Expand Up @@ -5410,6 +5445,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;
Expand Down Expand Up @@ -5674,6 +5710,37 @@ CK_RV SoftHSM::AsymVerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMech
mechanism = AsymMech::EDDSA;
bAllowMultiPartOp = false;
isEDDSA = true;
if (pMechanism->pParameter == NULL_PTR)
{
if (pMechanism->ulParameterLen != 0)
{
ERROR_MSG("Invalid parameters");
return CKR_ARGUMENTS_BAD;
}
// Default EdDSA parameters
eddsaParam.flag = false;
}
else
{
if (pMechanism->ulParameterLen != sizeof(CK_EDDSA_PARAMS))
{
ERROR_MSG("Invalid parameters");
return CKR_ARGUMENTS_BAD;
}
CK_EDDSA_PARAMS* ckEddsaParams = (CK_EDDSA_PARAMS*) pMechanism->pParameter;
eddsaParam.flag = (ckEddsaParams->phFlag != 0x00);
if (ckEddsaParams->ulContextDataLen > 0)
{
if (ckEddsaParams->pContextData == NULL_PTR)
{
ERROR_MSG("Invalid parameters");
return CKR_ARGUMENTS_BAD;
}
eddsaParam.contextData = ByteString(ckEddsaParams->pContextData, ckEddsaParams->ulContextDataLen);
DEBUG_MSG("Verify EdDSA flag=%s, contextDataLen=%lu", eddsaParam.flag ? "true" : "false", (unsigned long)eddsaParam.contextData.size());
}
mechanismParam = &eddsaParam;
}
break;
#endif
#ifdef WITH_ML_DSA
Expand Down
85 changes: 76 additions & 9 deletions src/lib/crypto/BotanEDDSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "CryptoFactory.h"
#include "BotanCryptoFactory.h"
#include "ECParameters.h"
#include "EDDSAMechanismParam.h"
#include "BotanEDKeyPair.h"
#include "BotanUtil.h"
#include <algorithm>
Expand Down Expand Up @@ -65,19 +66,19 @@ BotanEDDSA::~BotanEDDSA()
// 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)
{
emsa = "Pure";
}
else
{
else
{
ERROR_MSG("Invalid mechanism supplied (%i)", mechanism);
return false;
}
}

// Check if the private key is the right type
if (!privateKey->isOfType(BotanEDPrivateKey::type))
Expand All @@ -87,16 +88,49 @@ bool BotanEDDSA::sign(PrivateKey* privateKey, const ByteString& dataToSign,
return false;
}

BotanEDPrivateKey* pk = (BotanEDPrivateKey*) privateKey;
Botan::Ed25519_PrivateKey* botanKey = dynamic_cast<Botan::Ed25519_PrivateKey*>(pk->getBotanKey());
BotanEDPrivateKey* pk = (BotanEDPrivateKey*) privateKey;
Botan::Ed25519_PrivateKey* botanKey = dynamic_cast<Botan::Ed25519_PrivateKey*>(pk->getBotanKey());

if (botanKey == NULL)
{
if (botanKey == NULL)
{
ERROR_MSG("Could not get the Botan private key");

return false;
}

// Extract context parameters if provided
bool preHash = false;
ByteString contextData;
if (mechanismParam != NULL && mechanismParam->isOfType(EDDSAMechanismParam::type))
{
EDDSAMechanismParam* eddsaParam = (EDDSAMechanismParam*) mechanismParam;
preHash = eddsaParam->flag;
if (!preHash && eddsaParam->contextData.size() > 0)
{
ERROR_MSG("Context data is not supported in Botan EDDSA implementation");
return false;
}
}

if (preHash)
{
unsigned long len = pk->getOrderLength();
if (len == 0)
{
ERROR_MSG("EDDSA: Could not get the order length");
return false;
}

if (pk->getOrderLength() == 32)
{
emsa = "Ed25519ph";
}
else
{
emsa = "Ed448ph";
}
}
Comment on lines +101 to +132

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Context data is silently discarded when preHash is true.

The guard only rejects context when preHash == false. When preHash == true, eddsaParam->contextData is never passed to Botan — Ed25519ph/Ed448ph are constructed with no context, so a caller-supplied contextData (a valid PKCS#11 CK_EDDSA_PARAMS combination) is silently ignored and the resulting signature will not verify against a context-aware verifier. Either reject non-empty contextData outright in the Botan backend (as done for the pure case) or fail loudly, rather than dropping it.

Also note ByteString contextData; (Line 103) is declared but never assigned or used.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/crypto/BotanEDDSA.cpp` around lines 101 - 132, The Botan EDDSA path
is ignoring caller-provided contextData when preHash is true, which can produce
signatures that do not match context-aware verification. In BotanEDDSA::sign or
the mechanism-parameter handling block, either explicitly reject any non-empty
eddsaParam->contextData for both preHash and non-preHash cases, or wire it
through only if Botan supports it; do not silently discard it. Also remove the
unused local ByteString contextData or assign and validate it consistently
alongside EDDSAMechanismParam.


try
{
BotanRNG* rng = (BotanRNG*)BotanCryptoFactory::i()->getRNG();
Expand Down Expand Up @@ -162,7 +196,7 @@ 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;

Expand Down Expand Up @@ -195,6 +229,39 @@ bool BotanEDDSA::verify(PublicKey* publicKey, const ByteString& originalData,
return false;
}

// Extract context parameters if provided
bool preHash = false;
ByteString contextData;
if (mechanismParam != NULL && mechanismParam->isOfType(EDDSAMechanismParam::type))
{
EDDSAMechanismParam* eddsaParam = (EDDSAMechanismParam*) mechanismParam;
preHash = eddsaParam->flag;
if (!preHash && eddsaParam->contextData.size() > 0)
{
ERROR_MSG("Context data is not supported in Botan EDDSA implementation");
return false;
}
}

if (preHash)
{
unsigned long len = pk->getOrderLength();
if (len == 0)
{
ERROR_MSG("EDDSA: Could not get the order length");
return false;
}

if (pk->getOrderLength() == 32)
{
emsa = "Ed25519ph";
}
else
{
emsa = "Ed448ph";
Comment on lines +259 to +261

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same as in the openssl case, there should be explicit check for the order length 57B and fail in other cases as other "key sizes" are not defined by eddsa

}
}

try
{
verifier = new Botan::PK_Verifier(*botanKey, emsa);
Expand Down
1 change: 1 addition & 0 deletions src/lib/crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ set(SOURCES AESKey.cpp
ECPublicKey.cpp
EDPrivateKey.cpp
EDPublicKey.cpp
EDDSAMechanismParam.cpp
GOSTPrivateKey.cpp
GOSTPublicKey.cpp
HashAlgorithm.cpp
Expand Down
44 changes: 44 additions & 0 deletions src/lib/crypto/EDDSAMechanismParam.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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"

EDDSAMechanismParam::EDDSAMechanismParam() {
this->flag = false;
}

EDDSAMechanismParam::EDDSAMechanismParam(bool flag) {
this->flag = flag;
}

EDDSAMechanismParam::EDDSAMechanismParam(bool flag, ByteString contextData) {
this->flag = flag;
this->contextData = contextData;
}

// Set the type
/*static*/ const char* EDDSAMechanismParam::type = "EdDSA Signature param";

EDDSAMechanismParam* EDDSAMechanismParam::clone() const
{
return new EDDSAMechanismParam(static_cast<const EDDSAMechanismParam&>(*this)); // call the copy ctor.
}

// Check if the parameter is of the given type
bool EDDSAMechanismParam::isOfType(const char* inType) const
{
return !strcmp(type, inType);
}
#endif
44 changes: 44 additions & 0 deletions src/lib/crypto/EDDSAMechanismParam.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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();

EDDSAMechanismParam(bool flag);

EDDSAMechanismParam(bool flag, ByteString contextData);

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
1 change: 1 addition & 0 deletions src/lib/crypto/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ libsofthsm_crypto_la_SOURCES = AESKey.cpp \
ECPrivateKey.cpp \
EDPublicKey.cpp \
EDPrivateKey.cpp \
EDDSAMechanismParam.cpp \
GOSTPublicKey.cpp \
GOSTPrivateKey.cpp \
HashAlgorithm.cpp \
Expand Down
Loading
Loading