Commit e0b716092d for openssl.org
commit e0b716092d83adb21524c10af4254c043c2a47c7
Author: Viktor Dukhovni <viktor@openssl.org>
Date: Mon Aug 3 17:27:09 2026 +1000
Additional ML-KEM cleansing
Also, mark X448MLKEM1024 key management as FIPS non-approved (completion of #26220)
Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Bob Beck <beck@openssl.org>
Reviewed-by: Milan Broz <mbroz@openssl.org>
MergeDate: Tue Aug 11 06:57:33 2026
(Merged from https://github.com/openssl/openssl/pull/32148)
diff --git a/crypto/ml_kem/ml_kem.c b/crypto/ml_kem/ml_kem.c
index 89960dc105..60d3fd2af6 100644
--- a/crypto/ml_kem/ml_kem.c
+++ b/crypto/ml_kem/ml_kem.c
@@ -985,6 +985,12 @@ static __owur int matrix_expand(EVP_MD_CTX *mdctx, ML_KEM_KEY *key)
int rank = key->vinfo->rank;
int i, j;
+ /*
+ * The seeds derived below and the sampling buffers in sample_scalar()
+ * are not cleansed: per FIPS 203 section 3.3 the matrix A is easily
+ * computed from the public encapsulation key and does not require any
+ * special protections.
+ */
memcpy(input, key->rho, ML_KEM_RANDOM_BYTES);
for (i = 0; i < rank; i++) {
for (j = 0; j < rank; j++) {
@@ -1015,8 +1021,10 @@ static __owur int cbd_2(scalar *out, uint8_t in[ML_KEM_RANDOM_BYTES + 1],
uint16_t value, mask;
uint8_t b;
- if (!prf(randbuf, sizeof(randbuf), in, mdctx, key))
+ if (!prf(randbuf, sizeof(randbuf), in, mdctx, key)) {
+ OPENSSL_cleanse((void *)randbuf, sizeof(randbuf));
return 0;
+ }
do {
b = *r++;
@@ -1038,6 +1046,8 @@ static __owur int cbd_2(scalar *out, uint8_t in[ML_KEM_RANDOM_BYTES + 1],
mask = constish_time_true(value >> 15);
*curr++ = value + (kPrime & mask);
} while (curr < end);
+
+ OPENSSL_cleanse((void *)randbuf, sizeof(randbuf));
return 1;
}
@@ -1055,8 +1065,10 @@ static __owur int cbd_3(scalar *out, uint8_t in[ML_KEM_RANDOM_BYTES + 1],
uint8_t b1, b2, b3;
uint16_t value, mask;
- if (!prf(randbuf, sizeof(randbuf), in, mdctx, key))
+ if (!prf(randbuf, sizeof(randbuf), in, mdctx, key)) {
+ OPENSSL_cleanse((void *)randbuf, sizeof(randbuf));
return 0;
+ }
do {
b1 = *r++;
@@ -1090,6 +1102,8 @@ static __owur int cbd_3(scalar *out, uint8_t in[ML_KEM_RANDOM_BYTES + 1],
mask = constish_time_true(value >> 15);
*curr++ = value + (kPrime & mask);
} while (curr < end);
+
+ OPENSSL_cleanse((void *)randbuf, sizeof(randbuf));
return 1;
}
@@ -1102,14 +1116,19 @@ static __owur int gencbd_vector(scalar *out, CBD_FUNC cbd, uint8_t *counter,
EVP_MD_CTX *mdctx, const ML_KEM_KEY *key)
{
uint8_t input[ML_KEM_RANDOM_BYTES + 1];
+ int ret = 0;
memcpy(input, seed, ML_KEM_RANDOM_BYTES);
do {
input[ML_KEM_RANDOM_BYTES] = (*counter)++;
if (!cbd(out++, input, mdctx, key))
- return 0;
+ goto end;
} while (--rank > 0);
- return 1;
+ ret = 1;
+
+end:
+ OPENSSL_cleanse((void *)input, sizeof(input));
+ return ret;
}
/*
@@ -1120,15 +1139,20 @@ static __owur int gencbd_vector_ntt(scalar *out, CBD_FUNC cbd, uint8_t *counter,
EVP_MD_CTX *mdctx, const ML_KEM_KEY *key)
{
uint8_t input[ML_KEM_RANDOM_BYTES + 1];
+ int ret = 0;
memcpy(input, seed, ML_KEM_RANDOM_BYTES);
do {
input[ML_KEM_RANDOM_BYTES] = (*counter)++;
if (!cbd(out, input, mdctx, key))
- return 0;
+ goto end;
scalar_ntt(out++);
} while (--rank > 0);
- return 1;
+ ret = 1;
+
+end:
+ OPENSSL_cleanse((void *)input, sizeof(input));
+ return ret;
}
/* The |ETA1| value for ML-KEM-512 is 3, the rest and all ETA2 values are 2. */
@@ -1167,10 +1191,11 @@ static __owur int encrypt_cpa(uint8_t out[ML_KEM_SHARED_SECRET_BYTES],
uint8_t counter = 0;
int du = vinfo->du;
int dv = vinfo->dv;
+ int ret = 0;
/* FIPS 203 "y" vector */
if (!gencbd_vector_ntt(y, cbd_1, &counter, r, rank, mdctx, key))
- return 0;
+ goto end;
/* FIPS 203 "v" scalar */
inner_product(&v, key->t, y, rank);
scalar_inverse_ntt(&v);
@@ -1179,7 +1204,7 @@ static __owur int encrypt_cpa(uint8_t out[ML_KEM_SHARED_SECRET_BYTES],
/* All done with |y|, now free to reuse tmp[0] for FIPS 203 |e1| */
if (!gencbd_vector(e1, cbd_2, &counter, r, rank, mdctx, key))
- return 0;
+ goto end;
vector_add(u, e1, rank);
vector_compress(u, du, rank);
vector_encode(out, u, du, rank);
@@ -1188,14 +1213,19 @@ static __owur int encrypt_cpa(uint8_t out[ML_KEM_SHARED_SECRET_BYTES],
memcpy(input, r, ML_KEM_RANDOM_BYTES);
input[ML_KEM_RANDOM_BYTES] = counter;
if (!cbd_2(e2, input, mdctx, key))
- return 0;
+ goto end;
scalar_add(&v, e2);
/* Combine message with |v| */
scalar_decode_decompress_add(&v, message);
scalar_compress(&v, dv);
scalar_encode(out + vinfo->u_vector_bytes, &v, dv);
- return 1;
+ ret = 1;
+
+end:
+ OPENSSL_cleanse((void *)input, sizeof(input));
+ OPENSSL_cleanse((void *)&v, sizeof(v));
+ return ret;
}
/*
@@ -1219,6 +1249,9 @@ decrypt_cpa(uint8_t out[ML_KEM_SHARED_SECRET_BYTES],
scalar_sub(&v, &mask);
scalar_compress(&v, 1);
scalar_encode_1(out, &v);
+
+ OPENSSL_cleanse((void *)&v, sizeof(v));
+ OPENSSL_cleanse((void *)&mask, sizeof(mask));
}
/*-
@@ -1407,8 +1440,8 @@ static __owur int genkey(const uint8_t seed[ML_KEM_SEED_BYTES],
ret = 1;
end:
- OPENSSL_cleanse((void *)augmented_seed, ML_KEM_RANDOM_BYTES);
- OPENSSL_cleanse((void *)sigma, ML_KEM_RANDOM_BYTES);
+ OPENSSL_cleanse((void *)augmented_seed, sizeof(augmented_seed));
+ OPENSSL_cleanse((void *)hashed, sizeof(hashed));
if (ret == 0) {
ERR_raise_data(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR,
"internal error while generating %s private key",
@@ -1446,6 +1479,7 @@ static int encap(uint8_t *ctext, uint8_t secret[ML_KEM_SHARED_SECRET_BYTES],
ERR_raise_data(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR,
"internal error while performing %s encapsulation",
key->vinfo->algorithm_name);
+ OPENSSL_cleanse((void *)Kr, sizeof(Kr));
return ret;
}
@@ -1510,7 +1544,7 @@ static int decap(uint8_t secret[ML_KEM_SHARED_SECRET_BYTES],
ERR_raise_data(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR,
"internal error while performing %s decapsulation",
vinfo->algorithm_name);
- return 0;
+ goto end;
}
decrypt_cpa(m, ctext, tmp, key);
if (!hash_kr(Kr, m, mdctx, key)
@@ -1930,6 +1964,9 @@ int ossl_ml_kem_genkey(uint8_t *pubenc, size_t publen, ML_KEM_KEY *key)
EVP_MD_CTX_free(mdctx);
if (!ret) {
+ /* Erase any partial public key output */
+ if (pubenc != NULL)
+ OPENSSL_cleanse(pubenc, vinfo->pubkey_bytes);
ossl_ml_kem_key_reset(key);
return 0;
}
@@ -1993,6 +2030,10 @@ int ossl_ml_kem_encap_seed(uint8_t *ctext, size_t clen,
}
#undef case_encap_seed
+ /* Erase any partial ciphertext output on failure */
+ if (!ret)
+ OPENSSL_cleanse(ctext, clen);
+
/* Declassify secret inputs and derived outputs before returning control */
CONSTTIME_DECLASSIFY(entropy, elen);
CONSTTIME_DECLASSIFY(ctext, clen);
@@ -2007,6 +2048,7 @@ int ossl_ml_kem_encap_rand(uint8_t *ctext, size_t clen,
const ML_KEM_KEY *key)
{
uint8_t r[ML_KEM_RANDOM_BYTES];
+ int ret;
if (key == NULL)
return 0;
@@ -2016,8 +2058,11 @@ int ossl_ml_kem_encap_rand(uint8_t *ctext, size_t clen,
< 1)
return 0;
- return ossl_ml_kem_encap_seed(ctext, clen, shared_secret, slen,
+ ret = ossl_ml_kem_encap_seed(ctext, clen, shared_secret, slen,
r, sizeof(r), key);
+
+ OPENSSL_cleanse((void *)r, sizeof(r));
+ return ret;
}
int ossl_ml_kem_decap(uint8_t *shared_secret, size_t slen,
@@ -2067,6 +2112,7 @@ int ossl_ml_kem_decap(uint8_t *shared_secret, size_t slen,
\
ret = decap(shared_secret, ctext, cbuf, tmp, mdctx, key); \
OPENSSL_cleanse((void *)tmp, sizeof(tmp)); \
+ OPENSSL_cleanse((void *)cbuf, sizeof(cbuf)); \
}
switch (vinfo->evp_type) {
case EVP_PKEY_ML_KEM_512:
diff --git a/providers/fips/fipsprov.c b/providers/fips/fipsprov.c
index 5f331920ba..b7a4f83217 100644
--- a/providers/fips/fipsprov.c
+++ b/providers/fips/fipsprov.c
@@ -657,7 +657,7 @@ static const OSSL_ALGORITHM fips_keymgmt[] = {
#if !defined(OPENSSL_NO_ECX)
{ PROV_NAMES_X25519MLKEM768, FIPS_DEFAULT_PROPERTIES, ossl_mlx_x25519_kem_kmgmt_functions,
PROV_DESCS_X25519MLKEM768 },
- { PROV_NAMES_X448MLKEM1024, FIPS_DEFAULT_PROPERTIES, ossl_mlx_x448_kem_kmgmt_functions,
+ { PROV_NAMES_X448MLKEM1024, FIPS_UNAPPROVED_PROPERTIES, ossl_mlx_x448_kem_kmgmt_functions,
PROV_DESCS_X448MLKEM1024 },
#endif
#if !defined(OPENSSL_NO_EC)
diff --git a/providers/implementations/kem/ml_kem_kem.c b/providers/implementations/kem/ml_kem_kem.c
index df3bcb5cd8..456b15db3a 100644
--- a/providers/implementations/kem/ml_kem_kem.c
+++ b/providers/implementations/kem/ml_kem_kem.c
@@ -128,6 +128,7 @@ static int ml_kem_set_ctx_params(void *vctx, const OSSL_PARAM params[])
/* Possibly, but much less likely wrong type */
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH);
+ OPENSSL_cleanse((void *)ctx->entropy_buf, sizeof(ctx->entropy_buf));
ctx->entropy = NULL;
return 0;
}
diff --git a/providers/implementations/kem/mlx_kem.c b/providers/implementations/kem/mlx_kem.c
index 376b3342dd..fb0a50f753 100644
--- a/providers/implementations/kem/mlx_kem.c
+++ b/providers/implementations/kem/mlx_kem.c
@@ -118,7 +118,7 @@ static int mlx_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen,
if (!mlx_kem_have_pubkey(key)) {
ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
- goto end;
+ return 0;
}
encap_clen = key->minfo->ctext_bytes + key->xinfo->pubkey_bytes;
encap_slen = ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes;
@@ -236,6 +236,10 @@ static int mlx_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen,
ret = 1;
end:
+ /* Erase any partial shared secret on failure */
+ if (ret == 0)
+ OPENSSL_cleanse(shsec,
+ ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes);
EVP_PKEY_free(xkey);
EVP_PKEY_CTX_free(ctx);
return ret;
@@ -324,6 +328,10 @@ static int mlx_kem_decapsulate(void *vctx, uint8_t *shsec, size_t *slen,
ret = 1;
end:
+ /* Erase any partial shared secret on failure */
+ if (ret == 0)
+ OPENSSL_cleanse(shsec,
+ ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes);
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(xkey);
return ret;
diff --git a/providers/implementations/keymgmt/ml_kem_kmgmt.c b/providers/implementations/keymgmt/ml_kem_kmgmt.c
index d7f2d87685..9101b6ad5d 100644
--- a/providers/implementations/keymgmt/ml_kem_kmgmt.c
+++ b/providers/implementations/keymgmt/ml_kem_kmgmt.c
@@ -118,10 +118,6 @@ static int ml_kem_pairwise_test(const ML_KEM_KEY *key, int key_flags)
memset(out, 0, sizeof(out));
- /*
- * 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);
@@ -156,7 +152,10 @@ err:
v->algorithm_name);
}
#endif
- OPENSSL_free(ctext);
+ OPENSSL_cleanse((void *)entropy, sizeof(entropy));
+ OPENSSL_cleanse((void *)secret, sizeof(secret));
+ OPENSSL_cleanse((void *)out, sizeof(out));
+ OPENSSL_clear_free(ctext, v->ctext_bytes);
return ret;
}
@@ -338,7 +337,7 @@ err:
OSSL_PARAM_BLD_free(tmpl);
OPENSSL_secure_clear_free(seedenc, seedlen);
OPENSSL_secure_clear_free(prvenc, prvlen);
- OPENSSL_free(pubenc);
+ OPENSSL_clear_free(pubenc, v->pubkey_bytes);
return ret;
}
@@ -549,12 +548,14 @@ static void *ml_kem_load(const void *reference, size_t reference_sz)
goto err;
}
OPENSSL_secure_clear_free(encoded_dk, key->vinfo->prvkey_bytes);
+ OPENSSL_cleanse((void *)seed, sizeof(seed));
return key;
}
err:
if (key != NULL && key->vinfo != NULL)
OPENSSL_secure_clear_free(encoded_dk, key->vinfo->prvkey_bytes);
+ OPENSSL_cleanse((void *)seed, sizeof(seed));
ossl_ml_kem_key_free(key);
return NULL;
}
@@ -722,6 +723,7 @@ static int ml_kem_gen_set_params(void *vgctx, const OSSL_PARAM params[])
/* Possibly, but less likely wrong data type */
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH);
+ OPENSSL_cleanse((void *)gctx->seedbuf, sizeof(gctx->seedbuf));
gctx->seed = NULL;
return 0;
}
@@ -778,8 +780,10 @@ static void *ml_kem_gen(void *vgctx, OSSL_CALLBACK *osslcb, void *cbarg)
if ((gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0)
return key;
- if (seed != NULL && !ossl_ml_kem_set_seed(seed, ML_KEM_SEED_BYTES, key))
+ if (seed != NULL && !ossl_ml_kem_set_seed(seed, ML_KEM_SEED_BYTES, key)) {
+ ossl_ml_kem_key_free(key);
return NULL;
+ }
genok = ossl_ml_kem_genkey(nopub, 0, key);
/* Erase the single-use seed */
diff --git a/providers/implementations/keymgmt/mlx_kmgmt.c b/providers/implementations/keymgmt/mlx_kmgmt.c
index 00ac258682..3621688326 100644
--- a/providers/implementations/keymgmt/mlx_kmgmt.c
+++ b/providers/implementations/keymgmt/mlx_kmgmt.c
@@ -320,7 +320,7 @@ static int mlx_kem_export(void *vkey, int selection, OSSL_CALLBACK *param_cb,
err:
OSSL_PARAM_BLD_free(tmpl);
OPENSSL_secure_clear_free(sub_arg.prvenc, prvlen);
- OPENSSL_free(sub_arg.pubenc);
+ OPENSSL_clear_free(sub_arg.pubenc, publen);
return ret;
}
@@ -564,12 +564,18 @@ static int mlx_kem_get_params(void *vkey, OSSL_PARAM params[])
selection |= OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS;
/* Extract sub-component key material */
- if (!export_sub(&sub_arg, selection, key))
- return 0;
-
- if ((pub != NULL && sub_arg.pubcount != 2)
- || (prv != NULL && sub_arg.prvcount != 2))
+ if (!export_sub(&sub_arg, selection, key)
+ || (pub != NULL && sub_arg.pubcount != 2)
+ || (prv != NULL && sub_arg.prvcount != 2)) {
+ /* Erase any partial key material on failure */
+ if (sub_arg.pubenc != NULL)
+ OPENSSL_cleanse(sub_arg.pubenc,
+ key->minfo->pubkey_bytes + key->xinfo->pubkey_bytes);
+ if (sub_arg.prvenc != NULL)
+ OPENSSL_cleanse(sub_arg.prvenc,
+ key->minfo->prvkey_bytes + key->xinfo->prvkey_bytes);
return 0;
+ }
return 1;
}