/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
/* SPDX-License-Identifier: Unlicense */
#include "tomcrypt_private.h"

/**
  @file rsa_key.c
  Free an RSA key, Tom St Denis
  Basic operations on an RSA key, Steffen Jaeckel
*/

#ifdef LTC_MRSA
static void s_mpi_shrink_multi(void **a, ...)
{
   void **cur;
   unsigned n;
   int err;
   va_list args;
   void *tmp[10] = { 0 };
   void **arg[10] = { 0 };

   /* We re-allocate in the order that we received the varargs */
   n = 0;
   err = CRYPT_ERROR;
   cur = a;
   va_start(args, a);
   while (cur != NULL) {
      if (n >= LTC_ARRAY_SIZE(tmp)) {
         goto out;
      }
      if (*cur != NULL) {
         arg[n] = cur;
         if ((err = ltc_mp_init_copy(&tmp[n], *arg[n])) != CRYPT_OK) {
            goto out;
         }
         n++;
      }
      cur = va_arg(args, void**);
   }
   va_end(args);

   /* but we clear the old values in the reverse order */
   while (n != 0 && arg[--n] != NULL) {
      ltc_mp_clear(*arg[n]);
      *arg[n] = tmp[n];
   }
out:
   va_end(args);
   /* clean-up after an error
    * or after this was called with too many args
    */
   if ((err != CRYPT_OK) ||
         (n >= LTC_ARRAY_SIZE(tmp))) {
      for (n = 0; n < LTC_ARRAY_SIZE(tmp); ++n) {
         if (tmp[n] != NULL) {
            ltc_mp_clear(tmp[n]);
         }
      }
   }
}

/**
  This shrinks the allocated memory of a RSA key

     It will use up some more memory temporarily,
     but then it will free-up the entire sequence that
     was once allocated when the key was created/populated.

     This only works with libtommath >= 1.2.0 in earlier versions
     it has the inverse effect due to the way it worked internally.
     Also works for GNU MP, tomsfastmath naturally shows no effect.

  @param key   The RSA key to shrink
*/
void rsa_shrink_key(rsa_key *key)
{
   LTC_ARGCHKVD(key != NULL);
   s_mpi_shrink_multi(&key->e, &key->d, &key->N, &key->dQ, &key->dP, &key->qP, &key->p, &key->q, NULL);
}

/**
  Init an RSA key
  @param key   The RSA key to initialize
  @return CRYPT_OK if successful
*/
int rsa_init(rsa_key *key)
{
   LTC_ARGCHK(key != NULL);
   key->pss_oaep = 0;
   XMEMSET(&key->params, 0, sizeof(key->params));
   return ltc_mp_init_multi(&key->e, &key->d, &key->N, &key->dQ, &key->dP, &key->qP, &key->p, &key->q, LTC_NULL);
}

/**
  Free an RSA key from memory
  @param key   The RSA key to free
*/
void rsa_free(rsa_key *key)
{
   LTC_ARGCHKVD(key != NULL);
   ltc_mp_cleanup_multi(&key->q, &key->p, &key->qP, &key->dP, &key->dQ, &key->N, &key->d, &key->e, LTC_NULL);
   key->pss_oaep = 0;
   XMEMSET(&key->params, 0, sizeof(key->params));
}

static LTC_INLINE int s_rsa_key_valid_rsa_params(ltc_rsa_op_checked *check)
{
   const ltc_rsa_parameters *key_params;
   /* This is called from PKCS#1 de-/encoder code, so we can't check the key */
   if (check->key == NULL) {
      return CRYPT_OK;
   }
   key_params = &check->key->params;
   /* Key has no PSS/OAEP constraints */
   if (!check->key->pss_oaep) {
      return CRYPT_OK;
   }
   /* Key is constrained - operation must use matching PSS/OAEP params */
   if (check->params->padding != LTC_PKCS_1_PSS
         && check->params->padding != LTC_PKCS_1_OAEP) {
      return CRYPT_PK_TYPE_MISMATCH;
   }
   if (key_params->hash_idx != check->hash_alg) {
      return CRYPT_INVALID_HASH;
   }
   if (key_params->mgf1_hash_idx != check->mgf1_hash_alg) {
      return CRYPT_INVALID_HASH;
   }
   return CRYPT_OK;
}

