Skip to content

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 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.

@@ -33861,6 +33805,103 @@ int wc_MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz,
#endif /* WOLFSSL_CERT_REQ */



Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like this new feature to be wrapped in new build option to avoid code growth. Also some of this looks to be duplicate code now. Is there way way to implement such that the old code uses the callback path internally when not set. If so you can keep it always like it is.

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.

Comment on lines 31958 to 32022
#if !defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && !defined(WOLFSSL_RSA_VERIFY_ONLY)
if (keyType == RSA_TYPE && signCtx->rsaKey) {
/* For RSA, input is already encoded digest */
ret = wc_RsaSSL_Sign(in, inLen, out, *outLen,
signCtx->rsaKey, signCtx->rng);
if (ret > 0) {
*outLen = (word32)ret;
ret = 0;
}
}
#endif /* !NO_RSA && !WOLFSSL_RSA_PUBLIC_ONLY && !WOLFSSL_RSA_VERIFY_ONLY */

#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN)
if (keyType == ECC_TYPE && signCtx->eccKey) {
/* For ECC, input is the raw hash */
ret = wc_ecc_sign_hash(in, inLen, out, outLen,
signCtx->rng, signCtx->eccKey);
}
#endif /* HAVE_ECC && HAVE_ECC_SIGN */

#if defined(HAVE_ED25519) && defined(HAVE_ED25519_SIGN)
if (keyType == ED25519_TYPE && signCtx->ed25519Key) {
/* Ed25519 needs the original message, not hash */
/* Note: For Ed25519, 'in' should be the original message buffer */
/* This is a limitation of the refactoring - Ed25519 signs messages, not hashes */
ret = NOT_COMPILED_IN; /* Cannot support Ed25519 through callback path */
}
#endif /* HAVE_ED25519 && HAVE_ED25519_SIGN */

#if defined(HAVE_ED448) && defined(HAVE_ED448_SIGN)
if (keyType == ED448_TYPE && signCtx->ed448Key) {
/* Ed448 needs the original message, not hash */
ret = NOT_COMPILED_IN; /* Cannot support Ed448 through callback path */
}
#endif /* HAVE_ED448 && HAVE_ED448_SIGN */

#if defined(HAVE_FALCON)
if (keyType == FALCON_LEVEL1_TYPE || keyType == FALCON_LEVEL5_TYPE) {
if (signCtx->falconKey) {
/* Falcon needs the original message */
ret = NOT_COMPILED_IN; /* Cannot support Falcon through callback path */
}
}
#endif /* HAVE_FALCON */

#if defined(HAVE_DILITHIUM) && !defined(WOLFSSL_DILITHIUM_NO_SIGN)
if (keyType == DILITHIUM_LEVEL2_TYPE || keyType == DILITHIUM_LEVEL3_TYPE ||
keyType == DILITHIUM_LEVEL5_TYPE) {
if (signCtx->dilithiumKey) {
/* Dilithium needs the original message */
ret = NOT_COMPILED_IN; /* Cannot support Dilithium through callback path */
}
}
#endif /* HAVE_DILITHIUM && !WOLFSSL_DILITHIUM_NO_SIGN */

#if defined(HAVE_SPHINCS)
if (keyType == SPHINCS_FAST_LEVEL1_TYPE || keyType == SPHINCS_FAST_LEVEL3_TYPE ||
keyType == SPHINCS_FAST_LEVEL5_TYPE || keyType == SPHINCS_SMALL_LEVEL1_TYPE ||
keyType == SPHINCS_SMALL_LEVEL3_TYPE || keyType == SPHINCS_SMALL_LEVEL5_TYPE) {
if (signCtx->sphincsKey) {
/* Sphincs needs the original message */
ret = NOT_COMPILED_IN; /* Cannot support Sphincs through callback path */
}
}
#endif /* HAVE_SPHINCS */
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The InternalSignCb function uses multiple independent if statements instead of else-if chains. While this works correctly in practice because MakeSignature ensures only one key type is set, it's inconsistent with the coding pattern elsewhere and could cause issues if the logic changes. The function should use else-if to make the mutual exclusivity explicit and prevent potential future bugs.

