Blob Blame History Raw
From: Sverker Eriksson <sverker@erlang.org>
Date: Wed, 14 Jun 2023 22:00:35 +0200
Subject: [PATCH] crypto: Try generate ec key by name if possible

OpenSSL 3 FIPS does only accept named ec curves.

So we try that first, and if it fails we retry
with the params passed down by crypto.erl.

diff --git a/lib/crypto/c_src/ec.c b/lib/crypto/c_src/ec.c
index af5f5d6f4a..49e1da4fe4 100644
--- a/lib/crypto/c_src/ec.c
+++ b/lib/crypto/c_src/ec.c
@@ -28,7 +28,8 @@
 
 int get_curve_definition(ErlNifEnv* env, ERL_NIF_TERM *ret, ERL_NIF_TERM def,
                          OSSL_PARAM params[], int *i,
-                         size_t *order_size)
+                         size_t *order_size,
+                         struct get_curve_def_ctx* gcd)
 {
     const ERL_NIF_TERM* curve;
     int c_arity = -1;
@@ -40,6 +41,7 @@ int get_curve_definition(ErlNifEnv* env, ERL_NIF_TERM *ret, ERL_NIF_TERM def,
     int arity = -1;
     const ERL_NIF_TERM* curve_tuple;
 
+
     /* Here are two random curve definition examples, one prime_field and
        one characteristic_two_field. Both are from the crypto/src/crypto_ec_curves.erl.
 
@@ -79,6 +81,23 @@ int get_curve_definition(ErlNifEnv* env, ERL_NIF_TERM *ret, ERL_NIF_TERM def,
         c_arity != 5)
         assign_goto(*ret, err, EXCP_ERROR_N(env, 1, "Bad curve def. Expect 5-tuple."));
 
+    if (gcd->use_curve_name
+        && curve_tuple[1] != atom_undefined
+        && enif_get_atom(env, curve_tuple[1], gcd->curve_name,
+                         sizeof(gcd->curve_name), ERL_NIF_LATIN1)) {
+        ErlNifBinary order_bin;
+        params[(*i)++] = OSSL_PARAM_construct_utf8_string("group", gcd->curve_name, 0);
+
+        if (order_size) {
+            if (!enif_inspect_binary(env, curve[3], &order_bin))
+                assign_goto(*ret, err, EXCP_ERROR_N(env, 1, "Bad order"));
+            *order_size = order_bin.size;
+        }
+        gcd->use_curve_name = 1;
+        return 1;
+    }
+    gcd->use_curve_name = 0;
+
     if (!get_ossl_octet_string_param_from_bin(env, "generator", curve[2], &params[(*i)++]))
         assign_goto(*ret, err, EXCP_ERROR_N(env, 1, "Bad Generator (Point)"));
 
@@ -211,6 +230,7 @@ int get_ec_public_key(ErlNifEnv* env, ERL_NIF_TERM key, EVP_PKEY **pkey)
     int tpl_arity;
     int i = 0;
     OSSL_PARAM params[15];
+    struct get_curve_def_ctx gcd;
     EVP_PKEY_CTX *pctx = NULL;
     
     if (!enif_get_tuple(env, key, &tpl_arity, &tpl_terms) ||
@@ -222,19 +242,27 @@ int get_ec_public_key(ErlNifEnv* env, ERL_NIF_TERM key, EVP_PKEY **pkey)
     if (!get_ossl_octet_string_param_from_bin(env, "pub",  tpl_terms[1], &params[i++]))
         assign_goto(ret, err, EXCP_BADARG_N(env, 0, "Bad public key"));
 
-    if (!get_curve_definition(env, &ret, tpl_terms[0], params, &i, NULL))
+    if (!(pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)))
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't make EVP_PKEY_CTX"));
+
+    gcd.use_curve_name = 1;
+retry_without_name:
+    if (!get_curve_definition(env, &ret, tpl_terms[0], params, &i, NULL, &gcd))
         goto err;
 
     params[i++] = OSSL_PARAM_construct_end();
 
-    if (!(pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)))
-        assign_goto(ret, err, EXCP_ERROR(env, "Can't make EVP_PKEY_CTX"));
-
     if (EVP_PKEY_fromdata_init(pctx) <= 0)
         assign_goto(ret, err, EXCP_ERROR(env, "Can't init fromdata"));
     
-    if (EVP_PKEY_fromdata(pctx, pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
+    if (EVP_PKEY_fromdata(pctx, pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) {
+        if (gcd.use_curve_name) {
+            gcd.use_curve_name = 0;
+            i = 1;
+            goto retry_without_name;
+        }
         assign_goto(ret, err, EXCP_ERROR(env, "Can't do fromdata"));
+    }
 
     if (!*pkey)
         assign_goto(ret, err, EXCP_ERROR(env, "Couldn't get a public key"));
@@ -256,24 +284,33 @@ static int get_ec_private_key_2(ErlNifEnv* env,
 {
     int i = 0;
     OSSL_PARAM params[15];
+    struct get_curve_def_ctx gcd;
     EVP_PKEY_CTX *pctx = NULL;
 
     if (!get_ossl_BN_param_from_bin(env, "priv",  key, &params[i++]))
         assign_goto(*ret, err, EXCP_BADARG_N(env, 0, "Bad private key"));
 
-    if (!get_curve_definition(env, ret, curve, params, &i, order_size))
+    if (!(pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)))
+        assign_goto(*ret, err, EXCP_ERROR(env, "Can't make EVP_PKEY_CTX"));
+
+    gcd.use_curve_name = 1;
+retry_without_name:
+    if (!get_curve_definition(env, ret, curve, params, &i, order_size, &gcd))
         goto err;
 
     params[i++] = OSSL_PARAM_construct_end();
 
-    if (!(pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)))
-        assign_goto(*ret, err, EXCP_ERROR(env, "Can't make EVP_PKEY_CTX"));
-
     if (EVP_PKEY_fromdata_init(pctx) <= 0)
         assign_goto(*ret, err, EXCP_ERROR(env, "Can't init fromdata"));
     
-    if (EVP_PKEY_fromdata(pctx, pkey, EVP_PKEY_KEYPAIR, params) <= 0)
+    if (EVP_PKEY_fromdata(pctx, pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
+        if (gcd.use_curve_name) {
+            gcd.use_curve_name = 0;
+            i = 1;
+            goto retry_without_name;
+        }
         assign_goto(*ret, err, EXCP_ERROR(env, "Can't do fromdata"));
+    }
 
     if (!*pkey)
         assign_goto(*ret, err, EXCP_ERROR(env, "Couldn't get a private key"));
@@ -316,9 +353,10 @@ ERL_NIF_TERM ec_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
     ERL_NIF_TERM ret = atom_undefined;
     int i = 0;
     OSSL_PARAM params[15];
+    struct get_curve_def_ctx gcd;
     EVP_PKEY_CTX *pctx = NULL;
     EVP_PKEY *pkey = NULL, *peer_pkey = NULL;
-    size_t sz, order_size;
+    size_t sz, order_size = 0;
     BIGNUM *priv_bn = NULL;
     ErlNifBinary pubkey_bin;
     
@@ -338,26 +376,36 @@ ERL_NIF_TERM ec_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
         }
     else
         {
+            /* Neither the private nor the public key is known, so we generate the pair: */
+            if (!(pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)))
+                assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_CTX_new_from_name"));
+
+            gcd.use_curve_name = 1;
+    retry_without_name:
             /* PrivKey (that is, argv[1]) == atom_undefined */
-            if (!get_curve_definition(env, &ret, argv[0], params, &i, &order_size))
+            if (!get_curve_definition(env, &ret, argv[0], params, &i,
+                                      &order_size, &gcd))
                 // INSERT "ret" parameter in get_curve_definition !!
                 assign_goto(ret, err, EXCP_BADARG_N(env, 0, "Couldn't get Curve definition"));
     
             params[i++] = OSSL_PARAM_construct_end();
 
-            /* Neither the private nor the public key is known, so we generate the pair: */
-            if (!(pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)))
-                assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_CTX_new_from_name"));
-
             if (EVP_PKEY_keygen_init(pctx) <= 0)
                 assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_keygen_init"));
 
             if (!EVP_PKEY_CTX_set_params(pctx, params))
                 assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_CTX_set_params"));
