feat(crypto): ECDSA-P256 signing, RFC 6979 nonce (rv2 9 phase G1b)
- wo_ecdsa_p256_sha256_sign: deterministic nonce (RFC 6979 HMAC-DRBG over
the key + message — no RNG, no nonce-reuse/bias risk), then r = (k*G).x
mod n and s = k^-1 (z + r*d) mod n
- constant-time in the secret: jmul_ct (double-and-add-always + point
cmov) for k*G, and bn_modexp_ct for k^-1 mod n and the affine inversion.
Known residual (documented): the ladder leaks k's leading-zero count (a
bit-length hint, not the key) — a complete-formula/Montgomery-ladder
upgrade is the named follow-up
- KAT: byte-for-byte vs the RFC 6979 A.2.5 P-256/SHA-256 vectors ("sample"
+ "test"), our sign verifies with our verify, determinism checked.
test_crypto 115, ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 1bc6d04f9e18180dc7d0a6e5e7021dbd588e499a)
This commit is contained in:
parent
631506d36f
commit
df5054b07d
4 changed files with 179 additions and 0 deletions
|
|
@ -1511,6 +1511,103 @@ int wo_ecdsa_p256_sha256_verify(const uint8_t qx[32], const uint8_t qy[32],
|
||||||
return fp_eq(xr, rv) ? 1 : 0;
|
return fp_eq(xr, rv) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- ECDSA-P256 signing (rv2 9 phase G1b) --------------------------------
|
||||||
|
* Deterministic nonce (RFC 6979) — no RNG, no nonce-reuse/bias risk, and
|
||||||
|
* KAT-able against the published vectors. The scalar multiply k*G and the
|
||||||
|
* inversions touch the secret k/d, so they use the constant-time modexp and a
|
||||||
|
* double-and-add-always ladder. Known residual: the ladder's point-at-infinity
|
||||||
|
* handling leaks k's leading-zero count (a bit-length hint, not the key); a
|
||||||
|
* complete-formula / Montgomery-ladder upgrade is a named follow-up. */
|
||||||
|
|
||||||
|
static void jpt_cmov(jpt *d, const jpt *s, uint64_t mask) {
|
||||||
|
bn_cmov(d->X, s->X, mask, 4);
|
||||||
|
bn_cmov(d->Y, s->Y, mask, 4);
|
||||||
|
bn_cmov(d->Z, s->Z, mask, 4);
|
||||||
|
}
|
||||||
|
/* R = k*pt, constant-time in k (double-and-add-always). */
|
||||||
|
static void jmul_ct(const modctx *P, jpt *o, const uint8_t k[32], const jpt *pt) {
|
||||||
|
jpt acc; for (int i = 0; i < 4; i++) { acc.X[i] = 0; acc.Y[i] = 0; acc.Z[i] = 0; }
|
||||||
|
for (int bit = 255; bit >= 0; bit--) {
|
||||||
|
jdouble(P, &acc, &acc);
|
||||||
|
jpt t; jadd(P, &t, &acc, pt);
|
||||||
|
uint64_t m = (uint64_t)0 - (uint64_t)((k[(255 - bit) / 8] >> (7 - ((255 - bit) & 7))) & 1);
|
||||||
|
jpt_cmov(&acc, &t, m);
|
||||||
|
}
|
||||||
|
*o = acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HMAC-SHA256 with a 32-byte key (RFC 6979's DRBG uses fixed-size keys). */
|
||||||
|
static void hmac32(const uint8_t key[32], const uint8_t *msg, size_t mlen,
|
||||||
|
uint8_t out[32]) {
|
||||||
|
wo_hmac_sha256(key, 32, msg, mlen, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ECDSA-P256 sign over SHA-256 with an RFC 6979 deterministic nonce. Private
|
||||||
|
* scalar d and 32-byte message hash in; (r,s) out, big-endian. 0 ok, -1 on
|
||||||
|
* failure (astronomically unlikely nonce exhaustion, or d out of range). */
|
||||||
|
int wo_ecdsa_p256_sha256_sign(const uint8_t d[32], const uint8_t hash[32],
|
||||||
|
uint8_t r_out[32], uint8_t s_out[32]) {
|
||||||
|
modctx P, N;
|
||||||
|
modctx_init(&P, P256_P);
|
||||||
|
modctx_init(&N, P256_N);
|
||||||
|
fp dfp; bn_from_be(dfp, d, 32);
|
||||||
|
if (fp_zero(dfp) || bn_ge(dfp, N.m, 4)) return -1; /* d in [1, n-1] */
|
||||||
|
|
||||||
|
/* z = hash mod n, and bits2octets(hash) = z as 32 bytes */
|
||||||
|
fp z; bn_from_be(z, hash, 32);
|
||||||
|
if (bn_ge(z, N.m, 4)) bn_sub(z, z, N.m, 4);
|
||||||
|
uint8_t h1o[32]; bn_to_be(h1o, 32, z, 4);
|
||||||
|
|
||||||
|
/* RFC 6979 §3.2 seeding */
|
||||||
|
uint8_t V[32], K[32], buf[32 + 1 + 32 + 32];
|
||||||
|
memset(V, 0x01, 32); memset(K, 0x00, 32);
|
||||||
|
memcpy(buf, V, 32); buf[32] = 0x00; memcpy(buf + 33, d, 32); memcpy(buf + 65, h1o, 32);
|
||||||
|
hmac32(K, buf, 97, K); hmac32(K, V, 32, V);
|
||||||
|
memcpy(buf, V, 32); buf[32] = 0x01; memcpy(buf + 33, d, 32); memcpy(buf + 65, h1o, 32);
|
||||||
|
hmac32(K, buf, 97, K); hmac32(K, V, 32, V);
|
||||||
|
|
||||||
|
/* pre-mont G */
|
||||||
|
jpt G; fp gx, gy;
|
||||||
|
bn_from_be(gx, P256_GX, 32); bn_from_be(gy, P256_GY, 32);
|
||||||
|
to_mont(&P, G.X, gx); to_mont(&P, G.Y, gy);
|
||||||
|
for (int i = 0; i < 4; i++) G.Z[i] = P.one_mont[i];
|
||||||
|
|
||||||
|
for (int tries = 0; tries < 64; tries++) {
|
||||||
|
hmac32(K, V, 32, V); /* T = V (qlen = 256) */
|
||||||
|
fp kfp; bn_from_be(kfp, V, 32);
|
||||||
|
if (!fp_zero(kfp) && !bn_ge(kfp, N.m, 4)) {
|
||||||
|
jpt R; jmul_ct(&P, &R, V, &G);
|
||||||
|
if (!fp_zero(R.Z)) {
|
||||||
|
/* affine x of R (Z^-2 * X, mod p, all constant-time) */
|
||||||
|
fp Xn, Zn, zinv, zinv2, tm, xaff, rr;
|
||||||
|
from_mont(&P, Xn, R.X); from_mont(&P, Zn, R.Z);
|
||||||
|
bn_modexp_ct(zinv, Zn, P.m, 4, P256_PM2, 32);
|
||||||
|
to_mont(&P, tm, zinv); fpmul(&P, zinv2, tm, zinv);
|
||||||
|
to_mont(&P, tm, Xn); fpmul(&P, xaff, tm, zinv2);
|
||||||
|
for (int i = 0; i < 4; i++) rr[i] = xaff[i];
|
||||||
|
if (bn_ge(rr, N.m, 4)) bn_sub(rr, rr, N.m, 4);
|
||||||
|
if (!fp_zero(rr)) {
|
||||||
|
/* s = k^-1 (z + r*d) mod n */
|
||||||
|
fp kinv, rd, zrd, ss;
|
||||||
|
bn_modexp_ct(kinv, kfp, N.m, 4, P256_NM2, 32);
|
||||||
|
to_mont(&N, tm, rr); fpmul(&N, rd, tm, dfp); /* r*d */
|
||||||
|
modadd(zrd, z, rd, N.m, 4); /* z + r*d */
|
||||||
|
to_mont(&N, tm, kinv); fpmul(&N, ss, tm, zrd); /* k^-1*(z+rd) */
|
||||||
|
if (!fp_zero(ss)) {
|
||||||
|
bn_to_be(r_out, 32, rr, 4);
|
||||||
|
bn_to_be(s_out, 32, ss, 4);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* reject: K = HMAC(K, V||0x00); V = HMAC(K, V) */
|
||||||
|
memcpy(buf, V, 32); buf[32] = 0x00;
|
||||||
|
hmac32(K, buf, 33, K); hmac32(K, V, 32, V);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- X.509 / ASN.1 DER (rv2 9 phase E, core) ----------------------------
|
/* ---- X.509 / ASN.1 DER (rv2 9 phase E, core) ----------------------------
|
||||||
* A defensive DER reader and the certificate-field extraction TLS needs:
|
* A defensive DER reader and the certificate-field extraction TLS needs:
|
||||||
* tbsCertificate (raw, for signature verification), the signature algorithm,
|
* tbsCertificate (raw, for signature verification), the signature algorithm,
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,12 @@ int wo_ecdsa_p256_sha256_verify(const uint8_t qx[32], const uint8_t qy[32],
|
||||||
const uint8_t r[32], const uint8_t s[32],
|
const uint8_t r[32], const uint8_t s[32],
|
||||||
const uint8_t hash[32]);
|
const uint8_t hash[32]);
|
||||||
|
|
||||||
|
/* ECDSA-P256 SIGN over SHA-256 with an RFC 6979 deterministic nonce (rv2 9
|
||||||
|
* phase G1b). Private scalar d + 32-byte hash in; (r,s) big-endian out. The
|
||||||
|
* secret-dependent scalar mult and inversions are constant-time. 0 ok, -1. */
|
||||||
|
int wo_ecdsa_p256_sha256_sign(const uint8_t d[32], const uint8_t hash[32],
|
||||||
|
uint8_t r_out[32], uint8_t s_out[32]);
|
||||||
|
|
||||||
/* X.509 / ASN.1 DER (rv2 9 phase E, core). Internal C consumed by the TLS
|
/* X.509 / ASN.1 DER (rv2 9 phase E, core). Internal C consumed by the TLS
|
||||||
* handshake. key_alg / return values use the WO_X509_* enums in crypto.c
|
* handshake. key_alg / return values use the WO_X509_* enums in crypto.c
|
||||||
* (RSA = 1, EC_P256 = 2). */
|
* (RSA = 1, EC_P256 = 2). */
|
||||||
|
|
|
||||||
56
runtime/test/ecdsa_sign_vectors.h
Normal file
56
runtime/test/ecdsa_sign_vectors.h
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
/* RFC 6979 A.2.5 ECDSA P-256/SHA-256 KAT (deterministic nonce). */
|
||||||
|
static const unsigned char ecs_d[] = {
|
||||||
|
0xc9,0xaf,0xa9,0xd8,0x45,0xba,0x75,0x16,0x6b,0x5c,0x21,0x57,
|
||||||
|
0x67,0xb1,0xd6,0x93,0x4e,0x50,0xc3,0xdb,0x36,0xe8,0x9b,0x12,
|
||||||
|
0x7b,0x8a,0x62,0x2b,0x12,0x0f,0x67,0x21,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char ecs_qx[] = {
|
||||||
|
0x60,0xfe,0xd4,0xba,0x25,0x5a,0x9d,0x31,0xc9,0x61,0xeb,0x74,
|
||||||
|
0xc6,0x35,0x6d,0x68,0xc0,0x49,0xb8,0x92,0x3b,0x61,0xfa,0x6c,
|
||||||
|
0xe6,0x69,0x62,0x2e,0x60,0xf2,0x9f,0xb6,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char ecs_qy[] = {
|
||||||
|
0x79,0x03,0xfe,0x10,0x08,0xb8,0xbc,0x99,0xa4,0x1a,0xe9,0xe9,
|
||||||
|
0x56,0x28,0xbc,0x64,0xf2,0xf1,0xb2,0x0c,0x2d,0x7e,0x9f,0x51,
|
||||||
|
0x77,0xa3,0xc2,0x94,0xd4,0x46,0x22,0x99,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char ecs_sample_mhash[] = {
|
||||||
|
0xaf,0x2b,0xdb,0xe1,0xaa,0x9b,0x6e,0xc1,0xe2,0xad,0xe1,0xd6,
|
||||||
|
0x94,0xf4,0x1f,0xc7,0x1a,0x83,0x1d,0x02,0x68,0xe9,0x89,0x15,
|
||||||
|
0x62,0x11,0x3d,0x8a,0x62,0xad,0xd1,0xbf,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char ecs_sample_r[] = {
|
||||||
|
0xef,0xd4,0x8b,0x2a,0xac,0xb6,0xa8,0xfd,0x11,0x40,0xdd,0x9c,
|
||||||
|
0xd4,0x5e,0x81,0xd6,0x9d,0x2c,0x87,0x7b,0x56,0xaa,0xf9,0x91,
|
||||||
|
0xc3,0x4d,0x0e,0xa8,0x4e,0xaf,0x37,0x16,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char ecs_sample_s[] = {
|
||||||
|
0xf7,0xcb,0x1c,0x94,0x2d,0x65,0x7c,0x41,0xd4,0x36,0xc7,0xa1,
|
||||||
|
0xb6,0xe2,0x9f,0x65,0xf3,0xe9,0x00,0xdb,0xb9,0xaf,0xf4,0x06,
|
||||||
|
0x4d,0xc4,0xab,0x2f,0x84,0x3a,0xcd,0xa8,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char ecs_test_mhash[] = {
|
||||||
|
0x9f,0x86,0xd0,0x81,0x88,0x4c,0x7d,0x65,0x9a,0x2f,0xea,0xa0,
|
||||||
|
0xc5,0x5a,0xd0,0x15,0xa3,0xbf,0x4f,0x1b,0x2b,0x0b,0x82,0x2c,
|
||||||
|
0xd1,0x5d,0x6c,0x15,0xb0,0xf0,0x0a,0x08,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char ecs_test_r[] = {
|
||||||
|
0xf1,0xab,0xb0,0x23,0x51,0x83,0x51,0xcd,0x71,0xd8,0x81,0x56,
|
||||||
|
0x7b,0x1e,0xa6,0x63,0xed,0x3e,0xfc,0xf6,0xc5,0x13,0x2b,0x35,
|
||||||
|
0x4f,0x28,0xd3,0xb0,0xb7,0xd3,0x83,0x67,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char ecs_test_s[] = {
|
||||||
|
0x01,0x9f,0x41,0x13,0x74,0x2a,0x2b,0x14,0xbd,0x25,0x92,0x6b,
|
||||||
|
0x49,0xc6,0x49,0x15,0x5f,0x26,0x7e,0x60,0xd3,0x81,0x4b,0x4c,
|
||||||
|
0x0c,0xc8,0x42,0x50,0xe4,0x6f,0x00,0x83,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* (published r,s cross-verified against Q by python) */
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "t.h"
|
#include "t.h"
|
||||||
#include "x509_vectors.h"
|
#include "x509_vectors.h"
|
||||||
#include "rsa_sign_vectors.h"
|
#include "rsa_sign_vectors.h"
|
||||||
|
#include "ecdsa_sign_vectors.h"
|
||||||
|
|
||||||
static void hex(const uint8_t *d, size_t n, char *out) {
|
static void hex(const uint8_t *d, size_t n, char *out) {
|
||||||
static const char *h = "0123456789abcdef";
|
static const char *h = "0123456789abcdef";
|
||||||
|
|
@ -472,5 +473,24 @@ int main(void) {
|
||||||
sizeof rsasig_e, sig, 256, rsasig_mhash, 32) == 0);
|
sizeof rsasig_e, sig, 256, rsasig_mhash, 32) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ECDSA-P256 SIGN with RFC 6979 nonce (rv2 9 phase G1b) — byte-for-byte vs
|
||||||
|
* the RFC 6979 A.2.5 vectors, and our sign verifies with our verify. */
|
||||||
|
{
|
||||||
|
uint8_t r[32], s[32];
|
||||||
|
/* "sample" */
|
||||||
|
T_CHECK(wo_ecdsa_p256_sha256_sign(ecs_d, ecs_sample_mhash, r, s) == 0);
|
||||||
|
T_CHECK(memcmp(r, ecs_sample_r, 32) == 0 && memcmp(s, ecs_sample_s, 32) == 0);
|
||||||
|
T_CHECK(wo_ecdsa_p256_sha256_verify(ecs_qx, ecs_qy, r, s, ecs_sample_mhash) == 1);
|
||||||
|
/* "test" */
|
||||||
|
T_CHECK(wo_ecdsa_p256_sha256_sign(ecs_d, ecs_test_mhash, r, s) == 0);
|
||||||
|
T_CHECK(memcmp(r, ecs_test_r, 32) == 0 && memcmp(s, ecs_test_s, 32) == 0);
|
||||||
|
T_CHECK(wo_ecdsa_p256_sha256_verify(ecs_qx, ecs_qy, r, s, ecs_test_mhash) == 1);
|
||||||
|
/* determinism: same input, same signature */
|
||||||
|
uint8_t r2[32], s2[32];
|
||||||
|
wo_ecdsa_p256_sha256_sign(ecs_d, ecs_sample_mhash, r2, s2);
|
||||||
|
wo_ecdsa_p256_sha256_sign(ecs_d, ecs_sample_mhash, r, s);
|
||||||
|
T_CHECK(memcmp(r, r2, 32) == 0 && memcmp(s, s2, 32) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
return t_report("test_crypto");
|
return t_report("test_crypto");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue