1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00

tests: stage the eccsi rejection-sampling retry rows

This commit is contained in:
Daniele Lacamera
2026-08-12 10:01:21 +02:00
parent 7bbd6e2f39
commit 7cbcc4ecfd

View File

@@ -50,26 +50,139 @@
* so the map=0 side of the decision is never reached through the API.
* Calling the helpers directly with map=0 and map=1 drives both halves.
*
* Not driven (justified residuals, documented rather than forced):
* eccsi_make_pair() (line ~916):
* while ((err == 0) && (mp_iszero(ssk) ||
* (mp_cmp(ssk, wc_ecc_key_get_priv(&key->ecc)) == MP_EQ)));
* wc_SignEccsiHash()'s retry loop (line ~1926):
* while ((err == 0) && (mp_iszero(s) || (mp_cmp(s, he) == MP_EQ)));
* Both loops only take their extra iteration when a freshly generated
* random scalar happens to be exactly zero or collide with another
* value -- a cryptographically negligible event (probability ~2^-256 on
* P-256) that cannot be forced through the public RNG-driven API without
* corrupting the RNG or hand-crafting an internal scalar, which would
* defeat the point of testing the real retry logic. Left as defensive
* residuals.
* The two rejection-sampling retry loops (924 and 1934), all three
* conditions each:
* eccsi_make_pair() (924)
* while ((err == 0) && (mp_iszero(ssk) ||
* (mp_cmp(ssk, wc_ecc_key_get_priv(&key->ecc)) == MP_EQ)));
* eccsi_gen_sig() (1934)
* while ((err == 0) && (mp_iszero(s) || (mp_cmp(s, he) == MP_EQ)));
* Both loops re-sample when the derived scalar comes out zero or collides
* with the value it is compared against. Neither scalar is a raw random
* draw: ssk = (KSAK + HS*v) mod q and s = (HE + r*SSK) mod q, and HS/HE
* are hashes of the freshly generated ephemeral point, so no choice of RNG
* stream (seeded or not) can steer either to a chosen value -- that would
* be a hash preimage. What CAN be steered is the last arithmetic step of
* each loop body: both scalars are produced by the loop's final
* mp_addmod(), which this file interposes (wb_am_addmod, installed before
* eccsi.c so eccsi.c's call sites bind to it). The interposer is a
* ONE-SHOT keyed on the destination mp_int pointer, so it tampers with
* exactly one iteration and the retry then runs the genuine arithmetic --
* the loop terminates on real data, not on a second injected value.
*
* Four vectors per loop, all in this one binary, which is what llvm-cov
* needs to derive the independence pairs:
* (d) OFF -> scalar non-zero and != comparand : (T,F,F), decision FALSE
* (b) ZERO -> mp_iszero() TRUE on pass 1 : (T,T), decision TRUE
* (c) COPY -> mp_cmp() == MP_EQ on pass 1 : (T,F,T), decision TRUE
* (the copied value is the addmod operand the loop then
* compares against: operand b at 920, operand a at 1931)
* (a) FAIL -> mp_addmod returns MP_VAL : (F), decision FALSE
* (a)+(b) is the independence pair for cond 0 (err == 0); (b)+(d) for
* cond 1 (mp_iszero); (c)+(d) for cond 2 (mp_cmp == MP_EQ). A rejection
* vector on its own would prove nothing, so all four always run.
*
* 924 is driven through the public wc_MakeEccsiPair() (ssk is the caller's
* mp_int, so the pointer key is unambiguous). 1934 is driven by calling the
* file-static eccsi_gen_sig() directly with caller-owned r/s, after
* replicating exactly the state wc_SignEccsiHash() sets up before it
* (pair set, id hash set, order loaded); wc_SignEccsiHash() itself aliases
* r/s onto key->pubkey.pubkey.y/z, and owning them here makes the pointer
* key exact.
*
* Crash-safety: every call here operates on a single EccsiKey that has been
* fully initialized (wc_InitEccsiKey) and had its curve parameters and base
* point loaded before any helper that dereferences them is called; scratch
* ecc_points and mp_ints are heap/stack allocated and freed at the end.
* ecc_points and mp_ints are heap/stack allocated and freed at the end. The
* FAIL vectors make the loop's last mp_addmod return MP_VAL, which the loop
* body's own "if (err == 0)" chain absorbs; the tampered/failed ssk, r and s
* are never read afterwards, and the destructive 924 vectors run last, with a
* clean pair regenerated before the signing fixture is built.
*/
/* --------------------------------------------------------------------------
* Value-forcing mp_addmod interposer.
*
* Same macro-interposition ordering as mcdc_fault_mp.h: the wrapper is
* compiled while mp_addmod still means the real (sp_addmod / integer.c / tfm.c)
* entry point, and only then is the name redefined, so the wrapper reaches the
* genuine operation. eccsi.c must be #included AFTER this block.
*
* Unlike the fault injectors this does not only fail a call, it can also
* substitute the RESULT -- which is what the two retry loops need, since their
* loop condition is a predicate on the value, not on an error code. Both
* eccsi.c call sites (920 in eccsi_make_pair, 1931 in eccsi_gen_sig) are
* targets; the destination-pointer key selects which one is affected.
* ----------------------------------------------------------------------- */
#include <wolfssl/wolfcrypt/libwolfssl_sources.h>
#include <wolfssl/wolfcrypt/types.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssl/wolfcrypt/wolfmath.h>
#if defined(__GNUC__) || defined(__clang__)
#define WB_MAYBE_UNUSED __attribute__((unused))
#else
#define WB_MAYBE_UNUSED
#endif
#ifdef WOLFCRYPT_HAVE_ECCSI
#define WB_AM_OFF 0 /* pass through */
#define WB_AM_ZERO 1 /* succeed, then force the result to zero */
#define WB_AM_COPY_A 2 /* succeed, then force the result to operand a */
#define WB_AM_COPY_B 3 /* succeed, then force the result to operand b */
#define WB_AM_FAIL 4 /* return MP_VAL without touching the result */
/* The COPY modes take the operand the call was given rather than a pointer
* cached by the driver: wc_ecc_key_get_priv() is a function under
* WOLFSSL_ECC_BLIND_K, so a pointer captured before the call is not
* guaranteed to still name the private key during it. Only the operand that
* does NOT alias the destination is ever copied (eccsi.c's 920 site writes
* over operand a, its 1931 site over operand b), so the copy is always of a
* live value. */
static int wb_am_mode = WB_AM_OFF;
static mp_int* wb_am_dst = NULL;
/* const-in / non-const-out signature: sp_int.h declares the operands const
* while integer.h and tfm.h do not, and this is the one shape that binds under
* every math backend without a qualifier warning at eccsi.c's call sites. */
WB_MAYBE_UNUSED static int wb_am_addmod(const mp_int* a, const mp_int* b,
const mp_int* m, mp_int* r)
{
int mode = WB_AM_OFF;
int ret;
if ((wb_am_mode != WB_AM_OFF) && (r == wb_am_dst)) {
mode = wb_am_mode;
/* One-shot: the loop's retry iteration runs untampered, so the loop
* terminates on genuine arithmetic rather than a second injection. */
wb_am_mode = WB_AM_OFF;
}
if (mode == WB_AM_FAIL) {
return MP_VAL;
}
ret = mp_addmod((mp_int*)a, (mp_int*)b, (mp_int*)m, r);
if (ret == 0) {
if (mode == WB_AM_ZERO) {
mp_zero(r);
}
else if (mode == WB_AM_COPY_A) {
ret = mp_copy((mp_int*)a, r);
}
else if (mode == WB_AM_COPY_B) {
ret = mp_copy((mp_int*)b, r);
}
}
return ret;
}
#undef mp_addmod
#define mp_addmod(a, b, m, r) wb_am_addmod((a), (b), (m), (r))
#endif /* WOLFCRYPT_HAVE_ECCSI */
#include <wolfcrypt/src/eccsi.c>
#include <stdio.h>
@@ -81,8 +194,196 @@
static int wb_fail = 0;
#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0)
/* --------------------------------------------------------------------------
* 924 / 1934: the two rejection-sampling retry loops.
*
* Vector map (see the file header for the independence-pair argument):
* eccsi_make_pair() 924, ssk = (KSAK + HS*v) mod q
* OFF -> (T,F,F) ZERO -> (T,T) COPY_B(KSAK) -> (T,F,T) FAIL -> (F)
* eccsi_gen_sig() 1934, s = (HE + r*SSK) mod q
* OFF -> (T,F,F) ZERO -> (T,T) COPY_A(he) -> (T,F,T) FAIL -> (F)
* ----------------------------------------------------------------------- */
#if defined(WOLFCRYPT_HAVE_ECCSI) && defined(WOLFCRYPT_ECCSI_KMS) && \
defined(WOLFCRYPT_ECCSI_CLIENT) && !defined(NO_SHA256)
static void wb_eccsi_retry_loops(void)
{
WC_RNG rng;
EccsiKey key;
ecc_point* pvt = NULL;
mp_int ssk;
mp_int r;
mp_int s;
byte id[] = "eccsi-retry@wolfssl.com";
byte msg[32];
byte hash[WC_MAX_DIGEST_SIZE];
int ready = 0;
int ret;
XMEMSET(&rng, 0, sizeof(rng));
XMEMSET(&key, 0, sizeof(key));
XMEMSET(&ssk, 0, sizeof(ssk));
XMEMSET(&r, 0, sizeof(r));
XMEMSET(&s, 0, sizeof(s));
XMEMSET(msg, 0x3c, sizeof(msg));
XMEMSET(hash, 0, sizeof(hash));
if (wc_InitRng(&rng) != 0) {
WB_NOTE("wc_InitRng failed; retry-loop vectors skipped");
wb_fail = 1;
return;
}
if (wc_InitEccsiKey(&key, NULL, INVALID_DEVID) != 0) {
WB_NOTE("wc_InitEccsiKey failed; retry-loop vectors skipped");
wb_fail = 1;
wc_FreeRng(&rng);
return;
}
pvt = wc_ecc_new_point_h(NULL);
if (pvt != NULL) {
(void)mp_init(&ssk);
(void)mp_init(&r);
(void)mp_init(&s);
if (wc_MakeEccsiKey(&key, &rng) == 0) {
ready = 1;
}
}
if (!ready) {
WB_NOTE("eccsi retry-loop fixture setup failed; vectors skipped");
wb_fail = 1;
goto done;
}
/* ---- 924: eccsi_make_pair() via the public wc_MakeEccsiPair(). ------ */
/* (d) all-false single-pass exit. */
wb_am_mode = WB_AM_OFF;
ret = wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id,
(word32)sizeof(id), &ssk, pvt);
if (ret != 0) {
WB_NOTE("wc_MakeEccsiPair baseline (924 all-false) failed");
wb_fail = 1;
goto done;
}
/* (b) mp_iszero(ssk) TRUE on pass 1, retry, then normal exit. */
wb_am_dst = &ssk;
wb_am_mode = WB_AM_ZERO;
ret = wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id,
(word32)sizeof(id), &ssk, pvt);
wb_am_mode = WB_AM_OFF;
if (ret != 0) {
WB_NOTE("wc_MakeEccsiPair zero-ssk retry vector failed");
wb_fail = 1;
}
/* (c) mp_cmp(ssk, KSAK) == MP_EQ on pass 1, retry, then normal exit.
* Operand b of the 920 mp_addmod IS wc_ecc_key_get_priv(&key->ecc). */
wb_am_dst = &ssk;
wb_am_mode = WB_AM_COPY_B;
ret = wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id,
(word32)sizeof(id), &ssk, pvt);
wb_am_mode = WB_AM_OFF;
if (ret != 0) {
WB_NOTE("wc_MakeEccsiPair ssk==KSAK retry vector failed");
wb_fail = 1;
}
/* (a) err != 0 on loop exit. ssk is not read after this. */
wb_am_dst = &ssk;
wb_am_mode = WB_AM_FAIL;
(void)wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id,
(word32)sizeof(id), &ssk, pvt);
wb_am_mode = WB_AM_OFF;
/* Regenerate a valid pair: the FAIL vector left ssk holding a partial
* product, and the signing fixture below needs a genuine (ssk, pvt). */
ret = wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id,
(word32)sizeof(id), &ssk, pvt);
if (ret != 0) {
WB_NOTE("wc_MakeEccsiPair restore failed; 1934 vectors skipped");
wb_fail = 1;
goto done;
}
WB_NOTE("eccsi_make_pair 924 retry-loop pairs exercised");
/* ---- 1934: eccsi_gen_sig() called directly with caller-owned r/s. ---
* Same state wc_SignEccsiHash() establishes before it: pair set, identity
* hash set, curve order loaded. */
if ((wc_SetEccsiPair(&key, &ssk, pvt) != 0) ||
(wc_HashEccsiId(&key, WC_HASH_TYPE_SHA256, id,
(word32)sizeof(id), pvt, hash, NULL) != 0) ||
(wc_SetEccsiHash(&key, hash, WC_SHA256_DIGEST_SIZE) != 0) ||
(eccsi_load_order(&key) != 0)) {
WB_NOTE("eccsi signing fixture setup failed; 1934 vectors skipped");
wb_fail = 1;
goto done;
}
/* (d) all-false single-pass exit. */
wb_am_mode = WB_AM_OFF;
ret = eccsi_gen_sig(&key, &rng, WC_HASH_TYPE_SHA256, msg,
(word32)sizeof(msg), &r, &s);
if (ret != 0) {
WB_NOTE("eccsi_gen_sig baseline (1934 all-false) failed");
wb_fail = 1;
}
/* (b) mp_iszero(s) TRUE on pass 1, retry, then normal exit. */
wb_am_dst = &s;
wb_am_mode = WB_AM_ZERO;
ret = eccsi_gen_sig(&key, &rng, WC_HASH_TYPE_SHA256, msg,
(word32)sizeof(msg), &r, &s);
wb_am_mode = WB_AM_OFF;
if (ret != 0) {
WB_NOTE("eccsi_gen_sig zero-s retry vector failed");
wb_fail = 1;
}
/* (c) mp_cmp(s, he) == MP_EQ on pass 1, retry, then normal exit.
* Operand a of the 1931 mp_addmod IS he (== &key->tmp), which
* eccsi_gen_sig() reloads from the digest on every iteration. */
wb_am_dst = &s;
wb_am_mode = WB_AM_COPY_A;
ret = eccsi_gen_sig(&key, &rng, WC_HASH_TYPE_SHA256, msg,
(word32)sizeof(msg), &r, &s);
wb_am_mode = WB_AM_OFF;
if (ret != 0) {
WB_NOTE("eccsi_gen_sig s==he retry vector failed");
wb_fail = 1;
}
/* (a) err != 0 on loop exit. r/s are not read after this. */
wb_am_dst = &s;
wb_am_mode = WB_AM_FAIL;
(void)eccsi_gen_sig(&key, &rng, WC_HASH_TYPE_SHA256, msg,
(word32)sizeof(msg), &r, &s);
wb_am_mode = WB_AM_OFF;
WB_NOTE("eccsi_gen_sig 1934 retry-loop pairs exercised");
done:
wb_am_mode = WB_AM_OFF;
wb_am_dst = NULL;
mp_free(&s);
mp_free(&r);
mp_forcezero(&ssk);
mp_free(&ssk);
if (pvt != NULL) {
wc_ecc_del_point_h(pvt, NULL);
}
wc_FreeEccsiKey(&key);
wc_FreeRng(&rng);
}
#else
static void wb_eccsi_retry_loops(void)
{ WB_NOTE("ECCSI KMS/CLIENT or SHA-256 unavailable; 924/1934 retry-loop "
"vectors skipped"); }
#endif
int main(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
printf("eccsi.c white-box supplement\n");
#ifdef WOLFCRYPT_HAVE_ECCSI
EccsiKey key;
@@ -221,8 +522,12 @@ int main(void)
wc_FreeEccsiKey(&key);
}
/* 924 / 1934 retry loops, on their own key/RNG fixture. */
wb_eccsi_retry_loops();
printf("done (%s)\n", wb_fail ? "with skips" : "ok");
#else
wb_eccsi_retry_loops();
printf(" WOLFCRYPT_HAVE_ECCSI not defined; nothing to exercise\n");
#endif /* WOLFCRYPT_HAVE_ECCSI */
(void)wb_fail;