-        
-            if (!EVP_PKEY_generate(pctx, &pkey))
+
+            if (!EVP_PKEY_generate(pctx, &pkey)) {
+                if (gcd.use_curve_name) {
+                    gcd.use_curve_name = 0;
+                    i = 0;
+                    goto retry_without_name;
+                }
                 assign_goto(ret, err, EXCP_ERROR(env, "Couldn't generate EC key"));
-    
+            }
+
+
             /* Get the two keys, pub as binary and priv as BN */
             if (!EVP_PKEY_get_octet_string_param(pkey, "encoded-pub-key", NULL, 0, &sz))
                 assign_goto(ret, err, EXCP_ERROR(env, "Can't get pub octet string size"));
@@ -375,6 +423,8 @@ ERL_NIF_TERM ec_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
                 assign_goto(ret, err, EXCP_BADARG_N(env, 1, "Couldn't get priv key bytes"));
         }
 
+    if (order_size == 0)
+        order_size = BN_num_bytes(priv_bn);
     ret = enif_make_tuple2(env,
                            enif_make_binary(env, &pubkey_bin),
                            bn2term(env, order_size, priv_bn));
diff --git a/lib/crypto/c_src/ec.h b/lib/crypto/c_src/ec.h
index e53986d64e..f0b0b96207 100644
--- a/lib/crypto/c_src/ec.h
+++ b/lib/crypto/c_src/ec.h
@@ -26,9 +26,17 @@
 #if defined(HAVE_EC)
 
 # if defined(HAS_3_0_API)
