]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Refactor ML-KEM decap, also cleanse failure_key
authorViktor Dukhovni <openssl-users@dukhovni.org>
Thu, 26 Mar 2026 17:02:34 +0000 (04:02 +1100)
committerViktor Dukhovni <openssl-users@dukhovni.org>
Tue, 31 Mar 2026 05:20:37 +0000 (16:20 +1100)
Pedantically cleanse the typically unused decap failure_key's stack
copy.

When actually used, it is copied into the caller's shared secret result,
perhaps to be cleansed there after use, or not, that's the callers
business.

While at it, slightly refactor the internal decap() implementation to
consolidate all the data to be cleansed into a single buffer, but now
avoid copying the public key hash, instead, when computing "K || r" as
"G(m || h)" include "h" via a separate EVP_DigestUpdate() call.

Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Tue Mar 31 05:19:40 2026
(Merged from https://github.com/openssl/openssl/pull/30598)

crypto/ml_kem/ml_kem.c

index babc2281c13158f825c20396b049de3a441bf953..9ea824ccffdf140da44957941b99f7d6b493fab1 100644 (file)
@@ -1448,6 +1448,30 @@ static int encap(uint8_t *ctext, uint8_t secret[ML_KEM_SHARED_SECRET_BYTES],
     return ret;
 }
 
+/*
+ * Hash the input message |m'| and public key digest |h|
+ * to obtain |K| and |r|.
+ */
+static int hash_kr(uint8_t *out, uint8_t *in,
+    EVP_MD_CTX *mdctx, const ML_KEM_KEY *key)
+{
+    unsigned int sz, wanted;
+
+    wanted = ML_KEM_SHARED_SECRET_BYTES + ML_KEM_RANDOM_BYTES;
+    return (EVP_DigestInit_ex(mdctx, key->sha3_512_md, NULL)
+        && EVP_DigestUpdate(mdctx, in, ML_KEM_RANDOM_BYTES)
+        && EVP_DigestUpdate(mdctx, key->pkhash, ML_KEM_PKHASH_BYTES)
+        && EVP_DigestFinal_ex(mdctx, out, &sz)
+        && ossl_assert(sz == wanted));
+}
+
+/*-
+ * Decap needs space for: Kbar | K | r | m'
+ * We slice up a single buffer to hold them all.
+ * We don't need to cleanse the public pkhash value.
+ */
+#define DECAP_BUFFER_SZ (2 * ML_KEM_SHARED_SECRET_BYTES + 2 * ML_KEM_RANDOM_BYTES)
+
 /*
  * FIPS 203, Section 6.3, Algorithm 18: ML-KEM.Decaps_internal
  *
@@ -1463,11 +1487,11 @@ static int decap(uint8_t secret[ML_KEM_SHARED_SECRET_BYTES],
     const uint8_t *ctext, uint8_t *tmp_ctext, scalar *tmp,
     EVP_MD_CTX *mdctx, const ML_KEM_KEY *key)
 {
-    uint8_t decrypted[ML_KEM_SHARED_SECRET_BYTES + ML_KEM_PKHASH_BYTES];
-    uint8_t failure_key[ML_KEM_RANDOM_BYTES];
-    uint8_t Kr[ML_KEM_SHARED_SECRET_BYTES + ML_KEM_RANDOM_BYTES];
+    uint8_t buf[DECAP_BUFFER_SZ];
+    uint8_t *failure_key = buf; /* Kbar */
+    uint8_t *Kr = failure_key + ML_KEM_SHARED_SECRET_BYTES;
     uint8_t *r = Kr + ML_KEM_SHARED_SECRET_BYTES;
-    const uint8_t *pkhash = key->pkhash;
+    uint8_t *m = r + ML_KEM_RANDOM_BYTES; /* m' */
     const ML_KEM_VINFO *vinfo = key->vinfo;
     int i;
     uint8_t mask;
@@ -1493,20 +1517,18 @@ static int decap(uint8_t secret[ML_KEM_SHARED_SECRET_BYTES],
             vinfo->algorithm_name);
         return 0;
     }
-    decrypt_cpa(decrypted, ctext, tmp, key);
-    memcpy(decrypted + ML_KEM_SHARED_SECRET_BYTES, pkhash, ML_KEM_PKHASH_BYTES);
-    if (!hash_g(Kr, decrypted, sizeof(decrypted), mdctx, key)
-        || !encrypt_cpa(tmp_ctext, decrypted, r, tmp, mdctx, key)) {
+    decrypt_cpa(m, ctext, tmp, key);
+    if (!hash_kr(Kr, m, mdctx, key)
+        || !encrypt_cpa(tmp_ctext, m, r, tmp, mdctx, key)) {
         memcpy(secret, failure_key, ML_KEM_SHARED_SECRET_BYTES);
-        OPENSSL_cleanse(decrypted, ML_KEM_SHARED_SECRET_BYTES);
-        return 1;
+        goto end;
     }
     mask = constant_time_eq_int_8(0,
         CRYPTO_memcmp(ctext, tmp_ctext, vinfo->ctext_bytes));
     for (i = 0; i < ML_KEM_SHARED_SECRET_BYTES; i++)
         secret[i] = constant_time_select_8(mask, Kr[i], failure_key[i]);
-    OPENSSL_cleanse(decrypted, ML_KEM_SHARED_SECRET_BYTES);
-    OPENSSL_cleanse(Kr, sizeof(Kr));
+end:
+    OPENSSL_cleanse(buf, DECAP_BUFFER_SZ);
     return 1;
 }