Copilot uses AI. Check for mistakes.
tests/api.c Outdated
word32 outSz = *outLen;

/* For RSA, input is pre-encoded digest, just sign it */
ret = wc_RsaSSL_Sign(in, inLen, out, outSz, rsaKey, NULL);
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The test mock callback passes NULL as the RNG parameter to wc_RsaSSL_Sign, but it should pass signCtx->rng to be consistent with the internal implementation and to support RSA features that require RNG (such as blinding). The RNG is available in the context and should be used.

Copilot uses AI. Check for mistakes.
Comment on lines +26980 to +27024
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);
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The test only exercises the ECC code path. The mockSignCb function includes RSA support, but test_wc_SignCert_cb only tests with ECC keys. Consider adding a separate test case or expanding this test to also verify RSA certificate signing via the callback to ensure complete test coverage.

Copilot uses AI. Check for mistakes.
word32 outLen;

(void)rng;
(void)heap;
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The heap parameter is marked as unused with (void)heap on line 33871, but it is actually used on lines 33878 and 33898 for memory allocation. The (void)heap statement should be removed to avoid misleading readers and to prevent potential compiler warnings in some configurations.

Copilot uses AI. Check for mistakes.
else
#endif /* !NO_RSA */
{
/* ECC/EdDSA: pass raw hash or message */
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The comment says "ECC/EdDSA: pass raw hash or message" but EdDSA algorithms (Ed25519, Ed448) are explicitly not supported through the callback path as shown in InternalSignCb. The comment should be corrected to just say "ECC: pass raw hash" to avoid confusion.

Copilot uses AI. Check for mistakes.
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.

Comment on lines 31925 to 31929
#ifndef WOLFSSL_CERT_SIGN_CB
typedef int (*wc_SignCertCb)(const byte* in, word32 inLen,
byte* out, word32* outLen,
int sigAlgo, int keyType, void* ctx);
#endif
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The wc_SignCertCb typedef is duplicated here as a fallback when WOLFSSL_CERT_SIGN_CB is not defined. This creates potential for inconsistency if the public API typedef in asn_public.h is updated but this internal version is not. Consider removing this internal typedef and ensuring that code using this callback always includes the public header, or add a comment explaining why the duplication is necessary.

Copilot uses AI. Check for mistakes.
Comment on lines +32054 to +32056
#if (!defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && !defined(WOLFSSL_RSA_VERIFY_ONLY)) || \
(defined(HAVE_ECC) && defined(HAVE_ECC_SIGN))
if (rsaKey || eccKey) {
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The condition checks if RSA or ECC is enabled, but then the if statement assumes at least one key is non-NULL. If both RSA and ECC are disabled at compile time, the code after line 32084 (exit_ms) will still execute and may fall through to algorithms that are not supported. The logic should ensure that when RSA and ECC are both disabled, the function properly handles or rejects the request.

Copilot uses AI. Check for mistakes.
static int test_wc_SignCert_cb(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && !defined(NO_ASN_TIME)
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The test only covers ECC key types, but the API and implementation support both RSA and ECC. Consider adding a test case for RSA to ensure the RSA code path (wc_RsaSSL_Sign in the callback) is also tested and works correctly with the new API.

Copilot uses AI. Check for mistakes.
/* Ed25519 needs the original message, not hash */
/* Note: For Ed25519, 'in' should be the original message buffer */
/* This is a limitation of the refactoring - Ed25519 signs messages, not hashes */
ret = NOT_COMPILED_IN; /* Cannot support Ed25519 through callback path */
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The NOT_COMPILED_IN error code for Ed25519, Ed448, and post-quantum algorithms may be confusing to users since these algorithms ARE compiled in when their respective macros are defined. A more descriptive error code like NOT_SUPPORTED or a new error code like SIG_ALGO_NOT_SUPPORTED_BY_CB would better convey that the callback path doesn't support these signature types.

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