+
+struct get_curve_def_ctx
+{
+    char curve_name[20];
+    int use_curve_name;
+};
+
 int get_curve_definition(ErlNifEnv* env, ERL_NIF_TERM *ret, ERL_NIF_TERM def,
                          OSSL_PARAM params[], int *i,
-                         size_t *order_size);
+                         size_t *order_size,
+                         struct get_curve_def_ctx*);
 # endif /* HAS_3_0_API */
 
 # if ! defined(HAS_3_0_API)
diff --git a/lib/crypto/c_src/ecdh.c b/lib/crypto/c_src/ecdh.c
index 7509d9cb84..f1b0f7eb28 100644
--- a/lib/crypto/c_src/ecdh.c
+++ b/lib/crypto/c_src/ecdh.c
@@ -42,6 +42,7 @@ ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
     int ret_bin_alloc = 0;
     int i = 0, i_key = 0;
     OSSL_PARAM params[15];
+    struct get_curve_def_ctx gcd;
     EVP_PKEY_CTX *own_pctx = NULL, *peer_pctx = NULL, *pctx_gen = NULL;
     EVP_PKEY *own_pkey = NULL, *peer_pkey = NULL;
     int err;
@@ -53,21 +54,29 @@ ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
     if (!get_ossl_octet_string_param_from_bin(env, "pub",  argv[0], &params[i++]))
         assign_goto(ret, err, EXCP_BADARG_N(env, 0, "Bad peer public key; binary expected"));
 
+    /* Build the remote public key in peer_pkey */
+    peer_pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
+
+    gcd.use_curve_name = 1;
+retry_without_name:
     /* Curve definition/name */
-    if (!get_curve_definition(env, &ret, argv[1], params, &i, NULL))
+    if (!get_curve_definition(env, &ret, argv[1], params, &i, NULL, &gcd))
         goto err;
 
     /* End of params */
     params[i++] = OSSL_PARAM_construct_end();
 
-    /* Build the remote public key in peer_pkey */
-    peer_pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
-
     if (EVP_PKEY_fromdata_init(peer_pctx) <= 0)
         assign_goto(ret, err, EXCP_ERROR(env, "Can't init fromdata"));
     
-    if (EVP_PKEY_fromdata(peer_pctx, &peer_pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
+    if (EVP_PKEY_fromdata(peer_pctx, &peer_pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) {
+        if (gcd.use_curve_name) {
+            gcd.use_curve_name = 0;
+            i = 1;
+            goto retry_without_name;
+        }
         assign_goto(ret, err, EXCP_ERROR(env, "Can't do fromdata"));
+    }
 
     if (!peer_pkey)
         assign_goto(ret, err, EXCP_ERROR(env, "No peer_pkey"));
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 15fd40be1d..cf6ef5dde3 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -3568,7 +3568,7 @@ dss_params() ->
      16#e3a93c09da6f560e4d483a382a4c546f2335c36a4c35ac1463c08a3e6dd415df56fdc537f25fd5372be63e4f5300780b782f1acd01c8b4eb33414615fd0ea82573acba7ef83f5a943854151afc2d7dfe121fb8cd03335b065b549c5dcc606be9052483bc284e12ac3c8dba09b426e08402030e70bc1cc2bf8957c4ba0630f3f32ad689389ac47443176063f247d9e2296b3ea5b5bc2335828ea1a080ed35918dee212fd031279d1b894f01afec523833669eac031a420e540ba1320a59c424a3e5849a460a56bcb001647885b1433c4f992971746bfe2977ce7259c550b551a6c35761e4a41af764e8d92132fcc0a59d1684eab90d863f29f41cf7578faa908c].
 
 ec_key_named() ->
-    Curve = hd(crypto:ec_curves()),
+    Curve = secp224r1, %hd(crypto:ec_curves()),
     {D2_pub, D2_priv} = crypto:generate_key(ecdh, Curve),
     {[D2_priv, Curve], [D2_pub, Curve]}.