/* ========================================================================== */
/*! \file
 * \brief Message Digests
 *
 * Copyright (c) 2012-2020 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>
#endif  /* CFG_USE_TLS */
#include <string.h>

#include "digest.h"
#include "main.h"


/* ========================================================================== */
/*! \defgroup DIGEST DIGEST: Hash algorithms
 *
 * This module provides optional support for message digest algorithms. It is
 * intended to comply with RFC 3174.
 *
 * \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 DIGEST module */
#define MAIN_ERR_PREFIX  "DIGEST: "


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

enum digest_alg
{
   DIGEST_ALG_SHA1_160,
   DIGEST_ALG_SHA2_256
};


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

static int  digest_openssl(enum digest_alg  alg, const char*  text,
                           size_t  text_len, unsigned char*  md)
{
   int  res = -1;
#if CFG_USE_TLS
   EVP_MD_CTX*  (*openssl_evp_md_ctx_new)(void);
   void  (*openssl_evp_md_ctx_free)(EVP_MD_CTX*);
   EVP_MD_CTX*  ctx;
   const EVP_MD*  md_id;
   size_t  md_len_check = 1;  /* Value never used by real digest */
   unsigned int  md_len = 0;
   int  rv;

   /* Setup context management functions (changed in API 1.1) */
#if  CFG_USE_OPENSSL_API_1_1
   openssl_evp_md_ctx_new = EVP_MD_CTX_new;
   openssl_evp_md_ctx_free = EVP_MD_CTX_free;
#else   /* CFG_USE_OPENSSL_API_1_1 */
   openssl_evp_md_ctx_new = EVP_MD_CTX_create;
   openssl_evp_md_ctx_free = EVP_MD_CTX_destroy;
#endif   /* CFG_USE_OPENSSL_API_1_1 */

   /* Select digest algorithm */
   switch(alg)
   {
      case DIGEST_ALG_SHA1_160:
      {
         md_id = EVP_sha1();
         md_len_check = DIGEST_SHA1_160_LEN;
         break;
      }
      case DIGEST_ALG_SHA2_256:
      {
         md_id = EVP_sha256();
         md_len_check = DIGEST_SHA2_256_LEN;
         break;
      }
      default:
      {
         PRINT_ERROR("Requested algorithm not supported");
         md_id = NULL;
         break;
      }
   }
   if(NULL != md_id)
   {
      /* Create context */
      ctx = openssl_evp_md_ctx_new();
      if(NULL == ctx)  { PRINT_ERROR("Creating context failed"); }
      /* Check whether length value can be represented as data type 'int' */
      else if(!(POSIX_INT_MAX >= text_len))
      {
         PRINT_ERROR("Message is too long");
      }
      else
      {
         /* Delegate job to OpenSSL */
         rv = EVP_DigestInit_ex(ctx, md_id, NULL);
         if(1 == rv)
         {
            rv = EVP_DigestUpdate(ctx, (void*) text, text_len);
            if(1 == rv)
            {
               rv = EVP_DigestFinal_ex(ctx, md, &md_len);
               if(1 == rv && md_len_check == (size_t) md_len)
               {
                  /* Success */
                  res = 0;
               }
            }
         }
      }
      /* Destroy context */
      if(NULL != ctx)  { openssl_evp_md_ctx_free(ctx); }
   }
#endif  /* CFG_USE_TLS */

   return(res);
}


/* ========================================================================== */
/*! \brief Secure Hash Algorithm SHA1-160
 *
 * \param[in]  text      Message
 * \param[in]  text_len  Message length
 * \param[out] md        Message digest
 *
 * The values of paramater \e text_len must represent bytes.
 *
 * On success, the result is written to \e md and has a length of
 * \c DIGEST_SHA1_160_LEN bytes.
 *
 * \return
 * - 0 on success
 * - -1 on error
 */

int  digest_sha1_160(const char*  text, size_t  text_len, unsigned char*  md)
{
   return(digest_openssl(DIGEST_ALG_SHA1_160, text, text_len, md));
}


/* ========================================================================== */
/*! \brief Secure Hash Algorithm SHA2-256
 *
 * \param[in]  text      Message
 * \param[in]  text_len  Message length
 * \param[out] md        Message digest
 *
 * The values of paramater \e text_len must represent bytes.
 *
 * On success, the result is written to \e md and has a length of
 * \c DIGEST_SHA2_256_LEN bytes.
 *
 * \return
 * - 0 on success
 * - -1 on error
 */

int  digest_sha2_256(const char*  text, size_t  text_len, unsigned char*  md)
{
   return(digest_openssl(DIGEST_ALG_SHA2_256, text, text_len, md));
}


/* ========================================================================== */
/*! \brief Initialize message digest module */

void  digest_init(void)
{
   return;
}


/* ========================================================================== */
/*! \brief Shutdown message digest module */

void  digest_exit(void)
{
   /*
    * Note:
    * \c EVP_cleanup() must be called by the exit handler of the TLS module.
    */

   return;
}


/*! @} */

/* EOF */
