Commit 63c17a531c for openssl.org

commit 63c17a531c7cd7f6aae0ebef4eb318505a4af87e
Author: Viktor Dukhovni <viktor@openssl.org>
Date:   Mon Aug 3 19:28:48 2026 +1000

    Additional ML-DSA cleansing

    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:34 2026
    (Merged from https://github.com/openssl/openssl/pull/32148)

diff --git a/crypto/ml_dsa/ml_dsa_encoders.c b/crypto/ml_dsa/ml_dsa_encoders.c
index de93f404fa..25636c18c8 100644
--- a/crypto/ml_dsa/ml_dsa_encoders.c
+++ b/crypto/ml_dsa/ml_dsa_encoders.c
@@ -935,6 +935,9 @@ int ossl_ml_dsa_sig_encode(const ML_DSA_SIG *sig, const ML_DSA_PARAMS *params,
     ret = 1;
 err:
     WPACKET_finish(&pkt);
+    /* Erase any partial signature output on failure */
+    if (ret == 0)
+        OPENSSL_cleanse(out, params->sig_len);
     return ret;
 }

diff --git a/crypto/ml_dsa/ml_dsa_key.c b/crypto/ml_dsa/ml_dsa_key.c
index f1cc5e694a..96db3a730c 100644
--- a/crypto/ml_dsa/ml_dsa_key.c
+++ b/crypto/ml_dsa/ml_dsa_key.c
@@ -365,10 +365,15 @@ static int public_from_private(const ML_DSA_KEY *key, EVP_MD_CTX *md_ctx,
     /* Compress t */
     vector_power2_round(&t, t1, t0);

-    /* Zeroize secret */
-    vector_zero(&s1_ntt);
     ret = 1;
 err:
+    /*
+     * The low bits of |t| are private and |s1_ntt| is secret, wipe both.
+     * The trailing |a_ntt| matrix is not wiped: per FIPS 204 section 3.6.3
+     * the matrix A is easily computed from the public key and does not
+     * require any special protections.
+     */
+    OPENSSL_cleanse(polys, (k + l) * sizeof(*polys));
     OPENSSL_free(polys);
     return ret;
 }
@@ -390,6 +395,7 @@ int ossl_ml_dsa_key_public_from_private(ML_DSA_KEY *key)
         && shake_xof(md_ctx, key->shake256_md,
             key->pub_encoding, key->params->pk_len,
             key->tr, sizeof(key->tr));
+    vector_zero(&t0);
     vector_free(&t0);
     EVP_MD_CTX_free(md_ctx);
     return ret;
@@ -422,7 +428,7 @@ int ossl_ml_dsa_key_pairwise_check(const ML_DSA_KEY *key)
     ret = vector_equal(&t1, &key->t1) && vector_equal(&t0, &key->t0);
 err:
     EVP_MD_CTX_free(md_ctx);
-    OPENSSL_free(polys);
+    OPENSSL_clear_free(polys, 2 * k * sizeof(*polys));
     return ret;
 }

diff --git a/crypto/ml_dsa/ml_dsa_matrix.c b/crypto/ml_dsa/ml_dsa_matrix.c
index c7ff598452..5a9811df12 100644
--- a/crypto/ml_dsa/ml_dsa_matrix.c
+++ b/crypto/ml_dsa/ml_dsa_matrix.c
@@ -7,6 +7,7 @@
  * https://www.openssl.org/source/license.html
  */

+#include <openssl/crypto.h>
 #include "ml_dsa_local.h"
 #include "ml_dsa_vector.h"
 #include "ml_dsa_matrix.h"
