Skip to content

Commit 24f3f38

Browse files
committed
Add RSA/DH SP non-blocking support for C/Small 2048/3072/4096
1 parent 5ffdb9f commit 24f3f38

13 files changed

Lines changed: 4382 additions & 29 deletions

File tree

.github/workflows/os-check.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,14 @@ jobs:
106106
'CPPFLAGS=-DNO_WOLFSSL_CLIENT',
107107
'CPPFLAGS=-DNO_WOLFSSL_SERVER',
108108
'--enable-lms=small,verify-only --enable-xmss=small,verify-only',
109-
'--enable-curve25519=nonblock --enable-ecc=nonblock --enable-sp=yes,nonblock CPPFLAGS="-DWOLFSSL_PUBLIC_MP -DWOLFSSL_DEBUG_NONBLOCK"',
109+
# Non-blocking ECC + Curve25519 + RSA + DH on the default SP word
110+
# size for the host (sp_c64.c on x86_64). RSA/DH non-block require
111+
# RSA_LOW_MEM (CRT path is not supported in non-block mode).
112+
'--enable-curve25519=nonblock --enable-ecc=nonblock --enable-rsa=nonblock --enable-dh=nonblock --enable-sp=yes,nonblock CPPFLAGS="-DWOLFSSL_PUBLIC_MP -DWOLFSSL_DEBUG_NONBLOCK -DRSA_LOW_MEM"',
113+
# Same configuration but force SP_WORD_SIZE=32 to exercise sp_c32.c
114+
# on a 64-bit host. The two builds together cover both generated
115+
# variants of mod_exp_<words>_nb / RSA / DH wrappers.
116+
'--enable-curve25519=nonblock --enable-ecc=nonblock --enable-rsa=nonblock --enable-dh=nonblock --enable-sp=yes,nonblock CPPFLAGS="-DWOLFSSL_PUBLIC_MP -DWOLFSSL_DEBUG_NONBLOCK -DRSA_LOW_MEM -DSP_WORD_SIZE=32"',
110117
'--enable-certreq --enable-certext --enable-certgen --disable-secure-renegotiation-info CPPFLAGS="-DNO_TLS"',
111118
]
112119
name: make check linux

configure.ac

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4880,6 +4880,15 @@ then
48804880
test -z "$enable_asynccrypt_sw" && enable_asynccrypt_sw=yes
48814881
fi
48824882

4883+
# Handle RSA/DH nonblock - the SP non-blocking dispatch wants the same
4884+
# WOLFSSL_ASYNC_CRYPT_SW shim that ECC/Curve25519 nonblock use so the
4885+
# TLS layer can manage per-SSL nb contexts and yield MP_WOULDBLOCK.
4886+
if test "$enable_rsa" = "nonblock" || test "$enable_dh" = "nonblock"
4887+
then
4888+
test -z "$enable_asynccrypt" && enable_asynccrypt=yes
4889+
test -z "$enable_asynccrypt_sw" && enable_asynccrypt_sw=yes
4890+
fi
4891+
48834892
if test "$ENABLED_CURVE25519" = "no" && test "$ENABLED_QUIC" = "yes" && test "$ENABLED_FIPS" = "no"
48844893
then
48854894
ENABLED_CURVE25519=yes
@@ -5396,14 +5405,25 @@ fi
53965405

53975406
# RSA
53985407
AC_ARG_ENABLE([rsa],
5399-
[AS_HELP_STRING([--enable-rsa],[Enable RSA (default: enabled)])],
5408+
[AS_HELP_STRING([--enable-rsa],[Enable RSA (default: enabled). Set to "nonblock" to enable non-blocking RSA via TFM fp_exptmod_nb or SP small mod_exp_nb])],
54005409
[ ENABLED_RSA=$enableval ],
54015410
[ ENABLED_RSA=yes ]
54025411
)
54035412

54045413
if test "$ENABLED_RSA" = "no"
54055414
then
54065415
AM_CFLAGS="$AM_CFLAGS -DNO_RSA"
5416+
elif test "$ENABLED_RSA" = "nonblock"
5417+
then
5418+
AM_CFLAGS="$AM_CFLAGS -DWC_RSA_NONBLOCK"
5419+
ENABLED_RSA=yes
5420+
ENABLED_CERTS=yes
5421+
# asynccrypt + asynccrypt-sw are auto-enabled earlier in this file when
5422+
# --enable-rsa=nonblock is detected, so the TLS layer can pick up the
5423+
# per-SSL nb context and yield MP_WOULDBLOCK. RSA_LOW_MEM is left as a
5424+
# user choice - the SP non-block backend's compile-time check in
5425+
# wolfssl/wolfcrypt/rsa.h enforces it for SP, while the TFM (fastmath)
5426+
# backend supports the CRT path without it.
54075427
else
54085428
# turn off RSA if leanpsk or leantls on
54095429
if test "$ENABLED_LEANPSK" = "yes" || test "$ENABLED_LEANTLS" = "yes"
@@ -5483,7 +5503,7 @@ fi
54835503

