Commit e57f7941af for openssl.org

commit e57f7941af75b6d79720e482d0990a98f9dc54c8
Author: Viktor Dukhovni <openssl-users@dukhovni.org>
Date:   Sat Jan 3 17:36:40 2026 +1100

    Expose and report EC curve field degrees

    Expose the EC field degree as a gettable parameter for both provided
    and legacy EC keys.  In the latter case, drop a spurious assertion,
    since even in debug builds an application may try to get an unknown
    parameter, and this should return an error rather than abort.

    In the EC `TEXT` encoding format, instead of reporting the bit count of
    the group order, report the field degree (which matches the size number
    in the curve's name when present) and also the symmetric-equivalent
    security-bits (adjusted down the the standard numbers (80, 112, 128,
    192, 256).

    Along the way, add a missing getter method for the EC_GROUP security
    bits.

    Reviewed-by: Kurt Roeckx <kurt@roeckx.be>
    Reviewed-by: Tim Hudson <tjh@openssl.org>
    Reviewed-by: Paul Dale <paul.dale@oracle.com>
    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    MergeDate: Thu Jan 15 16:10:26 2026
    (Merged from https://github.com/openssl/openssl/pull/29539)

diff --git a/crypto/ec/ec_ameth.c b/crypto/ec/ec_ameth.c
index 541d9936e5..f25f5d80d6 100644
--- a/crypto/ec/ec_ameth.c
+++ b/crypto/ec/ec_ameth.c
@@ -214,19 +214,7 @@ static int ec_bits(const EVP_PKEY *pkey)

 static int ec_security_bits(const EVP_PKEY *pkey)
 {
-    int ecbits = ec_bits(pkey);
-
-    if (ecbits >= 512)
-        return 256;
-    if (ecbits >= 384)
-        return 192;
-    if (ecbits >= 256)
-        return 128;
-    if (ecbits >= 224)
-        return 112;
-    if (ecbits >= 160)
-        return 80;
-    return ecbits / 2;
+    return EC_GROUP_security_bits(EC_KEY_get0_group(pkey->pkey.ec));
 }

 static int ec_missing_parameters(const EVP_PKEY *pkey)
diff --git a/crypto/ec/ec_lib.c b/crypto/ec/ec_lib.c
index 13dcd29b11..b041ecd0f7 100644
--- a/crypto/ec/ec_lib.c
+++ b/crypto/ec/ec_lib.c
@@ -465,10 +465,36 @@ int EC_GROUP_order_bits(const EC_GROUP *group)
     return group->meth->group_order_bits(group);
 }

+int EC_GROUP_security_bits(const EC_GROUP *group)
+{
+    int ecbits = group->meth->group_order_bits(group);
+
+    /*
+     * The following estimates are based on the values published in Table 2 of
+     * "NIST Special Publication 800-57 Part 1 Revision 4" at
+     * http://dx.doi.org/10.6028/NIST.SP.800-57pt1r4 .
+     *
+     * Note that the above reference explicitly categorizes algorithms in a
+     * discrete set of values {80, 112, 128, 192, 256}, and that it is relevant
+     * only for NIST approved Elliptic Curves, while OpenSSL applies the same
+     * logic also to other curves.
+     */
+    if (ecbits >= 512)
+        return 256;
+    if (ecbits >= 384)
+        return 192;
+    if (ecbits >= 256)
+        return 128;
+    if (ecbits >= 224)
+        return 112;
+    if (ecbits >= 160)
+        return 80;
+    return ecbits / 2;
+}
+
 int EC_GROUP_get_cofactor(const EC_GROUP *group, BIGNUM *cofactor,
     BN_CTX *ctx)
 {
-
     if (group->cofactor == NULL)
         return 0;
     if (!BN_copy(cofactor, group->cofactor))
diff --git a/crypto/evp/ctrl_params_translate.c b/crypto/evp/ctrl_params_translate.c
index 5371e21fc4..0ab5ce1257 100644
--- a/crypto/evp/ctrl_params_translate.c
+++ b/crypto/evp/ctrl_params_translate.c
@@ -1833,6 +1833,36 @@ static int get_ec_decoded_from_explicit_params(enum state state,
     return get_payload_int(state, translation, ctx, val);
 }

+static int get_ec_field_degree(enum state state,
+    const struct translation_st *translation,
+    struct translation_ctx_st *ctx)
+{
+#ifndef OPENSSL_NO_EC
+    const EC_KEY *key;
+    const EC_GROUP *group;
+#endif
+    EVP_PKEY *pkey = ctx->p2;
+    int val = 0;
+
+    switch (EVP_PKEY_base_id(pkey)) {
+#ifndef OPENSSL_NO_EC
+    case EVP_PKEY_EC:
+        if ((key = EVP_PKEY_get0_EC_KEY(pkey)) == NULL
+            || (group = EC_KEY_get0_group(key)) == NULL) {
+            ERR_raise(ERR_LIB_EVP, EVP_R_INVALID_KEY);
+            return 0;
+        }
+        val = EC_GROUP_get_degree(group);
+        break;
+#endif
+    default:
+        ERR_raise(ERR_LIB_EVP, EVP_R_UNSUPPORTED_KEY_TYPE);
+        return 0;
+    }
+
+    return get_payload_int(state, translation, ctx, val);
+}
+
 static int get_rsa_payload_n(enum state state,
     const struct translation_st *translation,
     struct translation_ctx_st *ctx)
@@ -2568,6 +2598,9 @@ static const struct translation_st evp_pkey_translations[] = {
     { OSSL_ACTION_GET, -1, -1, -1, 0, NULL, NULL,
         OSSL_PKEY_PARAM_EC_DECODED_FROM_EXPLICIT_PARAMS, OSSL_PARAM_INTEGER,
         get_ec_decoded_from_explicit_params },
+    { OSSL_ACTION_GET, -1, -1, -1, 0, NULL, NULL,
+        OSSL_PKEY_PARAM_EC_FIELD_DEGREE, OSSL_PARAM_INTEGER,
+        get_ec_field_degree },
 };

 static const struct translation_st *
@@ -2935,7 +2968,7 @@ static int evp_pkey_setget_params_to_ctrl(const EVP_PKEY *pkey,
          * on fixup_args to do the whole work.  Also, we currently only
          * support getting.
          */
-        if (!ossl_assert(translation != NULL)
+        if (translation == NULL
             || !ossl_assert(translation->action_type == OSSL_ACTION_GET)
             || !ossl_assert(translation->fixup_args != NULL)) {
             return -2;
diff --git a/doc/man3/EC_GROUP_copy.pod b/doc/man3/EC_GROUP_copy.pod
index e525fad0bf..da2fa49838 100644
--- a/doc/man3/EC_GROUP_copy.pod
+++ b/doc/man3/EC_GROUP_copy.pod
@@ -2,18 +2,17 @@

 =head1 NAME

-EC_GROUP_get0_order, EC_GROUP_order_bits, EC_GROUP_get0_cofactor,
-EC_GROUP_copy, EC_GROUP_dup, EC_GROUP_method_of, EC_GROUP_set_generator,
-EC_GROUP_get0_generator, EC_GROUP_get_order, EC_GROUP_get_cofactor,
-EC_GROUP_set_curve_name, EC_GROUP_get_curve_name, EC_GROUP_set_asn1_flag,
-EC_GROUP_get_asn1_flag, EC_GROUP_set_point_conversion_form,
-EC_GROUP_get_point_conversion_form, EC_GROUP_get0_seed,
-EC_GROUP_get_seed_len, EC_GROUP_set_seed, EC_GROUP_get_degree,
-EC_GROUP_check, EC_GROUP_check_named_curve,
-EC_GROUP_check_discriminant, EC_GROUP_cmp,
-EC_GROUP_get_basis_type, EC_GROUP_get_trinomial_basis,
-EC_GROUP_get_pentanomial_basis, EC_GROUP_get0_field,
-EC_GROUP_get_field_type
+EC_GROUP_get0_order, EC_GROUP_order_bits, EC_GROUP_security_bits,
+EC_GROUP_get0_cofactor, EC_GROUP_copy, EC_GROUP_dup, EC_GROUP_method_of,
+EC_GROUP_set_generator, EC_GROUP_get0_generator, EC_GROUP_get_order,
+EC_GROUP_get_cofactor, EC_GROUP_set_curve_name, EC_GROUP_get_curve_name,
+EC_GROUP_set_asn1_flag, EC_GROUP_get_asn1_flag,
+EC_GROUP_set_point_conversion_form, EC_GROUP_get_point_conversion_form,
+EC_GROUP_get0_seed, EC_GROUP_get_seed_len, EC_GROUP_set_seed,
+EC_GROUP_get_degree, EC_GROUP_check, EC_GROUP_check_named_curve,
+EC_GROUP_check_discriminant, EC_GROUP_cmp, EC_GROUP_get_basis_type,
+EC_GROUP_get_trinomial_basis, EC_GROUP_get_pentanomial_basis,
+EC_GROUP_get0_field, EC_GROUP_get_field_type
 - Functions for manipulating EC_GROUP objects

 =head1 SYNOPSIS
@@ -30,6 +29,7 @@ EC_GROUP_get_field_type
  int EC_GROUP_get_order(const EC_GROUP *group, BIGNUM *order, BN_CTX *ctx);
  const BIGNUM *EC_GROUP_get0_order(const EC_GROUP *group);
  int EC_GROUP_order_bits(const EC_GROUP *group);
+ int EC_GROUP_security_bits(const EC_GROUP *group);
  int EC_GROUP_get_cofactor(const EC_GROUP *group, BIGNUM *cofactor, BN_CTX *ctx);
  const BIGNUM *EC_GROUP_get0_cofactor(const EC_GROUP *group);
  const BIGNUM *EC_GROUP_get0_field(const EC_GROUP *group);
@@ -223,6 +223,8 @@ EC_GROUP_check_named_curve() returns the nid of the matching named curve, otherw

 EC_GROUP_get0_order() returns an internal pointer to the group order.
 EC_GROUP_order_bits() returns the number of bits in the group order.
+EC_GROUP_security_bits() returns the symmetric-equivalent security bit count of the group,
+rounding down to the standard sizes (80, 112, 128, 192, 256) when the value is at least 80.
 EC_GROUP_get0_cofactor() returns an internal pointer to the group cofactor.
 EC_GROUP_get0_field() returns an internal pointer to the group field. For curves over GF(p), this is the modulus; for curves
 over GF(2^m), this is the irreducible polynomial defining the field.
@@ -250,6 +252,8 @@ EC_GROUP_method_of() was deprecated in OpenSSL 3.0.
 EC_GROUP_get0_field(), EC_GROUP_check_named_curve() and EC_GROUP_get_field_type() were added in OpenSSL 3.0.
 EC_GROUP_get0_order(), EC_GROUP_order_bits() and EC_GROUP_get0_cofactor() were added in OpenSSL 1.1.0.

+EC_GROUP_security_bits() was added in OpenSSL 4.0.
+
 =head1 COPYRIGHT

 Copyright 2013-2023 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man7/EVP_PKEY-EC.pod b/doc/man7/EVP_PKEY-EC.pod
index 25177aa2da..5822e21088 100644
--- a/doc/man7/EVP_PKEY-EC.pod
+++ b/doc/man7/EVP_PKEY-EC.pod
@@ -161,6 +161,15 @@ The following Gettable types are also available for the built-in EC algorithm:

 =over 4

+=item "field-degree" (B<OSSL_PKEY_PARAM_EC_FIELD_DEGREE>) <integer>
+
+This is the bit length of the curve's field coefficients.
+It coincides with the B<OSSL_PKEY_PARAM_EC_CHAR2_M> parameter for binary
+fields, and is otherwise (for prime fields) equal to the bit length of the
+field's characteristic.
+For most curves (e.g. C<secp160r1>) this number is embedded in the curve's
+name.
+
 =item "basis-type" (B<OSSL_PKEY_PARAM_EC_CHAR2_TYPE>) <UTF8 string>

 Supports the values "tpBasis" for a trinomial or "ppBasis" for a pentanomial.
@@ -194,7 +203,8 @@ OpenSSL FIPS provider's EC algorithm:

 =item "key-check" (B<OSSL_PKEY_PARAM_FIPS_KEY_CHECK>) <integer>

-See L<provider-keymgmt(7)/Common Information Parameters> for further information.
+See L<provider-keymgmt(7)/Common Information Parameters> for descriptions
+of additional parameters generally available across all algorithms.

 =back

@@ -300,6 +310,10 @@ L<provider-keymgmt(7)>,
 L<EVP_SIGNATURE-ECDSA(7)>,
 L<EVP_KEYEXCH-ECDH(7)>

+=head1 HISTORY
+
+The B<OSSL_PKEY_PARAM_EC_FIELD_DEGREE> parameter was added in OpenSSL 4.0.
+
 =head1 COPYRIGHT

 Copyright 2020-2024 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/include/openssl/ec.h b/include/openssl/ec.h
index 4ea2c36321..3d8b681d71 100644
--- a/include/openssl/ec.h
+++ b/include/openssl/ec.h
@@ -253,6 +253,14 @@ const BIGNUM *EC_GROUP_get0_order(const EC_GROUP *group);
  */
 int EC_GROUP_order_bits(const EC_GROUP *group);

+/** Gets the symmetric-equivalent security bit size an EC_GROUP.
+ * This is rounded down to one of the standard sizes, (80, 112,
+ * 128, 192, 256) or reported as-is when smaller than 80.
+ *  \param  group  EC_GROUP object
+ *  \return symmetric-equivalent security bits.
+ */
+int EC_GROUP_security_bits(const EC_GROUP *group);
+
 /** Gets the cofactor of a EC_GROUP
  *  \param  group     EC_GROUP object
  *  \param  cofactor  BIGNUM to which the cofactor is copied
diff --git a/providers/implementations/encode_decode/encode_key2text.c b/providers/implementations/encode_decode/encode_key2text.c
index 026929275c..e87532a75d 100644
--- a/providers/implementations/encode_decode/encode_key2text.c
+++ b/providers/implementations/encode_decode/encode_key2text.c
@@ -362,8 +362,9 @@ static int ec_to_text(BIO *out, const void *key, int selection)
     }

     if (type_label != NULL
-        && BIO_printf(out, "%s: (%d bit)\n", type_label,
-               EC_GROUP_order_bits(group))
+        && BIO_printf(out, "%s: (%d bit field, %d bit security level)\n",
+               type_label, EC_GROUP_get_degree(group),
+               EC_GROUP_security_bits(group))
             <= 0)
         goto err;
     if (priv != NULL
diff --git a/providers/implementations/keymgmt/ec_kmgmt.c b/providers/implementations/keymgmt/ec_kmgmt.c
index cc3cf75cd8..b54f69df51 100644
--- a/providers/implementations/keymgmt/ec_kmgmt.c
+++ b/providers/implementations/keymgmt/ec_kmgmt.c
@@ -647,43 +647,12 @@ static int common_get_params(void *key, OSSL_PARAM params[], int sm2)
     if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_BITS)) != NULL
         && !OSSL_PARAM_set_int(p, EC_GROUP_order_bits(ecg)))
         goto err;
-    if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_SECURITY_BITS)) != NULL) {
-        int ecbits, sec_bits;
-
-        ecbits = EC_GROUP_order_bits(ecg);
-
-        /*
-         * The following estimates are based on the values published
-         * in Table 2 of "NIST Special Publication 800-57 Part 1 Revision 4"
-         * at http://dx.doi.org/10.6028/NIST.SP.800-57pt1r4 .
-         *
-         * Note that the above reference explicitly categorizes algorithms in a
-         * discrete set of values {80, 112, 128, 192, 256}, and that it is
-         * relevant only for NIST approved Elliptic Curves, while OpenSSL
-         * applies the same logic also to other curves.
-         *
-         * Classifications produced by other standardazing bodies might differ,
-         * so the results provided for "bits of security" by this provider are
-         * to be considered merely indicative, and it is the users'
-         * responsibility to compare these values against the normative
-         * references that may be relevant for their intent and purposes.
-         */
-        if (ecbits >= 512)
-            sec_bits = 256;
-        else if (ecbits >= 384)
-            sec_bits = 192;
-        else if (ecbits >= 256)
-            sec_bits = 128;
-        else if (ecbits >= 224)
-            sec_bits = 112;
-        else if (ecbits >= 160)
-            sec_bits = 80;
-        else
-            sec_bits = ecbits / 2;
-
-        if (!OSSL_PARAM_set_int(p, sec_bits))
-            goto err;
-    }
+    if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_EC_FIELD_DEGREE)) != NULL
+        && !OSSL_PARAM_set_int(p, EC_GROUP_get_degree(ecg)))
+        goto err;
+    if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_SECURITY_BITS)) != NULL
+        && !OSSL_PARAM_set_int(p, EC_GROUP_security_bits(ecg)))
+        goto err;
     if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_SECURITY_CATEGORY)) != NULL)
         if (!OSSL_PARAM_set_int(p, 0))
             goto err;
