Skip to content

Add wc_SignCert_cb API for external signing callbacks#9632

Open
jackctj117 wants to merge 5 commits intowolfSSL:masterfrom
jackctj117:CSR-signing
Open

Add wc_SignCert_cb API for external signing callbacks#9632
jackctj117 wants to merge 5 commits intowolfSSL:masterfrom
jackctj117:CSR-signing

Conversation

@jackctj117
Copy link
Contributor

@jackctj117 jackctj117 commented Jan 8, 2026

This pull request adds support for signing certificates and CSRs using a user-provided callback function, enabling integration with external signing devices (such as TPMs or HSMs) without relying on the crypto callback infrastructure. This is particularly useful for FIPS-compliant applications and scenarios where offloading cryptographic operations is required. The changes include new API definitions, documentation, internal implementation, and tests for the callback-based signing mechanism.

New Callback-Based Certificate Signing API

  • Introduced the wc_SignCert_cb function and the wc_SignCertCb callback type, allowing certificates and CSRs to be signed via an external callback for flexible integration with devices like TPMs/HSMs. [1] [2] [3]

Internal Implementation

  • Added the internal MakeSignatureCb function to handle hashing, digest encoding, and invoking the user-provided signing callback, supporting both RSA and ECC key types.

Testing
Setup:
TPM simulator: swtpm running on port 2321
Built wolfSSL with: --enable-certgen --enable-certreq --enable-certext --enable-cryptocb
Built wolfTPM with: --enable-swtpm --enable-certgen --enable-debug
Tests Run:
Generated RSA and ECC test keys in TPM
Created CSRs using ./examples/csr/csr
Validated CSRs with openssl req -text -noout
Results:
wc_SignCert_cb compiled into wolfSSL
wolfTPM2_SignCertCb and CSR_MakeAndSign_Cb compiled into wolfTPM
Generated valid RSA (1228 bytes) and ECC (696 bytes) CSRs
CSRs verified successfully with OpenSSL

@jackctj117 jackctj117 self-assigned this Jan 8, 2026
@jackctj117 jackctj117 marked this pull request as draft January 8, 2026 22:22
@jackctj117 jackctj117 marked this pull request as ready for review January 9, 2026 21:59
@wolfSSL wolfSSL deleted a comment from devin-ai-integration bot Jan 9, 2026
@jackctj117 jackctj117 requested a review from wolfSSL-Bot January 9, 2026 23:30
@dgarske dgarske requested review from dgarske and lealem47 and removed request for wolfSSL-Bot January 14, 2026 16:59
@dgarske
Copy link
Contributor

dgarske commented Jan 14, 2026

Jenkins retest this please. History lost.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds support for signing certificates and CSRs using a user-provided callback function, enabling integration with external signing devices (TPMs/HSMs) without relying on the crypto callback infrastructure. This is particularly useful for FIPS-compliant applications where offloading cryptographic operations is not acceptable.

Changes:

  • Introduced new wc_SignCert_cb API and wc_SignCertCb callback type for external certificate/CSR signing
  • Refactored internal MakeSignature function to use new MakeSignatureCb internally for RSA and ECC, eliminating code duplication
  • Added configure option --enable-certsigncb to enable the feature

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
wolfssl/wolfcrypt/asn_public.h Added public API declarations for the callback-based certificate signing, including typedef for wc_SignCertCb and function declaration for wc_SignCert_cb
wolfcrypt/src/asn.c Implemented internal MakeSignatureCb function and refactored MakeSignature to use callback path for RSA/ECC; added wc_SignCert_cb implementation
tests/api.c Added test case test_wc_SignCert_cb with mock callback to verify the new API functionality
configure.ac Added configuration option --enable-certsigncb to control compilation of the new feature

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dgarske dgarske self-requested a review January 19, 2026 20:06
@jackctj117 jackctj117 force-pushed the CSR-signing branch 2 times, most recently from 4dd06e1 to 8e28ab2 Compare January 20, 2026 20:26
@jackctj117
Copy link
Contributor Author

Jenkins retest this

@jackctj117
Copy link
Contributor Author

jackctj117 commented Jan 21, 2026

Jenkins retest this please.

Copy link
Contributor

@dgarske dgarske left a comment

Choose a reason for hiding this comment

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

[check-source-text] [2 of 7] [wolfssl]
    autogen.sh wolfssl...   real 0m17.507s  user 0m15.557s  sys 0m0.446s
    configure...   real 0m12.829s  user 0m10.049s  sys 0m2.680s
