blob: 5accee6177940cfbe54a5ea258777f3b764a9e47 [file] [edit]
/*
* 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 <listfile>
*
* where the list file holds one file name per line. The list is passed
* this way because the corpus has more files in it than a test can be
* given arguments.
*
* 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 char **files;
static int num_files;
static const char *corpus_file(int idx)
{
return files[idx];
}
/* Read the corpus file names, one per line. */
static int read_file_list(const char *path)
{
BIO *bio = BIO_new_file(path, "r");
char line[512];
int n = 0, allocated = 0;
if (bio == NULL)
return 0;
while (BIO_gets(bio, line, sizeof(line)) > 0) {
size_t len = strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
if (len == 0)
continue;
if (n == allocated) {
char **tmp;
allocated = allocated == 0 ? 64 : allocated * 2;
tmp = OPENSSL_realloc(files, allocated * sizeof(*files));
if (tmp == NULL)
goto err;
files = tmp;
}
if ((files[n] = OPENSSL_strdup(line)) == NULL)
goto err;
n++;
}
BIO_free(bio);
num_files = n;
return n > 0;
err:
num_files = n;
BIO_free(bio);
return 0;
}
/*
* 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;
if (!TEST_size_t_eq(test_get_argument_count(), 2)) {
TEST_error("usage: ecparam_test valid|noncanon|invalid <listfile>");
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;
if (!TEST_true(read_file_list(test_get_argument(1)))) {
TEST_error("cannot read corpus list %s", test_get_argument(1));
return 0;
}
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;
}
void cleanup_tests(void)
{
int i;
for (i = 0; i < num_files; i++)
OPENSSL_free(files[i]);
OPENSSL_free(files);
}