From: Viktor Dukhovni Date: Fri, 14 Feb 2025 17:36:25 +0000 (+1100) Subject: Configurable import-time PCT for ML-KEM X-Git-Tag: openssl-3.5.0-alpha1~142 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cab4e7cbd14f97ee4c3e5b9f900cb599ee454ee5;p=thirdparty%2Fopenssl.git Configurable import-time PCT for ML-KEM And related cleanup. Reviewed-by: Shane Lontis Reviewed-by: Tim Hudson (Merged from https://github.com/openssl/openssl/pull/26789) --- diff --git a/crypto/ml_dsa/ml_dsa_key.c b/crypto/ml_dsa/ml_dsa_key.c index 05bf142d979..be041fc4e30 100644 --- a/crypto/ml_dsa/ml_dsa_key.c +++ b/crypto/ml_dsa/ml_dsa_key.c @@ -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; } diff --git a/crypto/ml_dsa/ml_dsa_key.h b/crypto/ml_dsa/ml_dsa_key.h index 10d61cb5451..5a495ce3c44 100644 --- a/crypto/ml_dsa/ml_dsa_key.h +++ b/crypto/ml_dsa/ml_dsa_key.h @@ -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 diff --git a/crypto/ml_kem/ml_kem.c b/crypto/ml_kem/ml_kem.c index a37951f4282..bace597c80e 100644 --- a/crypto/ml_kem/ml_kem.c +++ b/crypto/ml_kem/ml_kem.c @@ -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); diff --git a/doc/man7/EVP_PKEY-ML-KEM.pod b/doc/man7/EVP_PKEY-ML-KEM.pod index f1cbf9104d4..b83f5fb9052 100644 --- a/doc/man7/EVP_PKEY-ML-KEM.pod +++ b/doc/man7/EVP_PKEY-ML-KEM.pod @@ -108,6 +108,17 @@ configuration options programmatically. =over 4 +=item C (B) + +When an B key is imported as an explict FIPS 203 B 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, the PCT +is performed with a random entropy value for the encapsulation step. +Setting the parameter to C, still runs the test, but the encapsulation +entropy is a fixed 32 byte value. +Specifying any other value of the parameter, e.g. C, skips the test. + =item C (B) When set to a string representing a false boolean value (see diff --git a/include/crypto/ml_dsa.h b/include/crypto/ml_dsa.h index f66d93d06d3..fd21a408893 100644 --- a/include/crypto/ml_dsa.h +++ b/include/crypto/ml_dsa.h @@ -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); diff --git a/include/crypto/ml_kem.h b/include/crypto/ml_kem.h index d2df667f51c..e9915c10213 100644 --- a/include/crypto/ml_kem.h +++ b/include/crypto/ml_kem.h @@ -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 diff --git a/providers/implementations/encode_decode/ml_dsa_codecs.c b/providers/implementations/encode_decode/ml_dsa_codecs.c index a0944d8a11c..baa5a656fc2 100644 --- a/providers/implementations/encode_decode/ml_dsa_codecs.c +++ b/providers/implementations/encode_decode/ml_dsa_codecs.c @@ -14,6 +14,7 @@ #include #include #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; diff --git a/providers/implementations/encode_decode/ml_kem_codecs.c b/providers/implementations/encode_decode/ml_kem_codecs.c index 7a50210a7d2..d5b0fb5f951 100644 --- a/providers/implementations/encode_decode/ml_kem_codecs.c +++ b/providers/implementations/encode_decode/ml_kem_codecs.c @@ -13,6 +13,7 @@ #include #include #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)) { diff --git a/providers/implementations/include/prov/ml_dsa.h b/providers/implementations/include/prov/ml_dsa.h new file mode 100644 index 00000000000..0d65c7e539c --- /dev/null +++ b/providers/implementations/include/prov/ml_dsa.h @@ -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); diff --git a/providers/implementations/include/prov/ml_kem.h b/providers/implementations/include/prov/ml_kem.h new file mode 100644 index 00000000000..6e20492bc80 --- /dev/null +++ b/providers/implementations/include/prov/ml_kem.h @@ -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); diff --git a/providers/implementations/keymgmt/ml_dsa_kmgmt.c b/providers/implementations/keymgmt/ml_dsa_kmgmt.c index fc1000c3e1a..2c80d4f2b56 100644 --- a/providers/implementations/keymgmt/ml_dsa_kmgmt.c +++ b/providers/implementations/keymgmt/ml_dsa_kmgmt.c @@ -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; + + ossl_ml_dsa_key_free(key); + 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)\ { \ diff --git a/providers/implementations/keymgmt/ml_kem_kmgmt.c b/providers/implementations/keymgmt/ml_kem_kmgmt.c index a2fb4ad939a..5a007f3dc5e 100644 --- a/providers/implementations/keymgmt/ml_kem_kmgmt.c +++ b/providers/implementations/keymgmt/ml_kem_kmgmt.c @@ -17,11 +17,12 @@ #include #include #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. */ - 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 (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); + } 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[]) \ diff --git a/test/recipes/15-test_ml_kem_codecs.t b/test/recipes/15-test_ml_kem_codecs.t index 03e6c56a366..88e3d4deab0 100644 --- a/test/recipes/15-test_ml_kem_codecs.t +++ b/test/recipes/15-test_ml_kem_codecs.t @@ -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); @@ -171,35 +171,50 @@ foreach my $alg (@algs) { ok(run(app([qw(openssl genpkey -algorithm), "ml-kem-$alg", qw(-provparam ml-kem.output_formats=seed-priv -pkeyopt), "hexseed:$seed", qw(-outform DER -out), $real])), - sprintf("create real private key: %s", $alg)); + sprintf("create real private key: %s", $alg)); ok(run(app([qw(openssl genpkey -algorithm), "ml-kem-$alg", qw(-provparam ml-kem.output_formats=seed-priv -pkeyopt), "hexseed:$weed", qw(-outform DER -out), $fake])), - sprintf("create fake private key: %s", $alg)); + sprintf("create fake private key: %s", $alg)); my $realfh = IO::File->new($real, "r"); my $fakefh = IO::File->new($fake, "r"); 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)); + 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])), - sprintf("reject real private and mutated public: %s", $alg)); + sprintf("reject real private and mutated public: %s", $alg)); } diff --git a/util/perl/OpenSSL/paramnames.pm b/util/perl/OpenSSL/paramnames.pm index 91bff80e3fa..9d536f3f559 100644 --- a/util/perl/OpenSSL/paramnames.pm +++ b/util/perl/OpenSSL/paramnames.pm @@ -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",