trailing whitespace:
./wolfcrypt/src/asn.c:32089:/* Make signature from buffer (sz), write to sig (sigSz)·
./wolfcrypt/src/asn.c:32100:····
./wolfcrypt/src/asn.c:32119:········
./wolfcrypt/src/asn.c:32125:········
./wolfcrypt/src/asn.c:32137:········
C++-style comments:
./wolfssl/wolfcrypt/asn_public.h:269:        // Perform signing using external device/HSM
./wolfssl/wolfcrypt/asn_public.h:601:    // Initialize cert and set subject, etc.
./wolfssl/wolfcrypt/asn_public.h:603:    // ... set cert fields ...
./wolfssl/wolfcrypt/asn_public.h:605:    // Make certificate body
./wolfssl/wolfcrypt/asn_public.h:608:    // Sign using callback
unescaped error code operands (missing WC_NO_ERR_TRACE()):
wolfcrypt/src/asn.c:32016:    int ret = ALGO_ID_E;

@lealem47
Copy link
Contributor

lealem47 commented Jan 27, 2026

Jenkins retest this please.

@jackctj117 jackctj117 requested a review from dgarske January 28, 2026 18:16
lealem47
lealem47 previously approved these changes Feb 3, 2026
lealem47
lealem47 previously approved these changes Feb 3, 2026
Copy link
Contributor

@lealem47 lealem47 left a comment

Choose a reason for hiding this comment

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

Thanks Jack!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 20047 to 20107
static int test_wc_SignCert_cb(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && !defined(NO_ASN_TIME)
Cert cert;
byte der[FOURK_BUF];
int derSize = 0;
WC_RNG rng;
ecc_key key;
MockSignCtx signCtx;
int ret;

XMEMSET(&rng, 0, sizeof(WC_RNG));
XMEMSET(&key, 0, sizeof(ecc_key));
XMEMSET(&cert, 0, sizeof(Cert));
XMEMSET(&signCtx, 0, sizeof(MockSignCtx));

ExpectIntEQ(wc_InitRng(&rng), 0);
ExpectIntEQ(wc_ecc_init(&key), 0);
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &key), 0);
ExpectIntEQ(wc_InitCert(&cert), 0);

(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.state, "state", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.locality, "locality", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.org, "org", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.unit, "unit", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.commonName, "www.example.com",
CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.email, "test@example.com", CTC_NAME_SIZE);

cert.selfSigned = 1;
cert.isCA = 0;
cert.sigType = CTC_SHA256wECDSA;

/* Make cert body */
ExpectIntGT(wc_MakeCert(&cert, der, FOURK_BUF, NULL, &key, &rng), 0);

/* Setup signing context with key and RNG */
signCtx.key = &key;
signCtx.rng = &rng;

/* Sign using callback API */
ExpectIntGT(derSize = wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, ECC_TYPE, mockSignCb, &signCtx, &rng), 0);

/* Verify the certificate was created properly */
ExpectIntGT(derSize, 0);

/* Test error cases */
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, ECC_TYPE, NULL, &signCtx, &rng), BAD_FUNC_ARG);

ret = wc_ecc_free(&key);
ExpectIntEQ(ret, 0);
ret = wc_FreeRng(&rng);
ExpectIntEQ(ret, 0);
#endif
return EXPECT_RESULT();
}
#endif /* WOLFSSL_CERT_SIGN_CB */
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

The test function test_wc_SignCert_cb only tests with ECC keys (CTC_SHA256wECDSA). Since the new API explicitly supports both RSA and ECC key types, and the mockSignCb callback implementation includes RSA support (lines 20011-20023), there should be test coverage for RSA as well to ensure the RSA code path works correctly. Consider adding an RSA test case to verify that RSA signing through the callback works properly.

Copilot uses AI. Check for mistakes.
@dgarske
Copy link
Contributor

dgarske commented Feb 5, 2026

Jenkins retest this please

