From: Viktor Dukhovni Date: Sat, 18 Jan 2025 23:48:01 +0000 (+1100) Subject: ASN.1 ML-KEM private key format X-Git-Tag: openssl-3.5.0-alpha1~523 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=318994a121daa479f9418514ccf0ac196792de3b;p=thirdparty%2Fopenssl.git ASN.1 ML-KEM private key format Reviewed-by: Matt Caswell Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/26512) --- diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index a796cf48e68..1ab85982337 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -1131,6 +1131,7 @@ PROV_R_MISSING_SEED:140:missing seed PROV_R_MISSING_SESSION_ID:133:missing session id PROV_R_MISSING_TYPE:134:missing type PROV_R_MISSING_XCGHASH:135:missing xcghash +PROV_R_ML_KEM_NO_FORMAT:251:ml kem no format PROV_R_MODULE_INTEGRITY_FAILURE:214:module integrity failure PROV_R_NOT_A_PRIVATE_KEY:221:not a private key PROV_R_NOT_A_PUBLIC_KEY:220:not a public key diff --git a/crypto/ml_kem/ml_kem.c b/crypto/ml_kem/ml_kem.c index 1379d849a2b..990caa83d9e 100644 --- a/crypto/ml_kem/ml_kem.c +++ b/crypto/ml_kem/ml_kem.c @@ -7,12 +7,17 @@ * https://www.openssl.org/source/license.html */ -#include -#include -#include -#include +#include #include #include +#include +#include +#include "crypto/ctype.h" +#include "crypto/ml_kem.h" +#include "internal/common.h" +#include "internal/constant_time.h" +#include "internal/sha3.h" +#include "internal/encoder.h" #if defined(OPENSSL_CONSTANT_TIME_VALIDATION) #include @@ -167,12 +172,96 @@ static void scalar_encode(uint8_t *out, const scalar *s, int bits); #endif +/* + * Indices of slots in the vinfo tables below + */ +#define ML_KEM_512_VINFO 0 +#define ML_KEM_768_VINFO 1 +#define ML_KEM_1024_VINFO 2 + +/*- + * Tables describing supported ASN.1 input/output formats. + * + * On output the PKCS8 info table order is important: + * - When we have a seed we'll use the first entry with a non-zero seed offset. + * - Otherwise, the first entry with a zero seed offset. + * + * As written, when possible, we prefer to output both the seed and private + * key, otherwise, just the private key ([1] IMPLICIT OCTET STRING form). + * + * The various lengths in the PKCS#8 tag/len fields could have been left + * zeroed, and filled in on the fly from the algorithm parameters, but that + * makes the code more complex, so a choice was made to embed them directly + * into the tables. Had they been zeroed, one table could cover all three + * algorithms. + */ + +/*- + * ML-KEM-512: + * Public key bytes: 800 (0x0320) + * Private key bytes: 1632 (0x0660) + */ +static const ML_KEM_SPKI_INFO ml_kem_512_spki_info = { + { 0x30, 0x82, 0x03, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x04, 0x01, 0x03, 0x82, 0x03, 0x21, 0x00, } +}; +static const ML_KEM_PKCS8_INFO ml_kem_512_pkcs8_info[] = { + { "seed-priv", 1706, 0x308206a6, 0x0440, 6, 0x81820660, 74, 0, }, + { "priv-only", 1640, 0x30820664, 0, 0, 0x81820660, 8, 0, }, + { "seed-only", 68, 0x30420440, 0x0440, 4, 0, 0, 0, }, + { "priv-oqs", 1636, 0x04820660, 0, 0, 0x04820660, 4, 0, }, + { "pair-oqs", 2436, 0x04820980, 0, 0, 0x04820980, 4, 1636, }, +}; + +/*- + * ML-KEM-768: + * Public key bytes: 1184 (0x04a0) + * Private key bytes: 2400 (0x0960) + */ +static const ML_KEM_SPKI_INFO ml_kem_768_spki_info = { + { 0x30, 0x82, 0x04, 0xb2, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x04, 0x02, 0x03, 0x82, 0x04, 0xa1, 0x00, } +}; +static const ML_KEM_PKCS8_INFO ml_kem_768_pkcs8_info[] = { + { "seed-priv", 2474, 0x308209a6, 0x0440, 6, 0x81820960, 74, 0, }, + { "priv-only", 2408, 0x30820964, 0, 0, 0x81820960, 8, 0, }, + { "seed-only", 68, 0x30420440, 0x0440, 4, 0, 0, 0, }, + { "priv-oqs", 2404, 0x04820960, 0, 0, 0x04820960, 4, 0, }, + { "pair-oqs", 3588, 0x04820e00, 0, 0, 0x04820e00, 4, 2404, }, +}; + +/*- + * ML-KEM-1024: + * Private key bytes: 3168 (0x0c60) + * Public key bytes: 1568 (0x0620) + */ +static const ML_KEM_SPKI_INFO ml_kem_1024_spki_info = { + { 0x30, 0x82, 0x06, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x04, 0x03, 0x03, 0x82, 0x06, 0x21, 0x00, } +}; +static const ML_KEM_PKCS8_INFO ml_kem_1024_pkcs8_info[] = { + { "seed-priv", 3242, 0x30820ca6, 0x0440, 6, 0x81820c60, 74, 0, }, + { "priv-only", 3176, 0x30820c64, 0, 0, 0x81820c60, 8, 0, }, + { "seed-only", 68, 0x30420440, 0x0440, 4, 0, 0, 0, }, + { "priv-oqs", 3172, 0x04820c60, 0, 0, 0x04820c60, 4, 0, }, + { "pair-oqs", 4740, 0x04821280, 0, 0, 0x04821280, 4, 3172, }, +}; + +#define NUM_PKCS8_FORMATS 5 + +typedef struct { + const ML_KEM_PKCS8_INFO *vp8_entry; + int vp8_pref; +} ML_KEM_PKCS8_PREF; + /* * Per-variant fixed parameters */ static const ML_KEM_VINFO vinfo_map[3] = { { "ML-KEM-512", + &ml_kem_512_spki_info, + ml_kem_512_pkcs8_info, PRVKEY_BYTES(512), sizeof(struct prvkey_512_alloc), PUBKEY_BYTES(512), @@ -189,6 +278,8 @@ static const ML_KEM_VINFO vinfo_map[3] = { }, { "ML-KEM-768", + &ml_kem_768_spki_info, + ml_kem_768_pkcs8_info, PRVKEY_BYTES(768), sizeof(struct prvkey_768_alloc), PUBKEY_BYTES(768), @@ -205,6 +296,8 @@ static const ML_KEM_VINFO vinfo_map[3] = { }, { "ML-KEM-1024", + &ml_kem_1024_spki_info, + ml_kem_1024_pkcs8_info, PRVKEY_BYTES(1024), sizeof(struct prvkey_1024_alloc), PUBKEY_BYTES(1024), @@ -1146,7 +1239,7 @@ int gencbd_vector_ntt(scalar *out, CBD_FUNC cbd, uint8_t *counter, * * Note also that the input public key is assumed to hold a precomputed matrix * |A| (our key->m, with the public key holding an expanded (16-bit per scalar - * coefficient) key->t vector. + * coefficient) key->t vector). * * Caller passes storage in |tmp| for for two temporary vectors. */ @@ -1562,11 +1655,11 @@ const ML_KEM_VINFO *ossl_ml_kem_get_vinfo(int evp_type) { switch (evp_type) { case EVP_PKEY_ML_KEM_512: - return &vinfo_map[0]; + return &vinfo_map[ML_KEM_512_VINFO]; case EVP_PKEY_ML_KEM_768: - return &vinfo_map[1]; + return &vinfo_map[ML_KEM_768_VINFO]; case EVP_PKEY_ML_KEM_1024: - return &vinfo_map[2]; + return &vinfo_map[ML_KEM_1024_VINFO]; } return NULL; } @@ -1994,3 +2087,406 @@ int ossl_ml_kem_pubkey_cmp(const ML_KEM_KEY *key1, const ML_KEM_KEY *key2) */ return (ossl_ml_kem_have_pubkey(key1) ^ ossl_ml_kem_have_pubkey(key2)); } + +#ifndef FIPS_MODULE +static int vp8_pref_cmp(const void *va, const void *vb) +{ + const ML_KEM_PKCS8_PREF *a = va; + const ML_KEM_PKCS8_PREF *b = vb; + + /* + * Zeros sort last, otherwise the sort is in increasing order. + * + * The preferences are small enough to ensure the comparison is monotone as + * required. Some versions of qsort(3) have been known to crash when the + * comparison is not monotone. + */ + if (a->vp8_pref > 0 && b->vp8_pref > 0) + return a->vp8_pref - b->vp8_pref; + if (a->vp8_pref == 0) + return b->vp8_pref; + return -a->vp8_pref; +} + +static ML_KEM_PKCS8_PREF *vp8_order(const ML_KEM_VINFO *v, + const char *direction, const char *formats) +{ + ML_KEM_PKCS8_PREF *ret; + int i, count = 0; + const char *fmt = formats, *end; + const char *sep = "\t ,"; + + if ((ret = OPENSSL_zalloc((NUM_PKCS8_FORMATS + 1) * sizeof(*ret))) == NULL) + return NULL; + for (i = 0; i < NUM_PKCS8_FORMATS; ++i) { + ret[i].vp8_entry = &v->pkcs8_info[i]; + ret[i].vp8_pref = 0; + } + + /* Default to compile-time table order. */ + if (formats == NULL) + return ret; + + /* Formats are case-insensitive, separated by spaces, tabs and/or commas */ + do { + if (*(fmt += strspn(fmt, sep)) == '\0') + break; + end = fmt + strcspn(fmt, sep); + for (i = 0; i < NUM_PKCS8_FORMATS; ++i) { + if (ret[i].vp8_pref > 0 + || OPENSSL_strncasecmp(ret[i].vp8_entry->p8_name, + fmt, (end - fmt)) != 0) + continue; + ret[i].vp8_pref = ++count; + break; + } + fmt = end; + } while (count < NUM_PKCS8_FORMATS); + + if (count == 0) { + OPENSSL_free(ret); + ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT, + "no %s private key %s formats are enabled", + v->algorithm_name, direction); + return NULL; + } + qsort(ret, NUM_PKCS8_FORMATS, sizeof(*ret), vp8_pref_cmp); + ret[count].vp8_entry = NULL; + return ret; +} + +ML_KEM_KEY * +ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen, int evp_type, + OSSL_LIB_CTX *libctx, const char *propq) +{ + const ML_KEM_VINFO *v; + const ML_KEM_SPKI_INFO *vspki; + ML_KEM_KEY *ret; + + if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL) + return NULL; + vspki = v->spki_info; + if (publen != ML_KEM_SPKI_OVERHEAD + (ossl_ssize_t) v->pubkey_bytes + || memcmp(pubenc, vspki->asn1_prefix, ML_KEM_SPKI_OVERHEAD) != 0) + return NULL; + publen -= ML_KEM_SPKI_OVERHEAD; + pubenc += ML_KEM_SPKI_OVERHEAD; + + if ((ret = ossl_ml_kem_key_new(libctx, propq, 0, evp_type)) == NULL) + return NULL; + + if (!ossl_ml_kem_parse_public_key(pubenc, (size_t) publen, ret)) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING, + "errror parsing %s public key from input SPKI", + v->algorithm_name); + ossl_ml_kem_key_free(ret); + return NULL; + } + + return ret; +} + +ML_KEM_KEY * +ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen, + int retain_seed, const char *formats, + int evp_type, OSSL_LIB_CTX *libctx, + const char *propq) +{ + const ML_KEM_VINFO *v; + ML_KEM_PKCS8_PREF *vp8_alloc = NULL, *vp8_slot; + const ML_KEM_PKCS8_INFO *vp8; + ML_KEM_KEY *key = NULL, *ret = NULL; + PKCS8_PRIV_KEY_INFO *p8inf = NULL; + const uint8_t *buf, *pos; + const X509_ALGOR *alg = NULL; + int len, ptype; + uint32_t magic; + uint16_t seed_magic; + + /* Which ML-KEM variant? */ + if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL) + return 0; + + /* Extract the key OID and any parameters. */ + if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, &prvenc, prvlen)) == NULL) + return 0; + /* Shortest prefix is 4 bytes: seq tag/len + octet string tag/len */ + if (!PKCS8_pkey_get0(NULL, &buf, &len, &alg, p8inf)) + goto end; + /* Bail out early if this is some other key type. */ + if (OBJ_obj2nid(alg->algorithm) != evp_type) + goto end; + + /* Get the list of enabled decoders. Their order is not important here. */ + vp8_slot = vp8_alloc = vp8_order(v, "input", formats); + if (vp8_alloc == NULL) + goto end; + + /* Parameters must be absent. */ + X509_ALGOR_get0(NULL, &ptype, NULL, alg); + if (ptype != V_ASN1_UNDEF) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_UNEXPECTED_KEY_PARAMETERS, + "unexpected parameters with a PKCS#8 %s private key", + v->algorithm_name); + goto end; + } + if ((ossl_ssize_t)len < (ossl_ssize_t)sizeof(magic)) + goto end; + + /* Find the matching p8 info slot, this has the expected length. */ + pos = OPENSSL_load_u32_be(&magic, buf); + for (vp8_slot = vp8_alloc; vp8_slot->vp8_entry != NULL; ++vp8_slot) { + if (magic == vp8_slot->vp8_entry->p8_magic + && len == (ossl_ssize_t)vp8_slot->vp8_entry->p8_bytes) + break; + } + if ((vp8 = vp8_slot->vp8_entry) == NULL) + goto end; + + if (vp8->seed_offset > 0) { + /* Check |seed| tag/len, if not subsumed by |magic|. */ + if (pos == buf + vp8->seed_offset - 2) { + pos = OPENSSL_load_u16_be(&seed_magic, pos); + if (seed_magic != vp8->seed_magic) + goto end; + } else if (pos != buf + vp8->seed_offset) { + goto end; + } + pos += ML_KEM_SEED_BYTES; + } + if (vp8->priv_offset > 0) { + /* Check |priv| tag/len */ + if (pos == buf + vp8->priv_offset - 4) { + pos = OPENSSL_load_u32_be(&magic, pos); + if (magic != vp8->priv_magic) + goto end; + } else if (pos != buf + vp8->priv_offset) { + goto end; + } + pos += v->prvkey_bytes; + } + if (vp8->pub_offset > 0) { + if (pos != buf + vp8->pub_offset) + goto end; + pos += v->pubkey_bytes; + } + if (pos != buf + len) + goto end; + + if (vp8->seed_offset > 0) { + if ((key = ossl_ml_kem_key_new(libctx, propq, + retain_seed, evp_type)) != NULL) + ret = ossl_ml_kem_set_seed(buf + vp8->seed_offset, + ML_KEM_SEED_BYTES, key); + else + ERR_raise_data(ERR_LIB_OSSL_DECODER, ERR_R_INTERNAL_ERROR, + "error storing %s private key seed", + v->algorithm_name); + } else { + if ((key = ossl_ml_kem_key_new(libctx, propq, 1, evp_type)) != NULL + && ossl_ml_kem_parse_private_key(buf + vp8->priv_offset, + v->prvkey_bytes, key)) + ret = key; + else + ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, + "error parsing %s private key", + v->algorithm_name); + } + /* OQS public key content is ignored */ + + end: + OPENSSL_free(vp8_alloc); + PKCS8_PRIV_KEY_INFO_free(p8inf); + if (ret == NULL) + ossl_ml_kem_key_free(key); + return ret; +} + +/* Same as ossl_ml_kem_encode_pubkey, but allocates the output buffer. */ +int ossl_ml_kem_i2d_pubkey(const ML_KEM_KEY *key, unsigned char **out) +{ + size_t publen; + + if (!ossl_ml_kem_have_pubkey(key)) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY, + "no %s public key data available", + key->vinfo->algorithm_name); + return 0; + } + publen = key->vinfo->pubkey_bytes; + + if (out != NULL + && (*out = OPENSSL_malloc(publen)) == NULL) + return 0; + if (!ossl_ml_kem_encode_public_key(*out, publen, key)) { + ERR_raise_data(ERR_LIB_OSSL_ENCODER, ERR_R_INTERNAL_ERROR, + "error encoding %s public key", + key->vinfo->algorithm_name); + OPENSSL_free(*out); + return 0; + } + + return (int)publen; +} + +/* Allocate and encode PKCS#8 private key payload. */ +int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, uint8_t **out, + const char *formats) +{ + const ML_KEM_VINFO *v = key->vinfo; + ML_KEM_PKCS8_PREF *vp8_alloc, *vp8_slot; + const ML_KEM_PKCS8_INFO *vp8; + int len = ML_KEM_SEED_BYTES; + uint8_t *buf = NULL, *pos; + int ret = 0; + + if (!ossl_ml_kem_have_prvkey(key)) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PRIVATE_KEY, + "no %s private key data available", + key->vinfo->algorithm_name); + return 0; + } + + vp8_slot = vp8_alloc = vp8_order(v, "output", formats); + if (vp8_alloc == NULL) + return 0; + + /* If we don't have a seed, skip seedful entries */ + if (!ossl_ml_kem_have_seed(key)) + while (vp8_slot->vp8_entry != NULL + && vp8_slot->vp8_entry->seed_offset != 0) + ++vp8_slot; + /* No matching table entries, give up */ + if ((vp8 = vp8_slot->vp8_entry) == NULL) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT, + "no matching enabled %s private key output formats", + v->algorithm_name); + goto end; + } + len = vp8->p8_bytes; + + if (out == NULL) { + ret = len; + goto end; + } + + if ((pos = buf = OPENSSL_malloc((size_t) len)) == NULL) + goto end; + + pos = OPENSSL_store_u32_be(pos, vp8->p8_magic); + if (vp8->seed_offset != 0) { + /* + * Either the tag/len were already included in |magic| or they require + * is to write two bytes now. + */ + if (pos == buf + vp8->seed_offset - 2) + pos = OPENSSL_store_u16_be(pos, vp8->seed_magic); + if (pos != buf + vp8->seed_offset + || !ossl_ml_kem_encode_seed(pos, ML_KEM_SEED_BYTES, key)) { + ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR, + "error encoding %s private key", + v->algorithm_name); + goto end; + } + pos += ML_KEM_SEED_BYTES; + } + if (vp8->priv_offset != 0) { + if (pos == buf + vp8->priv_offset - 4) + pos = OPENSSL_store_u32_be(pos, vp8->priv_magic); + if (pos != buf + vp8->priv_offset + || !ossl_ml_kem_encode_private_key(pos, v->prvkey_bytes, key)) { + ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR, + "error encoding %s private key", + v->algorithm_name); + goto end; + } + pos += v->prvkey_bytes; + } + /* OQS form output with tacked-on public key */ + if (vp8->pub_offset != 0) { + /* The OQS pubkey is never separately DER-wrapped */ + if (pos != buf + vp8->pub_offset + || !ossl_ml_kem_encode_public_key(pos, v->pubkey_bytes, key)) { + ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR, + "error encoding %s private key", + v->algorithm_name); + goto end; + } + pos += v->pubkey_bytes; + } + + if (pos == buf + len) { + *out = buf; + ret = len; + } + + end: + OPENSSL_free(vp8_alloc); + if (ret == 0) + OPENSSL_free(buf); + return ret; +} + +int ossl_ml_kem_key_to_text(BIO *out, const ML_KEM_KEY *key, int selection) +{ + uint8_t seed[ML_KEM_SEED_BYTES], *prvenc = NULL, *pubenc = NULL; + size_t publen, prvlen; + const char *type_label = NULL; + int ret = 0; + + if (out == NULL || key == NULL) { + ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + type_label = key->vinfo->algorithm_name; + publen = key->vinfo->pubkey_bytes; + prvlen = key->vinfo->prvkey_bytes; + + if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0 + && (ossl_ml_kem_have_prvkey(key) + || ossl_ml_kem_have_seed(key))) { + if (BIO_printf(out, "%s Private-Key:\n", type_label) <= 0) + return 0; + + if (ossl_ml_kem_have_seed(key)) { + if (!ossl_ml_kem_encode_seed(seed, sizeof(seed), key)) + goto end; + if (!ossl_bio_print_labeled_buf(out, "seed:", seed, sizeof(seed))) + goto end; + } + if (ossl_ml_kem_have_prvkey(key)) { + if ((prvenc = OPENSSL_malloc(prvlen)) == NULL) + return 0; + if (!ossl_ml_kem_encode_private_key(prvenc, prvlen, key)) + goto end; + if (!ossl_bio_print_labeled_buf(out, "dk:", prvenc, prvlen)) + goto end; + } + ret = 1; + } + + /* The public key is output regardless of the selection */ + if (ossl_ml_kem_have_pubkey(key)) { + /* If we did not output private key bits, this is a public key */ + if (ret == 0 && BIO_printf(out, "%s Public-Key:\n", type_label) <= 0) + goto end; + + if ((pubenc = OPENSSL_malloc(key->vinfo->pubkey_bytes)) == NULL + || !ossl_ml_kem_encode_public_key(pubenc, publen, key) + || !ossl_bio_print_labeled_buf(out, "ek:", pubenc, publen)) + goto end; + ret = 1; + } + + /* If we got here, and ret == 0, there was no key material */ + if (ret == 0) + ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY, + "no %s key material available", + type_label); + + end: + OPENSSL_free(pubenc); + OPENSSL_free(prvenc); + return ret; +} +#endif diff --git a/doc/man7/EVP_PKEY-ML-KEM.pod b/doc/man7/EVP_PKEY-ML-KEM.pod index 12b687902ec..d787cb57701 100644 --- a/doc/man7/EVP_PKEY-ML-KEM.pod +++ b/doc/man7/EVP_PKEY-ML-KEM.pod @@ -68,6 +68,92 @@ When this parameter is simply omitted, and the C provider configuration parameter is set to a false boolean value, the seed is not retained, otherwise it is retained (see also L). +=item C (B) + +List of enabled private key input formats in PKCS#8 files. +List elements are separated by commas and/or spaces or tabs. +The list of enabled formats can be specified in the configuration file, as seen +in the L section below, or the via the B<-provparam> command-line +option. + +Values specified on the command-line override any configuration file settings. +By default all the supported formats are enabled. +The supported formats are: + +=over 4 + +=item C: + +This format represents keys in which both the 64-byte B<(d, z)> seed and the +FIPS 203 decapsulation private key B are present in the PKCS#8 private key +as part of the DER encoding of the ASN.1 sequence: + + PrivateKey ::= SEQUENCE { + seed OCTET STRING OPTIONAL, + expandedKey [1] IMPLICIT OCTET STRING OPTIONAL } + (WITH COMPONENTS {..., seed PRESENT } | + WITH COMPONENTS {..., expandedKey PRESENT }) + +If the C format is not included in the list, this format will not be +recognised on input. + +=item C: + +This format represents keys in which only the 64-byte B<(d, z)> seed is present +in the above sequence. +If the C format is not included in the list, this format will not be +recognised on input. + +=item C: + +This format represents keys in which only the FIPS 203 decapsulation key B +is present in the above sequence. +If the C format is not included in the list, this format will not be +recognised on input. + +=item C: + +This format represents keys in which the private key value is a DER encoding of an +octet string containing the FIPS 203 decapsulation key B. +This format is used in some builds of the C, with a non-NIST value of +the algorithm OID by default. +For interoperability with OpenSSL, environment variable settings, such as +C, need to be used to configure +C to use the expected NIST OIDs for B. +If the C format is not included in the list, this format will not be +recognised on input. + +=item C: + +This format represents keys in which the private keys a DER encoding of an +octet string containing the concatenaton of the FIPS 203 decapsulation key B and +the encapsulation key B. +This encoding is used in some builds of the C, with a non-NIST +value of the OID by default. +For interoperability with OpenSSL, environment variable settings, such as +C, need to be used to configure +C to use the expected NIST OIDs for B. +If the C format is not included in the list, this format will not be +recognised on input. + +=back + +=item C (B) + +Ordered list of enabled private key output formats when writing PKCS#8 files. +List elements are separated by commas and/or spaces or tabs. +The list of enabled formats can be specified in the configuration file, as seen +in the L section below, or the via the B<-provparam> command-line +option. + +This supports the same set of formats as described under C +above. +The order in which elements are listed is important, the selected format will be +the first one that is possible to output. +If the key seed is known, the first listed format will be selected. +If the key seed is not known, the first format that omits the seed will be selected. +By default C is listed first and C second. + =back Use EVP_PKEY_CTX_set_params() after calling EVP_PKEY_keygen_init(). @@ -153,7 +239,11 @@ In the B file, this looks like: # Can be referenced in one or more provider sections [ml_kem_sect] - retain_seed = no + retain_seed = yes + # OQS legacy formats disabled + input_formats = seed-priv, seed-only, priv-only + # Output either the seed alone, or else the key alone + output_formats = seed-only, priv-only [providers_sect] default = default_sect diff --git a/include/crypto/ml_kem.h b/include/crypto/ml_kem.h index c47ff12779e..63225810d53 100644 --- a/include/crypto/ml_kem.h +++ b/include/crypto/ml_kem.h @@ -12,6 +12,7 @@ # pragma once # include +# include # include # include @@ -121,8 +122,49 @@ * ----------------------------- */ + /*- + * The DER ASN.1 encoding of ML-KEM (and ML-DSA) public keys prepends 22 bytes + * to the encoded public key: + * + * - 4 byte outer sequence tag and length + * - 2 byte algorithm sequence tag and length + * - 2 byte algorithm OID tag and length + * - 9 byte algorithm OID (from NIST CSOR OID arc) + * - 4 byte bit string tag and length + * - 1 bitstring lead byte + */ +# define ML_KEM_SPKI_OVERHEAD 22 +typedef struct { + const uint8_t asn1_prefix[ML_KEM_SPKI_OVERHEAD]; +} ML_KEM_SPKI_INFO; + +/*- + * For each algorithm we support a few PKCS#8 input formats, + * + * - Seed: SEQUENCE(OCTET STRING) + * - Private key: SEQUENCE([1] IMPLICIT OCTET STRING) + * - Seed & private key: SEQUENCE(OCTET STRING, [1] IMPLICIT OCTET STRING) + * - OQS private key: OCTET STRING + * - OQS private + public key: OCTET STRING + * (The public key is ignored, just as with PKCS#8 v2.) + * + * An offset of zero means that particular field is absent. + */ +typedef struct { + const char *p8_name; + size_t p8_bytes; + uint32_t p8_magic; + uint16_t seed_magic; + size_t seed_offset; + uint32_t priv_magic; + size_t priv_offset; + size_t pub_offset; +} ML_KEM_PKCS8_INFO; + typedef struct { const char *algorithm_name; + const ML_KEM_SPKI_INFO *spki_info; + const ML_KEM_PKCS8_INFO *pkcs8_info; size_t prvkey_bytes; size_t prvalloc; size_t pubkey_bytes; @@ -226,6 +268,17 @@ int ossl_ml_kem_parse_private_key(const uint8_t *in, size_t len, ML_KEM_KEY *ossl_ml_kem_set_seed(const uint8_t *seed, size_t seedlen, ML_KEM_KEY *key); __owur +ML_KEM_KEY *ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen, + int evp_type, OSSL_LIB_CTX *libctx, + const char *propq); +__owur +ML_KEM_KEY *ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen, + int retain_seed, const char *formats, + int evp_type, OSSL_LIB_CTX *ctx, + const char *propq); +__owur +int ossl_ml_kem_key_to_text(BIO *out, const ML_KEM_KEY *key, int selection); +__owur int ossl_ml_kem_genkey(uint8_t *pubenc, size_t publen, ML_KEM_KEY *key); /* @@ -237,8 +290,13 @@ __owur int ossl_ml_kem_encode_public_key(uint8_t *out, size_t len, const ML_KEM_KEY *key); __owur +int ossl_ml_kem_i2d_pubkey(const ML_KEM_KEY *key, unsigned char **out); +__owur int ossl_ml_kem_encode_private_key(uint8_t *out, size_t len, const ML_KEM_KEY *key); +__owur +int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, unsigned char **out, + const char *formats); int ossl_ml_kem_encode_seed(uint8_t *out, size_t len, const ML_KEM_KEY *key); diff --git a/include/openssl/core_names.h.in b/include/openssl/core_names.h.in index c14520fe286..d26893a8869 100644 --- a/include/openssl/core_names.h.in +++ b/include/openssl/core_names.h.in @@ -109,6 +109,9 @@ extern "C" { #define OSSL_KEM_PARAM_OPERATION_RSASVE "RSASVE" #define OSSL_KEM_PARAM_OPERATION_DHKEM "DHKEM" +/* Provider configuration variables */ +#define OSSL_PKEY_RETAIN_SEED "pkey_retain_seed" + /* Parameter name definitions - generated by util/perl/OpenSSL/paramnames.pm */ {- generate_public_macros(); -} diff --git a/include/openssl/proverr.h b/include/openssl/proverr.h index 79c57e3f7ab..d4cc811410a 100644 --- a/include/openssl/proverr.h +++ b/include/openssl/proverr.h @@ -109,6 +109,7 @@ # define PROV_R_MISSING_SESSION_ID 133 # define PROV_R_MISSING_TYPE 134 # define PROV_R_MISSING_XCGHASH 135 +# define PROV_R_ML_KEM_NO_FORMAT 251 # define PROV_R_MODULE_INTEGRITY_FAILURE 214 # define PROV_R_NOT_A_PRIVATE_KEY 221 # define PROV_R_NOT_A_PUBLIC_KEY 220 diff --git a/providers/common/provider_err.c b/providers/common/provider_err.c index 54ee700abef..55edc3218b7 100644 --- a/providers/common/provider_err.c +++ b/providers/common/provider_err.c @@ -153,6 +153,7 @@ static const ERR_STRING_DATA PROV_str_reasons[] = { "missing session id"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_TYPE), "missing type"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_XCGHASH), "missing xcghash"}, + {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_ML_KEM_NO_FORMAT), "ml kem no format"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MODULE_INTEGRITY_FAILURE), "module integrity failure"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_NOT_A_PRIVATE_KEY), "not a private key"}, diff --git a/providers/implementations/encode_decode/decode_der2key.c b/providers/implementations/encode_decode/decode_der2key.c index 0d30c3f08d8..a46a5d69e3e 100644 --- a/providers/implementations/encode_decode/decode_der2key.c +++ b/providers/implementations/encode_decode/decode_der2key.c @@ -13,7 +13,6 @@ */ #include "internal/deprecated.h" -#include #include #include #include @@ -43,26 +42,6 @@ #include "internal/nelem.h" #include "ml_dsa_codecs.h" -#ifndef OPENSSL_NO_ML_KEM -typedef struct { - ASN1_OBJECT *oid; -} BARE_ALGOR; - -typedef struct { - BARE_ALGOR algor; - ASN1_BIT_STRING *pubkey; -} BARE_PUBKEY; - -ASN1_SEQUENCE(BARE_ALGOR) = { - ASN1_SIMPLE(BARE_ALGOR, oid, ASN1_OBJECT), -} static_ASN1_SEQUENCE_END(BARE_ALGOR) - -ASN1_SEQUENCE(BARE_PUBKEY) = { - ASN1_EMBED(BARE_PUBKEY, algor, BARE_ALGOR), - ASN1_SIMPLE(BARE_PUBKEY, pubkey, ASN1_BIT_STRING) -} static_ASN1_SEQUENCE_END(BARE_PUBKEY) -#endif - struct der2key_ctx_st; /* Forward declaration */ typedef int check_key_fn(void *, struct der2key_ctx_st *ctx); typedef void adjust_key_fn(void *, struct der2key_ctx_st *ctx); @@ -583,200 +562,36 @@ static void *sm2_d2i_PKCS8(const unsigned char **der, long der_len, /* ---------------------------------------------------------------------- */ #ifndef OPENSSL_NO_ML_KEM - -/* For lengths between 256 and 65535 the DER encoding is 4 bytes longer */ -#define ML_KEM_OCTET_STRING_OVERHEAD 4 - -/*- - * The DER ASN.1 encoding of ML-KEM and ML-DSA public keys prepends 22 bytes to - * the encoded public key: - * - * - 4 byte outer sequence tag and length - * - 2 byte algorithm sequence tag and length - * - 2 byte algorithm OID tag and length - * - 9 byte algorithm OID (from NIST CSOR OID arc) - * - 4 byte bit string tag and length - * - 1 bitstring lead byte - */ -#define ML_KEM_SPKI_OVERHEAD 22 - -/* - * The accepted private key format is either the 64-bytes (d, z) seed pair, or - * else the optionally DER wrapped octet-string encoding (one byte tag, three - * byte length) of the FIPS 203 expanded |dk| key, optionally concatenated with - * the public |ek|. We don't need to worry about whether the public key is or - * isn't appended, those are just ignored bytes at the end of an input buffer. - */ -typedef enum { - NONE_FMT, /* Unknown or unsupported format */ - SEED_FMT, /* Just the seed, no DER wrapping */ - LONG_FMT, /* Key(s), no octet-string DER wrapping */ - ASN1_FMT /* Key(s), octet-string DER wrapped */ -} ML_KEM_PRIV_FMT; - static void * ml_kem_d2i_PKCS8(const uint8_t **der, long der_len, struct der2key_ctx_st *ctx) { - const ML_KEM_VINFO *vinfo; - ML_KEM_KEY *key = NULL, *ret = NULL; + ML_KEM_KEY *key; OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(ctx->provctx); - PKCS8_PRIV_KEY_INFO *p8inf = NULL; - const unsigned char *p; - const X509_ALGOR *alg = NULL; - ML_KEM_PRIV_FMT fmt; - int plen, ptype, privlen, pairlen; - uint16_t keylen; - int retain; - - /* Extract the key OID and any parameters (we want none of those) */ - if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, der, der_len)) == NULL) - return 0; - if (!PKCS8_pkey_get0(NULL, &p, &plen, &alg, p8inf)) - goto end; - - /* Bail out early if this is some other key type. */ - if (OBJ_obj2nid(alg->algorithm) != ctx->desc->evp_type) - goto end; - vinfo = ossl_ml_kem_get_vinfo(ctx->desc->evp_type); - - /* Parameters must be absent. */ - X509_ALGOR_get0(NULL, &ptype, NULL, alg); - if (ptype != V_ASN1_UNDEF) { - ERR_raise_data(ERR_LIB_PROV, PROV_R_UNEXPECTED_KEY_PARAMETERS, - "unexpected parameters with a PKCS#8 %s private key", - vinfo->algorithm_name); - goto end; - } - privlen = (int) vinfo->prvkey_bytes; - pairlen = (int) (vinfo->prvkey_bytes + vinfo->pubkey_bytes); - - if (plen == ML_KEM_SEED_BYTES) - fmt = SEED_FMT; - else if (plen == privlen || plen == pairlen) - fmt = LONG_FMT; - else if (plen == ML_KEM_OCTET_STRING_OVERHEAD + privlen - || plen == ML_KEM_OCTET_STRING_OVERHEAD + pairlen) - fmt = ASN1_FMT; - else - fmt = NONE_FMT; - - switch (fmt) { - case NONE_FMT: - ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH, - "unexpected PKCS#8 private key length: %d", plen); - break; - case SEED_FMT: - retain = ossl_prov_ctx_get_bool_param( + int retain_seed = ossl_prov_ctx_get_bool_param( ctx->provctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1); - if ((key = ossl_ml_kem_key_new(libctx, ctx->propq, retain, - ctx->desc->evp_type)) != NULL) - ret = ossl_ml_kem_set_seed(p, plen, key); - else - ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR, - "error storing %s private key seed", - vinfo->algorithm_name); - break; - case ASN1_FMT: - if (*p++ != V_ASN1_OCTET_STRING || *p++ != 0x82) - break; - p = OPENSSL_load_u16_be(&keylen, p); - if (keylen != (plen -= ML_KEM_OCTET_STRING_OVERHEAD)) - break; - /* fallthrough */ - case LONG_FMT: - /* Check public key consistency if provided? */ - if ((key = ossl_ml_kem_key_new(libctx, ctx->propq, 1, - ctx->desc->evp_type)) != NULL - && ossl_ml_kem_parse_private_key(p, vinfo->prvkey_bytes, key)) - ret = key; - else - ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, - "error parsing %s private key", - vinfo->algorithm_name); - break; - } + const char *formats = ossl_prov_ctx_get_param( + ctx->provctx, OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS, NULL); - end: - PKCS8_PRIV_KEY_INFO_free(p8inf); - if (ret == NULL) - ossl_ml_kem_key_free(key); - return ret; + key = ossl_ml_kem_d2i_PKCS8(*der, der_len, retain_seed, formats, + ctx->desc->evp_type, libctx, ctx->propq); + if (key != NULL) + *der += der_len; + return key; } static ossl_inline void * ml_kem_d2i_PUBKEY(const uint8_t **der, long der_len, struct der2key_ctx_st *ctx) { - const ML_KEM_VINFO *vinfo = ossl_ml_kem_get_vinfo(ctx->desc->evp_type); - OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(ctx->provctx); - ML_KEM_KEY *ret = NULL; - BARE_PUBKEY *spki; - const uint8_t *end = *der; - - /* - * Not our NID! Is there a sensible error to report here? Why was this - * decoder called? - */ - if (vinfo == NULL) - return NULL; - - /* - * Check that we have the right OID, the bit string has no "bits left" and - * that we consume all the input exactly. - */ - if (der_len != ML_KEM_SPKI_OVERHEAD + (long) vinfo->pubkey_bytes) { - ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING, - "unexpected %s public key length: %ld != %ld", - vinfo->algorithm_name, der_len, - ML_KEM_SPKI_OVERHEAD + (long) vinfo->pubkey_bytes); - return NULL; - } - - if ((spki = OPENSSL_zalloc(sizeof(*spki))) == NULL) - return NULL; + ML_KEM_KEY *key; - /* The spki storage is freed on error */ - if (ASN1_item_d2i_ex((ASN1_VALUE **)&spki, &end, der_len, - ASN1_ITEM_rptr(BARE_PUBKEY), NULL, NULL) == NULL) { - ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING, - "malformed %s public key ASN.1 encoding", - vinfo->algorithm_name); - return NULL; - } - - /* The spki structure now owns some memory */ - if ((spki->pubkey->flags & 0x7) != 0 || end != *der + der_len) { - ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING, - "malformed %s public key ASN.1 encoding", - vinfo->algorithm_name); - goto end; - } - if (OBJ_cmp(OBJ_nid2obj(ctx->desc->evp_type), spki->algor.oid) != 0) { - ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING, - "unexpected algorithm OID for an %s public key", - vinfo->algorithm_name); - goto end; - } - - ret = ossl_ml_kem_key_new(libctx, ctx->propq, 1, ctx->desc->evp_type); - if (ret == NULL - || !ossl_ml_kem_parse_public_key(spki->pubkey->data, - spki->pubkey->length, ret)) { - ossl_ml_kem_key_free(ret); - ret = NULL; - ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING, - "failed to parse %s public key from the input data", - vinfo->algorithm_name); - } - - end: - ASN1_OBJECT_free(spki->algor.oid); - ASN1_BIT_STRING_free(spki->pubkey); - OPENSSL_free(spki); - return ret; + key = ossl_ml_kem_d2i_PUBKEY(*der, der_len, ctx->desc->evp_type, + PROV_LIBCTX_OF(ctx->provctx), ctx->propq); + if (key != NULL) + *der += der_len; + return key; } - # define ml_kem_512_evp_type EVP_PKEY_ML_KEM_512 # define ml_kem_512_d2i_private_key NULL # define ml_kem_512_d2i_public_key NULL diff --git a/providers/implementations/encode_decode/encode_key2any.c b/providers/implementations/encode_decode/encode_key2any.c index d2370c99016..b2db2983701 100644 --- a/providers/implementations/encode_decode/encode_key2any.c +++ b/providers/implementations/encode_decode/encode_key2any.c @@ -12,6 +12,7 @@ */ #include "internal/deprecated.h" +#include #include #include #include @@ -883,68 +884,17 @@ static int ml_dsa_pki_priv_to_der(const void *vkey, unsigned char **pder, static int ml_kem_spki_pub_to_der(const void *vkey, unsigned char **pder, ossl_unused void *ctx) { - const ML_KEM_KEY *key = vkey; - size_t publen; - - if (!ossl_ml_kem_have_pubkey(key)) { - ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER); - return 0; - } - publen = key->vinfo->pubkey_bytes; - - if (pder != NULL - && (*pder = OPENSSL_malloc(publen)) == NULL) - return 0; - if (!ossl_ml_kem_encode_public_key(*pder, publen, key)) { - ERR_raise_data(ERR_LIB_OSSL_ENCODER, ERR_R_INTERNAL_ERROR, - "error encoding %s public key", - key->vinfo->algorithm_name); - OPENSSL_free(*pder); - return 0; - } - - return publen; + return ossl_ml_kem_i2d_pubkey(vkey, pder); } static int ml_kem_pki_priv_to_der(const void *vkey, unsigned char **pder, - ossl_unused void *ctx) + void *vctx) { - const ML_KEM_KEY *key = vkey; - int len = ML_KEM_SEED_BYTES; - - if (!ossl_ml_kem_have_prvkey(key)) { - ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY, - "no %s private key data available", - key->vinfo->algorithm_name); - return 0; - } - - /* - * Output the seed bytes directly when available, but otherwise, be - * compatible with Bouncy Castle's non-seed "long" output format, which is - * just the FIPS 203 |dk| private key bytes sans DER octet-string wrapping. - */ - if (ossl_ml_kem_have_seed(key)) - len = ML_KEM_SEED_BYTES; - else - len = key->vinfo->prvkey_bytes; - - if (pder == NULL) - return len; - - if ((*pder = OPENSSL_malloc(len)) == NULL) - return 0; - - if (ossl_ml_kem_have_seed(key)) { - if (ossl_ml_kem_encode_seed(*pder, len, key)) - return len; - } else { - if (ossl_ml_kem_encode_private_key(*pder, len, key)) - return len; - } + KEY2ANY_CTX *ctx = vctx; + const char *fmtkey = OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS; + const char *formats = ossl_prov_ctx_get_param(ctx->provctx, fmtkey, NULL); - OPENSSL_free(*pder); - return 0; + return ossl_ml_kem_i2d_prvkey(vkey, pder, formats); } # define ml_kem_epki_priv_to_der ml_kem_pki_priv_to_der diff --git a/providers/implementations/encode_decode/encode_key2text.c b/providers/implementations/encode_decode/encode_key2text.c index 3a335eb4bc8..5fd4fcb315e 100644 --- a/providers/implementations/encode_decode/encode_key2text.c +++ b/providers/implementations/encode_decode/encode_key2text.c @@ -438,66 +438,7 @@ static int ecx_to_text(BIO *out, const void *key, int selection) #ifndef OPENSSL_NO_ML_KEM static int ml_kem_to_text(BIO *out, const void *vkey, int selection) { - const ML_KEM_KEY *key = vkey; - uint8_t seed[ML_KEM_SEED_BYTES], *prvenc = NULL, *pubenc = NULL; - size_t publen, prvlen; - const char *type_label = NULL; - int ret = 0; - - if (out == NULL || key == NULL) { - ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER); - return 0; - } - type_label = key->vinfo->algorithm_name; - publen = key->vinfo->pubkey_bytes; - prvlen = key->vinfo->prvkey_bytes; - - if (!ossl_ml_kem_have_pubkey(key)) { - ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY, - "no %s key material available", - type_label); - return 0; - } - - if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) { - if (!ossl_ml_kem_have_prvkey(key)) { - ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY, - "no %s private key material available", - key->vinfo->algorithm_name); - return 0; - } - if (BIO_printf(out, "%s Private-Key:\n", type_label) <= 0) - return 0; - - if ((prvenc = OPENSSL_malloc(prvlen)) == NULL) - return 0; - - if (ossl_ml_kem_have_seed(key)) { - if (!ossl_ml_kem_encode_seed(seed, sizeof(seed), key)) - goto end; - if (!ossl_bio_print_labeled_buf(out, "seed:", seed, sizeof(seed))) - goto end; - } - - if (!ossl_ml_kem_encode_private_key(prvenc, prvlen, key)) - goto end; - if (!ossl_bio_print_labeled_buf(out, "dk:", prvenc, prvlen)) - goto end; - - } else if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) { - if (BIO_printf(out, "%s Public-Key:\n", type_label) <= 0) - goto end; - } - - pubenc = OPENSSL_malloc(key->vinfo->pubkey_bytes); - if (pubenc != NULL) - ret = ossl_ml_kem_encode_public_key(pubenc, publen, key) - && ossl_bio_print_labeled_buf(out, "ek:", pubenc, publen); - - end: - OPENSSL_free(pubenc); - OPENSSL_free(prvenc); - return ret; + return ossl_ml_kem_key_to_text(out, (ML_KEM_KEY *)vkey, selection); } #endif diff --git a/util/perl/OpenSSL/paramnames.pm b/util/perl/OpenSSL/paramnames.pm index 6494cbe169d..d8025431980 100644 --- a/util/perl/OpenSSL/paramnames.pm +++ b/util/perl/OpenSSL/paramnames.pm @@ -420,6 +420,8 @@ my %params = ( # ML-KEM parameters 'PKEY_PARAM_ML_KEM_SEED' => "seed", 'PKEY_PARAM_ML_KEM_RETAIN_SEED' => "ml-kem.retain_seed", + 'PKEY_PARAM_ML_KEM_INPUT_FORMATS' => "ml-kem.input_formats", + 'PKEY_PARAM_ML_KEM_OUTPUT_FORMATS' => "ml-kem.output_formats", # Key generation parameters 'PKEY_PARAM_FFC_TYPE' => "type",