Commit 9071cd2874 for openssl.org
commit 9071cd2874ca4f2c35ab6eedf2d90fe7811f61ce
Author: Bob Beck <beck@openssl.org>
Date: Sun Aug 16 08:16:15 2026 -0700
Make the genec test not test process fork and teardown.
The genec test tested 912 curves with a spawn and teardown of an
run of the app each - this takes a long time, and is largely
bound on how fast you can start up and tear down the app.
Instead, let's test all the curves in a C program doing the same
thing, which takes almost no time at all, and test the representative
parameters in the app to ensure they also get test coverage. Doing
those same parameters 912 times in the app to make sure they function
912 times buys us nothing for testing the app. So this will give
us coverage while making the test run faster, and being less
trouble for OS zoo to run testing fork exec teardown instead
of what we want to test.
Reviewed-by: Andrew Dinh <andrewd@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Tue Sep 1 14:17:15 2026
Merged-from: https://github.com/openssl/openssl/pull/32404
diff --git a/test/build.info b/test/build.info
index 618026945d..260817fd2c 100644
--- a/test/build.info
+++ b/test/build.info
@@ -163,6 +163,13 @@ IF[{- !$disabled{tests} -}]
INCLUDE[ecstresstest]=../include ../apps/include
DEPEND[ecstresstest]=../libcrypto libtestutil.a
+ IF[{- !$disabled{ec} -}]
+ PROGRAMS{noinst}=genec_test
+ SOURCE[genec_test]=genec_test.c
+ INCLUDE[genec_test]=../include ../apps/include
+ DEPEND[genec_test]=../libcrypto libtestutil.a
+ ENDIF
+
SOURCE[gmdifftest]=gmdifftest.c
INCLUDE[gmdifftest]=../include ../apps/include
DEPEND[gmdifftest]=../libcrypto libtestutil.a
diff --git a/test/genec_test.c b/test/genec_test.c
new file mode 100644
index 0000000000..7504bed270
--- /dev/null
+++ b/test/genec_test.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+/*-
+ * Table driven EC parameter and key generation tests.
+ *
+ * Every curve is exercised in this process. The generation is driven
+ * through EVP_PKEY_CTX_ctrl_str() with the same option names the
+ * "openssl genpkey" application passes to -pkeyopt, so the provider side
+ * of the application's code path is covered here. The application's own
+ * command line surface is covered separately by
+ * test/recipes/20-test_app_genec.t, which needs only a handful of curves
+ * because the option handling does not vary between them.
+ */
+
+#include <string.h>
+
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/crypto.h>
+#include <openssl/decoder.h>
+#include <openssl/encoder.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+
+#include "testutil.h"
+
+static const char *const curves[] = {
+ "secp112r1",
+ "secp112r2",
+ "secp128r1",
+ "secp128r2",
+ "secp160k1",
+ "secp160r1",
+ "secp160r2",
+ "secp192k1",
+ "secp224k1",
+ "secp224r1",
+ "secp256k1",
+ "secp384r1",
+ "secp521r1",
+ "prime192v1",
+ "prime192v2",
+ "prime192v3",
+ "prime239v1",
+ "prime239v2",
+ "prime239v3",
+ "prime256v1",
+ "wap-wsg-idm-ecid-wtls6",
+ "wap-wsg-idm-ecid-wtls7",
+ "wap-wsg-idm-ecid-wtls8",
+ "wap-wsg-idm-ecid-wtls9",
+ "wap-wsg-idm-ecid-wtls12",
+ "brainpoolP160r1",
+ "brainpoolP160t1",
+ "brainpoolP192r1",
+ "brainpoolP192t1",
+ "brainpoolP224r1",
+ "brainpoolP224t1",
+ "brainpoolP256r1",
+ "brainpoolP256t1",
+ "brainpoolP320r1",
+ "brainpoolP320t1",
+ "brainpoolP384r1",
+ "brainpoolP384t1",
+ "brainpoolP512r1",
+ "brainpoolP512t1",
+#if !defined(OPENSSL_NO_EC2M)
+ "sect113r1",
+ "sect113r2",
+ "sect131r1",
+ "sect131r2",
+ "sect163k1",
+ "sect163r1",
+ "sect163r2",
+ "sect193r1",
+ "sect193r2",
+ "sect233k1",
+ "sect233r1",
+ "sect239k1",
+ "sect283k1",
+ "sect283r1",
+ "sect409k1",
+ "sect409r1",
+ "sect571k1",
+ "sect571r1",
+ "c2pnb163v1",
+ "c2pnb163v2",
+ "c2pnb163v3",
+ "c2pnb176v1",
+ "c2tnb191v1",
+ "c2tnb191v2",
+ "c2tnb191v3",
+ "c2pnb208w1",
+ "c2tnb239v1",
+ "c2tnb239v2",
+ "c2tnb239v3",
+ "c2pnb272w1",
+ "c2pnb304w1",
+ "c2tnb359v1",
+ "c2pnb368w1",
+ "c2tnb431r1",
+ "wap-wsg-idm-ecid-wtls1",
+ "wap-wsg-idm-ecid-wtls3",
+ "wap-wsg-idm-ecid-wtls4",
+ "wap-wsg-idm-ecid-wtls5",
+ "wap-wsg-idm-ecid-wtls10",
+ "wap-wsg-idm-ecid-wtls11",
+#endif /* !defined(OPENSSL_NO_EC2M) */
+ /*
+ * The SM2 curve is deliberately absent. A key generated on it as an
+ * EC key decodes back as an SM2 key, so it does not satisfy the round
+ * trip below. It is covered by test/recipes/20-test_app_genec.t,
+ * which only requires that generation and output succeed.
+ */
+ "P-192",
+ "P-224",
+ "P-256",
+ "P-384",
+ "P-521",
+#if !defined(OPENSSL_NO_EC2M)
+ "B-163",
+ "B-233",
+ "B-283",
+ "B-409",
+ "B-571",
+ "K-163",
+ "K-233",
+ "K-283",
+ "K-409",
+ "K-571",
+#endif /* !defined(OPENSSL_NO_EC2M) */
+};
+
+#if !defined(OPENSSL_NO_EC2M)
+/* Curves that have no assigned OID and so cannot use a named encoding. */
+static const char *const explicit_only_curves[] = {
+ "Oakley-EC2N-3",
+ "Oakley-EC2N-4",
+};
+#endif /* !defined(OPENSSL_NO_EC2M) */
+
+static const char *const param_encodings[] = { "named_curve", "explicit" };
+
+static const char *const formats[] = { "PEM", "DER" };
+
+/**
+ * @brief Generate EC parameters or an EC key on a named curve.
+ *
+ * The curve and the parameter encoding are set with the same option
+ * names the genpkey application accepts via -pkeyopt. No test
+ * assertions are made here so that callers can also use this to check
+ * that a combination is correctly rejected.
+ *
+ * @param curve the name of the curve to generate on
+ * @param param_enc the parameter encoding, "named_curve" or "explicit"
+ * @param params_only 1 to generate parameters only, 0 to generate a key
+ * @returns the generated EVP_PKEY, or NULL on failure
+ */
+static EVP_PKEY *gen_ec(const char *curve, const char *param_enc,
+ int params_only)
+{
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL)
+ return NULL;
+
+ if (params_only) {
+ if (EVP_PKEY_paramgen_init(ctx) <= 0)
+ goto end;
+ } else {
+ if (EVP_PKEY_keygen_init(ctx) <= 0)
+ goto end;
+ }
+
+ if (EVP_PKEY_CTX_ctrl_str(ctx, "ec_paramgen_curve", curve) <= 0
+ || EVP_PKEY_CTX_ctrl_str(ctx, "ec_param_enc", param_enc) <= 0)
+ goto end;
+
+ if (EVP_PKEY_generate(ctx, &pkey) <= 0) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+end:
+ EVP_PKEY_CTX_free(ctx);
+ return pkey;
+}
+
+/**
+ * @brief Encode a key or parameter set into a freshly allocated buffer.
+ *
+ * No test assertions are made here so that callers can also use this to
+ * check that an encoding is correctly refused.
+ *
+ * @param pkey the key or parameter set to encode
+ * @param selection the EVP_PKEY_* selection to encode
+ * @param structure the encoder output structure name
+ * @param format the encoding to use, "PEM" or "DER"
+ * @param out where to store the allocated encoding, freed by the caller
+ * @param out_len where to store the length of the encoding
+ * @returns 1 on success, 0 on failure
+ */
+static int encode_pkey(EVP_PKEY *pkey, int selection, const char *structure,
+ const char *format, unsigned char **out, size_t *out_len)
+{
+ OSSL_ENCODER_CTX *ectx = NULL;
+ BIO *mem = NULL;
+ BUF_MEM *buf = NULL;
+ int ret = 0;
+
+ ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey, selection, format, structure,
+ NULL);
+ if (ectx == NULL || OSSL_ENCODER_CTX_get_num_encoders(ectx) == 0)
+ goto end;
+
+ if ((mem = BIO_new(BIO_s_mem())) == NULL
+ || !OSSL_ENCODER_to_bio(ectx, mem)
+ || BIO_get_mem_ptr(mem, &buf) <= 0
+ || buf->length == 0)
+ goto end;
+
+ if ((*out = OPENSSL_memdup(buf->data, buf->length)) == NULL)
+ goto end;
+ *out_len = buf->length;
+ ret = 1;
+
+end:
+ BIO_free(mem);
+ OSSL_ENCODER_CTX_free(ectx);
+ return ret;
+}
+
+/**
+ * @brief Encode a key, decode the result, and encode it a second time.
+ *
+ * The two encodings must be identical. This is compared rather than the
+ * key objects themselves because an explicit encoding of a curve that is
+ * an alias of another curve does not decode to an object that
+ * EVP_PKEY_eq() considers equal to the original, even though the key
+ * material survives intact.
+ *
+ * @param pkey the key or parameter set to round trip
+ * @param selection the EVP_PKEY_* selection to encode and decode
+ * @param structure the encoder output structure name
+ * @param format the encoding to use, "PEM" or "DER"
+ * @returns 1 if the re-encoding matched the original encoding, 0 otherwise
+ */
+static int round_trip(EVP_PKEY *pkey, int selection, const char *structure,
+ const char *format)
+{
+ OSSL_DECODER_CTX *dctx = NULL;
+ EVP_PKEY *decoded = NULL;
+ BIO *in = NULL;
+ unsigned char *first = NULL, *second = NULL;
+ size_t first_len = 0, second_len = 0;
+ int ret = 0;
+
+ if (!TEST_true(encode_pkey(pkey, selection, structure, format, &first,
+ &first_len)))
+ goto end;
+
+ /*
+ * Decode from a separate read only BIO. Resetting the BIO written to
+ * by the encoder would discard the encoding rather than rewind it.
+ */
+ if (!TEST_ptr(in = BIO_new_mem_buf(first, (int)first_len))
+ || !TEST_ptr(dctx = OSSL_DECODER_CTX_new_for_pkey(&decoded, format,
+ structure, NULL, selection, NULL, NULL))
+ || !TEST_true(OSSL_DECODER_from_bio(dctx, in))
+ || !TEST_ptr(decoded))
+ goto end;
+
+ if (!TEST_true(encode_pkey(decoded, selection, structure, format, &second,
+ &second_len)))
+ goto end;
+
+ ret = TEST_mem_eq(first, first_len, second, second_len);
+
+end:
+ OPENSSL_free(first);
+ OPENSSL_free(second);
+ EVP_PKEY_free(decoded);
+ OSSL_DECODER_CTX_free(dctx);
+ BIO_free(in);
+ return ret;
+}
+
+/**
+ * @brief Render a key as text, as the genpkey -text option does.
+ *
+ * @param pkey the key or parameter set to print
+ * @param params_only 1 if pkey holds parameters only, 0 if it holds a key
+ * @returns 1 on success, 0 on failure
+ */
+static int print_ec(EVP_PKEY *pkey, int params_only)
+{
+ BIO *bio = NULL;
+ int ret = 0;
+
+ if (!TEST_ptr(bio = BIO_new(BIO_s_null())))
+ return 0;
+
+ if (params_only)
+ ret = TEST_int_gt(EVP_PKEY_print_params(bio, pkey, 0, NULL), 0);
+ else
+ ret = TEST_int_gt(EVP_PKEY_print_private(bio, pkey, 0, NULL), 0);
+
+ BIO_free(bio);
+ return ret;
+}
+
+/**
+ * @brief Generate parameters on one curve in every supported encoding.
+ *
+ * @param idx the index into the curve table
+ * @returns 1 on success, 0 on failure
+ */
+static int test_genec_params(int idx)
+{
+ const char *curve = curves[idx];
+ EVP_PKEY *pkey = NULL;
+ size_t i, j;
+ int ret = 0;
+
+ for (i = 0; i < OSSL_NELEM(param_encodings); i++) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+
+ if (!TEST_ptr(pkey = gen_ec(curve, param_encodings[i], 1))
+ || !print_ec(pkey, 1))
+ goto end;
+
+ for (j = 0; j < OSSL_NELEM(formats); j++)
+ if (!round_trip(pkey, EVP_PKEY_KEY_PARAMETERS, "type-specific",
+ formats[j]))
+ goto end;
+ }
+ ret = 1;
+
+end:
+ if (ret == 0)
+ TEST_info("EC parameter generation failed for curve %s", curve);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+/**
+ * @brief Generate a key on one curve in every supported encoding.
+ *
+ * @param idx the index into the curve table
+ * @returns 1 on success, 0 on failure
+ */
+static int test_genec_key(int idx)
+{
+ const char *curve = curves[idx];
+ EVP_PKEY *pkey = NULL;
+ size_t i, j;
+ int ret = 0;
+
+ for (i = 0; i < OSSL_NELEM(param_encodings); i++) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+
+ if (!TEST_ptr(pkey = gen_ec(curve, param_encodings[i], 0))
+ || !print_ec(pkey, 0))
+ goto end;
+
+ for (j = 0; j < OSSL_NELEM(formats); j++)
+ if (!round_trip(pkey, EVP_PKEY_KEYPAIR, "PrivateKeyInfo",
+ formats[j])
+ || !round_trip(pkey, EVP_PKEY_PUBLIC_KEY,
+ "SubjectPublicKeyInfo", formats[j]))
+ goto end;
+ }
+ ret = 1;
+
+end:
+ if (ret == 0)
+ TEST_info("EC key generation failed for curve %s", curve);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+#if !defined(OPENSSL_NO_EC2M)
+/**
+ * @brief Check a curve that only supports an explicit parameter encoding.
+ *
+ * Generation itself succeeds for either encoding; it is the structured
+ * encoding of a named curve that must be refused, because the curve has
+ * no OID to name it by.
+ *
+ * @param idx the index into the explicit only curve table
+ * @returns 1 on success, 0 on failure
+ */
+static int test_genec_explicit_only(int idx)
+{
+ const char *curve = explicit_only_curves[idx];
+ EVP_PKEY *pkey = NULL;
+ unsigned char *enc = NULL;
+ size_t enc_len = 0, j;
+ int ret = 0;
+
+ if (!TEST_ptr(pkey = gen_ec(curve, "named_curve", 1)))
+ goto end;
+
+ for (j = 0; j < OSSL_NELEM(formats); j++) {
+ ERR_set_mark();
+ ret = encode_pkey(pkey, EVP_PKEY_KEY_PARAMETERS, "type-specific",
+ formats[j], &enc, &enc_len);
+ ERR_pop_to_mark();
+ OPENSSL_free(enc);
+ enc = NULL;
+ if (!TEST_false(ret)) {
+ ret = 0;
+ goto end;
+ }
+ }
+ ret = 0;
+
+ EVP_PKEY_free(pkey);
+ if (!TEST_ptr(pkey = gen_ec(curve, "explicit", 1))
+ || !print_ec(pkey, 1))
+ goto end;
+
+ for (j = 0; j < OSSL_NELEM(formats); j++)
+ if (!round_trip(pkey, EVP_PKEY_KEY_PARAMETERS, "type-specific",
+ formats[j]))
+ goto end;
+ ret = 1;
+
+end:
+ if (ret == 0)
+ TEST_info("explicit only curve %s failed", curve);
+ OPENSSL_free(enc);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+#endif /* !defined(OPENSSL_NO_EC2M) */
+
+/**
+ * @brief Check that an unknown curve name is rejected.
+ *
+ * @returns 1 on success, 0 on failure
+ */
+static int test_genec_unknown_curve(void)
+{
+ EVP_PKEY *pkey;
+
+ ERR_set_mark();
+ pkey = gen_ec("bogus_foobar_curve", "named_curve", 1);
+ ERR_pop_to_mark();
+
+ if (!TEST_ptr_null(pkey)) {
+ EVP_PKEY_free(pkey);
+ return 0;
+ }
+ return 1;
+}
+
+int setup_tests(void)
+{
+ ADD_ALL_TESTS(test_genec_params, OSSL_NELEM(curves));
+ ADD_ALL_TESTS(test_genec_key, OSSL_NELEM(curves));
+#if !defined(OPENSSL_NO_EC2M)
+ ADD_ALL_TESTS(test_genec_explicit_only, OSSL_NELEM(explicit_only_curves));
+#endif /* !defined(OPENSSL_NO_EC2M) */
+ ADD_TEST(test_genec_unknown_curve);
+ return 1;
+}
diff --git a/test/recipes/15-test_genec.t b/test/recipes/15-test_genec.t
index 4d5090fa39..6df40ccd3d 100644
--- a/test/recipes/15-test_genec.t
+++ b/test/recipes/15-test_genec.t
@@ -1,5 +1,5 @@
#! /usr/bin/env perl
-# Copyright 2017-2023 The OpenSSL Project Authors. All Rights Reserved.
+# Copyright 2017-2026 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
@@ -7,302 +7,6 @@
# https://www.openssl.org/source/license.html
-use strict;
-use warnings;
+use OpenSSL::Test::Simple;
-use File::Spec;
-use OpenSSL::Test qw/:DEFAULT srctop_file/;
-use OpenSSL::Test::Utils;
-
-# 'supported' and 'unsupported' reflect the current state of things. In
-# Test::More terms, 'supported' works exactly like ok(run(whatever)), while
-# 'unsupported' wraps that in a TODO: { } block.
-#
-# The first argument is the test name (this becomes the last argument to
-# 'ok')
-# The remaining argument are passed unchecked to 'run'.
-
-# 1: the result of app() or similar, i.e. something you can pass to
-sub supported_pass {
- my $str = shift;
-
- ok(run(@_), $str);
-}
-
-sub supported_fail {
- my $str = shift;
-
- ok(!run(@_), $str);
-}
-
-setup("test_genec");
-
-plan skip_all => "This test is unsupported in a no-ec build"
- if disabled("ec");
-
-my @prime_curves = qw(
- secp112r1
- secp112r2
- secp128r1
- secp128r2
- secp160k1
- secp160r1
- secp160r2
- secp192k1
- secp224k1
- secp224r1
- secp256k1
- secp384r1
- secp521r1
- prime192v1
- prime192v2
- prime192v3
- prime239v1
- prime239v2
- prime239v3
- prime256v1
- wap-wsg-idm-ecid-wtls6
- wap-wsg-idm-ecid-wtls7
- wap-wsg-idm-ecid-wtls8
- wap-wsg-idm-ecid-wtls9
- wap-wsg-idm-ecid-wtls12
- brainpoolP160r1
- brainpoolP160t1
- brainpoolP192r1
- brainpoolP192t1
- brainpoolP224r1
- brainpoolP224t1
- brainpoolP256r1
- brainpoolP256t1
- brainpoolP320r1
- brainpoolP320t1
- brainpoolP384r1
- brainpoolP384t1
- brainpoolP512r1
- brainpoolP512t1
-);
-
-my @binary_curves = qw(
- sect113r1
- sect113r2
- sect131r1
- sect131r2
- sect163k1
- sect163r1
- sect163r2
- sect193r1
- sect193r2
- sect233k1
- sect233r1
- sect239k1
- sect283k1
- sect283r1
- sect409k1
- sect409r1
- sect571k1
- sect571r1
- c2pnb163v1
- c2pnb163v2
- c2pnb163v3
- c2pnb176v1
- c2tnb191v1
- c2tnb191v2
- c2tnb191v3
- c2pnb208w1
- c2tnb239v1
- c2tnb239v2
- c2tnb239v3
- c2pnb272w1
- c2pnb304w1
- c2tnb359v1
- c2pnb368w1
- c2tnb431r1
- wap-wsg-idm-ecid-wtls1
- wap-wsg-idm-ecid-wtls3
- wap-wsg-idm-ecid-wtls4
- wap-wsg-idm-ecid-wtls5
- wap-wsg-idm-ecid-wtls10
- wap-wsg-idm-ecid-wtls11
-);
-
-my @explicit_only_curves = ();
-push(@explicit_only_curves, qw(
- Oakley-EC2N-3
- Oakley-EC2N-4
- )) if !disabled("ec2m");
-
-my @other_curves = ();
-push(@other_curves, 'SM2')
- if !disabled("sm2");
-
-my @curve_aliases = qw(
- P-192
- P-224
- P-256
- P-384
- P-521
-);
-push(@curve_aliases, qw(
- B-163
- B-233
- B-283
- B-409
- B-571
- K-163
- K-233
- K-283
- K-409
- K-571
-)) if !disabled("ec2m");
-
-my @curve_list = ();
-push(@curve_list, @prime_curves);
-push(@curve_list, @binary_curves)
- if !disabled("ec2m");
-push(@curve_list, @other_curves);
-push(@curve_list, @curve_aliases);
-
-my %params_encodings =
- (
- 'named_curve' => \&supported_pass,
- 'explicit' => \&supported_pass
- );
-
-my @output_formats = ('PEM', 'DER');
-
-plan tests => scalar(@curve_list) * scalar(keys %params_encodings)
- * (1 + scalar(@output_formats)) # Try listed @output_formats and text output
- * 2 # Test generating parameters and keys
- + 1 # Checking that with no curve it fails
- + 1 # Checking that with unknown curve it fails
- + 1 # Subtest for explicit only curves
- + 1 # base serializer test
- ;
-
-ok(!run(app([ 'openssl', 'genpkey',
- '-algorithm', 'EC'])),
- "genpkey EC with no params should fail");
-
-ok(!run(app([ 'openssl', 'genpkey',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:bogus_foobar_curve'])),
- "genpkey EC with unknown curve name should fail");
-
-ok(run(app([ 'openssl', 'genpkey',
- '-provider-path', 'providers',
- '-provider', 'base',
- '-config', srctop_file("test", "default.cnf"),
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:prime256v1',
- '-text'])),
- "generate a private key and serialize it using the base provider");
-
-foreach my $curvename (@curve_list) {
- foreach my $paramenc (sort keys %params_encodings) {
- my $fn = $params_encodings{$paramenc};
-
- # --- Test generating parameters ---
-
- $fn->("genpkey EC params ${curvename} with ec_param_enc:'${paramenc}' (text)",
- app([ 'openssl', 'genpkey', '-genparam',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
- '-pkeyopt', 'ec_param_enc:'.$paramenc,
- '-text']));
-
- foreach my $outform (@output_formats) {
- my $outfile = "ecgen.${curvename}.${paramenc}." . lc $outform;
- $fn->("genpkey EC params ${curvename} with ec_param_enc:'${paramenc}' (${outform})",
- app([ 'openssl', 'genpkey', '-genparam',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
- '-pkeyopt', 'ec_param_enc:'.$paramenc,
- '-outform', $outform,
- '-out', $outfile]));
- }
-
- # --- Test generating actual keys ---
-
- $fn->("genpkey EC key on ${curvename} with ec_param_enc:'${paramenc}' (text)",
- app([ 'openssl', 'genpkey',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
- '-pkeyopt', 'ec_param_enc:'.$paramenc,
- '-text']));
-
- foreach my $outform (@output_formats) {
- my $outfile = "ecgen.${curvename}.${paramenc}." . lc $outform;
- my $outpubfile = "ecgen.${curvename}.${paramenc}-pub." . lc $outform;
- $fn->("genpkey EC key on ${curvename} with ec_param_enc:'${paramenc}' (${outform})",
- app([ 'openssl', 'genpkey',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
- '-pkeyopt', 'ec_param_enc:'.$paramenc,
- '-outform', $outform,
- '-out', $outfile,
- '-outpubkey', $outpubfile]));
- }
- }
-}
-
-subtest "test curves that only support explicit parameters encoding" => sub {
- plan skip_all => "This test is unsupported under current configuration"
- if scalar(@explicit_only_curves) <= 0;
-
- plan tests => scalar(@explicit_only_curves) * scalar(keys %params_encodings)
- * (1 + scalar(@output_formats)) # Try listed @output_formats and text output
- * 2 # Test generating parameters and keys
- ;
-
- my %params_encodings =
- (
- 'named_curve' => \&supported_fail,
- 'explicit' => \&supported_pass
- );
-
- foreach my $curvename (@explicit_only_curves) {
- foreach my $paramenc (sort keys %params_encodings) {
- my $fn = $params_encodings{$paramenc};
-
- # --- Test generating parameters ---
-
- $fn->("genpkey EC params ${curvename} with ec_param_enc:'${paramenc}' (text)",
- app([ 'openssl', 'genpkey', '-genparam',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
- '-pkeyopt', 'ec_param_enc:'.$paramenc,
- '-text']));
-
- foreach my $outform (@output_formats) {
- my $outfile = "ecgen.${curvename}.${paramenc}." . lc $outform;
- $fn->("genpkey EC params ${curvename} with ec_param_enc:'${paramenc}' (${outform})",
- app([ 'openssl', 'genpkey', '-genparam',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
- '-pkeyopt', 'ec_param_enc:'.$paramenc,
- '-outform', $outform,
- '-out', $outfile]));
- }
-
- # --- Test generating actual keys ---
-
- $fn->("genpkey EC key on ${curvename} with ec_param_enc:'${paramenc}' (text)",
- app([ 'openssl', 'genpkey',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
- '-pkeyopt', 'ec_param_enc:'.$paramenc,
- '-text']));
-
- foreach my $outform (@output_formats) {
- my $outfile = "ecgen.${curvename}.${paramenc}." . lc $outform;
- $fn->("genpkey EC key on ${curvename} with ec_param_enc:'${paramenc}' (${outform})",
- app([ 'openssl', 'genpkey',
- '-algorithm', 'EC',
- '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
- '-pkeyopt', 'ec_param_enc:'.$paramenc,
- '-outform', $outform,
- '-out', $outfile]));
- }
- }
- }
-};
+simple_test("test_genec", "genec_test", "ec");
diff --git a/test/recipes/20-test_app_genec.t b/test/recipes/20-test_app_genec.t
new file mode 100644
index 0000000000..9038d2bb9d
--- /dev/null
+++ b/test/recipes/20-test_app_genec.t
@@ -0,0 +1,158 @@
+#! /usr/bin/env perl
+# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License"). You may not use
+# this file except in compliance with the License. You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+
+use strict;
+use warnings;
+
+use OpenSSL::Test qw/:DEFAULT srctop_file/;
+use OpenSSL::Test::Utils;
+
+# The genpkey command line surface for EC: the -pkeyopt names, the output
+# format and text options, and the negative cases. None of this varies
+# from one curve to the next, so only a few representative curves are
+# used here. Every curve is covered in this process by genec_test, which
+# is driven by test/recipes/15-test_genec.t.
+
+sub supported_pass {
+ my $str = shift;
+
+ ok(run(@_), $str);
+}
+
+sub supported_fail {
+ my $str = shift;
+
+ ok(!run(@_), $str);
+}
+
+setup("test_app_genec");
+
+plan skip_all => "This test is unsupported in a no-ec build"
+ if disabled("ec");
+
+my @curve_list = ('prime256v1');
+push(@curve_list, 'sect233k1')
+ if !disabled("ec2m");
+push(@curve_list, 'P-256');
+push(@curve_list, 'SM2')
+ if !disabled("sm2");
+
+my @explicit_only_curves = ();
+push(@explicit_only_curves, qw(
+ Oakley-EC2N-3
+ Oakley-EC2N-4
+ )) if !disabled("ec2m");
+
+my @param_encodings = ('named_curve', 'explicit');
+
+my @output_formats = ('PEM', 'DER');
+
+plan tests => scalar(@curve_list) * scalar(@param_encodings)
+ * (1 + scalar(@output_formats)) # Try listed @output_formats and text output
+ * 2 # Test generating parameters and keys
+ + 1 # Checking that with no curve it fails
+ + 1 # Checking that with unknown curve it fails
+ + 1 # Checking that a bad encoding fails
+ + 1 # Subtest for explicit only curves
+ + 1 # base serializer test
+ ;
+
+ok(!run(app([ 'openssl', 'genpkey',
+ '-algorithm', 'EC'])),
+ "genpkey EC with no params should fail");
+
+ok(!run(app([ 'openssl', 'genpkey',
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:bogus_foobar_curve'])),
+ "genpkey EC with unknown curve name should fail");
+
+ok(!run(app([ 'openssl', 'genpkey',
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:prime256v1',
+ '-pkeyopt', 'ec_param_enc:bogus_foobar_encoding'])),
+ "genpkey EC with unknown parameter encoding should fail");
+
+ok(run(app([ 'openssl', 'genpkey',
+ '-provider-path', 'providers',
+ '-provider', 'base',
+ '-config', srctop_file("test", "default.cnf"),
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:prime256v1',
+ '-text'])),
+ "generate a private key and serialize it using the base provider");
+
+foreach my $curvename (@curve_list) {
+ foreach my $paramenc (@param_encodings) {
+
+ # --- Test generating parameters ---
+
+ supported_pass("genpkey EC params ${curvename} with ec_param_enc:'${paramenc}' (text)",
+ app([ 'openssl', 'genpkey', '-genparam',
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
+ '-pkeyopt', 'ec_param_enc:'.$paramenc,
+ '-text']));
+
+ foreach my $outform (@output_formats) {
+ my $outfile = "ecgen.${curvename}.${paramenc}." . lc $outform;
+ supported_pass("genpkey EC params ${curvename} with ec_param_enc:'${paramenc}' (${outform})",
+ app([ 'openssl', 'genpkey', '-genparam',
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
+ '-pkeyopt', 'ec_param_enc:'.$paramenc,
+ '-outform', $outform,
+ '-out', $outfile]));
+ }
+
+ # --- Test generating actual keys ---
+
+ supported_pass("genpkey EC key on ${curvename} with ec_param_enc:'${paramenc}' (text)",
+ app([ 'openssl', 'genpkey',
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
+ '-pkeyopt', 'ec_param_enc:'.$paramenc,
+ '-text']));
+
+ foreach my $outform (@output_formats) {
+ my $outfile = "ecgen.${curvename}.${paramenc}." . lc $outform;
+ my $outpubfile = "ecgen.${curvename}.${paramenc}-pub." . lc $outform;
+ supported_pass("genpkey EC key on ${curvename} with ec_param_enc:'${paramenc}' (${outform})",
+ app([ 'openssl', 'genpkey',
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
+ '-pkeyopt', 'ec_param_enc:'.$paramenc,
+ '-outform', $outform,
+ '-out', $outfile,
+ '-outpubkey', $outpubfile]));
+ }
+ }
+}
+
+subtest "test curves that only support explicit parameters encoding" => sub {
+ plan skip_all => "This test is unsupported under current configuration"
+ if scalar(@explicit_only_curves) <= 0;
+
+ plan tests => scalar(@explicit_only_curves) * 2;
+
+ foreach my $curvename (@explicit_only_curves) {
+ supported_fail("genpkey EC params ${curvename} with ec_param_enc:'named_curve' should fail",
+ app([ 'openssl', 'genpkey', '-genparam',
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
+ '-pkeyopt', 'ec_param_enc:named_curve',
+ '-text']));
+
+ supported_pass("genpkey EC params ${curvename} with ec_param_enc:'explicit'",
+ app([ 'openssl', 'genpkey', '-genparam',
+ '-algorithm', 'EC',
+ '-pkeyopt', 'ec_paramgen_curve:'.$curvename,
+ '-pkeyopt', 'ec_param_enc:explicit',
+ '-text']));
+ }
+};