Implement seed/key preference when decoding

- Moved the codec code out of `ml_kem.c` into its own file in
  the provider tree.  Will be easier to share some code with
  ML-DSA, and possible to use PROV_CTX, to do config lookups
  directly in the functions doing the work.

- Update and fixes of the EVP_PKEY-ML-KEM(8) documentation, which
  had accumulated some stale/inaccurate material, and needed new
  text for the "prefer_seed" parameter.

- Test the "prefer_seed=no" behaviour.

Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
Reviewed-by: Paul Dale <ppzgs1@gmail.com>
(Merged from https://github.com/openssl/openssl/pull/26569)
This commit is contained in:
Viktor Dukhovni 2025-01-28 03:12:47 +11:00 committed by Tomas Mraz
parent cc5403f33a
commit 5b2d996f91
13 changed files with 873 additions and 731 deletions

View file

@ -7,17 +7,12 @@
* https://www.openssl.org/source/license.html * https://www.openssl.org/source/license.html
*/ */
#include <string.h>
#include <openssl/rand.h>
#include <openssl/byteorder.h> #include <openssl/byteorder.h>
#include <openssl/proverr.h> #include <openssl/rand.h>
#include <openssl/x509.h>
#include "crypto/ctype.h"
#include "crypto/ml_kem.h" #include "crypto/ml_kem.h"
#include "internal/common.h" #include "internal/common.h"
#include "internal/constant_time.h" #include "internal/constant_time.h"
#include "internal/sha3.h" #include "internal/sha3.h"
#include "internal/encoder.h"
#if defined(OPENSSL_CONSTANT_TIME_VALIDATION) #if defined(OPENSSL_CONSTANT_TIME_VALIDATION)
#include <valgrind/memcheck.h> #include <valgrind/memcheck.h>
@ -179,89 +174,12 @@ static void scalar_encode(uint8_t *out, const scalar *s, int bits);
#define ML_KEM_768_VINFO 1 #define ML_KEM_768_VINFO 1
#define ML_KEM_1024_VINFO 2 #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 * Per-variant fixed parameters
*/ */
static const ML_KEM_VINFO vinfo_map[3] = { static const ML_KEM_VINFO vinfo_map[3] = {
{ {
"ML-KEM-512", "ML-KEM-512",
&ml_kem_512_spki_info,
ml_kem_512_pkcs8_info,
PRVKEY_BYTES(512), PRVKEY_BYTES(512),
sizeof(struct prvkey_512_alloc), sizeof(struct prvkey_512_alloc),
PUBKEY_BYTES(512), PUBKEY_BYTES(512),
@ -278,8 +196,6 @@ static const ML_KEM_VINFO vinfo_map[3] = {
}, },
{ {
"ML-KEM-768", "ML-KEM-768",
&ml_kem_768_spki_info,
ml_kem_768_pkcs8_info,
PRVKEY_BYTES(768), PRVKEY_BYTES(768),
sizeof(struct prvkey_768_alloc), sizeof(struct prvkey_768_alloc),
PUBKEY_BYTES(768), PUBKEY_BYTES(768),
@ -296,8 +212,6 @@ static const ML_KEM_VINFO vinfo_map[3] = {
}, },
{ {
"ML-KEM-1024", "ML-KEM-1024",
&ml_kem_1024_spki_info,
ml_kem_1024_pkcs8_info,
PRVKEY_BYTES(1024), PRVKEY_BYTES(1024),
sizeof(struct prvkey_1024_alloc), sizeof(struct prvkey_1024_alloc),
PUBKEY_BYTES(1024), PUBKEY_BYTES(1024),
@ -1637,8 +1551,6 @@ free_storage(ML_KEM_KEY *key)
if (ossl_ml_kem_have_prvkey(key)) if (ossl_ml_kem_have_prvkey(key))
OPENSSL_cleanse(key->s, OPENSSL_cleanse(key->s,
key->vinfo->vector_bytes + 2 * ML_KEM_RANDOM_BYTES); 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); OPENSSL_free(key->t);
key->d = key->z = (uint8_t *)(key->s = key->m = key->t = NULL); key->d = key->z = (uint8_t *)(key->s = key->m = key->t = NULL);
} }
@ -1669,7 +1581,7 @@ const ML_KEM_VINFO *ossl_ml_kem_get_vinfo(int evp_type)
* once the key is generated. * once the key is generated.
*/ */
ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, const char *properties, 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); const ML_KEM_VINFO *vinfo = ossl_ml_kem_get_vinfo(evp_type);
ML_KEM_KEY *key; ML_KEM_KEY *key;
@ -1682,12 +1594,13 @@ ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, const char *properties,
key->vinfo = vinfo; key->vinfo = vinfo;
key->libctx = libctx; 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->shake128_md = EVP_MD_fetch(libctx, "SHAKE128", properties);
key->shake256_md = EVP_MD_fetch(libctx, "SHAKE256", properties); key->shake256_md = EVP_MD_fetch(libctx, "SHAKE256", properties);
key->sha3_256_md = EVP_MD_fetch(libctx, "SHA3-256", properties); key->sha3_256_md = EVP_MD_fetch(libctx, "SHA3-256", properties);
key->sha3_512_md = EVP_MD_fetch(libctx, "SHA3-512", 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; key->s = key->m = key->t = NULL;
if (key->shake128_md != NULL if (key->shake128_md != NULL
@ -1705,20 +1618,19 @@ ML_KEM_KEY *ossl_ml_kem_key_dup(const ML_KEM_KEY *key, int selection)
int ok = 0; int ok = 0;
ML_KEM_KEY *ret; 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 if (key == NULL
|| (ret = OPENSSL_memdup(key, sizeof(*key))) == NULL) || (ret = OPENSSL_memdup(key, sizeof(*key))) == NULL)
return NULL; return NULL;
ret->d = ret->z = ret->rho = ret->pkhash = NULL; ret->d = ret->z = ret->rho = ret->pkhash = NULL;
ret->s = ret->m = ret->t = 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 */ /* Clear selection bits we can't fulfill */
if (!ossl_ml_kem_have_pubkey(key)) if (!ossl_ml_kem_have_pubkey(key))
selection = 0; selection = 0;
@ -1765,6 +1677,13 @@ void ossl_ml_kem_key_free(ML_KEM_KEY *key)
EVP_MD_free(key->sha3_256_md); EVP_MD_free(key->sha3_256_md);
EVP_MD_free(key->sha3_512_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); free_storage(key);
OPENSSL_free(key); OPENSSL_free(key);
} }
@ -1838,7 +1757,9 @@ int ossl_ml_kem_parse_public_key(const uint8_t *in, size_t len, ML_KEM_KEY *key)
int ret = 0; int ret = 0;
/* Keys with key material are immutable */ /* 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; return 0;
vinfo = key->vinfo; vinfo = key->vinfo;
@ -1864,7 +1785,9 @@ int ossl_ml_kem_parse_private_key(const uint8_t *in, size_t len,
int ret = 0; int ret = 0;
/* Keys with key material are immutable */ /* 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; return 0;
vinfo = key->vinfo; vinfo = key->vinfo;
@ -1892,7 +1815,9 @@ int ossl_ml_kem_genkey(uint8_t *pubenc, size_t publen, ML_KEM_KEY *key)
const ML_KEM_VINFO *vinfo; const ML_KEM_VINFO *vinfo;
int ret = 0; 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; return 0;
vinfo = key->vinfo; vinfo = key->vinfo;
@ -1901,7 +1826,8 @@ int ossl_ml_kem_genkey(uint8_t *pubenc, size_t publen, ML_KEM_KEY *key)
return 0; return 0;
if (ossl_ml_kem_have_seed(key)) { 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; key->d = key->z = NULL;
} else if (RAND_priv_bytes_ex(key->libctx, seed, sizeof(seed), } else if (RAND_priv_bytes_ex(key->libctx, seed, sizeof(seed),
key->vinfo->secbits) <= 0) { key->vinfo->secbits) <= 0) {
@ -2087,406 +2013,3 @@ 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)); 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

View file

@ -19,6 +19,8 @@ in OpenSSL's default and FIPS providers.
=head2 Keygen Parameters =head2 Keygen Parameters
No mandatory parameters are required for generating a key pair. 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 =over 4
@ -33,48 +35,91 @@ Generated keys default to retaining the seed used.
The seed is also by default retained when keys are loaded from B<PKCS#8> files The seed is also by default retained when keys are loaded from B<PKCS#8> files
in the seed format. in the seed format.
When available, the seed parameter is also used during key export and import, 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, 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 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 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 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 See L<OSSL_PROVIDER_add_conf_parameter(3)> to learn how to set provider
configuration options programmatically. 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 =over 4
settable.
See L<provider-keymgmt(7)/Common Information Parameters> for further =item C<ml-kem.retain_seed> (B<OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED>) <UTF8 string>
information.
=item C<ml-kem.retain_seed> (B<OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED>) <int> 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.
This parameter is used only in key generation. =item C<ml-kem.prefer_seed> (B<OSSL_PKEY_PARAM_ML_KEM_PREFER_SEED>) <UTF8 string>
When keys are generated, by default the seed is retained and used as the
private key form on output when encoding. When decoding PKCS#8 objects that contain both a seed and the FIPS 203 C<dk>
When this parameter is set to a nonzero value, the seed is retained during key private key, the seed is by default used to regenerate the key, and the
generation, when set to 0, the seed is not retained. companion key is ignored.
When this parameter is simply omitted, and the C<ml-kem.retain_seed> provider When this configuration parameter is set to a string representing a false
configuration parameter is set to a false boolean value, the seed is not boolean value (see L<OSSL_PROVIDER_conf_get_bool(3)>), the seed is ignored
retained, otherwise it is retained (see also L<OSSL_PROVIDER_conf_get_bool(3)>). (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> =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. 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 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 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. Values specified on the command-line override any configuration file settings.
By default all the supported formats are enabled. By default all the supported formats are enabled.
@ -138,7 +183,7 @@ recognised on input.
=back =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. Ordered list of enabled private key output formats when writing PKCS#8 files.
List elements are separated by commas and/or spaces or tabs. List elements are separated by commas and/or spaces or tabs.
@ -152,50 +197,14 @@ The order in which elements are listed is important, the selected format will be
the first one that is possible to output. 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 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. 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. 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
=back key is output.
If C<seed-only> is listed first, then the seed will be output without the key
Use EVP_PKEY_CTX_set_params() after calling EVP_PKEY_keygen_init(). 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
=head2 Common parameters whether the seed is present.
The legacy C<oqs> formats can also be output, by listing either of those first.
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.
=back =back
@ -220,40 +229,49 @@ An B<ML-KEM-768> key can be generated like this:
Equivalent calls are available for B<ML-KEM-512> and B<ML-KEM-1024>. 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 An B<ML-KEM> private key in seed format can be converted to a key in the FIPS
FIPS 203 B<dk> format by running: 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 \ $ openssl genpkey -provparam ml-kem.retain_seed=no \
-algorithm ml-kem-768 -out long.pem -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: In the B<openssl.cnf> file, this looks like:
openssl_conf = openssl_init openssl_conf = openssl_init
[openssl_init] [openssl_init]
providers = providers_sect providers = providers_sect
# Can be referenced in one or more provider sections # Can be referenced in one or more provider sections
[ml_kem_sect] [ml_kem_sect]
retain_seed = yes prefer_seed = yes
# OQS legacy formats disabled retain_seed = yes
input_formats = seed-priv, seed-only, priv-only # OQS legacy formats disabled
# Output either the seed alone, or else the key alone input_formats = seed-priv, seed-only, priv-only
output_formats = seed-only, priv-only # Output either the seed alone, or else the key alone
output_formats = seed-only, priv-only
[providers_sect] [providers_sect]
default = default_sect default = default_sect
base = base_sect # Or perhaps just: base = default_sect
base = base_sect
[default_sect] [default_sect]
ml-kem = ml_kem_sect ml-kem = ml_kem_sect
[base_sect] [base_sect]
ml-kem = ml_kem_sect ml-kem = ml_kem_sect
=head1 SEE ALSO =head1 SEE ALSO

View file

@ -122,49 +122,8 @@
* ----------------------------- * -----------------------------
*/ */
/*-
* 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 { typedef struct {
const char *algorithm_name; const char *algorithm_name;
const ML_KEM_SPKI_INFO *spki_info;
const ML_KEM_PKCS8_INFO *pkcs8_info;
size_t prvkey_bytes; size_t prvkey_bytes;
size_t prvalloc; size_t prvalloc;
size_t pubkey_bytes; size_t pubkey_bytes;
@ -212,6 +171,7 @@ typedef struct ossl_ml_kem_key_st {
struct ossl_ml_kem_scalar_st *s; /* Private key secret vector */ struct ossl_ml_kem_scalar_st *s; /* Private key secret vector */
uint8_t *z; /* Private key FO failure secret */ uint8_t *z; /* Private key FO failure secret */
uint8_t *d; /* Private key seed */ uint8_t *d; /* Private key seed */
int prefer_seed; /* Given seed and key use seed? */
int retain_seed; /* Retain the seed after keygen? */ int retain_seed; /* Retain the seed after keygen? */
/* /*
@ -221,13 +181,17 @@ typedef struct ossl_ml_kem_key_st {
* |z| and |d| components in that order. * |z| and |d| components in that order.
*/ */
uint8_t seedbuf[64]; /* |rho| + |pkhash| / |z| + |d| */ uint8_t seedbuf[64]; /* |rho| + |pkhash| / |z| + |d| */
uint8_t *encoded_dk; /* Unparsed P8 private key */
} ML_KEM_KEY; } ML_KEM_KEY;
/* The public key is always present, when the private is */ /* The public key is always present, when the private is */
# define ossl_ml_kem_key_vinfo(key) ((key)->vinfo) # define ossl_ml_kem_key_vinfo(key) ((key)->vinfo)
# define ossl_ml_kem_have_pubkey(key) ((key)->t != NULL) # 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_prvkey(key) ((key)->s != NULL)
# define ossl_ml_kem_have_seed(key) ((key)->d != 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 * ----- ML-KEM key lifecycle
@ -237,9 +201,8 @@ typedef struct ossl_ml_kem_key_st {
* Allocate a "bare" key for given ML-KEM variant. Initially without any public * Allocate a "bare" key for given ML-KEM variant. Initially without any public
* or private key material. * or private key material.
*/ */
ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, const char *properties,
const char *properties, int evp_type);
int retain_seed, int evp_type);
/* Deallocate the key */ /* Deallocate the key */
void ossl_ml_kem_key_free(ML_KEM_KEY *key); void ossl_ml_kem_key_free(ML_KEM_KEY *key);
/* /*
@ -268,17 +231,6 @@ 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 *ossl_ml_kem_set_seed(const uint8_t *seed, size_t seedlen,
ML_KEM_KEY *key); ML_KEM_KEY *key);
__owur __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_genkey(uint8_t *pubenc, size_t publen, ML_KEM_KEY *key);
/* /*
@ -290,13 +242,9 @@ __owur
int ossl_ml_kem_encode_public_key(uint8_t *out, size_t len, int ossl_ml_kem_encode_public_key(uint8_t *out, size_t len,
const ML_KEM_KEY *key); const ML_KEM_KEY *key);
__owur __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, int ossl_ml_kem_encode_private_key(uint8_t *out, size_t len,
const ML_KEM_KEY *key); const ML_KEM_KEY *key);
__owur __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, int ossl_ml_kem_encode_seed(uint8_t *out, size_t len,
const ML_KEM_KEY *key); const ML_KEM_KEY *key);

View file

@ -22,3 +22,7 @@ DEPEND[encode_key2any.o]=../../common/include/prov/der_rsa.h
IF[{- !$disabled{'ml-dsa'} -}] IF[{- !$disabled{'ml-dsa'} -}]
SOURCE[$DECODER_GOAL]=ml_dsa_codecs.c SOURCE[$DECODER_GOAL]=ml_dsa_codecs.c
ENDIF ENDIF
IF[{- !$disabled{'ml-kem'} -}]
SOURCE[$DECODER_GOAL]=ml_kem_codecs.c
ENDIF

View file

@ -41,6 +41,7 @@
#include "endecoder_local.h" #include "endecoder_local.h"
#include "internal/nelem.h" #include "internal/nelem.h"
#include "ml_dsa_codecs.h" #include "ml_dsa_codecs.h"
#include "ml_kem_codecs.h"
struct der2key_ctx_st; /* Forward declaration */ struct der2key_ctx_st; /* Forward declaration */
typedef int check_key_fn(void *, struct der2key_ctx_st *ctx); typedef int check_key_fn(void *, struct der2key_ctx_st *ctx);
@ -566,14 +567,9 @@ static void *
ml_kem_d2i_PKCS8(const uint8_t **der, long der_len, 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; 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, key = ossl_ml_kem_d2i_PKCS8(*der, der_len, ctx->desc->evp_type,
ctx->desc->evp_type, libctx, ctx->propq); ctx->provctx, ctx->propq);
if (key != NULL) if (key != NULL)
*der += der_len; *der += der_len;
return key; return key;
@ -586,7 +582,7 @@ ml_kem_d2i_PUBKEY(const uint8_t **der, long der_len,
ML_KEM_KEY *key; ML_KEM_KEY *key;
key = ossl_ml_kem_d2i_PUBKEY(*der, der_len, ctx->desc->evp_type, 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) if (key != NULL)
*der += der_len; *der += der_len;
return key; return key;

View file

@ -39,6 +39,7 @@
#include "prov/der_rsa.h" #include "prov/der_rsa.h"
#include "endecoder_local.h" #include "endecoder_local.h"
#include "ml_dsa_codecs.h" #include "ml_dsa_codecs.h"
#include "ml_kem_codecs.h"
#if defined(OPENSSL_NO_DH) && defined(OPENSSL_NO_DSA) && defined(OPENSSL_NO_EC) #if defined(OPENSSL_NO_DH) && defined(OPENSSL_NO_DSA) && defined(OPENSSL_NO_EC)
# define OPENSSL_NO_KEYPARAMS # define OPENSSL_NO_KEYPARAMS
@ -891,10 +892,8 @@ static int ml_kem_pki_priv_to_der(const void *vkey, unsigned char **pder,
void *vctx) void *vctx)
{ {
KEY2ANY_CTX *ctx = 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 # define ml_kem_epki_priv_to_der ml_kem_pki_priv_to_der

View file

@ -31,6 +31,7 @@
#include "internal/encoder.h" #include "internal/encoder.h"
#include "endecoder_local.h" #include "endecoder_local.h"
#include "ml_dsa_codecs.h" #include "ml_dsa_codecs.h"
#include "ml_kem_codecs.h"
DEFINE_SPECIAL_STACK_OF_CONST(BIGNUM_const, BIGNUM) DEFINE_SPECIAL_STACK_OF_CONST(BIGNUM_const, BIGNUM)

View file

@ -0,0 +1,559 @@
/*
* 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;
}

View file

@ -0,0 +1,91 @@
/*
* 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 */

View file

@ -52,10 +52,9 @@ static const int minimal_selection = OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS
| OSSL_KEYMGMT_SELECT_PRIVATE_KEY; | OSSL_KEYMGMT_SELECT_PRIVATE_KEY;
typedef struct ml_kem_gen_ctx_st { typedef struct ml_kem_gen_ctx_st {
OSSL_LIB_CTX *libctx; PROV_CTX *provctx;
char *propq; char *propq;
int selection; int selection;
int retain_seed;
int evp_type; int evp_type;
uint8_t seedbuf[ML_KEM_SEED_BYTES]; uint8_t seedbuf[ML_KEM_SEED_BYTES];
uint8_t *seed; uint8_t *seed;
@ -127,19 +126,19 @@ err:
} }
#endif /* FIPS_MODULE */ #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; ML_KEM_KEY *key;
int retain_seed = 1;
if (!ossl_prov_is_running()) if (!ossl_prov_is_running())
return NULL; return NULL;
if (ctx != NULL) { if ((key = ossl_ml_kem_key_new(PROV_LIBCTX_OF(ctx), propq, evp_type)) != NULL) {
libctx = PROV_LIBCTX_OF(ctx); key->retain_seed = ossl_prov_ctx_get_bool_param(
retain_seed = ossl_prov_ctx_get_bool_param(
ctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1); 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) static int ml_kem_has(const void *vkey, int selection)
@ -196,7 +195,7 @@ static int ml_kem_export(void *vkey, int selection, OSSL_CALLBACK *param_cb,
if (!ossl_ml_kem_have_pubkey(key)) { if (!ossl_ml_kem_have_pubkey(key)) {
/* Fail when no key material can be returned */ /* Fail when no key material can be returned */
if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) == 0 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); ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
return 0; return 0;
} }
@ -224,6 +223,11 @@ static int ml_kem_export(void *vkey, int selection, OSSL_CALLBACK *param_cb,
if ((prvenc = OPENSSL_secure_zalloc(prvlen)) == NULL if ((prvenc = OPENSSL_secure_zalloc(prvlen)) == NULL
|| !ossl_ml_kem_encode_private_key(prvenc, prvlen, key)) || !ossl_ml_kem_encode_private_key(prvenc, prvlen, key))
goto err; 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);
} }
} }
@ -233,10 +237,8 @@ static int ml_kem_export(void *vkey, int selection, OSSL_CALLBACK *param_cb,
/* The (d, z) seed, when available and private keys are requested. */ /* The (d, z) seed, when available and private keys are requested. */
if (seedenc != NULL if (seedenc != NULL
&& (!ossl_param_build_set_octet_string( && !ossl_param_build_set_octet_string(
tmpl, params, OSSL_PKEY_PARAM_ML_KEM_SEED, seedenc, seedlen) 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))))
goto err; goto err;
/* The private key in the FIPS 203 |dk| format, when requested. */ /* The private key in the FIPS 203 |dk| format, when requested. */
@ -269,7 +271,6 @@ err:
static const OSSL_PARAM *ml_kem_imexport_types(int selection) static const OSSL_PARAM *ml_kem_imexport_types(int selection)
{ {
static const OSSL_PARAM key_types[] = { 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_ML_KEM_SEED, NULL, 0),
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0), OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0),
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0),
@ -295,32 +296,24 @@ static int ml_kem_key_fromdata(ML_KEM_KEY *key,
return 0; return 0;
v = ossl_ml_kem_key_vinfo(key); 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 * 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. * provided will be ignored (apart from length), just as with the seed.
*/ */
if (include_private) { 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); p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY);
if (p != NULL if (p != NULL
&& OSSL_PARAM_get_octet_string_ptr(p, &prvenc, &prvlen) != 1) && OSSL_PARAM_get_octet_string_ptr(p, &prvenc, &prvlen) != 1)
@ -347,8 +340,9 @@ static int ml_kem_key_fromdata(ML_KEM_KEY *key,
return 0; return 0;
} }
if (seedlen != 0) if (seedlen != 0 && (prvlen == 0 || key->prefer_seed))
return ossl_ml_kem_genkey(NULL, 0, key); return ossl_ml_kem_set_seed(seedenc, seedlen, key)
&& ossl_ml_kem_genkey(NULL, 0, key);
else if (prvlen != 0) else if (prvlen != 0)
return ossl_ml_kem_parse_private_key(prvenc, prvlen, key); return ossl_ml_kem_parse_private_key(prvenc, prvlen, key);
return ossl_ml_kem_parse_public_key(pubenc, publen, key); return ossl_ml_kem_parse_public_key(pubenc, publen, key);
@ -401,23 +395,31 @@ static const OSSL_PARAM *ml_kem_gettable_params(void *provctx)
void *ml_kem_load(const void *reference, size_t reference_sz) void *ml_kem_load(const void *reference, size_t reference_sz)
{ {
ML_KEM_KEY *key = NULL; ML_KEM_KEY *key = NULL;
uint8_t *encoded_dk;
if (ossl_prov_is_running() && reference_sz == sizeof(key)) { if (ossl_prov_is_running() && reference_sz == sizeof(key)) {
/* The contents of the reference is the address to our object */ /* The contents of the reference is the address to our object */
key = *(ML_KEM_KEY **)reference; key = *(ML_KEM_KEY **)reference;
encoded_dk = key->encoded_dk;
key->encoded_dk = NULL;
/* We grabbed, so we detach it */ /* We grabbed, so we detach it */
*(ML_KEM_KEY **)reference = NULL; *(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. */ /* Generate the key now, if it holds only a stashed seed. */
if (ossl_ml_kem_have_seed(key)) { if (ossl_ml_kem_have_seed(key) &&
if (!ossl_ml_kem_genkey(NULL, 0, key)) { (encoded_dk == NULL || key->prefer_seed)) {
ossl_ml_kem_key_free(key); if (!ossl_ml_kem_genkey(NULL, 0, key))
return NULL; 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; return key;
} }
err:
ossl_ml_kem_key_free(key);
return NULL; return NULL;
} }
#endif #endif
@ -581,11 +583,6 @@ static int ml_kem_gen_set_params(void *vgctx, const OSSL_PARAM params[])
return 0; 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; return 1;
} }
@ -605,10 +602,7 @@ static void *ml_kem_gen_init(void *provctx, int selection,
gctx->selection = selection; gctx->selection = selection;
gctx->evp_type = evp_type; gctx->evp_type = evp_type;
gctx->retain_seed = ossl_prov_ctx_get_bool_param( gctx->provctx = provctx;
provctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1);
if (provctx != NULL)
gctx->libctx = PROV_LIBCTX_OF(provctx);
if (ml_kem_gen_set_params(gctx, params)) if (ml_kem_gen_set_params(gctx, params))
return gctx; return gctx;
@ -638,8 +632,7 @@ static void *ml_kem_gen(void *vgctx, OSSL_CALLBACK *osslcb, void *cbarg)
|| (gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == || (gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) ==
OSSL_KEYMGMT_SELECT_PUBLIC_KEY) OSSL_KEYMGMT_SELECT_PUBLIC_KEY)
return NULL; return NULL;
key = ossl_ml_kem_key_new(gctx->libctx, gctx->propq, key = ml_kem_new(gctx->provctx, gctx->propq, gctx->evp_type);
gctx->retain_seed, gctx->evp_type);
if (key == NULL) if (key == NULL)
return NULL; return NULL;
@ -700,7 +693,7 @@ static void *ml_kem_dup(const void *vkey, int selection)
#define DECLARE_VARIANT(bits) \ #define DECLARE_VARIANT(bits) \
static void *ml_kem_##bits##_new(void *provctx) \ 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, \ static void *ml_kem_##bits##_gen_init(void *provctx, int selection, \
const OSSL_PARAM params[]) \ const OSSL_PARAM params[]) \

