Commit 71d721d357 for openssl.org
commit 71d721d3574b9783cd0149026b4a52f67794506a
Author: Bob Beck <beck@openssl.org>
Date: Sun Aug 16 14:59:52 2026 -0700
Stop running the entire ecparam corpus through the app
Checking 136 parameter files with ecparam and pkeyparam cost a process
per file per check, 641 in all, which is process startup rather than
coverage. Test all of these in one process per set in C instead.
The app keeps it's own test, covering the options and a few
files through the output path, in 20-test_app_ecparam.t.
Together the two now take 1.0 seconds where the one took 24.5.
Reviewed-by: Andrew Dinh <andrewd@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Tue Sep 1 14:17:19 2026
Merged-from: https://github.com/openssl/openssl/pull/32404
diff --git a/test/build.info b/test/build.info
index 260817fd2c..6be71ede1a 100644
--- a/test/build.info
+++ b/test/build.info
@@ -168,6 +168,11 @@ IF[{- !$disabled{tests} -}]
SOURCE[genec_test]=genec_test.c
INCLUDE[genec_test]=../include ../apps/include
DEPEND[genec_test]=../libcrypto libtestutil.a
+
+ PROGRAMS{noinst}=ecparam_test
+ SOURCE[ecparam_test]=ecparam_test.c
+ INCLUDE[ecparam_test]=../include ../apps/include
+ DEPEND[ecparam_test]=../libcrypto libtestutil.a
ENDIF
SOURCE[gmdifftest]=gmdifftest.c
diff --git a/test/ecparam_test.c b/test/ecparam_test.c
new file mode 100644
index 0000000000..462b2d2ce9
--- /dev/null
+++ b/test/ecparam_test.c
@@ -0,0 +1,264 @@
+/*
+ * 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
+ */
+
+#include <string.h>
+
+#include <openssl/bio.h>
+#include <openssl/core_names.h>
+#include <openssl/decoder.h>
+#include <openssl/encoder.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+#include "testutil.h"
+
+/*-
+ * Sweep a corpus of EC parameter files, exercising what the ecparam and
+ * pkeyparam applications do to each one. The applications are covered
+ * separately in 20-test_app_ecparam.t; running the whole corpus through
+ * them costs a process per file per check, which is startup time rather
+ * than test coverage.
+ *
+ * Invoked as:
+ *
+ * ecparam_test valid|noncanon|invalid <file>...
+ *
+ * Valid and non-canonically encoded parameters must load and check.
+ * Invalid ones must not. Only the canonically encoded valid files are
+ * expected to re-encode to exactly the bytes they were read from.
+ */
+typedef enum {
+ CORPUS_VALID,
+ CORPUS_NONCANON,
+ CORPUS_INVALID
+} corpus_kind;
+
+static corpus_kind corpus;
+static int expect_check; /* Whether loading and checking should succeed */
+static int num_files;
+
+/* The files start at argument 1; argument 0 names the corpus. */
+static const char *corpus_file(int idx)
+{
+ return test_get_argument(idx + 1);
+}
+
+/*
+ * Load domain parameters the way the applications do. ecparam insists the
+ * result be an EC or SM2 key, pkeyparam takes whatever it is given.
+ */
+static EVP_PKEY *load_params(const char *file, int ec_only)
+{
+ EVP_PKEY *pkey = NULL;
+ OSSL_DECODER_CTX *dctx = NULL;
+ BIO *bio = BIO_new_file(file, "rb");
+
+ if (bio == NULL)
+ return NULL;
+
+ dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "PEM", NULL, NULL,
+ OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL);
+ if (dctx == NULL) {
+ BIO_free(bio);
+ return NULL;
+ }
+
+ if (!OSSL_DECODER_from_bio(dctx, bio)) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ OSSL_DECODER_CTX_free(dctx);
+ BIO_free(bio);
+
+ if (pkey != NULL && ec_only
+ && !EVP_PKEY_is_a(pkey, "EC") && !EVP_PKEY_is_a(pkey, "SM2")) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ return pkey;
+}
+
+/* Run EVP_PKEY_param_check() as the applications do after loading. */
+static int check_params(EVP_PKEY *pkey)
+{
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
+ int ret;
+
+ if (ctx == NULL)
+ return 0;
+
+ ret = EVP_PKEY_param_check(ctx) > 0;
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+
+/*
+ * Load and check, optionally restricting the check to named curves as
+ * "ecparam -check_named" does. Returns 1 if the outcome matched what the
+ * corpus expects.
+ */
+static int load_and_check(int idx, int ec_only, int named)
+{
+ const char *file = corpus_file(idx);
+ EVP_PKEY *pkey = load_params(file, ec_only);
+ int ok;
+
+ if (pkey == NULL)
+ return TEST_int_eq(expect_check, 0);
+
+ if (named
+ && !TEST_true(EVP_PKEY_set_utf8_string_param(pkey,
+ OSSL_PKEY_PARAM_EC_GROUP_CHECK_TYPE,
+ OSSL_PKEY_EC_GROUP_CHECK_NAMED))) {
+ EVP_PKEY_free(pkey);
+ return 0;
+ }
+
+ ok = check_params(pkey);
+ EVP_PKEY_free(pkey);
+
+ if (!TEST_int_eq(ok, expect_check)) {
+ TEST_info("%s", file);
+ return 0;
+ }
+ return 1;
+}
+
+static int test_ecparam_check(int idx)
+{
+ return load_and_check(idx, 1, 0);
+}
+
+static int test_ecparam_check_named(int idx)
+{
+ return load_and_check(idx, 1, 1);
+}
+
+static int test_pkeyparam_check(int idx)
+{
+ return load_and_check(idx, 0, 0);
+}
+
+/* Read a whole file, so the re-encoded form can be compared against it. */
+static int read_file(const char *file, unsigned char **out, long *out_len)
+{
+ BIO *bio = BIO_new_file(file, "rb");
+ unsigned char *buf = NULL;
+ long len = 0, n, i, j;
+
+ if (bio == NULL)
+ return 0;
+
+ for (;;) {
+ unsigned char *tmp = OPENSSL_realloc(buf, (size_t)len + 4096);
+
+ if (tmp == NULL) {
+ OPENSSL_free(buf);
+ BIO_free(bio);
+ return 0;
+ }
+ buf = tmp;
+ n = BIO_read(bio, buf + len, 4096);
+ if (n <= 0)
+ break;
+ len += n;
+ }
+
+ BIO_free(bio);
+
+ for (i = 0, j = 0; i < len; i++)
+ if (buf[i] != '\r')
+ buf[j++] = buf[i];
+
+ *out = buf;
+ *out_len = j;
+ return 1;
+}
+
+/*
+ * Canonically encoded parameters must survive a decode and re-encode
+ * unchanged, which is what "ecparam -in x -out y" is checked to do.
+ */
+static int test_reencode(int idx)
+{
+ const char *file = corpus_file(idx);
+ EVP_PKEY *pkey = load_params(file, 1);
+ OSSL_ENCODER_CTX *ectx = NULL;
+ BIO *mem = NULL;
+ unsigned char *orig = NULL;
+ char *enc = NULL;
+ long orig_len = 0;
+ long enc_len;
+ int ret = 0;
+
+ if (!TEST_ptr(pkey))
+ goto err;
+
+ if (!TEST_ptr(mem = BIO_new(BIO_s_mem())))
+ goto err;
+
+ ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey,
+ OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, "PEM", NULL, NULL);
+ if (!TEST_ptr(ectx) || !TEST_true(OSSL_ENCODER_to_bio(ectx, mem)))
+ goto err;
+
+ if (!TEST_true(read_file(file, &orig, &orig_len)))
+ goto err;
+
+ enc_len = BIO_get_mem_data(mem, &enc);
+ if (!TEST_mem_eq(enc, (size_t)enc_len, orig, (size_t)orig_len)) {
+ TEST_info("%s", file);
+ goto err;
+ }
+
+ ret = 1;
+err:
+ OPENSSL_free(orig);
+ OSSL_ENCODER_CTX_free(ectx);
+ BIO_free(mem);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+int setup_tests(void)
+{
+ const char *kind;
+ size_t argc = test_get_argument_count();
+
+ if (!TEST_size_t_gt(argc, 1)) {
+ TEST_error("usage: ecparam_test valid|noncanon|invalid <file>...");
+ return 0;
+ }
+
+ kind = test_get_argument(0);
+ if (strcmp(kind, "valid") == 0) {
+ corpus = CORPUS_VALID;
+ } else if (strcmp(kind, "noncanon") == 0) {
+ corpus = CORPUS_NONCANON;
+ } else if (strcmp(kind, "invalid") == 0) {
+ corpus = CORPUS_INVALID;
+ } else {
+ TEST_error("unknown corpus \"%s\"", kind);
+ return 0;
+ }
+
+ expect_check = corpus != CORPUS_INVALID;
+ num_files = (int)argc - 1;
+
+ ADD_ALL_TESTS(test_ecparam_check, num_files);
+ ADD_ALL_TESTS(test_ecparam_check_named, num_files);
+ ADD_ALL_TESTS(test_pkeyparam_check, num_files);
+ /* Only the canonical encodings are expected to be byte stable. */
+ if (corpus == CORPUS_VALID)
+ ADD_ALL_TESTS(test_reencode, num_files);
+
+ return 1;
+}
diff --git a/test/recipes/15-test_ecparam.t b/test/recipes/15-test_ecparam.t
index 6ff1df815b..20e664873d 100644
--- a/test/recipes/15-test_ecparam.t
+++ b/test/recipes/15-test_ecparam.t
@@ -1,5 +1,5 @@
#! /usr/bin/env perl
-# Copyright 2017-2025 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
@@ -10,11 +10,8 @@
use strict;
use warnings;
-use File::Spec;
-use File::Copy;
-use File::Compare qw/compare_text compare/;
use OpenSSL::Glob;
-use OpenSSL::Test qw/:DEFAULT data_file srctop_file bldtop_dir/;
+use OpenSSL::Test qw/:DEFAULT data_file/;
use OpenSSL::Test::Utils;
setup("test_ecparam");
@@ -30,286 +27,15 @@ if (disabled("sm2")) {
@valid = grep { !/sm2-.*\.pem/} @valid;
}
-plan tests => 18;
+# The corpus is swept in a single process per set; the ecparam and
+# pkeyparam applications themselves are covered by 20-test_app_ecparam.t.
+plan tests => 3;
-sub checkload {
- my $files = shift; # List of files
- my $valid = shift; # Check should pass or fail?
- my $app = shift; # Which application
- my $opt = shift; # Additional option
+ok(run(test(["ecparam_test", "valid", @valid])),
+ "Load and check valid parameters");
- foreach (@$files) {
- if ($valid) {
- ok(run(app(['openssl', $app, '-noout', $opt, '-in', $_])));
- } else {
- ok(!run(app(['openssl', $app, '-noout', $opt, '-in', $_])));
- }
- }
-}
-
-sub checkcompare {
- my $files = shift; # List of files
- my $app = shift; # Which application
-
- foreach (@$files) {
- my $testout = "$app.tst";
-
- ok(run(app(['openssl', $app, '-out', $testout, '-in', $_])));
- ok(!compare_text($_, $testout, sub {
- my $in1 = $_[0];
- my $in2 = $_[1];
- $in1 =~ s/\r\n/\n/g;
- $in2 =~ s/\r\n/\n/g;
- $in1 ne $in2}), "Original file $_ is the same as new one");
- }
-}
-
-sub check_identical {
- my $apps = shift; # List of applications
-
- foreach (@$apps) {
- my $inout = "$_.tst";
- my $backup = "backup.tst";
-
- copy($inout, $backup);
- ok(run(app(['openssl', $_, '-in', $inout, '-out', $inout])));
- ok(!compare($inout, $backup), "converted file $inout did not change");
- }
-}
-
-my $no_fips = disabled('fips') || ($ENV{NO_FIPS} // 0);
-
-subtest "Check loading valid parameters by ecparam with -check" => sub {
- plan tests => scalar(@valid);
- checkload(\@valid, 1, "ecparam", "-check");
-};
-
-subtest "Check loading valid parameters by ecparam with -check_named" => sub {
- plan tests => scalar(@valid);
- checkload(\@valid, 1, "ecparam", "-check_named");
-};
-
-subtest "Check loading valid parameters by pkeyparam with -check" => sub {
- plan tests => scalar(@valid);
- checkload(\@valid, 1, "pkeyparam", "-check");
-};
-
-subtest "Check loading non-canonically encoded parameters by ecparam with -check" => sub {
- plan tests => scalar(@noncanon);
- checkload(\@noncanon, 1, "ecparam", "-check");
-};
-
-subtest "Check loading non-canonically encoded parameters by ecparam with -check_named" => sub {
- plan tests => scalar(@noncanon);
- checkload(\@noncanon, 1, "ecparam", "-check_named");
-};
-
-subtest "Check loading non-canonically encoded parameters by pkeyparam with -check" => sub {
- plan tests => scalar(@noncanon);
- checkload(\@noncanon, 1, "pkeyparam", "-check");
-};
-
-subtest "Check loading invalid parameters by ecparam with -check" => sub {
- plan tests => scalar(@invalid);
- checkload(\@invalid, 0, "ecparam", "-check");
-};
-
-subtest "Check loading invalid parameters by ecparam with -check_named" => sub {
- plan tests => scalar(@invalid);
- checkload(\@invalid, 0, "ecparam", "-check_named");
-};
-
-subtest "Check loading invalid parameters by pkeyparam with -check" => sub {
- plan tests => scalar(@invalid);
- checkload(\@invalid, 0, "pkeyparam", "-check");
-};
-
-subtest "Check ecparam does not change the parameter file on output" => sub {
- plan tests => 2 * scalar(@valid);
- checkcompare(\@valid, "ecparam");
-};
-
-subtest "Check pkeyparam does not change the parameter file on output" => sub {
- plan tests => 2 * scalar(@valid);
- checkcompare(\@valid, "pkeyparam");
-};
-
-my @apps = ("ecparam", "pkeyparam");
-subtest "Check param apps do not garble infile identical to outfile" => sub {
- plan tests => 2 * scalar(@apps);
- check_identical(\@apps);
-};
-
-subtest "Check loading of fips and non-fips params" => sub {
- plan skip_all => "FIPS is disabled"
- if $no_fips;
- plan tests => 8;
-
- my $fipsconf = srctop_file("test", "fips-and-base.cnf");
- my $defaultconf = srctop_file("test", "default.cnf");
-
- $ENV{OPENSSL_CONF} = $fipsconf;
-
- ok(run(app(['openssl', 'ecparam',
- '-in', data_file('valid', 'secp384r1-explicit.pem'),
- '-check'])),
- "Loading explicitly encoded valid curve");
-
- ok(run(app(['openssl', 'ecparam',
- '-in', data_file('valid', 'secp384r1-named.pem'),
- '-check'])),
- "Loading named valid curve");
-
- ok(!run(app(['openssl', 'ecparam',
- '-in', data_file('valid', 'secp112r1-named.pem'),
- '-check'])),
- "Fail loading named non-fips curve");
-
- ok(!run(app(['openssl', 'pkeyparam',
- '-in', data_file('valid', 'secp112r1-named.pem'),
- '-check'])),
- "Fail loading named non-fips curve using pkeyparam");
-
- ok(run(app(['openssl', 'ecparam',
- '-provider', 'default',
- '-propquery', '?fips!=yes',
- '-in', data_file('valid', 'secp112r1-named.pem'),
- '-check'])),
- "Loading named non-fips curve in FIPS mode with non-FIPS property".
- " query");
-
- ok(run(app(['openssl', 'pkeyparam',
- '-provider', 'default',
- '-propquery', '?fips!=yes',
- '-in', data_file('valid', 'secp112r1-named.pem'),
- '-check'])),
- "Loading named non-fips curve in FIPS mode with non-FIPS property".
- " query using pkeyparam");
-
- ok(!run(app(['openssl', 'ecparam',
- '-genkey', '-name', 'secp112r1'])),
- "Fail generating key for named non-fips curve");
-
- ok(run(app(['openssl', 'ecparam',
- '-provider', 'default',
- '-propquery', '?fips!=yes',
- '-genkey', '-name', 'secp112r1'])),
- "Generating key for named non-fips curve with non-FIPS property query");
-
- $ENV{OPENSSL_CONF} = $defaultconf;
-};
-
-subtest "Check ecparam -param_enc converts between named and explicit" => sub {
- plan tests => 3;
-
- my $named = data_file('valid', 'secp384r1-named.pem');
- my $explicit = data_file('valid', 'secp384r1-explicit.pem');
-
- # The encodings are canonical, so re-encoding a named curve as explicit
- # (and vice versa) must reproduce the matching reference file byte for byte.
- my $to_explicit = 'param-explicit.tst';
- ok(run(app(['openssl', 'ecparam', '-in', $named, '-param_enc', 'explicit',
- '-out', $to_explicit]))
- && !compare($to_explicit, $explicit),
- "named_curve params re-encoded as explicit match the reference file");
-
- my $to_named = 'param-named.tst';
- ok(run(app(['openssl', 'ecparam', '-in', $explicit, '-param_enc',
- 'named_curve', '-out', $to_named]))
- && !compare($to_named, $named),
- "explicit params re-encoded as named_curve match the reference file");
-
- ok(!run(app(['openssl', 'ecparam', '-in', $named, '-noout',
- '-param_enc', 'bogus'])),
- "an invalid parameter encoding is rejected");
-};
-
-subtest "Check ecparam -inform and -outform handling" => sub {
- plan tests => 4;
-
- my $named = data_file('valid', 'secp384r1-named.pem');
-
- my $der = 'param.der';
- ok(run(app(['openssl', 'ecparam', '-in', $named, '-outform', 'DER',
- '-out', $der])),
- "write DER-encoded parameters");
- my $pem = 'param-der.pem';
- ok(run(app(['openssl', 'ecparam', '-inform', 'DER', '-in', $der,
- '-out', $pem]))
- && !compare($pem, $named),
- "parameters survive a PEM -> DER -> PEM roundtrip");
-
- ok(!run(app(['openssl', 'ecparam', '-in', $der, '-noout'])),
- "DER input without -inform is rejected as the default is PEM");
-
- ok(!run(app(['openssl', 'ecparam', '-in', $named, '-outform', 'MSBLOB',
- '-out', 'param.tmp'])),
- "-outform is limited to PEM and DER");
-};
-
-subtest "Check ecparam -conv_form selects the generator point encoding" => sub {
- plan tests => 5;
-
- my $named = data_file('valid', 'secp384r1-named.pem');
- my $explicit = data_file('valid', 'secp384r1-explicit.pem');
-
- # Only explicit parameters encode the generator point; the reference file
- # uses the default uncompressed form.
- my $comp = 'param-comp.pem';
- ok(run(app(['openssl', 'ecparam', '-in', $explicit, '-conv_form',
- 'compressed', '-out', $comp])),
- "write explicit parameters with a compressed generator");
- ok((-s $comp) < (-s $explicit),
- "compressed generator encoding is smaller than uncompressed");
-
- my $back = 'param-unc.pem';
- ok(run(app(['openssl', 'ecparam', '-in', $comp, '-conv_form',
- 'uncompressed', '-out', $back]))
- && !compare($back, $explicit),
- "converting back to uncompressed matches the reference file");
-
- my $namedout = 'param-named-conv.pem';
- ok(run(app(['openssl', 'ecparam', '-in', $named, '-conv_form',
- 'compressed', '-out', $namedout]))
- && !compare($namedout, $named),
- "-conv_form does not change named curve parameters");
-
- ok(!run(app(['openssl', 'ecparam', '-in', $named, '-noout',
- '-conv_form', 'bogus'])),
- "an invalid conversion form is rejected");
-};
-
-subtest "Check ecparam -text prints the parameters in text form" => sub {
- plan tests => 6;
-
- my $named = data_file('valid', 'secp384r1-named.pem');
- my $explicit = data_file('valid', 'secp384r1-explicit.pem');
-
- # Named parameters print the curve identification.
- my @named = run(app(['openssl', 'ecparam', '-text', '-noout', '-in', $named],
- stderr => undef),
- capture => 1);
- chomp @named;
- ok(grep(/^EC-Parameters: \(384 bit field, 192 bit security level\)$/, @named),
- "named parameters print the EC-Parameters header");
- ok(grep(/^ASN1 OID: secp384r1$/, @named),
- "named parameters print the expected curve OID");
- ok(grep(/^NIST CURVE: P-384$/, @named),
- "named parameters print the expected NIST curve name");
-
- # Explicit parameters print the field parameters instead of the curve name.
- my @explicit = run(app(['openssl', 'ecparam', '-text', '-noout',
- '-in', $explicit],
- stderr => undef),
- capture => 1);
- chomp @explicit;
- ok(grep(/^EC-Parameters: \(384 bit field, 192 bit security level\)$/, @explicit),
- "explicit parameters print the EC-Parameters header");
- ok(grep(/^Field Type: prime-field$/, @explicit)
- && grep(/^Cofactor:/, @explicit),
- "explicit parameters print the field parameters");
- ok(!grep(/^ASN1 OID:/, @explicit),
- "explicit parameters do not print a curve OID");
-};
+ok(run(test(["ecparam_test", "noncanon", @noncanon])),
+ "Load and check non-canonically encoded parameters");
-ok(run(app(['openssl', 'ecparam', '-list_curves'])), "Test -list_curves");
+ok(run(test(["ecparam_test", "invalid", @invalid])),
+ "Reject invalid parameters");
diff --git a/test/recipes/20-test_app_ecparam.t b/test/recipes/20-test_app_ecparam.t
new file mode 100644
index 0000000000..1c03efff1d
--- /dev/null
+++ b/test/recipes/20-test_app_ecparam.t
@@ -0,0 +1,251 @@
+#! /usr/bin/env perl
+# 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
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+
+use strict;
+use warnings;
+
+use File::Copy;
+use File::Compare qw/compare_text compare/;
+use OpenSSL::Test qw/:DEFAULT srctop_file/;
+use OpenSSL::Test::Utils;
+
+setup("test_app_ecparam");
+
+plan skip_all => "EC or EC2M isn't supported in this build"
+ if disabled("ec") || disabled("ec2m");
+
+# The parameter corpus belongs to 15-test_ecparam.t, so it has to be named
+# by path rather than through data_file(), which resolves against the data
+# directory of the recipe that calls it.
+sub param_file {
+ return srctop_file("test", "recipes", "15-test_ecparam_data", @_);
+}
+
+# The parameter corpus is swept in process by 15-test_ecparam.t. What is
+# tested here is the applications: their options, and that they write what
+# they read. A representative curve is enough for that; running the whole
+# corpus through them buys process startup rather than coverage.
+my $named = param_file('valid', 'secp384r1-named.pem');
+my $explicit = param_file('valid', 'secp384r1-explicit.pem');
+my $prime = param_file('valid', 'prime256v1-named.pem');
+
+my $no_fips = disabled('fips') || ($ENV{NO_FIPS} // 0);
+
+plan tests => 8;
+
+sub checkcompare {
+ my $files = shift; # List of files
+ my $app = shift; # Which application
+
+ foreach (@$files) {
+ my $testout = "$app.tst";
+
+ ok(run(app(['openssl', $app, '-out', $testout, '-in', $_])));
+ ok(!compare_text($_, $testout, sub {
+ my $in1 = $_[0];
+ my $in2 = $_[1];
+ $in1 =~ s/\r\n/\n/g;
+ $in2 =~ s/\r\n/\n/g;
+ $in1 ne $in2}), "Original file $_ is the same as new one");
+ }
+}
+
+sub check_identical {
+ my $apps = shift; # List of applications
+
+ foreach (@$apps) {
+ my $inout = "$_.tst";
+ my $backup = "backup.tst";
+
+ copy($inout, $backup);
+ ok(run(app(['openssl', $_, '-in', $inout, '-out', $inout])));
+ ok(!compare($inout, $backup), "converted file $inout did not change");
+ }
+}
+
+my @representative = ($named, $explicit, $prime);
+
+subtest "Check ecparam does not change the parameter file on output" => sub {
+ plan tests => 2 * scalar(@representative);
+ checkcompare(\@representative, "ecparam");
+};
+
+subtest "Check pkeyparam does not change the parameter file on output" => sub {
+ plan tests => 2 * scalar(@representative);
+ checkcompare(\@representative, "pkeyparam");
+};
+
+my @apps = ("ecparam", "pkeyparam");
+subtest "Check param apps do not garble infile identical to outfile" => sub {
+ plan tests => 2 * scalar(@apps);
+ check_identical(\@apps);
+};
+
+subtest "Check loading of fips and non-fips params" => sub {
+ plan skip_all => "FIPS is disabled"
+ if $no_fips;
+ plan tests => 8;
+
+ my $fipsconf = srctop_file("test", "fips-and-base.cnf");
+ my $defaultconf = srctop_file("test", "default.cnf");
+
+ $ENV{OPENSSL_CONF} = $fipsconf;
+
+ ok(run(app(['openssl', 'ecparam',
+ '-in', param_file('valid', 'secp384r1-explicit.pem'),
+ '-check'])),
+ "Loading explicitly encoded valid curve");
+
+ ok(run(app(['openssl', 'ecparam',
+ '-in', param_file('valid', 'secp384r1-named.pem'),
+ '-check'])),
+ "Loading named valid curve");
+
+ ok(!run(app(['openssl', 'ecparam',
+ '-in', param_file('valid', 'secp112r1-named.pem'),
+ '-check'])),
+ "Fail loading named non-fips curve");
+
+ ok(!run(app(['openssl', 'pkeyparam',
+ '-in', param_file('valid', 'secp112r1-named.pem'),
+ '-check'])),
+ "Fail loading named non-fips curve using pkeyparam");
+
+ ok(run(app(['openssl', 'ecparam',
+ '-provider', 'default',
+ '-propquery', '?fips!=yes',
+ '-in', param_file('valid', 'secp112r1-named.pem'),
+ '-check'])),
+ "Loading named non-fips curve in FIPS mode with non-FIPS property".
+ " query");
+
+ ok(run(app(['openssl', 'pkeyparam',
+ '-provider', 'default',
+ '-propquery', '?fips!=yes',
+ '-in', param_file('valid', 'secp112r1-named.pem'),
+ '-check'])),
+ "Loading named non-fips curve in FIPS mode with non-FIPS property".
+ " query using pkeyparam");
+
+ ok(!run(app(['openssl', 'ecparam',
+ '-genkey', '-name', 'secp112r1'])),
+ "Fail generating key for named non-fips curve");
+
+ ok(run(app(['openssl', 'ecparam',
+ '-provider', 'default',
+ '-propquery', '?fips!=yes',
+ '-genkey', '-name', 'secp112r1'])),
+ "Generating key for named non-fips curve with non-FIPS property query");
+
+ $ENV{OPENSSL_CONF} = $defaultconf;
+};
+
+subtest "Check ecparam -param_enc converts between named and explicit" => sub {
+ plan tests => 3;
+
+ # The encodings are canonical, so re-encoding a named curve as explicit
+ # (and vice versa) must reproduce the matching reference file byte for byte.
+ my $to_explicit = 'param-explicit.tst';
+ ok(run(app(['openssl', 'ecparam', '-in', $named, '-param_enc', 'explicit',
+ '-out', $to_explicit]))
+ && !compare($to_explicit, $explicit),
+ "named_curve params re-encoded as explicit match the reference file");
+
+ my $to_named = 'param-named.tst';
+ ok(run(app(['openssl', 'ecparam', '-in', $explicit, '-param_enc',
+ 'named_curve', '-out', $to_named]))
+ && !compare($to_named, $named),
+ "explicit params re-encoded as named_curve match the reference file");
+
+ ok(!run(app(['openssl', 'ecparam', '-in', $named, '-noout',
+ '-param_enc', 'bogus'])),
+ "an invalid parameter encoding is rejected");
+};
+
+subtest "Check ecparam -inform and -outform handling" => sub {
+ plan tests => 4;
+
+ my $der = 'param.der';
+ ok(run(app(['openssl', 'ecparam', '-in', $named, '-outform', 'DER',
+ '-out', $der])),
+ "write DER-encoded parameters");
+ my $pem = 'param-der.pem';
+ ok(run(app(['openssl', 'ecparam', '-inform', 'DER', '-in', $der,
+ '-out', $pem]))
+ && !compare($pem, $named),
+ "parameters survive a PEM -> DER -> PEM roundtrip");
+
+ ok(!run(app(['openssl', 'ecparam', '-in', $der, '-noout'])),
+ "DER input without -inform is rejected as the default is PEM");
+
+ ok(!run(app(['openssl', 'ecparam', '-in', $named, '-outform', 'MSBLOB',
+ '-out', 'param.tmp'])),
+ "-outform is limited to PEM and DER");
+};
+
+subtest "Check ecparam -conv_form selects the generator point encoding" => sub {
+ plan tests => 5;
+
+ # Only explicit parameters encode the generator point; the reference file
+ # uses the default uncompressed form.
+ my $comp = 'param-comp.pem';
+ ok(run(app(['openssl', 'ecparam', '-in', $explicit, '-conv_form',
+ 'compressed', '-out', $comp])),
+ "write explicit parameters with a compressed generator");
+ ok((-s $comp) < (-s $explicit),
+ "compressed generator encoding is smaller than uncompressed");
+
+ my $back = 'param-unc.pem';
+ ok(run(app(['openssl', 'ecparam', '-in', $comp, '-conv_form',
+ 'uncompressed', '-out', $back]))
+ && !compare($back, $explicit),
+ "converting back to uncompressed matches the reference file");
+
+ my $namedout = 'param-named-conv.pem';
+ ok(run(app(['openssl', 'ecparam', '-in', $named, '-conv_form',
+ 'compressed', '-out', $namedout]))
+ && !compare($namedout, $named),
+ "-conv_form does not change named curve parameters");
+
+ ok(!run(app(['openssl', 'ecparam', '-in', $named, '-noout',
+ '-conv_form', 'bogus'])),
+ "an invalid conversion form is rejected");
+};
+
+subtest "Check ecparam -text and -list_curves" => sub {
+ plan tests => 7;
+
+ # Named parameters print the curve identification.
+ my @named = run(app(['openssl', 'ecparam', '-text', '-noout', '-in', $named],
+ stderr => undef),
+ capture => 1);
+ chomp @named;
+ ok(grep(/^EC-Parameters: \(384 bit field, 192 bit security level\)$/, @named),
+ "named parameters print the EC-Parameters header");
+ ok(grep(/^ASN1 OID: secp384r1$/, @named),
+ "named parameters print the expected curve OID");
+ ok(grep(/^NIST CURVE: P-384$/, @named),
+ "named parameters print the expected NIST curve name");
+
+ # Explicit parameters print the field parameters instead of the curve name.
+ my @explicit = run(app(['openssl', 'ecparam', '-text', '-noout',
+ '-in', $explicit],
+ stderr => undef),
+ capture => 1);
+ chomp @explicit;
+ ok(grep(/^EC-Parameters: \(384 bit field, 192 bit security level\)$/, @explicit),
+ "explicit parameters print the EC-Parameters header");
+ ok(grep(/^Field Type: prime-field$/, @explicit)
+ && grep(/^Cofactor:/, @explicit),
+ "explicit parameters print the field parameters");
+ ok(!grep(/^ASN1 OID:/, @explicit),
+ "explicit parameters do not print a curve OID");
+
+ ok(run(app(['openssl', 'ecparam', '-list_curves'])), "Test -list_curves");
+};