/* ========================================================================== */
/*! \file
 * \brief Keyed-Hash Message Authentication Code
 *
 * Copyright (c) 2012-2022 by the developers. See the LICENSE file for details.
 *
 * If nothing else is specified, function return zero to indicate success
 * and a negative value to indicate an error.
 */


/* ========================================================================== */
/* Include headers */

#include "posix.h"  /* Include this first because of feature test macros */

#include "config.h"

#if CFG_USE_TLS  /* This requires data from "config.h" */
#  include <openssl/evp.h>
#  if !CFG_USE_OPENSSL_API_3
#     include <openssl/hmac.h>
#  endif  /* !CFG_USE_OPENSSL_API_3 */
#endif  /* CFG_USE_TLS */
#include <string.h>

#include "hmac.h"
#include "main.h"


/* ========================================================================== */
/*! \defgroup HMAC HMAC: Hash-based Message Authentication Code
 *
 * This module provides optional support for Keyed-Hash Message Authentication
 * Codes. It is intended to comply with RFC 2104.
 *
 * \attention
 * This module currently works only if TLS support is available (because both
 * share the cryptographic functions provided by OpenSSL).
 */
/*! @{ */


/* ========================================================================== */
/* Constants */

/*! \brief Message prefix for HMAC module */
#define MAIN_ERR_PREFIX  "HMAC: "


/* ========================================================================== */
/* Data types */

enum hmac_alg
{
   HMAC_ALG_SHA1_160,
   HMAC_ALG_SHA2_256
};


/* ========================================================================== */
/* Delegate digest calculation to OpenSSL */

static int  hmac_openssl(enum hmac_alg  alg, const char*  text,
                         size_t  text_len, const char*  key, size_t  key_len,
                         unsigned char*  mac)
{
   int  res = -1;
#if CFG_USE_TLS
#  if CFG_USE_OPENSSL_API_3
   EVP_MAC*  evp_mac = NULL;
   EVP_MAC_CTX*  ctx = NULL;
   OSSL_PARAM params[2];
   const char *digest;
   size_t  mac_len = 0;
   int  rv;
#  else  /* CFG_USE_OPENSSL_API_3 */
   HMAC_CTX*  ctx = NULL;
   const EVP_MD*  hmac_id = NULL;
   unsigned int  mac_len = 0;
#  endif  /* CFG_USE_OPENSSL_API_3 */
   size_t  mac_len_check = 1;  /* Value never used by real digest */
   int  alg_valid = 0;

   /* Select digest algorithm */
   switch(alg)
   {
      case HMAC_ALG_SHA1_160:
      {
#  if CFG_USE_OPENSSL_API_3
         digest = "sha1";
#  else   /* CFG_USE_OPENSSL_API_3 */
         hmac_id = EVP_sha1();
#  endif   /* CFG_USE_OPENSSL_API_3 */
         mac_len_check = HMAC_SHA1_160_LEN;
         alg_valid = 1;
         break;
      }
      case HMAC_ALG_SHA2_256:
      {
#  if CFG_USE_OPENSSL_API_3
         digest = "sha256";
#  else   /* CFG_USE_OPENSSL_API_3 */
         hmac_id = EVP_sha256();
#  endif   /* CFG_USE_OPENSSL_API_3 */
         mac_len_check = HMAC_SHA2_256_LEN;
         alg_valid = 1;
         break;
      }
      default:
      {
         PRINT_ERROR("Requested algorithm not supported");
         break;
      }
   }

   /* Calculate MAC */
   if(alg_valid)
   {
      /* Create context (even if it is currently not used) */
#  if CFG_USE_OPENSSL_API_3
      evp_mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
      if(NULL != evp_mac)
      {
         ctx = EVP_MAC_CTX_new(evp_mac);
      }
/* CFG_USE_OPENSSL_API_3 */
#  elif CFG_USE_OPENSSL_API_1_1 || CFG_USE_LIBRESSL_API_3_5
      ctx = HMAC_CTX_new();
#  else   /* CFG_USE_OPENSSL_API_1_1 */
      ctx = (HMAC_CTX*) posix_malloc(sizeof(HMAC_CTX));
#  endif   /* CFG_USE_OPENSSL_API_1_1 */
      if(NULL == ctx)  { PRINT_ERROR("Creating context failed"); }

      /* Check whether length values can be represented as data type 'int' */
      else if(!(POSIX_INT_MAX >= text_len && POSIX_INT_MAX >= key_len))
      {
         PRINT_ERROR("Message or key is too long");
      }
      else
      {
         /* Delegate job to OpenSSL */
#  if CFG_USE_OPENSSL_API_3
         params[0] = OSSL_PARAM_construct_utf8_string("digest",
                                                      (char*) digest, 0);
         params[1] = OSSL_PARAM_construct_end();
         rv = EVP_MAC_init(ctx, (const unsigned char*) key, key_len, params);
         if (1 != rv)
         {
            PRINT_ERROR("Initialization of algorithm failed");
         }
         else
         {
            rv = EVP_MAC_update(ctx, (const unsigned char*) text, text_len);
            if (1 == rv)
            {
               rv = EVP_MAC_final(ctx, mac, &mac_len, mac_len_check);
            }
            if(1 != rv)
            {
               PRINT_ERROR("Calculation failed");
            }
            else if(NULL != mac && mac_len_check == (size_t) mac_len)
            {
               res = 0;
            }
         }
#  else   /* CFG_USE_OPENSSL_API_3 */
#     if CFG_USE_OPENSSL_API_1_1 || CFG_USE_LIBRESSL_API_3_5
         HMAC_CTX_reset(ctx);
#     else  /* CFG_USE_OPENSSL_API_1_1 */
         HMAC_CTX_init(ctx);
#     endif  /* CFG_USE_OPENSSL_API_1_1 */
         /* Note: OpenSSL documentation is wong! 5th argument is 'size_t' */
         mac = HMAC(hmac_id, (void*) key, (int) key_len, (unsigned char*) text,
                    text_len, (unsigned char*) mac, &mac_len);
         if(NULL != mac && mac_len_check == (size_t) mac_len)  { res = 0; }
#  endif   /* CFG_USE_OPENSSL_API_3 */
      }
   }

   /* Destroy context */
   if(NULL != ctx)
   {
#  if CFG_USE_OPENSSL_API_3
      EVP_MAC_CTX_free(ctx);
#  elif CFG_USE_OPENSSL_API_1_1 || CFG_USE_LIBRESSL_API_3_5
      HMAC_CTX_free(ctx);
#  else  /* CFG_USE_OPENSSL_API_1_1 */
      HMAC_CTX_cleanup(ctx);
      posix_free((void*) ctx);
#  endif  /* CFG_USE_OPENSSL_API_1_1 */
   }

#  if CFG_USE_OPENSSL_API_3
   /* Destroy fetched MAC algorithm */
   EVP_MAC_free(evp_mac);
#  endif  /* CFG_USE_OPENSSL_API_3 */
#endif  /* CFG_USE_TLS */

   return(res);
}