View file

@ -137,8 +137,8 @@ static int sanity_test(void)
if (!TEST_true(EVP_RAND_CTX_set_params(privctx, params))) if (!TEST_true(EVP_RAND_CTX_set_params(privctx, params)))
return 0; return 0;
public_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, 0, alg[i]); private_key = ossl_ml_kem_key_new(NULL, NULL, alg[i]);
if (private_key == NULL || public_key == NULL if (private_key == NULL || public_key == NULL
|| (v = ossl_ml_kem_key_vinfo(public_key)) == NULL) || (v = ossl_ml_kem_key_vinfo(public_key)) == NULL)
goto done; goto done;

View file

@ -25,7 +25,7 @@ my @formats = qw(seed-priv priv-only seed-only priv-oqs pair-oqs);
plan skip_all => "ML-KEM isn't supported in this build" plan skip_all => "ML-KEM isn't supported in this build"
if disabled("ml-kem"); 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 $seed = join ("", map {sprintf "%02x", $_} (0..63));
my $ikme = join ("", map {sprintf "%02x", $_} (0..31)); my $ikme = join ("", map {sprintf "%02x", $_} (0..31));
@ -139,6 +139,15 @@ foreach my $alg (@algs) {
sprintf("seedfull via cli vs. conf key match: %s", $alg)); 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 # Check text encoding
while (my ($f, $k) = each %formats) { while (my ($f, $k) = each %formats) {
my $txt = sprintf("prv-%s-%s.txt", $alg, my $txt = sprintf("prv-%s-%s.txt", $alg,

View file

@ -419,6 +419,7 @@ my %params = (
# ML-KEM parameters # ML-KEM parameters
'PKEY_PARAM_ML_KEM_SEED' => "seed", '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_RETAIN_SEED' => "ml-kem.retain_seed",
'PKEY_PARAM_ML_KEM_INPUT_FORMATS' => "ml-kem.input_formats", 'PKEY_PARAM_ML_KEM_INPUT_FORMATS' => "ml-kem.input_formats",
'PKEY_PARAM_ML_KEM_OUTPUT_FORMATS' => "ml-kem.output_formats", 'PKEY_PARAM_ML_KEM_OUTPUT_FORMATS' => "ml-kem.output_formats",