mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-10-24 04:09:07 +03:00
3885 lines
152 KiB
C
3885 lines
152 KiB
C
/* test_ecc_whitebox.c
|
|
*
|
|
* White-box MC/DC supplement for wolfcrypt/src/ecc.c.
|
|
*
|
|
* The tests/api ECC suite (test_ecc.c, including its MC/DC-focused
|
|
* test_wc_EccDecisionCoverage) drives ecc.c through its *public* API. A
|
|
* handful of decision conditions live in file-static helpers whose "bad"
|
|
* operand combinations are rejected by every public caller *before* the
|
|
* helper runs (the caller hard-codes a value, or pre-validates the same
|
|
* condition), so those combinations can never be exercised from the API
|
|
* without modifying library source. This translation unit reaches them by
|
|
* compiling ecc.c directly (#include) and calling the helpers with both
|
|
* halves of each MC/DC independence pair.
|
|
*
|
|
* Coverage from this binary is unioned with the tests/api variant coverage by
|
|
* source line:col in the per-module suite:
|
|
* llvm-cov computes MC/DC independence PER BINARY, and the
|
|
* aggregate.sh ORs the "independence shown" bit across binaries by key. That
|
|
* is why every pair below is completed *within this file* rather than
|
|
* relying on the API tests to supply the other half.
|
|
*
|
|
* Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS,
|
|
* -DHAVE_CONFIG_H and -I<workspace> as the instrumented library, then linked
|
|
* against that variant's libwolfssl.a with its ecc.o removed (this TU
|
|
* supplies the instrumented ecc.c). NOT part of the wolfSSL build; not
|
|
* registered in tests/api. See tests/unit-mcdc/README.md.
|
|
*
|
|
* Targeted residuals (ecc.c), by class:
|
|
* Class 1 wc_ecc_curve_load() dp/pCurve NULL guard ............. 2 conditions
|
|
* Class 2 _ecc_import_private_key_ex() key/priv NULL guard ...... 2 conditions
|
|
* Class 3 ecc_ctx_set_salt() ctx/flags==0 guard ................. 2 conditions
|
|
* Class 4 wc_ecc_ctx_get_own_salt() ctx->protocol==0 half ....... 1 condition
|
|
* Class 5 wc_ecc_ctx_set_peer_salt() ctx->protocol==0 half ...... 1 condition
|
|
* Class 6 wc_ecc_ctx_set_own_salt() ctx->protocol==0 half ....... 1 condition
|
|
* These are the only ecc.c gaps confirmed structurally unreachable through
|
|
* any public wrapper (every wrapper either hard-codes the "safe" side of the
|
|
* static helper's own re-check, or -- for the ecEncCtx cases -- there is no
|
|
* public constructor that leaves ctx->protocol == 0 on a live, non-NULL
|
|
* context). See RESIDUALS.md for everything else.
|
|
*/
|
|
|
|
/* ecc.c refuses the AES-GCM ECIES DEM in the default IV mode unless one of
|
|
* WOLFSSL_ECIES_OLD / _GEN_IV / _STATIC_GCM_NONCE is set, so without an opt-in
|
|
* every GCM call returns NOT_COMPILED_IN before reaching ecc_is_gcm's callers
|
|
* (15809 / 16157) and their operands stay unreachable in every variant. This
|
|
* TU compiles its OWN instrumented copy of ecc.c and links against the library
|
|
* with ecc.o removed, so the opt-in is local to the white-box binary: it does
|
|
* not change the library under test, and it touches nothing outside ecc.c
|
|
* (the macro appears in no header, so no shared type or layout moves). The
|
|
* suite unions MC/DC per source line, which is exactly how a white-box is
|
|
* meant to add rows the native variants cannot produce. */
|
|
#ifndef WOLFSSL_ECIES_STATIC_GCM_NONCE
|
|
#define WOLFSSL_ECIES_STATIC_GCM_NONCE
|
|
#endif
|
|
|
|
/* Pull ecc.c in verbatim so the file-static helpers below are in scope and
|
|
* instrumented in THIS binary. ecc.c includes settings.h (which picks up
|
|
* user_settings.h via -DWOLFSSL_USER_SETTINGS) and ecc.h itself. */
|
|
#include <wolfcrypt/src/ecc.c>
|
|
|
|
#include <stdio.h>
|
|
|
|
#ifndef INVALID_DEVID
|
|
#define INVALID_DEVID (-2)
|
|
#endif
|
|
|
|
static int wb_fail = 0;
|
|
#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0)
|
|
|
|
#if defined(HAVE_ECC) && !defined(WOLF_CRYPTO_CB_ONLY_ECC)
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 1: wc_ecc_curve_load() dp/pCurve NULL guard (line ~1772).
|
|
*
|
|
* if (dp == NULL || pCurve == NULL)
|
|
*
|
|
* Every public caller (wc_ecc_set_curve, wc_ecc_make_key_ex, ...) looks up a
|
|
* non-NULL ecc_set_type* from the static ecc_sets[] table and always passes
|
|
* the address of a live ecc_curve_spec* local, so neither operand's TRUE
|
|
* side is reachable from the API.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_curve_load(void)
|
|
{
|
|
/* Without ECC_CACHE_CURVE (our variants don't define it), wc_ecc_
|
|
* curve_load()'s *pCurve is NOT an out-only "give me a fresh one"
|
|
* slot -- the DECLARE_CURVE_SPECS() macro (used by every real caller)
|
|
* pre-allocates a real ecc_curve_spec on the caller's stack and passes
|
|
* its address; wc_ecc_curve_load() only fills it in. A bare NULL
|
|
* ecc_curve_spec* (valid ONLY under ECC_CACHE_CURVE, where the second
|
|
* branch of DECLARE_CURVE_SPECS lets the callee allocate/cache it) hits
|
|
* "curve = *pCurve; curve->dp != dp" on a NULL curve and crashes. Match
|
|
* the real DECLARE_CURVE_SPECS(1) shape here for the all-false call.
|
|
*/
|
|
ecc_curve_spec* curveNull = NULL;
|
|
int ret;
|
|
|
|
ret = wc_ecc_curve_load(NULL, &curveNull, ECC_CURVE_FIELD_ALL);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_curve_load(dp=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
ret = wc_ecc_curve_load(&ecc_sets[0], NULL, ECC_CURVE_FIELD_ALL);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_curve_load(pCurve=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
/* all-false: real dp + a properly pre-allocated pCurve slot (already
|
|
* exercised by every public caller via DECLARE_CURVE_SPECS(), repeated
|
|
* here so the independence PAIR -- not just each TRUE half -- lives in
|
|
* this binary too). */
|
|
{
|
|
/* ECC_CURVE_FIELD_ALL loads all six fields (prime/Af/Bf/order/Gx/
|
|
* Gy), matching DECLARE_CURVE_SPECS(ECC_CURVE_FIELD_COUNT) -- an
|
|
* undersized spec_ints (e.g. count 1) makes wc_ecc_curve_load()
|
|
* overrun it while loading the later fields. */
|
|
DECLARE_CURVE_SPECS(ECC_CURVE_FIELD_COUNT);
|
|
int allocErr = MP_OKAY;
|
|
ALLOC_CURVE_SPECS(ECC_CURVE_FIELD_COUNT, allocErr);
|
|
if (allocErr == MP_OKAY) {
|
|
ret = wc_ecc_curve_load(&ecc_sets[0], &curve, ECC_CURVE_FIELD_ALL);
|
|
if (ret != 0) {
|
|
WB_NOTE("wc_ecc_curve_load(all-false) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
wc_ecc_curve_free(curve);
|
|
}
|
|
else {
|
|
WB_NOTE("ALLOC_CURVE_SPECS failed (all-false case skipped)");
|
|
}
|
|
FREE_CURVE_SPECS();
|
|
}
|
|
WB_NOTE("wc_ecc_curve_load dp/pCurve NULL guard pairs exercised");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 2: _ecc_import_private_key_ex() key/priv NULL guard (line ~11671).
|
|
*
|
|
* if (key == NULL || priv == NULL)
|
|
*
|
|
* wc_ecc_import_private_key_ex() (the public wrapper) performs the identical
|
|
* check itself before ever reaching the static, so both operands' TRUE side
|
|
* inside the static are white-box only. The all-false ("both valid") side
|
|
* is exercised elsewhere by tests/api's test_wc_ecc_import_private_key --
|
|
* but that is a DIFFERENT binary, and llvm-cov computes MC/DC independence
|
|
* per binary, so this function also drives one real (all-false) call
|
|
* itself to complete both operands' pairs within this binary.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_import_private_key_ex(void)
|
|
{
|
|
byte priv[32];
|
|
ecc_key key;
|
|
int ret;
|
|
|
|
XMEMSET(priv, 0x5A, sizeof(priv));
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
|
|
ret = _ecc_import_private_key_ex(priv, sizeof(priv), NULL, 0, NULL,
|
|
ECC_CURVE_DEF);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("_ecc_import_private_key_ex(key=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
ret = _ecc_import_private_key_ex(NULL, 0, NULL, 0, &key, ECC_CURVE_DEF);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("_ecc_import_private_key_ex(priv=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
/* all-false: real key + real priv (pub left NULL -- private-only
|
|
* import), a small but nonzero/in-range scalar (k=1) so wc_ecc_
|
|
* set_curve()'s size check and the rest of the import succeed. */
|
|
if (wc_ecc_init(&key) == 0) {
|
|
byte k1[32];
|
|
XMEMSET(k1, 0, sizeof(k1));
|
|
k1[sizeof(k1) - 1] = 1;
|
|
ret = _ecc_import_private_key_ex(k1, sizeof(k1), NULL, 0, &key,
|
|
ECC_SECP256R1);
|
|
if (ret != 0) {
|
|
WB_NOTE("_ecc_import_private_key_ex(all-false) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
wc_ecc_free(&key);
|
|
}
|
|
|
|
WB_NOTE("_ecc_import_private_key_ex key/priv NULL guard pairs exercised");
|
|
}
|
|
|
|
#ifdef HAVE_ECC_ENCRYPT
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 3: ecc_ctx_set_salt() ctx/flags==0 guard (line ~14665).
|
|
*
|
|
* if (ctx == NULL || flags == 0)
|
|
*
|
|
* Both public callers (wc_ecc_ctx_set_own_salt via REQ_RESP_CLIENT/SERVER,
|
|
* ecc_ctx_init) always pass a live ctx and a hard-coded nonzero flags
|
|
* (REQ_RESP_CLIENT/REQ_RESP_SERVER), so flags==0 can never be observed from
|
|
* the API; ctx==NULL is likewise never forwarded by any caller (they all
|
|
* either early-return on their own NULL check or pass &localCtx).
|
|
*
|
|
* Classes 4-6: ecEncCtx.protocol == 0 halves of the get_own_salt /
|
|
* set_peer_salt / set_own_salt guards (lines ~14506, ~14554, ~14646). The
|
|
* only public constructor, wc_ecc_ctx_new()/wc_ecc_ctx_new_ex(), always sets
|
|
* ctx->protocol to REQ_RESP_CLIENT or REQ_RESP_SERVER (or fails and frees the
|
|
* ctx), so a live ctx with protocol==0 does not exist on any API path. Build
|
|
* one directly here since ecEncCtx's full definition is only visible inside
|
|
* this TU (it is an opaque forward-declared type in ecc.h).
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_ctx_set_salt(void)
|
|
{
|
|
ecEncCtx ctx;
|
|
WC_RNG rng;
|
|
int ret;
|
|
|
|
XMEMSET(&ctx, 0, sizeof(ctx));
|
|
if (wc_InitRng(&rng) != 0) {
|
|
WB_NOTE("wc_InitRng failed (ecc_ctx_set_salt skipped)");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
ctx.rng = &rng;
|
|
|
|
ret = ecc_ctx_set_salt(NULL, REQ_RESP_CLIENT);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("ecc_ctx_set_salt(ctx=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
ret = ecc_ctx_set_salt(&ctx, 0);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("ecc_ctx_set_salt(flags=0) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
/* all-false: real ctx + real flags value. */
|
|
ret = ecc_ctx_set_salt(&ctx, REQ_RESP_CLIENT);
|
|
if (ret != 0) {
|
|
WB_NOTE("ecc_ctx_set_salt(all-false) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
(void)wc_FreeRng(&rng);
|
|
WB_NOTE("ecc_ctx_set_salt ctx/flags==0 guard pairs exercised");
|
|
}
|
|
|
|
/* llvm-cov computes MC/DC independence PER BINARY: this whitebox TU is the
|
|
* ONLY place wc_ecc_ctx_set_own_salt() is ever called at all (the tests/api
|
|
* suite only exercises wc_ecc_ctx_set_peer_salt()), so every operand of
|
|
* both functions' 3-operand OR guard --
|
|
* if (ctx == NULL || ctx->protocol == 0 || salt == NULL)
|
|
* -- must get its full independence pair (toggle one operand, hold the
|
|
* other two fixed at their "continue" value) from calls made HERE; a TRUE
|
|
* shown in one binary and a FALSE shown in another do not combine. */
|
|
static void wb_ctx_protocol_zero(void)
|
|
{
|
|
ecEncCtx zeroCtx; /* protocol left at 0 by XMEMSET */
|
|
ecEncCtx liveCtx;
|
|
WC_RNG rng;
|
|
byte salt[EXCHANGE_SALT_SZ];
|
|
|
|
XMEMSET(&zeroCtx, 0, sizeof(zeroCtx));
|
|
XMEMSET(salt, 0x11, sizeof(salt));
|
|
|
|
/* wc_ecc_ctx_get_own_salt: protocol==0 TRUE half (line ~14506). The
|
|
* NULL/valid halves are already shown by the tests/api suite. */
|
|
if (wc_ecc_ctx_get_own_salt(&zeroCtx) != NULL) {
|
|
WB_NOTE("wc_ecc_ctx_get_own_salt(protocol=0) unexpected non-NULL");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
WB_NOTE("wc_InitRng failed (protocol guard pairs skipped)");
|
|
return;
|
|
}
|
|
ecc_ctx_init(&liveCtx, REQ_RESP_CLIENT, &rng);
|
|
|
|
/* wc_ecc_ctx_get_own_salt: protocol==0 FALSE half (live ctx, real
|
|
* protocol) -- completes the independence pair for that operand
|
|
* within this binary (the TRUE half was zeroCtx above). */
|
|
if (wc_ecc_ctx_get_own_salt(&liveCtx) == NULL) {
|
|
WB_NOTE("wc_ecc_ctx_get_own_salt(live) unexpected NULL");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
/* ---- wc_ecc_ctx_set_peer_salt (line ~14554): ctx==NULL and salt==NULL
|
|
* halves are already shown by tests/api's test_wc_ecc_ctx_set_peer_salt
|
|
* (same pattern, different binary -- doesn't count here); supply ALL
|
|
* THREE operands' pairs in this one binary too so this binary's own
|
|
* MC/DC is complete independent of what the API binary shows. */
|
|
if (wc_ecc_ctx_set_peer_salt(NULL, salt) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_ctx_set_peer_salt(ctx=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
if (wc_ecc_ctx_set_peer_salt(&zeroCtx, salt) !=
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_ctx_set_peer_salt(protocol=0) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
if (wc_ecc_ctx_set_peer_salt(&liveCtx, NULL) !=
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_ctx_set_peer_salt(salt=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
/* all-false companion: real ctx (protocol != 0), real salt. */
|
|
(void)wc_ecc_ctx_set_peer_salt(&liveCtx, salt);
|
|
|
|
/* ---- wc_ecc_ctx_set_own_salt (line ~14646): never called anywhere
|
|
* else, so needs its complete independence set here. */
|
|
if (wc_ecc_ctx_set_own_salt(NULL, salt, sizeof(salt)) !=
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_ctx_set_own_salt(ctx=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
if (wc_ecc_ctx_set_own_salt(&zeroCtx, salt, sizeof(salt)) !=
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_ctx_set_own_salt(protocol=0) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
if (wc_ecc_ctx_set_own_salt(&liveCtx, NULL, 0) !=
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_ctx_set_own_salt(salt=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
/* all-false companion: real ctx (protocol != 0), real salt. */
|
|
(void)wc_ecc_ctx_set_own_salt(&liveCtx, salt, sizeof(salt));
|
|
|
|
(void)wc_FreeRng(&rng);
|
|
WB_NOTE("ecEncCtx protocol==0 guard halves exercised");
|
|
}
|
|
#else
|
|
static void wb_ctx_set_salt(void) { WB_NOTE("HAVE_ECC_ENCRYPT off; skipped"); }
|
|
static void wb_ctx_protocol_zero(void)
|
|
{
|
|
WB_NOTE("HAVE_ECC_ENCRYPT off; skipped");
|
|
}
|
|
#endif /* HAVE_ECC_ENCRYPT */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 7: wc_ecc_is_valid_idx() "n < x" independence (line ~4335).
|
|
*
|
|
* if ((n >= ECC_CUSTOM_IDX) && (n < x)) { return 1; }
|
|
*
|
|
* Every public caller passes either a real ecc_sets[] index (n < x, always
|
|
* true) or a value already rejected by the earlier "n >= ECC_SET_COUNT"
|
|
* guard, so the "n < x" operand's FALSE half (n >= ECC_CUSTOM_IDX true, but
|
|
* n not less than the live table size x) is never observed. n == x (the
|
|
* table's own terminator slot) is the smallest value that is still
|
|
* ECC_CUSTOM_IDX and still under ECC_SET_COUNT.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_is_valid_idx_n_lt_x(void)
|
|
{
|
|
int x;
|
|
for (x = 0; ecc_sets[x].size != 0; x++) { }
|
|
if (wc_ecc_is_valid_idx(x) != 0) {
|
|
WB_NOTE("wc_ecc_is_valid_idx(terminator idx) unexpected valid");
|
|
wb_fail = 1;
|
|
}
|
|
WB_NOTE("wc_ecc_is_valid_idx n<x independence exercised");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 8: wc_ecc_cmp_param() param/curveParam NULL guard (line ~4460).
|
|
*
|
|
* if (param == NULL || curveParam == NULL) return BAD_FUNC_ARG;
|
|
*
|
|
* Both public callers always pass live ecc_sets[] hex strings and a live
|
|
* caller-supplied buffer, so neither NULL half is reachable from the API.
|
|
* File-static: only callable because this TU #includes ecc.c.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_cmp_param_null(void)
|
|
{
|
|
byte param[4] = { 1, 2, 3, 4 };
|
|
int ret;
|
|
|
|
ret = wc_ecc_cmp_param(NULL, param, sizeof(param), WC_TYPE_UNSIGNED_BIN);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_cmp_param(curveParam=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
ret = wc_ecc_cmp_param("01020304", NULL, 0, WC_TYPE_UNSIGNED_BIN);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
|
|
WB_NOTE("wc_ecc_cmp_param(param=NULL) unexpected return");
|
|
wb_fail = 1;
|
|
}
|
|
/* all-false companion, in this binary. */
|
|
(void)wc_ecc_cmp_param("01020304", param, sizeof(param),
|
|
WC_TYPE_UNSIGNED_BIN);
|
|
WB_NOTE("wc_ecc_cmp_param NULL guard pairs exercised");
|
|
}
|
|
|
|
/* Convert a curve hex-string field to its raw big-endian bytes, using the
|
|
* same mp_read_radix()/mp_to_unsigned_bin() the library itself uses -- so
|
|
* the bytes compare equal (via wc_ecc_cmp_param) to the source hex string
|
|
* regardless of any leading-zero-nibble width difference. */
|
|
#define WB_MAXFIELD 100 /* > any ecc_sets[] field at P-521 (66 bytes) */
|
|
static int wb_hex_to_bin(const char* hex, byte* out, word32* outLen)
|
|
{
|
|
mp_int t;
|
|
int err;
|
|
int sz;
|
|
|
|
if (mp_init(&t) != MP_OKAY)
|
|
return -1;
|
|
err = mp_read_radix(&t, hex, MP_RADIX_HEX);
|
|
if (err == MP_OKAY) {
|
|
sz = mp_unsigned_bin_size(&t);
|
|
if (sz < 0 || (word32)sz > WB_MAXFIELD) {
|
|
err = -1;
|
|
}
|
|
else {
|
|
*outLen = (word32)sz;
|
|
err = mp_to_unsigned_bin(&t, out);
|
|
}
|
|
}
|
|
mp_clear(&t);
|
|
return err;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 9/10: wc_ecc_get_curve_id_from_params() (lines ~4537, ~4545).
|
|
*
|
|
* NULL OR guard: prime==NULL || Af==NULL || Bf==NULL || order==NULL ||
|
|
* Gx==NULL || Gy==NULL
|
|
* AND match chain: prime match && Af match && Bf match && order match &&
|
|
* Gx match && Gy match && cofactor match
|
|
*
|
|
* No tests/api caller ever supplies a real matching parameter set (every
|
|
* caller either passes a deliberately-wrong set to prove ECC_CURVE_INVALID,
|
|
* or does not call this function at all), so neither the NULL guard's non-
|
|
* prime operands nor any operand of the match chain past "prime" has its
|
|
* independence pair shown anywhere. Build a byte-exact copy of a real
|
|
* curve's fields (SECP256R1) to drive both.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_get_curve_id_from_params(void)
|
|
{
|
|
int idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
const ecc_set_type* cs;
|
|
byte prime[WB_MAXFIELD], Af[WB_MAXFIELD], Bf[WB_MAXFIELD];
|
|
byte order[WB_MAXFIELD], Gx[WB_MAXFIELD], Gy[WB_MAXFIELD];
|
|
byte bad[WB_MAXFIELD];
|
|
word32 primeSz, AfSz, BfSz, orderSz, GxSz, GySz;
|
|
int fieldSize, ret;
|
|
|
|
if (idx == ECC_CURVE_INVALID) {
|
|
WB_NOTE("SECP256R1 not in ecc_sets[]; skipped");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
cs = wc_ecc_get_curve_params(idx);
|
|
fieldSize = cs->size * 8;
|
|
|
|
if (wb_hex_to_bin(cs->prime, prime, &primeSz) != 0 ||
|
|
wb_hex_to_bin(cs->Af, Af, &AfSz) != 0 ||
|
|
wb_hex_to_bin(cs->Bf, Bf, &BfSz) != 0 ||
|
|
wb_hex_to_bin(cs->order, order, &orderSz) != 0 ||
|
|
wb_hex_to_bin(cs->Gx, Gx, &GxSz) != 0 ||
|
|
wb_hex_to_bin(cs->Gy, Gy, &GySz) != 0) {
|
|
WB_NOTE("wb_hex_to_bin failed; wb_get_curve_id_from_params skipped");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
|
|
/* ---- NULL OR guard: isolate each non-prime operand, rest valid ---- */
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, NULL,
|
|
AfSz, Bf, BfSz, order, orderSz, Gx, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, NULL, BfSz, order, orderSz, Gx, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, Bf, BfSz, NULL, orderSz, Gx, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, Bf, BfSz, order, orderSz, NULL, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, Bf, BfSz, order, orderSz, Gx, GxSz, NULL, GySz, cs->cofactor);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
if (wb_fail) {
|
|
WB_NOTE("wc_ecc_get_curve_id_from_params NULL guard unexpected");
|
|
}
|
|
|
|
/* ---- all-valid baseline: full real match, needed in THIS binary ---- */
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, Bf, BfSz, order, orderSz, Gx, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != cs->id) {
|
|
WB_NOTE("wc_ecc_get_curve_id_from_params(all-match) unexpected id");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
/* ---- AND-chain independence: corrupt exactly one field, keep the
|
|
* rest matching, expect ECC_CURVE_INVALID (no ecc_sets[] entry can
|
|
* coincidentally match a flipped cryptographic field of the same
|
|
* length). ---- */
|
|
#define WB_CORRUPT1(buf, len) do { \
|
|
XMEMCPY(bad, (buf), (len)); \
|
|
bad[(len) - 1] = (byte)(bad[(len) - 1] ^ 0xFFu); \
|
|
} while (0)
|
|
|
|
WB_CORRUPT1(Af, AfSz);
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, bad,
|
|
AfSz, Bf, BfSz, order, orderSz, Gx, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
|
|
WB_CORRUPT1(Bf, BfSz);
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, bad, BfSz, order, orderSz, Gx, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
|
|
WB_CORRUPT1(order, orderSz);
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, Bf, BfSz, bad, orderSz, Gx, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
|
|
WB_CORRUPT1(Gx, GxSz);
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, Bf, BfSz, order, orderSz, bad, GxSz, Gy, GySz, cs->cofactor);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
|
|
WB_CORRUPT1(Gy, GySz);
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, Bf, BfSz, order, orderSz, Gx, GxSz, bad, GySz, cs->cofactor);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
|
|
ret = wc_ecc_get_curve_id_from_params(fieldSize, prime, primeSz, Af,
|
|
AfSz, Bf, BfSz, order, orderSz, Gx, GxSz, Gy, GySz, cs->cofactor + 1);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
#undef WB_CORRUPT1
|
|
|
|
WB_NOTE("wc_ecc_get_curve_id_from_params NULL+match-chain pairs done");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 11: wc_ecc_get_curve_id_from_dp_params() (lines ~4580, ~4591).
|
|
*
|
|
* 7-operand OR guard: dp==NULL || dp->prime==NULL || dp->Af==NULL ||
|
|
* dp->Bf==NULL || dp->order==NULL || dp->Gx==NULL || dp->Gy==NULL
|
|
* AND match chain: same 6 hex-string fields (WC_TYPE_HEX_STR) + cofactor.
|
|
*
|
|
* No public caller ever builds an ecc_set_type with a live curve's own hex
|
|
* strings copied in field-by-field (real callers pass a whole, pre-existing
|
|
* dp), so every operand past "dp itself" is unreached both in the OR guard
|
|
* and the match chain.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_get_curve_id_from_dp_params(void)
|
|
{
|
|
int idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
const ecc_set_type* cs;
|
|
ecc_set_type dp;
|
|
char bad[WB_MAXFIELD * 2 + 4];
|
|
int ret;
|
|
|
|
if (idx == ECC_CURVE_INVALID) {
|
|
WB_NOTE("SECP256R1 not in ecc_sets[]; skipped");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
cs = wc_ecc_get_curve_params(idx);
|
|
|
|
ret = wc_ecc_get_curve_id_from_dp_params(NULL);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
|
|
XMEMSET(&dp, 0, sizeof(dp));
|
|
dp.size = cs->size;
|
|
dp.cofactor = cs->cofactor;
|
|
dp.Af = cs->Af; dp.Bf = cs->Bf; dp.order = cs->order;
|
|
dp.Gx = cs->Gx; dp.Gy = cs->Gy;
|
|
|
|
dp.prime = NULL;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
dp.prime = cs->prime;
|
|
|
|
dp.Af = NULL;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
dp.Af = cs->Af;
|
|
|
|
dp.Bf = NULL;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
dp.Bf = cs->Bf;
|
|
|
|
dp.order = NULL;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
dp.order = cs->order;
|
|
|
|
dp.Gx = NULL;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
dp.Gx = cs->Gx;
|
|
|
|
dp.Gy = NULL;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
dp.Gy = cs->Gy;
|
|
if (wb_fail) {
|
|
WB_NOTE("wc_ecc_get_curve_id_from_dp_params NULL guard unexpected");
|
|
}
|
|
|
|
/* all-valid baseline: full real match. */
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != cs->id) {
|
|
WB_NOTE("wc_ecc_get_curve_id_from_dp_params(all-match) unexpected");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
/* AND-chain independence: corrupt one hex digit at a time (same
|
|
* strlen, so the "strlen mismatch" fast-reject in wc_ecc_cmp_param
|
|
* does not short-circuit before the byte compare runs), keep every
|
|
* other field matching. */
|
|
#define WB_CORRUPT_HEX(field) do { \
|
|
size_t wb_n = XSTRLEN((field)); \
|
|
XSTRNCPY(bad, (field), sizeof(bad) - 1); \
|
|
bad[sizeof(bad) - 1] = '\0'; \
|
|
bad[wb_n - 1] = (bad[wb_n - 1] == '0') ? '1' : '0'; \
|
|
} while (0)
|
|
|
|
WB_CORRUPT_HEX(cs->Af); dp.Af = bad;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
dp.Af = cs->Af;
|
|
|
|
WB_CORRUPT_HEX(cs->Bf); dp.Bf = bad;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
dp.Bf = cs->Bf;
|
|
|
|
WB_CORRUPT_HEX(cs->order); dp.order = bad;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
dp.order = cs->order;
|
|
|
|
WB_CORRUPT_HEX(cs->Gx); dp.Gx = bad;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
dp.Gx = cs->Gx;
|
|
|
|
WB_CORRUPT_HEX(cs->Gy); dp.Gy = bad;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
dp.Gy = cs->Gy;
|
|
#undef WB_CORRUPT_HEX
|
|
|
|
dp.cofactor = cs->cofactor + 1;
|
|
ret = wc_ecc_get_curve_id_from_dp_params(&dp);
|
|
if (ret != ECC_CURVE_INVALID) { wb_fail = 1; }
|
|
|
|
WB_NOTE("wc_ecc_get_curve_id_from_dp_params NULL+match-chain pairs done");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 12: wc_ecc_mulmod_ex2() 4-operand NULL guard, generic (!SP_MATH)
|
|
* path (line ~4009): k==NULL || G==NULL || R==NULL || modulus==NULL.
|
|
*
|
|
* No variant here defines bare WOLFSSL_SP_MATH, so this is the branch every
|
|
* variant compiles. No current test calls this entry point directly (real
|
|
* traffic goes through wc_ecc_mulmod_ex/wc_ecc_mulmod, or the FP_ECC/SP
|
|
* layers above it), so not even the all-valid baseline is shown elsewhere
|
|
* in this binary; supply the full independence set plus one real call.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_mulmod_ex2_null_guard(void)
|
|
{
|
|
ecc_point *G = NULL, *R = NULL;
|
|
mp_int k, a, modulus, order;
|
|
int ret;
|
|
|
|
if (mp_init_multi(&k, &a, &modulus, &order, NULL, NULL) != MP_OKAY) {
|
|
WB_NOTE("mp_init_multi failed; wb_mulmod_ex2_null_guard skipped");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
(void)mp_set(&k, 3);
|
|
(void)mp_set(&a, 2);
|
|
(void)mp_set_int(&modulus, 1000000007uL);
|
|
(void)mp_set_int(&order, 1000000007uL);
|
|
|
|
G = wc_ecc_new_point();
|
|
R = wc_ecc_new_point();
|
|
if (G == NULL || R == NULL) {
|
|
WB_NOTE("wc_ecc_new_point failed; wb_mulmod_ex2_null_guard skipped");
|
|
wb_fail = 1;
|
|
goto out;
|
|
}
|
|
(void)mp_set(G->x, 5);
|
|
(void)mp_set(G->y, 7);
|
|
(void)mp_set(G->z, 1);
|
|
|
|
ret = wc_ecc_mulmod_ex2(NULL, G, R, &a, &modulus, &order, NULL, 1, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = wc_ecc_mulmod_ex2(&k, NULL, R, &a, &modulus, &order, NULL, 1, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = wc_ecc_mulmod_ex2(&k, G, NULL, &a, &modulus, &order, NULL, 1, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = wc_ecc_mulmod_ex2(&k, G, R, &a, NULL, &order, NULL, 1, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
/* all-false baseline: real (if not curve-accurate) arithmetic inputs;
|
|
* the generic point-math does not require the operands to satisfy a
|
|
* real curve equation, only that modulus is odd (for montgomery). */
|
|
(void)wc_ecc_mulmod_ex2(&k, G, R, &a, &modulus, &order, NULL, 1, NULL);
|
|
|
|
if (wb_fail) {
|
|
WB_NOTE("wc_ecc_mulmod_ex2 NULL guard unexpected return");
|
|
}
|
|
WB_NOTE("wc_ecc_mulmod_ex2 4-operand NULL guard pairs exercised");
|
|
|
|
out:
|
|
wc_ecc_del_point(G);
|
|
wc_ecc_del_point(R);
|
|
mp_clear(&order);
|
|
mp_clear(&modulus);
|
|
mp_clear(&a);
|
|
mp_clear(&k);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 13: ecc_map_ex() P/modulus NULL guard (line ~2791).
|
|
*
|
|
* if (P == NULL || modulus == NULL) return ECC_BAD_ARG_E;
|
|
*
|
|
* Every caller of ecc_map()/ecc_map_ex() passes a live point off the stack
|
|
* and a live curve modulus, so neither NULL half is reachable via the API.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_ecc_map_ex_null(void)
|
|
{
|
|
ecc_point* P;
|
|
mp_int modulus;
|
|
int ret;
|
|
|
|
if (mp_init(&modulus) != MP_OKAY) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
(void)mp_set_int(&modulus, 1000000007uL);
|
|
P = wc_ecc_new_point();
|
|
if (P == NULL) {
|
|
mp_clear(&modulus);
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
(void)mp_set(P->x, 3);
|
|
(void)mp_set(P->y, 5);
|
|
(void)mp_set(P->z, 1);
|
|
|
|
ret = ecc_map_ex(NULL, &modulus, 0, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = ecc_map_ex(P, NULL, 0, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
wc_ecc_del_point(P);
|
|
mp_clear(&modulus);
|
|
WB_NOTE("ecc_map_ex P/modulus NULL guard pairs exercised");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 14: ecc_projective_add_point()/ecc_projective_dbl_point() public
|
|
* wrappers (lines ~2393/2397, ~2761/2764): NULL guard + coordinate range
|
|
* check ("mp_cmp(..) != MP_LT" over x/y/z of both operands).
|
|
*
|
|
* These wrappers are compiled unconditionally in ecc.c (always in scope for
|
|
* a same-TU #include), but only PROTOTYPED under WOLFSSL_PUBLIC_ECC_ADD_DBL
|
|
* -- no in-tree caller (all of which use the _safe() variants) reaches them
|
|
* at all, so no operand of either guard has any coverage.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_projective_wrappers(void)
|
|
{
|
|
ecc_point *P, *Q, *R;
|
|
mp_int a, modulus;
|
|
int ret;
|
|
|
|
if (mp_init_multi(&a, &modulus, NULL, NULL, NULL, NULL) != MP_OKAY) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
(void)mp_set(&a, 2);
|
|
(void)mp_set_int(&modulus, 1000000007uL);
|
|
|
|
P = wc_ecc_new_point();
|
|
Q = wc_ecc_new_point();
|
|
R = wc_ecc_new_point();
|
|
if (P == NULL || Q == NULL || R == NULL) {
|
|
wb_fail = 1;
|
|
goto out;
|
|
}
|
|
(void)mp_set(P->x, 3); (void)mp_set(P->y, 5); (void)mp_set(P->z, 1);
|
|
(void)mp_set(Q->x, 11); (void)mp_set(Q->y, 13); (void)mp_set(Q->z, 1);
|
|
|
|
/* ---- ecc_projective_add_point: NULL guard, each operand isolated ---- */
|
|
ret = ecc_projective_add_point(NULL, Q, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = ecc_projective_add_point(P, NULL, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = ecc_projective_add_point(P, Q, NULL, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = ecc_projective_add_point(P, Q, R, &a, NULL, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
/* ---- range check: each of the 6 coordinate comparisons isolated,
|
|
* one coordinate at a time set >= modulus, rest in range. ---- */
|
|
(void)mp_set_int(P->x, 1000000007uL); /* == modulus: not MP_LT */
|
|
ret = ecc_projective_add_point(P, Q, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(P->x, 3);
|
|
|
|
(void)mp_set_int(P->y, 1000000007uL);
|
|
ret = ecc_projective_add_point(P, Q, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(P->y, 5);
|
|
|
|
(void)mp_set_int(P->z, 1000000007uL);
|
|
ret = ecc_projective_add_point(P, Q, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(P->z, 1);
|
|
|
|
(void)mp_set_int(Q->x, 1000000007uL);
|
|
ret = ecc_projective_add_point(P, Q, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(Q->x, 11);
|
|
|
|
(void)mp_set_int(Q->y, 1000000007uL);
|
|
ret = ecc_projective_add_point(P, Q, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(Q->y, 13);
|
|
|
|
(void)mp_set_int(Q->z, 1000000007uL);
|
|
ret = ecc_projective_add_point(P, Q, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(Q->z, 1);
|
|
|
|
/* all-in-range baseline (drives the real _ecc_projective_add_point). */
|
|
ret = ecc_projective_add_point(P, Q, R, &a, &modulus, 0);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
/* ---- ecc_projective_dbl_point: NULL guard + range check, same idea
|
|
* with 3 operands (P/R/modulus; no Q). ---- */
|
|
ret = ecc_projective_dbl_point(NULL, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = ecc_projective_dbl_point(P, NULL, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = ecc_projective_dbl_point(P, R, &a, NULL, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
(void)mp_set_int(P->x, 1000000007uL);
|
|
ret = ecc_projective_dbl_point(P, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(P->x, 3);
|
|
|
|
(void)mp_set_int(P->y, 1000000007uL);
|
|
ret = ecc_projective_dbl_point(P, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(P->y, 5);
|
|
|
|
(void)mp_set_int(P->z, 1000000007uL);
|
|
ret = ecc_projective_dbl_point(P, R, &a, &modulus, 0);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)) { wb_fail = 1; }
|
|
(void)mp_set(P->z, 1);
|
|
|
|
ret = ecc_projective_dbl_point(P, R, &a, &modulus, 0);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
if (wb_fail) {
|
|
WB_NOTE("ecc_projective_add/dbl_point wrapper unexpected return");
|
|
}
|
|
WB_NOTE("ecc_projective_add/dbl_point wrapper NULL+range pairs done");
|
|
|
|
out:
|
|
wc_ecc_del_point(P);
|
|
wc_ecc_del_point(Q);
|
|
wc_ecc_del_point(R);
|
|
mp_clear(&modulus);
|
|
mp_clear(&a);
|
|
}
|
|
|
|
/* ECC_SHAMIR is unconditional in the base config (see the module registry "ecc"
|
|
* notes): under it, ecc_mul2add() is `static normal_ecc_mul2add()` (FP_ECC
|
|
* on, the default) or the public `ecc_mul2add()` itself (no_fp_shamir, FP_ECC
|
|
* off) -- select the same symbol the source itself would use. */
|
|
#ifdef ECC_SHAMIR
|
|
#ifdef FP_ECC
|
|
#define WB_MUL2ADD_FN normal_ecc_mul2add
|
|
#else
|
|
#define WB_MUL2ADD_FN ecc_mul2add
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 15: ecc_mul2add()/normal_ecc_mul2add() (line ~8774): 6-operand NULL
|
|
* guard (A/kA/B/kB/C/modulus -- "a" is not checked) and the ECC_BUFSIZE
|
|
* scalar-length sanity check (line ~8861).
|
|
*
|
|
* No caller passes a NULL operand (every public path -- verify_hash's
|
|
* Shamir's-trick optimization -- already validated key/hash/sig upstream),
|
|
* and no caller's scalar ever exceeds ECC_BUFSIZE (257) bytes (they are all
|
|
* bounded by a curve order), so neither guard's TRUE half is reachable via
|
|
* the API. Drives real SECP256R1 generator-point arithmetic (kA=3, kB=5) so
|
|
* the all-false baseline is also completed within this binary.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_mul2add_and_bufsize(void)
|
|
{
|
|
int idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
const ecc_set_type* cs;
|
|
mp_int a, modulus, kA, kB, kBig;
|
|
ecc_point *A = NULL, *B = NULL, *C = NULL;
|
|
byte fbuf[WB_MAXFIELD];
|
|
byte bigbuf[300];
|
|
word32 sz;
|
|
int ret;
|
|
|
|
if (idx == ECC_CURVE_INVALID) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
cs = wc_ecc_get_curve_params(idx);
|
|
|
|
if (mp_init_multi(&a, &modulus, &kA, &kB, &kBig, NULL) != MP_OKAY) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
|
|
if (wb_hex_to_bin(cs->Af, fbuf, &sz) != 0 ||
|
|
mp_read_unsigned_bin(&a, fbuf, (int)sz) != MP_OKAY) {
|
|
wb_fail = 1; goto out;
|
|
}
|
|
if (wb_hex_to_bin(cs->prime, fbuf, &sz) != 0 ||
|
|
mp_read_unsigned_bin(&modulus, fbuf, (int)sz) != MP_OKAY) {
|
|
wb_fail = 1; goto out;
|
|
}
|
|
(void)mp_set(&kA, 3);
|
|
(void)mp_set(&kB, 5);
|
|
XMEMSET(bigbuf, 0xFF, sizeof(bigbuf)); /* > ECC_BUFSIZE (257) bytes */
|
|
if (mp_read_unsigned_bin(&kBig, bigbuf, (int)sizeof(bigbuf)) != MP_OKAY) {
|
|
wb_fail = 1; goto out;
|
|
}
|
|
|
|
A = wc_ecc_new_point();
|
|
B = wc_ecc_new_point();
|
|
C = wc_ecc_new_point();
|
|
if (A == NULL || B == NULL || C == NULL) {
|
|
wb_fail = 1; goto out;
|
|
}
|
|
if (wb_hex_to_bin(cs->Gx, fbuf, &sz) != 0 ||
|
|
mp_read_unsigned_bin(A->x, fbuf, (int)sz) != MP_OKAY) {
|
|
wb_fail = 1; goto out;
|
|
}
|
|
if (wb_hex_to_bin(cs->Gy, fbuf, &sz) != 0 ||
|
|
mp_read_unsigned_bin(A->y, fbuf, (int)sz) != MP_OKAY) {
|
|
wb_fail = 1; goto out;
|
|
}
|
|
(void)mp_set(A->z, 1);
|
|
(void)mp_copy(A->x, B->x);
|
|
(void)mp_copy(A->y, B->y);
|
|
(void)mp_copy(A->z, B->z);
|
|
|
|
/* ---- 6-operand NULL guard, each isolated ---- */
|
|
ret = WB_MUL2ADD_FN(NULL, &kA, B, &kB, C, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = WB_MUL2ADD_FN(A, NULL, B, &kB, C, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = WB_MUL2ADD_FN(A, &kA, NULL, &kB, C, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = WB_MUL2ADD_FN(A, &kA, B, NULL, C, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = WB_MUL2ADD_FN(A, &kA, B, &kB, NULL, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = WB_MUL2ADD_FN(A, &kA, B, &kB, C, &a, NULL, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
/* ---- ECC_BUFSIZE check: lenA/lenB isolated ---- */
|
|
ret = WB_MUL2ADD_FN(A, &kBig, B, &kB, C, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
ret = WB_MUL2ADD_FN(A, &kA, B, &kBig, C, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
|
|
/* ---- all-false baseline: real kA*G + kB*G Shamir computation ---- */
|
|
ret = WB_MUL2ADD_FN(A, &kA, B, &kB, C, &a, &modulus, NULL);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
if (wb_fail) {
|
|
WB_NOTE("ecc_mul2add NULL/ECC_BUFSIZE guard unexpected return");
|
|
}
|
|
WB_NOTE("ecc_mul2add NULL guard + ECC_BUFSIZE pairs done");
|
|
|
|
out:
|
|
wc_ecc_del_point(A);
|
|
wc_ecc_del_point(B);
|
|
wc_ecc_del_point(C);
|
|
mp_clear(&kBig);
|
|
mp_clear(&kB);
|
|
mp_clear(&kA);
|
|
mp_clear(&modulus);
|
|
mp_clear(&a);
|
|
}
|
|
#else
|
|
static void wb_mul2add_and_bufsize(void)
|
|
{
|
|
WB_NOTE("ECC_SHAMIR off; ecc_mul2add skipped");
|
|
}
|
|
#endif /* ECC_SHAMIR */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 16: ecc_projective_add_point_safe()/ecc_projective_dbl_point_safe()
|
|
* (lines ~8581-8656): the point-at-infinity special cases and the
|
|
* A==B / A==-B collision cases that the "safe" wrappers exist to handle.
|
|
*
|
|
* Real ECDSA/ECDH traffic essentially never presents the infinity point or
|
|
* an exact doubling/negation collision to these wrappers (a valid random
|
|
* scalar practically never produces one), so none of the mp_iszero()/
|
|
* mp_cmp() branches here have ever been shown with a live point. Uses a
|
|
* small modulus (not a real curve) since the wrappers only need montgomery-
|
|
* valid modular arithmetic, not an actual point-on-curve check.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_projective_safe_special_cases(void)
|
|
{
|
|
mp_int a, modulus;
|
|
mp_digit mp;
|
|
ecc_point *A, *B, *R;
|
|
int ret, infinity;
|
|
|
|
if (mp_init_multi(&a, &modulus, NULL, NULL, NULL, NULL) != MP_OKAY) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
(void)mp_set(&a, 2);
|
|
(void)mp_set_int(&modulus, 1000000007uL);
|
|
if (mp_montgomery_setup(&modulus, &mp) != MP_OKAY) {
|
|
wb_fail = 1;
|
|
mp_clear(&modulus); mp_clear(&a);
|
|
return;
|
|
}
|
|
|
|
A = wc_ecc_new_point();
|
|
B = wc_ecc_new_point();
|
|
R = wc_ecc_new_point();
|
|
if (A == NULL || B == NULL || R == NULL) {
|
|
wb_fail = 1;
|
|
goto out;
|
|
}
|
|
|
|
/* ---- A at infinity (x==0 && y==0): copy B into R. ---- */
|
|
(void)mp_set(A->x, 0); (void)mp_set(A->y, 0); (void)mp_set(A->z, 1);
|
|
(void)mp_set(B->x, 3); (void)mp_set(B->y, 5); (void)mp_set(B->z, 1);
|
|
infinity = 0;
|
|
ret = ecc_projective_add_point_safe(A, B, R, &a, &modulus, mp, &infinity);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
/* ---- A not infinity (x==0,y!=0: isolates the "&&"), B at infinity:
|
|
* copy A into R. ---- */
|
|
(void)mp_set(A->x, 0); (void)mp_set(A->y, 5); (void)mp_set(A->z, 1);
|
|
(void)mp_set(B->x, 0); (void)mp_set(B->y, 0); (void)mp_set(B->z, 1);
|
|
infinity = 0;
|
|
ret = ecc_projective_add_point_safe(A, B, R, &a, &modulus, mp, &infinity);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
/* ---- neither infinite (y==0,x!=0 isolates A's "&&" other side;
|
|
* x==0,y!=0 isolates B's), same x/z, same y: A == B -> double. ---- */
|
|
(void)mp_set(A->x, 7); (void)mp_set(A->y, 0); (void)mp_set(A->z, 1);
|
|
(void)mp_set(B->x, 0); (void)mp_set(B->y, 5); (void)mp_set(B->z, 1);
|
|
infinity = 0;
|
|
ret = ecc_projective_add_point_safe(A, B, R, &a, &modulus, mp, &infinity);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
(void)mp_set(A->x, 11); (void)mp_set(A->y, 13); (void)mp_set(A->z, 1);
|
|
(void)mp_set(B->x, 11); (void)mp_set(B->y, 13); (void)mp_set(B->z, 1);
|
|
infinity = 0;
|
|
ret = ecc_projective_add_point_safe(A, B, R, &a, &modulus, mp, &infinity);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
/* ---- same x/z, y differs: A == -B -> result set to infinity,
|
|
* *infinity set (infinity!=NULL true half). ---- */
|
|
(void)mp_set(A->x, 11); (void)mp_set(A->y, 13); (void)mp_set(A->z, 1);
|
|
(void)mp_set(B->x, 11); (void)mp_set(B->y, 17); (void)mp_set(B->z, 1);
|
|
infinity = 0;
|
|
ret = ecc_projective_add_point_safe(A, B, R, &a, &modulus, mp, &infinity);
|
|
if (ret != MP_OKAY || !infinity) { wb_fail = 1; }
|
|
/* same call, infinity==NULL: isolates that operand's other half. */
|
|
ret = ecc_projective_add_point_safe(A, B, R, &a, &modulus, mp, NULL);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
/* ---- general add, neither special case (the common path). ---- */
|
|
(void)mp_set(A->x, 3); (void)mp_set(A->y, 5); (void)mp_set(A->z, 1);
|
|
(void)mp_set(B->x, 11); (void)mp_set(B->y, 13); (void)mp_set(B->z, 1);
|
|
infinity = 0;
|
|
ret = ecc_projective_add_point_safe(A, B, R, &a, &modulus, mp, &infinity);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
/* ---- ecc_projective_dbl_point_safe: P at infinity vs not. ---- */
|
|
(void)mp_set(A->x, 0); (void)mp_set(A->y, 0); (void)mp_set(A->z, 1);
|
|
ret = ecc_projective_dbl_point_safe(A, R, &a, &modulus, mp);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
(void)mp_set(A->x, 0); (void)mp_set(A->y, 5); (void)mp_set(A->z, 1);
|
|
ret = ecc_projective_dbl_point_safe(A, R, &a, &modulus, mp);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
(void)mp_set(A->x, 3); (void)mp_set(A->y, 5); (void)mp_set(A->z, 1);
|
|
ret = ecc_projective_dbl_point_safe(A, R, &a, &modulus, mp);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
if (wb_fail) {
|
|
WB_NOTE("ecc_projective_*_safe special-case unexpected return");
|
|
}
|
|
WB_NOTE("ecc_projective_add/dbl_point_safe special cases exercised");
|
|
|
|
out:
|
|
wc_ecc_del_point(A);
|
|
wc_ecc_del_point(B);
|
|
wc_ecc_del_point(R);
|
|
mp_clear(&modulus);
|
|
mp_clear(&a);
|
|
}
|
|
|
|
/* find_hole()/find_base()/add_entry() and fp_cache[] itself only exist
|
|
* inside ecc.c's own #ifdef FP_ECC (+ !WOLFSSL_SP_MATH) block. */
|
|
#if defined(FP_ECC) && !defined(WOLFSSL_SP_MATH)
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 17: FP_ECC fixed-point cache internals: find_base() (line ~13585),
|
|
* find_hole() (line ~13548). Both are file-static and process-global
|
|
* (fp_cache[FP_ENTRIES]), reset via wc_ecc_fp_free() so each shot starts
|
|
* from deterministic empty-cache state.
|
|
*
|
|
* Real traffic keeps the cache warm across many identical-curve operations,
|
|
* so a fresh process practically never observes find_base() miss on an
|
|
* OCCUPIED-but-different-point entry, nor find_hole() choosing between a
|
|
* locked low-lru entry and an empty one, nor its "evict a live entry"
|
|
* cleanup path -- none of those have a public-API trigger this precise.
|
|
* add_entry() itself only needs mp_copy (no curve math), so it is safe to
|
|
* call directly; the mp_init() of fp_cache[idx].mu below stands in for what
|
|
* build_lut() would normally have set up right after add_entry(), so
|
|
* find_hole()'s later mp_clear(&fp_cache[idx].mu) operates on a live value.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_fp_cache_internals(void)
|
|
{
|
|
ecc_point *g0, *g1;
|
|
int idx0, ret;
|
|
unsigned x;
|
|
|
|
wc_ecc_fp_free(); /* deterministic empty-cache start */
|
|
|
|
g0 = wc_ecc_new_point();
|
|
g1 = wc_ecc_new_point();
|
|
if (g0 == NULL || g1 == NULL) {
|
|
wb_fail = 1;
|
|
goto out;
|
|
}
|
|
(void)mp_set(g0->x, 3); (void)mp_set(g0->y, 5); (void)mp_set(g0->z, 1);
|
|
(void)mp_set(g1->x, 11); (void)mp_set(g1->y, 13); (void)mp_set(g1->z, 1);
|
|
|
|
/* find_base on an empty cache: every fp_cache[x].g == NULL -> -1. */
|
|
if (find_base(g0) != -1) { wb_fail = 1; }
|
|
|
|
ret = add_entry(0, g0);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
(void)mp_init(&fp_cache[0].mu); /* stand-in for build_lut()'s own init */
|
|
|
|
/* find_base: entry 0 occupied and matches (all 3 mp_cmp true) -> 0. */
|
|
if (find_base(g0) != 0) { wb_fail = 1; }
|
|
/* find_base: entry 0 occupied but a DIFFERENT point -> falls through
|
|
* to -1 (isolates the mp_cmp x/y/z operands' FALSE side). */
|
|
if (find_base(g1) != -1) { wb_fail = 1; }
|
|
|
|
/* find_hole: lock entry 0 so it is excluded (lock==0 FALSE) even
|
|
* though it has the lowest lru_count; some other (empty, g==NULL)
|
|
* slot must be chosen -- exercises the "z>=0 && g" FALSE half. */
|
|
fp_cache[0].lock = 1;
|
|
idx0 = find_hole();
|
|
if (idx0 < 0 || idx0 == 0 || fp_cache[idx0].g != NULL) { wb_fail = 1; }
|
|
fp_cache[0].lock = 0;
|
|
|
|
/* find_hole: bump every OTHER entry's lru_count above entry 0's, so
|
|
* entry 0 (lowest lru_count, unlocked) is chosen and its live g/LUT
|
|
* get freed -- exercises the "z>=0 && g" TRUE half and the eviction
|
|
* cleanup block, plus the "lru_count>3" TRUE half on entries 1..N. */
|
|
for (x = 1; x < FP_ENTRIES; x++) {
|
|
fp_cache[x].lru_count = 5;
|
|
}
|
|
idx0 = find_hole();
|
|
if (idx0 != 0) { wb_fail = 1; }
|
|
|
|
if (wb_fail) {
|
|
WB_NOTE("fp_cache find_base/find_hole unexpected state");
|
|
}
|
|
WB_NOTE("fp_cache find_base/find_hole/add_entry internals exercised");
|
|
|
|
out:
|
|
wc_ecc_del_point(g0);
|
|
wc_ecc_del_point(g1);
|
|
for (x = 0; x < FP_ENTRIES; x++) {
|
|
fp_cache[x].lru_count = 0;
|
|
fp_cache[x].lock = 0;
|
|
}
|
|
wc_ecc_fp_free();
|
|
}
|
|
#else
|
|
static void wb_fp_cache_internals(void)
|
|
{
|
|
WB_NOTE("FP_ECC off (or WOLFSSL_SP_MATH); fp_cache internals skipped");
|
|
}
|
|
#endif /* FP_ECC && !WOLFSSL_SP_MATH */
|
|
|
|
#ifdef HAVE_ECC_KEY_EXPORT
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 18: wc_ecc_export_point_der()/wc_ecc_export_point_der_compressed()
|
|
* (lines ~10330-10351, ~10396-10418): curve_idx range/valid-idx guard,
|
|
* length-query idiom, NULL guard, coordinate-size sanity check.
|
|
*
|
|
* No tests/api caller drives curve_idx with an in-range-but-invalid index,
|
|
* nor a point whose x/y encodes larger than the curve's own byte width, so
|
|
* those halves are white-box only.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_export_point_der(void)
|
|
{
|
|
int idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
ecc_point* point;
|
|
byte out[300];
|
|
byte big[400];
|
|
word32 outLen;
|
|
int ret;
|
|
|
|
if (idx == ECC_CURVE_INVALID) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
point = wc_ecc_new_point();
|
|
if (point == NULL) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
(void)mp_set(point->x, 3);
|
|
(void)mp_set(point->y, 5);
|
|
(void)mp_set(point->z, 1);
|
|
XMEMSET(big, 0xFF, sizeof(big));
|
|
|
|
/* ---- curve_idx guard: both operands isolated ---- */
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der(-1, point, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der(9999, point, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
/* ---- length-query (point!=NULL && out==NULL && outLen!=NULL): isolate
|
|
* point and outLen operands (out==NULL held true throughout). ---- */
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der(idx, point, NULL, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(LENGTH_ONLY_E)) { wb_fail = 1; }
|
|
ret = wc_ecc_export_point_der(idx, point, NULL, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der(idx, NULL, NULL, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
/* ---- coordinate-size check: x/y isolated ---- */
|
|
(void)mp_read_unsigned_bin(point->x, big, (int)sizeof(big));
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der(idx, point, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
(void)mp_set(point->x, 3);
|
|
|
|
(void)mp_read_unsigned_bin(point->y, big, (int)sizeof(big));
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der(idx, point, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
(void)mp_set(point->y, 5);
|
|
|
|
/* baseline real export. */
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der(idx, point, out, &outLen);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
#ifdef HAVE_COMP_KEY
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der_compressed(-1, point, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der_compressed(9999, point, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der_compressed(idx, point, NULL, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(LENGTH_ONLY_E)) { wb_fail = 1; }
|
|
ret = wc_ecc_export_point_der_compressed(idx, point, NULL, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der_compressed(idx, NULL, NULL, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
(void)mp_read_unsigned_bin(point->x, big, (int)sizeof(big));
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der_compressed(idx, point, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
(void)mp_set(point->x, 3);
|
|
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_point_der_compressed(idx, point, out, &outLen);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
#endif /* HAVE_COMP_KEY */
|
|
|
|
if (wb_fail) {
|
|
WB_NOTE("wc_ecc_export_point_der[_compressed] unexpected return");
|
|
}
|
|
WB_NOTE("wc_ecc_export_point_der[_compressed] guard+range pairs done");
|
|
|
|
wc_ecc_del_point(point);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 19: _ecc_export_x963() (lines ~10456-10503): length-query idiom,
|
|
* NULL guard, key->type/idx/dp validity guard, pubkey x/y size check.
|
|
* File-static (only callable because this TU #includes ecc.c).
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_export_x963_internal(void)
|
|
{
|
|
int idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
const ecc_set_type* cs;
|
|
ecc_key key, k2;
|
|
byte out[200];
|
|
byte big[400];
|
|
word32 outLen;
|
|
int ret;
|
|
|
|
if (idx == ECC_CURVE_INVALID) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
cs = wc_ecc_get_curve_params(idx);
|
|
|
|
if (wc_ecc_init(&key) != 0) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
(void)mp_set(key.pubkey.x, 3);
|
|
(void)mp_set(key.pubkey.y, 5);
|
|
(void)mp_set(key.pubkey.z, 1);
|
|
key.type = ECC_PUBLICKEY;
|
|
key.idx = idx;
|
|
key.dp = cs;
|
|
XMEMSET(big, 0xFF, sizeof(big));
|
|
|
|
/* ---- length-query (key!=NULL && out==NULL && outLen!=NULL) ---- */
|
|
outLen = sizeof(out);
|
|
ret = _ecc_export_x963(&key, NULL, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(LENGTH_ONLY_E)) { wb_fail = 1; }
|
|
ret = _ecc_export_x963(&key, NULL, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
outLen = sizeof(out);
|
|
ret = _ecc_export_x963(NULL, NULL, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
/* ---- NULL guard (key==NULL || out==NULL || outLen==NULL) ---- */
|
|
ret = _ecc_export_x963(NULL, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
ret = _ecc_export_x963(&key, out, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
/* ---- key->type==0 || is_valid_idx==0 || dp==NULL (shallow copies:
|
|
* only .type/.dp differ, never freed, never dereferences pubkey). ---- */
|
|
k2 = key;
|
|
k2.type = 0;
|
|
outLen = sizeof(out);
|
|
ret = _ecc_export_x963(&k2, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
k2 = key;
|
|
k2.dp = NULL;
|
|
outLen = sizeof(out);
|
|
ret = _ecc_export_x963(&k2, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
/* ---- pubxlen>numlen || pubylen>numlen: isolated ---- */
|
|
(void)mp_read_unsigned_bin(key.pubkey.x, big, (int)sizeof(big));
|
|
outLen = sizeof(out);
|
|
ret = _ecc_export_x963(&key, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(BUFFER_E)) { wb_fail = 1; }
|
|
(void)mp_set(key.pubkey.x, 3);
|
|
|
|
(void)mp_read_unsigned_bin(key.pubkey.y, big, (int)sizeof(big));
|
|
outLen = sizeof(out);
|
|
ret = _ecc_export_x963(&key, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(BUFFER_E)) { wb_fail = 1; }
|
|
(void)mp_set(key.pubkey.y, 5);
|
|
|
|
/* baseline real export. */
|
|
outLen = sizeof(out);
|
|
ret = _ecc_export_x963(&key, out, &outLen);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
if (wb_fail) {
|
|
WB_NOTE("_ecc_export_x963 unexpected return");
|
|
}
|
|
WB_NOTE("_ecc_export_x963 NULL/type-idx-dp/size pairs done");
|
|
|
|
wc_ecc_free(&key);
|
|
}
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 20: repeated "wc_ecc_is_valid_idx(key->idx) == 0 || key->dp == NULL"
|
|
* guard, isolated at each of its easy-to-drive call sites:
|
|
* _ecc_export_ex() (line ~11843, static)
|
|
* wc_ecc_export_public_raw() (line ~12005, qx/qxLen/qy/qyLen NULL
|
|
* guard -- a different, public, guard on
|
|
* the same call path)
|
|
* wc_ecc_export_x963_compressed() (line ~16779, static, 3-operand chain
|
|
* with key->type==0 in front)
|
|
* Every public caller of these already has a real dp/idx by construction
|
|
* (wc_ecc_init/make_key/import always set both together), so an
|
|
* idx-invalid-but-dp-set or idx-valid-but-dp-NULL key never reaches them
|
|
* via the API.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_idx_dp_guard_export_paths(void)
|
|
{
|
|
int idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
const ecc_set_type* cs;
|
|
ecc_key key;
|
|
byte qx[64], qy[64];
|
|
word32 qxLen, qyLen;
|
|
byte out[200];
|
|
word32 outLen;
|
|
int ret;
|
|
|
|
if (idx == ECC_CURVE_INVALID) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
cs = wc_ecc_get_curve_params(idx);
|
|
if (wc_ecc_init(&key) != 0) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
(void)mp_set(key.pubkey.x, 3);
|
|
(void)mp_set(key.pubkey.y, 5);
|
|
(void)mp_set(key.pubkey.z, 1);
|
|
key.type = ECC_PUBLICKEY;
|
|
|
|
/* ---- _ecc_export_ex: idx invalid/dp valid, then idx valid/dp NULL ---- */
|
|
key.idx = 9999; key.dp = cs;
|
|
qxLen = sizeof(qx); qyLen = sizeof(qy);
|
|
ret = _ecc_export_ex(&key, qx, &qxLen, qy, &qyLen, NULL, NULL,
|
|
WC_TYPE_UNSIGNED_BIN);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
key.idx = idx; key.dp = NULL;
|
|
qxLen = sizeof(qx); qyLen = sizeof(qy);
|
|
ret = _ecc_export_ex(&key, qx, &qxLen, qy, &qyLen, NULL, NULL,
|
|
WC_TYPE_UNSIGNED_BIN);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
key.idx = idx; key.dp = cs; /* baseline: real export */
|
|
qxLen = sizeof(qx); qyLen = sizeof(qy);
|
|
ret = _ecc_export_ex(&key, qx, &qxLen, qy, &qyLen, NULL, NULL,
|
|
WC_TYPE_UNSIGNED_BIN);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
/* ---- wc_ecc_export_public_raw: qx/qxLen/qy/qyLen NULL guard,
|
|
* each operand isolated (rest valid). ---- */
|
|
qxLen = sizeof(qx); qyLen = sizeof(qy);
|
|
ret = wc_ecc_export_public_raw(&key, NULL, &qxLen, qy, &qyLen);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
ret = wc_ecc_export_public_raw(&key, qx, NULL, qy, &qyLen);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
ret = wc_ecc_export_public_raw(&key, qx, &qxLen, NULL, &qyLen);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
ret = wc_ecc_export_public_raw(&key, qx, &qxLen, qy, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { wb_fail = 1; }
|
|
qxLen = sizeof(qx); qyLen = sizeof(qy);
|
|
ret = wc_ecc_export_public_raw(&key, qx, &qxLen, qy, &qyLen);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
|
|
/* ---- wc_ecc_export_x963_compressed: type==0 / idx-invalid / dp==NULL,
|
|
* each isolated (needs HAVE_COMP_KEY; the function itself is only
|
|
* compiled under it). ---- */
|
|
#ifdef HAVE_COMP_KEY
|
|
key.idx = idx; key.dp = cs; key.type = 0;
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_x963_compressed(&key, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
key.type = ECC_PUBLICKEY; key.idx = 9999; key.dp = cs;
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_x963_compressed(&key, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
key.idx = idx; key.dp = NULL;
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_x963_compressed(&key, out, &outLen);
|
|
if (ret != WC_NO_ERR_TRACE(ECC_BAD_ARG_E)) { wb_fail = 1; }
|
|
|
|
key.dp = cs; /* baseline: real compressed export */
|
|
outLen = sizeof(out);
|
|
ret = wc_ecc_export_x963_compressed(&key, out, &outLen);
|
|
if (ret != MP_OKAY) { wb_fail = 1; }
|
|
#endif
|
|
|
|
if (wb_fail) {
|
|
WB_NOTE("idx/dp guard export paths unexpected return");
|
|
}
|
|
WB_NOTE("_ecc_export_ex/export_public_raw/export_x963_compressed "
|
|
"idx/dp guard pairs done");
|
|
|
|
wc_ecc_free(&key);
|
|
}
|
|
#else
|
|
static void wb_export_point_der(void)
|
|
{
|
|
WB_NOTE("HAVE_ECC_KEY_EXPORT off; skipped");
|
|
}
|
|
static void wb_export_x963_internal(void)
|
|
{
|
|
WB_NOTE("HAVE_ECC_KEY_EXPORT off; skipped");
|
|
}
|
|
static void wb_idx_dp_guard_export_paths(void)
|
|
{
|
|
WB_NOTE("HAVE_ECC_KEY_EXPORT off; skipped");
|
|
}
|
|
#endif /* HAVE_ECC_KEY_EXPORT */
|
|
|
|
#if defined(HAVE_ECC_MAKE_PUB) || !defined(WOLFSSL_ATECC508A)
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 21: wc_ecc_make_pub_ex() "key->type == ECC_PRIVATEKEY_ONLY &&
|
|
* pubOut == NULL" (line ~5730): on a private-only key, a NULL pubOut means
|
|
* "cache the recomputed public part on the key" -- promotes key->type to
|
|
* ECC_PRIVATEKEY. No caller in the API ever calls wc_ecc_make_pub() on a key
|
|
* whose type is already ECC_PRIVATEKEY_ONLY (make_key always leaves type ==
|
|
* ECC_PRIVATEKEY), so this promotion path is unreached. A real key is needed
|
|
* (wc_ecc_make_pub_ex does the actual k*G scalar multiply), so build one via
|
|
* the real wc_ecc_make_key(), then force the private-only state by hand.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_make_pub_privatekey_only(void)
|
|
{
|
|
WC_RNG rng;
|
|
ecc_key key;
|
|
int ret;
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
if (wc_ecc_init(&key) != 0) {
|
|
wc_FreeRng(&rng);
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
ret = wc_ecc_make_key_ex(&rng, 0, &key, ECC_SECP256R1);
|
|
if (ret != 0) {
|
|
WB_NOTE("wc_ecc_make_key_ex failed; wb_make_pub_privatekey_only "
|
|
"skipped");
|
|
wb_fail = 1;
|
|
goto out;
|
|
}
|
|
key.type = ECC_PRIVATEKEY_ONLY;
|
|
|
|
ret = wc_ecc_make_pub(&key, NULL);
|
|
if (ret != 0 || key.type != ECC_PRIVATEKEY) {
|
|
WB_NOTE("wc_ecc_make_pub(PRIVATEKEY_ONLY, pubOut=NULL) unexpected");
|
|
wb_fail = 1;
|
|
}
|
|
else {
|
|
WB_NOTE("wc_ecc_make_pub PRIVATEKEY_ONLY promotion exercised");
|
|
}
|
|
|
|
out:
|
|
wc_ecc_free(&key);
|
|
wc_FreeRng(&rng);
|
|
}
|
|
#else
|
|
static void wb_make_pub_privatekey_only(void)
|
|
{
|
|
WB_NOTE("wc_ecc_make_pub not available; skipped");
|
|
}
|
|
#endif
|
|
|
|
#endif /* HAVE_ECC && !WOLF_CRYPTO_CB_ONLY_ECC */
|
|
|
|
|
|
/* Argument-guard vectors that ordinary use never produces. Each guard needs
|
|
* BOTH halves inside THIS binary: llvm-cov derives MC/DC per binary, so a
|
|
* rejection vector on its own proves nothing without the accepting vector of
|
|
* the same decision to pair it against. */
|
|
static void wb_arg_guards(void)
|
|
{
|
|
WC_RNG rng;
|
|
ecc_key key;
|
|
ecc_point* pt = NULL;
|
|
mp_int m1;
|
|
word32 sz = 0;
|
|
byte buf[256];
|
|
word32 bufLen = (word32)sizeof(buf);
|
|
int haveKey = 0;
|
|
int haveM1 = 0;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(&m1, 0, sizeof(m1));
|
|
XMEMSET(buf, 0, sizeof(buf));
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
WB_NOTE("wc_InitRng failed; wb_arg_guards skipped");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
if (wc_ecc_init(&key) == 0) {
|
|
haveKey = (wc_ecc_make_key_ex(&rng, 0, &key, ECC_SECP256R1) == 0);
|
|
}
|
|
if (!haveKey) {
|
|
WB_NOTE("key setup failed; wb_arg_guards skipped");
|
|
wb_fail = 1;
|
|
wc_ecc_free(&key);
|
|
wc_FreeRng(&rng);
|
|
return;
|
|
}
|
|
pt = wc_ecc_new_point();
|
|
haveM1 = (mp_init(&m1) == MP_OKAY);
|
|
|
|
/* The import guards run against a scratch key: their accepting vectors
|
|
* overwrite whatever they are handed, and the operations further down need
|
|
* `key` to still hold a valid point. */
|
|
{
|
|
ecc_key imp;
|
|
word32 xLen = (word32)sizeof(buf);
|
|
int haveImp = (wc_ecc_init(&imp) == 0);
|
|
|
|
/* key == NULL || qx == NULL || qy == NULL */
|
|
(void)_ecc_import_raw_private(NULL, "1", "1", "1", ECC_SECP256R1,
|
|
WC_TYPE_HEX_STR);
|
|
if (haveImp) {
|
|
(void)_ecc_import_raw_private(&imp, NULL, "1", "1", ECC_SECP256R1,
|
|
WC_TYPE_HEX_STR);
|
|
(void)_ecc_import_raw_private(&imp, "1", NULL, "1", ECC_SECP256R1,
|
|
WC_TYPE_HEX_STR);
|
|
(void)_ecc_import_raw_private(&imp, "1", "1", "1", ECC_SECP256R1,
|
|
WC_TYPE_HEX_STR);
|
|
}
|
|
|
|
/* in == NULL || key == NULL */
|
|
(void)_ecc_import_x963_ex2(NULL, 1, &imp, ECC_SECP256R1, 0);
|
|
(void)_ecc_import_x963_ex2(buf, 1, NULL, ECC_SECP256R1, 0);
|
|
if (haveImp && wc_ecc_export_x963(&key, buf, &xLen) == 0) {
|
|
wc_ecc_free(&imp);
|
|
if (wc_ecc_init(&imp) == 0) {
|
|
(void)_ecc_import_x963_ex2(buf, xLen, &imp, ECC_SECP256R1, 0);
|
|
}
|
|
}
|
|
if (haveImp) {
|
|
wc_ecc_free(&imp);
|
|
}
|
|
}
|
|
|
|
/* ecc_public_key_size: key == NULL || key->dp == NULL */
|
|
{
|
|
const ecc_set_type* savedDp = key.dp;
|
|
|
|
(void)ecc_public_key_size(NULL, &sz);
|
|
key.dp = NULL;
|
|
(void)ecc_public_key_size(&key, &sz);
|
|
key.dp = savedDp;
|
|
(void)ecc_public_key_size(&key, &sz);
|
|
}
|
|
|
|
/* The accepting vectors below need the real curve constants: a zero
|
|
* modulus/order would make wc_ecc_gen_deterministic_k's RFC 6979 retry
|
|
* loop spin, and the harness kills the variant on TEST_TIMEOUT. */
|
|
{
|
|
mp_int prime;
|
|
mp_int order;
|
|
mp_int af;
|
|
int haveCurve = 0;
|
|
|
|
XMEMSET(&prime, 0, sizeof(prime));
|
|
XMEMSET(&order, 0, sizeof(order));
|
|
XMEMSET(&af, 0, sizeof(af));
|
|
|
|
if (key.dp != NULL && mp_init(&prime) == MP_OKAY) {
|
|
if (mp_init(&order) == MP_OKAY) {
|
|
if (mp_init(&af) == MP_OKAY) {
|
|
haveCurve =
|
|
(mp_read_radix(&prime, key.dp->prime, 16) == MP_OKAY) &&
|
|
(mp_read_radix(&order, key.dp->order, 16) == MP_OKAY) &&
|
|
(mp_read_radix(&af, key.dp->Af, 16) == MP_OKAY);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* hash == NULL || k == NULL || order == NULL */
|
|
if (haveM1 && haveCurve) {
|
|
(void)wc_ecc_gen_deterministic_k(NULL, 32, WC_HASH_TYPE_SHA256,
|
|
key.k, &m1, &order, key.heap);
|
|
(void)wc_ecc_gen_deterministic_k(buf, 32, WC_HASH_TYPE_SHA256,
|
|
key.k, NULL, &order, key.heap);
|
|
(void)wc_ecc_gen_deterministic_k(buf, 32, WC_HASH_TYPE_SHA256,
|
|
key.k, &m1, NULL, key.heap);
|
|
(void)wc_ecc_gen_deterministic_k(buf, 32, WC_HASH_TYPE_SHA256,
|
|
key.k, &m1, &order, key.heap);
|
|
}
|
|
|
|
/* wc_ecc_mulmod_ex / _ex2 NULL chains, then the accepting vector. */
|
|
if (pt != NULL && haveCurve &&
|
|
wc_ecc_copy_point(&key.pubkey, pt) == MP_OKAY) {
|
|
ecc_point* out = wc_ecc_new_point();
|
|
|
|
#ifdef WOLFSSL_SP_MATH
|
|
/* Only the SP build rejects these: the generic implementation has
|
|
* no such guard and dereferences both operands. */
|
|
(void)wc_ecc_mulmod_ex(key.k, pt, pt, NULL, &prime, 1, key.heap);
|
|
(void)wc_ecc_mulmod_ex2(key.k, pt, pt, NULL, &prime, &order, &rng,
|
|
1, key.heap);
|
|
(void)wc_ecc_mulmod_ex2(key.k, pt, pt, &af, &prime, NULL, &rng, 1,
|
|
key.heap);
|
|
#endif
|
|
if (out != NULL) {
|
|
(void)wc_ecc_mulmod_ex(key.k, pt, out, &af, &prime, 1,
|
|
key.heap);
|
|
(void)wc_ecc_mulmod_ex2(key.k, pt, out, &af, &prime, &order,
|
|
&rng, 1, key.heap);
|
|
wc_ecc_del_point(out);
|
|
}
|
|
|
|
/* point == NULL || out == NULL || outLen == NULL */
|
|
sz = (word32)sizeof(buf);
|
|
(void)wc_ecc_export_point_der_compressed(key.idx, pt, buf, NULL);
|
|
(void)wc_ecc_export_point_der_compressed(key.idx, pt, buf, &sz);
|
|
}
|
|
|
|
mp_free(&af);
|
|
mp_free(&order);
|
|
mp_free(&prime);
|
|
}
|
|
|
|
#ifdef WC_ECC_NONBLOCK
|
|
/* wc_ecc_set_nonblock: key->nb_ctx != NULL && key->nb_ctx != ctx */
|
|
{
|
|
ecc_nb_ctx_t nb;
|
|
|
|
XMEMSET(&nb, 0, sizeof(nb));
|
|
(void)wc_ecc_set_nonblock(&key, &nb);
|
|
(void)wc_ecc_set_nonblock(&key, &nb);
|
|
(void)wc_ecc_set_nonblock(&key, NULL);
|
|
}
|
|
#endif
|
|
|
|
/* The is_valid_idx/dp family: an invalid idx fires the first operand, a
|
|
* valid idx with dp cleared fires the second, an untouched key neither. */
|
|
{
|
|
const ecc_set_type* savedDp = key.dp;
|
|
int savedIdx = key.idx;
|
|
mp_int r;
|
|
mp_int s;
|
|
int stat = 0;
|
|
|
|
key.idx = ECC_CUSTOM_IDX - 1;
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&key, &key, buf, &bufLen);
|
|
(void)wc_ecc_shared_secret_ex(&key, &key.pubkey, buf, &bufLen);
|
|
key.idx = savedIdx;
|
|
|
|
key.dp = NULL;
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&key, &key, buf, &bufLen);
|
|
(void)wc_ecc_shared_secret_ex(&key, &key.pubkey, buf, &bufLen);
|
|
key.dp = savedDp;
|
|
|
|
PRIVATE_KEY_UNLOCK();
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&key, &key, buf, &bufLen);
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret_ex(&key, &key.pubkey, buf, &bufLen);
|
|
PRIVATE_KEY_LOCK();
|
|
|
|
if (mp_init(&r) == MP_OKAY) {
|
|
if (mp_init(&s) == MP_OKAY) {
|
|
key.idx = ECC_CUSTOM_IDX - 1;
|
|
(void)wc_ecc_sign_hash_ex(buf, 32, &rng, &key, &r, &s);
|
|
(void)wc_ecc_verify_hash_ex(&r, &s, buf, 32, &stat, &key);
|
|
key.idx = savedIdx;
|
|
|
|
key.dp = NULL;
|
|
(void)wc_ecc_sign_hash_ex(buf, 32, &rng, &key, &r, &s);
|
|
(void)wc_ecc_verify_hash_ex(&r, &s, buf, 32, &stat, &key);
|
|
key.dp = savedDp;
|
|
|
|
if (wc_ecc_sign_hash_ex(buf, 32, &rng, &key, &r, &s) == 0) {
|
|
(void)wc_ecc_verify_hash_ex(&r, &s, buf, 32, &stat, &key);
|
|
}
|
|
mp_free(&s);
|
|
}
|
|
mp_free(&r);
|
|
}
|
|
}
|
|
|
|
if (pt != NULL) {
|
|
wc_ecc_del_point(pt);
|
|
}
|
|
if (haveM1) {
|
|
mp_free(&m1);
|
|
}
|
|
wc_ecc_free(&key);
|
|
wc_FreeRng(&rng);
|
|
|
|
/* wc_ecc_free: key->deallocSet && key->dp != NULL */
|
|
{
|
|
ecc_key k2;
|
|
|
|
if (wc_ecc_init(&k2) == 0) {
|
|
k2.dp = NULL;
|
|
(void)wc_ecc_free(&k2);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Pass 2: argument/state shapes that no ordinary caller produces.
|
|
*
|
|
* Same rule as everywhere else in this file -- MC/DC is judged per BINARY, so
|
|
* each decision's accepting vector is issued here next to its rejecting one
|
|
* even when the tests/api lane already covers the accepting side.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_gap_pass2(void)
|
|
{
|
|
WC_RNG rng;
|
|
ecc_key key;
|
|
ecc_key peer;
|
|
byte buf[256];
|
|
word32 bufLen;
|
|
int haveKey = 0, havePeer = 0;
|
|
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(&peer, 0, sizeof(peer));
|
|
/* NOT all-zero: ecc.c rejects an all-zero digest (WC_ALLOW_ECC_ZERO_HASH
|
|
* is off) several lines BEFORE the key-type guard at 7672, so a zeroed
|
|
* buffer never reaches it. */
|
|
XMEMSET(buf, 0xa7, sizeof(buf));
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
WB_NOTE("wc_InitRng failed; wb_gap_pass2 skipped");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
if (wc_ecc_init(&key) == 0)
|
|
haveKey = (wc_ecc_make_key_ex(&rng, 0, &key, ECC_SECP256R1) == 0);
|
|
if (wc_ecc_init(&peer) == 0)
|
|
havePeer = (wc_ecc_make_key_ex(&rng, 0, &peer, ECC_SECP256R1) == 0);
|
|
if (!haveKey || !havePeer) {
|
|
WB_NOTE("key setup failed; wb_gap_pass2 skipped");
|
|
wb_fail = 1;
|
|
wc_ecc_free(&peer);
|
|
wc_ecc_free(&key);
|
|
wc_FreeRng(&rng);
|
|
return;
|
|
}
|
|
|
|
/* ---- wc_ecc_sign_hash_ex key-type guard (7672)
|
|
* if (key->type != ECC_PRIVATEKEY && key->type != ECC_PRIVATEKEY_ONLY)
|
|
* A signing key is by construction one of the two accepted types, so the
|
|
* rejecting rows only exist if the type is set by hand. */
|
|
{
|
|
int savedType = key.type;
|
|
mp_int r, s;
|
|
|
|
XMEMSET(&r, 0, sizeof(r));
|
|
XMEMSET(&s, 0, sizeof(s));
|
|
#if defined(HAVE_ECC_SIGN)
|
|
if ((mp_init(&r) == MP_OKAY) && (mp_init(&s) == MP_OKAY)) {
|
|
key.type = ECC_PUBLICKEY; /* (T,T) -> rejected */
|
|
(void)wc_ecc_sign_hash_ex(buf, 32, &rng, &key, &r, &s);
|
|
key.type = ECC_PRIVATEKEY_ONLY; /* (T,F) -> accepted */
|
|
(void)wc_ecc_sign_hash_ex(buf, 32, &rng, &key, &r, &s);
|
|
key.type = ECC_PRIVATEKEY; /* (F,-) -> accepted */
|
|
(void)wc_ecc_sign_hash_ex(buf, 32, &rng, &key, &r, &s);
|
|
mp_free(&s);
|
|
mp_free(&r);
|
|
}
|
|
#endif /* HAVE_ECC_SIGN */
|
|
key.type = savedType;
|
|
}
|
|
|
|
/* ---- wc_ecc_shared_secret private-key-type guard (4792) and the
|
|
* PUBLIC half of the idx/dp chain (4806 idx2/idx3). wb_arg_guards above
|
|
* already drives idx0/idx1 by corrupting the key it passes as BOTH
|
|
* arguments; a separate peer key is needed to leave the private side
|
|
* valid and break only the public one. */
|
|
#ifdef HAVE_ECC_DHE
|
|
{
|
|
int savedType = key.type;
|
|
int savedIdx = peer.idx;
|
|
const ecc_set_type* savedDp = peer.dp;
|
|
|
|
PRIVATE_KEY_UNLOCK();
|
|
|
|
key.type = ECC_PRIVATEKEY; /* 4792 (T,-) */
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&key, &peer, buf, &bufLen);
|
|
key.type = ECC_PRIVATEKEY_ONLY; /* 4792 (F,T) */
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&key, &peer, buf, &bufLen);
|
|
key.type = ECC_PUBLICKEY; /* 4792 (F,F) -> reject */
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&key, &peer, buf, &bufLen);
|
|
key.type = savedType;
|
|
|
|
peer.idx = ECC_CUSTOM_IDX - 1; /* 4806 (F,F,T,-) */
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&key, &peer, buf, &bufLen);
|
|
peer.idx = savedIdx;
|
|
|
|
peer.dp = NULL; /* 4806 (F,F,F,T) */
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&key, &peer, buf, &bufLen);
|
|
peer.dp = savedDp;
|
|
|
|
bufLen = (word32)sizeof(buf); /* 4806 (F,F,F,F) */
|
|
(void)wc_ecc_shared_secret(&key, &peer, buf, &bufLen);
|
|
|
|
PRIVATE_KEY_LOCK();
|
|
}
|
|
#endif /* HAVE_ECC_DHE */
|
|
|
|
/* ---- wc_ecc_make_pub state promotion (5768)
|
|
* if (key->type == ECC_PRIVATEKEY_ONLY && pubOut == NULL)
|
|
* wb_make_pub_privatekey_only supplies (T,T); the (F,-) row needs a key
|
|
* that already holds its public point, i.e. a plain ECC_PRIVATEKEY. */
|
|
#if defined(HAVE_ECC_MAKE_PUB) || !defined(WOLFSSL_ATECC508A)
|
|
{
|
|
int savedType = key.type;
|
|
ecc_point* pubPt = wc_ecc_new_point();
|
|
|
|
/* (T,T): pubOut == NULL. Note ecc_make_pub_ex FORCES
|
|
* key->type = ECC_PRIVATEKEY_ONLY on its way in whenever pubOut is
|
|
* NULL, so this row cannot be varied -- with a NULL pubOut the first
|
|
* operand is always TRUE. */
|
|
(void)wc_ecc_make_pub(&key, NULL);
|
|
|
|
/* (F,-): the only way to reach the guard with the first operand
|
|
* FALSE is a NON-NULL pubOut (which leaves key->type alone) on a key
|
|
* that is a full ECC_PRIVATEKEY. Every API caller passes NULL, which
|
|
* is why this row is missing from the harness. */
|
|
if (pubPt != NULL) {
|
|
key.type = ECC_PRIVATEKEY;
|
|
(void)wc_ecc_make_pub(&key, pubPt);
|
|
wc_ecc_del_point(pubPt);
|
|
}
|
|
key.type = savedType;
|
|
}
|
|
#endif
|
|
|
|
/* ---- _ecc_import_x963_ex2 point-type guard (11485)
|
|
* if (pointType != 4 && pointType != 2 && pointType != 3)
|
|
* Every real X9.63 blob starts 0x04 (or 0x02/0x03 when compressed), so
|
|
* the all-TRUE row needs a hand-made prefix. One call per operand:
|
|
* 0x05 -> (T,T,T) reject, 0x04 -> (F,-), 0x02 -> (T,F,-),
|
|
* 0x03 -> (T,T,F). */
|
|
#ifdef HAVE_ECC_KEY_EXPORT
|
|
{
|
|
word32 xLen = (word32)sizeof(buf);
|
|
|
|
if (wc_ecc_export_x963(&key, buf, &xLen) == 0) {
|
|
static const byte prefixes[4] = { 0x05, 0x04, 0x02, 0x03 };
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
ecc_key imp;
|
|
XMEMSET(&imp, 0, sizeof(imp));
|
|
if (wc_ecc_init(&imp) != 0)
|
|
break;
|
|
buf[0] = prefixes[i];
|
|
(void)_ecc_import_x963_ex2(buf, xLen, &imp, ECC_SECP256R1, 0);
|
|
wc_ecc_free(&imp);
|
|
}
|
|
}
|
|
else {
|
|
WB_NOTE("wc_ecc_export_x963 failed; 11485 vectors skipped");
|
|
}
|
|
}
|
|
#endif /* HAVE_ECC_KEY_EXPORT */
|
|
|
|
/* ---- wc_X963_KDF X9.63 hash whitelist (17023). Five operands, so five
|
|
* accepted algorithms plus one rejected one; only the SHA224 and SHA384
|
|
* rows are missing from ordinary use (the KDF is always driven with
|
|
* SHA-256 in ECIES). */
|
|
#ifdef HAVE_X963_KDF
|
|
{
|
|
static const byte secret[32] = { 0 };
|
|
byte out[32];
|
|
|
|
(void)wc_X963_KDF(WC_HASH_TYPE_SHA, secret, sizeof(secret), NULL, 0,
|
|
out, sizeof(out));
|
|
#ifdef WOLFSSL_SHA224
|
|
(void)wc_X963_KDF(WC_HASH_TYPE_SHA224, secret, sizeof(secret), NULL, 0,
|
|
out, sizeof(out));
|
|
#endif
|
|
(void)wc_X963_KDF(WC_HASH_TYPE_SHA256, secret, sizeof(secret), NULL, 0,
|
|
out, sizeof(out));
|
|
#ifdef WOLFSSL_SHA384
|
|
(void)wc_X963_KDF(WC_HASH_TYPE_SHA384, secret, sizeof(secret), NULL, 0,
|
|
out, sizeof(out));
|
|
#endif
|
|
#ifdef WOLFSSL_SHA512
|
|
(void)wc_X963_KDF(WC_HASH_TYPE_SHA512, secret, sizeof(secret), NULL, 0,
|
|
out, sizeof(out));
|
|
#endif
|
|
/* all five operands TRUE */
|
|
(void)wc_X963_KDF(WC_HASH_TYPE_MD5, secret, sizeof(secret), NULL, 0,
|
|
out, sizeof(out));
|
|
}
|
|
#endif /* HAVE_X963_KDF */
|
|
|
|
/* ---- wc_ecc_is_point coordinate range guards (10836 / 10843)
|
|
* if ((mp_cmp(ecp->x, prime) != MP_LT) || mp_isneg(ecp->x))
|
|
* The public import paths reduce/reject out-of-range coordinates before
|
|
* building a point, so only a hand-built point reaches these with a
|
|
* coordinate >= p. (The mp_isneg operand of each is a separate matter --
|
|
* see the residual note in the harness report.) */
|
|
{
|
|
mp_int prime, af, bf;
|
|
ecc_point* pt = wc_ecc_new_point();
|
|
|
|
XMEMSET(&prime, 0, sizeof(prime));
|
|
XMEMSET(&af, 0, sizeof(af));
|
|
XMEMSET(&bf, 0, sizeof(bf));
|
|
|
|
if ((pt != NULL) && (key.dp != NULL) &&
|
|
(mp_init(&prime) == MP_OKAY) && (mp_init(&af) == MP_OKAY) &&
|
|
(mp_init(&bf) == MP_OKAY) &&
|
|
(mp_read_radix(&prime, key.dp->prime, 16) == MP_OKAY) &&
|
|
(mp_read_radix(&af, key.dp->Af, 16) == MP_OKAY) &&
|
|
(mp_read_radix(&bf, key.dp->Bf, 16) == MP_OKAY) &&
|
|
(wc_ecc_copy_point(&key.pubkey, pt) == MP_OKAY)) {
|
|
|
|
/* all-false row: the key's own (valid, in-range, affine) point */
|
|
(void)wc_ecc_is_point(pt, &af, &bf, &prime);
|
|
|
|
/* x = p -> 10836 (T,-) */
|
|
if (mp_copy(&prime, pt->x) == MP_OKAY)
|
|
(void)wc_ecc_is_point(pt, &af, &bf, &prime);
|
|
|
|
/* restore x, then y = p -> 10843 (T,-) */
|
|
if ((wc_ecc_copy_point(&key.pubkey, pt) == MP_OKAY) &&
|
|
(mp_copy(&prime, pt->y) == MP_OKAY))
|
|
(void)wc_ecc_is_point(pt, &af, &bf, &prime);
|
|
}
|
|
else {
|
|
WB_NOTE("wc_ecc_is_point range vectors skipped (setup)");
|
|
}
|
|
|
|
mp_free(&bf);
|
|
mp_free(&af);
|
|
mp_free(&prime);
|
|
if (pt != NULL)
|
|
wc_ecc_del_point(pt);
|
|
}
|
|
|
|
/* ---- wc_ecc_free custom-curve teardown (8611)
|
|
* if (key->deallocSet && key->dp != NULL)
|
|
* deallocSet is only ever set by wc_ecc_set_custom_curve, which sets dp in
|
|
* the same breath, so the (T,F) row cannot arise from the API. Setting the
|
|
* flag by hand on a dp-less key is safe precisely because the guard is
|
|
* what stops the free. */
|
|
{
|
|
ecc_key k3;
|
|
|
|
/* (T,F): flag set, dp already NULL. Safe precisely because the guard
|
|
* is what stops wc_ecc_free_curve from running. */
|
|
XMEMSET(&k3, 0, sizeof(k3));
|
|
if (wc_ecc_init(&k3) == 0) {
|
|
k3.deallocSet = 1;
|
|
k3.dp = NULL;
|
|
wc_ecc_free(&k3);
|
|
}
|
|
|
|
#ifdef WOLFSSL_CUSTOM_CURVES
|
|
/* (T,T): the shape asn.c builds when it decodes a certificate with an
|
|
* EXPLICIT (specified) curve -- a heap ecc_set_type owned by the key.
|
|
* wc_ecc_free_curve XFREEs each string field only when non-NULL and
|
|
* then the struct itself, so an all-zero record allocated from the
|
|
* same pool is exactly what it expects. No public API produces this
|
|
* state, which is why the row is missing everywhere else. */
|
|
XMEMSET(&k3, 0, sizeof(k3));
|
|
if (wc_ecc_init(&k3) == 0) {
|
|
ecc_set_type* dyn = (ecc_set_type*)XMALLOC(sizeof(*dyn), NULL,
|
|
DYNAMIC_TYPE_ECC_BUFFER);
|
|
if (dyn != NULL) {
|
|
XMEMSET(dyn, 0, sizeof(*dyn));
|
|
k3.dp = dyn;
|
|
k3.deallocSet = 1;
|
|
}
|
|
wc_ecc_free(&k3);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
wc_ecc_free(&peer);
|
|
wc_ecc_free(&key);
|
|
wc_FreeRng(&rng);
|
|
WB_NOTE("pass-2 argument/state gap vectors done");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Custom-curve dispatch guards.
|
|
*
|
|
* ecc.c dispatches to the accelerated single-precision routines with
|
|
* if (key->idx != ECC_CUSTOM_IDX && ecc_sets[key->idx].id == <curve>)
|
|
* repeated for each SP-supported curve (make_pub 5599, uncompress 10221 /
|
|
* 10242, order check 11144 / 11165, on-curve check 11728, plus the pubkey
|
|
* copy at 7314). Nothing in the API test set builds a CUSTOM-curve key, so
|
|
* the first operand is never FALSE; and where only one SP curve is compiled
|
|
* in, the second operand is never FALSE either unless a key on a DIFFERENT
|
|
* standard curve is put through the same call.
|
|
*
|
|
* This runs each of those entry points three times: on a custom curve (idx ==
|
|
* ECC_CUSTOM_IDX), on SECP256R1, and on the largest other standard curve
|
|
* available. The custom curve is a verbatim copy of a standard curve's
|
|
* parameters with oidSz zeroed so wc_ecc_get_curve_idx_from_params cannot
|
|
* fold it back onto the table entry.
|
|
* ------------------------------------------------------------------------- */
|
|
#ifdef WOLFSSL_CUSTOM_CURVES
|
|
static void wb_custom_curve_dispatch(void)
|
|
{
|
|
WC_RNG rng;
|
|
ecc_key ck;
|
|
ecc_key sk;
|
|
ecc_set_type custom;
|
|
const ecc_set_type* base = NULL;
|
|
int idx;
|
|
byte buf[256];
|
|
word32 bufLen;
|
|
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
XMEMSET(&ck, 0, sizeof(ck));
|
|
XMEMSET(&sk, 0, sizeof(sk));
|
|
XMEMSET(&custom, 0, sizeof(custom));
|
|
XMEMSET(buf, 0, sizeof(buf));
|
|
|
|
idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
if (idx < 0) {
|
|
WB_NOTE("SECP256R1 not in ecc_sets[]; custom-curve pass skipped");
|
|
return;
|
|
}
|
|
base = &ecc_sets[idx];
|
|
/* A verbatim copy of a real curve's parameters is enough: what makes this
|
|
* a "custom" key is wc_ecc_set_custom_curve setting key->idx to
|
|
* ECC_CUSTOM_IDX, and wc_ecc_set_curve deliberately leaves that alone on
|
|
* every later call. Every dispatch guard below then short-circuits on its
|
|
* FIRST operand, which is exactly the row that is missing -- and it is
|
|
* also what keeps `ecc_sets[key->idx]` from being indexed with
|
|
* ECC_CUSTOM_IDX. Copying real parameters (rather than inventing them)
|
|
* keeps the key usable, so the calls run to completion instead of
|
|
* bailing out early. */
|
|
custom = *base;
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
WB_NOTE("wc_InitRng failed; custom-curve pass skipped");
|
|
return;
|
|
}
|
|
|
|
if ((wc_ecc_init(&ck) == 0) && (wc_ecc_init(&sk) == 0) &&
|
|
(wc_ecc_set_custom_curve(&ck, &custom) == 0) &&
|
|
(wc_ecc_make_key_ex(&rng, 0, &sk, ECC_SECP256R1) == 0)) {
|
|
|
|
/* wc_ecc_make_key_ex on the custom curve keeps idx == ECC_CUSTOM_IDX
|
|
* and walks 5599 / 7314 with the first operand FALSE. */
|
|
int madeCustom = (wc_ecc_make_key_ex(&rng, base->size, &ck,
|
|
ECC_CURVE_DEF) == 0);
|
|
|
|
if (madeCustom) {
|
|
/* 11144 / 11165 / 11728: full public-key validation. */
|
|
(void)wc_ecc_check_key(&ck);
|
|
|
|
/* 7314: the ECDH/keygen path that copies custom params onto a
|
|
* freshly made pubkey. */
|
|
#ifdef HAVE_ECC_DHE
|
|
PRIVATE_KEY_UNLOCK();
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&ck, &ck, buf, &bufLen);
|
|
PRIVATE_KEY_LOCK();
|
|
#endif
|
|
|
|
#ifdef HAVE_ECC_SIGN
|
|
/* 7314: ecc_sign_hash_sw copies the custom parameters onto the
|
|
* ephemeral pubkey it builds for the nonce point. Only a SIGN with
|
|
* a custom-curve key reaches it. */
|
|
{
|
|
byte dig[32];
|
|
byte sig[ECC_MAX_SIG_SIZE];
|
|
word32 sigSz = (word32)sizeof(sig);
|
|
XMEMSET(dig, 0x9c, sizeof(dig));
|
|
XMEMSET(sig, 0, sizeof(sig));
|
|
(void)wc_ecc_sign_hash(dig, (word32)sizeof(dig), sig, &sigSz,
|
|
&rng, &ck);
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_COMP_KEY) && defined(HAVE_ECC_KEY_EXPORT)
|
|
/* 10221 / 10242: the compressed-point uncompress dispatch. */
|
|
bufLen = (word32)sizeof(buf);
|
|
if (wc_ecc_export_x963_compressed(&ck, buf, &bufLen) == 0) {
|
|
ecc_point* pt = wc_ecc_new_point();
|
|
if (pt != NULL) {
|
|
(void)wc_ecc_import_point_der(buf, bufLen, ck.idx, pt);
|
|
wc_ecc_del_point(pt);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
WB_NOTE("custom-curve key generation failed");
|
|
}
|
|
|
|
/* Same entry points on a STANDARD curve so each decision's (T,T)/(T,F)
|
|
* rows live in this binary next to the (F,-) rows. */
|
|
(void)wc_ecc_check_key(&sk);
|
|
#ifdef HAVE_ECC_SIGN
|
|
{
|
|
byte dig[32];
|
|
byte sig[ECC_MAX_SIG_SIZE];
|
|
word32 sigSz = (word32)sizeof(sig);
|
|
XMEMSET(dig, 0x9c, sizeof(dig));
|
|
XMEMSET(sig, 0, sizeof(sig));
|
|
(void)wc_ecc_sign_hash(dig, (word32)sizeof(dig), sig, &sigSz, &rng,
|
|
&sk);
|
|
}
|
|
#endif
|
|
#ifdef HAVE_ECC_DHE
|
|
PRIVATE_KEY_UNLOCK();
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&sk, &sk, buf, &bufLen);
|
|
PRIVATE_KEY_LOCK();
|
|
#endif
|
|
#if defined(HAVE_COMP_KEY) && defined(HAVE_ECC_KEY_EXPORT)
|
|
bufLen = (word32)sizeof(buf);
|
|
if (wc_ecc_export_x963_compressed(&sk, buf, &bufLen) == 0) {
|
|
ecc_point* pt = wc_ecc_new_point();
|
|
if (pt != NULL) {
|
|
(void)wc_ecc_import_point_der(buf, bufLen, sk.idx, pt);
|
|
wc_ecc_del_point(pt);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* And on a non-SECP256R1 standard curve, which is what makes each
|
|
* dispatch's `ecc_sets[idx].id == <curve>` operand FALSE with the
|
|
* first operand still TRUE. Prefer the largest curve compiled in so
|
|
* the SP_384/SP_521 arms of the same chain are reached too. */
|
|
{
|
|
static const int others[] = {
|
|
#ifdef HAVE_ECC521
|
|
ECC_SECP521R1,
|
|
#endif
|
|
#ifdef HAVE_ECC384
|
|
ECC_SECP384R1,
|
|
#endif
|
|
#ifdef HAVE_ECC224
|
|
ECC_SECP224R1,
|
|
#endif
|
|
ECC_SECP256R1 /* fallback: never absent, adds nothing new */
|
|
};
|
|
size_t i;
|
|
for (i = 0; i < sizeof(others) / sizeof(others[0]); i++) {
|
|
ecc_key ok;
|
|
XMEMSET(&ok, 0, sizeof(ok));
|
|
if (wc_ecc_init(&ok) != 0)
|
|
break;
|
|
if (wc_ecc_make_key_ex(&rng, 0, &ok, (int)others[i]) == 0) {
|
|
(void)wc_ecc_check_key(&ok);
|
|
#ifdef HAVE_ECC_DHE
|
|
PRIVATE_KEY_UNLOCK();
|
|
bufLen = (word32)sizeof(buf);
|
|
(void)wc_ecc_shared_secret(&ok, &ok, buf, &bufLen);
|
|
PRIVATE_KEY_LOCK();
|
|
#endif
|
|
#if defined(HAVE_COMP_KEY) && defined(HAVE_ECC_KEY_EXPORT)
|
|
bufLen = (word32)sizeof(buf);
|
|
if (wc_ecc_export_x963_compressed(&ok, buf, &bufLen) == 0) {
|
|
ecc_point* pt = wc_ecc_new_point();
|
|
if (pt != NULL) {
|
|
(void)wc_ecc_import_point_der(buf, bufLen, ok.idx,
|
|
pt);
|
|
wc_ecc_del_point(pt);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
wc_ecc_free(&ok);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
WB_NOTE("custom-curve setup failed; pass skipped");
|
|
}
|
|
|
|
wc_ecc_free(&sk);
|
|
wc_ecc_free(&ck);
|
|
wc_FreeRng(&rng);
|
|
WB_NOTE("custom-curve dispatch vectors done");
|
|
}
|
|
#else
|
|
static void wb_custom_curve_dispatch(void)
|
|
{
|
|
WB_NOTE("WOLFSSL_CUSTOM_CURVES off; custom-curve dispatch skipped");
|
|
}
|
|
#endif /* WOLFSSL_CUSTOM_CURVES */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* ECIES AES-GCM cluster (15269 / 15809 / 16157 / 16221).
|
|
*
|
|
* ecc_is_gcm() and the two `ret == 0 && [!]ecc_is_gcm(ctx->encAlgo)` guards
|
|
* only separate once the SAME binary runs both a GCM and a non-GCM ECIES
|
|
* exchange; the API suite runs the default AES-128-CBC + HMAC-SHA256 suite
|
|
* only. 16221's ConstantCompare operand additionally needs a decrypt whose
|
|
* MAC does not match, i.e. a deliberately corrupted ciphertext.
|
|
* ------------------------------------------------------------------------- */
|
|
#if defined(HAVE_ECC_ENCRYPT) && defined(HAVE_HKDF) && !defined(NO_AES) && \
|
|
!defined(NO_HMAC)
|
|
static void wb_ecies_algos(void)
|
|
{
|
|
static const byte encAlgos[3] = { ecAES_128_CBC, ecAES_128_GCM,
|
|
ecAES_256_GCM };
|
|
WC_RNG rng;
|
|
ecc_key a;
|
|
ecc_key b;
|
|
byte plain[32];
|
|
byte enc[256];
|
|
byte dec[256];
|
|
size_t i;
|
|
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
XMEMSET(&a, 0, sizeof(a));
|
|
XMEMSET(&b, 0, sizeof(b));
|
|
XMEMSET(plain, 0x5a, sizeof(plain));
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
WB_NOTE("wc_InitRng failed; ECIES pass skipped");
|
|
return;
|
|
}
|
|
|
|
/* One full exchange per (algorithm, corrupt?) pair. Fresh contexts every
|
|
* time: wc_ecc_ctx_set_peer_salt MIXES the two salts in place and advances
|
|
* a state machine that rejects a second call, so a context cannot be
|
|
* reused and the two own-salts must be COPIED OUT before either side is
|
|
* mixed. */
|
|
for (i = 0; i < (sizeof(encAlgos) / sizeof(encAlgos[0])) * 2; i++) {
|
|
byte algo = encAlgos[i / 2];
|
|
int corrupt = (int)(i % 2);
|
|
ecEncCtx* ec = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng);
|
|
ecEncCtx* dc = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng);
|
|
word32 encSz = (word32)sizeof(enc);
|
|
word32 decSz = (word32)sizeof(dec);
|
|
byte saltCli[EXCHANGE_SALT_SZ];
|
|
byte saltSrv[EXCHANGE_SALT_SZ];
|
|
const byte* sp;
|
|
int ok = 0;
|
|
|
|
/* A FRESH key pair every time: in the non-WOLFSSL_ECIES_OLD message
|
|
* format wc_ecc_decrypt imports the sender's ephemeral point into the
|
|
* ecc_key passed as its pubKey argument, which leaves that key a
|
|
* public-only key and makes the next wc_ecc_encrypt reject it. */
|
|
XMEMSET(&a, 0, sizeof(a));
|
|
XMEMSET(&b, 0, sizeof(b));
|
|
if ((wc_ecc_init(&a) != 0) || (wc_ecc_init(&b) != 0) ||
|
|
(wc_ecc_make_key_ex(&rng, 0, &a, ECC_SECP256R1) != 0) ||
|
|
(wc_ecc_make_key_ex(&rng, 0, &b, ECC_SECP256R1) != 0)) {
|
|
WB_NOTE("ECIES key setup failed");
|
|
wc_ecc_free(&b);
|
|
wc_ecc_free(&a);
|
|
wc_ecc_ctx_free(dc);
|
|
wc_ecc_ctx_free(ec);
|
|
break;
|
|
}
|
|
|
|
if ((ec != NULL) && (dc != NULL)) {
|
|
sp = wc_ecc_ctx_get_own_salt(ec);
|
|
if (sp != NULL) {
|
|
XMEMCPY(saltCli, sp, EXCHANGE_SALT_SZ);
|
|
sp = wc_ecc_ctx_get_own_salt(dc);
|
|
if (sp != NULL) {
|
|
XMEMCPY(saltSrv, sp, EXCHANGE_SALT_SZ);
|
|
ok = (wc_ecc_ctx_set_peer_salt(ec, saltSrv) == 0) &&
|
|
(wc_ecc_ctx_set_peer_salt(dc, saltCli) == 0) &&
|
|
(wc_ecc_ctx_set_algo(ec, algo, ecHKDF_SHA256,
|
|
ecHMAC_SHA256) == 0) &&
|
|
(wc_ecc_ctx_set_algo(dc, algo, ecHKDF_SHA256,
|
|
ecHMAC_SHA256) == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok) {
|
|
ok = (wc_ecc_encrypt(&a, &b, plain, (word32)sizeof(plain), enc,
|
|
&encSz, ec) == 0) && (encSz > 0);
|
|
}
|
|
if (ok) {
|
|
if (corrupt) {
|
|
/* 16221: break the authenticator so ConstantCompare (HMAC
|
|
* suites) / the GCM tag check reports a mismatch. */
|
|
enc[encSz - 1] ^= 0xff;
|
|
}
|
|
(void)wc_ecc_decrypt(&b, &a, enc, encSz, dec, &decSz, dc);
|
|
}
|
|
|
|
wc_ecc_ctx_free(dc);
|
|
wc_ecc_ctx_free(ec);
|
|
wc_ecc_free(&b);
|
|
wc_ecc_free(&a);
|
|
}
|
|
|
|
/* The trailing one-byte-message vector needs a live pair of its own. */
|
|
XMEMSET(&a, 0, sizeof(a));
|
|
XMEMSET(&b, 0, sizeof(b));
|
|
if ((wc_ecc_init(&a) != 0) || (wc_ecc_init(&b) != 0) ||
|
|
(wc_ecc_make_key_ex(&rng, 0, &a, ECC_SECP256R1) != 0) ||
|
|
(wc_ecc_make_key_ex(&rng, 0, &b, ECC_SECP256R1) != 0)) {
|
|
WB_NOTE("ECIES tail key setup failed");
|
|
wc_ecc_free(&b);
|
|
wc_ecc_free(&a);
|
|
wc_FreeRng(&rng);
|
|
return;
|
|
}
|
|
|
|
/* 15978 idx0: `(msgSz > 1) && ((msg[0] == 0x02) || (msg[0] == 0x03))`
|
|
* -- a one-byte message is shorter than any real ECIES blob, so only a
|
|
* direct call supplies the FALSE row of the length operand. */
|
|
{
|
|
ecEncCtx* dc = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng);
|
|
word32 decSz = (word32)sizeof(dec);
|
|
byte one = 0x02;
|
|
|
|
if (dc != NULL) {
|
|
(void)wc_ecc_decrypt(&b, &a, &one, 1, dec, &decSz, dc);
|
|
wc_ecc_ctx_free(dc);
|
|
}
|
|
|
|
/* (T,T,-) and (T,F,T): longer than one byte AND a compressed-point
|
|
* prefix, once for each parity tag. Only an ECIES sender that chose
|
|
* point compression emits these, and nothing in the harness does --
|
|
* which also made the 0x03 operand a COIN FLIP before this vector
|
|
* existed: it was only covered when a random ephemeral key happened to
|
|
* have an odd y, so the module's number moved between runs of an
|
|
* unchanged tree. Issuing both tags explicitly pins it. The buffer does
|
|
* not need to decrypt: the decision is reached before any of it is
|
|
* parsed. */
|
|
{
|
|
static const byte tags[2] = { 0x02, 0x03 };
|
|
size_t t;
|
|
|
|
for (t = 0; t < sizeof(tags) / sizeof(tags[0]); t++) {
|
|
dc = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng);
|
|
if (dc != NULL) {
|
|
byte comp[80];
|
|
XMEMSET(comp, 0, sizeof(comp));
|
|
comp[0] = tags[t];
|
|
decSz = (word32)sizeof(dec);
|
|
(void)wc_ecc_decrypt(&b, &a, comp, (word32)sizeof(comp),
|
|
dec, &decSz, dc);
|
|
wc_ecc_ctx_free(dc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wc_ecc_free(&b);
|
|
wc_ecc_free(&a);
|
|
wc_FreeRng(&rng);
|
|
WB_NOTE("ECIES CBC/GCM + corrupted-MAC vectors done");
|
|
}
|
|
#else
|
|
static void wb_ecies_algos(void)
|
|
{
|
|
WB_NOTE("HAVE_ECC_ENCRYPT/HKDF/AES/HMAC off; ECIES pass skipped");
|
|
}
|
|
#endif
|
|
|
|
/* ========================================================================= *
|
|
* Pass 3 -- forged keys and points.
|
|
*
|
|
* What is left after pass 2 is dominated by validation guards that only fire
|
|
* on keys and points that are deliberately WRONG: an ordinate wider than the
|
|
* field prime (or negative modulo it), a point that is not on the curve, a
|
|
* private scalar of 0 / of n / negative, a curve index that disagrees with the
|
|
* key's own parameters. No correct operation can produce those, so rather than
|
|
* hunting for one, the vectors below BUILD the exact shape each guard rejects
|
|
* and hand it straight to the file-static helper that owns the guard -- which
|
|
* this TU can do because it #includes ecc.c.
|
|
*
|
|
* Calling the helpers directly is also what makes the SP-dispatch rows
|
|
* reachable at all: every public entry point (wc_ecc_check_key,
|
|
* wc_ecc_sign_hash, wc_ecc_import_point_der) either returns from an SP fast
|
|
* path before the generic helper runs, or rejects the odd shape earlier, so
|
|
* from the API only ONE side of each dispatch decision is ever taken.
|
|
*
|
|
* Determinism: every value comes from ecc_sets[] -- the curve's own generator
|
|
* and its published prime/order -- or from a small fixed integer. Nothing here
|
|
* depends on live entropy, so the pass contributes identical MC/DC rows on
|
|
* every run.
|
|
*
|
|
* Crash-safety: each forged shape is rejected by the very guard it targets, or
|
|
* (the off-curve / wrong-modulus vectors) is processed as plain modular
|
|
* arithmetic whose result is never dereferenced. Buffers are sized from
|
|
* ecc_sets[].size exactly as the real callers size them.
|
|
* ========================================================================= */
|
|
|
|
/* Curves the pass-3 vectors run over: P-256 (the curve every SP build
|
|
* accelerates) plus the larger and the smaller standard curves, so each
|
|
* `ecc_sets[idx].id == <curve>` dispatch operand gets both a matching and a
|
|
* non-matching key inside one binary. */
|
|
static const int wbCurveIds[] = {
|
|
ECC_SECP256R1,
|
|
ECC_SECP384R1,
|
|
ECC_SECP521R1,
|
|
ECC_SECP224R1
|
|
};
|
|
#define WB_CURVE_COUNT ((int)(sizeof(wbCurveIds) / sizeof(wbCurveIds[0])))
|
|
|
|
/* Curve availability is a RUNTIME question here, not a compile-time one: the
|
|
* per-curve HAVE_ECCnnn macros are only set when a build hand-picks curves,
|
|
* and this suite's configs take the HAVE_ALL_CURVES default instead -- so
|
|
* guarding on them would silently reduce every sweep below to P-256 and leave
|
|
* each `ecc_sets[idx].id == <curve>` operand permanently TRUE. Asking the
|
|
* table is correct for both kinds of build. */
|
|
static int wb_curve_present(int curveId)
|
|
{
|
|
return wc_ecc_get_curve_idx(curveId) >= 0;
|
|
}
|
|
|
|
/* Make *a hold -1, but only when the math backend can represent a negative
|
|
* mp_int; returns 1 when it did.
|
|
*
|
|
* Under SP math without WOLFSSL_SP_INT_NEGATIVE, mp_isneg() expands to the
|
|
* literal (0) and no mp_int is ever negative, so the mp_isneg operand of every
|
|
* range guard is dead there and each caller simply skips its negative vector.
|
|
* The fastmath variant links tfm, where sign is a real field, and that is the
|
|
* build in which these rows are produced. The value is built in a scratch
|
|
* mp_int and only copied over on success, so a backend that refuses the
|
|
* subtraction leaves the caller's operand untouched. */
|
|
static int wb_set_neg_one(mp_int* a)
|
|
{
|
|
int ok = 0;
|
|
mp_int t[2];
|
|
|
|
XMEMSET(t, 0, sizeof(t));
|
|
if (mp_init_multi(&t[0], &t[1], NULL, NULL, NULL, NULL) != MP_OKAY)
|
|
return 0;
|
|
if ((mp_set(&t[0], 0) == MP_OKAY) && (mp_set(&t[1], 1) == MP_OKAY) &&
|
|
(mp_sub(&t[0], &t[1], &t[0]) == MP_OKAY) &&
|
|
(mp_isneg(&t[0]) != 0) && (mp_copy(&t[0], a) == MP_OKAY)) {
|
|
ok = 1;
|
|
}
|
|
mp_free(&t[1]);
|
|
mp_free(&t[0]);
|
|
return ok;
|
|
}
|
|
|
|
/* Reset pt to the curve's own generator: a valid, in-range, affine point of
|
|
* full order -- the "all guards pass" half of every vector below. */
|
|
static int wb_point_from_generator(ecc_point* pt, ecc_curve_spec* curve)
|
|
{
|
|
int err = mp_copy(curve->Gx, pt->x);
|
|
if (err == MP_OKAY)
|
|
err = mp_copy(curve->Gy, pt->y);
|
|
if (err == MP_OKAY)
|
|
err = mp_set(pt->z, 1);
|
|
return err;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* ecc_check_pubkey_order() -- 11131 (ordinate wider than the modulus),
|
|
* 11144 / 11165 (the SP order-multiply dispatch) and 11174 (order*Q really is
|
|
* the identity).
|
|
*
|
|
* Reached from wc_ecc_check_key, which for an SP-supported curve returns from
|
|
* sp_ecc_check_key_<n>() long before this helper, and which no test drives
|
|
* with a CUSTOM-curve key -- so from the API the `key->idx != ECC_CUSTOM_IDX`
|
|
* operand is never FALSE and the `id == <curve>` operand never separates.
|
|
* ------------------------------------------------------------------------- */
|
|
#if defined(HAVE_ECC_CHECK_PUBKEY_ORDER) && !defined(WOLFSSL_SP_MATH)
|
|
static void wb_forged_pubkey_order(void)
|
|
{
|
|
int c;
|
|
|
|
for (c = 0; c < WB_CURVE_COUNT; c++) {
|
|
DECLARE_CURVE_SPECS(ECC_CURVE_FIELD_COUNT);
|
|
ecc_key key;
|
|
ecc_point* pt = NULL;
|
|
mp_int tmp[1];
|
|
int err = MP_OKAY;
|
|
int savedIdx;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(tmp, 0, sizeof(tmp));
|
|
if (!wb_curve_present(wbCurveIds[c]))
|
|
continue;
|
|
if (wc_ecc_init(&key) != 0)
|
|
continue;
|
|
if (wc_ecc_set_curve(&key, 0, wbCurveIds[c]) != 0) {
|
|
wc_ecc_free(&key);
|
|
continue;
|
|
}
|
|
ALLOC_CURVE_SPECS(ECC_CURVE_FIELD_COUNT, err);
|
|
if (err == MP_OKAY)
|
|
err = wc_ecc_curve_load(key.dp, &curve, ECC_CURVE_FIELD_ALL);
|
|
if (err == MP_OKAY)
|
|
err = mp_init(tmp);
|
|
pt = wc_ecc_new_point();
|
|
if ((err != MP_OKAY) || (pt == NULL)) {
|
|
WB_NOTE("pubkey-order setup failed; curve skipped");
|
|
wb_fail = 1;
|
|
if (pt != NULL)
|
|
wc_ecc_del_point(pt);
|
|
mp_free(tmp);
|
|
if (err == MP_OKAY)
|
|
wc_ecc_curve_free(curve);
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
continue;
|
|
}
|
|
|
|
/* 11131 (F,F,F) + 11144/11165 (T,T) or (T,F) + 11174 (T,F): the
|
|
* curve's own generator, whose order-multiple IS the identity. */
|
|
if (wb_point_from_generator(pt, curve) == MP_OKAY)
|
|
(void)ecc_check_pubkey_order(&key, pt, curve->Af, curve->prime,
|
|
curve->order);
|
|
|
|
/* 11131 (T,-,-) / (F,T,-) / (F,F,T): one ordinate two times the
|
|
* modulus, i.e. one bit wider than it. Rejected before any point math
|
|
* runs, so an ordinate that is not a field element is never used. */
|
|
if ((mp_copy(curve->prime, tmp) == MP_OKAY) &&
|
|
(mp_add(tmp, tmp, tmp) == MP_OKAY)) {
|
|
if ((wb_point_from_generator(pt, curve) == MP_OKAY) &&
|
|
(mp_copy(tmp, pt->x) == MP_OKAY))
|
|
(void)ecc_check_pubkey_order(&key, pt, curve->Af, curve->prime,
|
|
curve->order);
|
|
if ((wb_point_from_generator(pt, curve) == MP_OKAY) &&
|
|
(mp_copy(tmp, pt->y) == MP_OKAY))
|
|
(void)ecc_check_pubkey_order(&key, pt, curve->Af, curve->prime,
|
|
curve->order);
|
|
if ((wb_point_from_generator(pt, curve) == MP_OKAY) &&
|
|
(mp_copy(tmp, pt->z) == MP_OKAY))
|
|
(void)ecc_check_pubkey_order(&key, pt, curve->Af, curve->prime,
|
|
curve->order);
|
|
}
|
|
|
|
/* 11144 / 11165 (F,-): a key whose index says "custom" while its dp
|
|
* still points at a real curve. That is exactly the shape
|
|
* wc_ecc_set_custom_curve() leaves behind, and it is the only way the
|
|
* first operand of each SP dispatch is FALSE. */
|
|
savedIdx = key.idx;
|
|
key.idx = ECC_CUSTOM_IDX;
|
|
|
|
if (wb_point_from_generator(pt, curve) == MP_OKAY)
|
|
(void)ecc_check_pubkey_order(&key, pt, curve->Af, curve->prime,
|
|
curve->order);
|
|
|
|
/* 11174 (T,T): a point that is NOT on the curve, so order*Q is not the
|
|
* identity and the guard rejects it. Kept on the custom index so the
|
|
* generic multiply -- the one this guard follows -- is what runs. */
|
|
if ((wb_point_from_generator(pt, curve) == MP_OKAY) &&
|
|
(mp_add_d(pt->y, 1, pt->y) == MP_OKAY))
|
|
(void)ecc_check_pubkey_order(&key, pt, curve->Af, curve->prime,
|
|
curve->order);
|
|
|
|
/* 11174 (F,-): prime+1 is even, so the Montgomery setup inside the
|
|
* multiply refuses it and the guard is reached with err already set.
|
|
* Still one bit-count wider than every ordinate, so the 11131 guard
|
|
* above lets it through to here. */
|
|
if ((wb_point_from_generator(pt, curve) == MP_OKAY) &&
|
|
(mp_copy(curve->prime, tmp) == MP_OKAY) &&
|
|
(mp_add_d(tmp, 1, tmp) == MP_OKAY))
|
|
(void)ecc_check_pubkey_order(&key, pt, curve->Af, tmp,
|
|
curve->order);
|
|
|
|
key.idx = savedIdx;
|
|
|
|
wc_ecc_del_point(pt);
|
|
mp_free(tmp);
|
|
wc_ecc_curve_free(curve);
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
}
|
|
WB_NOTE("forged pubkey-order vectors done");
|
|
}
|
|
#else
|
|
static void wb_forged_pubkey_order(void)
|
|
{
|
|
WB_NOTE("pubkey-order check not compiled in; forged vectors skipped");
|
|
}
|
|
#endif /* HAVE_ECC_CHECK_PUBKEY_ORDER && !WOLFSSL_SP_MATH */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* ecc_make_pub_sw() -- 5553 (private scalar out of range) and 5599 (the
|
|
* base-point-multiply SP dispatch).
|
|
*
|
|
* Every public path into this helper generates or imports a scalar that has
|
|
* already been range-checked, so the three operands of the range guard are
|
|
* never TRUE; and the dispatch chain is only entered for keys whose curve the
|
|
* public caller already resolved.
|
|
* ------------------------------------------------------------------------- */
|
|
#if defined(HAVE_ECC_MAKE_PUB) && !defined(WOLF_CRYPTO_CB_ONLY_ECC)
|
|
static void wb_forged_make_pub_sw(void)
|
|
{
|
|
int c;
|
|
WC_RNG deadRng;
|
|
|
|
/* A never-initialized WC_RNG: its status is WC_DRBG_NOT_INIT, so every
|
|
* wc_RNG_GenerateBlock() through it fails. That is the lever for the
|
|
* "err is not MP_OKAY" halves inside ecc_mulmod()'s z-randomization and
|
|
* for the `err == MP_OKAY && map` guard that follows the multiply --
|
|
* a computation failure no allocation fault can produce. */
|
|
XMEMSET(&deadRng, 0, sizeof(deadRng));
|
|
|
|
for (c = 0; c < WB_CURVE_COUNT; c++) {
|
|
DECLARE_CURVE_SPECS(ECC_CURVE_FIELD_COUNT);
|
|
ecc_key key;
|
|
ecc_point* pub = NULL;
|
|
int err = MP_OKAY;
|
|
int savedIdx;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
if (!wb_curve_present(wbCurveIds[c]))
|
|
continue;
|
|
if (wc_ecc_init(&key) != 0)
|
|
continue;
|
|
if (wc_ecc_set_curve(&key, 0, wbCurveIds[c]) != 0) {
|
|
wc_ecc_free(&key);
|
|
continue;
|
|
}
|
|
ALLOC_CURVE_SPECS(ECC_CURVE_FIELD_COUNT, err);
|
|
if (err == MP_OKAY)
|
|
err = wc_ecc_curve_load(key.dp, &curve, ECC_CURVE_FIELD_ALL);
|
|
pub = wc_ecc_new_point();
|
|
if ((err != MP_OKAY) || (pub == NULL)) {
|
|
WB_NOTE("make-pub-sw setup failed; curve skipped");
|
|
wb_fail = 1;
|
|
if (pub != NULL)
|
|
wc_ecc_del_point(pub);
|
|
if (err == MP_OKAY)
|
|
wc_ecc_curve_free(curve);
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
continue;
|
|
}
|
|
savedIdx = key.idx;
|
|
|
|
/* 5553 (F,F,F) + 5599 (T,T) or (T,F): a scalar of 1, so the result is
|
|
* the generator itself. */
|
|
if (mp_set(ecc_get_k(&key), 1) == MP_OKAY)
|
|
(void)ecc_make_pub_sw(&key, curve, pub, NULL);
|
|
|
|
/* 5599 (F,-): same scalar, "custom" index -- the generic multiply. */
|
|
key.idx = ECC_CUSTOM_IDX;
|
|
if (mp_set(ecc_get_k(&key), 1) == MP_OKAY)
|
|
(void)ecc_make_pub_sw(&key, curve, pub, NULL);
|
|
|
|
/* 3296/3298/3314 and the `err == MP_OKAY && map` guards after the
|
|
* multiply: a dead RNG makes the z-randomization fail part-way, which
|
|
* is the only way those success-chain operands go FALSE. */
|
|
if (mp_set(ecc_get_k(&key), 1) == MP_OKAY)
|
|
(void)ecc_make_pub_sw(&key, curve, pub, &deadRng);
|
|
key.idx = savedIdx;
|
|
|
|
/* 5553 (T,-,-): the scalar zero. */
|
|
if (mp_set(ecc_get_k(&key), 0) == MP_OKAY)
|
|
(void)ecc_make_pub_sw(&key, curve, pub, NULL);
|
|
|
|
/* 5553 (F,F,T): the scalar n -- in range for the field, out of range
|
|
* for the group. */
|
|
if (mp_copy(curve->order, ecc_get_k(&key)) == MP_OKAY)
|
|
(void)ecc_make_pub_sw(&key, curve, pub, NULL);
|
|
|
|
/* 5553 (F,T,-): a negative scalar (fastmath/tfm only). */
|
|
if (wb_set_neg_one(ecc_get_k(&key)))
|
|
(void)ecc_make_pub_sw(&key, curve, pub, NULL);
|
|
|
|
(void)mp_set(ecc_get_k(&key), 1);
|
|
wc_ecc_del_point(pub);
|
|
wc_ecc_curve_free(curve);
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
}
|
|
WB_NOTE("forged make-pub-sw vectors done");
|
|
}
|
|
#else
|
|
static void wb_forged_make_pub_sw(void)
|
|
{
|
|
WB_NOTE("HAVE_ECC_MAKE_PUB off; forged make-pub-sw vectors skipped");
|
|
}
|
|
#endif /* HAVE_ECC_MAKE_PUB && !WOLF_CRYPTO_CB_ONLY_ECC */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* _ecc_validate_public_key() -- 11343 / 11351 (negative ordinate) and 11374
|
|
* (the five-operand private-scalar bound check).
|
|
*
|
|
* wc_ecc_check_key() hands an SP-supported curve to sp_ecc_check_key_<n>()
|
|
* and returns, so on the accelerated variants this body is only entered for a
|
|
* key whose index says "custom". Forcing that index is therefore what makes
|
|
* the whole decision reachable, and hand-set scalars/ordinates are what
|
|
* separate its operands.
|
|
* ------------------------------------------------------------------------- */
|
|
#if defined(HAVE_ECC_CHECK_PUBKEY_ORDER) && !defined(WOLFSSL_SP_MATH)
|
|
static void wb_forged_validate_public_key(void)
|
|
{
|
|
DECLARE_CURVE_SPECS(ECC_CURVE_FIELD_COUNT);
|
|
ecc_key key;
|
|
int err = MP_OKAY;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
if (wc_ecc_init(&key) != 0) {
|
|
WB_NOTE("validate-public-key init failed");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
if (wc_ecc_set_curve(&key, 0, ECC_SECP256R1) != 0) {
|
|
wc_ecc_free(&key);
|
|
WB_NOTE("validate-public-key set_curve failed");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
ALLOC_CURVE_SPECS(ECC_CURVE_FIELD_COUNT, err);
|
|
if (err == MP_OKAY)
|
|
err = wc_ecc_curve_load(key.dp, &curve, ECC_CURVE_FIELD_ALL);
|
|
if (err != MP_OKAY) {
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
WB_NOTE("validate-public-key curve load failed");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
|
|
/* A consistent private key built entirely from the curve table: Q = G and
|
|
* d = 1. dp keeps pointing at the real curve; only the index is forced to
|
|
* "custom" so the generic validation body runs on every variant. */
|
|
if ((wb_point_from_generator(&key.pubkey, curve) == MP_OKAY) &&
|
|
(mp_set(ecc_get_k(&key), 1) == MP_OKAY)) {
|
|
key.idx = ECC_CUSTOM_IDX;
|
|
key.type = ECC_PRIVATEKEY;
|
|
|
|
/* 11374 (T,T,F,F,F): everything in range. partial = 1 keeps the
|
|
* (separately covered) order multiply out of the way. */
|
|
(void)_ecc_validate_public_key(&key, 1, 1);
|
|
|
|
/* 11374 (T,F,-,-,-): a key that carries no cached public point is
|
|
* still asked for the private-side check. */
|
|
key.type = ECC_PRIVATEKEY_ONLY;
|
|
(void)_ecc_validate_public_key(&key, 1, 1);
|
|
key.type = ECC_PRIVATEKEY;
|
|
|
|
/* 11374 (T,T,T,-,-): the scalar zero. */
|
|
if (mp_set(ecc_get_k(&key), 0) == MP_OKAY)
|
|
(void)_ecc_validate_public_key(&key, 1, 1);
|
|
|
|
/* 11374 (T,T,F,F,T): the scalar n. */
|
|
if (mp_copy(curve->order, ecc_get_k(&key)) == MP_OKAY)
|
|
(void)_ecc_validate_public_key(&key, 1, 1);
|
|
|
|
/* 11374 (T,T,F,T,-): a negative scalar (fastmath/tfm only). */
|
|
if (wb_set_neg_one(ecc_get_k(&key)))
|
|
(void)_ecc_validate_public_key(&key, 1, 1);
|
|
|
|
/* 11374 (F,-,-,-,-): a public point one unit off the curve, so the
|
|
* on-curve check leaves err set before the private-side guard. */
|
|
if ((mp_set(ecc_get_k(&key), 1) == MP_OKAY) &&
|
|
(mp_add_d(key.pubkey.y, 1, key.pubkey.y) == MP_OKAY))
|
|
(void)_ecc_validate_public_key(&key, 1, 1);
|
|
|
|
/* 11343 (F,T) / 11351 (F,T): a NEGATIVE ordinate compares less-than
|
|
* the prime, so only the mp_isneg operand can reject it (fastmath). */
|
|
if (wb_point_from_generator(&key.pubkey, curve) == MP_OKAY) {
|
|
if (wb_set_neg_one(key.pubkey.x))
|
|
(void)_ecc_validate_public_key(&key, 1, 0);
|
|
}
|
|
if (wb_point_from_generator(&key.pubkey, curve) == MP_OKAY) {
|
|
if (wb_set_neg_one(key.pubkey.y))
|
|
(void)_ecc_validate_public_key(&key, 1, 0);
|
|
}
|
|
(void)wb_point_from_generator(&key.pubkey, curve);
|
|
}
|
|
else {
|
|
WB_NOTE("validate-public-key vector setup failed");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
wc_ecc_curve_free(curve);
|
|
FREE_CURVE_SPECS();
|
|
key.idx = 0;
|
|
wc_ecc_free(&key);
|
|
WB_NOTE("forged validate-public-key vectors done");
|
|
}
|
|
#else
|
|
static void wb_forged_validate_public_key(void)
|
|
{
|
|
WB_NOTE("public-key validation not compiled in; forged vectors skipped");
|
|
}
|
|
#endif /* HAVE_ECC_CHECK_PUBKEY_ORDER && !WOLFSSL_SP_MATH */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* wc_ecc_is_point() -- 10836 / 10843 mp_isneg halves.
|
|
*
|
|
* pass 2 supplied the x == p and y == p rows (the mp_cmp operand). The second
|
|
* operand of each needs an ordinate that is LESS than the prime and still
|
|
* invalid, which only a negative value is -- unrepresentable under SP math,
|
|
* real under tfm.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_forged_is_point_neg(void)
|
|
{
|
|
DECLARE_CURVE_SPECS(ECC_CURVE_FIELD_COUNT);
|
|
ecc_point* pt = NULL;
|
|
ecc_key key;
|
|
int err = MP_OKAY;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
if (wc_ecc_init(&key) != 0)
|
|
return;
|
|
if (wc_ecc_set_curve(&key, 0, ECC_SECP256R1) != 0) {
|
|
wc_ecc_free(&key);
|
|
return;
|
|
}
|
|
ALLOC_CURVE_SPECS(ECC_CURVE_FIELD_COUNT, err);
|
|
if (err == MP_OKAY)
|
|
err = wc_ecc_curve_load(key.dp, &curve, ECC_CURVE_FIELD_ALL);
|
|
pt = wc_ecc_new_point();
|
|
if ((err == MP_OKAY) && (pt != NULL)) {
|
|
/* (F,F) reference row, then a negative x, then a negative y. */
|
|
if (wb_point_from_generator(pt, curve) == MP_OKAY)
|
|
(void)wc_ecc_is_point(pt, curve->Af, curve->Bf, curve->prime);
|
|
if ((wb_point_from_generator(pt, curve) == MP_OKAY) &&
|
|
wb_set_neg_one(pt->x))
|
|
(void)wc_ecc_is_point(pt, curve->Af, curve->Bf, curve->prime);
|
|
if ((wb_point_from_generator(pt, curve) == MP_OKAY) &&
|
|
wb_set_neg_one(pt->y))
|
|
(void)wc_ecc_is_point(pt, curve->Af, curve->Bf, curve->prime);
|
|
}
|
|
if (pt != NULL)
|
|
wc_ecc_del_point(pt);
|
|
if (err == MP_OKAY)
|
|
wc_ecc_curve_free(curve);
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
WB_NOTE("forged is-point range vectors done");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Point decompression -- 10221 / 10242 (the SP uncompress dispatch), 10325
|
|
* and 11611 (the sqrt-parity adjust).
|
|
*
|
|
* wc_ecc_import_point_der() passes shortKeySize = 1, which makes the
|
|
* compressed branch compute keysize = inLen/2 and bail with a size mismatch
|
|
* BEFORE the decompression runs -- which is why every compressed vector in the
|
|
* API suite (and in pass 2) stops short of these decisions. Calling
|
|
* wc_ecc_import_point_der_ex() with shortKeySize = 0 is the documented way to
|
|
* import a compressed point, and is what gets past it.
|
|
*
|
|
* The parity adjust needs BOTH a square root that is odd and one that is even,
|
|
* against BOTH the 0x02 and 0x03 prefixes. Rather than depend on a generated
|
|
* key's y (whose parity is a coin flip -- a genuine source of run-to-run
|
|
* variation in this file's numbers), the vectors sweep a fixed list of small
|
|
* x values: those that are on the curve yield a deterministic root, and the
|
|
* list is long enough to contain both parities on every curve.
|
|
*
|
|
* Note the third operand, `mp_isodd(t2) == MP_NO`, is the exact complement of
|
|
* the first, `mp_isodd(t2) == MP_YES`: no pair of test vectors can vary one
|
|
* while holding the other fixed, so it has no independence pair by
|
|
* construction (see RESIDUALS).
|
|
* ------------------------------------------------------------------------- */
|
|
#ifdef HAVE_COMP_KEY
|
|
static void wb_uncompress_parity(void)
|
|
{
|
|
byte blob[1 + ECC_MAXSIZE];
|
|
int c;
|
|
|
|
for (c = 0; c < WB_CURVE_COUNT; c++) {
|
|
int idx = wc_ecc_get_curve_idx(wbCurveIds[c]);
|
|
int size;
|
|
int xv;
|
|
|
|
if (idx < 0)
|
|
continue;
|
|
size = ecc_sets[idx].size;
|
|
if ((size <= 0) || (size > ECC_MAXSIZE))
|
|
continue;
|
|
|
|
for (xv = 1; xv <= 12; xv++) {
|
|
int p;
|
|
|
|
for (p = 0; p < 2; p++) {
|
|
ecc_point* pt;
|
|
ecc_key ik;
|
|
|
|
XMEMSET(blob, 0, sizeof(blob));
|
|
blob[0] = (p == 0) ? ECC_POINT_COMP_EVEN : ECC_POINT_COMP_ODD;
|
|
blob[size] = (byte)xv; /* x = xv, big-endian, size bytes */
|
|
|
|
pt = wc_ecc_new_point();
|
|
if (pt != NULL) {
|
|
(void)wc_ecc_import_point_der_ex(blob, (word32)size + 1,
|
|
idx, pt, 0);
|
|
wc_ecc_del_point(pt);
|
|
}
|
|
|
|
/* Same blob through the key importer, whose own copy of the
|
|
* parity adjust is 11611. */
|
|
XMEMSET(&ik, 0, sizeof(ik));
|
|
if (wc_ecc_init(&ik) == 0) {
|
|
(void)_ecc_import_x963_ex2(blob, (word32)size + 1, &ik,
|
|
wbCurveIds[c], 0);
|
|
wc_ecc_free(&ik);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
WB_NOTE("compressed-point parity vectors done");
|
|
}
|
|
#else
|
|
static void wb_uncompress_parity(void)
|
|
{
|
|
WB_NOTE("HAVE_COMP_KEY off; compressed-point parity vectors skipped");
|
|
}
|
|
#endif /* HAVE_COMP_KEY */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* _ecc_import_x963_ex2() untrusted branch -- 11728.
|
|
*
|
|
* The first operand only goes FALSE when the imported point is the identity,
|
|
* because the line above sets err in exactly that case; an all-zero X9.63 blob
|
|
* is that point. The second needs a key whose index says "custom", which no
|
|
* X9.63 import produces on its own.
|
|
* ------------------------------------------------------------------------- */
|
|
#ifdef HAVE_ECC_KEY_IMPORT
|
|
static void wb_untrusted_import(void)
|
|
{
|
|
byte blob[1 + 2 * ECC_MAXSIZE];
|
|
int idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
int size;
|
|
|
|
if (idx < 0) {
|
|
WB_NOTE("SECP256R1 absent; untrusted-import vectors skipped");
|
|
return;
|
|
}
|
|
size = ecc_sets[idx].size;
|
|
if ((size <= 0) || (size > ECC_MAXSIZE))
|
|
return;
|
|
|
|
/* (F,-): x = y = 0 is the point at infinity, rejected one line earlier. */
|
|
{
|
|
ecc_key ik;
|
|
XMEMSET(blob, 0, sizeof(blob));
|
|
blob[0] = ECC_POINT_UNCOMP;
|
|
XMEMSET(&ik, 0, sizeof(ik));
|
|
if (wc_ecc_init(&ik) == 0) {
|
|
(void)_ecc_import_x963_ex2(blob, (word32)(2 * size + 1), &ik,
|
|
ECC_SECP256R1, 1);
|
|
wc_ecc_free(&ik);
|
|
}
|
|
}
|
|
|
|
/* (T,T) and (T,F): the generator, imported into a plain key and into a key
|
|
* that already carries a custom curve. */
|
|
{
|
|
DECLARE_CURVE_SPECS(2);
|
|
ecc_key ik;
|
|
int err = MP_OKAY;
|
|
|
|
ALLOC_CURVE_SPECS(2, err);
|
|
if (err == MP_OKAY)
|
|
err = wc_ecc_curve_load(&ecc_sets[idx], &curve,
|
|
ECC_CURVE_FIELD_GX | ECC_CURVE_FIELD_GY);
|
|
if (err == MP_OKAY) {
|
|
XMEMSET(blob, 0, sizeof(blob));
|
|
blob[0] = ECC_POINT_UNCOMP;
|
|
if ((mp_to_unsigned_bin_len(curve->Gx, blob + 1, size)
|
|
== MP_OKAY) &&
|
|
(mp_to_unsigned_bin_len(curve->Gy, blob + 1 + size, size)
|
|
== MP_OKAY)) {
|
|
XMEMSET(&ik, 0, sizeof(ik));
|
|
if (wc_ecc_init(&ik) == 0) {
|
|
(void)_ecc_import_x963_ex2(blob, (word32)(2 * size + 1),
|
|
&ik, ECC_SECP256R1, 1);
|
|
wc_ecc_free(&ik);
|
|
}
|
|
#ifdef WOLFSSL_CUSTOM_CURVES
|
|
XMEMSET(&ik, 0, sizeof(ik));
|
|
if (wc_ecc_init(&ik) == 0) {
|
|
if (wc_ecc_set_custom_curve(&ik, &ecc_sets[idx]) == 0)
|
|
(void)_ecc_import_x963_ex2(blob,
|
|
(word32)(2 * size + 1), &ik,
|
|
ECC_CURVE_DEF, 1);
|
|
ik.dp = NULL; /* borrowed from ecc_sets[]; not ours */
|
|
wc_ecc_free(&ik);
|
|
}
|
|
#endif
|
|
}
|
|
wc_ecc_curve_free(curve);
|
|
}
|
|
FREE_CURVE_SPECS();
|
|
}
|
|
WB_NOTE("untrusted-import vectors done");
|
|
}
|
|
#else
|
|
static void wb_untrusted_import(void)
|
|
{
|
|
WB_NOTE("HAVE_ECC_KEY_IMPORT off; untrusted-import vectors skipped");
|
|
}
|
|
#endif /* HAVE_ECC_KEY_IMPORT */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Negative-value guards on the public import/convert helpers -- 12426
|
|
* (wc_ecc_rs_to_sig) and 12745 (_ecc_import_raw_private).
|
|
*
|
|
* Both read their operand from a hex STRING, and tfm's mp_read_radix accepts a
|
|
* leading '-'. That is the only way a negative scalar / signature component
|
|
* enters ecc.c at all; under SP math the read simply fails and the guard is
|
|
* skipped, which is why these rows only exist in the fastmath variant.
|
|
* ------------------------------------------------------------------------- */
|
|
static void wb_forged_negative_strings(void)
|
|
{
|
|
#ifdef HAVE_ECC_SIGN
|
|
{
|
|
byte out[128];
|
|
word32 outLen;
|
|
|
|
outLen = (word32)sizeof(out);
|
|
(void)wc_ecc_rs_to_sig("1", "2", out, &outLen); /* (F,F) */
|
|
outLen = (word32)sizeof(out);
|
|
(void)wc_ecc_rs_to_sig("-1", "2", out, &outLen); /* (T,-) */
|
|
outLen = (word32)sizeof(out);
|
|
(void)wc_ecc_rs_to_sig("1", "-2", out, &outLen); /* (F,T) */
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_ECC_KEY_IMPORT) && !defined(WOLFSSL_ECC_CURVE_STATIC)
|
|
{
|
|
int idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
|
|
if (idx >= 0) {
|
|
static const char* const privs[3] = { "1", "0", "-1" };
|
|
int i;
|
|
|
|
/* (F,F), then (T,-) via a zero scalar, then (F,T) via a negative
|
|
* one. Public point is the curve's own generator, so nothing but
|
|
* the scalar is out of the ordinary. */
|
|
for (i = 0; i < 3; i++) {
|
|
ecc_key ik;
|
|
XMEMSET(&ik, 0, sizeof(ik));
|
|
if (wc_ecc_init(&ik) != 0)
|
|
continue;
|
|
(void)wc_ecc_import_raw(&ik, ecc_sets[idx].Gx,
|
|
ecc_sets[idx].Gy, privs[i], ecc_sets[idx].name);
|
|
wc_ecc_free(&ik);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
WB_NOTE("negative-string import vectors done");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* ecc_check_order_minus_1() -- 3941.
|
|
*
|
|
* Compiled only where the fixed-point cache is off (the no_fp_shamir variant).
|
|
* Both operands reject a curve whose order or modulus is wider than the build
|
|
* supports; no entry in ecc_sets[] is, so only a hand-built mp_int gets there.
|
|
* The guard returns before the points are touched.
|
|
* ------------------------------------------------------------------------- */
|
|
#if !defined(FP_ECC) && !defined(WOLFSSL_SP_MATH) && defined(ECC_TIMING_RESISTANT)
|
|
static void wb_forged_order_minus_1(void)
|
|
{
|
|
DECLARE_CURVE_SPECS(ECC_CURVE_FIELD_COUNT);
|
|
ecc_key key;
|
|
ecc_point* tG = NULL;
|
|
ecc_point* R = NULL;
|
|
mp_int huge[1];
|
|
int err = MP_OKAY;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(huge, 0, sizeof(huge));
|
|
if (wc_ecc_init(&key) != 0)
|
|
return;
|
|
if (wc_ecc_set_curve(&key, 0, ECC_SECP256R1) != 0) {
|
|
wc_ecc_free(&key);
|
|
return;
|
|
}
|
|
ALLOC_CURVE_SPECS(ECC_CURVE_FIELD_COUNT, err);
|
|
if (err == MP_OKAY)
|
|
err = wc_ecc_curve_load(key.dp, &curve, ECC_CURVE_FIELD_ALL);
|
|
if (err == MP_OKAY)
|
|
err = mp_init(huge);
|
|
tG = wc_ecc_new_point();
|
|
R = wc_ecc_new_point();
|
|
|
|
if ((err == MP_OKAY) && (tG != NULL) && (R != NULL) &&
|
|
(wb_point_from_generator(tG, curve) == MP_OKAY) &&
|
|
(mp_set(huge, 0) == MP_OKAY) &&
|
|
(mp_set_bit(huge, MAX_ECC_BITS_USE + 64) == MP_OKAY)) {
|
|
/* (T,-): an order wider than the build's maximum. */
|
|
(void)ecc_check_order_minus_1(curve->order, tG, R, curve->prime, huge);
|
|
/* (F,T): the order is fine, the modulus is not. */
|
|
(void)ecc_check_order_minus_1(curve->order, tG, R, huge, curve->order);
|
|
}
|
|
else if (err == MP_OKAY) {
|
|
WB_NOTE("order-minus-1 vector setup failed");
|
|
}
|
|
|
|
if (R != NULL)
|
|
wc_ecc_del_point(R);
|
|
if (tG != NULL)
|
|
wc_ecc_del_point(tG);
|
|
mp_free(huge);
|
|
if (err == MP_OKAY)
|
|
wc_ecc_curve_free(curve);
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
WB_NOTE("forged order-minus-1 vectors done");
|
|
}
|
|
#else
|
|
static void wb_forged_order_minus_1(void)
|
|
{
|
|
WB_NOTE("ecc_check_order_minus_1 not compiled in; vectors skipped");
|
|
}
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* ecc_sign_hash_sw() custom-curve pubkey fixup -- 7314.
|
|
*
|
|
* wc_ecc_sign_hash() returns from sp_ecc_sign_<n>() for every SP-supported
|
|
* curve, so on the accelerated variants the ONLY key that reaches this helper
|
|
* through the API is a custom-curve one -- the second operand never separates.
|
|
* Calling the helper directly with the same key on both indices supplies both
|
|
* rows. dp keeps pointing at a real curve either way, so the ephemeral key the
|
|
* signer builds from dp->id is a normal one.
|
|
* ------------------------------------------------------------------------- */
|
|
#if defined(HAVE_ECC_SIGN) && !defined(WOLFSSL_SP_MATH) && \
|
|
!defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
|
|
!defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \
|
|
!defined(WOLFSSL_KCAPI_ECC) && !defined(WOLFSSL_DHUK) && \
|
|
defined(WOLFSSL_CUSTOM_CURVES)
|
|
static void wb_sign_hash_sw_indices(void)
|
|
{
|
|
DECLARE_CURVE_SPECS(ECC_CURVE_FIELD_COUNT);
|
|
WC_RNG rng;
|
|
ecc_key key;
|
|
ecc_key eph;
|
|
mp_int ers[3];
|
|
int err = MP_OKAY;
|
|
int haveRng = 0;
|
|
int i;
|
|
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(ers, 0, sizeof(ers));
|
|
|
|
if (wc_ecc_init(&key) != 0)
|
|
return;
|
|
if (wc_ecc_set_curve(&key, 0, ECC_SECP256R1) != 0) {
|
|
wc_ecc_free(&key);
|
|
return;
|
|
}
|
|
ALLOC_CURVE_SPECS(ECC_CURVE_FIELD_COUNT, err);
|
|
if (err == MP_OKAY)
|
|
err = wc_ecc_curve_load(key.dp, &curve, ECC_CURVE_FIELD_ALL);
|
|
if (err == MP_OKAY)
|
|
err = mp_init_multi(&ers[0], &ers[1], &ers[2], NULL, NULL, NULL);
|
|
if (err == MP_OKAY)
|
|
haveRng = (wc_InitRng(&rng) == 0);
|
|
|
|
if ((err == MP_OKAY) && haveRng &&
|
|
(mp_set(ecc_get_k(&key), 3) == MP_OKAY) &&
|
|
(mp_set(&ers[0], 0x2a) == MP_OKAY)) {
|
|
key.type = ECC_PRIVATEKEY;
|
|
/* i == 0: the table index -> (T,F). i == 1: "custom" -> (T,T), which
|
|
* copies key->dp onto the ephemeral key before it is generated. */
|
|
for (i = 0; i < 2; i++) {
|
|
key.idx = (i == 0) ? wc_ecc_get_curve_idx(ECC_SECP256R1)
|
|
: ECC_CUSTOM_IDX;
|
|
XMEMSET(&eph, 0, sizeof(eph));
|
|
if (wc_ecc_init(&eph) == 0) {
|
|
(void)ecc_sign_hash_sw(&key, &eph, &rng, curve, &ers[0],
|
|
&ers[1], &ers[2]);
|
|
/* The custom-curve fixup points eph.dp at key.dp, an entry of
|
|
* the static ecc_sets[] table. wc_ecc_set_custom_curve does
|
|
* not set deallocSet, so wc_ecc_free leaves it alone. */
|
|
wc_ecc_free(&eph);
|
|
}
|
|
}
|
|
key.idx = wc_ecc_get_curve_idx(ECC_SECP256R1);
|
|
}
|
|
|
|
if (haveRng)
|
|
wc_FreeRng(&rng);
|
|
mp_free(&ers[2]);
|
|
mp_free(&ers[1]);
|
|
mp_free(&ers[0]);
|
|
if (err == MP_OKAY)
|
|
wc_ecc_curve_free(curve);
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
WB_NOTE("sign-hash-sw index vectors done");
|
|
}
|
|
#else
|
|
static void wb_sign_hash_sw_indices(void)
|
|
{
|
|
WB_NOTE("ecc_sign_hash_sw not compiled in; index vectors skipped");
|
|
}
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* wc_ecc_check_r_s_range() -- 9345.
|
|
*
|
|
* A signature produced by ecc.c always has r and s reduced mod n, and the
|
|
* verify paths that reach this helper only ever see such a pair. Handing it a
|
|
* hand-built (r, s) is the only way its second range check separates, and the
|
|
* only way it is reached with err already set by the first one.
|
|
* ------------------------------------------------------------------------- */
|
|
#if !defined(WOLF_CRYPTO_CB_ONLY_ECC) && !defined(WOLFSSL_PSOC6_CRYPTO) && \
|
|
(!defined(WOLFSSL_STM32_PKA) || defined(WC_STM32_PKA_SIGN_ONLY))
|
|
static void wb_forged_rs_range(void)
|
|
{
|
|
DECLARE_CURVE_SPECS(1);
|
|
ecc_key key;
|
|
mp_int rs[2];
|
|
int err = MP_OKAY;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(rs, 0, sizeof(rs));
|
|
if (wc_ecc_init(&key) != 0)
|
|
return;
|
|
if (wc_ecc_set_curve(&key, 0, ECC_SECP256R1) != 0) {
|
|
wc_ecc_free(&key);
|
|
return;
|
|
}
|
|
ALLOC_CURVE_SPECS(1, err);
|
|
if (err == MP_OKAY)
|
|
err = wc_ecc_curve_load(key.dp, &curve, ECC_CURVE_FIELD_ORDER);
|
|
if (err == MP_OKAY)
|
|
err = mp_init_multi(&rs[0], &rs[1], NULL, NULL, NULL, NULL);
|
|
|
|
if (err == MP_OKAY) {
|
|
/* (T,F): both in range. */
|
|
if ((mp_set(&rs[0], 1) == MP_OKAY) && (mp_set(&rs[1], 1) == MP_OKAY))
|
|
(void)wc_ecc_check_r_s_range(&key, &rs[0], &rs[1]);
|
|
/* (T,T): s == n, which is one past the top of the range. */
|
|
if (mp_copy(curve->order, &rs[1]) == MP_OKAY)
|
|
(void)wc_ecc_check_r_s_range(&key, &rs[0], &rs[1]);
|
|
/* (F,-): r == n, so the r check has already set err. */
|
|
if ((mp_copy(curve->order, &rs[0]) == MP_OKAY) &&
|
|
(mp_set(&rs[1], 1) == MP_OKAY))
|
|
(void)wc_ecc_check_r_s_range(&key, &rs[0], &rs[1]);
|
|
mp_free(&rs[1]);
|
|
mp_free(&rs[0]);
|
|
wc_ecc_curve_free(curve);
|
|
}
|
|
FREE_CURVE_SPECS();
|
|
wc_ecc_free(&key);
|
|
WB_NOTE("forged r/s range vectors done");
|
|
}
|
|
#else
|
|
static void wb_forged_rs_range(void)
|
|
{
|
|
WB_NOTE("r/s range helper not compiled in; vectors skipped");
|
|
}
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* RFC 6979 deterministic-k helpers -- 7939 (_HMAC_K) and 8205 / 8213
|
|
* (wc_ecc_gen_deterministic_k's retry loop).
|
|
*
|
|
* The retry loop only turns over when the candidate k falls outside [1, n-1].
|
|
* Against a real curve order that is a ~2^-128 event, so the API can never
|
|
* show it. Against a deliberately TINY order it happens on most iterations,
|
|
* and the whole chain stays deterministic because every input is fixed.
|
|
*
|
|
* _HMAC_K's `ret == 0` operand needs the HMAC itself to fail, which a hash
|
|
* type the build does not implement does -- inside wc_HmacSetKey, before any
|
|
* buffer is touched.
|
|
* ------------------------------------------------------------------------- */
|
|
#if defined(WOLFSSL_ECDSA_DETERMINISTIC_K) || \
|
|
defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT)
|
|
static void wb_deterministic_k_retry(void)
|
|
{
|
|
byte K[WC_MAX_DIGEST_SIZE];
|
|
byte V[WC_MAX_DIGEST_SIZE];
|
|
byte x[8];
|
|
byte h1[8];
|
|
byte out[WC_MAX_DIGEST_SIZE];
|
|
byte intOct = 0x00;
|
|
mp_int nums[3];
|
|
|
|
XMEMSET(K, 0x00, sizeof(K));
|
|
XMEMSET(V, 0x01, sizeof(V));
|
|
XMEMSET(x, 0x11, sizeof(x));
|
|
XMEMSET(h1, 0x22, sizeof(h1));
|
|
XMEMSET(out, 0, sizeof(out));
|
|
XMEMSET(nums, 0, sizeof(nums));
|
|
|
|
/* 7939 (T,T) / (T,F): a working HMAC with and without the octet. */
|
|
(void)_HMAC_K(K, WC_SHA256_DIGEST_SIZE, V, WC_SHA256_DIGEST_SIZE,
|
|
h1, (word32)sizeof(h1), x, (word32)sizeof(x), &intOct, out,
|
|
WC_HASH_TYPE_SHA256, NULL);
|
|
(void)_HMAC_K(K, WC_SHA256_DIGEST_SIZE, V, WC_SHA256_DIGEST_SIZE,
|
|
NULL, 0, NULL, 0, NULL, out, WC_HASH_TYPE_SHA256, NULL);
|
|
/* 7939 (F,-): wc_HmacSetKey rejects the type, so ret is already set when
|
|
* the octet operand would otherwise be looked at. */
|
|
(void)_HMAC_K(K, WC_SHA256_DIGEST_SIZE, V, WC_SHA256_DIGEST_SIZE,
|
|
h1, (word32)sizeof(h1), x, (word32)sizeof(x), &intOct, out,
|
|
WC_HASH_TYPE_NONE, NULL);
|
|
|
|
/* 8205 / 8213: a one-byte order of 2 leaves exactly one acceptable
|
|
* candidate out of the four values a two-bit k can take, so the loop
|
|
* rejects and re-derives several times before it settles -- both halves
|
|
* of both `ret == 0 && err != 0` guards, from fixed inputs. */
|
|
if (mp_init_multi(&nums[0], &nums[1], &nums[2], NULL, NULL, NULL)
|
|
== MP_OKAY) {
|
|
byte hash[WC_SHA256_DIGEST_SIZE];
|
|
|
|
XMEMSET(hash, 0x5c, sizeof(hash));
|
|
if ((mp_set(&nums[0], 1) == MP_OKAY) && /* priv */
|
|
(mp_set(&nums[2], 2) == MP_OKAY)) { /* order */
|
|
(void)wc_ecc_gen_deterministic_k(hash, (word32)sizeof(hash),
|
|
WC_HASH_TYPE_SHA256, &nums[0], &nums[1], &nums[2], NULL);
|
|
}
|
|
mp_free(&nums[2]);
|
|
mp_free(&nums[1]);
|
|
mp_free(&nums[0]);
|
|
}
|
|
|
|
#ifndef USE_FAST_MATH
|
|
/* 8186 / 8205 / 8213 (F,-): the same loop, but with a destination k that
|
|
* has room for a single digit. The candidate is read back as qLen bytes,
|
|
* so the read fails and every guard downstream of it is reached with ret
|
|
* already set -- something no caller can arrange, because every caller
|
|
* passes a full-width mp_int. tfm has no mp_init_size, so this row comes
|
|
* from the SP-math variants. */
|
|
{
|
|
mp_int small[3];
|
|
byte hash[WC_SHA256_DIGEST_SIZE];
|
|
|
|
XMEMSET(small, 0, sizeof(small));
|
|
XMEMSET(hash, 0x5c, sizeof(hash));
|
|
if ((mp_init(&small[0]) == MP_OKAY) &&
|
|
(mp_init(&small[2]) == MP_OKAY) &&
|
|
(mp_init_size(&small[1], 1) == MP_OKAY) &&
|
|
(mp_set(&small[0], 1) == MP_OKAY) && /* priv */
|
|
(mp_set(&small[2], 0) == MP_OKAY) &&
|
|
(mp_set_bit(&small[2], 127) == MP_OKAY)) { /* 16-byte order */
|
|
(void)wc_ecc_gen_deterministic_k(hash, (word32)sizeof(hash),
|
|
WC_HASH_TYPE_SHA256, &small[0], &small[1], &small[2],
|
|
NULL);
|
|
}
|
|
mp_free(&small[2]);
|
|
mp_free(&small[1]);
|
|
mp_free(&small[0]);
|
|
}
|
|
#endif
|
|
WB_NOTE("deterministic-k retry vectors done");
|
|
}
|
|
#else
|
|
static void wb_deterministic_k_retry(void)
|
|
{
|
|
WB_NOTE("deterministic k not compiled in; retry vectors skipped");
|
|
}
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* ECIES with a KDF the build does not implement -- 15809 / 16157.
|
|
*
|
|
* Both guards are `ret == 0 && [!]ecc_is_gcm(...)`, and their first operand
|
|
* only goes FALSE when something upstream already failed. ecc_get_key_sizes()
|
|
* validates the CIPHER and the MAC, but not the KDF, so a context carrying an
|
|
* unimplemented kdfAlgo passes every size check and then falls into the KDF
|
|
* switch's default -- reaching both guards with ret set, which no correctly
|
|
* configured exchange does.
|
|
* ------------------------------------------------------------------------- */
|
|
#if defined(HAVE_ECC_ENCRYPT) && defined(HAVE_HKDF) && !defined(NO_AES) && \
|
|
!defined(NO_HMAC)
|
|
static void wb_ecies_bad_kdf(void)
|
|
{
|
|
WC_RNG rng;
|
|
ecc_key a;
|
|
ecc_key b;
|
|
byte plain[32];
|
|
byte enc[256];
|
|
byte dec[256];
|
|
byte good[256];
|
|
word32 goodSz = 0;
|
|
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
XMEMSET(plain, 0x5a, sizeof(plain));
|
|
XMEMSET(good, 0, sizeof(good));
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
WB_NOTE("wc_InitRng failed; ECIES bad-KDF pass skipped");
|
|
return;
|
|
}
|
|
|
|
/* Pass 0 encrypts with a broken KDF (15809). Pass 1 builds a good message
|
|
* first and then decrypts it with a broken KDF (16157). Pass 2 decrypts a
|
|
* good message with a MAC salt whose pointer is NULL while its length is
|
|
* not, so the HMAC that authenticates the message fails and 16221 is
|
|
* reached with ret already set -- the only way that guard's first operand
|
|
* goes FALSE, since the salt is otherwise always a real buffer. */
|
|
{
|
|
int pass;
|
|
|
|
for (pass = 0; pass < 3; pass++) {
|
|
ecEncCtx* ec = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng);
|
|
ecEncCtx* dc = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng);
|
|
word32 encSz = (word32)sizeof(enc);
|
|
word32 decSz = (word32)sizeof(dec);
|
|
byte saltCli[EXCHANGE_SALT_SZ];
|
|
byte saltSrv[EXCHANGE_SALT_SZ];
|
|
const byte* sp;
|
|
int ok = 0;
|
|
|
|
/* A fresh pair each pass: wc_ecc_decrypt overwrites its pubKey
|
|
* argument with the sender's ephemeral point. */
|
|
XMEMSET(&a, 0, sizeof(a));
|
|
XMEMSET(&b, 0, sizeof(b));
|
|
if ((wc_ecc_init(&a) != 0) || (wc_ecc_init(&b) != 0) ||
|
|
(wc_ecc_make_key_ex(&rng, 0, &a, ECC_SECP256R1) != 0) ||
|
|
(wc_ecc_make_key_ex(&rng, 0, &b, ECC_SECP256R1) != 0)) {
|
|
WB_NOTE("ECIES bad-KDF key setup failed");
|
|
wc_ecc_free(&b);
|
|
wc_ecc_free(&a);
|
|
wc_ecc_ctx_free(dc);
|
|
wc_ecc_ctx_free(ec);
|
|
break;
|
|
}
|
|
|
|
if ((ec != NULL) && (dc != NULL)) {
|
|
sp = wc_ecc_ctx_get_own_salt(ec);
|
|
if (sp != NULL) {
|
|
XMEMCPY(saltCli, sp, EXCHANGE_SALT_SZ);
|
|
sp = wc_ecc_ctx_get_own_salt(dc);
|
|
if (sp != NULL) {
|
|
XMEMCPY(saltSrv, sp, EXCHANGE_SALT_SZ);
|
|
ok = (wc_ecc_ctx_set_peer_salt(ec, saltSrv) == 0) &&
|
|
(wc_ecc_ctx_set_peer_salt(dc, saltCli) == 0) &&
|
|
(wc_ecc_ctx_set_algo(ec, ecAES_128_CBC,
|
|
ecHKDF_SHA256, ecHMAC_SHA256) == 0) &&
|
|
(wc_ecc_ctx_set_algo(dc, ecAES_128_CBC,
|
|
ecHKDF_SHA256, ecHMAC_SHA256) == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok && (pass == 0)) {
|
|
/* Set the field directly: wc_ecc_ctx_set_algo screens the
|
|
* value, and screening it is exactly what we need to skip. */
|
|
ec->kdfAlgo = 0x7f;
|
|
(void)wc_ecc_encrypt(&a, &b, plain, (word32)sizeof(plain), enc,
|
|
&encSz, ec);
|
|
}
|
|
else if (ok) {
|
|
if ((wc_ecc_encrypt(&a, &b, plain, (word32)sizeof(plain), good,
|
|
&goodSz, ec) == 0) && (goodSz > 0)) {
|
|
if (pass == 1) {
|
|
dc->kdfAlgo = 0x7f;
|
|
}
|
|
else {
|
|
dc->macSalt = NULL;
|
|
dc->macSaltSz = 8;
|
|
}
|
|
(void)wc_ecc_decrypt(&b, &a, good, goodSz, dec, &decSz,
|
|
dc);
|
|
}
|
|
}
|
|
|
|
wc_ecc_ctx_free(dc);
|
|
wc_ecc_ctx_free(ec);
|
|
wc_ecc_free(&b);
|
|
wc_ecc_free(&a);
|
|
goodSz = (word32)sizeof(good);
|
|
}
|
|
}
|
|
|
|
wc_FreeRng(&rng);
|
|
WB_NOTE("ECIES bad-KDF vectors done");
|
|
}
|
|
#else
|
|
static void wb_ecies_bad_kdf(void)
|
|
{
|
|
WB_NOTE("ECIES not compiled in; bad-KDF vectors skipped");
|
|
}
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* RESIDUAL, recorded here so it is not re-attempted: 8684's second operand,
|
|
* `mp_iszero(R->y)` in
|
|
*
|
|
* if ((err == MP_OKAY) && mp_iszero(R->z)) {
|
|
* if (mp_iszero(R->x) && mp_iszero(R->y)) {
|
|
*
|
|
* has no independence pair. The adder computes
|
|
* Z3 = -Z*Z'*H, X3 = r^2 - (U1+U2)*H^2,
|
|
* Y3 = (-r^3 + 3*r*X3 + (S1+S2)*H^3) / 2
|
|
* over a prime modulus, so Z3 == 0 forces H == 0, which collapses those to
|
|
* X3 = r^2 and Y3 = -r*X3/2: X3 == 0 therefore forces Y3 == 0 and the
|
|
* (X3 == 0, Y3 != 0) row does not exist.
|
|
*
|
|
* Feeding the helper a COMPOSITE modulus directly (the only other way Z*Z' can
|
|
* vanish with H != 0) does not help either, and was tried: for every way of
|
|
* splitting the modulus so that Z*Z' == 0, one side's own U and S collapse to
|
|
* zero in that prime component, which restores a Y3 = c*X3 relation there.
|
|
* Exhaustive sweeps over the moduli 9, 15, 21 and 35 with the Z ordinates set
|
|
* to the factors produced no such row.
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
/* ========================================================================= *
|
|
* RESIDUALS after pass 3 -- conditions with no independence pair, and why.
|
|
* Recorded here so they are not re-attempted. Line numbers are ecc.c's.
|
|
*
|
|
* 4394:0 `ecc_sets[curve_idx].name &&`
|
|
* 4666:0 `ecc_sets[curve_idx].oid &&`
|
|
* ecc_sets[] is a static const table; every entry before the
|
|
* size == 0 sentinel (which stops both loops first) initializes name
|
|
* and oid from a string literal, so neither operand is ever FALSE.
|
|
* For the oid loop there is a second, independent reason: this build
|
|
* has HAVE_OID_ENCODING without HAVE_OID_DECODING, so the iteration
|
|
* calls EncodeObjectId(ecc_sets[i].oid, ...) BEFORE the guard, and
|
|
* EncodeObjectId rejects a NULL input and makes the loop `continue`
|
|
* without ever evaluating the guard.
|
|
*
|
|
* 5082:0 `if (checkInf && wc_ecc_point_is_at_infinity(result))`
|
|
* 5110:1 `if ((err == MP_OKAY) && checkInf)`
|
|
* checkInf is a local initialized to the constant 1 and never
|
|
* assigned again outside WOLFSSL_SE050 builds, so it cannot be FALSE
|
|
* in any variant this suite compiles.
|
|
*
|
|
* 5088:1 `x < mp_unsigned_bin_size(result->x)`
|
|
* x is mp_unsigned_bin_size(curve->prime) and the guard only runs
|
|
* after ecc_map_ex() succeeded, which leaves result->x reduced mod
|
|
* that same prime. A reduced value can never need more bytes than
|
|
* the modulus, so this operand is FALSE on every path that reaches
|
|
* it -- including when the caller supplies its own curve, because
|
|
* the multiply and the size both come from the one loaded prime.
|
|
*
|
|
* 5737:0 `if ((err == MP_OKAY) && !doneInCb)`
|
|
* 5737:1 doneInCb is only ever set inside the WOLF_CRYPTO_CB offload block,
|
|
* which no variant of this module compiles, so it is a constant 0;
|
|
* and with it absent nothing between the function's entry and this
|
|
* guard can fail (the mp_init_multi above it cannot fail for the
|
|
* fixed-size mp_ints of a live ecc_point), so err is a constant
|
|
* MP_OKAY. Reaching either row needs a registered crypto callback,
|
|
* i.e. a different build, not a different test.
|
|
*
|
|
* 8684:1 See the dedicated note above: over a prime field Z3 == 0 forces
|
|
* H == 0, which makes Y3 a multiple of X3, so X3 == 0 drags Y3 to
|
|
* zero with it. Composite moduli were tried too and do not help.
|
|
*
|
|
* 10221:0 `curve_idx != ECC_CUSTOM_IDX &&` (x2, uncompress dispatch)
|
|
* 10242:0 ECC_CUSTOM_IDX is -1 and wc_ecc_import_point_der_ex() returns
|
|
* ECC_BAD_ARG_E for any curve_idx < 0 in its first guard, so the
|
|
* custom index can never reach these decisions. The operand is a
|
|
* defensive re-check of something already rejected.
|
|
*
|
|
* 10325:2 `mp_isodd(t2) == MP_NO` (and 11611:2, the same code in the key
|
|
* 11611:2 importer). This is the exact complement of the decision's FIRST
|
|
* operand, `mp_isodd(t2) == MP_YES`, computed from the same value:
|
|
* no pair of test vectors can vary one while holding the other
|
|
* fixed, so the condition has no independence pair by construction.
|
|
* The other three operands of each decision are covered.
|
|
*
|
|
* 10769:0 `while (err == MP_OKAY && mp_isneg(t1))`
|
|
* 10769:1
|
|
* 10772:0 `while (err == MP_OKAY && mp_cmp(t1, prime) != MP_LT)`
|
|
* 10772:1 Both loops are defensive range fixups running immediately after
|
|
* the step that already normalized t1: whichever arm of the "is a
|
|
* equal to -3" test was taken, t1 was last written by mp_mod() or
|
|
* mp_addmod() against prime, and both backends return a result with
|
|
* the sign of the modulus and strictly below it. So t1 is never
|
|
* negative and never >= prime, neither loop body ever runs, and
|
|
* neither the loop-entry operand nor the err operand can form a
|
|
* pair. (A negative prime WOULD make the first test TRUE, but the
|
|
* body then adds that negative prime and never terminates, so it is
|
|
* not a legitimate input.)
|
|
*
|
|
* 14289:1 `} else if (zB && first == 1)`
|
|
* first is declared int, initialized to 1, and only ever assigned 0
|
|
* here or 1 through the `infinity` out-parameter of
|
|
* ecc_projective_add_point_safe(). The immediately preceding arm
|
|
* tested `(zB && first == 0)`, so reaching this one with zB true
|
|
* means first != 0, i.e. first == 1. Always TRUE where evaluated.
|
|
*
|
|
* 16564:0 `if ((mp_init_multi(t1, C, Q, S, Z, M) != MP_OKAY) ||`
|
|
* 16564:1 ` (mp_init_multi(T, R, N, two, NULL, NULL) != MP_OKAY))`
|
|
* Both backends this module builds return MP_OKAY unconditionally
|
|
* and skip NULL arguments: sp_init_multi() (sp_int.c) and
|
|
* mp_init_multi() (tfm.c) are `if (x) init(x);` six times followed
|
|
* by `return MP_OKAY;`. The storage is caller-owned and fixed-size,
|
|
* so neither call can report anything else and the decision is
|
|
* never TRUE.
|
|
*
|
|
* 14180:0 `if ((mp_unsigned_bin_size(tka) > (int)(KB_SIZE - 2)) ||`
|
|
* 14180:1 ` (mp_unsigned_bin_size(tkb) > (int)(KB_SIZE - 2)))`
|
|
* NOT a residual -- COVERED by Class 33 below. The argument that
|
|
* used to be filed here (MAX_ECC_BITS caps the scalars at 521 bits,
|
|
* 66 bytes, well under KB_SIZE - 2 = 126) is false for this
|
|
* suite's configs: they #define WOLFCRYPT_HAVE_SAKKE, which
|
|
* raises MAX_ECC_BITS to 1024 and puts the 128-byte ECC_SAKKE_1
|
|
* curve in ecc_sets[], while accel_fp_mul2add() uses a flat
|
|
* `#define KB_SIZE 128` (accel_fp_mul() is 256 under the same
|
|
* macro). A scalar the width of that curve's modulus is 128 bytes
|
|
* and is not reduced on the way in, so the guard fires.
|
|
* ========================================================================= */
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* Class 33: accel_fp_mul2add()'s KB_SIZE scalar-length guard (ecc.c:14180),
|
|
* if ((mp_unsigned_bin_size(tka) > (int)(KB_SIZE - 2)) ||
|
|
* (mp_unsigned_bin_size(tkb) > (int)(KB_SIZE - 2)) )
|
|
*
|
|
* accel_fp_mul2add() declares a flat `#define KB_SIZE 128` -- unlike
|
|
* accel_fp_mul(), which is 256 under WOLFCRYPT_HAVE_SAKKE -- so the bound is
|
|
* 126 bytes. The base configs #define WOLFCRYPT_HAVE_SAKKE, which puts the
|
|
* 128-byte ECC_SAKKE_1 curve in ecc_sets[] and raises MAX_ECC_BITS to 1024,
|
|
* so a scalar sized to that curve is 128 bytes and the guard fires.
|
|
*
|
|
* Each operand is isolated: the scalars are only reduced when they are LARGER
|
|
* than the modulus, so a 128-byte scalar against a 128-byte modulus is
|
|
* carried through unchanged and reaches the test at full width, while a
|
|
* one-digit scalar stays short. The all-false row is the same call with both
|
|
* scalars short. Reaching accel_fp_mul2add() at all needs both points in the
|
|
* fixed-point cache with their LUTs built, which ecc_mul2add() does once an
|
|
* entry's lru_count reaches 2 -- A and B are the same point here, so a single
|
|
* warm-up call gets there.
|
|
* ------------------------------------------------------------------------- */
|
|
#if defined(FP_ECC) && defined(ECC_SHAMIR) && !defined(WOLFSSL_SP_MATH) && \
|
|
defined(WOLFCRYPT_HAVE_SAKKE)
|
|
static void wb_fp_mul2add_kb_size(void)
|
|
{
|
|
int idx = wc_ecc_get_curve_idx(ECC_SAKKE_1);
|
|
const ecc_set_type* cs;
|
|
mp_int a, modulus, kShort, kWide;
|
|
ecc_point *A = NULL, *B = NULL, *C = NULL;
|
|
byte wide[128];
|
|
int ret;
|
|
|
|
if (idx == ECC_CURVE_INVALID) {
|
|
WB_NOTE("SAKKE1 curve absent; KB_SIZE guard skipped");
|
|
return;
|
|
}
|
|
cs = wc_ecc_get_curve_params(idx);
|
|
if (cs == NULL || cs->size != 128) {
|
|
WB_NOTE("SAKKE1 curve is not 128 bytes; KB_SIZE guard skipped");
|
|
return;
|
|
}
|
|
if (mp_init_multi(&a, &modulus, &kShort, &kWide, NULL, NULL) != MP_OKAY) {
|
|
WB_NOTE("KB_SIZE: mp_init_multi failed");
|
|
wb_fail = 1;
|
|
return;
|
|
}
|
|
|
|
wc_ecc_fp_free(); /* deterministic empty-cache start */
|
|
|
|
/* Read the curve fields straight into mp_ints: the file's shared
|
|
* wb_hex_to_bin() caps a field at WB_MAXFIELD (100) bytes, which the
|
|
* 128-byte SAKKE1 fields exceed. */
|
|
if ((mp_read_radix(&a, cs->Af, MP_RADIX_HEX) != MP_OKAY) ||
|
|
(mp_read_radix(&modulus, cs->prime, MP_RADIX_HEX) != MP_OKAY)) {
|
|
WB_NOTE("KB_SIZE: SAKKE1 a/prime would not parse");
|
|
wb_fail = 1; goto out;
|
|
}
|
|
if (mp_unsigned_bin_size(&modulus) != 128) {
|
|
WB_NOTE("KB_SIZE: SAKKE1 prime is not 128 bytes");
|
|
wb_fail = 1; goto out;
|
|
}
|
|
|
|
(void)mp_set(&kShort, 3);
|
|
/* 128 bytes: the same width as the modulus, so accel_fp_mul2add's
|
|
* "smaller than modulus" test does not reduce it and it reaches the
|
|
* KB_SIZE test at full width. */
|
|
XMEMSET(wide, 0xFF, sizeof(wide));
|
|
if (mp_read_unsigned_bin(&kWide, wide, (word32)sizeof(wide)) != MP_OKAY) {
|
|
WB_NOTE("KB_SIZE: 128-byte scalar would not load");
|
|
wb_fail = 1; goto out;
|
|
}
|
|
|
|
A = wc_ecc_new_point();
|
|
B = wc_ecc_new_point();
|
|
C = wc_ecc_new_point();
|
|
if (A == NULL || B == NULL || C == NULL) {
|
|
WB_NOTE("KB_SIZE: point allocation failed");
|
|
wb_fail = 1; goto out;
|
|
}
|
|
if ((mp_read_radix(A->x, cs->Gx, MP_RADIX_HEX) != MP_OKAY) ||
|
|
(mp_read_radix(A->y, cs->Gy, MP_RADIX_HEX) != MP_OKAY)) {
|
|
WB_NOTE("KB_SIZE: SAKKE1 generator would not parse");
|
|
wb_fail = 1; goto out;
|
|
}
|
|
(void)mp_set(A->z, 1);
|
|
(void)mp_copy(A->x, B->x);
|
|
(void)mp_copy(A->y, B->y);
|
|
(void)mp_copy(A->z, B->z);
|
|
|
|
/* Warm-up: caches the base point and builds its LUT, then dispatches to
|
|
* accel_fp_mul2add with both scalars short -- the (FALSE,FALSE) row. */
|
|
ret = ecc_mul2add(A, &kShort, B, &kShort, C, &a, &modulus, NULL);
|
|
if (ret != MP_OKAY) {
|
|
WB_NOTE("KB_SIZE: SAKKE1 fp mul2add warm-up did not complete");
|
|
wb_fail = 1; goto out;
|
|
}
|
|
ret = ecc_mul2add(A, &kShort, B, &kShort, C, &a, &modulus, NULL);
|
|
if (ret != MP_OKAY) {
|
|
WB_NOTE("KB_SIZE: second warm-up call failed");
|
|
wb_fail = 1; goto out;
|
|
}
|
|
|
|
/* operand 0 TRUE (short-circuits) */
|
|
ret = ecc_mul2add(A, &kWide, B, &kShort, C, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(BUFFER_E)) {
|
|
WB_NOTE("KB_SIZE: wide kA did not give BUFFER_E");
|
|
wb_fail = 1;
|
|
}
|
|
/* operand 0 FALSE, operand 1 TRUE */
|
|
ret = ecc_mul2add(A, &kShort, B, &kWide, C, &a, &modulus, NULL);
|
|
if (ret != WC_NO_ERR_TRACE(BUFFER_E)) {
|
|
WB_NOTE("KB_SIZE: wide kB did not give BUFFER_E");
|
|
wb_fail = 1;
|
|
}
|
|
|
|
WB_NOTE("accel_fp_mul2add KB_SIZE scalar-width pairs done");
|
|
|
|
out:
|
|
wc_ecc_del_point(A);
|
|
wc_ecc_del_point(B);
|
|
wc_ecc_del_point(C);
|
|
mp_clear(&kWide);
|
|
mp_clear(&kShort);
|
|
mp_clear(&modulus);
|
|
mp_clear(&a);
|
|
wc_ecc_fp_free();
|
|
}
|
|
#else
|
|
static void wb_fp_mul2add_kb_size(void)
|
|
{
|
|
WB_NOTE("FP_ECC/ECC_SHAMIR/SAKKE off; accel_fp_mul2add KB_SIZE skipped");
|
|
}
|
|
#endif
|
|
|
|
int main(void)
|
|
{
|
|
/* Unbuffered: on a timeout or a fault the process is killed and anything
|
|
* still buffered is lost, which reads as an empty log. */
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
printf("ecc.c white-box MC/DC supplement\n");
|
|
#if !defined(HAVE_ECC) || defined(WOLF_CRYPTO_CB_ONLY_ECC)
|
|
printf(" HAVE_ECC off (or crypto-cb-only build); nothing to exercise\n");
|
|
return 0;
|
|
#else
|
|
wb_curve_load();
|
|
wb_import_private_key_ex();
|
|
wb_ctx_set_salt();
|
|
wb_ctx_protocol_zero();
|
|
wb_is_valid_idx_n_lt_x();
|
|
wb_cmp_param_null();
|
|
wb_get_curve_id_from_params();
|
|
wb_get_curve_id_from_dp_params();
|
|
wb_mulmod_ex2_null_guard();
|
|
wb_ecc_map_ex_null();
|
|
wb_projective_wrappers();
|
|
wb_mul2add_and_bufsize();
|
|
wb_projective_safe_special_cases();
|
|
wb_fp_cache_internals();
|
|
wb_export_point_der();
|
|
wb_export_x963_internal();
|
|
wb_idx_dp_guard_export_paths();
|
|
wb_make_pub_privatekey_only();
|
|
wb_arg_guards();
|
|
wb_gap_pass2();
|
|
wb_custom_curve_dispatch();
|
|
wb_ecies_algos();
|
|
wb_forged_pubkey_order();
|
|
wb_forged_make_pub_sw();
|
|
wb_forged_validate_public_key();
|
|
wb_forged_is_point_neg();
|
|
wb_uncompress_parity();
|
|
wb_untrusted_import();
|
|
wb_forged_negative_strings();
|
|
wb_forged_order_minus_1();
|
|
wb_sign_hash_sw_indices();
|
|
wb_forged_rs_range();
|
|
wb_deterministic_k_retry();
|
|
wb_ecies_bad_kdf();
|
|
wb_fp_mul2add_kb_size();
|
|
printf("done (%s)\n", wb_fail ? "with skips" : "ok");
|
|
/* Setup failures are surfaced as skips, not test failures: the harness
|
|
* treats a nonzero exit as a failed variant and discards its coverage. */
|
|
return 0;
|
|
#endif
|
|
}
|