@@ -768,6 +737,7 @@ static int ec_get_params(void *key, OSSL_PARAM params[])

 static const OSSL_PARAM ec_known_gettable_params[] = {
     OSSL_PARAM_int(OSSL_PKEY_PARAM_BITS, NULL),
+    OSSL_PARAM_int(OSSL_PKEY_PARAM_EC_FIELD_DEGREE, NULL),
     OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL),
     OSSL_PARAM_int(OSSL_PKEY_PARAM_MAX_SIZE, NULL),
     OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_CATEGORY, NULL),
@@ -844,6 +814,7 @@ static int sm2_get_params(void *key, OSSL_PARAM params[])

 static const OSSL_PARAM sm2_known_gettable_params[] = {
     OSSL_PARAM_int(OSSL_PKEY_PARAM_BITS, NULL),
+    OSSL_PARAM_int(OSSL_PKEY_PARAM_EC_FIELD_DEGREE, NULL),
     OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL),
     OSSL_PARAM_int(OSSL_PKEY_PARAM_MAX_SIZE, NULL),
     OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_DEFAULT_DIGEST, NULL, 0),
diff --git a/test/ectest.c b/test/ectest.c
index 9b1c911b5b..9fca45bad4 100644
--- a/test/ectest.c
+++ b/test/ectest.c
@@ -306,6 +306,7 @@ static int prime_field_tests(void)
                                  "3168947d59dcc912042351377ac5fb32"))
         || !TEST_BN_eq(y, z)
         || !TEST_int_eq(EC_GROUP_get_degree(group), 160)
