Implement seed/key preference when decoding
- Moved the codec code out of `ml_kem.c` into its own file in the provider tree. Will be easier to share some code with ML-DSA, and possible to use PROV_CTX, to do config lookups directly in the functions doing the work. - Update and fixes of the EVP_PKEY-ML-KEM(8) documentation, which had accumulated some stale/inaccurate material, and needed new text for the "prefer_seed" parameter. - Test the "prefer_seed=no" behaviour. Reviewed-by: Shane Lontis <shane.lontis@oracle.com> Reviewed-by: Paul Dale <ppzgs1@gmail.com> (Merged from https://github.com/openssl/openssl/pull/26569)
This commit is contained in:
parent
cc5403f33a
commit
5b2d996f91
13 changed files with 873 additions and 731 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
See L<provider-keymgmt(7)/Common Information Parameters> for further
|
||||
information.
|
||||
=item C<ml-kem.retain_seed> (B<OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED>) <UTF8 string>
|
||||
|
||||
=item C<ml-kem.retain_seed> (B<OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED>) <int>
|
||||
When set to a string representing a false boolean value (see
|
||||
L<OSSL_PROVIDER_conf_get_bool(3)>), the seed will not be retained after key
|
||||
generation or key import from a seed value.
|
||||
If the resulting key is then written to a PKCS#8 object, it will contain
|
||||
only the FIPS 203 C<dk> key.
|
||||
|
||||
This parameter is used only in key generation.
|
||||
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)>).
|
||||
=item C<ml-kem.prefer_seed> (B<OSSL_PKEY_PARAM_ML_KEM_PREFER_SEED>) <UTF8 string>
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -122,49 +122,8 @@
|
|||
* -----------------------------
|
||||
*/
|
||||
|
||||
/*-
|
||||
* The DER ASN.1 encoding of ML-KEM (and ML-DSA) public keys prepends 22 bytes
|
||||
* to the encoded public key:
|
||||
*
|
||||
* - 4 byte outer sequence tag and length
|
||||
* - 2 byte algorithm sequence tag and length
|
||||
* - 2 byte algorithm OID tag and length
|
||||
* - 9 byte algorithm OID (from NIST CSOR OID arc)
|
||||
* - 4 byte bit string tag and length
|
||||
* - 1 bitstring lead byte
|
||||
*/
|
||||
# define ML_KEM_SPKI_OVERHEAD 22
|
||||
typedef struct {
|
||||
const uint8_t asn1_prefix[ML_KEM_SPKI_OVERHEAD];
|
||||
} ML_KEM_SPKI_INFO;
|
||||
|
||||
/*-
|
||||
* For each algorithm we support a few PKCS#8 input formats,
|
||||
*
|
||||
* - Seed: SEQUENCE(OCTET STRING)
|
||||
* - Private key: SEQUENCE([1] IMPLICIT OCTET STRING)
|
||||
* - Seed & private key: SEQUENCE(OCTET STRING, [1] IMPLICIT OCTET STRING)
|
||||
* - OQS private key: OCTET STRING
|
||||
* - OQS private + public key: OCTET STRING
|
||||
* (The public key is ignored, just as with PKCS#8 v2.)
|
||||
*
|
||||
* An offset of zero means that particular field is absent.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *p8_name;
|
||||
size_t p8_bytes;
|
||||
uint32_t p8_magic;
|
||||
uint16_t seed_magic;
|
||||
size_t seed_offset;
|
||||
uint32_t priv_magic;
|
||||
size_t priv_offset;
|
||||
size_t pub_offset;
|
||||
} ML_KEM_PKCS8_INFO;
|
||||
|
||||
typedef struct {
|
||||
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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
559
providers/implementations/encode_decode/ml_kem_codecs.c
Normal file
559
providers/implementations/encode_decode/ml_kem_codecs.c
Normal file
|
@ -0,0 +1,559 @@
|
|||
/*
|
||||
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <openssl/byteorder.h>
|
||||
#include <openssl/proverr.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/core_names.h>
|
||||
#include "internal/encoder.h"
|
||||
#include "ml_kem_codecs.h"
|
||||
|
||||
/*-
|
||||
* Tables describing supported ASN.1 input/output formats.
|
||||
* For each parameter set we support a few PKCS#8 input formats, three
|
||||
* corresponding to the "either or both" variants of:
|
||||
*
|
||||
* ML-KEM-PrivateKey ::= SEQUENCE {
|
||||
* seed OCTET STRING SIZE (64) OPTIONAL,
|
||||
* expandedKey [1] IMPLICIT OCTET STRING SIZE (1632 | 2400 | 3172) OPTIONAL }
|
||||
* (WITH COMPONENTS {..., seed PRESENT } |
|
||||
* WITH COMPONENTS {..., expandedKey PRESENT })
|
||||
*
|
||||
* and two more for historical OQS encodings.
|
||||
*
|
||||
* - OQS private key: OCTET STRING
|
||||
* - OQS private + public key: OCTET STRING
|
||||
* (The public key is ignored, just as with PKCS#8 v2.)
|
||||
*
|
||||
* An offset of zero means that particular field is absent.
|
||||
*
|
||||
* On output the PKCS8 info table order is important:
|
||||
* - When we have a seed we'll use the first entry with a non-zero seed offset.
|
||||
* - Otherwise, the first entry with a zero seed offset.
|
||||
*
|
||||
* As written, when possible, we prefer to output both the seed and private
|
||||
* key, otherwise, just the private key ([1] IMPLICIT OCTET STRING form).
|
||||
*
|
||||
* The various lengths in the PKCS#8 tag/len fields could have been left
|
||||
* zeroed, and filled in on the fly from the algorithm parameters, but that
|
||||
* makes the code more complex, so a choice was made to embed them directly
|
||||
* into the tables. Had they been zeroed, one table could cover all three
|
||||
* ML-KEM parameter sets.
|
||||
*/
|
||||
#define NUM_PKCS8_FORMATS 5
|
||||
|
||||
/*-
|
||||
* ML-KEM-512:
|
||||
* Public key bytes: 800 (0x0320)
|
||||
* Private key bytes: 1632 (0x0660)
|
||||
*/
|
||||
static const ML_KEM_SPKI_INFO ml_kem_512_spki_info = {
|
||||
{ 0x30, 0x82, 0x03, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48,
|
||||
0x01, 0x65, 0x03, 0x04, 0x04, 0x01, 0x03, 0x82, 0x03, 0x21, 0x00, }
|
||||
};
|
||||
static const ML_KEM_PKCS8_INFO ml_kem_512_pkcs8_info[NUM_PKCS8_FORMATS] = {
|
||||
{ "seed-priv", 1706, 0x308206a6, 0x0440, 6, 0x81820660, 74, 0, },
|
||||
{ "priv-only", 1640, 0x30820664, 0, 0, 0x81820660, 8, 0, },
|
||||
{ "seed-only", 68, 0x30420440, 0x0440, 4, 0, 0, 0, },
|
||||
{ "priv-oqs", 1636, 0x04820660, 0, 0, 0x04820660, 4, 0, },
|
||||
{ "pair-oqs", 2436, 0x04820980, 0, 0, 0x04820980, 4, 1636, },
|
||||
};
|
||||
|
||||
/*-
|
||||
* ML-KEM-768:
|
||||
* Public key bytes: 1184 (0x04a0)
|
||||
* Private key bytes: 2400 (0x0960)
|
||||
*/
|
||||
static const ML_KEM_SPKI_INFO ml_kem_768_spki_info = {
|
||||
{ 0x30, 0x82, 0x04, 0xb2, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48,
|
||||
0x01, 0x65, 0x03, 0x04, 0x04, 0x02, 0x03, 0x82, 0x04, 0xa1, 0x00, }
|
||||
};
|
||||
static const ML_KEM_PKCS8_INFO ml_kem_768_pkcs8_info[NUM_PKCS8_FORMATS] = {
|
||||
{ "seed-priv", 2474, 0x308209a6, 0x0440, 6, 0x81820960, 74, 0, },
|
||||
{ "priv-only", 2408, 0x30820964, 0, 0, 0x81820960, 8, 0, },
|
||||
{ "seed-only", 68, 0x30420440, 0x0440, 4, 0, 0, 0, },
|
||||
{ "priv-oqs", 2404, 0x04820960, 0, 0, 0x04820960, 4, 0, },
|
||||
{ "pair-oqs", 3588, 0x04820e00, 0, 0, 0x04820e00, 4, 2404, },
|
||||
};
|
||||
|
||||
/*-
|
||||
* ML-KEM-1024:
|
||||
* Private key bytes: 3168 (0x0c60)
|
||||
* Public key bytes: 1568 (0x0620)
|
||||
*/
|
||||
static const ML_KEM_SPKI_INFO ml_kem_1024_spki_info = {
|
||||
{ 0x30, 0x82, 0x06, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48,
|
||||
0x01, 0x65, 0x03, 0x04, 0x04, 0x03, 0x03, 0x82, 0x06, 0x21, 0x00, }
|
||||
};
|
||||
static const ML_KEM_PKCS8_INFO ml_kem_1024_pkcs8_info[NUM_PKCS8_FORMATS] = {
|
||||
{ "seed-priv", 3242, 0x30820ca6, 0x0440, 6, 0x81820c60, 74, 0, },
|
||||
{ "priv-only", 3176, 0x30820c64, 0, 0, 0x81820c60, 8, 0, },
|
||||
{ "seed-only", 68, 0x30420440, 0x0440, 4, 0, 0, 0, },
|
||||
{ "priv-oqs", 3172, 0x04820c60, 0, 0, 0x04820c60, 4, 0, },
|
||||
{ "pair-oqs", 4740, 0x04821280, 0, 0, 0x04821280, 4, 3172, },
|
||||
};
|
||||
|
||||
/* Indices of slots in the `cinfo_map` table below */
|
||||
#define ML_KEM_512_CINFO 0
|
||||
#define ML_KEM_768_CINFO 1
|
||||
#define ML_KEM_1024_CINFO 2
|
||||
|
||||
/*
|
||||
* Per-variant fixed parameters
|
||||
*/
|
||||
static const ML_KEM_CINFO cinfo_map[3] = {
|
||||
{ &ml_kem_512_spki_info, ml_kem_512_pkcs8_info },
|
||||
{ &ml_kem_768_spki_info, ml_kem_768_pkcs8_info },
|
||||
{ &ml_kem_1024_spki_info, ml_kem_1024_pkcs8_info }
|
||||
};
|
||||
|
||||
/* Retrieve the parameters of one of the ML-KEM variants */
|
||||
static const ML_KEM_CINFO *ml_kem_get_cinfo(int evp_type)
|
||||
{
|
||||
switch (evp_type) {
|
||||
case EVP_PKEY_ML_KEM_512:
|
||||
return &cinfo_map[ML_KEM_512_CINFO];
|
||||
case EVP_PKEY_ML_KEM_768:
|
||||
return &cinfo_map[ML_KEM_768_CINFO];
|
||||
case EVP_PKEY_ML_KEM_1024:
|
||||
return &cinfo_map[ML_KEM_1024_CINFO];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int vp8_pref_cmp(const void *va, const void *vb)
|
||||
{
|
||||
const ML_KEM_PKCS8_PREF *a = va;
|
||||
const ML_KEM_PKCS8_PREF *b = vb;
|
||||
|
||||
/*
|
||||
* Zeros sort last, otherwise the sort is in increasing order.
|
||||
*
|
||||
* The preferences are small enough to ensure the comparison is monotone as
|
||||
* required. Some versions of qsort(3) have been known to crash when the
|
||||
* comparison is not monotone.
|
||||
*/
|
||||
if (a->vp8_pref > 0 && b->vp8_pref > 0)
|
||||
return a->vp8_pref - b->vp8_pref;
|
||||
if (a->vp8_pref == 0)
|
||||
return b->vp8_pref;
|
||||
return -a->vp8_pref;
|
||||
}
|
||||
|
||||
static ML_KEM_PKCS8_PREF *vp8_order(const char *algorithm_name,
|
||||
const ML_KEM_PKCS8_INFO *pkcs8_info,
|
||||
const char *direction, const char *formats)
|
||||
{
|
||||
ML_KEM_PKCS8_PREF *ret;
|
||||
int i, count = 0;
|
||||
const char *fmt = formats, *end;
|
||||
const char *sep = "\t ,";
|
||||
|
||||
if ((ret = OPENSSL_zalloc((NUM_PKCS8_FORMATS + 1) * sizeof(*ret))) == NULL)
|
||||
return NULL;
|
||||
for (i = 0; i < NUM_PKCS8_FORMATS; ++i) {
|
||||
ret[i].vp8_entry = &pkcs8_info[i];
|
||||
ret[i].vp8_pref = 0;
|
||||
}
|
||||
|
||||
/* Default to compile-time table order. */
|
||||
if (formats == NULL)
|
||||
return ret;
|
||||
|
||||
/* Formats are case-insensitive, separated by spaces, tabs and/or commas */
|
||||
do {
|
||||
if (*(fmt += strspn(fmt, sep)) == '\0')
|
||||
break;
|
||||
end = fmt + strcspn(fmt, sep);
|
||||
for (i = 0; i < NUM_PKCS8_FORMATS; ++i) {
|
||||
if (ret[i].vp8_pref > 0
|
||||
|| OPENSSL_strncasecmp(ret[i].vp8_entry->p8_name,
|
||||
fmt, (end - fmt)) != 0)
|
||||
continue;
|
||||
ret[i].vp8_pref = ++count;
|
||||
break;
|
||||
}
|
||||
fmt = end;
|
||||
} while (count < NUM_PKCS8_FORMATS);
|
||||
|
||||
if (count == 0) {
|
||||
OPENSSL_free(ret);
|
||||
ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT,
|
||||
"no %s private key %s formats are enabled",
|
||||
algorithm_name, direction);
|
||||
return NULL;
|
||||
}
|
||||
qsort(ret, NUM_PKCS8_FORMATS, sizeof(*ret), vp8_pref_cmp);
|
||||
ret[count].vp8_entry = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ML_KEM_KEY *
|
||||
ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen, int evp_type,
|
||||
PROV_CTX *provctx, const char *propq)
|
||||
{
|
||||
OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
|
||||
const ML_KEM_VINFO *v;
|
||||
const ML_KEM_CINFO *c;
|
||||
const ML_KEM_SPKI_INFO *vspki;
|
||||
ML_KEM_KEY *ret;
|
||||
|
||||
if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL
|
||||
|| (c = ml_kem_get_cinfo(evp_type)) == NULL)
|
||||
return NULL;
|
||||
vspki = c->spki_info;
|
||||
if (publen != ML_KEM_SPKI_OVERHEAD + (ossl_ssize_t) v->pubkey_bytes
|
||||
|| memcmp(pubenc, vspki->asn1_prefix, ML_KEM_SPKI_OVERHEAD) != 0)
|
||||
return NULL;
|
||||
publen -= ML_KEM_SPKI_OVERHEAD;
|
||||
pubenc += ML_KEM_SPKI_OVERHEAD;
|
||||
|
||||
if ((ret = ossl_ml_kem_key_new(libctx, propq, evp_type)) == NULL)
|
||||
return NULL;
|
||||
|
||||
if (!ossl_ml_kem_parse_public_key(pubenc, (size_t) publen, ret)) {
|
||||
ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
|
||||
"errror parsing %s public key from input SPKI",
|
||||
v->algorithm_name);
|
||||
ossl_ml_kem_key_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ML_KEM_KEY *
|
||||
ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
|
||||
int evp_type, PROV_CTX *provctx,
|
||||
const char *propq)
|
||||
{
|
||||
OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
|
||||
const ML_KEM_VINFO *v;
|
||||
const ML_KEM_CINFO *c;
|
||||
ML_KEM_PKCS8_PREF *vp8_alloc = NULL, *vp8_slot;
|
||||
const ML_KEM_PKCS8_INFO *vp8;
|
||||
ML_KEM_KEY *key = NULL, *ret = NULL;
|
||||
PKCS8_PRIV_KEY_INFO *p8inf = NULL;
|
||||
const uint8_t *buf, *pos;
|
||||
const X509_ALGOR *alg = NULL;
|
||||
const char *formats;
|
||||
int len, ptype;
|
||||
uint32_t magic;
|
||||
uint16_t seed_magic;
|
||||
|
||||
/* Which ML-KEM variant? */
|
||||
if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL
|
||||
|| (c = ml_kem_get_cinfo(evp_type)) == NULL)
|
||||
return 0;
|
||||
|
||||
/* Extract the key OID and any parameters. */
|
||||
if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, &prvenc, prvlen)) == NULL)
|
||||
return 0;
|
||||
/* Shortest prefix is 4 bytes: seq tag/len + octet string tag/len */
|
||||
if (!PKCS8_pkey_get0(NULL, &buf, &len, &alg, p8inf))
|
||||
goto end;
|
||||
/* Bail out early if this is some other key type. */
|
||||
if (OBJ_obj2nid(alg->algorithm) != evp_type)
|
||||
goto end;
|
||||
|
||||
/* Get the list of enabled decoders. Their order is not important here. */
|
||||
formats = ossl_prov_ctx_get_param(
|
||||
provctx, OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS, NULL);
|
||||
vp8_slot = vp8_alloc = vp8_order(v->algorithm_name, c->pkcs8_info,
|
||||
"input", formats);
|
||||
if (vp8_alloc == NULL)
|
||||
goto end;
|
||||
|
||||
/* Parameters must be absent. */
|
||||
X509_ALGOR_get0(NULL, &ptype, NULL, alg);
|
||||
if (ptype != V_ASN1_UNDEF) {
|
||||
ERR_raise_data(ERR_LIB_PROV, PROV_R_UNEXPECTED_KEY_PARAMETERS,
|
||||
"unexpected parameters with a PKCS#8 %s private key",
|
||||
v->algorithm_name);
|
||||
goto end;
|
||||
}
|
||||
if ((ossl_ssize_t)len < (ossl_ssize_t)sizeof(magic))
|
||||
goto end;
|
||||
|
||||
/* Find the matching p8 info slot, that also has the expected length. */
|
||||
pos = OPENSSL_load_u32_be(&magic, buf);
|
||||
for (vp8_slot = vp8_alloc; vp8_slot->vp8_entry != NULL; ++vp8_slot) {
|
||||
if (magic == vp8_slot->vp8_entry->p8_magic
|
||||
&& len == (ossl_ssize_t)vp8_slot->vp8_entry->p8_bytes)
|
||||
break;
|
||||
}
|
||||
if ((vp8 = vp8_slot->vp8_entry) == NULL)
|
||||
goto end;
|
||||
|
||||
if (vp8->seed_offset > 0) {
|
||||
/* Check |seed| tag/len, if not subsumed by |magic|. */
|
||||
if (pos + sizeof(uint16_t) == buf + vp8->seed_offset) {
|
||||
pos = OPENSSL_load_u16_be(&seed_magic, pos);
|
||||
if (seed_magic != vp8->seed_magic)
|
||||
goto end;
|
||||
} else if (pos != buf + vp8->seed_offset) {
|
||||
goto end;
|
||||
}
|
||||
pos += ML_KEM_SEED_BYTES;
|
||||
}
|
||||
if (vp8->priv_offset > 0) {
|
||||
/* Check |priv| tag/len */
|
||||
if (pos + sizeof(uint32_t) == buf + vp8->priv_offset) {
|
||||
pos = OPENSSL_load_u32_be(&magic, pos);
|
||||
if (magic != vp8->priv_magic)
|
||||
goto end;
|
||||
} else if (pos != buf + vp8->priv_offset) {
|
||||
goto end;
|
||||
}
|
||||
pos += v->prvkey_bytes;
|
||||
}
|
||||
if (vp8->pub_offset > 0) {
|
||||
if (pos != buf + vp8->pub_offset)
|
||||
goto end;
|
||||
pos += v->pubkey_bytes;
|
||||
}
|
||||
if (pos != buf + len)
|
||||
goto end;
|
||||
|
||||
/*
|
||||
* Collect the seed and/or key into a "decoded" private key object,
|
||||
* to be turned into a real key on provider "load" or "import".
|
||||
*/
|
||||
if ((key = ossl_ml_kem_key_new(libctx, propq, evp_type)) == NULL)
|
||||
goto end;
|
||||
key->retain_seed = ossl_prov_ctx_get_bool_param(
|
||||
provctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1);
|
||||
key->prefer_seed = ossl_prov_ctx_get_bool_param(
|
||||
provctx, OSSL_PKEY_PARAM_ML_KEM_PREFER_SEED, 1);
|
||||
if (vp8->seed_offset > 0) {
|
||||
if (!ossl_ml_kem_set_seed(buf + vp8->seed_offset,
|
||||
ML_KEM_SEED_BYTES, key)) {
|
||||
ERR_raise_data(ERR_LIB_OSSL_DECODER, ERR_R_INTERNAL_ERROR,
|
||||
"error storing %s private key seed",
|
||||
v->algorithm_name);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (vp8->priv_offset > 0) {
|
||||
if ((key->encoded_dk = OPENSSL_malloc(v->prvkey_bytes)) == NULL) {
|
||||
ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY,
|
||||
"error parsing %s private key",
|
||||
v->algorithm_name);
|
||||
goto end;
|
||||
}
|
||||
memcpy(key->encoded_dk, buf + vp8->priv_offset, v->prvkey_bytes);
|
||||
}
|
||||
/* Any OQS public key content is ignored */
|
||||
ret = key;
|
||||
|
||||
end:
|
||||
OPENSSL_free(vp8_alloc);
|
||||
PKCS8_PRIV_KEY_INFO_free(p8inf);
|
||||
if (ret == NULL)
|
||||
ossl_ml_kem_key_free(key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Same as ossl_ml_kem_encode_pubkey, but allocates the output buffer. */
|
||||
int ossl_ml_kem_i2d_pubkey(const ML_KEM_KEY *key, unsigned char **out)
|
||||
{
|
||||
size_t publen;
|
||||
|
||||
if (!ossl_ml_kem_have_pubkey(key)) {
|
||||
ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY,
|
||||
"no %s public key data available",
|
||||
key->vinfo->algorithm_name);
|
||||
return 0;
|
||||
}
|
||||
publen = key->vinfo->pubkey_bytes;
|
||||
|
||||
if (out != NULL
|
||||
&& (*out = OPENSSL_malloc(publen)) == NULL)
|
||||
return 0;
|
||||
if (!ossl_ml_kem_encode_public_key(*out, publen, key)) {
|
||||
ERR_raise_data(ERR_LIB_OSSL_ENCODER, ERR_R_INTERNAL_ERROR,
|
||||
"error encoding %s public key",
|
||||
key->vinfo->algorithm_name);
|
||||
OPENSSL_free(*out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)publen;
|
||||
}
|
||||
|
||||
/* Allocate and encode PKCS#8 private key payload. */
|
||||
int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, uint8_t **out,
|
||||
PROV_CTX *provctx)
|
||||
{
|
||||
const ML_KEM_VINFO *v = key->vinfo;
|
||||
const ML_KEM_CINFO *c;
|
||||
ML_KEM_PKCS8_PREF *vp8_alloc, *vp8_slot;
|
||||
const ML_KEM_PKCS8_INFO *vp8;
|
||||
uint8_t *buf = NULL, *pos;
|
||||
const char *formats;
|
||||
int len = ML_KEM_SEED_BYTES;
|
||||
int ret = 0;
|
||||
|
||||
/* Not ours to handle */
|
||||
if ((c = ml_kem_get_cinfo(v->evp_type)) == NULL)
|
||||
return 0;
|
||||
|
||||
if (!ossl_ml_kem_have_prvkey(key)) {
|
||||
ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PRIVATE_KEY,
|
||||
"no %s private key data available",
|
||||
key->vinfo->algorithm_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
formats = ossl_prov_ctx_get_param(
|
||||
provctx, OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS, NULL);
|
||||
vp8_slot = vp8_alloc = vp8_order(v->algorithm_name, c->pkcs8_info,
|
||||
"output", formats);
|
||||
if (vp8_alloc == NULL)
|
||||
return 0;
|
||||
|
||||
/* If we don't have a seed, skip seedful entries */
|
||||
if (!ossl_ml_kem_have_seed(key))
|
||||
while (vp8_slot->vp8_entry != NULL
|
||||
&& vp8_slot->vp8_entry->seed_offset != 0)
|
||||
++vp8_slot;
|
||||
/* No matching table entries, give up */
|
||||
if ((vp8 = vp8_slot->vp8_entry) == NULL) {
|
||||
ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT,
|
||||
"no matching enabled %s private key output formats",
|
||||
v->algorithm_name);
|
||||
goto end;
|
||||
}
|
||||
len = vp8->p8_bytes;
|
||||
|
||||
if (out == NULL) {
|
||||
ret = len;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((pos = buf = OPENSSL_malloc((size_t) len)) == NULL)
|
||||
goto end;
|
||||
|
||||
pos = OPENSSL_store_u32_be(pos, vp8->p8_magic);
|
||||
if (vp8->seed_offset != 0) {
|
||||
/*
|
||||
* Either the tag/len were already included in |magic| or they require
|
||||
* us to write two bytes now.
|
||||
*/
|
||||
if (pos + sizeof(uint16_t) == buf + vp8->seed_offset)
|
||||
pos = OPENSSL_store_u16_be(pos, vp8->seed_magic);
|
||||
if (pos != buf + vp8->seed_offset
|
||||
|| !ossl_ml_kem_encode_seed(pos, ML_KEM_SEED_BYTES, key)) {
|
||||
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
|
||||
"error encoding %s private key",
|
||||
v->algorithm_name);
|
||||
goto end;
|
||||
}
|
||||
pos += ML_KEM_SEED_BYTES;
|
||||
}
|
||||
if (vp8->priv_offset != 0) {
|
||||
if (pos + sizeof(uint32_t) == buf + vp8->priv_offset)
|
||||
pos = OPENSSL_store_u32_be(pos, vp8->priv_magic);
|
||||
if (pos != buf + vp8->priv_offset
|
||||
|| !ossl_ml_kem_encode_private_key(pos, v->prvkey_bytes, key)) {
|
||||
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
|
||||
"error encoding %s private key",
|
||||
v->algorithm_name);
|
||||
goto end;
|
||||
}
|
||||
pos += v->prvkey_bytes;
|
||||
}
|
||||
/* OQS form output with tacked-on public key */
|
||||
if (vp8->pub_offset != 0) {
|
||||
/* The OQS pubkey is never separately DER-wrapped */
|
||||
if (pos != buf + vp8->pub_offset
|
||||
|| !ossl_ml_kem_encode_public_key(pos, v->pubkey_bytes, key)) {
|
||||
ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
|
||||
"error encoding %s private key",
|
||||
v->algorithm_name);
|
||||
goto end;
|
||||
}
|
||||
pos += v->pubkey_bytes;
|
||||
}
|
||||
|
||||
if (pos == buf + len) {
|
||||
*out = buf;
|
||||
ret = len;
|
||||
}
|
||||
|
||||
end:
|
||||
OPENSSL_free(vp8_alloc);
|
||||
if (ret == 0)
|
||||
OPENSSL_free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ossl_ml_kem_key_to_text(BIO *out, const ML_KEM_KEY *key, int selection)
|
||||
{
|
||||
uint8_t seed[ML_KEM_SEED_BYTES], *prvenc = NULL, *pubenc = NULL;
|
||||
size_t publen, prvlen;
|
||||
const char *type_label = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (out == NULL || key == NULL) {
|
||||
ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER);
|
||||
return 0;
|
||||
}
|
||||
type_label = key->vinfo->algorithm_name;
|
||||
publen = key->vinfo->pubkey_bytes;
|
||||
prvlen = key->vinfo->prvkey_bytes;
|
||||
|
||||
if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0
|
||||
&& (ossl_ml_kem_have_prvkey(key)
|
||||
|| ossl_ml_kem_have_seed(key))) {
|
||||
if (BIO_printf(out, "%s Private-Key:\n", type_label) <= 0)
|
||||
return 0;
|
||||
|
||||
if (ossl_ml_kem_have_seed(key)) {
|
||||
if (!ossl_ml_kem_encode_seed(seed, sizeof(seed), key))
|
||||
goto end;
|
||||
if (!ossl_bio_print_labeled_buf(out, "seed:", seed, sizeof(seed)))
|
||||
goto end;
|
||||
}
|
||||
if (ossl_ml_kem_have_prvkey(key)) {
|
||||
if ((prvenc = OPENSSL_malloc(prvlen)) == NULL)
|
||||
return 0;
|
||||
if (!ossl_ml_kem_encode_private_key(prvenc, prvlen, key))
|
||||
goto end;
|
||||
if (!ossl_bio_print_labeled_buf(out, "dk:", prvenc, prvlen))
|
||||
goto end;
|
||||
}
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
/* The public key is output regardless of the selection */
|
||||
if (ossl_ml_kem_have_pubkey(key)) {
|
||||
/* If we did not output private key bits, this is a public key */
|
||||
if (ret == 0 && BIO_printf(out, "%s Public-Key:\n", type_label) <= 0)
|
||||
goto end;
|
||||
|
||||
if ((pubenc = OPENSSL_malloc(key->vinfo->pubkey_bytes)) == NULL
|
||||
|| !ossl_ml_kem_encode_public_key(pubenc, publen, key)
|
||||
|| !ossl_bio_print_labeled_buf(out, "ek:", pubenc, publen))
|
||||
goto end;
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
/* If we got here, and ret == 0, there was no key material */
|
||||
if (ret == 0)
|
||||
ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
|
||||
"no %s key material available",
|
||||
type_label);
|
||||
|
||||
end:
|
||||
OPENSSL_free(pubenc);
|
||||
OPENSSL_free(prvenc);
|
||||
return ret;
|
||||
}
|
91
providers/implementations/encode_decode/ml_kem_codecs.h
Normal file
91
providers/implementations/encode_decode/ml_kem_codecs.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#ifndef PROV_ML_KEM_CODECS_H
|
||||
# define PROV_ML_KEM_CODECS_H
|
||||
# pragma once
|
||||
|
||||
# include <openssl/e_os2.h>
|
||||
# include "crypto/ml_kem.h"
|
||||
# include "prov/provider_ctx.h"
|
||||
|
||||
/*-
|
||||
* The DER ASN.1 encoding of ML-KEM (and ML-DSA) public keys prepends 22 bytes
|
||||
* to the encoded public key:
|
||||
*
|
||||
* - 4 byte outer sequence tag and length
|
||||
* - 2 byte algorithm sequence tag and length
|
||||
* - 2 byte algorithm OID tag and length
|
||||
* - 9 byte algorithm OID (from NIST CSOR OID arc)
|
||||
* - 4 byte bit string tag and length
|
||||
* - 1 bitstring lead byte
|
||||
*/
|
||||
# define ML_KEM_SPKI_OVERHEAD 22
|
||||
typedef struct {
|
||||
const uint8_t asn1_prefix[ML_KEM_SPKI_OVERHEAD];
|
||||
} ML_KEM_SPKI_INFO;
|
||||
|
||||
/*-
|
||||
* For each parameter set we support a few PKCS#8 input formats, three
|
||||
* corresponding to the "either or both" variants of:
|
||||
*
|
||||
* ML-KEM-PrivateKey ::= SEQUENCE {
|
||||
* seed OCTET STRING SIZE (64) OPTIONAL,
|
||||
* expandedKey [1] IMPLICIT OCTET STRING SIZE (1632 | 2400 | 3172) OPTIONAL }
|
||||
* (WITH COMPONENTS {..., seed PRESENT } |
|
||||
* WITH COMPONENTS {..., expandedKey PRESENT })
|
||||
*
|
||||
* and two more for historical OQS encodings.
|
||||
*
|
||||
* - OQS private key: OCTET STRING
|
||||
* - OQS private + public key: OCTET STRING
|
||||
* (The public key is ignored, just as with PKCS#8 v2.)
|
||||
*
|
||||
* An offset of zero means that particular field is absent.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *p8_name;
|
||||
size_t p8_bytes;
|
||||
uint32_t p8_magic;
|
||||
uint16_t seed_magic;
|
||||
size_t seed_offset;
|
||||
uint32_t priv_magic;
|
||||
size_t priv_offset;
|
||||
size_t pub_offset;
|
||||
} ML_KEM_PKCS8_INFO;
|
||||
|
||||
typedef struct {
|
||||
const ML_KEM_SPKI_INFO *spki_info;
|
||||
const ML_KEM_PKCS8_INFO *pkcs8_info;
|
||||
} ML_KEM_CINFO;
|
||||
|
||||
typedef struct {
|
||||
const ML_KEM_PKCS8_INFO *vp8_entry;
|
||||
int vp8_pref;
|
||||
} ML_KEM_PKCS8_PREF;
|
||||
|
||||
__owur
|
||||
ML_KEM_KEY *ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen,
|
||||
int evp_type, PROV_CTX *provctx,
|
||||
const char *propq);
|
||||
__owur
|
||||
ML_KEM_KEY *ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
|
||||
int evp_type, PROV_CTX *provctx,
|
||||
const char *propq);
|
||||
__owur
|
||||
int ossl_ml_kem_key_to_text(BIO *out, const ML_KEM_KEY *key, int selection);
|
||||
__owur
|
||||
__owur
|
||||
int ossl_ml_kem_i2d_pubkey(const ML_KEM_KEY *key, unsigned char **out);
|
||||
__owur
|
||||
__owur
|
||||
int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, unsigned char **out,
|
||||
PROV_CTX *provctx);
|
||||
|
||||
#endif /* PROV_ML_KEM_CODECS_H */
|
|
@ -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[]) \
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue