Configurable import-time PCT for ML-KEM

And related cleanup.

Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
Reviewed-by: Tim Hudson <tjh@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26789)
This commit is contained in:
Viktor Dukhovni 2025-02-15 04:36:25 +11:00
parent f31b98fefe
commit cab4e7cbd1
14 changed files with 207 additions and 118 deletions

View file

@ -28,17 +28,12 @@ const uint8_t *ossl_ml_dsa_key_get_seed(const ML_DSA_KEY *key)
return key->seed;
}
int ossl_ml_dsa_key_prefer_seed(const ML_DSA_KEY *key)
int ossl_ml_dsa_key_get_prov_flags(const ML_DSA_KEY *key)
{
return key->prefer_seed;
return key->prov_flags;
}
int ossl_ml_dsa_key_retain_seed(const ML_DSA_KEY *key)
{
return key->retain_seed;
}
int ossl_ml_dsa_set_prekey(ML_DSA_KEY *key, int prefer_seed, int retain_seed,
int ossl_ml_dsa_set_prekey(ML_DSA_KEY *key, int flags_set, int flags_clr,
const uint8_t *seed, size_t seed_len,
const uint8_t *sk, size_t sk_len)
{
@ -58,10 +53,8 @@ int ossl_ml_dsa_set_prekey(ML_DSA_KEY *key, int prefer_seed, int retain_seed,
if (seed != NULL
&& (key->seed = OPENSSL_memdup(seed, seed_len)) == NULL)
goto end;
if (prefer_seed >= 0)
key->prefer_seed = prefer_seed;
if (retain_seed >= 0)
key->retain_seed = retain_seed;
key->prov_flags |= flags_set;
key->prov_flags &= ~flags_clr;
ret = 1;
end:
@ -94,8 +87,7 @@ ML_DSA_KEY *ossl_ml_dsa_key_new(OSSL_LIB_CTX *libctx, const char *propq,
if (ret != NULL) {
ret->libctx = libctx;
ret->params = params;
ret->prefer_seed = 1;
ret->retain_seed = 1;
ret->prov_flags = ML_DSA_KEY_PROV_FLAGS_DEFAULT;
ret->shake128_md = EVP_MD_fetch(libctx, "SHAKE-128", propq);
ret->shake256_md = EVP_MD_fetch(libctx, "SHAKE-256", propq);
if (ret->shake128_md == NULL || ret->shake256_md == NULL)
@ -190,8 +182,7 @@ ML_DSA_KEY *ossl_ml_dsa_key_dup(const ML_DSA_KEY *src, int selection)
if (ret != NULL) {
ret->libctx = src->libctx;
ret->params = src->params;
ret->retain_seed = src->retain_seed;
ret->prefer_seed = src->prefer_seed;
ret->prov_flags = src->prov_flags;
if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0) {
if (src->pub_encoding != NULL) {
/* The public components are present if the private key is present */
@ -443,7 +434,7 @@ static int keygen_internal(ML_DSA_KEY *out)
&& ossl_ml_dsa_sk_encode(out);
err:
if (out->seed != NULL && !out->retain_seed) {
if (out->seed != NULL && (out->prov_flags & ML_DSA_KEY_RETAIN_SEED) == 0) {
OPENSSL_clear_free(out->seed, ML_DSA_SEED_BYTES);
out->seed = NULL;
}

View file

@ -34,8 +34,7 @@ struct ml_dsa_key_st {
uint8_t *pub_encoding;
uint8_t *priv_encoding;
uint8_t *seed;
int retain_seed;
int prefer_seed;
int prov_flags;
/*
* t1 is the Polynomial encoding of the 10 MSB of each coefficient of the

View file

@ -1394,7 +1394,7 @@ int genkey(const uint8_t seed[ML_KEM_SEED_BYTES],
/* Optionally save the |d| portion of the seed */
key->d = key->z + ML_KEM_RANDOM_BYTES;
if (key->retain_seed) {
if (key->prov_flags & ML_KEM_KEY_RETAIN_SEED) {
memcpy(key->d, seed, ML_KEM_RANDOM_BYTES);
} else {
OPENSSL_cleanse(key->d, ML_KEM_RANDOM_BYTES);
@ -1576,10 +1576,6 @@ const ML_KEM_VINFO *ossl_ml_kem_get_vinfo(int evp_type)
return NULL;
}
/*
* The |retain_seed| parameter indicates whether the seed should be retained
* once the key is generated.
*/
ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, const char *properties,
int evp_type)
{
@ -1594,8 +1590,7 @@ ML_KEM_KEY *ossl_ml_kem_key_new(OSSL_LIB_CTX *libctx, const char *properties,
key->vinfo = vinfo;
key->libctx = libctx;
key->prefer_seed = 1;
key->retain_seed = 1;
key->prov_flags = ML_KEM_KEY_PROV_FLAGS_DEFAULT;
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);

View file

@ -108,6 +108,17 @@ configuration options programmatically.
=over 4
=item C<ml-kem.import_pct_type> (B<OSSL_PKEY_PARAM_ML_KEM_IMPORT_PCT_TYPE>) <UTF8 string>
When an B<ML-KEM> key is imported as an explict FIPS 203 B<dk> decapsulation
key, rather than a seed, a pairwise consistency test (PCT) is optionally
performed.
By default, or when this parameter is set explicitly to C<random>, the PCT
is performed with a random entropy value for the encapsulation step.
Setting the parameter to C<fixed>, still runs the test, but the encapsulation
entropy is a fixed 32 byte value.
Specifying any other value of the parameter, e.g. C<none>, skips the test.
=item C<ml-kem.retain_seed> (B<OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED>) <UTF8 string>
When set to a string representing a false boolean value (see

View file

@ -42,6 +42,12 @@
# define MAX_ML_DSA_PUB_LEN ML_DSA_87_PUB_LEN
# define MAX_ML_DSA_SIG_LEN ML_DSA_87_SIG_LEN
# define ML_DSA_KEY_PREFER_SEED (1 << 0)
# define ML_DSA_KEY_RETAIN_SEED (1 << 1)
/* Default provider flags */
# define ML_DSA_KEY_PROV_FLAGS_DEFAULT \
(ML_DSA_KEY_PREFER_SEED | ML_DSA_KEY_RETAIN_SEED)
/*
* Refer to FIPS 204 Section 4 Parameter sets.
* Fields that are shared between all algorithms (such as q & d) have been omitted.
@ -86,9 +92,8 @@ __owur size_t ossl_ml_dsa_key_get_pub_len(const ML_DSA_KEY *key);
__owur const uint8_t *ossl_ml_dsa_key_get_priv(const ML_DSA_KEY *key);
__owur size_t ossl_ml_dsa_key_get_priv_len(const ML_DSA_KEY *key);
__owur const uint8_t *ossl_ml_dsa_key_get_seed(const ML_DSA_KEY *key);
__owur int ossl_ml_dsa_key_prefer_seed(const ML_DSA_KEY *key);
__owur int ossl_ml_dsa_key_retain_seed(const ML_DSA_KEY *key);
int ossl_ml_dsa_set_prekey(ML_DSA_KEY *key, int prefer_seed, int retain_seed,
__owur int ossl_ml_dsa_key_get_prov_flags(const ML_DSA_KEY *key);
int ossl_ml_dsa_set_prekey(ML_DSA_KEY *key, int flags_set, int flags_clr,
const uint8_t *seed, size_t seed_len,
const uint8_t *sk, size_t sk_len);
__owur size_t ossl_ml_dsa_key_get_collision_strength_bits(const ML_DSA_KEY *key);

View file

@ -117,6 +117,17 @@
# define ML_KEM_1024_DV 5
# define ML_KEM_1024_SECBITS 256
# define ML_KEM_KEY_RANDOM_PCT (1 << 0)
# define ML_KEM_KEY_FIXED_PCT (1 << 1)
# define ML_KEM_KEY_PREFER_SEED (1 << 2)
# define ML_KEM_KEY_RETAIN_SEED (1 << 3)
/* Mask to check whether PCT on import is enabled */
# define ML_KEM_KEY_PCT_TYPE \
(ML_KEM_KEY_RANDOM_PCT | ML_KEM_KEY_FIXED_PCT)
/* Default provider flags */
# define ML_KEM_KEY_PROV_FLAGS_DEFAULT \
(ML_KEM_KEY_RANDOM_PCT | ML_KEM_KEY_PREFER_SEED | ML_KEM_KEY_RETAIN_SEED)
/*
* External variant-specific API
* -----------------------------
@ -171,8 +182,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? */
int prov_flags; /* prefer/retain seed and PCT flags */
/*
* Fixed-size built-in buffer, which holds the |rho| and the public key

View file

@ -14,6 +14,7 @@
#include <openssl/x509.h>
#include <openssl/core_names.h>
#include "internal/encoder.h"
#include "prov/ml_dsa.h"
#include "ml_dsa_codecs.h"
/*-
@ -139,7 +140,6 @@ ossl_ml_dsa_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_DSA_PARAMS *v;
const ML_COMMON_CODEC *codec;
ML_COMMON_PKCS8_FMT_PREF *fmt_slots = NULL, *slot;
@ -149,7 +149,7 @@ ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
const uint8_t *buf, *pos;
const X509_ALGOR *alg = NULL;
const char *formats;
int len, ptype, retain, prefer;
int len, ptype;
uint32_t magic;
uint16_t seed_magic;
const uint8_t *seed = NULL;
@ -244,7 +244,7 @@ ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
* 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_dsa_key_new(libctx, propq, evp_type)) == NULL)
if ((key = ossl_prov_ml_dsa_new(provctx, propq, evp_type)) == NULL)
goto end;
if (p8fmt->seed_length > 0)
seed = buf + p8fmt->seed_offset;
@ -252,19 +252,7 @@ ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
priv = buf + p8fmt->priv_offset;
/* Any OQS public key content is ignored */
/*
* If the key ends up "loaded" into the same provider, these are the
* correct config settings, otherwise, new values will be assigned on
* import into a different provider. The "load" API does not pass along
* the provider context.
*/
retain =
ossl_prov_ctx_get_bool_param(
provctx, OSSL_PKEY_PARAM_ML_DSA_RETAIN_SEED, 1);
prefer =
ossl_prov_ctx_get_bool_param(
provctx, OSSL_PKEY_PARAM_ML_DSA_PREFER_SEED, 1);
if (ossl_ml_dsa_set_prekey(key, prefer, retain,
if (ossl_ml_dsa_set_prekey(key, 0, 0,
seed, ML_DSA_SEED_BYTES, priv, v->sk_len))
ret = key;

View file

@ -13,6 +13,7 @@
#include <openssl/x509.h>
#include <openssl/core_names.h>
#include "internal/encoder.h"
#include "prov/ml_kem.h"
#include "ml_kem_codecs.h"
/* Tables describing supported ASN.1 input/output formats. */
@ -138,7 +139,6 @@ 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_COMMON_CODEC *codec;
ML_COMMON_PKCS8_FMT_PREF *fmt_slots = NULL, *slot;
@ -241,12 +241,9 @@ ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
* 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)
if ((key = ossl_prov_ml_kem_new(provctx, 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 (p8fmt->seed_length > 0) {
if (!ossl_ml_kem_set_seed(buf + p8fmt->seed_offset,
ML_KEM_SEED_BYTES, key)) {

View file

@ -0,0 +1,14 @@
/*
* Copyright 2024 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 "crypto/ml_dsa.h"
#include "prov/provider_ctx.h"
ML_DSA_KEY *
ossl_prov_ml_dsa_new(PROV_CTX *provctx, const char *propq, int evp_type);

View file

@ -0,0 +1,14 @@
/*
* Copyright 2024 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 "crypto/ml_kem.h"
#include "prov/provider_ctx.h"
ML_KEM_KEY *
ossl_prov_ml_kem_new(PROV_CTX *provctx, const char *propq, int evp_type);

View file

@ -18,6 +18,7 @@
#include "prov/implementations.h"
#include "prov/providercommon.h"
#include "prov/provider_ctx.h"
#include "prov/ml_dsa.h"
static OSSL_FUNC_keymgmt_free_fn ml_dsa_free_key;
static OSSL_FUNC_keymgmt_has_fn ml_dsa_has;
@ -93,22 +94,36 @@ static int ml_dsa_pairwise_test(const ML_DSA_KEY *key)
}
#endif
static void *ml_dsa_new_key(void *provctx, const char *propq,
int evp_type)
ML_DSA_KEY *ossl_prov_ml_dsa_new(PROV_CTX *ctx, const char *propq, int evp_type)
{
ML_DSA_KEY *key;
int prefer, retain;
if (!ossl_prov_is_running())
return 0;
key = ossl_ml_dsa_key_new(PROV_LIBCTX_OF(provctx), propq, evp_type);
key = ossl_ml_dsa_key_new(PROV_LIBCTX_OF(ctx), propq, evp_type);
/*
* When decoding, if the key ends up "loaded" into the same provider, these
* are the correct config settings, otherwise, new values will be assigned
* on import into a different provider. The "load" API does not pass along
* the provider context.
*/
if (key != NULL) {
prefer = ossl_prov_ctx_get_bool_param(
provctx, OSSL_PKEY_PARAM_ML_DSA_PREFER_SEED, 1);
retain = ossl_prov_ctx_get_bool_param(
provctx, OSSL_PKEY_PARAM_ML_DSA_RETAIN_SEED, 1);
ossl_ml_dsa_set_prekey(key, prefer, retain, NULL, 0, NULL, 0);
int flags_set = 0, flags_clr = 0;
if (ossl_prov_ctx_get_bool_param(
ctx, OSSL_PKEY_PARAM_ML_DSA_RETAIN_SEED, 1))
flags_set |= ML_DSA_KEY_RETAIN_SEED;
else
flags_clr = ML_DSA_KEY_RETAIN_SEED;
if (ossl_prov_ctx_get_bool_param(
ctx, OSSL_PKEY_PARAM_ML_DSA_PREFER_SEED, 1))
flags_set |= ML_DSA_KEY_PREFER_SEED;
else
flags_clr |= ML_DSA_KEY_PREFER_SEED;
ossl_ml_dsa_set_prekey(key, flags_set, flags_clr, NULL, 0, NULL, 0);
}
return key;
}
@ -217,8 +232,9 @@ static int ml_dsa_key_fromdata(ML_DSA_KEY *key, const OSSL_PARAM params[],
}
if (seed_len != 0
&& (sk_len == 0 || ossl_ml_dsa_key_prefer_seed(key))) {
if (!ossl_ml_dsa_set_prekey(key, -1, -1, seed, seed_len, NULL, 0))
&& (sk_len == 0
|| (ossl_ml_dsa_key_get_prov_flags(key) & ML_DSA_KEY_PREFER_SEED))) {
if (!ossl_ml_dsa_set_prekey(key, 0, 0, seed, seed_len, NULL, 0))
return 0;
if (!ossl_ml_dsa_generate_key(key)) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GENERATE_KEY);
@ -373,7 +389,8 @@ static int ml_dsa_export(void *keydata, int selection,
#ifndef FIPS_MODULE
static void *ml_dsa_load(const void *reference, size_t reference_sz)
{
ML_DSA_KEY *ret = NULL, *key = NULL;
ML_DSA_KEY *key = NULL;
const ML_DSA_PARAMS *key_params;
const uint8_t *sk, *seed;
if (ossl_prov_is_running() && reference_sz == sizeof(key)) {
@ -388,18 +405,25 @@ static void *ml_dsa_load(const void *reference, size_t reference_sz)
sk = ossl_ml_dsa_key_get_priv(key);
seed = ossl_ml_dsa_key_get_seed(key);
if (seed != NULL
&& (sk == NULL || ossl_ml_dsa_key_prefer_seed(key))) {
&& (sk == NULL || (ossl_ml_dsa_key_get_prov_flags(key)
& ML_DSA_KEY_PREFER_SEED))) {
if (ossl_ml_dsa_generate_key(key))
ret = key;
return key;
} else if (sk != NULL) {
if (ossl_ml_dsa_sk_decode(key, sk,
ossl_ml_dsa_key_get_priv_len(key)))
ret = key;
return key;
key_params = ossl_ml_dsa_key_params(key);
ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY,
"error parsing %s private key",
key_params->alg);
} else {
return key;
}
}
if (ret == NULL)
ossl_ml_dsa_key_free(key);
return ret;
return NULL;
}
#endif
@ -428,11 +452,11 @@ static void *ml_dsa_gen(void *genctx, int evp_type)
if (!ossl_prov_is_running())
return NULL;
key = ml_dsa_new_key(gctx->provctx, gctx->propq, evp_type);
key = ossl_prov_ml_dsa_new(gctx->provctx, gctx->propq, evp_type);
if (key == NULL)
return NULL;
if (gctx->entropy_len != 0
&& !ossl_ml_dsa_set_prekey(key, -1, -1,
&& !ossl_ml_dsa_set_prekey(key, 0, 0,
gctx->entropy, gctx->entropy_len, NULL, 0))
goto err;
if (!ossl_ml_dsa_generate_key(key)) {
@ -510,7 +534,7 @@ static void ml_dsa_gen_cleanup(void *genctx)
static OSSL_FUNC_keymgmt_gen_fn ml_dsa_##alg##_gen; \
static void *ml_dsa_##alg##_new_key(void *provctx) \
{ \
return ml_dsa_new_key(provctx, NULL, EVP_PKEY_ML_DSA_##alg); \
return ossl_prov_ml_dsa_new(provctx, NULL, EVP_PKEY_ML_DSA_##alg); \
} \
static void *ml_dsa_##alg##_gen(void *genctx, OSSL_CALLBACK *osslcb, void *cbarg)\
{ \

View file

@ -17,11 +17,12 @@
#include <openssl/self_test.h>
#include <openssl/param_build.h>
#include "crypto/ml_kem.h"
#include "internal/param_build_set.h"
#include "prov/implementations.h"
#include "prov/providercommon.h"
#include "prov/provider_ctx.h"
#include "prov/securitycheck.h"
#include "internal/param_build_set.h"
#include "prov/ml_kem.h"
static OSSL_FUNC_keymgmt_new_fn ml_kem_512_new;
static OSSL_FUNC_keymgmt_new_fn ml_kem_768_new;
@ -61,14 +62,14 @@ typedef struct ml_kem_gen_ctx_st {
uint8_t *seed;
} PROV_ML_KEM_GEN_CTX;
static int ml_kem_pairwise_test(const ML_KEM_KEY *key)
static int ml_kem_pairwise_test(const ML_KEM_KEY *key, int key_flags)
{
#ifdef FIPS_MODULE
OSSL_SELF_TEST *st = NULL;
OSSL_CALLBACK *cb = NULL;
void *cbarg = NULL;
unsigned char entropy[ML_KEM_RANDOM_BYTES];
#endif
unsigned char entropy[ML_KEM_RANDOM_BYTES];
unsigned char secret[ML_KEM_SHARED_SECRET_BYTES];
unsigned char out[ML_KEM_SHARED_SECRET_BYTES];
unsigned char *ctext = NULL;
@ -77,9 +78,10 @@ static int ml_kem_pairwise_test(const ML_KEM_KEY *key)
int ret = 0;
/* Unless we have both a public and private key, we can't do the test */
if (!ossl_ml_kem_have_prvkey(key) || !ossl_ml_kem_have_pubkey(key))
if (!ossl_ml_kem_have_prvkey(key)
|| !ossl_ml_kem_have_pubkey(key)
|| (key_flags & ML_KEM_KEY_PCT_TYPE) == 0)
return 1;
#ifdef FIPS_MODULE
/*
* The functions `OSSL_SELF_TEST_*` will return directly if parameter `st`
@ -100,22 +102,21 @@ static int ml_kem_pairwise_test(const ML_KEM_KEY *key)
goto err;
memset(out, 0, sizeof(out));
#ifdef FIPS_MODULE
/*
* The FIPS module does a PCT on power-on, and would leak the RNG
* handle if use random entropy here. So we use fixed entropy in
* the FIPS case. Ideally, the leak will be fixed, and the test
* will also use random entropy in FIPS mode.
* The pairwise test is skipped unless either RANDOM or FIXED entropy PCTs
* are enabled.
*/
if (key_flags & ML_KEM_KEY_RANDOM_PCT) {
operation_result = ossl_ml_kem_encap_rand(ctext, v->ctext_bytes,
secret, sizeof(secret), key);
} else {
memset(entropy, 0125, sizeof(entropy));
operation_result = ossl_ml_kem_encap_seed(ctext, v->ctext_bytes,
secret, sizeof(secret),
entropy, sizeof(entropy),
key);
#else
operation_result = ossl_ml_kem_encap_rand(ctext, v->ctext_bytes,
secret, sizeof(secret), key);
#endif
}
if (operation_result != 1)
goto err;
@ -144,17 +145,38 @@ err:
return ret;
}
static void *ml_kem_new(PROV_CTX *ctx, const char *propq, int evp_type)
ML_KEM_KEY *ossl_prov_ml_kem_new(PROV_CTX *ctx, const char *propq, int evp_type)
{
ML_KEM_KEY *key;
if (!ossl_prov_is_running())
return NULL;
/*
* When decoding, if the key ends up "loaded" into the same provider, these
* are the correct config settings, otherwise, new values will be assigned
* on import into a different provider. The "load" API does not pass along
* the provider context.
*/
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);
const char *pct_type = ossl_prov_ctx_get_param(
ctx, OSSL_PKEY_PARAM_ML_KEM_IMPORT_PCT_TYPE, "random");
if (ossl_prov_ctx_get_bool_param(
ctx, OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED, 1))
key->prov_flags |= ML_KEM_KEY_RETAIN_SEED;
else
key->prov_flags &= ~ML_KEM_KEY_RETAIN_SEED;
if (ossl_prov_ctx_get_bool_param(
ctx, OSSL_PKEY_PARAM_ML_KEM_PREFER_SEED, 1))
key->prov_flags |= ML_KEM_KEY_PREFER_SEED;
else
key->prov_flags &= ~ML_KEM_KEY_PREFER_SEED;
if (OPENSSL_strcasecmp(pct_type, "random") == 0)
key->prov_flags |= ML_KEM_KEY_RANDOM_PCT;
else if (OPENSSL_strcasecmp(pct_type, "fixed") == 0)
key->prov_flags |= ML_KEM_KEY_FIXED_PCT;
else
key->prov_flags &= ~ML_KEM_KEY_PCT_TYPE;
}
return key;
}
@ -200,7 +222,7 @@ static int ml_kem_validate(const void *vkey, int selection, int check_type)
return 0;
if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == OSSL_KEYMGMT_SELECT_KEYPAIR)
return ml_kem_pairwise_test(key);
return ml_kem_pairwise_test(key, ML_KEM_KEY_RANDOM_PCT);
return 1;
}
@ -412,7 +434,8 @@ static int ml_kem_key_fromdata(ML_KEM_KEY *key,
}
}
if (seedlen != 0 && (prvlen == 0 || key->prefer_seed)) {
if (seedlen != 0
&& (prvlen == 0 || (key->prov_flags & ML_KEM_KEY_PREFER_SEED))) {
if (prvlen != 0 && !check_seed(seedenc, prvenc, key))
return 0;
if (!ossl_ml_kem_set_seed(seedenc, seedlen, key)
@ -439,7 +462,8 @@ static int ml_kem_import(void *vkey, int selection, const OSSL_PARAM params[])
include_private = selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY ? 1 : 0;
res = ml_kem_key_fromdata(key, params, include_private);
if (res > 0 && include_private && !ml_kem_pairwise_test(key)) {
if (res > 0 && include_private
&& !ml_kem_pairwise_test(key, key->prov_flags)) {
#ifdef FIPS_MODULE
ossl_set_error_state(OSSL_SELF_TEST_TYPE_PCT);
#endif
@ -488,8 +512,9 @@ void *ml_kem_load(const void *reference, size_t reference_sz)
&& !check_seed(seed, encoded_dk, key))
goto err;
/* Generate the key now, if it holds only a stashed seed. */
if (ossl_ml_kem_have_seed(key) &&
(encoded_dk == NULL || key->prefer_seed)) {
if (ossl_ml_kem_have_seed(key)
&& (encoded_dk == NULL
|| (key->prov_flags & ML_KEM_KEY_PREFER_SEED))) {
if (!ossl_ml_kem_genkey(NULL, 0, key)
|| (encoded_dk != NULL && !check_pkhash(encoded_dk, key)))
goto err;
@ -501,7 +526,7 @@ void *ml_kem_load(const void *reference, size_t reference_sz)
key->vinfo->algorithm_name);
goto err;
}
if (!ml_kem_pairwise_test(key))
if (!ml_kem_pairwise_test(key, key->prov_flags))
goto err;
}
OPENSSL_free(encoded_dk);
@ -724,7 +749,7 @@ static void *ml_kem_gen(void *vgctx, OSSL_CALLBACK *osslcb, void *cbarg)
OSSL_KEYMGMT_SELECT_PUBLIC_KEY)
return NULL;
seed = gctx->seed;
key = ml_kem_new(gctx->provctx, gctx->propq, gctx->evp_type);
key = ossl_prov_ml_kem_new(gctx->provctx, gctx->propq, gctx->evp_type);
if (key == NULL)
return NULL;
@ -742,7 +767,7 @@ static void *ml_kem_gen(void *vgctx, OSSL_CALLBACK *osslcb, void *cbarg)
if (genok) {
#ifdef FIPS_MODULE
if (!ml_kem_pairwise_test(key)) {
if (!ml_kem_pairwise_test(key, ML_KEM_KEY_FIXED_PCT)) {
ossl_set_error_state(OSSL_SELF_TEST_TYPE_PCT);
ossl_ml_kem_key_free(key);
return NULL;
@ -785,7 +810,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, NULL, EVP_PKEY_ML_KEM_##bits); \
return ossl_prov_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[]) \

View file

@ -25,7 +25,7 @@ my @formats = qw(seed-priv priv-only seed-only oqskeypair bare-seed bare-priv);
plan skip_all => "ML-KEM isn't supported in this build"
if disabled("ml-kem");
plan tests => @algs * (24 + 10 * @formats);
plan tests => @algs * (25 + 10 * @formats);
my $seed = join ("", map {sprintf "%02x", $_} (0..63));
my $weed = join ("", map {sprintf "%02x", $_} (1..64));
my $ikme = join ("", map {sprintf "%02x", $_} (0..31));
@ -160,7 +160,7 @@ foreach my $alg (@algs) {
sprintf("text form private key: %s with %s", $alg, $f));
}
# (5 tests): Test import/load PCT failure
# (6 tests): Test import/load PCT failure
my $real = sprintf('real-%s.der', $alg);
my $fake = sprintf('fake-%s.der', $alg);
my $mixt = sprintf('mixt-%s.der', $alg);
@ -181,23 +181,38 @@ foreach my $alg (@algs) {
local $/ = undef;
my $realder = <$realfh>;
my $fakeder = <$fakefh>;
ok (length($realder) == 28 + (2 + 64) + (4 + $slen + $plen + $zlen)
#
# - 20 bytes PKCS8 fixed overhead,
# - 4 byte private key octet string tag + length
# - 4 byte seed + key sequence tag + length
# - 2 byte seed tag + length
# - 64 byte seed
# - 4 byte key tag + length
# - |dk| 's' vector
# - |ek| public key ('t' vector || 'rho')
# - implicit rejection 'z' seed component
#
ok(length($realder) == 28 + (2 + 64) + (4 + $slen + $plen + $zlen)
&& length($fakeder) == 28 + (2 + 64) + (4 + $slen + $plen + $zlen));
my $mixtder = substr($realder, 0, 28 + 66 + 4 + $slen)
. substr($fakeder, 28 + 66 + 4 + $slen, $plen)
. substr($realder, 28 + 66 + 4 + $slen + $plen, $zlen);
my $mixtfh = IO::File->new($mixt, "w");
my $mixtfh = IO::File->new($mixt, ">:raw");
print $mixtfh $mixtder;
$mixtfh->close();
ok(run(app([qw(openssl pkey -inform DER -noout -in), $real],
sprintf("accept valid keypair: %s", $alg))));
ok(run(app([qw(openssl pkey -inform DER -noout -in), $real])),
sprintf("accept valid keypair: %s", $alg));
ok(!run(app([qw(openssl pkey -provparam ml-kem.prefer_seed=no),
qw(-inform DER -noout -in), $mixt])),
sprintf("reject real private and fake public: %s", $alg));
ok(run(app([qw(openssl pkey -provparam ml-kem.prefer_seed=no),
qw(-provparam ml-kem.import_pct_type=none),
qw(-inform DER -noout -in), $mixt])),
sprintf("Absent PCT accept fake public: %s", $alg));
# Mutate the public key hash
my $mashder = $realder;
substr($mashder, -64, 1) =~ s{(.)}{chr(ord($1)^1)}es;
my $mashfh = IO::File->new($mash, "w");
my $mashfh = IO::File->new($mash, ">:raw");
print $mashfh $mashder;
$mashfh->close();
ok(!run(app([qw(openssl pkey -inform DER -noout -in), $mash])),

View file

@ -423,6 +423,7 @@ my %params = (
'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",
'PKEY_PARAM_ML_KEM_IMPORT_PCT_TYPE' => "ml-kem.import_pct_type",
# Key generation parameters
'PKEY_PARAM_FFC_TYPE' => "type",