54845504
# DH
54855505
AC_ARG_ENABLE([dh],
5486-
[AS_HELP_STRING([--enable-dh],[Enable DH (default: enabled)])],
5506+
[AS_HELP_STRING([--enable-dh],[Enable DH (default: enabled). Set to "nonblock" to enable non-blocking DH key agreement via SP small mod_exp_nb])],
54875507
[ ENABLED_DH=$enableval ],
54885508
[ ENABLED_DH=yes ]
54895509
)
@@ -5496,6 +5516,11 @@ fi
54965516
if test "$ENABLED_DH" = "no"
54975517
then
54985518
AM_CFLAGS="$AM_CFLAGS -DNO_DH"
5519+
elif test "$ENABLED_DH" = "nonblock"
5520+
then
5521+
AM_CFLAGS="$AM_CFLAGS -DWC_DH_NONBLOCK"
5522+
ENABLED_DH=yes
5523+
# asynccrypt + asynccrypt-sw are auto-enabled earlier in this file.
54995524
else
55005525
# turn off DH if leanpsk or leantls on
55015526
if test "$ENABLED_LEANPSK" = "yes" || test "$ENABLED_LEANTLS" = "yes"

src/internal.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8300,6 +8300,15 @@ void FreeKey(WOLFSSL* ssl, int type, void** pKey)
83008300
switch (type) {
83018301
#ifndef NO_RSA
83028302
case DYNAMIC_TYPE_RSA:
8303+
#if defined(WC_RSA_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
8304+
defined(WC_ASYNC_ENABLE_RSA)
8305+
if (((RsaKey*)*pKey)->nb != NULL) {
8306+
XFREE(((RsaKey*)*pKey)->nb, ssl->heap,
8307+
DYNAMIC_TYPE_TMP_BUFFER);
8308+
((RsaKey*)*pKey)->nb = NULL;
8309+
}
8310+
#endif /* WC_RSA_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW &&
8311+
WC_ASYNC_ENABLE_RSA */
83038312
wc_FreeRsaKey((RsaKey*)*pKey);
83048313
break;
83058314
#endif /* ! NO_RSA */
@@ -8355,6 +8364,15 @@ void FreeKey(WOLFSSL* ssl, int type, void** pKey)
83558364
#endif /* HAVE_DILITHIUM */
83568365
#ifndef NO_DH
83578366
case DYNAMIC_TYPE_DH:
8367+
#if defined(WC_DH_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
8368+
defined(WC_ASYNC_ENABLE_DH)
8369+
if (((DhKey*)*pKey)->nb != NULL) {
8370+
XFREE(((DhKey*)*pKey)->nb, ssl->heap,
8371+
DYNAMIC_TYPE_TMP_BUFFER);
8372+
((DhKey*)*pKey)->nb = NULL;
8373+
}
8374+
#endif /* WC_DH_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW &&
8375+
WC_ASYNC_ENABLE_DH */
83588376
wc_FreeDhKey((DhKey*)*pKey);
83598377
break;
83608378
#endif /* !NO_DH */
@@ -8385,6 +8403,14 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey)
83858403
#if defined(WC_X25519_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW)
83868404
x25519_nb_ctx_t* x25519NbCtx;
83878405
#endif /* WC_X25519_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW */
8406+
#if !defined(NO_RSA) && defined(WC_RSA_NONBLOCK) && \
8407+
defined(WOLFSSL_ASYNC_CRYPT_SW) && defined(WC_ASYNC_ENABLE_RSA)
8408+
RsaNb* rsaNb;
8409+
#endif
8410+
#if !defined(NO_DH) && defined(WC_DH_NONBLOCK) && \
8411+
defined(WOLFSSL_ASYNC_CRYPT_SW) && defined(WC_ASYNC_ENABLE_DH)
8412+
DhNb* dhNb;
8413+
#endif
83888414

