/* * 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 #include #include #include #include #include #include #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 * * 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 "); 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); }