]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
ASN.1 ML-KEM private key format
authorViktor Dukhovni <openssl-users@dukhovni.org>
Sat, 18 Jan 2025 23:48:01 +0000 (10:48 +1100)
committerTomas Mraz <tomas@openssl.org>
Fri, 14 Feb 2025 09:50:58 +0000 (10:50 +0100)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26512)

crypto/err/openssl.txt
crypto/ml_kem/ml_kem.c
doc/man7/EVP_PKEY-ML-KEM.pod
include/crypto/ml_kem.h
include/openssl/core_names.h.in
include/openssl/proverr.h
providers/common/provider_err.c
providers/implementations/encode_decode/decode_der2key.c
providers/implementations/encode_decode/encode_key2any.c
providers/implementations/encode_decode/encode_key2text.c
util/perl/OpenSSL/paramnames.pm

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