From 23b6ef4894679aa0278c93de29007d1e695856ee Mon Sep 17 00:00:00 2001 From: Watson Ladd Date: Tue, 21 Nov 2023 12:59:05 -0500 Subject: [PATCH] Allow group methods to customize initialization for speed This commit also adds an implementation for P256 that avoids some expensive initialization of Montgomery arithmetic structures in favor of precomputation. Since ECC groups are not always cached by higher layers this brings significant savings to TLS handshakes. Reviewed-by: Shane Lontis Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/22746) --- CHANGES.md | 5 ++ crypto/bn/bn_mont.c | 42 +++++++++++++ crypto/ec/ec_curve.c | 36 +++++++++-- crypto/ec/ec_local.h | 1 + crypto/ec/ecp_nistz256.c | 128 ++++++++++++++++++++++++++++++++++++++- include/crypto/bn.h | 6 ++ test/ec_internal_test.c | 64 ++++++++++++++++++++ 7 files changed, 275 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 49dbe58502630..9918e10c972ba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -95,6 +95,11 @@ OpenSSL 3.4 *Alexander Kanavin* + * ECC groups may now customize their initialization to save CPU by using + precomputed values. This is used by the P-256 implementation. + + *Watson Ladd* + OpenSSL 3.3 ----------- diff --git a/crypto/bn/bn_mont.c b/crypto/bn/bn_mont.c index 8b4c7900ad47f..7cd16c66ee8af 100644 --- a/crypto/bn/bn_mont.c +++ b/crypto/bn/bn_mont.c @@ -465,3 +465,45 @@ BN_MONT_CTX *BN_MONT_CTX_set_locked(BN_MONT_CTX **pmont, CRYPTO_RWLOCK *lock, CRYPTO_THREAD_unlock(lock); return ret; } + +int ossl_bn_mont_ctx_set(BN_MONT_CTX *ctx, const BIGNUM *modulus, int ri, const unsigned char *rr, + size_t rrlen, uint32_t nlo, uint32_t nhi) +{ + if (BN_copy(&ctx->N, modulus) == NULL) + return 0; + if (BN_bin2bn(rr, rrlen, &ctx->RR) == NULL) + return 0; + ctx->ri = ri; +#if (BN_BITS2 <= 32) && defined(OPENSSL_BN_ASM_MONT) + ctx->n0[0] = nlo; + ctx->n0[1] = nhi; +#elif BN_BITS2 <= 32 + ctx->n0[0] = nlo; + ctx->n0[1] = 0; +#else + ctx->n0[0] = ((BN_ULONG)nhi << 32)| nlo; + ctx->n0[1] = 0; +#endif + + return 1; +} + +int ossl_bn_mont_ctx_eq(const BN_MONT_CTX *m1, const BN_MONT_CTX *m2) +{ + if (m1->ri != m2->ri) + return 0; + if (BN_cmp(&m1->RR, &m2->RR) != 0) + return 0; + if (m1->flags != m2->flags) + return 0; +#ifdef MONT_WORD + if (m1->n0[0] != m2->n0[0]) + return 0; + if (m1->n0[1] != m2->n0[1]) + return 0; +#else + if (BN_cmp(&m1->Ni, &m2->Ni) != 0) + return 0; +#endif + return 1; +} diff --git a/crypto/ec/ec_curve.c b/crypto/ec/ec_curve.c index d703d16b3cae8..75feaa79d499c 100644 --- a/crypto/ec/ec_curve.c +++ b/crypto/ec/ec_curve.c @@ -383,7 +383,7 @@ static const struct { static const struct { EC_CURVE_DATA h; - unsigned char data[20 + 32 * 6]; + unsigned char data[20 + 32 * 8]; } _EC_X9_62_PRIME_256V1 = { { NID_X9_62_prime_field, 20, 32, 1 @@ -415,7 +415,15 @@ static const struct { /* order */ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, - 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51 + 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, + /* RR for prime */ + 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + /* RR for order */ + 0x66, 0xe1, 0x2d, 0x94, 0xf3, 0xd9, 0x56, 0x20, 0x28, 0x45, 0xb2, 0x39, + 0x2b, 0x6b, 0xec, 0x59, 0x46, 0x99, 0x79, 0x9c, 0x49, 0xbd, 0x6f, 0xa6, + 0x83, 0x24, 0x4c, 0x95, 0xbe, 0x79, 0xee, 0xa2 } }; @@ -3168,6 +3176,24 @@ static EC_GROUP *ec_group_new_from_data(OSSL_LIB_CTX *libctx, seed_len = data->seed_len; param_len = data->param_len; params = (const unsigned char *)(data + 1); /* skip header */ + + if (curve.meth != NULL) { + meth = curve.meth(); + if ((group = ossl_ec_group_new_ex(libctx, propq, meth)) == NULL) { + ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); + goto err; + } + if (group->meth->group_full_init != NULL) { + if (!group->meth->group_full_init(group, params)){ + ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); + goto err; + } + EC_GROUP_set_curve_name(group, curve.nid); + BN_CTX_free(ctx); + return group; + } + } + params += seed_len; /* skip seed */ if ((p = BN_bin2bn(params + 0 * param_len, param_len, NULL)) == NULL @@ -3177,10 +3203,8 @@ static EC_GROUP *ec_group_new_from_data(OSSL_LIB_CTX *libctx, goto err; } - if (curve.meth != 0) { - meth = curve.meth(); - if (((group = ossl_ec_group_new_ex(libctx, propq, meth)) == NULL) || - (!(group->meth->group_set_curve(group, p, a, b, ctx)))) { + if (group != NULL) { + if (group->meth->group_set_curve(group, p, a, b, ctx) == 0) { ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); goto err; } diff --git a/crypto/ec/ec_local.h b/crypto/ec/ec_local.h index 2814d8739438f..a041db9c1335c 100644 --- a/crypto/ec/ec_local.h +++ b/crypto/ec/ec_local.h @@ -196,6 +196,7 @@ struct ec_method_st { int (*ladder_post)(const EC_GROUP *group, EC_POINT *r, EC_POINT *s, EC_POINT *p, BN_CTX *ctx); + int (*group_full_init)(EC_GROUP *group, const unsigned char *data); }; /* diff --git a/crypto/ec/ecp_nistz256.c b/crypto/ec/ecp_nistz256.c index 5760639a2ee24..765c344bec70a 100644 --- a/crypto/ec/ecp_nistz256.c +++ b/crypto/ec/ecp_nistz256.c @@ -1445,6 +1445,131 @@ static int ecp_nistz256_inv_mod_ord(const EC_GROUP *group, BIGNUM *r, # define ecp_nistz256_inv_mod_ord NULL #endif +static int ecp_nistz256group_full_init(EC_GROUP *group, + const unsigned char *params) { + BN_CTX *ctx = NULL; + BN_MONT_CTX *mont = NULL, *ordmont = NULL; + const int param_len = 32; + const int seed_len = 20; + int ok = 0; + uint32_t hi_order_n = 0xccd1c8aa; + uint32_t lo_order_n = 0xee00bc4f; + BIGNUM *p = NULL, *a = NULL, *b = NULL, *x = NULL, *y = NULL, *one = NULL, + *order = NULL; + EC_POINT *P = NULL; + + if ((ctx = BN_CTX_new_ex(group->libctx)) == NULL) { + ERR_raise(ERR_LIB_EC, ERR_R_MALLOC_FAILURE); + return 0; + } + + if (!EC_GROUP_set_seed(group, params, seed_len)) { + ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); + goto err; + } + params += seed_len; + + if ((p = BN_bin2bn(params + 0 * param_len, param_len, NULL)) == NULL + || (a = BN_bin2bn(params + 1 * param_len, param_len, NULL)) == NULL + || (b = BN_bin2bn(params + 2 * param_len, param_len, NULL)) == NULL) { + ERR_raise(ERR_LIB_EC, ERR_R_BN_LIB); + goto err; + } + + /* + * Set up curve params and montgomery for field + * Start by setting up montgomery and one + */ + mont = BN_MONT_CTX_new(); + if (mont == NULL) + goto err; + + if (!ossl_bn_mont_ctx_set(mont, p, 256, params + 6 * param_len, param_len, + 1, 0)) + goto err; + + one = BN_new(); + if (one == NULL) { + ERR_raise(ERR_LIB_EC, ERR_R_BN_LIB); + goto err; + } + if (!BN_to_montgomery(one, BN_value_one(), mont, ctx)){ + ERR_raise(ERR_LIB_EC, ERR_R_BN_LIB); + goto err; + } + group->field_data1 = mont; + mont = NULL; + group->field_data2 = one; + one = NULL; + + if (!ossl_ec_GFp_simple_group_set_curve(group, p, a, b, ctx)) { + ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); + goto err; + } + + if ((P = EC_POINT_new(group)) == NULL) { + ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); + goto err; + } + + if ((x = BN_bin2bn(params + 3 * param_len, param_len, NULL)) == NULL + || (y = BN_bin2bn(params + 4 * param_len, param_len, NULL)) == NULL) { + ERR_raise(ERR_LIB_EC, ERR_R_BN_LIB); + goto err; + } + if (!EC_POINT_set_affine_coordinates(group, P, x, y, ctx)) { + ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); + goto err; + } + if ((order = BN_bin2bn(params + 5 * param_len, param_len, NULL)) == NULL + || !BN_set_word(x, (BN_ULONG)1)) { // cofactor is 1 + ERR_raise(ERR_LIB_EC, ERR_R_BN_LIB); + goto err; + } + + /* + * Set up generator and order and montgomery data + */ + group->generator = EC_POINT_new(group); + if (group->generator == NULL){ + ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); + goto err; + } + if (!EC_POINT_copy(group->generator, P)) + goto err; + if (!BN_copy(group->order, order)) + goto err; + if (!BN_set_word(group->cofactor, 1)) + goto err; + + ordmont = BN_MONT_CTX_new(); + if (ordmont == NULL) + goto err; + if (!ossl_bn_mont_ctx_set(ordmont, order, 256, params + 7 * param_len, + param_len, lo_order_n, hi_order_n)) + goto err; + + group->mont_data = ordmont; + ordmont = NULL; + + ok = 1; + + err: + EC_POINT_free(P); + BN_CTX_free(ctx); + BN_MONT_CTX_free(mont); + BN_MONT_CTX_free(ordmont); + BN_free(p); + BN_free(one); + BN_free(a); + BN_free(b); + BN_free(order); + BN_free(x); + BN_free(y); + + return ok; +} + const EC_METHOD *EC_GFp_nistz256_method(void) { static const EC_METHOD ret = { @@ -1501,7 +1626,8 @@ const EC_METHOD *EC_GFp_nistz256_method(void) 0, /* blind_coordinates */ 0, /* ladder_pre */ 0, /* ladder_step */ - 0 /* ladder_post */ + 0, /* ladder_post */ + ecp_nistz256group_full_init }; return &ret; diff --git a/include/crypto/bn.h b/include/crypto/bn.h index 9a988a467de27..128cae3bf8558 100644 --- a/include/crypto/bn.h +++ b/include/crypto/bn.h @@ -135,3 +135,9 @@ int s390x_crt(BIGNUM *r, const BIGNUM *i, const BIGNUM *p, const BIGNUM *q, const BIGNUM *dmp, const BIGNUM *dmq, const BIGNUM *iqmp); #endif + +int ossl_bn_mont_ctx_set(BN_MONT_CTX *ctx, const BIGNUM *modulus, int ri, + const unsigned char *rr, size_t rrlen, + uint32_t nlo, uint32_t nhi); + +int ossl_bn_mont_ctx_eq(const BN_MONT_CTX *m1, const BN_MONT_CTX *m2); diff --git a/test/ec_internal_test.c b/test/ec_internal_test.c index 5076f9894d5b8..8e99f6210507d 100644 --- a/test/ec_internal_test.c +++ b/test/ec_internal_test.c @@ -16,6 +16,7 @@ #include "testutil.h" #include #include "ec_local.h" +#include #include static size_t crv_len = 0; @@ -433,6 +434,68 @@ int ecpkparams_i2d2i_test(int n) return testresult; } + +static int check_bn_mont_ctx(BN_MONT_CTX *mont, BIGNUM *mod, BN_CTX *ctx) +{ + int ret = 0; + BN_MONT_CTX *regenerated = BN_MONT_CTX_new(); + + if (!TEST_ptr(regenerated)) + return ret; + if (!TEST_ptr(mont)) + goto err; + + if (!TEST_true(BN_MONT_CTX_set(regenerated, mod, ctx))) + goto err; + + if (!TEST_true(ossl_bn_mont_ctx_eq(regenerated, mont))) + goto err; + + ret = 1; + + err: + BN_MONT_CTX_free(regenerated); + return ret; +} + +static int montgomery_correctness_test(EC_GROUP *group) +{ + int ret = 0; + BN_CTX *ctx = NULL; + + ctx = BN_CTX_new(); + if (!TEST_ptr(ctx)) + return ret; + if (!TEST_true(check_bn_mont_ctx(group->mont_data, group->order, ctx))) { + TEST_error("group order issue"); + goto err; + } + if (group->field_data1 != NULL) { + if (!TEST_true(check_bn_mont_ctx(group->field_data1, group->field, ctx))) + goto err; + } + ret = 1; + err: + BN_CTX_free(ctx); + return ret; +} + +static int named_group_creation_test(void) +{ + int ret = 0; + EC_GROUP *group = NULL; + + if (!TEST_ptr(group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) + || !TEST_true(montgomery_correctness_test(group))) + goto err; + + ret = 1; + + err: + EC_GROUP_free(group); + return ret; +} + int setup_tests(void) { crv_len = EC_get_builtin_curves(NULL, 0); @@ -452,6 +515,7 @@ int setup_tests(void) ADD_TEST(set_private_key); ADD_TEST(decoded_flag_test); ADD_ALL_TESTS(ecpkparams_i2d2i_test, crv_len); + ADD_TEST(named_group_creation_test); return 1; }