@@ -25,15 +26,16 @@ void ossl_ml_dsa_matrix_mult_vector(const MATRIX *a, const VECTOR *s,
 {
     size_t i, j;
     POLY *poly = a->m_poly;
+    POLY product;

     vector_zero(t);

     for (i = 0; i < a->k; i++) {
         for (j = 0; j < a->l; j++) {
-            POLY product;
-
             ossl_ml_dsa_poly_ntt_mult(poly++, &s->poly[j], &product);
             poly_add(&product, &t->poly[i], &t->poly[i]);
         }
     }
+
+    OPENSSL_cleanse(&product, sizeof(product));
 }
diff --git a/crypto/ml_dsa/ml_dsa_sample.c b/crypto/ml_dsa/ml_dsa_sample.c
index afa09b7971..169ae4ec52 100644
--- a/crypto/ml_dsa/ml_dsa_sample.c
+++ b/crypto/ml_dsa/ml_dsa_sample.c
@@ -219,6 +219,13 @@ static int matrix_expand_A_scalar(EVP_MD_CTX *g_ctx, const EVP_MD *md,
     uint8_t derived_seed[ML_DSA_RHO_BYTES + 2];
     POLY *poly = out->m_poly;

+    /*
+     * The seeds derived below and the sampling buffers in rej_ntt_poly() are
+     * not cleansed: per FIPS 204 section 3.6.3 the matrix A is easily
+     * computed from the public key and does not require any special
+     * protections.
+     */
+
     /* The seed used for each matrix element is rho + column_index + row_index */
     memcpy(derived_seed, rho, ML_DSA_RHO_BYTES);
     for (i = 0; i < out->k; i++) {
@@ -327,13 +334,14 @@ int ossl_ml_dsa_poly_sample_in_ball(POLY *out_c, const uint8_t *seed, int seed_l
     uint64_t signs;
     int offset = 8;
     size_t end;
+    int ret = 0;

     /*
      * Rather than squeeze 8 bytes followed by lots of 1 byte squeezes
      * the SHAKE blocksize is squeezed each time and buffered into 'block'.
      */
     if (!shake_xof(h_ctx, md, seed, seed_len, block, sizeof(block)))
-        return 0;
+        goto err;

     /*
      * grab the first 64 bits - since tau < 64
@@ -369,7 +377,7 @@ int ossl_ml_dsa_poly_sample_in_ball(POLY *out_c, const uint8_t *seed, int seed_l
             if (offset == sizeof(block)) {
                 /* squeeze another block if the bytes from block have been used */
                 if (!EVP_DigestSqueeze(h_ctx, block, sizeof(block)))
-                    return 0;
+                    goto err;
                 /* See comment above for why the block is declassified. */
                 CONSTTIME_DECLASSIFY(block, sizeof(block));
                 offset = 0;
@@ -389,7 +397,10 @@ int ossl_ml_dsa_poly_sample_in_ball(POLY *out_c, const uint8_t *seed, int seed_l
         out_c->coeff[index] = mod_sub(1, 2 * (signs & 1));
         signs >>= 1; /* grab the next random bit */
     }
-    return 1;
+    ret = 1;
+err:
+    OPENSSL_cleanse(block, sizeof(block));
+    return ret;
 }

 static void vector_expand_mask_scalar(VECTOR *out,
diff --git a/crypto/ml_dsa/ml_dsa_sample_hw_x86_64.inc b/crypto/ml_dsa/ml_dsa_sample_hw_x86_64.inc
index fcf5f03323..1fb8b2b64e 100644
--- a/crypto/ml_dsa/ml_dsa_sample_hw_x86_64.inc
+++ b/crypto/ml_dsa/ml_dsa_sample_hw_x86_64.inc
@@ -199,13 +199,20 @@ static int matrix_expand_A_mb(EVP_MD_CTX *g_ctx, const EVP_MD *md,
     POLY *polys[ML_DSA_SHAKE_X4_BATCH_SIZE];
     POLY *poly = out->m_poly;

+    /*
+     * The seeds derived below and the sampling buffers in rej_ntt_poly_mb()
+     * are not cleansed: per FIPS 204 section 3.6.3 the matrix A is easily
+     * computed from the public key and does not require any special
+     * protections.
+     */
+
     for (b = 0; b < ML_DSA_SHAKE_X4_BATCH_SIZE; b++) {
         memcpy(derived_seeds[b], rho, ML_DSA_RHO_BYTES);
         seeds[b] = derived_seeds[b];
     }

     for (idx = 0; (idx + ML_DSA_SHAKE_X4_BATCH_SIZE - 1) < (out->k * out->l);
-         idx += ML_DSA_SHAKE_X4_BATCH_SIZE) {
+        idx += ML_DSA_SHAKE_X4_BATCH_SIZE) {
         for (b = 0; b < ML_DSA_SHAKE_X4_BATCH_SIZE; b++) {
             const size_t row = (idx + b) / out->l;
             const size_t col = (idx + b) % out->l;
diff --git a/crypto/ml_dsa/ml_dsa_sign.c b/crypto/ml_dsa/ml_dsa_sign.c
index 62dfd08d53..bcff47dbc2 100644
--- a/crypto/ml_dsa/ml_dsa_sign.c
+++ b/crypto/ml_dsa/ml_dsa_sign.c
@@ -360,6 +360,7 @@ err:
     if (w1_encoded != NULL)
         OPENSSL_clear_free(w1_encoded, w1_encoded_len);
     OPENSSL_cleanse(rho_prime, sizeof(rho_prime));
+    OPENSSL_cleanse(c_tilde, sizeof(c_tilde));
     /*
      * Declassify the private key material before returning.  The key struct
      * is not owned here, so we do not free it, but we must remove the
@@ -540,6 +541,7 @@ int ossl_ml_dsa_sign(const ML_DSA_KEY *priv,

 err:
     EVP_MD_CTX_free(md_ctx);
+    OPENSSL_cleanse(mu, sizeof(mu));
     return ret;
 }

@@ -579,5 +581,6 @@ int ossl_ml_dsa_verify(const ML_DSA_KEY *pub,
     ret = ml_dsa_verify_internal(pub, mu_ptr, mu_len, sig, sig_len);
 err:
     EVP_MD_CTX_free(md_ctx);
+    OPENSSL_cleanse(mu, sizeof(mu));
     return ret;
 }
diff --git a/providers/implementations/digests/ml_dsa_mu_prov.c b/providers/implementations/digests/ml_dsa_mu_prov.c
index e2857c056d..0f1866ed0c 100644
--- a/providers/implementations/digests/ml_dsa_mu_prov.c
+++ b/providers/implementations/digests/ml_dsa_mu_prov.c
@@ -83,7 +83,7 @@ static void mu_freectx(void *vctx)
     OPENSSL_free(ctx->propq);
     EVP_MD_free(ctx->md);
     EVP_MD_CTX_free(ctx->mdctx);
-    OPENSSL_free(ctx);
+    OPENSSL_clear_free(ctx, sizeof(*ctx));
 }

 static void *mu_dupctx(void *ctx)
diff --git a/providers/implementations/keymgmt/ml_dsa_kmgmt.c b/providers/implementations/keymgmt/ml_dsa_kmgmt.c
index 24406ac602..218ef685b7 100644
--- a/providers/implementations/keymgmt/ml_dsa_kmgmt.c
+++ b/providers/implementations/keymgmt/ml_dsa_kmgmt.c
@@ -100,6 +100,7 @@ static int ml_dsa_pairwise_test(const ML_DSA_KEY *key)
 err:
     OSSL_SELF_TEST_onend(st, ret);
     OSSL_SELF_TEST_free(st);
+    OPENSSL_cleanse(sig, sizeof(sig));
     return ret;
 }
 #endif
@@ -565,7 +566,7 @@ static void ml_dsa_gen_cleanup(void *genctx)
     if (gctx == NULL)
         return;

-    OPENSSL_cleanse(gctx->entropy, gctx->entropy_len);
+    OPENSSL_cleanse(gctx->entropy, sizeof(gctx->entropy));
     OPENSSL_free(gctx->propq);
     OPENSSL_free(gctx);
 }
diff --git a/providers/implementations/signature/ml_dsa_sig.c b/providers/implementations/signature/ml_dsa_sig.c
index c661ab67b8..79d523ce17 100644
--- a/providers/implementations/signature/ml_dsa_sig.c
+++ b/providers/implementations/signature/ml_dsa_sig.c
@@ -280,14 +280,17 @@ static int ml_dsa_sign_msg_final(void *vctx, unsigned char *sig,
                 return 0;
         }

-        if (!ossl_ml_dsa_mu_finalize(ctx->md_ctx, mu, sizeof(mu)))
+        if (!ossl_ml_dsa_mu_finalize(ctx->md_ctx, mu, sizeof(mu))) {
+            OPENSSL_cleanse(mu, sizeof(mu));
             return 0;
+        }
     }

     ret = ossl_ml_dsa_sign(ctx->key, 1, mu, sizeof(mu), NULL, 0, rnd,
         sizeof(rand_tmp), 0, sig, siglen, sigsize);
     if (rnd != ctx->test_entropy)
         OPENSSL_cleanse(rand_tmp, sizeof(rand_tmp));
+    OPENSSL_cleanse(mu, sizeof(mu));
     return ret;
 }

@@ -338,6 +341,7 @@ static int ml_dsa_verify_msg_final(void *vctx)
 {
     PROV_ML_DSA_CTX *ctx = (PROV_ML_DSA_CTX *)vctx;
     uint8_t mu[ML_DSA_MU_BYTES];
+    int ret = 0;

     if (!ossl_prov_is_running())
         return 0;
@@ -345,11 +349,12 @@ static int ml_dsa_verify_msg_final(void *vctx)
     if (ctx->md_ctx == NULL)
         return 0;

-    if (!ossl_ml_dsa_mu_finalize(ctx->md_ctx, mu, sizeof(mu)))
-        return 0;
+    if (ossl_ml_dsa_mu_finalize(ctx->md_ctx, mu, sizeof(mu)))
+        ret = ossl_ml_dsa_verify(ctx->key, 1, mu, sizeof(mu), NULL, 0, 0,
+            ctx->sig, ctx->siglen);

-    return ossl_ml_dsa_verify(ctx->key, 1, mu, sizeof(mu), NULL, 0, 0,
-        ctx->sig, ctx->siglen);
+    OPENSSL_cleanse(mu, sizeof(mu));
+    return ret;
 }

 static int ml_dsa_verify(void *vctx, const uint8_t *sig, size_t siglen,