Commit 2e9244c778 for openssl.org

commit 2e9244c77879db4618ba24c0e9e771ebf07191a2
Author: Igor Ustinov <igus@openssl.foundation>
Date:   Fri Jul 31 17:18:41 2026 +0200

    sm2: make sm2_sig_gen() constant time

    After fixing CVE-2025-9231, sm2_sig_gen() still used variable-time
    BN_mod_mul() and BN_sub() on private key and nonce material.
    This commit makes it fully constant time.

    Fixes CVE-2026-77696

    Co-authored-by: Viktor Dukhovni <viktor@openssl.org>

    Assisted-by: Claude:claude-opus-4-8
    Reviewed-by: Alicja Kario <hkario@redhat.com>
    Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
    Merge-date: Tue Sep 29 11:23:54 2026

diff --git a/crypto/ec/ec_mult.c b/crypto/ec/ec_mult.c
index f78350b8df..19e35c339e 100644
--- a/crypto/ec/ec_mult.c
+++ b/crypto/ec/ec_mult.c
@@ -220,10 +220,9 @@ int ossl_ec_scalar_mul_ladder(const EC_GROUP *group, EC_POINT *r,
      * expected to arrive either BN_FLG_CONSTTIME or fixed-top, so that their
      * top is a public, value-independent width and the copy length does not
      * leak their magnitude.  ECDSA satisfies this via
-     * ossl_bn_priv_rand_range_fixed_top(); the generic SM2 signing path does
-     * not yet (see the sm2_sig_gen() hardening tracked separately).  The
-     * fixed-top pinning below makes the subsequent arithmetic constant time
-     * regardless, but cannot retroactively fix the copy length here.
+     * ossl_bn_priv_rand_range_fixed_top().  The fixed-top pinning below makes
+     * the subsequent arithmetic constant time regardless, but cannot
+     * retroactively fix the copy length here.
      */
     if (BN_copy(k, scalar) == NULL) {
         ERR_raise(ERR_LIB_EC, ERR_R_BN_LIB);
diff --git a/crypto/sm2/sm2_sign.c b/crypto/sm2/sm2_sign.c
index 5e29900968..7f1a40452c 100644
--- a/crypto/sm2/sm2_sign.c
+++ b/crypto/sm2/sm2_sign.c
@@ -14,6 +14,7 @@
 #include "crypto/sm2.h"
 #include "crypto/sm2err.h"
 #include "crypto/ec.h" /* ossl_ec_group_do_inverse_ord() */
+#include "crypto/bn.h" /* fixed-top / Montgomery constant-time BN helpers */
 #include "internal/numbers.h"
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -235,17 +236,22 @@ static ECDSA_SIG *sm2_sig_gen(const EC_KEY *key, const BIGNUM *e)
     EC_POINT *kG = NULL;
     BN_CTX *ctx = NULL;
     BIGNUM *k = NULL;
-    BIGNUM *rk = NULL;
     BIGNUM *r = NULL;
     BIGNUM *s = NULL;
     BIGNUM *x1 = NULL;
     BIGNUM *tmp = NULL;
+    BN_MONT_CTX *mont = EC_GROUP_get_mont_data(group);
     OSSL_LIB_CTX *libctx = ossl_ec_key_get_libctx(key);

     if (dA == NULL) {
         ERR_raise(ERR_LIB_SM2, SM2_R_INVALID_PRIVATE_KEY);
         goto done;
     }
+
+    if (mont == NULL) {
+        ERR_raise(ERR_LIB_SM2, ERR_R_EC_LIB);
+        goto done;
+    }
     kG = EC_POINT_new(group);
     if (kG == NULL) {
         ERR_raise(ERR_LIB_SM2, ERR_R_EC_LIB);
@@ -259,7 +265,6 @@ static ECDSA_SIG *sm2_sig_gen(const EC_KEY *key, const BIGNUM *e)

     BN_CTX_start(ctx);
     k = BN_CTX_get(ctx);
-    rk = BN_CTX_get(ctx);
     x1 = BN_CTX_get(ctx);
     tmp = BN_CTX_get(ctx);
     if (tmp == NULL) {
@@ -293,6 +298,18 @@ static ECDSA_SIG *sm2_sig_gen(const EC_KEY *key, const BIGNUM *e)
             ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR);
             goto done;
         }
+        /*
+         * Pin the nonce to a fixed, value-independent width and flag it
+         * BN_FLG_CONSTTIME, so its magnitude does not leak through operand
+         * lengths in the scalar copy inside the ladder, or in the arithmetic
+         * below.  BN_priv_rand_range_ex() is kept so the nonce value itself
+         * is unchanged; only its representation is pinned.
+         */
+        BN_set_flags(k, BN_FLG_CONSTTIME);
+        if (!bn_set_top_fixed(k, bn_get_top(order))) {
+            ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB);
+            goto done;
+        }

         if (!EC_POINT_mul(group, kG, k, NULL, NULL, ctx)
             || !EC_POINT_get_affine_coordinates(group, kG, x1, NULL,
@@ -302,23 +319,49 @@ static ECDSA_SIG *sm2_sig_gen(const EC_KEY *key, const BIGNUM *e)
             goto done;
         }

-        /* try again if r == 0 or r+k == n */
+        /* try again if r == 0 or r + k == n */
         if (BN_is_zero(r))
             continue;

-        if (!BN_add(rk, r, k)) {
-            ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR);
+        /*
+         * Since 0 < r < n and 0 < k < n, r + k == n is the same as
+         * k == n - r.  Both operands of the subtraction are public, so
+         * compute it in the open and then compare against the nonce with a
+         * fixed-width constant-time comparison.  A BN_cmp() on r + k would
+         * branch on whether the sum carried into an extra word, which
+         * depends on the value of k.
+         */
+        if (!BN_sub(tmp, order, r)
+            || !bn_set_top_fixed(tmp, bn_get_top(order))) {
+            ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB);
             goto done;
         }

-        if (BN_cmp(rk, order) == 0)
+        if (CRYPTO_memcmp(bn_get_words(k), bn_get_words(tmp),
+                bn_get_top(order) * sizeof(BN_ULONG))
+            == 0)
             continue;

+        /*
+         * s = ((1 + dA)^-1 * (k - r * dA)) mod order
+         *
+         * Computed with fixed-top / Montgomery constant-time primitives, so
+         * that the running time does not depend on the secret k or dA (the
+         * generic BN_mod_mul()/BN_sub() used previously reduce via BN_div(),
+         * whose timing is value dependent).  This mirrors the ECDSA path.
+         *
+         * s holds (1 + dA)^-1 throughout; the (k - r * dA) term is built in
+         * tmp.  bn_mul_mont_fixed_top() with one operand in the Montgomery
+         * domain yields the plain product, and the final
+         * BN_mod_mul_montgomery() returns the user-visible, normalised value.
+         */
         if (!BN_add(s, dA, BN_value_one())
             || !ossl_ec_group_do_inverse_ord(group, s, s, ctx)
-            || !BN_mod_mul(tmp, dA, r, order, ctx)
-            || !BN_sub(tmp, k, tmp)
-            || !BN_mod_mul(s, s, tmp, order, ctx)) {
+            || !bn_to_mont_fixed_top(tmp, r, mont, ctx)
+            || !bn_mul_mont_fixed_top(tmp, tmp, dA, mont, ctx)
+            || !bn_mod_sub_fixed_top(tmp, k, tmp, order)
+            || !bn_to_mont_fixed_top(tmp, tmp, mont, ctx)
+            || !BN_mod_mul_montgomery(s, tmp, s, mont, ctx)) {
             ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB);
             goto done;
         }