83898415
if (ssl == NULL || pKey == NULL) {
83908416
return BAD_FUNC_ARG;
@@ -8464,6 +8490,26 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey)
84648490
#ifndef NO_RSA
84658491
case DYNAMIC_TYPE_RSA:
84668492
ret = wc_InitRsaKey_ex((RsaKey*)*pKey, ssl->heap, ssl->devId);
8493+
#if defined(WC_RSA_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
8494+
defined(WC_ASYNC_ENABLE_RSA)
8495+
/* Only set non-blocking context when async device is active. With
8496+
* INVALID_DEVID there is no async loop to retry on MP_WOULDBLOCK, so
8497+
* skip non-blocking setup and use blocking mode instead. */
8498+
if (ret == 0 && ssl->devId != INVALID_DEVID) {
8499+
rsaNb = (RsaNb*)XMALLOC(sizeof(RsaNb), ssl->heap,
8500+
DYNAMIC_TYPE_TMP_BUFFER);
8501+
if (rsaNb == NULL) {
8502+
ret = MEMORY_E;
8503+
}
8504+
else {
8505+
ret = wc_RsaSetNonBlock((RsaKey*)*pKey, rsaNb);
8506+
if (ret != 0) {
8507+
XFREE(rsaNb, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
8508+
}
8509+
}
8510+
}
8511+
#endif /* WC_RSA_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW &&
8512+
WC_ASYNC_ENABLE_RSA */
84678513
break;
84688514
#endif /* ! NO_RSA */
84698515
#ifdef HAVE_ECC
@@ -8551,6 +8597,26 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey)
85518597
#ifndef NO_DH
85528598
case DYNAMIC_TYPE_DH:
85538599
ret = wc_InitDhKey_ex((DhKey*)*pKey, ssl->heap, ssl->devId);
8600+
#if defined(WC_DH_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
8601+
defined(WC_ASYNC_ENABLE_DH)
8602+
/* Only set non-blocking context when async device is active. With
8603+
* INVALID_DEVID there is no async loop to retry on MP_WOULDBLOCK, so
8604+
* skip non-blocking setup and use blocking mode instead. */
8605+
if (ret == 0 && ssl->devId != INVALID_DEVID) {
8606+
dhNb = (DhNb*)XMALLOC(sizeof(DhNb), ssl->heap,
8607+
DYNAMIC_TYPE_TMP_BUFFER);
8608+
if (dhNb == NULL) {
8609+
ret = MEMORY_E;
8610+
}
8611+
else {
8612+
ret = wc_DhSetNonBlock((DhKey*)*pKey, dhNb);
8613+
if (ret != 0) {
8614+
XFREE(dhNb, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
8615+
}
8616+
}
8617+
}
8618+
#endif /* WC_DH_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW &&
8619+
WC_ASYNC_ENABLE_DH */
85548620
break;
85558621
#endif /* !NO_DH */
85568622
default:

src/tls.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8010,6 +8010,26 @@ static int TLSX_KeyShare_GenDhKey(WOLFSSL *ssl, KeyShareEntry* kse)
80108010
ret = wc_DhSetNamedKey(dhKey, kse->group);
80118011
#endif
80128012
}
8013+
#if defined(WC_DH_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
8014+
defined(WC_ASYNC_ENABLE_DH)
8015+
/* Only set non-blocking context when async device is active. With
8016+
* INVALID_DEVID there is no async loop to retry on MP_WOULDBLOCK, so
8017+
* skip non-blocking setup and use blocking mode instead. */
8018+
if (ret == 0 && ssl->devId != INVALID_DEVID) {
8019+
DhNb* dhNb = (DhNb*)XMALLOC(sizeof(DhNb), ssl->heap,
8020+
DYNAMIC_TYPE_TMP_BUFFER);
8021+
if (dhNb == NULL) {
8022+
ret = MEMORY_E;
8023+
}
8024+
else {
8025+
ret = wc_DhSetNonBlock((DhKey*)kse->key, dhNb);
8026+
if (ret != 0) {
8027+
XFREE(dhNb, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
8028+
}
8029+
}
8030+
}
8031+
#endif /* WC_DH_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW &&
8032+
WC_ASYNC_ENABLE_DH */
80138033
}
80148034

