]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Implement seed/key preference when decoding
authorViktor Dukhovni <openssl-users@dukhovni.org>
Mon, 27 Jan 2025 16:12:47 +0000 (03:12 +1100)
committerTomas Mraz <tomas@openssl.org>
Fri, 14 Feb 2025 09:50:58 +0000 (10:50 +0100)
- 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)

13 files changed:
crypto/ml_kem/ml_kem.c
doc/man7/EVP_PKEY-ML-KEM.pod
include/crypto/ml_kem.h
providers/implementations/encode_decode/build.info
providers/implementations/encode_decode/decode_der2key.c
providers/implementations/encode_decode/encode_key2any.c
providers/implementations/encode_decode/encode_key2text.c
providers/implementations/encode_decode/ml_kem_codecs.c [new file with mode: 0644]
providers/implementations/encode_decode/ml_kem_codecs.h [new file with mode: 0644]
providers/implementations/keymgmt/ml_kem_kmgmt.c
test/ml_kem_internal_test.c
test/recipes/15-test_ml_kem.t
util/perl/OpenSSL/paramnames.pm

index c7f88c2e39104b8964c63d87291f3a123aa20938..3b12e720e12404930c652cdb9cf6eecbf9986e1e 100644 (file)
@@ -7,17 +7,12 @@
  * 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>
@@ -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_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),
@@ -278,8 +196,6 @@ static const ML_KEM_VINFO vinfo_map[3] = {
     },
     {
         "ML-KEM-768",
-        &ml_kem_768_spki_info,
-        ml_kem_768_pkcs8_info,
         PRVKEY_BYTES(768),
         sizeof(struct prvkey_768_alloc),
         PUBKEY_BYTES(768),
@@ -296,8 +212,6 @@ static const ML_KEM_VINFO vinfo_map[3] = {
     },
     {
         "ML-KEM-1024",
-        &ml_kem_1024_spki_info,
-        ml_kem_1024_pkcs8_info,
         PRVKEY_BYTES(1024),
         sizeof(struct prvkey_1024_alloc),
         PUBKEY_BYTES(1024),
@@ -1637,8 +1551,6 @@ free_storage(ML_KEM_KEY *key)
     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);
 }
@@ -1669,7 +1581,7 @@ const ML_KEM_VINFO *ossl_ml_kem_get_vinfo(int evp_type)
  * 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;
@@ -1682,12 +1594,13 @@ ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, const char *properties,
 
     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
@@ -1705,20 +1618,19 @@ ML_KEM_KEY *ossl_ml_kem_key_dup(const ML_KEM_KEY *key, int selection)
     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;
@@ -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_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);
 }
@@ -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;
 
     /* 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;
 
@@ -1864,7 +1785,9 @@ int ossl_ml_kem_parse_private_key(const uint8_t *in, size_t len,
     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;
 
@@ -1892,7 +1815,9 @@ int ossl_ml_kem_genkey(uint8_t *pubenc, size_t publen, ML_KEM_KEY *key)
     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;
 
@@ -1901,7 +1826,8 @@ int ossl_ml_kem_genkey(uint8_t *pubenc, size_t publen, ML_KEM_KEY *key)
         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) {
@@ -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));
 }
-
-#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
index d787cb57701bc367cfc1db6fe55397c0dd5b237e..70576cfe8090d047e2ba8b23c94fc697ece4b2f6 100644 (file)
@@ -19,6 +19,8 @@ in OpenSSL's default and FIPS providers.
 =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
 
@@ -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
 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.
@@ -138,7 +183,7 @@ recognised on input.
 
 =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.
@@ -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.
 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
 
@@ -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>.
 
-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
 
index 63225810d53ae5e2d512c398cd7719c7378180e9..37ebee78cde2d5df1b2bb59df38215f013ff0dc7 100644 (file)
  * -----------------------------
  */
 