static LTC_INLINE int s_rsa_key_set_hash_algs(ltc_rsa_op_checked *check)
{
   ltc_rsa_op_parameters *params = check->params;
   if (hash_is_valid(params->params.hash_idx) != CRYPT_OK) {
      return CRYPT_INVALID_HASH;
   }
   check->hash_alg = params->params.hash_idx;
   if (params->params.mgf1_hash_idx == -1) {
      if (params->padding != LTC_PKCS_1_PSS && params->padding != LTC_PKCS_1_OAEP)
         return CRYPT_OK;
   } else if (hash_is_valid(params->params.mgf1_hash_idx) == CRYPT_OK) {
      check->mgf1_hash_alg = params->params.mgf1_hash_idx;
      return CRYPT_OK;
   }
   return CRYPT_INVALID_HASH;
}

static LTC_INLINE int s_rsa_key_valid_sign(ltc_rsa_op_checked *check)
{
   ltc_rsa_op_parameters *params = check->params;
   if ((params->padding != LTC_PKCS_1_V1_5)
         && (params->padding != LTC_PKCS_1_PSS)
         && (params->padding != LTC_PKCS_1_V1_5_NA1)) {
      return CRYPT_PK_INVALID_PADDING;
   }

   if (params->padding != LTC_PKCS_1_V1_5_NA1) {
      int err = s_rsa_key_set_hash_algs(check);
      if (err != CRYPT_OK) {
         return err;
      }
   }
   if (params->padding == LTC_PKCS_1_V1_5) {
      /* not all hashes have OIDs... so sad */
      if (check->hash_alg == -1
            || hash_descriptor[check->hash_alg].OIDlen == 0) {
         return CRYPT_INVALID_ARG;
      }
   }
   return s_rsa_key_valid_rsa_params(check);
}

static LTC_INLINE int s_rsa_key_valid_crypt(ltc_rsa_op_checked *check)
{
   ltc_rsa_op_parameters *params = check->params;
   if ((params->padding != LTC_PKCS_1_V1_5) &&
       (params->padding != LTC_PKCS_1_OAEP)) {
     return CRYPT_PK_INVALID_PADDING;
   }

   if (params->padding == LTC_PKCS_1_OAEP) {
      int err = s_rsa_key_set_hash_algs(check);
      if (err != CRYPT_OK) {
         return err;
      }
   }
   return s_rsa_key_valid_rsa_params(check);
}

static LTC_INLINE int s_rsa_check_prng(ltc_rsa_op op, ltc_rsa_op_parameters *params)
{
   /* Only PSS signing needs a PRNG, v1.5 signing is deterministic.
    * All encryption needs a PRNG (OAEP seed, v1.5 EME random padding). */
   if ((op & LTC_RSA_OP_SIGN) == LTC_RSA_OP_SIGN
         && params->padding != LTC_PKCS_1_PSS)
      return CRYPT_OK;
   if (params->prng == NULL)
      return CRYPT_INVALID_PRNG;
   /* valid prng ? */
   return prng_is_valid(params->wprng);
}

int rsa_key_valid_op(ltc_rsa_op op, ltc_rsa_op_checked *check)
{
   int err;
   check->hash_alg = check->mgf1_hash_alg = -1;
   LTC_ARGCHK(check->params != NULL);
   if ((op & LTC_RSA_OP_PKCS1) != LTC_RSA_OP_PKCS1) {
      /* PKCS#1 ops don't need an RSA key */
      LTC_ARGCHK(check->key    != NULL);
   }
   if ((op & LTC_RSA_OP_SEND) == LTC_RSA_OP_SEND) {
      if ((err = s_rsa_check_prng(op, check->params)) != CRYPT_OK) {
         return err;
      }
   }
   switch (op) {
      case LTC_RSA_ENCRYPT:
      case LTC_RSA_DECRYPT:
      case LTC_PKCS1_ENCRYPT:
      case LTC_PKCS1_DECRYPT:
         return s_rsa_key_valid_crypt(check);
      case LTC_RSA_SIGN:
      case LTC_RSA_VERIFY:
      case LTC_PKCS1_SIGN:
      case LTC_PKCS1_VERIFY:
         return s_rsa_key_valid_sign(check);
   }
   return CRYPT_ERROR;
}

int rsa_params_equal(const ltc_rsa_parameters *a, const ltc_rsa_parameters *b)
{
   if (a->saltlen != b->saltlen)
      return 0;
   if (a->hash_idx != b->hash_idx)
      return 0;
   if (a->mgf1_hash_idx != b->mgf1_hash_idx)
      return 0;
   return 1;
}

#endif