blob: 7504bed27092e809fd3fc174215dd4a12d5bea50 [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
*/
/*-
* 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;
}