+        || !TEST_int_eq(EC_GROUP_security_bits(group), 80)
         || !group_order_tests(group)

         /* Curve P-192 (FIPS PUB 186-2, App. 6) */
@@ -343,6 +344,7 @@ static int prime_field_tests(void)
         || !TEST_false(EC_POINT_set_affine_coordinates(group, P, x, yplusone,
             ctx))
         || !TEST_int_eq(EC_GROUP_get_degree(group), 192)
+        || !TEST_int_eq(EC_GROUP_security_bits(group), 80)
         || !group_order_tests(group)

         /* Curve P-224 (FIPS PUB 186-2, App. 6) */
@@ -380,6 +382,7 @@ static int prime_field_tests(void)
         || !TEST_false(EC_POINT_set_affine_coordinates(group, P, x, yplusone,
             ctx))
         || !TEST_int_eq(EC_GROUP_get_degree(group), 224)
+        || !TEST_int_eq(EC_GROUP_security_bits(group), 112)
         || !group_order_tests(group)

         /* Curve P-256 (FIPS PUB 186-2, App. 6) */
@@ -418,6 +421,7 @@ static int prime_field_tests(void)
         || !TEST_false(EC_POINT_set_affine_coordinates(group, P, x, yplusone,
             ctx))
         || !TEST_int_eq(EC_GROUP_get_degree(group), 256)