@dgarske dgarske requested review from Copilot and lealem47 February 5, 2026 03:11
dgarske
dgarske previously approved these changes Feb 5, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 7 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 34417 to 34472
#ifdef WOLFSSL_CERT_SIGN_CB
int wc_SignCert_cb(int requestSz, int sType, byte* buf, word32 buffSz,
int keyType, wc_SignCertCb signCb, void* signCtx,
WC_RNG* rng)
{
int sigSz = 0;
CertSignCtx certSignCtx_lcl;
CertSignCtx* certSignCtx = &certSignCtx_lcl;

if (signCb == NULL || buf == NULL) {
return BAD_FUNC_ARG;
}

XMEMSET(certSignCtx, 0, sizeof(*certSignCtx));

if (requestSz < 0) {
return requestSz;
}

#ifndef WOLFSSL_NO_MALLOC
certSignCtx->sig = (byte*)XMALLOC(MAX_ENCODED_SIG_SZ, NULL,
DYNAMIC_TYPE_TMP_BUFFER);
if (certSignCtx->sig == NULL) {
return MEMORY_E;
}
#endif

sigSz = MakeSignatureCb(certSignCtx, buf, (word32)requestSz,
certSignCtx->sig, MAX_ENCODED_SIG_SZ, sType, keyType,
signCb, signCtx, rng, NULL);

#ifdef WOLFSSL_ASYNC_CRYPT
if (sigSz == WC_NO_ERR_TRACE(WC_PENDING_E)) {
/* Not free'ing certSignCtx->sig here because it could still be in use
* with async operations. */
return sigSz;
}
#endif

if (sigSz >= 0) {
if (requestSz + MAX_SEQ_SZ * 2 + sigSz > (int)buffSz) {
sigSz = BUFFER_E;
}
else {
sigSz = AddSignature(buf, requestSz, certSignCtx->sig, sigSz, sType);
}
}

#ifndef WOLFSSL_NO_MALLOC
XFREE(certSignCtx->sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
certSignCtx->sig = NULL;
#endif

return sigSz;
}
#endif /* WOLFSSL_CERT_SIGN_CB */
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Potential memory leak in wc_SignCert_cb when WOLFSSL_ASYNC_CRYPT is enabled. When WC_PENDING_E is returned at line 34452, the function returns without freeing certSignCtx->sig (correctly, as noted in the comment). However, there is no mechanism to track this allocation across async calls, and no way for the caller to free this memory later.

In the SignCert function (lines 34103-34175), the certSignCtx is stored in the key structure (rsaKey->certSignCtx or eccKey->certSignCtx) when WOLFSSL_ASYNC_CRYPT is enabled, allowing the allocation to be tracked across async calls. But in wc_SignCert_cb, certSignCtx is a local variable that goes out of scope, so the allocated certSignCtx->sig will be lost.

Either wc_SignCert_cb needs a way to persist the certSignCtx (similar to how SignCert uses the key's embedded certSignCtx), or the API needs to document that it cannot be used with async crypto operations.

Copilot uses AI. Check for mistakes.
Comment on lines 34053 to 34075
case CERTSIGN_STATE_DO:
certSignCtx->state = CERTSIGN_STATE_DO;
outLen = sigSz;

/* Call the user-provided signing callback */
#ifndef NO_RSA
if (keyType == RSA_TYPE) {
/* RSA: pass encoded digest */
ret = signCb(certSignCtx->encSig, (word32)certSignCtx->encSigSz,
sig, &outLen, sigAlgoType, keyType, signCtx);
}
else
#endif /* !NO_RSA */
{
/* ECC: pass raw hash */
ret = signCb(certSignCtx->digest, (word32)digestSz,
sig, &outLen, sigAlgoType, keyType, signCtx);
}

if (ret == 0) {
ret = (int)outLen;
}
break;
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

MakeSignatureCb doesn't check for or handle WC_PENDING_E return value from the user callback (signCb). At lines 34061-34070, the callback is invoked but the return value is only checked for success (ret == 0) or error (ret < 0), without any special handling for WC_PENDING_E.

If a user's callback returns WC_PENDING_E (e.g., when using async hardware), the function will not properly handle the pending state. It should check for WC_PENDING_E and return it without cleaning up state or resetting certSignCtx->state, similar to how the original MakeSignature function handled async operations.

The original implementation commented "set next state, since WC_PENDING_E reentry for these are not 'call again'" (formerly around the HashForSignature call), which suggests careful consideration was given to async state transitions. This logic needs to be preserved when the callback returns WC_PENDING_E.

Copilot uses AI. Check for mistakes.
Comment on lines 256 to 298
\param requestSz Size of the certificate body to sign (from Cert.bodySz).
\param sType Signature algorithm type (e.g., CTC_SHA256wRSA,
CTC_SHA256wECDSA).
\param buf Buffer containing the certificate/CSR DER data to sign.
\param buffSz Total size of the buffer (must be large enough for signature).
\param keyType Type of key used for signing (RSA_TYPE, ECC_TYPE, etc.).
\param signCb User-provided signing callback function.
\param signCtx Context pointer passed to the signing callback.
\param rng Random number generator (may be NULL if not needed).

\return Size of the signed certificate/CSR on success.
\return BAD_FUNC_ARG if signCb is NULL or other parameters are invalid.
\return BUFFER_E if the buffer is too small for the signed certificate.
\return MEMORY_E if memory allocation fails.
\return Negative error code on other failures.

_Example_
\code
Cert cert;
byte derBuf[4096];
int derSz;
MySignCtx myCtx;

wc_InitCert(&cert);

derSz = wc_MakeCert(&cert, derBuf, sizeof(derBuf), NULL, NULL, &rng);

derSz = wc_SignCert_cb(cert.bodySz, cert.sigType, derBuf, sizeof(derBuf),
RSA_TYPE, mySignCallback, &myCtx, &rng);
if (derSz > 0) {
printf("Signed certificate is %d bytes\n", derSz);
}
\endcode

\sa wc_SignCertCb
\sa wc_SignCert
\sa wc_SignCert_ex
\sa wc_MakeCert
\sa wc_MakeCertReq
*/
int wc_SignCert_cb(int requestSz, int sType, byte* buf, word32 buffSz,
int keyType, wc_SignCertCb signCb, void* signCtx,
WC_RNG* rng);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The documentation for wc_SignCert_cb does not mention support limitations for key types. The implementation in MakeSignatureCb (lines 34003-34098) only handles RSA_TYPE and ECC_TYPE, but the documentation at lines 261 and 253 says "keyType: Type of key used for signing (RSA_TYPE, ECC_TYPE, etc.)" with "etc." implying support for other types.

However, the function will silently fail or behave incorrectly for other key types like ED25519_TYPE, ED448_TYPE, or post-quantum types, since MakeSignatureCb only has conditional logic for RSA (line 34038) and falls through to ECC for everything else (line 34066-34070).

The documentation should explicitly state which key types are supported (RSA_TYPE and ECC_TYPE only) and explain that other algorithms like Ed25519, Ed448, and post-quantum schemes cannot use this callback-based API because they sign messages directly rather than hashes.

Copilot uses AI. Check for mistakes.
Comment on lines 20047 to 20172
static int test_wc_SignCert_cb(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_CERT_GEN) && !defined(NO_ASN_TIME)

#ifdef HAVE_ECC
/* Test with ECC key */
{
Cert cert;
byte der[FOURK_BUF];
int derSize = 0;
WC_RNG rng;
ecc_key key;
MockSignCtx signCtx;
int ret;

XMEMSET(&rng, 0, sizeof(WC_RNG));
XMEMSET(&key, 0, sizeof(ecc_key));
XMEMSET(&cert, 0, sizeof(Cert));
XMEMSET(&signCtx, 0, sizeof(MockSignCtx));

ExpectIntEQ(wc_InitRng(&rng), 0);
ExpectIntEQ(wc_ecc_init(&key), 0);
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &key), 0);
ExpectIntEQ(wc_InitCert(&cert), 0);

(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.state, "state", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.locality, "locality", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.org, "org", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.unit, "unit", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.commonName, "www.example.com",
CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.email, "test@example.com", CTC_NAME_SIZE);

cert.selfSigned = 1;
cert.isCA = 0;
cert.sigType = CTC_SHA256wECDSA;

/* Make cert body */
ExpectIntGT(wc_MakeCert(&cert, der, FOURK_BUF, NULL, &key, &rng), 0);

/* Setup signing context with key and RNG */
signCtx.key = &key;
signCtx.rng = &rng;

/* Sign using callback API */
ExpectIntGT(derSize = wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, ECC_TYPE, mockSignCb, &signCtx, &rng), 0);

