Skip to content

Commit 13a0163

Browse files
committed
eccsi: fix universal signature forgery via r=0/s=0
wc_VerifyEccsiHash did not validate that r and s lie in [1, q-1] after decoding them from the signature buffer. With s=0 the scalar multiplication [s](...) returns the point at infinity (J_x=0); with r=0 the final mp_cmp(0,0)==MP_EQ check then accepts the forged signature unconditionally against any message and any identity. Add [1, q-1] range checks for r (in wc_VerifyEccsiHash, after params are loaded) and for s (in eccsi_calc_j, after eccsi_decode_sig_s), mirroring the checks already present in wc_ecc_check_r_s_range. Add a defense-in-depth point-at-infinity guard on J before the final comparison. Reported-by: Nicholas Carlini (Anthropic) & Bronson Yen (Calif.io)
1 parent 10953f0 commit 13a0163

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

wolfcrypt/src/eccsi.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,18 @@ static int eccsi_calc_j(EccsiKey* key, const mp_int* hem, const byte* sig,
21592159
if (err == 0) {
21602160
err = eccsi_decode_sig_s(key, sig, sigSz, s);
21612161
}
2162+
/* Validate s is in [1, q-1]: reject zero or out-of-range second signature
2163+
* component. With s=0, [s](...) yields the point at infinity whose
2164+
* affine x-coordinate is 0, making the final mp_cmp(0,0) accept any
2165+
* forged signature. */
2166+
if (err == 0) {
2167+
if (mp_iszero(s)) {
2168+
err = MP_ZERO_E;
2169+
}
2170+
else if (mp_cmp(s, &key->params.order) != MP_LT) {
2171+
err = ECC_OUT_OF_RANGE_E;
2172+
}
2173+
}
21622174
/* [s]( [HE]G + [r]Y ) */
21632175
if (err == 0) {
21642176
err = eccsi_mulmod_point(key, s, j, j, 1);
@@ -2238,6 +2250,19 @@ int wc_VerifyEccsiHash(EccsiKey* key, enum wc_HashType hashType,
22382250
err = mp_montgomery_setup(&params->prime, &mp);
22392251
}
22402252

2253+
/* Validate r is in [1, q-1]: reject zero or out-of-range first signature
2254+
* component before any scalar multiplication takes place.
2255+
* Without this check, r=0 causes J_x=0 and the final mp_cmp(0,0)==MP_EQ
2256+
* comparison accepts the forged signature unconditionally. */
2257+
if (err == 0) {
2258+
if (mp_iszero(r)) {
2259+
err = MP_ZERO_E;
2260+
}
2261+
else if (mp_cmp(r, &params->order) != MP_LT) {
2262+
err = ECC_OUT_OF_RANGE_E;
2263+
}
2264+
}
2265+
22412266
/* Step 1: Validate PVT is on curve */
22422267
if (err == 0) {
22432268
err = wc_ecc_is_point(pvt, &params->a, &params->b, &params->prime);
@@ -2273,6 +2298,16 @@ int wc_VerifyEccsiHash(EccsiKey* key, enum wc_HashType hashType,
22732298
key->params.haveBase = 0;
22742299
}
22752300

2301+
/* Defense-in-depth: reject J = point at infinity before the final
2302+
* comparison. Catches any future path that might reach this point
2303+
* with a neutral-element result (e.g. s = 0 mod q for a non-zero
2304+
* encoded s). */
2305+
if (err == 0) {
2306+
if (wc_ecc_point_is_at_infinity(j)) {
2307+
err = ECC_INF_E;
2308+
}
2309+
}
2310+
22762311
/* Step 6: Jx fitting, compare with r */
22772312
if (err == 0) {
22782313
jx = &key->tmp;

0 commit comments

Comments
 (0)