80158035
/* Allocate space for the private and public key */
@@ -9297,6 +9317,26 @@ static int TLSX_KeyShare_ProcessDh(WOLFSSL* ssl, KeyShareEntry* keyShareEntry)
92979317
ret = wc_DhSetNamedKey(dhKey, keyShareEntry->group);
92989318
#endif
92999319
}
9320+
#if defined(WC_DH_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
9321+
defined(WC_ASYNC_ENABLE_DH)
9322+
/* Only set non-blocking context when async device is active. With
9323+
* INVALID_DEVID there is no async loop to retry on MP_WOULDBLOCK, so
9324+
* skip non-blocking setup and use blocking mode instead. */
9325+
if (ret == 0 && ssl->devId != INVALID_DEVID) {
9326+
DhNb* dhNb = (DhNb*)XMALLOC(sizeof(DhNb), ssl->heap,
9327+
DYNAMIC_TYPE_TMP_BUFFER);
9328+
if (dhNb == NULL) {
9329+
ret = MEMORY_E;
9330+
}
9331+
else {
9332+
ret = wc_DhSetNonBlock((DhKey*)keyShareEntry->key, dhNb);
9333+
if (ret != 0) {
9334+
XFREE(dhNb, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
9335+
}
9336+
}
9337+
}
9338+
#endif /* WC_DH_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW &&
9339+
WC_ASYNC_ENABLE_DH */
93009340
}
93019341

93029342
if (ret == 0

wolfcrypt/src/dh.c

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,10 @@ int wc_InitDhKey_ex(DhKey* key, void* heap, int devId)
972972
key->handle = NULL;
973973
#endif
974974

975+
#ifdef WC_DH_NONBLOCK
976+
key->nb = NULL;
977+
#endif
978+
975979
return ret;
976980
}
977981

@@ -980,6 +984,23 @@ int wc_InitDhKey(DhKey* key)
980984
return wc_InitDhKey_ex(key, NULL, INVALID_DEVID);
981985
}
982986

987+
#ifdef WC_DH_NONBLOCK
988+
int wc_DhSetNonBlock(DhKey* key, DhNb* nb)
989+
{
990+
if (key == NULL)
991+
return BAD_FUNC_ARG;
992+
993+
if (nb != NULL) {
994+
XMEMSET(nb, 0, sizeof(DhNb));
995+
}
996+
997+
/* Pass NULL to disable non-blocking mode. */
998+
key->nb = nb;
999+
1000+
return 0;
1001+
}
1002+
#endif
1003+
9831004

