mirror of https://github.com/wolfSSL/wolfssl.git
Falcon: address review findings (zeroization, PRNG errors, check_key)
Remaining fixes from the second review round: - keygen: falcon_compute_public's scratch buffer holds NTT(f) (private-key material) in its tail; wc_ForceZero it before both frees (the f-not-invertible reject path and the success path). Also zeroize the internally allocated hwork for consistency with the tmpbuf hardening. - sampler: falcon_sampler_z's rejection loop never consulted the sticky PRNG error flag, so a mid-signature SHAKE256 squeeze failure could make berexp deterministically reject and the loop spin forever. Check p.err each iteration and bail out; the returned value is discarded since falcon_sign_core rejects the whole signature once p.err is set. falcon_prng_init now frees the SHAKE256 context when a later init step fails (plugs a device-context leak in WOLFSSL_ASYNC_CRYPT builds), and falcon_prng_refill early-returns once the error is latched instead of re-issuing failing squeezes. - codec: guard the bits-dependent shifts in falcon_trim_i8_encode/decode against out-of-range widths (defense in depth; callers only pass 5..8). - check_key: implement the cryptographic private/public cross-check that 91ebd89d7 documented as a follow-up. New falcon_native_check_key decodes (f, g) from the private key and h from the public key and verifies the defining relation h*f == g (mod q, mod X^n + 1) slot-wise in the NTT domain (falcon_ntt keeps values canonical in [0, q)); a slot with NTT(f) == 0 is rejected too, as keygen only emits invertible f. wc_falcon_check_key dispatches to it whenever the native signing core is compiled in, and falls back to the presence check in verify-only / callback-only builds. Doxygen updated to the actual contract, and a unit test added: a mismatched pair (public half from a different key) must fail with PUBLIC_KEY_E. This also strengthens the keypair validation done via wc_falcon_check_key in asn.c.pull/10827/head
parent
7ce2465fd3
commit
a311654d45
|
|
@ -411,12 +411,16 @@ int wc_falcon_export_key(falcon_key* key, byte* priv, word32 *privSz,
|
|||
/*!
|
||||
\ingroup Falcon
|
||||
|
||||
\brief Checks the consistency of a Falcon key, verifying that the stored
|
||||
public key matches the private key when both are present.
|
||||
\brief Checks the consistency of a Falcon key. Requires both key halves to
|
||||
be present. When the native signing core is compiled in, the stored public
|
||||
key h is additionally verified against the private key by checking the
|
||||
defining relation h*f == g (mod q); in verify-only or crypto-callback-only
|
||||
builds only the presence of both halves is checked.
|
||||
|
||||
\return 0 on success.
|
||||
\return BAD_FUNC_ARG if key is NULL.
|
||||
\return PUBLIC_KEY_E if the public and private keys are inconsistent.
|
||||
\return BAD_FUNC_ARG if key is NULL or the level is unset.
|
||||
\return PUBLIC_KEY_E if either key half is missing, or if the public and
|
||||
private keys are cryptographically inconsistent.
|
||||
|
||||
\param [in] key Pointer to a falcon_key to check.
|
||||
|
||||
|
|
|
|||
|
|
@ -406,8 +406,9 @@ int test_wc_falcon_import_export(void)
|
|||
}
|
||||
|
||||
/*
|
||||
* check_key: valid key passes; a corrupted public copy, a public-only key, and
|
||||
* a private-only key all fail; NULL is rejected.
|
||||
* check_key: valid key passes; a public-only key, a private-only key, and a
|
||||
* mismatched public/private pair (which must fail the h*f == g cross-check)
|
||||
* all fail; NULL is rejected.
|
||||
*/
|
||||
int test_wc_falcon_check_key(void)
|
||||
{
|
||||
|
|
@ -478,6 +479,25 @@ int test_wc_falcon_check_key(void)
|
|||
ExpectIntEQ(wc_falcon_import_private_only(prv, prvLen, &key), 0);
|
||||
ExpectIntEQ(wc_falcon_check_key(&key), 0);
|
||||
wc_falcon_free(&key);
|
||||
|
||||
/* Mismatched pair: the public half of a DIFFERENT key of the same
|
||||
* level together with the original private key must fail the
|
||||
* cryptographic h*f == g (mod q) cross-check. */
|
||||
XMEMSET(&key, 0, sizeof(key));
|
||||
ExpectIntEQ(wc_falcon_init(&key), 0);
|
||||
ExpectIntEQ(wc_falcon_set_level(&key, level), 0);
|
||||
ExpectIntEQ(wc_falcon_make_key(&key, &rng), 0);
|
||||
pubLen = FALCON_MAX_PUB_KEY_SIZE;
|
||||
ExpectIntEQ(wc_falcon_export_public(&key, pub, &pubLen), 0);
|
||||
wc_falcon_free(&key);
|
||||
|
||||
XMEMSET(&key, 0, sizeof(key));
|
||||
ExpectIntEQ(wc_falcon_init(&key), 0);
|
||||
ExpectIntEQ(wc_falcon_set_level(&key, level), 0);
|
||||
ExpectIntEQ(wc_falcon_import_public(pub, pubLen, &key), 0);
|
||||
ExpectIntEQ(wc_falcon_import_private_only(prv, prvLen, &key), 0);
|
||||
ExpectIntEQ(wc_falcon_check_key(&key), WC_NO_ERR_TRACE(PUBLIC_KEY_E));
|
||||
wc_falcon_free(&key);
|
||||
}
|
||||
|
||||
wc_FreeRng(&rng);
|
||||
|
|
|
|||
|
|
@ -643,17 +643,18 @@ int wc_falcon_export_key(falcon_key* key, byte* priv, word32 *privSz,
|
|||
*
|
||||
* key [in] Falcon private/public key.
|
||||
* returns BAD_FUNC_ARG when key is NULL or the level is unset,
|
||||
* PUBLIC_KEY_E when either the public or private half is not set,
|
||||
* PUBLIC_KEY_E when either half is not set, or when the stored public
|
||||
* key h does not satisfy the defining relation h = g/f (mod q) for the
|
||||
* private (f, g),
|
||||
* 0 otherwise.
|
||||
*
|
||||
* Note: this verifies both halves of the pair are loaded. It does not yet
|
||||
* perform a full cryptographic cross-check (recomputing the public key h from
|
||||
* the private (f, g) and comparing it against the stored public key); that is a
|
||||
* TODO once a standalone public-key-from-private helper is exposed by the native
|
||||
* core. The previous implementation compared the stored public key against a
|
||||
* duplicate copy kept behind the private key, which was always a copy of the
|
||||
* same bytes and so could never detect a mismatch.
|
||||
*/
|
||||
* When the native signing core is compiled in, both halves are decoded and the
|
||||
* relation h*f == g (mod q, mod X^n + 1) is verified in the NTT domain, so a
|
||||
* mismatched pair is detected cryptographically. In verify-only or
|
||||
* callback-only builds (no private-key codec available) only the presence of
|
||||
* both halves is checked. The pre-native implementation compared the stored
|
||||
* public key against a duplicate copy kept behind the private key, which was
|
||||
* always a copy of the same bytes and so could never detect a mismatch. */
|
||||
int wc_falcon_check_key(falcon_key* key)
|
||||
{
|
||||
if (key == NULL) {
|
||||
|
|
@ -668,7 +669,11 @@ int wc_falcon_check_key(falcon_key* key)
|
|||
return PUBLIC_KEY_E;
|
||||
}
|
||||
|
||||
#ifdef WC_FALCON_HAVE_NATIVE_SIGN
|
||||
return falcon_native_check_key(key);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Returns the size of a falcon private key.
|
||||
|
|
|
|||
|
|
@ -882,6 +882,108 @@ out:
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Cryptographic private/public consistency check. Decodes (f, g) from the
|
||||
* private key and h from the public key, and verifies the defining relation
|
||||
* h = g/f (mod q, mod X^n + 1) as h*f == g slot-wise in the NTT domain
|
||||
* (falcon_ntt keeps every value canonical in [0, q), so direct comparison is
|
||||
* exact). A slot with NTT(f) == 0 is rejected as well: keygen only emits f
|
||||
* invertible mod q, and a non-invertible f does not determine h.
|
||||
*
|
||||
* Returns 0 when the pair is consistent, PUBLIC_KEY_E on mismatch, or a
|
||||
* negative wolfCrypt error on decode/allocation failure. */
|
||||
int falcon_native_check_key(falcon_key* key)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned logn = 0;
|
||||
int n = 0, i;
|
||||
word32 pubSz = 0, keySz;
|
||||
sword8 *f = NULL, *g = NULL, *F = NULL;
|
||||
word16 *h = NULL, *ft = NULL, *gt = NULL;
|
||||
const word16* zetas = NULL;
|
||||
const word16* izetas = NULL;
|
||||
void* heap;
|
||||
|
||||
if (key == NULL) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
if (falcon_level_params(key->level, &logn, &n, &pubSz) != 0) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
keySz = (key->level == FALCON_LEVEL1) ? FALCON_LEVEL1_KEY_SIZE
|
||||
: FALCON_LEVEL5_KEY_SIZE;
|
||||
heap = key->heap;
|
||||
|
||||
f = (sword8*)XMALLOC((size_t)n, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
g = (sword8*)XMALLOC((size_t)n, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
F = (sword8*)XMALLOC((size_t)n, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
h = (word16*)XMALLOC(sizeof(word16) * (size_t)n, heap,
|
||||
DYNAMIC_TYPE_TMP_BUFFER);
|
||||
ft = (word16*)XMALLOC(sizeof(word16) * (size_t)n, heap,
|
||||
DYNAMIC_TYPE_TMP_BUFFER);
|
||||
gt = (word16*)XMALLOC(sizeof(word16) * (size_t)n, heap,
|
||||
DYNAMIC_TYPE_TMP_BUFFER);
|
||||
if (f == NULL || g == NULL || F == NULL || h == NULL || ft == NULL ||
|
||||
gt == NULL) {
|
||||
ret = MEMORY_E;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = falcon_privkey_decode(key->k, keySz, f, g, F, logn);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
if (key->p[0] != (byte)(FALCON_PUB_HEAD | logn)) {
|
||||
ret = ASN_PARSE_E;
|
||||
goto out;
|
||||
}
|
||||
{
|
||||
int rc = falcon_modq_decode(key->p + 1, pubSz - 1, h, logn);
|
||||
if (rc < 0) {
|
||||
ret = rc;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
int x = f[i];
|
||||
if (x < 0) {
|
||||
x += FALCON_Q;
|
||||
}
|
||||
ft[i] = (word16)x;
|
||||
x = g[i];
|
||||
if (x < 0) {
|
||||
x += FALCON_Q;
|
||||
}
|
||||
gt[i] = (word16)x;
|
||||
}
|
||||
falcon_get_tables(logn, &zetas, &izetas);
|
||||
falcon_ntt(ft, n, zetas);
|
||||
falcon_ntt(gt, n, zetas);
|
||||
falcon_ntt(h, n, zetas);
|
||||
for (i = 0; i < n; i++) {
|
||||
if (ft[i] == 0 ||
|
||||
(word16)(((word64)h[i] * ft[i]) % FALCON_Q) != gt[i]) {
|
||||
ret = PUBLIC_KEY_E;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (f != NULL) { ForceZero(f, (word32)n); XFREE(f, heap, DYNAMIC_TYPE_TMP_BUFFER); }
|
||||
if (g != NULL) { ForceZero(g, (word32)n); XFREE(g, heap, DYNAMIC_TYPE_TMP_BUFFER); }
|
||||
if (F != NULL) { ForceZero(F, (word32)n); XFREE(F, heap, DYNAMIC_TYPE_TMP_BUFFER); }
|
||||
if (ft != NULL) {
|
||||
ForceZero(ft, (word32)(n * (int)sizeof(word16)));
|
||||
XFREE(ft, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
}
|
||||
if (gt != NULL) {
|
||||
ForceZero(gt, (word32)(n * (int)sizeof(word16)));
|
||||
XFREE(gt, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
}
|
||||
if (h != NULL) XFREE(h, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
return ret;
|
||||
}
|
||||
#endif /* !WOLFSSL_FALCON_VERIFY_ONLY */
|
||||
|
||||
int falcon_native_verify_msg(const byte* sig, word32 sigLen, const byte* msg,
|
||||
|
|
|
|||
|
|
@ -186,6 +186,12 @@ size_t falcon_trim_i8_encode(byte* out, size_t max_out, const sword8* x,
|
|||
word32 acc = 0, mask;
|
||||
unsigned acc_len = 0;
|
||||
|
||||
/* Callers only pass falcon_max_fg_bits/falcon_max_FG_bits values (5..8);
|
||||
* guard the shifts below against out-of-range widths anyway. */
|
||||
if (bits < 2 || bits > 8) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
maxv = (1 << (bits - 1)) - 1;
|
||||
minv = -maxv;
|
||||
for (u = 0; u < n; u++) {
|
||||
|
|
@ -225,6 +231,11 @@ size_t falcon_trim_i8_decode(sword8* x, unsigned logn, unsigned bits,
|
|||
word32 acc = 0, mask1, mask2;
|
||||
unsigned acc_len = 0;
|
||||
|
||||
/* Same defensive width guard as falcon_trim_i8_encode. */
|
||||
if (bits < 2 || bits > 8) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (in_len > max_in) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,6 +300,9 @@ static int falcon_compute_public(word16* h, const sword8* f, const sword8* g,
|
|||
mq_ntt(h, n, zetas);
|
||||
for (u = 0; u < n; u++) {
|
||||
if (ff[u] == 0) {
|
||||
/* The tail of the buffer (ff) holds NTT(f) -- secret material. */
|
||||
wc_ForceZero(zetas, (word32)((size_t)3 * (size_t)n
|
||||
* sizeof(word16)));
|
||||
XFREE(zetas, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -307,6 +310,8 @@ static int falcon_compute_public(word16* h, const sword8* f, const sword8* g,
|
|||
}
|
||||
mq_intt(h, n, izetas);
|
||||
|
||||
/* The tail of the buffer (ff) holds NTT(f) -- secret material. */
|
||||
wc_ForceZero(zetas, (word32)((size_t)3 * (size_t)n * sizeof(word16)));
|
||||
XFREE(zetas, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -1890,6 +1895,11 @@ int falcon_keygen(WC_RNG* rng, sword8* f, sword8* g, sword8* F, sword8* G,
|
|||
out:
|
||||
falcon_rng_free(&rc);
|
||||
if (h == NULL) {
|
||||
/* hwork holds the public key h by now (g was overwritten in place);
|
||||
* zeroized anyway for consistency with the tmpbuf hardening. */
|
||||
if (hwork != NULL) {
|
||||
wc_ForceZero(hwork, (word32)(n * sizeof(word16)));
|
||||
}
|
||||
XFREE(hwork, heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
}
|
||||
/* tmpbuf held the full secret-key expansion (f,g in RNS/NTT, F,G, FFT
|
||||
|
|
|
|||
|
|
@ -118,14 +118,23 @@ static const fpr falcon_fpr_sigma_min[11] = {
|
|||
/* Squeeze a fresh batch of blocks into the buffer. Constant-time. */
|
||||
static int falcon_prng_refill(falcon_prng* p)
|
||||
{
|
||||
int ret = wc_Shake256_SqueezeBlocks(&p->shake, p->buf, FALCON_PRNG_BLOCKS);
|
||||
int ret;
|
||||
|
||||
/* Once the sticky error is latched the stream is already invalid and the
|
||||
* result will be rejected; don't keep re-issuing failing squeezes. */
|
||||
if (p->err != 0) {
|
||||
p->ptr = 0;
|
||||
p->len = 0;
|
||||
return p->err;
|
||||
}
|
||||
ret = wc_Shake256_SqueezeBlocks(&p->shake, p->buf, FALCON_PRNG_BLOCKS);
|
||||
p->ptr = 0;
|
||||
p->len = (ret == 0) ? (word32)FALCON_PRNG_BUFLEN : 0;
|
||||
/* Latch the first failure. get_u8/get_u64 have no error return, so a squeeze
|
||||
* failure is made sticky here and checked by the signer (falcon_sign_core),
|
||||
* which rejects any signature produced from an invalid PRNG state instead of
|
||||
* consuming stale buffer bytes. */
|
||||
if (ret != 0 && p->err == 0)
|
||||
if (ret != 0)
|
||||
p->err = ret;
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -138,19 +147,27 @@ int falcon_prng_init(falcon_prng* p, WC_RNG* rng)
|
|||
if (p == NULL || rng == NULL)
|
||||
return BAD_FUNC_ARG;
|
||||
|
||||
ret = wc_RNG_GenerateBlock(rng, seed, (word32)sizeof(seed));
|
||||
if (ret == 0)
|
||||
ret = wc_InitShake256(&p->shake, NULL, INVALID_DEVID);
|
||||
if (ret == 0)
|
||||
ret = wc_Shake256_Absorb(&p->shake, seed, (word32)sizeof(seed));
|
||||
|
||||
p->ptr = 0;
|
||||
p->len = 0;
|
||||
p->err = 0;
|
||||
ForceZero(seed, (word32)sizeof(seed));
|
||||
|
||||
if (ret == 0)
|
||||
ret = falcon_prng_refill(p);
|
||||
ret = wc_RNG_GenerateBlock(rng, seed, (word32)sizeof(seed));
|
||||
if (ret == 0) {
|
||||
ret = wc_InitShake256(&p->shake, NULL, INVALID_DEVID);
|
||||
if (ret == 0) {
|
||||
ret = wc_Shake256_Absorb(&p->shake, seed, (word32)sizeof(seed));
|
||||
if (ret == 0)
|
||||
ret = falcon_prng_refill(p);
|
||||
/* On failure past a successful init the caller never sees a live
|
||||
* context (falcon_native_sign_msg only frees the sponge when this
|
||||
* function succeeded), so release it here. This matters in
|
||||
* WOLFSSL_ASYNC_CRYPT builds where wc_InitShake256 allocates a
|
||||
* device context. */
|
||||
if (ret != 0)
|
||||
wc_Shake256_Free(&p->shake);
|
||||
}
|
||||
}
|
||||
ForceZero(seed, (word32)sizeof(seed));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -322,6 +339,15 @@ int falcon_sampler_z(void* ctx, fpr mu, fpr isigma)
|
|||
int z0, z, b;
|
||||
fpr x;
|
||||
|
||||
/* A wedged PRNG (latched sticky error) turns every squeezed byte into
|
||||
* a constant, which can make the rejection test below deterministic --
|
||||
* and, if it rejects, this loop endless. Bail out instead: the value
|
||||
* returned is discarded, as falcon_sign_core rejects the entire
|
||||
* signature whenever p.err is set. */
|
||||
if (spc->p.err != 0) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Half-Gaussian sample, plus a random bit b turning it bimodal:
|
||||
* b = 1 -> use z0+1 (centered on 1), b = 0 -> use -z0 (centered 0). */
|
||||
z0 = falcon_gaussian0(&spc->p);
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ WOLFSSL_API int wc_Falcon_PublicKeyToDer(falcon_key* key, byte* output,
|
|||
WOLFSSL_LOCAL int falcon_native_make_key(falcon_key* key, WC_RNG* rng);
|
||||
WOLFSSL_LOCAL int falcon_native_sign_msg(const byte* in, word32 inLen,
|
||||
byte* out, word32* outLen, falcon_key* key, WC_RNG* rng);
|
||||
WOLFSSL_LOCAL int falcon_native_check_key(falcon_key* key);
|
||||
#endif
|
||||
WOLFSSL_LOCAL int falcon_native_verify_msg(const byte* sig, word32 sigLen,
|
||||
const byte* msg, word32 msgLen, int* res, falcon_key* key);
|
||||
|
|
|
|||
Loading…
Reference in New Issue