/* Verify the certificate was created properly */
ExpectIntGT(derSize, 0);

/* Test error cases */
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, ECC_TYPE, NULL, &signCtx, &rng), BAD_FUNC_ARG);

ret = wc_ecc_free(&key);
ExpectIntEQ(ret, 0);
ret = wc_FreeRng(&rng);
ExpectIntEQ(ret, 0);
}
#endif /* HAVE_ECC */

#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN)
/* Test with RSA key */
{
Cert cert;
byte der[FOURK_BUF];
int derSize = 0;
WC_RNG rng;
RsaKey key;
MockSignCtx signCtx;
int ret;

XMEMSET(&rng, 0, sizeof(WC_RNG));
XMEMSET(&key, 0, sizeof(RsaKey));
XMEMSET(&cert, 0, sizeof(Cert));
XMEMSET(&signCtx, 0, sizeof(MockSignCtx));

ExpectIntEQ(wc_InitRng(&rng), 0);
ExpectIntEQ(wc_InitRsaKey(&key, NULL), 0);
ExpectIntEQ(wc_MakeRsaKey(&key, 2048, WC_RSA_EXPONENT, &rng), 0);
ExpectIntEQ(wc_InitCert(&cert), 0);

(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.state, "state", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.locality, "locality", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.org, "org", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.unit, "unit", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.commonName, "www.example.com",
CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.email, "test@example.com", CTC_NAME_SIZE);

cert.selfSigned = 1;
cert.isCA = 0;
cert.sigType = CTC_SHA256wRSA;

/* Make cert body */
ExpectIntGT(wc_MakeCert(&cert, der, FOURK_BUF, &key, NULL, &rng), 0);

/* Setup signing context with key and RNG */
signCtx.key = &key;
signCtx.rng = &rng;

/* Sign using callback API with RSA */
ExpectIntGT(derSize = wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, RSA_TYPE, mockSignCb, &signCtx, &rng), 0);

