Skip to content

Commit 16ba668

Browse files
authored
Merge pull request #9632 from jackctj117/CSR-signing
Add wc_SignCert_cb API for external signing callbacks
2 parents 1c77414 + d581a40 commit 16ba668

File tree

6 files changed

+817
-139
lines changed

6 files changed

+817
-139
lines changed

configure.ac

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4622,6 +4622,19 @@ then
46224622
fi
46234623

46244624

4625+
# CERT SIGN CALLBACK
4626+
AC_ARG_ENABLE([certsigncb],
4627+
[AS_HELP_STRING([--enable-certsigncb],[Enable cert signing callback API for TPM/HSM (default: disabled)])],
4628+
[ ENABLED_CERTSIGNCB=$enableval ],
4629+
[ ENABLED_CERTSIGNCB=no ]
4630+
)
4631+
4632+
if test "$ENABLED_CERTSIGNCB" = "yes"
4633+
then
4634+
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_CERT_SIGN_CB"
4635+
fi
4636+
4637+
46254638
# SEP
46264639
AC_ARG_ENABLE([sep],
46274640
[AS_HELP_STRING([--enable-sep],[Enable sep extensions (default: disabled)])],

doc/dox_comments/header_files/asn.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,73 @@ word32 SetAlgoID(int algoOID, byte* output, int type, int curveSz);
237237
*/
238238
int wc_DhPublicKeyDecode(const byte* input, word32* inOutIdx, DhKey* key,
239239
word32 inSz);
240+
241+
/*!
242+
\ingroup CertManager
243+
\brief Sign a certificate or CSR using a callback function.
244+
245+
This function signs a certificate or Certificate Signing Request (CSR)
246+
using a user-provided signing callback. This allows external signing
247+
implementations (e.g., TPM, HSM) without requiring the crypto callback
248+
infrastructure, making it suitable for FIPS-compliant applications.
249+
250+
The function performs the following:
251+
1. Hashes the certificate/CSR body according to the signature algorithm
252+
2. Encodes the hash (RSA) or prepares it for signing (ECC)
253+
3. Calls the user-provided callback to perform the actual signing
254+
4. Encodes the signature into the certificate/CSR DER structure
255+
256+
NOTE: Only RSA and ECC key types are supported. Ed25519, Ed448, and
257+
post-quantum algorithms (Falcon, Dilithium, SPHINCS+) sign messages
258+
directly rather than hashes, so they cannot use this callback-based API.
259+
Use wc_SignCert_ex for those algorithms.
260+
261+
NOTE: This function does NOT support async crypto (WOLFSSL_ASYNC_CRYPT).
262+
The internal context is local to this function and cannot persist across
263+
async re-entry.
264+
265+
\param requestSz Size of the certificate body to sign (from Cert.bodySz).
266+
\param sType Signature algorithm type (e.g., CTC_SHA256wRSA,
267+
CTC_SHA256wECDSA).
268+
\param buf Buffer containing the certificate/CSR DER data to sign.
269+
\param buffSz Total size of the buffer (must be large enough for signature).
270+
\param keyType Type of key used for signing. Only RSA_TYPE and ECC_TYPE
271+
are supported.
272+
\param signCb User-provided signing callback function.
273+
\param signCtx Context pointer passed to the signing callback.
274+
\param rng Random number generator (may be NULL if not needed).
275+
276+
\return Size of the signed certificate/CSR on success.
277+
\return BAD_FUNC_ARG if signCb or buf is NULL, buffSz is 0, or keyType
278+
is not RSA_TYPE or ECC_TYPE.
279+
\return BUFFER_E if the buffer is too small for the signed certificate.
280+
\return MEMORY_E if memory allocation fails.
281+
\return Negative error code on other failures.
282+
283+
_Example_
284+
\code
285+
Cert cert;
286+
byte derBuf[4096];
287+
int derSz;
288+
MySignCtx myCtx;
289+
290+
wc_InitCert(&cert);
291+
292+
derSz = wc_MakeCert(&cert, derBuf, sizeof(derBuf), NULL, NULL, &rng);
293+
294+
derSz = wc_SignCert_cb(cert.bodySz, cert.sigType, derBuf, sizeof(derBuf),
295+
RSA_TYPE, mySignCallback, &myCtx, &rng);
296+
if (derSz > 0) {
297+
printf("Signed certificate is %d bytes\n", derSz);
298+
}
299+
\endcode
300+
301+
\sa wc_SignCertCb
302+
\sa wc_SignCert
303+
\sa wc_SignCert_ex
304+
\sa wc_MakeCert
305+
\sa wc_MakeCertReq
306+
*/
307+
int wc_SignCert_cb(int requestSz, int sType, byte* buf, word32 buffSz,
308+
int keyType, wc_SignCertCb signCb, void* signCtx,
309+
WC_RNG* rng);

tests/api.c

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20097,6 +20097,262 @@ static int test_MakeCertWithCaFalse(void)
2009720097
return EXPECT_RESULT();
2009820098
}
2009920099

