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,