/* Verify the certificate was created properly */
ExpectIntGT(derSize, 0);

/* Test error case - NULL callback */
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, RSA_TYPE, NULL, &signCtx, &rng), BAD_FUNC_ARG);

ret = wc_FreeRsaKey(&key);
ExpectIntEQ(ret, 0);
ret = wc_FreeRng(&rng);
ExpectIntEQ(ret, 0);
}
#endif /* !NO_RSA && WOLFSSL_KEY_GEN */

#endif /* WOLFSSL_CERT_GEN && !NO_ASN_TIME */
return EXPECT_RESULT();
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Limited error case testing for wc_SignCert_cb. The tests only verify:

  1. NULL callback returns BAD_FUNC_ARG (lines 20101-20102, 20160-20161)

Missing error case tests include:

  1. NULL buf parameter
  2. Invalid keyType (e.g., unsupported key type like ED25519_TYPE)
  3. Buffer too small (buffSz smaller than needed)
  4. Negative requestSz
  5. Callback returning an error
  6. Callback returning insufficient signature data in outLen

These additional tests would help ensure the API handles error conditions correctly and returns appropriate error codes to callers.

Copilot uses AI. Check for mistakes.
Comment on lines 32194 to 32224
/* For RSA and ECC, use the callback path to eliminate duplication */
#if (!defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && !defined(WOLFSSL_RSA_VERIFY_ONLY)) || \
(defined(HAVE_ECC) && defined(HAVE_ECC_SIGN))
if (rsaKey || eccKey) {
InternalSignCtx signCtx;

ret = HashForSignature(buf, sz, sigAlgoType, certSignCtx->digest,
&typeH, &digestSz, 0, NULL,
INVALID_DEVID);
/* set next state, since WC_PENDING_E rentry for these are not "call again" */
certSignCtx->state = CERTSIGN_STATE_ENCODE;
if (ret != 0) {
goto exit_ms;
}
FALL_THROUGH;
/* Setup internal signing context */
XMEMSET(&signCtx, 0, sizeof(signCtx));
signCtx.rng = rng;

case CERTSIGN_STATE_ENCODE:
#ifndef NO_RSA
/* Determine key type and set key pointer */
if (rsaKey) {
#ifndef WOLFSSL_NO_MALLOC
certSignCtx->encSig = (byte*)XMALLOC(MAX_DER_DIGEST_SZ, heap,
DYNAMIC_TYPE_TMP_BUFFER);
if (certSignCtx->encSig == NULL) {
ret = MEMORY_E; goto exit_ms;
}
#endif

/* signature */
certSignCtx->encSigSz = (int)wc_EncodeSignature(certSignCtx->encSig,
certSignCtx->digest, (word32)digestSz, typeH);
signCtx.key = rsaKey;
signCtx.keyType = RSA_TYPE;
}
#endif /* !NO_RSA */
FALL_THROUGH;

case CERTSIGN_STATE_DO:
certSignCtx->state = CERTSIGN_STATE_DO;
ret = -1; /* default to error, reassigned to ALGO_ID_E below. */

#if !defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && !defined(WOLFSSL_RSA_VERIFY_ONLY)
if (rsaKey) {
/* signature */
ret = wc_RsaSSL_Sign(certSignCtx->encSig,
(word32)certSignCtx->encSigSz,
sig, sigSz, rsaKey, rng);
else if (eccKey) {
signCtx.key = eccKey;
signCtx.keyType = ECC_TYPE;
}
#endif /* !NO_RSA */

#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN)
if (!rsaKey && eccKey) {
word32 outSz = sigSz;

ret = wc_ecc_sign_hash(certSignCtx->digest, (word32)digestSz,
sig, &outSz, rng, eccKey);
if (ret == 0)
ret = (int)outSz;
else {
ret = BAD_FUNC_ARG;
goto exit_ms;
}
#endif /* HAVE_ECC && HAVE_ECC_SIGN */

#if defined(HAVE_ED25519) && defined(HAVE_ED25519_SIGN)
if (!rsaKey && !eccKey && ed25519Key) {
word32 outSz = sigSz;
/* Use unified callback path */
ret = MakeSignatureCb(certSignCtx, buf, sz, sig, sigSz,
(int)sigAlgoType, signCtx.keyType,
InternalSignCb, &signCtx, rng, heap);
goto exit_ms;
}
#endif
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The refactored MakeSignature function no longer handles state management for async operations (CertSignCtx state machine), which is inconsistent with the original implementation. The original MakeSignature had a state machine (CERTSIGN_STATE_BEGIN, CERTSIGN_STATE_DIGEST, CERTSIGN_STATE_ENCODE, CERTSIGN_STATE_DO) that was critical for async crypto operations.

When WOLFSSL_ASYNC_CRYPT is enabled, the CertSignCtx state needs to be preserved across calls when WC_PENDING_E is returned. The new implementation delegates to MakeSignatureCb, which does have state management, but MakeSignature itself no longer handles state transitions properly for RSA/ECC cases.

The original MakeSignature code handled the full state machine in the main function body, allowing it to return WC_PENDING_E at different stages. Now the RSA/ECC path jumps straight to MakeSignatureCb and exits, which may break async operations if they are in progress.

Copilot uses AI. Check for mistakes.
Comment on lines 34426 to 34434
if (signCb == NULL || buf == NULL) {
return BAD_FUNC_ARG;
}

XMEMSET(certSignCtx, 0, sizeof(*certSignCtx));

if (requestSz < 0) {
return requestSz;
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Insufficient parameter validation in wc_SignCert_cb. The function validates that signCb and buf are not NULL (line 34426), but doesn't validate other critical parameters:

  1. buffSz is never checked - if buffSz is 0 or very small, buffer overflow could occur when calling AddSignature at line 34461.
  2. keyType is never validated - invalid/unsupported key types will silently be treated as ECC in MakeSignatureCb (line 34066-34070), potentially causing incorrect signature generation.
  3. signCtx is allowed to be NULL without validation, but the callback might expect it to be non-NULL (though this may be intentional for stateless callbacks).

The function should validate these parameters and return BAD_FUNC_ARG if they are invalid. For consistency, look at wc_SignCert_ex (line 34340) which doesn't validate keyType either, but that function has explicit casting logic for each supported key type.

Copilot uses AI. Check for mistakes.
Comment on lines 34078 to 34091
exit_ms:
#ifndef WOLFSSL_NO_MALLOC
#ifndef NO_RSA
if (keyType == RSA_TYPE) {
XFREE(certSignCtx->encSig, heap, DYNAMIC_TYPE_TMP_BUFFER);
certSignCtx->encSig = NULL;
}
#endif /* !NO_RSA */
XFREE(certSignCtx->digest, heap, DYNAMIC_TYPE_TMP_BUFFER);
certSignCtx->digest = NULL;
#endif

/* reset state */
certSignCtx->state = CERTSIGN_STATE_BEGIN;
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

MakeSignatureCb always resets the certSignCtx state and frees memory at exit_ms (lines 34078-34091), even when an error occurs in the middle of processing. This is problematic for async operations when combined with wc_SignCert_cb.

When using WOLFSSL_ASYNC_CRYPT, if the callback returns WC_PENDING_E, the function should preserve the state (not reset it to CERTSIGN_STATE_BEGIN) and not free the digest/encSig buffers, as they will be needed when the operation resumes. Currently, any error path (including WC_PENDING_E) will cause state reset and memory deallocation.

The cleanup at exit_ms should be conditional: only reset state and free memory if the operation completed (successfully or with a final error), not if it's pending. The original MakeSignature function structure avoided this by only having the cleanup code outside the main logic, but here it's in a shared exit path.

Copilot uses AI. Check for mistakes.
Comment on lines 34456 to 34462
if (sigSz >= 0) {
if (requestSz + MAX_SEQ_SZ * 2 + sigSz > (int)buffSz) {
sigSz = BUFFER_E;
}
else {
sigSz = AddSignature(buf, requestSz, certSignCtx->sig, sigSz, sType);
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The buffer size check in wc_SignCert_cb (line 34457) may be insufficient. The check requestSz + MAX_SEQ_SZ * 2 + sigSz > (int)buffSz is an approximation that may underestimate the actual space needed.

Looking at AddSignature (line 32368), it adds:

  1. Algorithm identifier (SetAlgoID)
  2. Bit string header (SetBitString)
  3. Signature data
  4. Outer sequence header (SetSequence)

The check uses MAX_SEQ_SZ * 2 to account for headers, but AddSignature also calls SetAlgoID which can vary in size depending on the algorithm. For some algorithms, the OID encoding plus any parameters could exceed the space allocated by this estimate.

A more accurate approach would be to either:

  1. Call AddSignature with a NULL buffer first to get the exact size needed (as the function supports this), or
  2. Use a more conservative buffer size estimate that accounts for algorithm-specific overhead

Compare this to SignCert (line 34162) which uses the same check, suggesting this is an existing pattern in the codebase, but it may still be worth documenting the buffer size requirements more clearly.

Copilot uses AI. Check for mistakes.
Comment on lines 20047 to 20172
static int test_wc_SignCert_cb(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_CERT_GEN) && !defined(NO_ASN_TIME)

#ifdef HAVE_ECC
/* Test with ECC key */
{
Cert cert;
byte der[FOURK_BUF];
int derSize = 0;
WC_RNG rng;
ecc_key key;
MockSignCtx signCtx;
int ret;

XMEMSET(&rng, 0, sizeof(WC_RNG));
XMEMSET(&key, 0, sizeof(ecc_key));
XMEMSET(&cert, 0, sizeof(Cert));
XMEMSET(&signCtx, 0, sizeof(MockSignCtx));

ExpectIntEQ(wc_InitRng(&rng), 0);
ExpectIntEQ(wc_ecc_init(&key), 0);
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &key), 0);
ExpectIntEQ(wc_InitCert(&cert), 0);

(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.state, "state", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.locality, "locality", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.org, "org", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.unit, "unit", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.commonName, "www.example.com",
CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.email, "test@example.com", CTC_NAME_SIZE);

cert.selfSigned = 1;
cert.isCA = 0;
cert.sigType = CTC_SHA256wECDSA;

/* Make cert body */
ExpectIntGT(wc_MakeCert(&cert, der, FOURK_BUF, NULL, &key, &rng), 0);

/* Setup signing context with key and RNG */
signCtx.key = &key;
signCtx.rng = &rng;

/* Sign using callback API */
ExpectIntGT(derSize = wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, ECC_TYPE, mockSignCb, &signCtx, &rng), 0);

/* Verify the certificate was created properly */
ExpectIntGT(derSize, 0);

/* Test error cases */
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, ECC_TYPE, NULL, &signCtx, &rng), BAD_FUNC_ARG);

ret = wc_ecc_free(&key);
ExpectIntEQ(ret, 0);
ret = wc_FreeRng(&rng);
ExpectIntEQ(ret, 0);
}
#endif /* HAVE_ECC */

#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN)
/* Test with RSA key */
{
Cert cert;
byte der[FOURK_BUF];
int derSize = 0;
WC_RNG rng;
RsaKey key;
MockSignCtx signCtx;
int ret;

XMEMSET(&rng, 0, sizeof(WC_RNG));
XMEMSET(&key, 0, sizeof(RsaKey));
XMEMSET(&cert, 0, sizeof(Cert));
XMEMSET(&signCtx, 0, sizeof(MockSignCtx));

ExpectIntEQ(wc_InitRng(&rng), 0);
ExpectIntEQ(wc_InitRsaKey(&key, NULL), 0);
ExpectIntEQ(wc_MakeRsaKey(&key, 2048, WC_RSA_EXPONENT, &rng), 0);
ExpectIntEQ(wc_InitCert(&cert), 0);

(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.state, "state", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.locality, "locality", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.org, "org", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.unit, "unit", CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.commonName, "www.example.com",
CTC_NAME_SIZE);
(void)XSTRNCPY(cert.subject.email, "test@example.com", CTC_NAME_SIZE);

cert.selfSigned = 1;
cert.isCA = 0;
cert.sigType = CTC_SHA256wRSA;

/* Make cert body */
ExpectIntGT(wc_MakeCert(&cert, der, FOURK_BUF, &key, NULL, &rng), 0);

/* Setup signing context with key and RNG */
signCtx.key = &key;
signCtx.rng = &rng;

/* Sign using callback API with RSA */
ExpectIntGT(derSize = wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, RSA_TYPE, mockSignCb, &signCtx, &rng), 0);

/* Verify the certificate was created properly */
ExpectIntGT(derSize, 0);

/* Test error case - NULL callback */
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
FOURK_BUF, RSA_TYPE, NULL, &signCtx, &rng), BAD_FUNC_ARG);