+        || !TEST_int_eq(EC_GROUP_security_bits(group), 128)
         || !group_order_tests(group)

         /* Curve P-384 (FIPS PUB 186-2, App. 6) */
@@ -462,6 +466,7 @@ static int prime_field_tests(void)
         || !TEST_false(EC_POINT_set_affine_coordinates(group, P, x, yplusone,
             ctx))
         || !TEST_int_eq(EC_GROUP_get_degree(group), 384)
+        || !TEST_int_eq(EC_GROUP_security_bits(group), 192)
         || !group_order_tests(group)

         /* Curve P-521 (FIPS PUB 186-2, App. 6) */
@@ -516,6 +521,7 @@ static int prime_field_tests(void)
         || !TEST_false(EC_POINT_set_affine_coordinates(group, P, x, yplusone,
             ctx))
         || !TEST_int_eq(EC_GROUP_get_degree(group), 521)
+        || !TEST_int_eq(EC_GROUP_security_bits(group), 256)
         || !group_order_tests(group)

         /* more tests using the last curve */
@@ -612,6 +618,7 @@ static struct c2_curve_test {
     const char *order;
     const char *cof;
     int degree;
+    int security;
 } char2_curve_tests[] = {
     /* Curve K-163 (FIPS PUB 186-2, App. 6) */
     {
@@ -621,7 +628,7 @@ static struct c2_curve_test {
         "1",
         "02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8",
         "0289070FB05D38FF58321F2E800536D538CCDAA3D9",
-        1, "04000000000000000000020108A2E0CC0D99F8A5EF", "2", 163 },
+        1, "04000000000000000000020108A2E0CC0D99F8A5EF", "2", 163, 80 },
     /* Curve B-163 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve B-163",
@@ -630,7 +637,7 @@ static struct c2_curve_test {
         "020A601907B8C953CA1481EB10512F78744A3205FD",
         "03F0EBA16286A2D57EA0991168D4994637E8343E36",
         "00D51FBC6C71A0094FA2CDD545B11C5C0C797324F1",
-        1, "040000000000000000000292FE77E70C12A4234C33", "2", 163 },
+        1, "040000000000000000000292FE77E70C12A4234C33", "2", 163, 80 },
     /* Curve K-233 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve K-233",
@@ -641,7 +648,7 @@ static struct c2_curve_test {
         "01DB537DECE819B7F70F555A67C427A8CD9BF18AEB9B56E0C11056FAE6A3",
         0,
         "008000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF",
-        "4", 233 },
+        "4", 233, 112 },
     /* Curve B-233 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve B-233",
@@ -652,7 +659,7 @@ static struct c2_curve_test {
         "01006A08A41903350678E58528BEBF8A0BEFF867A7CA36716F7E01F81052",
         1,
         "01000000000000000000000000000013E974E72F8A6922031D2603CFE0D7",
-        "2", 233 },
+        "2", 233, 112 },
     /* Curve K-283 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve K-283",
@@ -667,7 +674,7 @@ static struct c2_curve_test {
         0,
         "01FFFFFF"
         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61",
-        "4", 283 },
+        "4", 283, 128 },
     /* Curve B-283 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve B-283",
@@ -684,7 +691,7 @@ static struct c2_curve_test {
         1,
         "03FFFFFF"
         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307",
-        "2", 283 },
+        "2", 283, 128 },
     /* Curve K-409 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve K-409",
@@ -699,7 +706,7 @@ static struct c2_curve_test {
         1,
         "007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
         "FFFFFFFFFFFFFE5F83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF",
-        "4", 409 },
+        "4", 409, 192 },
     /* Curve B-409 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve B-409",
@@ -716,7 +723,7 @@ static struct c2_curve_test {
         1,
         "0100000000000000000000000000000000000000"
         "00000000000001E2AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173",
-        "2", 409 },
+        "2", 409, 192 },
     /* Curve K-571 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve K-571",
@@ -735,7 +742,7 @@ static struct c2_curve_test {
         "0200000000000000"
         "00000000000000000000000000000000000000000000000000000000131850E1"
         "F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001",
-        "4", 571 },
+        "4", 571, 256 },
     /* Curve B-571 (FIPS PUB 186-2, App. 6) */
     {
         "NIST curve B-571",
@@ -758,7 +765,7 @@ static struct c2_curve_test {
         "03FFFFFFFFFFFFFF"
         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE661CE18"
         "FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47",
-        "2", 571 }
+        "2", 571, 256 }
 };

 static int char2_curve_test(int n)