9841005
int wc_FreeDhKey(DhKey* key)
9851006
{
@@ -2030,18 +2051,80 @@ static int wc_DhAgree_Sync(DhKey* key, byte* agree, word32* agreeSz,
20302051
if (mp_iseven(&key->p) == MP_YES) {
20312052
return MP_VAL;
20322053
}
2054+
2055+
/* Non-blocking re-entry: the same wc_DhAgree call repeats until the
2056+
* SP state machine completes, so cache the per-op key validation
2057+
* results instead of re-running them each yield. The cache is
2058+
* scoped to non-blocking, non-const-time callers only. */
2059+
#ifdef WC_DH_NONBLOCK
2060+
if (key->nb == NULL || ct || !key->nb->pubKeyValidated)
2061+
#endif
2062+
{
20332063
#ifdef WOLFSSL_VALIDATE_FFC_IMPORT
2034-
if (wc_DhCheckPrivKey(key, priv, privSz) != 0) {
2035-
WOLFSSL_MSG("wc_DhAgree wc_DhCheckPrivKey failed");
2036-
return DH_CHECK_PRIV_E;
2037-
}
2064+
if (wc_DhCheckPrivKey(key, priv, privSz) != 0) {
2065+
WOLFSSL_MSG("wc_DhAgree wc_DhCheckPrivKey failed");
2066+
return DH_CHECK_PRIV_E;
2067+
}
2068+
#endif
2069+
/* Always validate peer public key (2 <= y <= p-2) per SP 800-56A */
2070+
if (wc_DhCheckPubKey(key, otherPub, pubSz) != 0) {
2071+
WOLFSSL_MSG("wc_DhAgree wc_DhCheckPubKey failed");
2072+
return DH_CHECK_PUB_E;
2073+
}
2074+
#ifdef WC_DH_NONBLOCK
2075+
if (key->nb != NULL && !ct) {
2076+
key->nb->pubKeyValidated = 1;
2077+
}
20382078
#endif
2079+
}
20392080

2040-
/* Always validate peer public key (2 <= y <= p-2) per SP 800-56A */
2041-
if (wc_DhCheckPubKey(key, otherPub, pubSz) != 0) {
2042-
WOLFSSL_MSG("wc_DhAgree wc_DhCheckPubKey failed");
2043-
return DH_CHECK_PUB_E;
2081+
#if defined(WC_DH_NONBLOCK) && defined(WOLFSSL_HAVE_SP_DH) && \
2082+
defined(WOLFSSL_SP_NONBLOCK) && defined(WOLFSSL_SP_SMALL) && \
2083+
!defined(WOLFSSL_SP_FAST_MODEXP)
2084+
/* Non-blocking dispatch bypasses the mp_int dance entirely - the SP
2085+
* wrapper takes byte buffers and persists across yields. The constant-
2086+
* time fold-back (ct branch) is intentionally not applied here; nb
2087+
* callers should use the standard wc_DhAgree(). */
2088+
if (key->nb != NULL && !ct) {
2089+
int nb_ret = MP_OKAY;
2090+
int dispatched = 0;
2091+
#ifndef WOLFSSL_SP_NO_2048
2092+
if (mp_count_bits(&key->p) == 2048) {
2093+
nb_ret = sp_DhExp_2048_nb(&key->nb->sp_ctx, otherPub, pubSz,
2094+
priv, privSz, &key->p, agree, agreeSz);
2095+
dispatched = 1;
2096+
}
2097+
#endif
2098+
#ifndef WOLFSSL_SP_NO_3072
2099+
if (!dispatched && mp_count_bits(&key->p) == 3072) {
2100+
nb_ret = sp_DhExp_3072_nb(&key->nb->sp_ctx, otherPub, pubSz,
2101+
priv, privSz, &key->p, agree, agreeSz);
2102+
dispatched = 1;
2103+
}
2104+
#endif
2105+
#ifdef WOLFSSL_SP_4096
2106+
if (!dispatched && mp_count_bits(&key->p) == 4096) {
2107+
nb_ret = sp_DhExp_4096_nb(&key->nb->sp_ctx, otherPub, pubSz,
2108+
priv, privSz, &key->p, agree, agreeSz);
2109+
dispatched = 1;
2110+
}
2111+
#endif
2112+
if (dispatched) {
2113+
/* Op finished (or hit a hard error) - clear the cached
2114+
* validation so the next op on this DhNb re-runs the
2115+
* SP 800-56A peer-key check. MP_WOULDBLOCK keeps it. */
2116+
if (nb_ret != MP_WOULDBLOCK) {
2117+
key->nb->pubKeyValidated = 0;
2118+
}
2119+
return nb_ret;
2120+
}
2121+
/* size not nb-supported - the blocking path below completes in
2122+
* one call, so the cached validation is single-use. Clear it
2123+
* here so the next agree on this DhNb re-validates. */
2124+
key->nb->pubKeyValidated = 0;
2125+
/* fall through to blocking path */
20442126
}
2127+
#endif
20452128

20462129
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
20472130
y = (mp_int*)XMALLOC(sizeof(mp_int), key->heap, DYNAMIC_TYPE_DH);
@@ -2304,13 +2387,21 @@ int wc_DhAgree(DhKey* key, byte* agree, word32* agreeSz, const byte* priv,
23042387
ret = KcapiDh_SharedSecret(key, otherPub, pubSz, agree, agreeSz);
23052388
#else
23062389
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_DH)
2390+
/* Async marker takes precedence: when wolfAsync_DoSw (wolfcrypt/src/
2391+
* async.c) re-enters the compute path, wc_DhAgree_Async dispatches
2392+
* to the SP nonblock wrapper if key->nb is attached, and per-yield
2393+
* FP_WOULDBLOCK (alias of MP_WOULDBLOCK) is translated to
2394+
* WC_PENDING_E by wolfAsync_DoSw so the TLS event loop drives it. */
23072395
if (key->asyncDev.marker == WOLFSSL_ASYNC_MARKER_DH) {
23082396
ret = wc_DhAgree_Async(key, agree, agreeSz, priv, privSz, otherPub,
23092397
pubSz);
23102398
}
23112399
else
23122400
#endif
23132401
{
2402+
/* wc_DhAgree_Sync handles key->nb internally; no separate dispatch
2403+
* needed here. wc_DhAgree_ct (constant-time fold-back) bypasses
2404+
* this function entirely so passing ct=0 is correct. */
23142405
ret = wc_DhAgree_Sync(key, agree, agreeSz, priv, privSz, otherPub,
23152406
pubSz, 0);
23162407
}

0 commit comments

Comments
 (0)