- /*-
-  * 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;
@@ -212,6 +171,7 @@ typedef struct ossl_ml_kem_key_st {
     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? */
 
     /*
@@ -221,13 +181,17 @@ typedef struct ossl_ml_kem_key_st {
      * |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
@@ -237,9 +201,8 @@ typedef struct ossl_ml_kem_key_st {
  * 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);
 /*
@@ -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 *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);
 
 /*
@@ -290,13 +242,9 @@ __owur
 int ossl_ml_kem_encode_public_key(uint8_t *out, size_t len,
                                   const ML_KEM_KEY *key);
 __owur
-int ossl_ml_kem_i2d_pubkey(const ML_KEM_KEY *key, unsigned char **out);
-__owur
 int ossl_ml_kem_encode_private_key(uint8_t *out, size_t len,
                                    const ML_KEM_KEY *key);
 __owur
-int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, unsigned char **out,
-                           const char *formats);
 int ossl_ml_kem_encode_seed(uint8_t *out, size_t len,
                             const ML_KEM_KEY *key);
 
index a70d4d1fba2a0cf6fc9c7d64bb580e1f02e393eb..a5a93cc072c50134b8616179123350e5ee752376 100644 (file)
@@ -22,3 +22,7 @@ DEPEND[encode_key2any.o]=../../common/include/prov/der_rsa.h
 IF[{- !$disabled{'ml-dsa'} -}]
   SOURCE[$DECODER_GOAL]=ml_dsa_codecs.c
 ENDIF
+
+IF[{- !$disabled{'ml-kem'} -}]
+  SOURCE[$DECODER_GOAL]=ml_kem_codecs.c
+ENDIF
index a46a5d69e3efb23de7467cb86d0b9f71eeec5398..da4a7d8abeb2fd86e48496814413480d30c7702b 100644 (file)
@@ -41,6 +41,7 @@
 #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);
@@ -566,14 +567,9 @@ static void *
 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;
@@ -586,7 +582,7 @@ ml_kem_d2i_PUBKEY(const uint8_t **der, long der_len,
     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;
index b2db2983701a72ebdad4bd58af024ecd2d039c03..bb7707a6dc71bf22b58a1c4fb604894ece538177 100644 (file)
@@ -39,6 +39,7 @@
 #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
@@ -891,10 +892,8 @@ static int ml_kem_pki_priv_to_der(const void *vkey, unsigned char **pder,
                                   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
index 5fd4fcb315e4595bc0438397f4d67e5ba54ab3c1..b3fd1c330dbc825966bb24f5ed0a1a7f1d80bb52 100644 (file)
@@ -31,6 +31,7 @@
 #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)
 
diff --git a/providers/implementations/encode_decode/ml_kem_codecs.c b/providers/implementations/encode_decode/ml_kem_codecs.c
new file mode 100644 (file)
index 0000000..4626618
--- /dev/null
@@ -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;
+}
diff --git a/providers/implementations/encode_decode/ml_kem_codecs.h b/providers/implementations/encode_decode/ml_kem_codecs.h
new file mode 100644 (file)
index 0000000..53f2588
--- /dev/null
@@ -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 */
index 9aa0289211ada95bae7bbac2bd93494b2d7fbb96..6c65183a4cabcd17086c12c2c49b34797bac0190 100644 (file)
@@ -52,10 +52,9 @@ static const int minimal_selection = OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS
     | 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;
@@ -127,19 +126,19 @@ err:
 }
 #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)
@@ -196,7 +195,7 @@ static int ml_kem_export(void *vkey, int selection, OSSL_CALLBACK *param_cb,
     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;
         }
@@ -224,6 +223,11 @@ static int ml_kem_export(void *vkey, int selection, OSSL_CALLBACK *param_cb,
             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);
         }
     }
 
@@ -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. */
     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. */
@@ -269,7 +271,6 @@ err:
 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),
@@ -295,32 +296,24 @@ static int ml_kem_key_fromdata(ML_KEM_KEY *key,
         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)
@@ -347,8 +340,9 @@ static int ml_kem_key_fromdata(ML_KEM_KEY *key,
         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);
@@ -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)
 {
     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
@@ -581,11 +583,6 @@ static int ml_kem_gen_set_params(void *vgctx, const OSSL_PARAM params[])
         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;
 }
 
@@ -605,10 +602,7 @@ static void *ml_kem_gen_init(void *provctx, int selection,
 
     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;
 
@@ -638,8 +632,7 @@ static void *ml_kem_gen(void *vgctx, OSSL_CALLBACK *osslcb, void *cbarg)
         || (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;
 
@@ -700,7 +693,7 @@ static void *ml_kem_dup(const void *vkey, int selection)
 #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[]) \
index e811237207cdcc049ee3f03df291b4c9627fd336..47e64121f5f5c9b0932757987ce28c2c3ac0f664 100644 (file)
@@ -137,8 +137,8 @@ static int sanity_test(void)
         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;
index 1b6c37ac2c0c40de40ca9918d51459895d609a09..30e4b168871ba00e314655e82532611a34aaf315 100644 (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"
     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));
 
@@ -139,6 +139,15 @@ foreach my $alg (@algs) {
             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,
index d80254319809a1011231fcb01a23a1bde0fa1dfe..58fc828ab3e8e91daad13a4b50f1ef80ad3b1422 100644 (file)
@@ -419,6 +419,7 @@ my %params = (
 
 # 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",