Skip to content
Open
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
22 changes: 22 additions & 0 deletions .github/configs/os-check-linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,28 @@
"comment": "Non-blocking ECC on the portable C implementation without the small math; RSA/DH non-block are left off because their mod_exp only exists in the small variant.",
"configure": ["--enable-ecc=nonblock", "--enable-sp=yes,nonblocklarge", "--disable-sp-asm",
"CPPFLAGS=-DWOLFSSL_PUBLIC_MP -DWOLFSSL_DEBUG_NONBLOCK"]},
{"name": "modexp-copy-default-sakke", "minutes": 2.6,
"comment": "Default (address-oblivious) cache-resistant modexp copy on a normal target: _sp_exptmod_mont_ex (sp_int.c) via _sp_cond_copy and the SAKKE ladder (sakke.c) via mp_cond_copy, which touch both t[0] and t[1] so the accessed address is secret-independent. Nothing else in this matrix builds SAKKE, and on a non-capability target the sp_int.c default is only otherwise reached incidentally. Pairs with modexp-copy-fast-sakke, which flips both to the pointer-copy opt-in.",
"configure": ["--enable-sakke", "--enable-sp-math-all"]},
{"name": "modexp-copy-fast-sakke", "minutes": 2.6,
"comment": "WOLFSSL_SP_FAST_MODEXP_COPY opt-in: the faster secret-dependent-pointer copy in _sp_exptmod_mont_ex (sp_int.c) and the SAKKE ladder (sakke.c). That branch is the #else of the oblivious default and is compiled by no other entry, and the macro is forced off on the capability (WC_NO_PTR_INT_CAST) legs in fil-c.yml, so a normal target is the only place it is built.",
"configure": ["--enable-sakke", "--enable-sp-math-all",
"CPPFLAGS=-DWOLFSSL_SP_FAST_MODEXP_COPY"]},
{"name": "sp-ladder-modexp", "minutes": 2.0,
"comment": "Specialized SP in portable C (sp_c64.c on x86_64) with WOLFSSL_SP_LADDER_MODEXP, which replaces the windowed exptmod with the cache-resistant ladder. Default (oblivious) copy via sp_cond_select/sp_cond_memcpy. --disable-sp-asm keeps RSA/DH/ECC on the generated C files; nothing else here builds the non-small ladder on a normal target.",
"configure": ["--enable-sp", "--disable-sp-asm",
"CPPFLAGS=-DWOLFSSL_SP_LADDER_MODEXP"]},
{"name": "sp-ladder-modexp-fast-copy", "minutes": 2.0,
"comment": "Same sp_c64.c ladder as sp-ladder-modexp but with WOLFSSL_SP_FAST_MODEXP_COPY opting the copy onto addr_mask pointer selection (the #else branch) instead of sp_cond_select/sp_cond_memcpy.",
"configure": ["--enable-sp", "--disable-sp-asm",
"CPPFLAGS=-DWOLFSSL_SP_LADDER_MODEXP -DWOLFSSL_SP_FAST_MODEXP_COPY"]},
{"name": "fastmath-modexp-copy-default", "minutes": 2.4,
"comment": "TomsFastMath (tfm.c) on a normal target: fp_exptmod_ct selects the working value with the address-oblivious fp_cond_copy default. os-check otherwise builds no fastmath leg (only fil-c.yml does, where WC_NO_PTR_INT_CAST already forces this branch); this covers it under plain gcc. Pairs with fastmath-modexp-copy-fast.",
"configure": ["--enable-fastmath"]},
{"name": "fastmath-modexp-copy-fast", "minutes": 2.4,
"comment": "tfm.c fp_exptmod_ct with WOLFSSL_SP_FAST_MODEXP_COPY opting onto the wc_off_on_addr pointer copy (the #else branch). Forced off on the fil-c.yml capability legs, so a normal target is the only place this branch is built.",
"configure": ["--enable-fastmath",
"CPPFLAGS=-DWOLFSSL_SP_FAST_MODEXP_COPY"]},
{"name": "all-asn-template", "minutes": 3.5,
"configure": ["--enable-all", "--enable-asn=template"]},
{"name": "chain-verify-cb-async", "minutes": 3.5,
Expand Down
21 changes: 21 additions & 0 deletions doc/ASM_AND_MATH_DEFINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ so choose according to what your target has:
| `WC_PROTECT_ENCRYPTED_MEM` | Extra protection for operations on encrypted memory. |
| `WC_NO_CACHE_RESISTANT` | Disable cache-resistant (constant-address) table access. Faster, weaker. |
| `WC_NO_HARDEN` | Disable timing-attack resistance. Faster, weaker. |
| `WOLFSSL_SP_FAST_MODEXP_COPY` | Select the cache-resistant `mod_exp` working value with a single secret-dependent pointer copy instead of touching both candidates. Faster, but leaks the exponent at cache-line granularity. Off by default; forced off on capability targets. |
| `WOLFSSL_NO_CT_OPS` | Disable constant-time operations. |
| `WOLFSSL_CHECK_MEM_ZERO` | Check that sensitive memory is zeroed on free (debug). |

Expand All @@ -289,6 +290,26 @@ wolfSSL recommends against defining `WC_NO_HARDEN`, `WC_NO_CACHE_RESISTANT` or
your threat model genuinely excludes an attacker able to observe local timing or
cache behaviour. The performance gain is small relative to the protection lost.

The cache-resistant modular exponentiation (and the ECC/SAKKE scalar-mult
ladders) select the working value from one of two objects on each secret
exponent bit. By default that selection is **address-oblivious**: both objects
are accessed every iteration through a masked constant-time copy
(`_sp_cond_copy` in `sp_int.c`, `sp_cond_select`/`sp_cond_memcpy` in the
specialised `sp_<arch>.c`, `fp_cond_copy` in `tfm.c`, `mp_cond_copy` in
`sakke.c`), so the touched memory address does not depend on the secret. This
resists a co-resident attacker that can observe cache-line accesses, for example
`Prime+Probe` from a sibling SMT thread sharing the L1 data cache. Defining
`WOLFSSL_SP_FAST_MODEXP_COPY` switches to the older, faster variant that computes
a single secret-dependent pointer by address masking and copies only the
selected object; it is a few percent faster on private-key operations but
discloses the exponent bits at cache-line granularity, so enable it only where a
co-resident cache observer is outside your threat model. It requires rebuilding a
pointer from integer arithmetic on two addresses, which capability targets (those
that define `WC_NO_PTR_INT_CAST`, such as CHERI and Fil-C) cannot do, so the
option is ignored and the oblivious copy is always used there. It is also a no-op
for the default windowed `mod_exp` used by the specialised SP assembly back ends,
which select from a table scanned in full rather than from two objects.

### Non-blocking

`WOLFSSL_SP_NONBLOCK` makes long operations return `FP_WOULDBLOCK` and expect
Expand Down
41 changes: 35 additions & 6 deletions wolfcrypt/src/sakke.c
Original file line number Diff line number Diff line change
Expand Up @@ -2617,6 +2617,24 @@ static int sakke_modexp_loop(SakkeKey* key, mp_int* b, mp_int* e, mp_proj* c,

#elif defined(WOLFSSL_SAKKE_SMALL_MODEXP) || !defined(USE_FAST_MATH)

/* The cache-resistant SAKKE modexp ladder selects the working point from c[0]
* or c[1] on each exponent bit. By default this is done with mp_cond_copy(),
* which accesses both objects every iteration so the touched address does not
* depend on the secret bit (resists co-resident L1D cache attacks such as
* Prime+Probe between SMT siblings).
*
* Defining WOLFSSL_SP_FAST_MODEXP_COPY (the same opt-in used by the SP math
* backends) selects the faster variant that instead computes a single
* secret-dependent pointer with wc_off_on_addr masking and copies only the
* selected point. It is faster but leaks the exponent at cache-line
* granularity, so only enable it where a co-resident cache observer is outside
* the threat model. The fast variant rebuilds a pointer from integer arithmetic
* on two addresses, which capability targets cannot do, so it is forced off
* when WC_NO_PTR_INT_CAST is defined. */
#if defined(WOLFSSL_SP_FAST_MODEXP_COPY) && !defined(WC_NO_PTR_INT_CAST)
#define WC_SAKKE_MODEXP_PTR_COPY
#endif

/*
* Modular exponentiate the value in F_p*.
*
Expand All @@ -2643,7 +2661,10 @@ static int sakke_modexp_loop(SakkeKey* key, mp_int* b, mp_int* e, mp_proj* r,
mp_int* by = key->tmp.p1->z;
mp_int* prime = &key->params.prime;
int i;
#ifdef WC_NO_GLOBAL_OBJECT_POINTERS
/* Local address mask only when the pointer-copy opt-in is active and global
* object pointers are unavailable; the default oblivious copy never uses
* it, so declaring it unconditionally is an unused variable. */
#if defined(WC_NO_GLOBAL_OBJECT_POINTERS) && defined(WC_SAKKE_MODEXP_PTR_COPY)
static const wc_ptr_t wc_off_on_addr[2] =
{
#if defined(WC_64BIT_CPU)
Expand Down Expand Up @@ -2686,11 +2707,19 @@ static int sakke_modexp_loop(SakkeKey* key, mp_int* b, mp_int* e, mp_proj* r,
err = sakke_proj_mul_qx1(c[0], by, prime, mp, c[j^1], t1, t2);
#else
err = sakke_proj_mul_qx1(c[0], by, prime, mp, c[2], t1, t2);
#ifdef WC_NO_PTR_INT_CAST
err = mp_cond_copy(c[2]->x, j, c[0]->x);
err = mp_cond_copy(c[2]->x, j^1, c[1]->x);
err = mp_cond_copy(c[2]->y, j, c[0]->y);
err = mp_cond_copy(c[2]->y, j^1, c[1]->y);
#ifndef WC_SAKKE_MODEXP_PTR_COPY
Comment thread
SparkiDev marked this conversation as resolved.
Resolved
/* Guard each copy on err so a failure in the multiply above or in
* an earlier copy is preserved rather than overwritten. err does
* not depend on the secret bit, so all four copies still run (and
* touch both c[0] and c[1]) on the success path. */
if (err == 0)
err = mp_cond_copy(c[2]->x, j, c[0]->x);
if (err == 0)
err = mp_cond_copy(c[2]->x, j^1, c[1]->x);
if (err == 0)
err = mp_cond_copy(c[2]->y, j, c[0]->y);
if (err == 0)
err = mp_cond_copy(c[2]->y, j^1, c[1]->y);
#else
mp_copy(c[2]->x,
(mp_int*) ( ((wc_ptr_t)c[0]->x & wc_off_on_addr[j]) +
Expand Down
Loading
Loading