@@ -836,6 +843,7 @@ static int char2_curve_test(int n)
 #endif

     if (!TEST_int_eq(EC_GROUP_get_degree(group), test->degree)
+        || !TEST_int_eq(EC_GROUP_security_bits(group), test->security)
         || !group_order_tests(group))
         goto err;

@@ -1183,6 +1191,7 @@ static int group_field_test(void)
 struct nistp_test_params {
     const int nid;
     int degree;
+    int security;
     /*
      * Qx, Qy and D are taken from
      * http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/ECDSA_Prime.pdf
@@ -1196,6 +1205,7 @@ static const struct nistp_test_params nistp_tests_params[] = {
         /* P-224 */
         NID_secp224r1,
         224,
+        112,
         /* p */
         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001",
         /* a */
@@ -1219,6 +1229,7 @@ static const struct nistp_test_params nistp_tests_params[] = {
         /* P-256 */
         NID_X9_62_prime256v1,
         256,
+        128,
         /* p */
         "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff",
         /* a */
@@ -1242,6 +1253,7 @@ static const struct nistp_test_params nistp_tests_params[] = {
         /* P-521 */
         NID_secp521r1,
         521,
+        256,
         /* p */
         "1ff"
         "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
@@ -1330,7 +1342,8 @@ static int nistp_single_test(int idx)
         || !TEST_true(EC_POINT_set_affine_coordinates(NISTP, G, x, y, ctx))
         || !TEST_true(BN_hex2bn(&order, test->order))
         || !TEST_true(EC_GROUP_set_generator(NISTP, G, order, BN_value_one()))
-        || !TEST_int_eq(EC_GROUP_get_degree(NISTP), test->degree))
+        || !TEST_int_eq(EC_GROUP_get_degree(NISTP), test->degree)
+        || !TEST_int_eq(EC_GROUP_security_bits(NISTP), test->security))
         goto err;

     TEST_note("NIST test vectors ... ");
@@ -3003,6 +3016,11 @@ static int do_test_custom_explicit_fromdata(EC_GROUP *group, BN_CTX *ctx,
             goto err;
     }

+    if (!TEST_true(EVP_PKEY_get_int_param(pkeyparam,
+            OSSL_PKEY_PARAM_EC_FIELD_DEGREE, &i_out))
+        || !TEST_int_eq(EC_GROUP_get_degree(group), i_out))
+        goto err;
+
     if (EC_GROUP_get_field_type(group) == NID_X9_62_prime_field) {
         /* No extra fields should be set for a prime field */
         if (!TEST_false(EVP_PKEY_get_int_param(pkeyparam,
@@ -3493,7 +3511,7 @@ static int ec_d2i_publickey_test(void)
     const unsigned char *pk_enc = pubkey_enc;
     EVP_PKEY *gen_key = NULL, *decoded_key = NULL;
     EVP_PKEY_CTX *pctx = NULL;
-    int pklen, ret = 0;
+    int pklen, i_out = 0, ret = 0;
     OSSL_PARAM params[2];

     if (!TEST_ptr(gen_key = EVP_EC_gen("P-256")))
@@ -3516,8 +3534,15 @@ static int ec_d2i_publickey_test(void)
                          &pk_enc, pklen)))
         goto err;

-    if (!TEST_true(EVP_PKEY_eq(gen_key, decoded_key)))
+    if (!TEST_true(EVP_PKEY_eq(gen_key, decoded_key))
+        || !TEST_true(EVP_PKEY_get_int_param(gen_key,
+            OSSL_PKEY_PARAM_EC_FIELD_DEGREE, &i_out))
+        || !TEST_int_eq(i_out, 256)
+        || !TEST_true(EVP_PKEY_get_int_param(decoded_key,
+            OSSL_PKEY_PARAM_EC_FIELD_DEGREE, &i_out))
+        || !TEST_int_eq(i_out, 256))
         goto err;
+
     ret = 1;

 err:
diff --git a/test/recipes/30-test_evp_pkey_provided/EC.priv.txt b/test/recipes/30-test_evp_pkey_provided/EC.priv.txt
index 9df6455401..84ca806062 100644
--- a/test/recipes/30-test_evp_pkey_provided/EC.priv.txt
+++ b/test/recipes/30-test_evp_pkey_provided/EC.priv.txt
@@ -1,4 +1,4 @@
-Private-Key: (256 bit)
+Private-Key: (256 bit field, 128 bit security level)
 priv:
     33:d0:43:83:a9:89:56:03:d2:d7:fe:6b:01:6f:e4:59:
     cc:0d:9a:24:6c:86:1b:2e:dc:4b:4d:35:43:e1:1b:ad
diff --git a/test/recipes/30-test_evp_pkey_provided/EC.pub.txt b/test/recipes/30-test_evp_pkey_provided/EC.pub.txt
index c72473f72b..d8dec75adc 100644
--- a/test/recipes/30-test_evp_pkey_provided/EC.pub.txt
+++ b/test/recipes/30-test_evp_pkey_provided/EC.pub.txt
@@ -1,4 +1,4 @@
-Public-Key: (256 bit)
+Public-Key: (256 bit field, 128 bit security level)
 pub:
     04:1b:93:67:55:1c:55:9f:63:d1:22:a4:d8:d1:0a:60:
     6d:02:a5:77:57:c8:a3:47:73:3a:6a:08:28:39:bd:c9:
diff --git a/util/libcrypto.num b/util/libcrypto.num
index c01447b177..81cda111b7 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -617,6 +617,7 @@ EC_GROUP_get_mont_data                  ?	4_0_0	EXIST::FUNCTION:EC
 EC_GROUP_get_order                      ?	4_0_0	EXIST::FUNCTION:EC
 EC_GROUP_get0_order                     ?	4_0_0	EXIST::FUNCTION:EC
 EC_GROUP_order_bits                     ?	4_0_0	EXIST::FUNCTION:EC
+EC_GROUP_security_bits                  ?	4_0_0	EXIST::FUNCTION:EC
 EC_GROUP_get_cofactor                   ?	4_0_0	EXIST::FUNCTION:EC
 EC_GROUP_get0_cofactor                  ?	4_0_0	EXIST::FUNCTION:EC
 EC_GROUP_set_curve_name                 ?	4_0_0	EXIST::FUNCTION:EC
diff --git a/util/perl/OpenSSL/paramnames.pm b/util/perl/OpenSSL/paramnames.pm
index 3a8cb6f1b1..8473aa2b07 100644
--- a/util/perl/OpenSSL/paramnames.pm
+++ b/util/perl/OpenSSL/paramnames.pm
@@ -348,6 +348,7 @@ my %params = (

 # Elliptic Curve Explicit Domain Parameters
     'OSSL_PKEY_PARAM_EC_FIELD_TYPE' =>                   "field-type",
+    'OSSL_PKEY_PARAM_EC_FIELD_DEGREE' =>                 "field-degree",
     'OSSL_PKEY_PARAM_EC_P' =>                            "p",
     'OSSL_PKEY_PARAM_EC_A' =>                            "a",
     'OSSL_PKEY_PARAM_EC_B' =>                            "b",