ret = wc_FreeRsaKey(&key);
ExpectIntEQ(ret, 0);
ret = wc_FreeRng(&rng);
ExpectIntEQ(ret, 0);
}
#endif /* !NO_RSA && WOLFSSL_KEY_GEN */

#endif /* WOLFSSL_CERT_GEN && !NO_ASN_TIME */
return EXPECT_RESULT();
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The test for wc_SignCert_cb doesn't verify the signature is valid or that the certificate structure is correctly formed. The test only checks that the function returns a positive value (lines 20094-20098 for ECC and 20153-20157 for RSA) but doesn't:

  1. Parse and verify the signed certificate structure
  2. Validate the signature against the certificate body
  3. Check that the signature algorithm matches expectations
  4. Verify the certificate can be parsed by wc_ParseCert

Consider adding verification similar to test_MakeCertWithCaFalse (lines 19977-19979) which uses wc_InitDecodedCert, wc_ParseCert, and validates the parsed certificate contents. This would provide stronger assurance that the callback-based signing produces correct certificates.

Copilot uses AI. Check for mistakes.
Comment on lines 34007 to 34070
int digestSz = 0, typeH = 0, ret = 0;
word32 outLen;

(void)rng;
#ifdef WOLFSSL_NO_MALLOC
(void)heap;
#endif

