/* ========================================================================== */
/*! \file
 * \brief Security related functions
 *
 * Copyright (c) 2017-2020 by the developers. See the LICENSE file for details.
 * If nothing else is specified, functions return zero to indicate success
 * and a negative value to indicate an error.
 *
 * Using a separate source file for this make it easier to use different
 * compiler flags. It will also make it harder for optimizers to break the
 * intended functionality of the functions in this file.
 */


/* ========================================================================== */
/* 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/crypto.h>
#  include <openssl/rand.h>
#endif  /*  CFG_USE_TLS */
#include <string.h>
#include "fileutils.h"
#include "main.h"
#include "secure.h"


/* ========================================================================== */
/*! \addtogroup MAIN */
/*! @{ */


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

/*! \brief Permissions for Cancel-Lock secret file */
#define SECURE_CL_PERM  (posix_mode_t) (POSIX_S_IRUSR | POSIX_S_IWUSR)

/*! \brief Size of secret (64 octets is sufficient for the SHA2 family) */
#define SECURE_CL_SECSIZE  (size_t) 64


/* ========================================================================== */
/* Variables */

/*! \brief Message prefix for security related functions */
#define MAIN_ERR_PREFIX  "MAIN: "


/* ========================================================================== */
/*! \brief Remove string from memory
 *
 * \param[in] p    Pointer to memory block
 * \param[in] len  Number of bytes to clear
 *
 * This function overwrites \e len bytes starting at location \e p with
 * undefined data.
 *
 * \note
 * One or both parameters are allowed to be zero. This function executes as NOP
 * in this case.
 *
 * \attention
 * This function is currently only secure if TLS support is available (because
 * it share a cryptographic function provided by OpenSSL).
 */

void  secure_clear_memory(char*  p, size_t  len)
{
   if (NULL != p && len)
   {
#if CFG_USE_TLS
      /* Older versions of OpenSSL may crash if 'len' is zero */
      OPENSSL_cleanse((void*) p, len);
#else  /*  CFG_USE_TLS */
      /*
       * This is insecure: There is no portable standard C90 way to do it
       *
       * The 'volatile' qualifier is not handled as required for this purpose by
       * some compilers (e.g. GCC 3.x).
       * Modern compilers with LTO support use optimization beyond object file
       * boundaries and may remove the whole function call (if they think the
       * overwritten memory locations are not read again).
       *
       * If this branch of the code is required to work as intended, the
       * optimizer of the compiler must be disabled.
       */
      memset((void*) p, 42, len);
#endif  /*  CFG_USE_TLS */
   }

   return;
}


/* ========================================================================== */
/*! \brief Remove string from memory
 *
 * \param[in] p  Pointer to string
 *
 * \attention
 * \e p must point to a \c NUL terminated string.
 *
 * The complete string is overwritten and then \c NUL is written to the first
 * byte. The function therefore returns with \e p pointing to an empty string.
 *
 * \attention
 * This function is currently only secure if TLS support is available (because
 * it share a cryptographic function provided by OpenSSL).
 */

void  secure_clear_string(char*  p)
{
   secure_clear_memory(p, strlen(p));
   p[0] = 0;

   return;
}


/* ========================================================================== */
/*! \brief Generate file with new secret if CL secret file is missing
 *
 * \param[in] pathname  Pathname string of CL secret file
 *
 * If \e pathname is \c NULL no operation is excecuted.
 *
 * \attention
 * This function currently only works if TLS support is available (because it
 * uses the cryptographic PRNG provided by OpenSSL).
 * The caller must ensure, that the OpenSSL PRNG is seeded!
 */

void  secure_cl_secret(const char*  pathname)
{
#if CFG_USE_TLS
   int  rv;
   int  fd = -1;
   char  buf[SECURE_CL_SECSIZE];  /* Do not initialize buffer! */

   if(NULL != pathname)
   {
      rv = fu_check_file(pathname, NULL);
      if(-1 == rv && POSIX_ENOENT == posix_errno)
      {
         /* CL secret file for scheme SHA256 not present */
         printf("%s: %s%s\n", CFG_NAME, MAIN_ERR_PREFIX,
                "Generate CL secret for scheme SHA256");
         rv = fu_open_file(pathname, &fd, POSIX_O_WRONLY | POSIX_O_CREAT,
                           SECURE_CL_PERM);
         if(rv)  { PRINT_ERROR("Creation of CL secret file failed"); }
         else
         {
            /*
             * Read 512 Bit (64 octets) random data from crypto PRNG
             *
             * The content of the buffer is mixed into the entropy pool by
             * RAND_bytes(3). The buffer should therefore be uninitialized.
             */
            rv = RAND_bytes((unsigned char*) buf, (int) SECURE_CL_SECSIZE);
            if(1 != rv)
            {
               /* Failed => Remove CL secret file again */
               PRINT_ERROR("Not enough random data from PRNG");
               fu_close_file(&fd, NULL);
               (void) fu_unlink_file(pathname);
            }
            else
            {
               /* Write random data to CL secret file */
               rv = fu_write_to_filedesc(fd, buf, (size_t) SECURE_CL_SECSIZE);
               secure_clear_memory(buf, SECURE_CL_SECSIZE);
               if(rv)  { PRINT_ERROR("Writing to CL secret file failed"); }
               fu_close_file(&fd, NULL);
            }
         }
      }
   }
#endif  /*  CFG_USE_TLS */

   return;
}


/*! @} */

/* EOF */
