summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortb <>2025-06-14 07:50:37 +0000
committertb <>2025-06-14 07:50:37 +0000
commite5a554b79b2bc282794430de6918d535b52eb82c (patch)
treea2d392b89037077b9834b578d0ed32d512467972 /src
parent591a81a1eb45c279f1a5b696396c259b999cd61d (diff)
downloadopenbsd-e5a554b79b2bc282794430de6918d535b52eb82c.tar.gz
openbsd-e5a554b79b2bc282794430de6918d535b52eb82c.tar.bz2
openbsd-e5a554b79b2bc282794430de6918d535b52eb82c.zip
ec_asn1_test: for curves that have a seed, validate the parameters a and b
per X9.62, F.3.4.b. This ensures that the table entries in ec_curves.c for the NIST curves P-224, P-256, P-384, and P-521 are internally consistent and in particular that the seed is correct.
Diffstat (limited to 'src')
-rw-r--r--src/regress/lib/libcrypto/ec/ec_asn1_test.c197
1 files changed, 195 insertions, 2 deletions
diff --git a/src/regress/lib/libcrypto/ec/ec_asn1_test.c b/src/regress/lib/libcrypto/ec/ec_asn1_test.c
index d9501922de..a4066ae732 100644
--- a/src/regress/lib/libcrypto/ec/ec_asn1_test.c
+++ b/src/regress/lib/libcrypto/ec/ec_asn1_test.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ec_asn1_test.c,v 1.33 2025/05/04 05:00:03 tb Exp $ */ 1/* $OpenBSD: ec_asn1_test.c,v 1.34 2025/06/14 07:50:37 tb Exp $ */
2/* 2/*
3 * Copyright (c) 2017, 2021 Joel Sing <jsing@openbsd.org> 3 * Copyright (c) 2017, 2021 Joel Sing <jsing@openbsd.org>
4 * Copyright (c) 2024 Theo Buehler <tb@openbsd.org> 4 * Copyright (c) 2024 Theo Buehler <tb@openbsd.org>
@@ -20,9 +20,10 @@
20#include <string.h> 20#include <string.h>
21 21
22#include <openssl/bio.h> 22#include <openssl/bio.h>
23#include <openssl/ec.h>
24#include <openssl/err.h> 23#include <openssl/err.h>
24#include <openssl/ec.h>
25#include <openssl/objects.h> 25#include <openssl/objects.h>
26#include <openssl/sha.h>
26 27
27#include "ec_local.h" 28#include "ec_local.h"
28 29
@@ -2348,6 +2349,197 @@ ec_group_check_private_keys(void)
2348 return failed; 2349 return failed;
2349} 2350}
2350 2351
2352static void
2353ec_group_sha1_bignum(BIGNUM *out, const BIGNUM *in)
2354{
2355 char md[SHA_DIGEST_LENGTH];
2356 unsigned char *bin;
2357 size_t bin_len;
2358
2359 if (BN_num_bytes(in) <= 0)
2360 errx(1, "%s: invalid bignum", __func__);
2361
2362 bin_len = BN_num_bytes(in);
2363 if ((bin = calloc(1, bin_len)) == NULL)
2364 err(1, "calloc");
2365 if (BN_bn2bin(in, bin) <= 0)
2366 errx(1, "BN_bn2bin");
2367
2368 SHA1(bin, bin_len, md);
2369 free(bin);
2370
2371 if (BN_bin2bn(md, sizeof(md), out) == NULL)
2372 errx(1, "BN_bin2bn");
2373}
2374
2375static int
2376ec_group_check_seed(const EC_builtin_curve *curve, BN_CTX *ctx)
2377{
2378 EC_GROUP *group = NULL;
2379 BIGNUM *p, *a, *b, *pow2, *r, *seed_bn, *w;
2380 const unsigned char *seed;
2381 size_t seed_len;
2382 int i, g, h, s, t;
2383 int failed = 1;
2384
2385 if ((group = EC_GROUP_new_by_curve_name(curve->nid)) == NULL)
2386 errx(1, "EC_GROUP_new_by_curve_name");
2387
2388 BN_CTX_start(ctx);
2389
2390 if ((p = BN_CTX_get(ctx)) == NULL)
2391 errx(1, "p = BN_CTX_get()");
2392 if ((a = BN_CTX_get(ctx)) == NULL)
2393 errx(1, "a = BN_CTX_get()");
2394 if ((b = BN_CTX_get(ctx)) == NULL)
2395 errx(1, "b = BN_CTX_get()");
2396 if ((r = BN_CTX_get(ctx)) == NULL)
2397 errx(1, "r = BN_CTX_get()");
2398 if ((pow2 = BN_CTX_get(ctx)) == NULL)
2399 errx(1, "pow2 = BN_CTX_get()");
2400 if ((seed_bn = BN_CTX_get(ctx)) == NULL)
2401 errx(1, "seed_bn = BN_CTX_get()");
2402 if ((w = BN_CTX_get(ctx)) == NULL)
2403 errx(1, "w = BN_CTX_get()");
2404
2405 /*
2406 * If the curve has a seed, verify that its parameters a and b have
2407 * been selected using that seed, loosely following X9.62, F.3.4.b.
2408 * Otherwise there's nothing to do.
2409 */
2410 if ((seed = EC_GROUP_get0_seed(group)) == NULL)
2411 goto done;
2412 seed_len = EC_GROUP_get_seed_len(group);
2413
2414 /*
2415 * This isn't a requirement but happens to be the case for NIST
2416 * curves - the only built-in curves that have a seed.
2417 */
2418 if (seed_len != SHA_DIGEST_LENGTH) {
2419 fprintf(stderr, "%s FAIL: unexpected seed length. "
2420 "want %d, got %zu\n", __func__, SHA_DIGEST_LENGTH, seed_len);
2421 goto err;
2422 }
2423
2424 /* Seed length in bits, per F.3.3.b. */
2425 g = 8 * seed_len;
2426
2427 /*
2428 * Prepare to build the verifiably random element r of GFp by
2429 * concatenating the SHA-1 of modifications of the seed as a number.
2430 */
2431 if (BN_bin2bn(seed, seed_len, seed_bn) == NULL)
2432 errx(1, "BN_bin2bn");
2433
2434 if (!EC_GROUP_get_curve(group, p, a, b, ctx))
2435 errx(1, "EC_GROUP_get_curve");
2436
2437 t = BN_num_bits(p); /* bit length needed. */
2438 s = (t - 1) / 160; /* number of SHA-1 fitting in bit length. */
2439 h = t - 160 * s; /* remaining number of bits in r. */
2440
2441 /*
2442 * Steps 1 - 3: compute hash of the seed and take h - 1 rightmost bits.
2443 */
2444
2445 ec_group_sha1_bignum(r, seed_bn);
2446 BN_zero(pow2);
2447 if (!BN_set_bit(pow2, h - 1))
2448 errx(1, "BN_set_bit");
2449 if (!BN_mod(r, r, pow2, ctx))
2450 errx(1, "BN_nnmod");
2451
2452 /*
2453 * Steps 4 - 6: for i from 1 to s do Wi = SHA-1(SEED + i mod 2^g),
2454 * With W0 = r as already computed, let r = W0 || W1 || ... || Ws.
2455 */
2456
2457 BN_zero(pow2);
2458 if (!BN_set_bit(pow2, g))
2459 errx(1, "BN_set_bit");
2460
2461 for (i = 0; i < s; i++) {
2462 /*
2463 * This is a bit silly since the seed isn't going to have all
2464 * its bits set, so BN_add_word(seed_bn, 1) would do, but for
2465 * the sake of correctness...
2466 */
2467 if (!BN_mod_add(seed_bn, seed_bn, BN_value_one(), pow2, ctx))
2468 errx(1, "BN_mod_add");
2469
2470 ec_group_sha1_bignum(w, seed_bn);
2471
2472 if (!BN_lshift(r, r, 8 * SHA_DIGEST_LENGTH))
2473 errx(1, "BN_lshift");
2474 if (!BN_add(r, r, w))
2475 errx(1, "BN_add");
2476 }
2477
2478 /*
2479 * Step 7: check that r * b^2 == a^3 (mod p)
2480 */
2481
2482 /* Compute r = r * b^2 (mod p). */
2483 if (!BN_mod_sqr(b, b, p, ctx))
2484 errx(1, "BN_mod_sqr");
2485 if (!BN_mod_mul(r, r, b, p, ctx))
2486 errx(1, "BN_mod_mul");
2487
2488 /* Compute a = a^3 (mod p). */
2489 if (!BN_mod_sqr(b, a, p, ctx))
2490 errx(1, "BN_mod_sqr");
2491 if (!BN_mod_mul(a, a, b, p, ctx))
2492 errx(1, "BN_mod_mul");
2493
2494 /*
2495 * XXX - this assumes that a, b, p >= 0, so the results are in [0, p).
2496 * This is currently enforced in the EC code.
2497 */
2498 if (BN_cmp(r, a) != 0) {
2499 fprintf(stderr, "FAIL: %s verification failed for %s\nr * b^2:\t",
2500 __func__, curve->comment);
2501 BN_print_fp(stderr, r);
2502 fprintf(stderr, "\na^3:\t\t");
2503 BN_print_fp(stderr, a);
2504 fprintf(stderr, "\n");
2505 goto err;
2506 }
2507
2508 done:
2509 failed = 0;
2510
2511 err:
2512 BN_CTX_end(ctx);
2513 EC_GROUP_free(group);
2514
2515 return failed;
2516}
2517
2518static int
2519ec_group_check_seeds(void)
2520{
2521 BN_CTX *ctx = NULL;
2522 EC_builtin_curve *all_curves = NULL;
2523 size_t curve_id, ncurves;
2524 int failed = 0;
2525
2526 if ((ctx = BN_CTX_new()) == NULL)
2527 errx(1, "BN_CTX_new");
2528
2529 ncurves = EC_get_builtin_curves(NULL, 0);
2530 if ((all_curves = calloc(ncurves, sizeof(*all_curves))) == NULL)
2531 err(1, "calloc builtin curves");
2532 EC_get_builtin_curves(all_curves, ncurves);
2533
2534 for (curve_id = 0; curve_id < ncurves; curve_id++)
2535 failed |= ec_group_check_seed(&all_curves[curve_id], ctx);
2536
2537 free(all_curves);
2538 BN_CTX_free(ctx);
2539
2540 return failed;
2541}
2542
2351int 2543int
2352main(int argc, char **argv) 2544main(int argc, char **argv)
2353{ 2545{
@@ -2359,6 +2551,7 @@ main(int argc, char **argv)
2359 failed |= ec_group_roundtrip_builtin_curves(); 2551 failed |= ec_group_roundtrip_builtin_curves();
2360 failed |= ec_group_non_builtin_curves(); 2552 failed |= ec_group_non_builtin_curves();
2361 failed |= ec_group_check_private_keys(); 2553 failed |= ec_group_check_private_keys();
2554 failed |= ec_group_check_seeds();
2362 2555
2363 return failed; 2556 return failed;
2364} 2557}