* https://www.openssl.org/source/license.html
*/
-#include <string.h>
-#include <openssl/rand.h>
#include <openssl/byteorder.h>
-#include <openssl/proverr.h>
-#include <openssl/x509.h>
-#include "crypto/ctype.h"
+#include <openssl/rand.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 <valgrind/memcheck.h>
#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),
},
{
"ML-KEM-768",
- &ml_kem_768_spki_info,
- ml_kem_768_pkcs8_info,
PRVKEY_BYTES(768),
sizeof(struct prvkey_768_alloc),
PUBKEY_BYTES(768),
},
{
"ML-KEM-1024",
- &ml_kem_1024_spki_info,
- ml_kem_1024_pkcs8_info,
PRVKEY_BYTES(1024),
sizeof(struct prvkey_1024_alloc),
PUBKEY_BYTES(1024),
if (ossl_ml_kem_have_prvkey(key))
OPENSSL_cleanse(key->s,
key->vinfo->vector_bytes + 2 * ML_KEM_RANDOM_BYTES);
- else if (ossl_ml_kem_have_seed(key))
- OPENSSL_cleanse(key->seedbuf, sizeof(key->seedbuf));
OPENSSL_free(key->t);
key->d = key->z = (uint8_t *)(key->s = key->m = key->t = NULL);
}
* once the key is generated.
*/
ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, const char *properties,
- int retain_seed, int evp_type)
+ int evp_type)
{
const ML_KEM_VINFO *vinfo = ossl_ml_kem_get_vinfo(evp_type);
ML_KEM_KEY *key;
key->vinfo = vinfo;
key->libctx = libctx;
- key->retain_seed = retain_seed;
+ key->prefer_seed = 1;
+ key->retain_seed = 1;
key->shake128_md = EVP_MD_fetch(libctx, "SHAKE128", properties);
key->shake256_md = EVP_MD_fetch(libctx, "SHAKE256", properties);
key->sha3_256_md = EVP_MD_fetch(libctx, "SHA3-256", properties);
key->sha3_512_md = EVP_MD_fetch(libctx, "SHA3-512", properties);
- key->d = key->z = key->rho = key->pkhash = NULL;
+ key->d = key->z = key->rho = key->pkhash = key->encoded_dk = NULL;
key->s = key->m = key->t = NULL;
if (key->shake128_md != NULL
int ok = 0;
ML_KEM_KEY *ret;
+ /*
+ * Partially decoded keys, not yet imported or loaded, should never be
+ * duplicated.
+ */
+ if (ossl_ml_kem_decoded_key(key))
+ return 0;
+
if (key == NULL
|| (ret = OPENSSL_memdup(key, sizeof(*key))) == NULL)
return NULL;
ret->d = ret->z = ret->rho = ret->pkhash = NULL;
ret->s = ret->m = ret->t = NULL;
- /* Handle seed-only keys */
- if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0
- && !ossl_ml_kem_have_prvkey(key)
- && ossl_ml_kem_have_seed(key)) {
- ret->z = ret->seedbuf;
- ret->d = ret->z + ML_KEM_RANDOM_BYTES;
- }
-
/* Clear selection bits we can't fulfill */
if (!ossl_ml_kem_have_pubkey(key))
selection = 0;
EVP_MD_free(key->sha3_256_md);
EVP_MD_free(key->sha3_512_md);
+ if (ossl_ml_kem_decoded_key(key)) {
+ OPENSSL_cleanse(key->seedbuf, sizeof(key->seedbuf));
+ if (ossl_ml_kem_have_dkenc(key)) {
+ OPENSSL_cleanse(key->encoded_dk, key->vinfo->prvkey_bytes);
+ OPENSSL_free(key->encoded_dk);
+ }
+ }
free_storage(key);
OPENSSL_free(key);
}
int ret = 0;
/* Keys with key material are immutable */
- if (key == NULL || ossl_ml_kem_have_pubkey(key))
+ if (key == NULL
+ || ossl_ml_kem_have_pubkey(key)
+ || ossl_ml_kem_have_dkenc(key))
return 0;
vinfo = key->vinfo;
int ret = 0;
/* Keys with key material are immutable */
- if (key == NULL || ossl_ml_kem_have_pubkey(key))
+ if (key == NULL
+ || ossl_ml_kem_have_pubkey(key)
+ || ossl_ml_kem_have_dkenc(key))
return 0;
vinfo = key->vinfo;
const ML_KEM_VINFO *vinfo;
int ret = 0;
- if (key == NULL || ossl_ml_kem_have_pubkey(key))
+ if (key == NULL
+ || ossl_ml_kem_have_pubkey(key)
+ || ossl_ml_kem_have_dkenc(key))
return 0;
vinfo = key->vinfo;
return 0;
if (ossl_ml_kem_have_seed(key)) {
- ossl_ml_kem_encode_seed(seed, sizeof(seed), key);
+ if (!ossl_ml_kem_encode_seed(seed, sizeof(seed), key))
+ return 0;
key->d = key->z = NULL;
} else if (RAND_priv_bytes_ex(key->libctx, seed, sizeof(seed),
key->vinfo->secbits) <= 0) {
*/
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
- * us 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
=head2 Keygen Parameters
No mandatory parameters are required for generating a key pair.
+To set explicit parameters, use EVP_PKEY_CTX_set_params() after calling
+EVP_PKEY_keygen_init().
=over 4
The seed is also by default retained when keys are loaded from B<PKCS#8> files
in the seed format.
When available, the seed parameter is also used during key export and import,
-with keys regenerated from the seed even if separately provided on import.
+with keys (by default) regenerated from the seed even when also provided on import.
+See L</Provider configuration parameters> below for related controls.
When the seed is retained, it is also available as a B<gettable> parameter,
-and private key output to B<PKCS#8> files will be in seed format.
+and private key output to B<PKCS#8> files will default to seed format.
When the seed is not available, because not included in the B<PKCS#8> file, not
available on import, or not retained, B<PKCS#8> private key files will have the
-private key in FIPS 203 C<dk> format, without DER octet-string wrapping.
+private key in FIPS 203 C<dk> format.
+
+=back
+
+=head2 Common parameters
+
+In addition to the common parameters that all keytypes should support (see
+L<provider-keymgmt(7)/Common Information Parameters>), B<ML-KEM> keys
+keys support the following.
+
+=over 4
+
+=item "pub" (B<OSSL_PKEY_PARAM_PUB_KEY>) <octet string>
+
+The public key value.
+
+This parameter is used when importing or exporting the public key value with
+the EVP_PKEY_fromdata() and EVP_PKEY_todata() functions.
+The key length and content is that of the FIPS 203 (Algorithm 16:
+B<ML-KEM.KeyGen_internal>) B<ek> public key for the given ML-KEM variant.
+Initial import aside, this parameter is otherwise only gettable.
+
+=item "priv" (B<OSSL_PKEY_PARAM_PRIV_KEY>) <octet string>
+
+The private key value.
+
+This parameter is used when importing or exporting the private key value with
+the EVP_PKEY_fromdata() and EVP_PKEY_todata() functions.
+The key length and content is that of the FIPS 203 (Algorithm 16:
+B<ML-KEM.KeyGen_internal>) B<dk> private key for the given ML-KEM variant.
+Initial import aside, this parameter is otherwise only gettable.
+
+=item "encoded-pub-key" (B<OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY>) <octet string>
+
+Used for getting and setting the encoding of a public key.
+The key format is that of B<ek> in FIPS 203, Algorithm 16:
+B<ML-KEM.KeyGen_internal>.
+Updates of the public and private key components are only allowed on keys that
+are empty.
+Once a public or private key component is set, no further changes are allowed.
+This parameter is gettable and settable (once only).
+
+=back
+
+=head2 Provider configuration parameters
-Retention of the seed can be disabled by setting the C<pkey_seed_retain>
-parameter of the C<default> provider to C<no>.
See the description of the B<-provparam> option in L<openssl(1)> to learn
-how to set provider options in the command line tools.
+how to set provider configuration parameters in the command line tools.
See L<OSSL_PROVIDER_add_conf_parameter(3)> to learn how to set provider
configuration options programmatically.
-When using the FIPS provider, the parameter may need to be set in the C<base>
-provider instead (or perhaps in all providers) if the intent is to retain seeds
-read from PKCS#8 files.
-In addition to its use in key generation, this parameter is also gettable and
-settable.
+=over 4
+
+=item C<ml-kem.retain_seed> (B<OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED>) <UTF8 string>
-See L<provider-keymgmt(7)/Common Information Parameters> for further
-information.
+When set to a string representing a false boolean value (see
+L<OSSL_PROVIDER_conf_get_bool(3)>), the seed will not be retained after key
+generation or key import from a seed value.
+If the resulting key is then written to a PKCS#8 object, it will contain
+only the FIPS 203 C<dk> key.
-=item C<ml-kem.retain_seed> (B<OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED>) <int>
+=item C<ml-kem.prefer_seed> (B<OSSL_PKEY_PARAM_ML_KEM_PREFER_SEED>) <UTF8 string>
-This parameter is used only in key generation.
-When keys are generated, by default the seed is retained and used as the
-private key form on output when encoding.
-When this parameter is set to a nonzero value, the seed is retained during key
-generation, when set to 0, the seed is not retained.
-When this parameter is simply omitted, and the C<ml-kem.retain_seed> provider
-configuration parameter is set to a false boolean value, the seed is not
-retained, otherwise it is retained (see also L<OSSL_PROVIDER_conf_get_bool(3)>).
+When decoding PKCS#8 objects that contain both a seed and the FIPS 203 C<dk>
+private key, the seed is by default used to regenerate the key, and the
+companion key is ignored.
+When this configuration parameter is set to a string representing a false
+boolean value (see L<OSSL_PROVIDER_conf_get_bool(3)>), the seed is ignored
+(neither used to regenerate the key, nor retained), and the companion key is
+used instead.
=item C<ml-kem.input_formats> (B<OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS>) <UTF8 string>
-List of enabled private key input formats in PKCS#8 files.
+List of enabled private key input formats when parsing PKCS#8 objects.
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</EXAMPLES> section below, or the via the B<-provparam> command-line
-option.
+option (see also L<OSSL_PROVIDER_add_conf_parameter(3)>).
Values specified on the command-line override any configuration file settings.
By default all the supported formats are enabled.
=back
-=item C<ml-kem.output_formats> (B<OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS>) <UTF8 string>
+=item C<ml-kem.output_formats> (B<OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS>) <UTF8 string>
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 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<seed-priv> is listed first and C<priv-only> second.
-
-=back
-
-Use EVP_PKEY_CTX_set_params() after calling EVP_PKEY_keygen_init().
-
-=head2 Common parameters
-
-In addition to the common parameters that all keytypes should support (see
-L<provider-keymgmt(7)/Common Information Parameters>), B<ML-KEM> keys
-keys support the following.
-
-=over 4
-
-=item "pub" (B<OSSL_PKEY_PARAM_PUB_KEY>) <octet string>
-
-The public key value.
-
-This parameter is used when importing or exporting the public key value with
-the EVP_PKEY_fromdata() and EVP_PKEY_todata() functions. The same underlying
-FIPS 203 (Algorithm 16: B<ML-KEM.KeyGen_internal>) B<ek> public key format is
-used for import, export, get and set operations.
-It is otherwise only gettable.
-
-=item "priv" (B<OSSL_PKEY_PARAM_PRIV_KEY>) <octet string>
-
-The private key value.
-
-This parameter is used when importing or exporting the private key value with
-the EVP_PKEY_fromdata() and EVP_PKEY_todata() functions.
-The key length and content must be that of the FIPS 203 (Algorithm 16:
-B<ML-KEM.KeyGen_internal>) B<dk> private key for the given ML-KEM variant.
-It is otherwise only gettable.
-
-=item "encoded-pub-key" (B<OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY>) <octet string>
-
-Used for getting and setting the encoding of a public key.
-The key format is that of B<ek> in FIPS 203, Algorithm 16:
-B<ML-KEM.KeyGen_internal>.
-Updates of the public and private key components are only allowed on keys that
-are empty.
-Once a public or private key component is set, no further changes are allowed.
-
-This parameter is gettable and settable.
+The default order is equivalent to C<seed-priv> and C<priv-only> second, with
+both seed and key output when the seed is available, and otherwise just the
+key is output.
+If C<seed-only> is listed first, then the seed will be output without the key
+when available, otherwise the output will have just the key.
+If C<priv-only> is listed first, then just the key is output regardless of
+whether the seed is present.
+The legacy C<oqs> formats can also be output, by listing either of those first.
=back
Equivalent calls are available for B<ML-KEM-512> and B<ML-KEM-1024>.
-An B<ML-KEM> private key in seed format can be converted to a key in expanded
-FIPS 203 B<dk> format by running:
+An B<ML-KEM> private key in seed format can be converted to a key in the FIPS
+203 B<dk> format by running:
- $ openssl pkey -provparam ml-kem.retain_seed=no -in seed.pem -out long.pem
+ $ openssl pkey -provparam ml-kem.retain_seed=no \
+ -in seed-only.pem -out priv-only.pem
-To generate an, e.g., B<ML-KEM-768> key, in expanded format, you can run:
+To generate an, e.g., B<ML-KEM-768> key, in FIPS 203 B<dk> format, you can run:
$ openssl genpkey -provparam ml-kem.retain_seed=no \
-algorithm ml-kem-768 -out long.pem
+If you have B<PKCS#8> file with both a seed and a key, and prefer to import the
+companion key rather than the seed, you can run:
+
+ $ openssl pkey -provparam ml-kem.prefer_seed=no \
+ -in seed-priv.pem -out priv-only.pem
+
In the B<openssl.cnf> file, this looks like:
- openssl_conf = openssl_init
+ openssl_conf = openssl_init
- [openssl_init]
- providers = providers_sect
+ [openssl_init]
+ providers = providers_sect
- # Can be referenced in one or more provider sections
- [ml_kem_sect]
- 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
+ # Can be referenced in one or more provider sections
+ [ml_kem_sect]
+ prefer_seed = yes
+ 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
- base = base_sect
+ [providers_sect]
+ default = default_sect
+ # Or perhaps just: base = default_sect
+ base = base_sect
- [default_sect]
- ml-kem = ml_kem_sect
+ [default_sect]
+ ml-kem = ml_kem_sect
- [base_sect]
- ml-kem = ml_kem_sect
+ [base_sect]
+ ml-kem = ml_kem_sect
=head1 SEE ALSO
* -----------------------------
*/
- /*-
- * 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;
struct ossl_ml_kem_scalar_st *s; /* Private key secret vector */
uint8_t *z; /* Private key FO failure secret */
uint8_t *d; /* Private key seed */
+ int prefer_seed; /* Given seed and key use seed? */
int retain_seed; /* Retain the seed after keygen? */
/*
* |z| and |d| components in that order.
*/
uint8_t seedbuf[64]; /* |rho| + |pkhash| / |z| + |d| */
+ uint8_t *encoded_dk; /* Unparsed P8 private key */
} ML_KEM_KEY;
/* The public key is always present, when the private is */
-# define ossl_ml_kem_key_vinfo(key) ((key)->vinfo)
-# define ossl_ml_kem_have_pubkey(key) ((key)->t != NULL)
-# define ossl_ml_kem_have_prvkey(key) ((key)->s != NULL)
-# define ossl_ml_kem_have_seed(key) ((key)->d != NULL)
+# define ossl_ml_kem_key_vinfo(key) ((key)->vinfo)
+# define ossl_ml_kem_have_pubkey(key) ((key)->t != NULL)
+# define ossl_ml_kem_have_prvkey(key) ((key)->s != NULL)
+# define ossl_ml_kem_have_seed(key) ((key)->d != NULL)
+# define ossl_ml_kem_have_dkenc(key) ((key)->encoded_dk != NULL)
+# define ossl_ml_kem_decoded_key(key) ((key)->encoded_dk != NULL \
+ || ((key)->s == NULL && (key)->d != NULL))
/*
* ----- ML-KEM key lifecycle
* Allocate a "bare" key for given ML-KEM variant. Initially without any public
* or private key material.
*/
-ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx,
- const char *properties,
- int retain_seed, int evp_type);
+ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, const char *properties,
+ int evp_type);
/* Deallocate the key */
void ossl_ml_kem_key_free(ML_KEM_KEY *key);
/*
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);
/*
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);
IF[{- !$disabled{'ml-dsa'} -}]
SOURCE[$DECODER_GOAL]=ml_dsa_codecs.c
ENDIF
+
+IF[{- !$disabled{'ml-kem'} -}]
+ SOURCE[$DECODER_GOAL]=ml_kem_codecs.c
+ENDIF
#include "endecoder_local.h"
#include "internal/nelem.h"
#include "ml_dsa_codecs.h"
+#include "ml_kem_codecs.h"
struct der2key_ctx_st; /* Forward declaration */
typedef int check_key_fn(void *, struct der2key_ctx_st *ctx);
ml_kem_d2i_PKCS8(const uint8_t **der, long der_len, struct der2key_ctx_st *ctx)
{
ML_KEM_KEY *key;
- OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(ctx->provctx);
- int retain_seed = ossl_prov_ctx_get_bool_param(
- ctx->provctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1);
- const char *formats = ossl_prov_ctx_get_param(
- ctx->provctx, OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS, NULL);
-
- key = ossl_ml_kem_d2i_PKCS8(*der, der_len, retain_seed, formats,
- ctx->desc->evp_type, libctx, ctx->propq);
+
+ key = ossl_ml_kem_d2i_PKCS8(*der, der_len, ctx->desc->evp_type,
+ ctx->provctx, ctx->propq);
if (key != NULL)
*der += der_len;
return key;
ML_KEM_KEY *key;
key = ossl_ml_kem_d2i_PUBKEY(*der, der_len, ctx->desc->evp_type,
- PROV_LIBCTX_OF(ctx->provctx), ctx->propq);
+ ctx->provctx, ctx->propq);
if (key != NULL)
*der += der_len;
return key;
#include "prov/der_rsa.h"
#include "endecoder_local.h"
#include "ml_dsa_codecs.h"
+#include "ml_kem_codecs.h"
#if defined(OPENSSL_NO_DH) && defined(OPENSSL_NO_DSA) && defined(OPENSSL_NO_EC)
# define OPENSSL_NO_KEYPARAMS
void *vctx)
{
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);
- return ossl_ml_kem_i2d_prvkey(vkey, pder, formats);
+ return ossl_ml_kem_i2d_prvkey(vkey, pder, ctx->provctx);
}
# define ml_kem_epki_priv_to_der ml_kem_pki_priv_to_der
#include "internal/encoder.h"
#include "endecoder_local.h"
#include "ml_dsa_codecs.h"
+#include "ml_kem_codecs.h"
DEFINE_SPECIAL_STACK_OF_CONST(BIGNUM_const, BIGNUM)
--- /dev/null
+/*
+ * Copyright 2025 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/byteorder.h>
+#include <openssl/proverr.h>
+#include <openssl/x509.h>
+#include <openssl/core_names.h>
+#include "internal/encoder.h"
+#include "ml_kem_codecs.h"
+
+/*-
+ * Tables describing supported ASN.1 input/output formats.
+ * For each parameter set we support a few PKCS#8 input formats, three
+ * corresponding to the "either or both" variants of:
+ *
+ * ML-KEM-PrivateKey ::= SEQUENCE {
+ * seed OCTET STRING SIZE (64) OPTIONAL,
+ * expandedKey [1] IMPLICIT OCTET STRING SIZE (1632 | 2400 | 3172) OPTIONAL }
+ * (WITH COMPONENTS {..., seed PRESENT } |
+ * WITH COMPONENTS {..., expandedKey PRESENT })
+ *
+ * and two more for historical OQS encodings.
+ *
+ * - 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.
+ *
+ * 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
+ * ML-KEM parameter sets.
+ */
+#define NUM_PKCS8_FORMATS 5
+
+/*-
+ * 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[NUM_PKCS8_FORMATS] = {
+ { "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[NUM_PKCS8_FORMATS] = {
+ { "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[NUM_PKCS8_FORMATS] = {
+ { "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, },
+};
+
+/* Indices of slots in the `cinfo_map` table below */
+#define ML_KEM_512_CINFO 0
+#define ML_KEM_768_CINFO 1
+#define ML_KEM_1024_CINFO 2
+
+/*
+ * Per-variant fixed parameters
+ */
+static const ML_KEM_CINFO cinfo_map[3] = {
+ { &ml_kem_512_spki_info, ml_kem_512_pkcs8_info },
+ { &ml_kem_768_spki_info, ml_kem_768_pkcs8_info },
+ { &ml_kem_1024_spki_info, ml_kem_1024_pkcs8_info }
+};
+
+/* Retrieve the parameters of one of the ML-KEM variants */
+static const ML_KEM_CINFO *ml_kem_get_cinfo(int evp_type)
+{
+ switch (evp_type) {
+ case EVP_PKEY_ML_KEM_512:
+ return &cinfo_map[ML_KEM_512_CINFO];
+ case EVP_PKEY_ML_KEM_768:
+ return &cinfo_map[ML_KEM_768_CINFO];
+ case EVP_PKEY_ML_KEM_1024:
+ return &cinfo_map[ML_KEM_1024_CINFO];
+ }
+ return NULL;
+}
+
+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 char *algorithm_name,
+ const ML_KEM_PKCS8_INFO *pkcs8_info,
+ 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 = &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",
+ 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,
+ PROV_CTX *provctx, const char *propq)
+{
+ OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
+ const ML_KEM_VINFO *v;
+ const ML_KEM_CINFO *c;
+ const ML_KEM_SPKI_INFO *vspki;
+ ML_KEM_KEY *ret;
+
+ if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL
+ || (c = ml_kem_get_cinfo(evp_type)) == NULL)
+ return NULL;
+ vspki = c->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, 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 evp_type, PROV_CTX *provctx,
+ const char *propq)
+{
+ OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
+ const ML_KEM_VINFO *v;
+ const ML_KEM_CINFO *c;
+ 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;
+ const char *formats;
+ int len, ptype;
+ uint32_t magic;
+ uint16_t seed_magic;
+
+ /* Which ML-KEM variant? */
+ if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL
+ || (c = ml_kem_get_cinfo(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. */
+ formats = ossl_prov_ctx_get_param(
+ provctx, OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS, NULL);
+ vp8_slot = vp8_alloc = vp8_order(v->algorithm_name, c->pkcs8_info,
+ "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, that also 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 + sizeof(uint16_t) == buf + vp8->seed_offset) {
+ 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 + sizeof(uint32_t) == buf + vp8->priv_offset) {
+ 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;
+
+ /*
+ * Collect the seed and/or key into a "decoded" private key object,
+ * to be turned into a real key on provider "load" or "import".
+ */
+ if ((key = ossl_ml_kem_key_new(libctx, propq, evp_type)) == NULL)
+ goto end;
+ key->retain_seed = ossl_prov_ctx_get_bool_param(
+ provctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1);
+ key->prefer_seed = ossl_prov_ctx_get_bool_param(
+ provctx, OSSL_PKEY_PARAM_ML_KEM_PREFER_SEED, 1);
+ if (vp8->seed_offset > 0) {
+ if (!ossl_ml_kem_set_seed(buf + vp8->seed_offset,
+ ML_KEM_SEED_BYTES, key)) {
+ ERR_raise_data(ERR_LIB_OSSL_DECODER, ERR_R_INTERNAL_ERROR,
+ "error storing %s private key seed",
+ v->algorithm_name);
+ goto end;
+ }
+ }
+ if (vp8->priv_offset > 0) {
+ if ((key->encoded_dk = OPENSSL_malloc(v->prvkey_bytes)) == NULL) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY,
+ "error parsing %s private key",
+ v->algorithm_name);
+ goto end;
+ }
+ memcpy(key->encoded_dk, buf + vp8->priv_offset, v->prvkey_bytes);
+ }
+ /* Any OQS public key content is ignored */
+ ret = key;
+
+ 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,
+ PROV_CTX *provctx)
+{
+ const ML_KEM_VINFO *v = key->vinfo;
+ const ML_KEM_CINFO *c;
+ ML_KEM_PKCS8_PREF *vp8_alloc, *vp8_slot;
+ const ML_KEM_PKCS8_INFO *vp8;
+ uint8_t *buf = NULL, *pos;
+ const char *formats;
+ int len = ML_KEM_SEED_BYTES;
+ int ret = 0;
+
+ /* Not ours to handle */
+ if ((c = ml_kem_get_cinfo(v->evp_type)) == NULL)
+ return 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;
+ }
+
+ formats = ossl_prov_ctx_get_param(
+ provctx, OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS, NULL);
+ vp8_slot = vp8_alloc = vp8_order(v->algorithm_name, c->pkcs8_info,
+ "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
+ * us to write two bytes now.
+ */
+ if (pos + sizeof(uint16_t) == buf + vp8->seed_offset)
+ 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 + sizeof(uint32_t) == buf + vp8->priv_offset)
+ 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_OSSL_ENCODER, 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;
+}
--- /dev/null
+/*
+ * Copyright 2025 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
+ */
+
+#ifndef PROV_ML_KEM_CODECS_H
+# define PROV_ML_KEM_CODECS_H
+# pragma once
+
+# include <openssl/e_os2.h>
+# include "crypto/ml_kem.h"
+# include "prov/provider_ctx.h"
+
+ /*-
+ * 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 parameter set we support a few PKCS#8 input formats, three
+ * corresponding to the "either or both" variants of:
+ *
+ * ML-KEM-PrivateKey ::= SEQUENCE {
+ * seed OCTET STRING SIZE (64) OPTIONAL,
+ * expandedKey [1] IMPLICIT OCTET STRING SIZE (1632 | 2400 | 3172) OPTIONAL }
+ * (WITH COMPONENTS {..., seed PRESENT } |
+ * WITH COMPONENTS {..., expandedKey PRESENT })
+ *
+ * and two more for historical OQS encodings.
+ *
+ * - 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 ML_KEM_SPKI_INFO *spki_info;
+ const ML_KEM_PKCS8_INFO *pkcs8_info;
+} ML_KEM_CINFO;
+
+typedef struct {
+ const ML_KEM_PKCS8_INFO *vp8_entry;
+ int vp8_pref;
+} ML_KEM_PKCS8_PREF;
+
+__owur
+ML_KEM_KEY *ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen,
+ int evp_type, PROV_CTX *provctx,
+ const char *propq);
+__owur
+ML_KEM_KEY *ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
+ int evp_type, PROV_CTX *provctx,
+ const char *propq);
+__owur
+int ossl_ml_kem_key_to_text(BIO *out, const ML_KEM_KEY *key, int selection);
+__owur
+__owur
+int ossl_ml_kem_i2d_pubkey(const ML_KEM_KEY *key, unsigned char **out);
+__owur
+__owur
+int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, unsigned char **out,
+ PROV_CTX *provctx);
+
+#endif /* PROV_ML_KEM_CODECS_H */
| OSSL_KEYMGMT_SELECT_PRIVATE_KEY;
typedef struct ml_kem_gen_ctx_st {
- OSSL_LIB_CTX *libctx;
+ PROV_CTX *provctx;
char *propq;
int selection;
- int retain_seed;
int evp_type;
uint8_t seedbuf[ML_KEM_SEED_BYTES];
uint8_t *seed;
}
#endif /* FIPS_MODULE */
-static void *ml_kem_new(PROV_CTX *ctx, int evp_type)
+static void *ml_kem_new(PROV_CTX *ctx, const char *propq, int evp_type)
{
- OSSL_LIB_CTX *libctx = NULL;
- int retain_seed = 1;
+ ML_KEM_KEY *key;
if (!ossl_prov_is_running())
return NULL;
- if (ctx != NULL) {
- libctx = PROV_LIBCTX_OF(ctx);
- retain_seed = ossl_prov_ctx_get_bool_param(
+ if ((key = ossl_ml_kem_key_new(PROV_LIBCTX_OF(ctx), propq, evp_type)) != NULL) {
+ key->retain_seed = ossl_prov_ctx_get_bool_param(
ctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1);
+ key->prefer_seed = ossl_prov_ctx_get_bool_param(
+ ctx, OSSL_PKEY_PARAM_ML_KEM_PREFER_SEED, 1);
}
- return ossl_ml_kem_key_new(libctx, NULL, retain_seed, evp_type);
+ return key;
}
static int ml_kem_has(const void *vkey, int selection)
if (!ossl_ml_kem_have_pubkey(key)) {
/* Fail when no key material can be returned */
if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) == 0
- || !ossl_ml_kem_have_seed(key)) {
+ || !ossl_ml_kem_decoded_key(key)) {
ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
return 0;
}
if ((prvenc = OPENSSL_secure_zalloc(prvlen)) == NULL
|| !ossl_ml_kem_encode_private_key(prvenc, prvlen, key))
goto err;
+ } else if (ossl_ml_kem_have_dkenc(key)) {
+ prvlen = v->prvkey_bytes;
+ if ((prvenc = OPENSSL_secure_zalloc(prvlen)) == NULL)
+ goto err;
+ memcpy(prvenc, key->encoded_dk, prvlen);
}
}
/* The (d, z) seed, when available and private keys are requested. */
if (seedenc != NULL
- && (!ossl_param_build_set_octet_string(
- tmpl, params, OSSL_PKEY_PARAM_ML_KEM_SEED, seedenc, seedlen)
- || (!key->retain_seed && !ossl_param_build_set_int(
- tmpl, params, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 0))))
+ && !ossl_param_build_set_octet_string(
+ tmpl, params, OSSL_PKEY_PARAM_ML_KEM_SEED, seedenc, seedlen))
goto err;
/* The private key in the FIPS 203 |dk| format, when requested. */
static const OSSL_PARAM *ml_kem_imexport_types(int selection)
{
static const OSSL_PARAM key_types[] = {
- OSSL_PARAM_int(OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, NULL),
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ML_KEM_SEED, NULL, 0),
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0),
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0),
return 0;
v = ossl_ml_kem_key_vinfo(key);
- p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED);
- if (p != NULL
- && !(OSSL_PARAM_get_int(p, &key->retain_seed)))
- return 0;
-
- /*
- * When a seed is provided, the private and public keys will be ignored,
- * after validating just their lengths. Comparing encodings or hashes
- * when applicable is possible, but not currently implemented.
- */
- p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ML_KEM_SEED);
- if (p != NULL
- && OSSL_PARAM_get_octet_string_ptr(p, &seedenc, &seedlen) != 1)
- return 0;
- if (seedlen == ML_KEM_SEED_BYTES) {
- ossl_ml_kem_set_seed((uint8_t *)seedenc, seedlen, key);
- } else if (seedlen != 0) {
- ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH);
- return 0;
- }
-
/*
* When a private key is provided, without a seed, any public key also
* provided will be ignored (apart from length), just as with the seed.
*/
if (include_private) {
+ /*
+ * When a seed is provided, the private and public keys may be ignored,
+ * after validating just their lengths. Comparing encodings or hashes
+ * when applicable is possible, but not currently implemented.
+ */
+ p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ML_KEM_SEED);
+ if (p != NULL
+ && OSSL_PARAM_get_octet_string_ptr(p, &seedenc, &seedlen) != 1)
+ return 0;
+ if (seedlen != 0 && seedlen != ML_KEM_SEED_BYTES) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH);
+ return 0;
+ }
p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY);
if (p != NULL
&& OSSL_PARAM_get_octet_string_ptr(p, &prvenc, &prvlen) != 1)
return 0;
}
- if (seedlen != 0)
- return ossl_ml_kem_genkey(NULL, 0, key);
+ if (seedlen != 0 && (prvlen == 0 || key->prefer_seed))
+ return ossl_ml_kem_set_seed(seedenc, seedlen, key)
+ && ossl_ml_kem_genkey(NULL, 0, key);
else if (prvlen != 0)
return ossl_ml_kem_parse_private_key(prvenc, prvlen, key);
return ossl_ml_kem_parse_public_key(pubenc, publen, key);
void *ml_kem_load(const void *reference, size_t reference_sz)
{
ML_KEM_KEY *key = NULL;
+ uint8_t *encoded_dk;
if (ossl_prov_is_running() && reference_sz == sizeof(key)) {
/* The contents of the reference is the address to our object */
key = *(ML_KEM_KEY **)reference;
+ encoded_dk = key->encoded_dk;
+ key->encoded_dk = NULL;
/* We grabbed, so we detach it */
*(ML_KEM_KEY **)reference = NULL;
- if (ossl_ml_kem_have_pubkey(key))
- return key;
/* Generate the key now, if it holds only a stashed seed. */
- if (ossl_ml_kem_have_seed(key)) {
- if (!ossl_ml_kem_genkey(NULL, 0, key)) {
- ossl_ml_kem_key_free(key);
- return NULL;
- }
+ if (ossl_ml_kem_have_seed(key) &&
+ (encoded_dk == NULL || key->prefer_seed)) {
+ if (!ossl_ml_kem_genkey(NULL, 0, key))
+ goto err;
+ } else if (encoded_dk != NULL) {
+ if (!ossl_ml_kem_parse_private_key(encoded_dk,
+ key->vinfo->prvkey_bytes, key))
+ goto err;
}
+ OPENSSL_free(encoded_dk);
return key;
}
+
+ err:
+ ossl_ml_kem_key_free(key);
return NULL;
}
#endif
return 0;
}
- p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED);
- if (p != NULL
- && !(OSSL_PARAM_get_int(p, &gctx->retain_seed)))
- return 0;
-
return 1;
}
gctx->selection = selection;
gctx->evp_type = evp_type;
- gctx->retain_seed = ossl_prov_ctx_get_bool_param(
- provctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1);
- if (provctx != NULL)
- gctx->libctx = PROV_LIBCTX_OF(provctx);
+ gctx->provctx = provctx;
if (ml_kem_gen_set_params(gctx, params))
return gctx;
|| (gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) ==
OSSL_KEYMGMT_SELECT_PUBLIC_KEY)
return NULL;
- key = ossl_ml_kem_key_new(gctx->libctx, gctx->propq,
- gctx->retain_seed, gctx->evp_type);
+ key = ml_kem_new(gctx->provctx, gctx->propq, gctx->evp_type);
if (key == NULL)
return NULL;
#define DECLARE_VARIANT(bits) \
static void *ml_kem_##bits##_new(void *provctx) \
{ \
- return ml_kem_new(provctx, EVP_PKEY_ML_KEM_##bits); \
+ return ml_kem_new(provctx, NULL, EVP_PKEY_ML_KEM_##bits); \
} \
static void *ml_kem_##bits##_gen_init(void *provctx, int selection, \
const OSSL_PARAM params[]) \
if (!TEST_true(EVP_RAND_CTX_set_params(privctx, params)))
return 0;
- public_key = ossl_ml_kem_key_new(NULL, NULL, 0, alg[i]);
- private_key = ossl_ml_kem_key_new(NULL, NULL, 0, alg[i]);
+ public_key = ossl_ml_kem_key_new(NULL, NULL, alg[i]);
+ private_key = ossl_ml_kem_key_new(NULL, NULL, alg[i]);
if (private_key == NULL || public_key == NULL
|| (v = ossl_ml_kem_key_vinfo(public_key)) == NULL)
goto done;
plan skip_all => "ML-KEM isn't supported in this build"
if disabled("ml-kem");
-plan tests => @algs * (16 + 10 * @formats);
+plan tests => @algs * (18 + 10 * @formats);
my $seed = join ("", map {sprintf "%02x", $_} (0..63));
my $ikme = join ("", map {sprintf "%02x", $_} (0..31));
sprintf("seedfull via cli vs. conf key match: %s", $alg));
}
+ # 2 tests
+ # Test decoder seed non-preference via the command-line.
+ my $privpref = sprintf("privpref-%s.dec.cli.pem", $alg);
+ ok(run(app(['openssl', 'pkey', '-provparam', 'ml-kem.prefer_seed=no',
+ '-in', data_file($formats{'seed-priv'}), '-out', $privpref])));
+ ok(!compare(data_file($formats{'priv-only'}), $privpref),
+ sprintf("seed non-preference via provparam key match: %s", $alg));
+
+ # 10(5 * 2) tests
# Check text encoding
while (my ($f, $k) = each %formats) {
my $txt = sprintf("prv-%s-%s.txt", $alg,
# ML-KEM parameters
'PKEY_PARAM_ML_KEM_SEED' => "seed",
+ 'PKEY_PARAM_ML_KEM_PREFER_SEED' => "ml-kem.prefer_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",