20100+
/* Mock callback for testing wc_SignCert_cb */
20101+
#if defined(WOLFSSL_CERT_SIGN_CB) && (defined(WOLFSSL_CERT_GEN) || defined(WOLFSSL_CERT_REQ))
20102+
/* Context structure for mock signing callback */
20103+
typedef struct {
20104+
void* key; /* Pointer to RSA or ECC key */
20105+
WC_RNG* rng; /* Random number generator (required for ECC) */
20106+
} MockSignCtx;
20107+
20108+
static int mockSignCb(const byte* in, word32 inLen, byte* out, word32* outLen,
20109+
int sigAlgo, int keyType, void* ctx)
20110+
{
20111+
int ret = 0;
20112+
MockSignCtx* signCtx = (MockSignCtx*)ctx;
20113+
20114+
if (signCtx == NULL || signCtx->key == NULL || in == NULL ||
20115+
out == NULL || outLen == NULL) {
20116+
return BAD_FUNC_ARG;
20117+
}
20118+
20119+
(void)sigAlgo;
20120+
20121+
#ifndef NO_RSA
20122+
if (keyType == RSA_TYPE) {
20123+
RsaKey* rsaKey = (RsaKey*)signCtx->key;
20124+
word32 outSz = *outLen;
20125+
20126+
/* For RSA, input is DER-encoded digest (DigestInfo structure) */
20127+
ret = wc_RsaSSL_Sign(in, inLen, out, outSz, rsaKey, signCtx->rng);
20128+
if (ret > 0) {
20129+
*outLen = (word32)ret;
20130+
ret = 0;
20131+
}
20132+
}
20133+
else
20134+
#endif
20135+
#ifdef HAVE_ECC
20136+
if (keyType == ECC_TYPE) {
20137+
ecc_key* eccKey = (ecc_key*)signCtx->key;
20138+
word32 outSz = *outLen;
20139+
20140+
/* For ECC, input is raw hash, sign it (RNG required for ECDSA k value) */
20141+
ret = wc_ecc_sign_hash(in, inLen, out, &outSz, signCtx->rng, eccKey);
20142+
if (ret == 0) {
20143+
*outLen = outSz;
20144+
}
20145+
}
20146+
else
20147+
#endif
20148+
{
20149+
ret = BAD_FUNC_ARG;
20150+
}
20151+
20152+
return ret;
20153+
}
20154+
20155+
/* Mock callback that always returns an error for testing */
20156+
static int mockSignCbError(const byte* in, word32 inLen, byte* out,
20157+
word32* outLen, int sigAlgo, int keyType, void* ctx)
20158+
{
20159+
(void)in;
20160+
(void)inLen;
20161+
(void)out;
20162+
(void)outLen;
20163+
(void)sigAlgo;
20164+
(void)keyType;
20165+
(void)ctx;
20166+
return BAD_STATE_E; /* Return an error */
20167+
}
20168+
#endif
20169+
20170+
#ifdef WOLFSSL_CERT_SIGN_CB
20171+
static int test_wc_SignCert_cb(void)
20172+
{
20173+
EXPECT_DECLS;
20174+
#if defined(WOLFSSL_CERT_GEN) && !defined(NO_ASN_TIME)
20175+
20176+
#ifdef HAVE_ECC
20177+
/* Test with ECC key */
20178+
{
20179+
Cert cert;
20180+
byte der[FOURK_BUF];
20181+
int derSize = 0;
20182+
WC_RNG rng;
20183+
ecc_key key;
20184+
MockSignCtx signCtx;
20185+
DecodedCert decodedCert;
20186+
int ret;
20187+
20188+
XMEMSET(&rng, 0, sizeof(WC_RNG));
20189+
XMEMSET(&key, 0, sizeof(ecc_key));
20190+
XMEMSET(&cert, 0, sizeof(Cert));
20191+
XMEMSET(&signCtx, 0, sizeof(MockSignCtx));
20192+
XMEMSET(&decodedCert, 0, sizeof(DecodedCert));
20193+
20194+
ExpectIntEQ(wc_InitRng(&rng), 0);
20195+
ExpectIntEQ(wc_ecc_init(&key), 0);
20196+
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &key), 0);
20197+
ExpectIntEQ(wc_InitCert(&cert), 0);
20198+
20199+
(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
20200+
(void)XSTRNCPY(cert.subject.state, "state", CTC_NAME_SIZE);
20201+
(void)XSTRNCPY(cert.subject.locality, "locality", CTC_NAME_SIZE);
20202+
(void)XSTRNCPY(cert.subject.org, "org", CTC_NAME_SIZE);
20203+
(void)XSTRNCPY(cert.subject.unit, "unit", CTC_NAME_SIZE);
20204+
(void)XSTRNCPY(cert.subject.commonName, "www.example.com",
20205+
CTC_NAME_SIZE);
20206+
(void)XSTRNCPY(cert.subject.email, "test@example.com", CTC_NAME_SIZE);
20207+
20208+
cert.selfSigned = 1;
20209+
cert.isCA = 0;
20210+
cert.sigType = CTC_SHA256wECDSA;
20211+
20212+
/* Make cert body */
20213+
ExpectIntGT(wc_MakeCert(&cert, der, FOURK_BUF, NULL, &key, &rng), 0);
20214+
20215+
/* Setup signing context with key and RNG */
20216+
signCtx.key = &key;
20217+
signCtx.rng = &rng;
20218+
20219+
/* Sign using callback API */
20220+
ExpectIntGT(derSize = wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20221+
FOURK_BUF, ECC_TYPE, mockSignCb, &signCtx, &rng), 0);
20222+
20223+
/* Verify the certificate was created properly */
20224+
ExpectIntGT(derSize, 0);
20225+
20226+
/* Parse the certificate and verify it's well-formed */
20227+
if (EXPECT_SUCCESS()) {
20228+
wc_InitDecodedCert(&decodedCert, der, (word32)derSize, NULL);
20229+
ExpectIntEQ(wc_ParseCert(&decodedCert, CERT_TYPE, NO_VERIFY, NULL),
20230+
0);
20231+
/* Verify signature algorithm matches what we set */
20232+
ExpectIntEQ(decodedCert.signatureOID, CTC_SHA256wECDSA);
20233+
wc_FreeDecodedCert(&decodedCert);
20234+
}
20235+
20236+
/* Test error cases */
20237+
/* NULL callback */
20238+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20239+
FOURK_BUF, ECC_TYPE, NULL, &signCtx, &rng), BAD_FUNC_ARG);
20240+
/* NULL buffer */
20241+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, NULL,
20242+
FOURK_BUF, ECC_TYPE, mockSignCb, &signCtx, &rng), BAD_FUNC_ARG);
20243+
/* Zero buffer size */
20244+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20245+
0, ECC_TYPE, mockSignCb, &signCtx, &rng), BAD_FUNC_ARG);
20246+
/* Negative requestSz - should return the negative value */
20247+
ExpectIntLT(wc_SignCert_cb(-1, cert.sigType, der,
20248+
FOURK_BUF, ECC_TYPE, mockSignCb, &signCtx, &rng), 0);
20249+
/* Callback returning error */
20250+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20251+
FOURK_BUF, ECC_TYPE, mockSignCbError, &signCtx, &rng), BAD_STATE_E);
20252+
#ifndef NO_RSA
20253+
/* Invalid keyType for ECC signature */
20254+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20255+
FOURK_BUF, ED25519_TYPE, mockSignCb, &signCtx, &rng), BAD_FUNC_ARG);
20256+
#endif
20257+
20258+
ret = wc_ecc_free(&key);
20259+
ExpectIntEQ(ret, 0);
20260+
ret = wc_FreeRng(&rng);
20261+
ExpectIntEQ(ret, 0);
20262+
}
20263+
#endif /* HAVE_ECC */
20264+
20265+
#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN)
20266+
/* Test with RSA key */
20267+
{
20268+
Cert cert;
20269+
byte der[FOURK_BUF];
20270+
int derSize = 0;
20271+
WC_RNG rng;
20272+
RsaKey key;
20273+
MockSignCtx signCtx;
20274+
DecodedCert decodedCert;
20275+
int ret;
20276+
20277+
XMEMSET(&rng, 0, sizeof(WC_RNG));
20278+
XMEMSET(&key, 0, sizeof(RsaKey));
20279+
XMEMSET(&cert, 0, sizeof(Cert));
20280+
XMEMSET(&signCtx, 0, sizeof(MockSignCtx));
20281+
XMEMSET(&decodedCert, 0, sizeof(DecodedCert));
20282+
20283+
ExpectIntEQ(wc_InitRng(&rng), 0);
20284+
ExpectIntEQ(wc_InitRsaKey(&key, NULL), 0);
20285+
ExpectIntEQ(wc_MakeRsaKey(&key, 2048, WC_RSA_EXPONENT, &rng), 0);
20286+
ExpectIntEQ(wc_InitCert(&cert), 0);
20287+
20288+
(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
20289+
(void)XSTRNCPY(cert.subject.state, "state", CTC_NAME_SIZE);
20290+
(void)XSTRNCPY(cert.subject.locality, "locality", CTC_NAME_SIZE);
20291+
(void)XSTRNCPY(cert.subject.org, "org", CTC_NAME_SIZE);
20292+
(void)XSTRNCPY(cert.subject.unit, "unit", CTC_NAME_SIZE);
20293+
(void)XSTRNCPY(cert.subject.commonName, "www.example.com",
20294+
CTC_NAME_SIZE);
20295+
(void)XSTRNCPY(cert.subject.email, "test@example.com", CTC_NAME_SIZE);
20296+
20297+
cert.selfSigned = 1;
20298+
cert.isCA = 0;
20299+
cert.sigType = CTC_SHA256wRSA;
20300+
20301+
/* Make cert body */
20302+
ExpectIntGT(wc_MakeCert(&cert, der, FOURK_BUF, &key, NULL, &rng), 0);
20303+
20304+
/* Setup signing context with key and RNG */
20305+
signCtx.key = &key;
20306+
signCtx.rng = &rng;
20307+
20308+
/* Sign using callback API with RSA */
20309+
ExpectIntGT(derSize = wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20310+
FOURK_BUF, RSA_TYPE, mockSignCb, &signCtx, &rng), 0);
20311+
20312+
/* Verify the certificate was created properly */
20313+
ExpectIntGT(derSize, 0);
20314+
20315+
/* Parse the certificate and verify it's well-formed */
20316+
if (EXPECT_SUCCESS()) {
20317+
wc_InitDecodedCert(&decodedCert, der, (word32)derSize, NULL);
20318+
ExpectIntEQ(wc_ParseCert(&decodedCert, CERT_TYPE, NO_VERIFY, NULL),
20319+
0);
20320+
/* Verify signature algorithm matches what we set */
20321+
ExpectIntEQ(decodedCert.signatureOID, CTC_SHA256wRSA);
20322+
wc_FreeDecodedCert(&decodedCert);
20323+
}
20324+
20325+
/* Test error cases */
20326+
/* NULL callback */
20327+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20328+
FOURK_BUF, RSA_TYPE, NULL, &signCtx, &rng), BAD_FUNC_ARG);
20329+
/* NULL buffer */
20330+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, NULL,
20331+
FOURK_BUF, RSA_TYPE, mockSignCb, &signCtx, &rng), BAD_FUNC_ARG);
20332+
/* Zero buffer size */
20333+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20334+
0, RSA_TYPE, mockSignCb, &signCtx, &rng), BAD_FUNC_ARG);
20335+
/* Callback returning error */
20336+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20337+
FOURK_BUF, RSA_TYPE, mockSignCbError, &signCtx, &rng), BAD_STATE_E);
20338+
#ifdef HAVE_ECC
20339+
/* Invalid keyType */
20340+
ExpectIntEQ(wc_SignCert_cb(cert.bodySz, cert.sigType, der,
20341+
FOURK_BUF, ED448_TYPE, mockSignCb, &signCtx, &rng), BAD_FUNC_ARG);
20342+
#endif
20343+
20344+
ret = wc_FreeRsaKey(&key);
20345+
ExpectIntEQ(ret, 0);
20346+
ret = wc_FreeRng(&rng);
20347+
ExpectIntEQ(ret, 0);
20348+
}
20349+
#endif /* !NO_RSA && WOLFSSL_KEY_GEN */
20350+
20351+
#endif /* WOLFSSL_CERT_GEN && !NO_ASN_TIME */
20352+
return EXPECT_RESULT();
20353+
}
20354+
#endif /* WOLFSSL_CERT_SIGN_CB */
20355+
2010020356
static int test_ERR_load_crypto_strings(void)
2010120357
{
2010220358
#if defined(OPENSSL_ALL)
@@ -31577,6 +31833,9 @@ TEST_CASE testCases[] = {
3157731833
TEST_DECL(test_MakeCertWithPathLen),
3157831834
TEST_DECL(test_MakeCertWith0Ser),
3157931835
TEST_DECL(test_MakeCertWithCaFalse),
31836+
#ifdef WOLFSSL_CERT_SIGN_CB
31837+
TEST_DECL(test_wc_SignCert_cb),
31838+
#endif
3158031839
TEST_DECL(test_wc_SetKeyUsage),
3158131840
TEST_DECL(test_wc_SetAuthKeyIdFromPublicKey_ex),
3158231841
TEST_DECL(test_wc_SetSubjectBuffer),

0 commit comments

Comments
 (0)