diff --git a/runtime/src/crypto.c b/runtime/src/crypto.c index 61ab407..24f3529 100644 --- a/runtime/src/crypto.c +++ b/runtime/src/crypto.c @@ -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; } +/* ---- 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) ---------------------------- * A defensive DER reader and the certificate-field extraction TLS needs: * tbsCertificate (raw, for signature verification), the signature algorithm, diff --git a/runtime/src/crypto.h b/runtime/src/crypto.h index 213db83..9d9061b 100644 --- a/runtime/src/crypto.h +++ b/runtime/src/crypto.h @@ -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 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 * handshake. key_alg / return values use the WO_X509_* enums in crypto.c * (RSA = 1, EC_P256 = 2). */ diff --git a/runtime/test/ecdsa_sign_vectors.h b/runtime/test/ecdsa_sign_vectors.h new file mode 100644 index 0000000..7fb5d35 --- /dev/null +++ b/runtime/test/ecdsa_sign_vectors.h @@ -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) */ diff --git a/runtime/test/test_crypto.c b/runtime/test/test_crypto.c index eb193a8..06b3fea 100644 --- a/runtime/test/test_crypto.c +++ b/runtime/test/test_crypto.c @@ -9,6 +9,7 @@ #include "t.h" #include "x509_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 const char *h = "0123456789abcdef"; @@ -472,5 +473,24 @@ int main(void) { 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"); }