/* ========================================================================== */
/*! \brief Message Authentication Code based on SHA1-160 hash algorithm
 *
 * \param[in]  text      Message
 * \param[in]  text_len  Message length
 * \param[in]  key       Key
 * \param[in]  key_len   Key length
 * \param[out] mac       Result
 *
 * The values of paramaters \e text_len and \e key_len must represent bytes.
 *
 * On success, the result is written to \e mac and has a length of
 * \c HMAC_SHA1_160_LEN bytes.
 *
 * \return
 * - 0 on success
 * - -1 on error
 */

int  hmac_sha1_160(const char*  text, size_t  text_len,
                   const char*  key, size_t  key_len, unsigned char*  mac)
{
   return(hmac_openssl(HMAC_ALG_SHA1_160, text, text_len, key, key_len, mac));
}


/* ========================================================================== */
/*! \brief Message Authentication Code based on SHA2-256 hash algorithm
 *
 * \param[in]  text      Message
 * \param[in]  text_len  Message length
 * \param[in]  key       Key
 * \param[in]  key_len   Key length
 * \param[out] mac       Result
 *
 * The values of paramaters \e text_len and \e key_len must represent bytes.
 *
 * On success, the result is written to \e mac and has a length of
 * \c HMAC_SHA2_256_LEN bytes.
 *
 * \return
 * - 0 on success
 * - -1 on error
 */

int  hmac_sha2_256(const char*  text, size_t  text_len,
                   const char*  key, size_t  key_len, unsigned char*  mac)
{
   return(hmac_openssl(HMAC_ALG_SHA2_256, text, text_len, key, key_len, mac));
}


/* ========================================================================== */
/*! \brief Initialize HMAC module */

void  hmac_init(void)
{
   return;
}


/* ========================================================================== */
/*! \brief Shutdown HMAC module */

void  hmac_exit(void)
{
   return;
}


/*! @} */

/* EOF */
