From 81f8b7357395a8c10fc3fcbf177db6267097c507 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Fri, 31 May 2024 15:03:29 +0200
Subject: [PATCH 11/11] cipher: Use the constant time conversion also for OAEP

--
* cipher/rsa-common.c (mpi_to_string): New function
  (_gcry_rsa_pkcs1_decode_for_enc): Use new function for MPI to string
  conversion.
  (_gcry_rsa_oaep_decode): Use the new function for MPI to string
  conversion.
* cipher/rsa.c (rsa_decrypt): Use constant time conversion to S-exp for
  OAEP padding.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
---
 cipher/rsa-common.c | 77 +++++++++++++++++++++++++++++++--------------
 cipher/rsa.c        |  4 +++
 2 files changed, 57 insertions(+), 24 deletions(-)

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
@@ -191,32 +191,26 @@ memmov_independently (void *dst, const v
     }
 }
 
-
-/* Decode a plaintext in VALUE assuming pkcs#1 block type 2 padding.
-   NBITS is the size of the secret key.  On success the result is
-   stored as a newly allocated buffer at R_RESULT and its valid length at
-   R_RESULTLEN.  On error NULL is stored at R_RESULT.  */
-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)
+static unsigned char *
+mpi_to_string(gcry_mpi_t value, size_t nframe)
 {
   unsigned char *frame = NULL;
-  size_t nframe = (nbits+7) / 8;
-  size_t n, n0;
-  unsigned int failed = 0;
-  unsigned int not_found = 1;
+  unsigned char *p;
+  size_t noff;
+  /* Allocate memory to fit the whole MPI limbs and allow moving it later to the right place */
+  size_t alloc_len = value->nlimbs * BYTES_PER_MPI_LIMB;
 
-  *r_result = NULL;
+  /* for too short input, we need to allocate at least modulus length (which might still be valid in case of OAEP) */
+  noff = (alloc_len < nframe) ? nframe - alloc_len : 0;
+  alloc_len = alloc_len + noff;
 
-  {
-#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 ();
+    return NULL;
 
+  if (noff)
+    memset (frame, 0, noff);
+  p = frame + noff;
   /* shovel the MPI content to the buffer as it is */
-  unsigned char *p = frame;
   int i;
   for (i = value->nlimbs - 1; i >= 0; i--)
     {
@@ -231,9 +225,34 @@ _gcry_rsa_pkcs1_decode_for_enc (unsigned
 #     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 */
+  /* Move the MPI to the right place -- with the least significant bytes aligned to the modulus length
+   * -- this might keep some leading zeroes at the beginning of the buffer, but this might be valid for OAEP */
   memmov_independently (frame, frame + (alloc_len - nframe), nframe, alloc_len);
+  return frame;
+}
+
+/* Decode a plaintext in VALUE assuming pkcs#1 block type 2 padding.
+   NBITS is the size of the secret key.  On success the result is
+   stored as a newly allocated buffer at R_RESULT and its valid length at
+   R_RESULTLEN.  On error NULL is stored at R_RESULT.  */
+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)
+{
+  unsigned char *frame = NULL;
+  size_t nframe = (nbits+7) / 8;
+  size_t n, n0;
+  unsigned int failed = 0;
+  unsigned int not_found = 1;
+
+  *r_result = NULL;
+
+  {
+#ifdef WITH_MARVIN_WORKAROUND
+  frame = mpi_to_string(value, nframe);
+  if (frame == NULL)
+    return gpg_err_code_from_syserror ();
+
   n = 0;
   failed |= ct_not_equal_byte (frame[n++], 0x00);
 #else
@@ -266,7 +285,7 @@ _gcry_rsa_pkcs1_decode_for_enc (unsigned
   n = 0;
   if (!frame[0])
     n++;
-#endif
+#endif /* WITH_MARVIN_WORKAROUND */
   }
 
   failed |= ct_not_equal_byte (frame[n++], 0x02);
@@ -645,14 +664,23 @@ _gcry_rsa_oaep_decode (unsigned char **r
      happen due to the leading zero in OAEP frames and due to the
      following random octets (seed^mask) which may have leading zero
      bytes.  This all is needed to cope with our leading zeroes
-     suppressing MPI implementation.  The code implictly implements
+     suppressing MPI implementation.  The code implicitly implements
      Step 1b (bail out if NFRAME != N).  */
+#ifdef WITH_MARVIN_WORKAROUND
+  frame = mpi_to_string(value, nkey);
+  if (frame == NULL)
+    {
+      xfree (lhash);
+      return gpg_err_code_from_syserror ();
+    }
+#else
   rc = octet_string_from_mpi (&frame, NULL, value, nkey);
   if (rc)
     {
       xfree (lhash);
       return GPG_ERR_ENCODING_PROBLEM;
     }
+#endif /* WITH_MARVIN_WORKAROUND */
   nframe = nkey;
 
   /* Step 1c: Check that the key is long enough.  */
@@ -667,7 +695,7 @@ _gcry_rsa_oaep_decode (unsigned char **r
      gcry_mpi_aprint above.  */
 
   /* Allocate space for SEED and DB.  */
-  seed = xtrymalloc_secure (nframe - 1);
+  seed = xtrymalloc_secure (nframe);
   if (!seed)
     {
       rc = gpg_err_code_from_syserror ();
Index: libgcrypt-1.6.1/cipher/rsa.c
===================================================================
--- libgcrypt-1.6.1.orig/cipher/rsa.c
+++ libgcrypt-1.6.1/cipher/rsa.c
@@ -362,6 +362,17 @@ generate_std (RSA_secret_key *sk, unsign
   return 0;
 }
 
+/* Check the RSA key length is acceptable for key generation or usage */
+static gpg_err_code_t
+rsa_check_keysize (unsigned int nbits)
+{
+  if (fips_mode() && nbits < 2048)
+    return GPG_ERR_INV_VALUE;
+
+  return GPG_ERR_NO_ERROR;
+}
+
+
 
 /* Helper for generate_x931.  */
 static gcry_mpi_t
@@ -1395,7 +1406,7 @@ static gcry_err_code_t
 rsa_decrypt (gcry_sexp_t *r_plain, gcry_sexp_t s_data, gcry_sexp_t keyparms)
 
 {
-  gpg_err_code_t rc;
+  gpg_err_code_t rc, rc_sexp;
   struct pk_encoding_ctx ctx;
   gcry_sexp_t l1 = NULL;
   gcry_mpi_t data = NULL;
@@ -1406,9 +1417,14 @@ rsa_decrypt (gcry_sexp_t *r_plain, gcry_
   gcry_mpi_t bldata = NULL;/* Blinded data to decrypt.  */
   unsigned char *unpad = NULL;
   size_t unpadlen = 0;
+  unsigned int nbits = rsa_get_nbits (keyparms);
   gcry_sexp_t result = NULL;
   gcry_sexp_t dummy = NULL;
 
+  rc = rsa_check_keysize (nbits);
+  if (rc)
+    return rc;
+
   _gcry_pk_util_init_encoding_ctx (&ctx, PUBKEY_OP_DECRYPT,
                                    rsa_get_nbits (keyparms));
 
@@ -1512,13 +1528,15 @@ rsa_decrypt (gcry_sexp_t *r_plain, gcry_
       mpi_free (plain);
       plain = NULL;
 #ifdef WITH_MARVIN_WORKAROUND
-      rc_sexp = sexp_build (&result, NULL, "(value %c)", (int)unpadlen, unpad, (nbits + 7) / 8);
+      rc_sexp = sexp_build (&result, NULL, "(value %c)", (int)unpadlen, unpad, (ctx.nbits + 7) / 8);
 #else
       sexp_build (&result, NULL, "(value %b)", (int)unpadlen, unpad);
 #endif /* WITH_MARVIN_WORKAROUND */
-      *r_plain = sexp_null_cond (result, !!rc);
-      dummy = sexp_null_cond (result,!rc);
+      *r_plain = sexp_null_cond (result, ct_is_not_zero (rc));
+      dummy = sexp_null_cond (result, ct_is_zero (rc));
       sexp_release (dummy);
+      rc = ct_ulong_select (rc_sexp, rc,
+                            ct_is_zero (rc) & ct_is_not_zero (rc_sexp));
       break;
 
     case PUBKEY_ENC_OAEP:
@@ -1527,12 +1545,20 @@ rsa_decrypt (gcry_sexp_t *r_plain, gcry_
                                   plain, ctx.label, ctx.labellen);
       mpi_free (plain);
       plain = NULL;
-      sexp_build (&result, NULL, "(value %b)", (int)unpadlen, unpad);
-      *r_plain = sexp_null_cond (result, !!rc);
-      dummy = sexp_null_cond (result, !rc);
+
+#ifdef WITH_MARVIN_WORKAROUND
+      rc_sexp = sexp_build (&result, NULL, "(value %c)", (int)unpadlen, unpad, (ctx.nbits + 7) / 8);
+#else
+      rc_sexp = sexp_build (&result, NULL, "(value %b)", (int)unpadlen, unpad);
+#endif /* WITH_MARVIN_WORKAROUND */
+      *r_plain = sexp_null_cond (result, ct_is_not_zero (rc));
+      dummy = sexp_null_cond (result, ct_is_zero (rc));
       sexp_release (dummy);
+      rc = ct_ulong_select (rc_sexp, rc,
+                            ct_is_zero (rc) & ct_is_not_zero (rc_sexp));
       break;
 
+
     default:
       /* Raw format.  For backward compatibility we need to assume a
          signed mpi by using the sexp format string "%m".  */
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
@@ -50,6 +50,7 @@
 #endif /*BITS_PER_MPI_LIMB*/
 
 #include "mpi.h"
+#include "const-time.h"
 
 /* If KARATSUBA_THRESHOLD is not already defined, define it to a
  * value which is good on most machines.  */
@@ -282,6 +283,28 @@ mpi_ptr_t _gcry_mpih_mod (mpi_ptr_t vp,
                           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_CT_TYPE_GEN_MASK(limb, mpi_limb_t)
+DEFINE_CT_TYPE_GEN_INV_MASK(limb, mpi_limb_t)
+
+
+static inline int
+mpih_limb_is_zero (mpi_limb_t a)
+{
+  /* Sign bit set if A == 0. */
+  a = ~a & ~(-a);
+
+  return a >> (BITS_PER_MPI_LIMB - 1);
+}
+
+static inline int
+mpih_limb_is_not_zero (mpi_limb_t a)
+{
+  /* Sign bit set if A != 0. */
+  a = a | (-a);
+
+  return a >> (BITS_PER_MPI_LIMB - 1);
+}
+
 
 
 /* Define stuff for longlong.h.  */
