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;
 }