From cb17e698f0271977df885c1e586f68be4874b7be Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Thu, 30 May 2024 17:50:36 +0200
Subject: [PATCH 10/11] rsa: Implement constant-time conversion of MPI to
 string

--
* cipher/rsa-common.c (_gcry_rsa_pkcs1_decode_for_enc): Replace
  _gcry_mpi_print() with constant time operation.

The _gcry_mpi_print() does a lot of housekeeping on the provided MPI
including skipping all the leading zeroes. If the ciphertext decrypted
to many leading zeroes, the output from this function is much shorter
(could be even all zeroes), which would immediately fail.

This implements the dummy conversion of the MPI with the knowledge that
the encrypted data will always have the fixed length, providing constant
time operation.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
---
 cipher/rsa-common.c | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

Index: libgcrypt-1.6.1/cipher/rsa-common.c
===================================================================
--- libgcrypt-1.6.1.orig/cipher/rsa-common.c
+++ libgcrypt-1.6.1/cipher/rsa-common.c
@@ -28,6 +28,7 @@
 #include "cipher.h"
 #include "pubkey-internal.h"
 #include "const-time.h"
+#include "bufhelp.h"
 
 
 /* Turn VALUE into an octet string and store it in an allocated buffer
@@ -199,7 +200,6 @@ gpg_err_code_t
 _gcry_rsa_pkcs1_decode_for_enc (unsigned char **r_result, size_t *r_resultlen,
                                 unsigned int nbits, gcry_mpi_t value)
 {
-  gcry_error_t err;
   unsigned char *frame = NULL;
   size_t nframe = (nbits+7) / 8;
   size_t n, n0;
@@ -208,6 +208,37 @@ _gcry_rsa_pkcs1_decode_for_enc (unsigned
 
   *r_result = NULL;
 
+  {
+#ifdef WITH_MARVIN_WORKAROUND
+  /* Allocate more to fit the whole MPI and allow moving it later to the right place */
+  size_t alloc_len = value->nlimbs * BYTES_PER_MPI_LIMB;
+  if ( !(frame = xtrymalloc_secure (alloc_len)))
+    return gpg_err_code_from_syserror ();
+
+  /* shovel the MPI content to the buffer as it is */
+  unsigned char *p = frame;
+  int i;
+  for (i = value->nlimbs - 1; i >= 0; i--)
+    {
+      mpi_limb_t *alimb = &value->d[i];
+#if BYTES_PER_MPI_LIMB == 4
+      buf_put_be32 (p, *alimb);
+      p += 4;
+#elif BYTES_PER_MPI_LIMB == 8
+      buf_put_be64 (p, *alimb);
+      p += 8;
+#else
+#     error please implement for this limb size.
+#endif
+    }
+  /* Remove leading zeroes, but not all! Keep the buffer to the nframe length!
+   * -- valid PKCS#1.5 padding will never have different lengths, than modulus */
+  memmov_independently (frame, frame + (alloc_len - nframe), nframe, alloc_len);
+  n = 0;
+  failed |= ct_not_equal_byte (frame[n++], 0x00);
+#else
+  gcry_error_t err;
+
   if ( !(frame = xtrymalloc_secure (nframe)))
     return gpg_err_code_from_syserror ();
 
@@ -235,6 +266,9 @@ _gcry_rsa_pkcs1_decode_for_enc (unsigned
   n = 0;
   if (!frame[0])
     n++;
+#endif
+  }
+
   failed |= ct_not_equal_byte (frame[n++], 0x02);
 
   /* Find the terminating zero byte.  */
Index: libgcrypt-1.6.1/src/const-time.h
===================================================================
--- libgcrypt-1.6.1.orig/src/const-time.h
+++ libgcrypt-1.6.1/src/const-time.h
@@ -17,7 +17,20 @@
  * License along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
+
+#ifndef GCRY_CONST_TIME_H
+#define GCRY_CONST_TIME_H
  