switch (certSignCtx->state) {
case CERTSIGN_STATE_BEGIN:
case CERTSIGN_STATE_DIGEST:
certSignCtx->state = CERTSIGN_STATE_DIGEST;
#ifndef WOLFSSL_NO_MALLOC
certSignCtx->digest = (byte*)XMALLOC(WC_MAX_DIGEST_SIZE, heap,
DYNAMIC_TYPE_TMP_BUFFER);
if (certSignCtx->digest == NULL) {
ret = MEMORY_E;
goto exit_ms;
}
#endif
ret = HashForSignature(buf, sz, (word32)sigAlgoType, certSignCtx->digest,
&typeH, &digestSz, 0, NULL, INVALID_DEVID);
certSignCtx->state = CERTSIGN_STATE_ENCODE;
if (ret != 0) {
goto exit_ms;
}
FALL_THROUGH;

case CERTSIGN_STATE_ENCODE:
/* For RSA, encode the digest with algorithm identifier */
#ifndef NO_RSA
if (keyType == RSA_TYPE) {
#ifndef WOLFSSL_NO_MALLOC
certSignCtx->encSig = (byte*)XMALLOC(MAX_DER_DIGEST_SZ, heap,
DYNAMIC_TYPE_TMP_BUFFER);
if (certSignCtx->encSig == NULL) {
ret = MEMORY_E;
goto exit_ms;
}
#endif
certSignCtx->encSigSz = (int)wc_EncodeSignature(certSignCtx->encSig,
certSignCtx->digest, (word32)digestSz, typeH);
}
#endif /* !NO_RSA */
FALL_THROUGH;

case CERTSIGN_STATE_DO:
certSignCtx->state = CERTSIGN_STATE_DO;
outLen = sigSz;

/* Call the user-provided signing callback */
#ifndef NO_RSA
if (keyType == RSA_TYPE) {
/* RSA: pass encoded digest */
ret = signCb(certSignCtx->encSig, (word32)certSignCtx->encSigSz,
sig, &outLen, sigAlgoType, keyType, signCtx);
}
else
#endif /* !NO_RSA */
{
/* ECC: pass raw hash */
ret = signCb(certSignCtx->digest, (word32)digestSz,
sig, &outLen, sigAlgoType, keyType, signCtx);
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Potential use of uninitialized variable in MakeSignatureCb. The variable digestSz is declared at line 34007 but is only set by HashForSignature at line 34027. However, if the state machine enters at CERTSIGN_STATE_ENCODE (line 34035) or CERTSIGN_STATE_DO (line 34053) on a retry (e.g., after WC_PENDING_E from HashForSignature), the digestSz variable will be uninitialized but is used at line 34068.

This is a problem because:

  1. The state machine supports re-entry at any state
  2. When re-entering at CERTSIGN_STATE_ENCODE or CERTSIGN_STATE_DO, digestSz is not recalculated
  3. The ECC signing path at line 34068 uses digestSz: signCb(certSignCtx->digest, (word32)digestSz, ...)

The digestSz should either be:

  1. Stored in certSignCtx structure to persist across calls, or
  2. Recalculated from the digest buffer size

Looking at the original code structure, this wasn't an issue because the original MakeSignature didn't store digestSz in the context, but it also didn't have separate states for each phase in the same way.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants