diff --git a/tests/unit-mcdc/test_eccsi_whitebox.c b/tests/unit-mcdc/test_eccsi_whitebox.c index b070a8321f..a449c2eb11 100644 --- a/tests/unit-mcdc/test_eccsi_whitebox.c +++ b/tests/unit-mcdc/test_eccsi_whitebox.c @@ -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 + +#include +#include +#include +#include + +#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 #include @@ -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;