+
+/*
+ * Return 1 if A < B and return 0 otherwise.
+ */
+static inline int
+ct_lt (unsigned int a, unsigned int b)
+{
+  return (a ^ ((a ^ b) | ((a - b) ^ b))) >> (sizeof(unsigned int)*8 - 1);
+}
+
 static inline size_t
 ct_lt_s (size_t a, size_t b)
 {
@@ -26,20 +39,40 @@ ct_lt_s (size_t a, size_t b)
 
 #include "types.h"
 
+#ifndef HAVE_GCC_ASM_VOLATILE_MEMORY
+extern volatile unsigned int _gcry_ct_vzero;
+extern volatile unsigned int _gcry_ct_vone;
+#endif
+
 #define ct_memmov_cond _gcry_ct_memmov_cond
+#define ct_not_memequal _gcry_ct_not_memequal
 #define ct_memequal _gcry_ct_memequal
 #define ct_memcpy _gcry_ct_memcpy
 
 /*
- * Return 1 if A < B and return 0 otherwise.
+ * Return 0 if A is 0 and return 1 otherwise.
  */
-static inline int
-ct_lt (unsigned int a, unsigned int b)
+static inline unsigned int
+ct_is_not_zero (unsigned int a)
 {
-  return (a ^ ((a ^ b) | ((a - b) ^ b))) >> (sizeof(unsigned int)*8 - 1);
+  /* Sign bit set if A != 0. */
+  a = a | (-a);
+
+  return a >> (sizeof(unsigned int) * 8 - 1);
 }
 
 /*
+ * Return 1 if A is 0 and return 0 otherwise.
+ */
+static inline unsigned int
+ct_is_zero (unsigned int a)
+{
+  /* Sign bit set if A == 0. */
+  a = ~a & ~(-a);
+
+  return a >> (sizeof(unsigned int) * 8 - 1);
+}
+/*
  * Return 1 if it's not same, 0 if same.
  */
 static inline unsigned int
@@ -109,19 +142,34 @@ DEFINE_CT_TYPE_GEN_INV_MASK(uintptr, uin
 DEFINE_CT_TYPE_GEN_INV_MASK(ulong, unsigned long)
 DEFINE_CT_TYPE_GEN_INV_MASK(uchar, unsigned char)
 
+
 /*
- *  Return NULL when OP_ENABLED=1
- *  otherwise, return W
+ *  Return A when OP_ENABLED=1
+ *  otherwise, return B
  */
-static inline gcry_sexp_t
-sexp_null_cond (gcry_sexp_t w, unsigned long op_enable)
-{
-  static volatile uintptr_t vone = 1;
-  size_t mask = (uintptr_t)op_enable - vone;
-
-  return (gcry_sexp_t)(void *)((uintptr_t)w & mask);
-}
-
+#define DEFINE_CT_TYPE_SELECT_FUNC(name, type) \
+  static inline type \
+  ct_##name##_select (type a, type b, unsigned long op_enable) \
+  { \
+    type mask_b = ct_##name##_gen_inv_mask(op_enable); \
+    type mask_a = ct_##name##_gen_mask(op_enable); \
+    return (mask_a & a) | (mask_b & b); \
+  }
+DEFINE_CT_TYPE_SELECT_FUNC(uintptr, uintptr_t)
+DEFINE_CT_TYPE_SELECT_FUNC(ulong, unsigned long)
+DEFINE_CT_TYPE_SELECT_FUNC(uchar, unsigned char)
+
+/*
+  *  Return NULL when OP_ENABLED=1
+  *  otherwise, return W
+  */
+ static inline gcry_sexp_t
+ sexp_null_cond (gcry_sexp_t w, unsigned long op_enable)
+ {
+  uintptr_t o = ct_uintptr_select((uintptr_t)NULL, (uintptr_t)w, op_enable);
+  return (gcry_sexp_t)(void *)o;
+ }
+ 
 /* Compare byte-arrays of length LEN, return 1 if it's same, 0 otherwise.
    We use pointer of void *, so that it can be used with any structure.  */
 unsigned int _gcry_ct_memequal (const void *b1, const void *b2, size_t len);
@@ -133,3 +181,5 @@ unsigned int _gcry_ct_memequal (const vo
 void _gcry_ct_memmov_cond (void *dst, const void *src, size_t len,
                            unsigned long op_enable);
 void _gcry_ct_memcpy (void *dst, const void *src, size_t len, size_t buffer_len);
+
+#endif
Index: libgcrypt-1.6.1/src/const-time.c
===================================================================
--- libgcrypt-1.6.1.orig/src/const-time.c
+++ libgcrypt-1.6.1/src/const-time.c
@@ -23,32 +23,48 @@
 #include "g10lib.h"
 #include "const-time.h"
 
-/* Originally in NetBSD.
- *
- * Written by Matthias Drochner <drochner@NetBSD.org>.
- * Public domain.
- *
- * Modified the function name and return type to unsigned.
+
+#ifndef HAVE_GCC_ASM_VOLATILE_MEMORY
+/* These variables are used to generate masks from conditional operation
+ * flag parameters.  Use of volatile prevents compiler optimizations from
+ * converting AND-masking to conditional branches.  */
+volatile unsigned int _gcry_ct_vzero = 0;
+volatile unsigned int _gcry_ct_vone = 1;
+#endif
+
+
+/*
+ * Compare byte arrays of length LEN, return 1 if it's not same,
+ * 0, otherwise.
  */
 unsigned int
-ct_memequal (const void *b1, const void *b2, size_t len)
+_gcry_ct_not_memequal (const void *b1, const void *b2, size_t len)
 {
-	const unsigned char *c1 = b1, *c2 = b2;
-	unsigned int res = 0;
+  const byte *a = b1;
+  const byte *b = b2;
+  int ab, ba;
+  size_t i;
+
+  /* Constant-time compare. */
+  for (i = 0, ab = 0, ba = 0; i < len; i++)
+    {
+      /* If a[i] != b[i], either ab or ba will be negative. */
+      ab |= a[i] - b[i];
+      ba |= b[i] - a[i];
+    }
 
-	while (len--)
-		res |= *c1++ ^ *c2++;
+  /* 'ab | ba' is negative when buffers are not equal, extract sign bit.  */
+  return ((unsigned int)(ab | ba) >> (sizeof(unsigned int) * 8 - 1)) & 1;
+}
 
-	/*
-	 * Map 0 to 1 and [1, 256) to 0 using only constant-time
-	 * arithmetic.
-	 *
-	 * This is not simply `!res' because although many CPUs support
-	 * branchless conditional moves and many compilers will take
-	 * advantage of them, certain compilers generate branches on
-	 * certain CPUs for `!res'.
-	 */
-	return (1 & ((res - 1) >> 8));
+/*
+ * Compare byte arrays of length LEN, return 0 if it's not same,
+ * 1, otherwise.
+ */
+unsigned int
+_gcry_ct_memequal (const void *b1, const void *b2, size_t len)
+{
+  return _gcry_ct_not_memequal (b1, b2, len) ^ 1;
 }
 
 /*
Index: libgcrypt-1.6.1/mpi/Makefile.am
===================================================================
--- libgcrypt-1.6.1.orig/mpi/Makefile.am
+++ libgcrypt-1.6.1/mpi/Makefile.am
@@ -166,6 +166,7 @@ libmpi_la_SOURCES = longlong.h	   \
 	      mpi-inline.c   \
 	      mpi-inv.c      \
 	      mpi-mul.c      \
+	      mpih-const-time.c \
 	      mpi-mod.c      \
 	      mpi-pow.c      \
 	      mpi-mpow.c     \
Index: libgcrypt-1.6.1/mpi/mpih-const-time.c
===================================================================
--- /dev/null
+++ libgcrypt-1.6.1/mpi/mpih-const-time.c
@@ -0,0 +1,217 @@
+/* mpih-const-time.c  -  Constant-time MPI helper functions
+ *      Copyright (C) 2020  g10 Code GmbH
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "mpi-internal.h"
+#include "g10lib.h"
+#include "const-time.h"
+
+#define A_LIMB_1 ((mpi_limb_t)1)
+
+
+/*
+ *  W = U when OP_ENABLED=1
+ *  otherwise, W keeps old value
+ */
+void
+_gcry_mpih_set_cond (mpi_ptr_t wp, mpi_ptr_t up, mpi_size_t usize,
+                     unsigned long op_enable)
+{
+  /* Note: dual mask with AND/OR used for EM leakage mitigation */
+  mpi_limb_t mask1 = ct_limb_gen_mask(op_enable);
+  mpi_limb_t mask2 = ct_limb_gen_inv_mask(op_enable);
+  mpi_size_t i;
+
+  for (i = 0; i < usize; i++)
+    {
+      wp[i] = (wp[i] & mask2) | (up[i] & mask1);
+    }
+}
+
+
+/*
+ *  W = U + V when OP_ENABLED=1
+ *  otherwise, W = U
+ */
+mpi_limb_t
+_gcry_mpih_add_n_cond (mpi_ptr_t wp, mpi_ptr_t up, mpi_ptr_t vp,
+                       mpi_size_t usize, unsigned long op_enable)
+{
+  /* Note: dual mask with AND/OR used for EM leakage mitigation */
+  mpi_limb_t mask1 = ct_limb_gen_mask(op_enable);
+  mpi_limb_t mask2 = ct_limb_gen_inv_mask(op_enable);
+  mpi_size_t i;
+  mpi_limb_t cy;
+
+  cy = 0;
+  for (i = 0; i < usize; i++)
+    {
+      mpi_limb_t u = up[i];
+      mpi_limb_t x = u + vp[i];
+      mpi_limb_t cy1 = x < u;
+      mpi_limb_t cy2;
+
+      x = x + cy;
+      cy2 = x < cy;
+      cy = cy1 | cy2;
+      wp[i] = (u & mask2) | (x & mask1);
+    }
+
+  return cy & mask1;
+}
+
+
+/*
+ *  W = U - V when OP_ENABLED=1
+ *  otherwise, W = U
+ */
+mpi_limb_t
+_gcry_mpih_sub_n_cond (mpi_ptr_t wp, mpi_ptr_t up, mpi_ptr_t vp,
+                       mpi_size_t usize, unsigned long op_enable)
+{
+  /* Note: dual mask with AND/OR used for EM leakage mitigation */
+  mpi_limb_t mask1 = ct_limb_gen_mask(op_enable);
+  mpi_limb_t mask2 = ct_limb_gen_inv_mask(op_enable);
+  mpi_size_t i;
+  mpi_limb_t cy;
+
+  cy = 0;
+  for (i = 0; i < usize; i++)
+    {
+      mpi_limb_t u = up[i];
+      mpi_limb_t x = u - vp[i];
+      mpi_limb_t cy1 = x > u;
+      mpi_limb_t cy2;
+
+      cy2 = x < cy;
+      x = x - cy;
+      cy = cy1 | cy2;
+      wp[i] = (u & mask2) | (x & mask1);
+    }
+
+  return cy & mask1;
+}
+
+
+/*
+ *  Swap value of U and V when OP_ENABLED=1
+ *  otherwise, no change
+ */
+void
+_gcry_mpih_swap_cond (mpi_ptr_t up, mpi_ptr_t vp, mpi_size_t usize,
+                      unsigned long op_enable)
+{
+  /* Note: dual mask with AND/OR used for EM leakage mitigation */
+  mpi_limb_t mask1 = ct_limb_gen_mask(op_enable);
+  mpi_limb_t mask2 = ct_limb_gen_inv_mask(op_enable);
+  mpi_size_t i;
+
+  for (i = 0; i < usize; i++)
+    {
+      mpi_limb_t u = up[i];
+      mpi_limb_t v = vp[i];
+      up[i] = (u & mask2) | (v & mask1);
+      vp[i] = (u & mask1) | (v & mask2);
+    }
+}
+
+
+/*
+ *  W = -U when OP_ENABLED=1
+ *  otherwise, W = U
+ */
+void
+_gcry_mpih_abs_cond (mpi_ptr_t wp, mpi_ptr_t up, mpi_size_t usize,
+                     unsigned long op_enable)
+{
+  /* Note: dual mask with AND/OR used for EM leakage mitigation */
+  mpi_limb_t mask1 = ct_limb_gen_mask(op_enable);
+  mpi_limb_t mask2 = ct_limb_gen_inv_mask(op_enable);
+  mpi_limb_t cy = op_enable;
+  mpi_size_t i;
+
+  for (i = 0; i < usize; i++)
+    {
+      mpi_limb_t u = up[i];
+      mpi_limb_t x = ~u + cy;
+
+      cy = (x < ~u);
+      wp[i] = (u & mask2) | (x & mask1);
+    }
+}
+
+
+/*
+ * Allocating memory for W,
+ * compute W = V % U, then return W
+ */
+mpi_ptr_t
+_gcry_mpih_mod (mpi_ptr_t vp, mpi_size_t vsize,
+                mpi_ptr_t up, mpi_size_t usize)
+{
+  int secure;
+  mpi_ptr_t rp;
+  mpi_size_t i;
+
+  secure = _gcry_is_secure (vp);
+  rp = mpi_alloc_limb_space (usize, secure);
+  MPN_ZERO (rp, usize);
+
+  for (i = 0; i < vsize * BITS_PER_MPI_LIMB; i++)
+    {
+      unsigned int j = vsize * BITS_PER_MPI_LIMB - 1 - i;
+      unsigned int limbno = j / BITS_PER_MPI_LIMB;
+      unsigned int bitno = j % BITS_PER_MPI_LIMB;
+      mpi_limb_t limb = vp[limbno];
+      unsigned int the_bit = ((limb & (A_LIMB_1 << bitno)) ? 1 : 0);
+      mpi_limb_t underflow;
+      mpi_limb_t overflow;
+
+      overflow = _gcry_mpih_lshift (rp, rp, usize, 1);
+      rp[0] |= the_bit;
+
+      underflow = _gcry_mpih_sub_n (rp, rp, up, usize);
+      mpih_add_n_cond (rp, rp, up, usize, overflow ^ underflow);
+    }
+
+  return rp;
+}
+
+int
+_gcry_mpih_cmp_ui (mpi_ptr_t up, mpi_size_t usize, unsigned long v)
+{
+  int is_all_zero = 1;
+  mpi_size_t i;
+
+  for (i = 1; i < usize; i++)
+    is_all_zero &= mpih_limb_is_zero (up[i]);
+
+  if (is_all_zero)
+    {
+      if (up[0] < v)
+        return -1;
+      else if (up[0] > v)
+        return 1;
+      else
+        return 0;
+    }
+  return 1;
+}
Index: libgcrypt-1.6.1/mpi/mpi-internal.h
===================================================================
--- libgcrypt-1.6.1.orig/mpi/mpi-internal.h
+++ libgcrypt-1.6.1/mpi/mpi-internal.h
@@ -260,6 +260,29 @@ mpi_limb_t _gcry_mpih_lshift( mpi_ptr_t
 mpi_limb_t _gcry_mpih_rshift( mpi_ptr_t wp, mpi_ptr_t up, mpi_size_t usize,
 							   unsigned cnt);
 
+/*-- mpih-const-time.c --*/
+#define mpih_set_cond(w,u,s,o) _gcry_mpih_set_cond ((w),(u),(s),(o))
+#define mpih_add_n_cond(w,u,v,s,o) _gcry_mpih_add_n_cond ((w),(u),(v),(s),(o))
+#define mpih_sub_n_cond(w,u,v,s,o) _gcry_mpih_sub_n_cond ((w),(u),(v),(s),(o))
+#define mpih_swap_cond(u,v,s,o) _gcry_mpih_swap_cond ((u),(v),(s),(o))
+#define mpih_abs_cond(w,u,s,o) _gcry_mpih_abs_cond ((w),(u),(s),(o))
+#define mpih_mod(v,vs,u,us) _gcry_mpih_mod ((v),(vs),(u),(us))
+
+void _gcry_mpih_set_cond (mpi_ptr_t wp, mpi_ptr_t up, mpi_size_t usize,
+                          unsigned long op_enable);
+mpi_limb_t _gcry_mpih_add_n_cond (mpi_ptr_t wp, mpi_ptr_t up, mpi_ptr_t vp,
+                                  mpi_size_t usize, unsigned long op_enable);
+mpi_limb_t _gcry_mpih_sub_n_cond (mpi_ptr_t wp, mpi_ptr_t up, mpi_ptr_t vp,
+                                  mpi_size_t usize, unsigned long op_enable);
+void _gcry_mpih_swap_cond (mpi_ptr_t up, mpi_ptr_t vp, mpi_size_t usize,
+                           unsigned long op_enable);
+void _gcry_mpih_abs_cond (mpi_ptr_t wp, mpi_ptr_t up,
+                          mpi_size_t usize, unsigned long op_enable);
+mpi_ptr_t _gcry_mpih_mod (mpi_ptr_t vp, mpi_size_t vsize,
+                          mpi_ptr_t up, mpi_size_t usize);
+int _gcry_mpih_cmp_ui (mpi_ptr_t up, mpi_size_t usize, unsigned long v);
+
+
 
 /* Define stuff for longlong.h.  */
 #define W_TYPE_SIZE BITS_PER_MPI_LIMB
