/* ========================================================================== */
/*! \file
 * \brief Transport layer security
 *
 * 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/crypto.h>
#  include <openssl/asn1.h>
#  include <openssl/bio.h>
#  include <openssl/err.h>
#  include <openssl/evp.h>
#  include <openssl/objects.h>
#  include <openssl/opensslv.h>
#  include <openssl/rand.h>
#  include <openssl/rsa.h>
#  include <openssl/ssl.h>
#  include <openssl/x509.h>
#  include <openssl/x509v3.h>
#  include <openssl/x509_vfy.h>
#endif  /* CFG_USE_TLS */
#include <string.h>

#include "conf.h"      /* For CRL update configuration */
#include "digest.h"    /* For public key randomart images */
#include "encoding.h"  /* For CRL update timestamp conversion */
#include "extutils.h"  /* For CRL download */
#include "fileutils.h"
#include "main.h"
#include "tls.h"
#include "xdg.h"


/* ========================================================================== */
/*! \defgroup TLS TLS: Transport Layer Security
 *
 * This module provides optional support for an encrypted connection to the
 * server. It is intended to follow RFC 2246, RFC 4346, RFC 5246, RFC 7366,
 * RFC 7507, RFC 7627 and RFC 8446 (depending on the underlying OpenSSL
 * library). OpenSSL 1.0.0 is the oldest supported version.
 * For full functionality at least OpenSSL 3.0.0 is required.
 *
 * The server to client authentication based on X.509 certificates should be
 * RFC 1422 and RFC 5280 conformant.
 *
 * \attention
 * It is required that 'SSIZE_MAX' is at least 'INT_MAX' (must be checked by
 * build system).
 *
 * \attention
 * We have no own source for random numbers. OpenSSL must be configured to be
 * able to fetch random numbers itself. If the operating system provides no
 * random number generator (RNG), there must be a separate one installed that
 * OpenSSL can use (consider egd or prngd if your OS provides no such option).
 * If OpenSSL reports there is no RNG available, the program fails to start.
 *
 * By default only cipher suites using ephemeral Diffie-Hellman-Merkle key
 * exchange (to provide forward secrecy) and strong symmetric data encryption
 * algorithms are offered to the server.
 * In the past (until RFC 8143 was released), RFC 4642 defined the weak cipher
 * suite RSA-RSA-RC4-MD5 as mandatory. It can still be added to the list
 * together with some other cipher suites that are considered weak (without
 * forward secrecy or based on elliptic curves). This option is selected with
 * a parameter of the \c tls_open() function.
 * This will also add the weak cipher suite RSA-RSA-AES128-SHA that is defined
 * as mandatory by RFC 5246.
 *
 * According to RFC 6176 and RFC 7568 the SSL protocol is never negotiated and
 * the TLS protocol used instead.
 *
 * We never offer anonymous cipher suites to the server. Therefore while the
 * encrypted connection is established, the server must present a certificate.
 * This certificate can optionally be verified for server to client
 * authentication with the function \c tls_cert_verify() . This will ensure
 * that a trusted CA is the root of the certificate chain and that the subject
 * of the last certificate contains the hostname we have resolved to connect the
 * server.
 */
/*! @{ */


#if CFG_USE_TLS


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

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

/*! \brief Set this to 1 to accept wildcard for left-most component of X.509
 * certificate subjects
 *
 * \note
 * RFC 8143 specifies this as valid with regard to the NNTP protocol, therefore
 * the value 1 is the default.
 */
#  define CFG_USE_TLS_WILDCARD_SUBJECT  1

/*! \brief Cipher suite list for TLSv1.3
 *
 * This list of cipher suites is ignored for older protocol versions.
 */
#  define TLS_CIPHERS_TLS13  "TLS_AES_256_GCM_SHA384" \
                             ":TLS_CHACHA20_POLY1305_SHA256" \
                             ":TLS_AES_128_GCM_SHA256" \
                             ":TLS_AES_128_CCM_SHA256"

/*! \brief Default (strong) cipher suite list for TLSv1.0 to TLSv1.2 protocols
 *
 * This list of cipher suites is the default. It is possible to use weaker
 * cipher suites when a connection is opened. See \ref tls_open() for details.
 *
 * This list contains only cipher suites that provide forward secrecy by using
 * ephemeral Diffie-Hellman-Merkle key exchange.
 *
 * TLSv1.2 protocol is preferred (if the OpenSSL library supports it).
 *
 * Cipher suites that use DSA or ECDSA based authentication are excluded
 * because both are very dependent on a high quality RNG for its nonce and the
 * key length of DSA for TLS is effectively limited to 1024 bit.
 *
 * ECC-based key exchange is excluded because the DHE group check code currently
 * can't handle it.
 *
 * \note
 * The size of the FFDHE group provided by the server is relevant for the
 * strength of the encryption and the forward secrecy. Unfortunately it can't
 * be checked with OpenSSL 1.0 (at least OpenSSL 1.1 API required).
 * If the OpenSSL 3 API is available, the FFDHE groups defined in RFC 7919 are
 * offered for negotiation.
 *
 * \note
 * The tag \c !SSLv2 in the list means the corresponding cipher suites not the
 * SSLv2 protocol.
 *
 * \attention
 * Ensure that this list never contains anonymous cipher suites. Otherwise the
 * server doesn't present a certificate and the authenticity check will fail.
 */
#  define TLS_CIPHERS_DEFAULT \
   "!SSLv2:!RC2:!RC4:!DES:!3DES:!MD5:!kEECDH:!aNULL:!aDSS:!aECDSA" \
   ":kEDH+HIGH@STRENGTH:-SSLv3:kEDH+HIGH:!AESCCM8"

/*! \brief Optional (weak) cipher suite list for TLSv1.0 to TLSv1.2 protocols
 *
 * This list contains cipher suites that are considered weak for better
 * compatibility to existing servers. Some obsolete standards like RFC 4642
 * defined some of the weak algorithms as mandatory in the past and we support
 * them for backward compatibility.
 *
 * \note
 * The tag \c !SSLv2 in the list means the corresponding cipher suites not the
 * SSLv2 protocol.
 *
 * \attention
 * Ensure that this list never contains anonymous cipher suites. Otherwise the
 * server doesn't present a certificate and the authenticity check will fail.
 */
#  define TLS_CIPHERS_WEAK \
   "!SSLv2:!RC2:!DES:!aNULL:!aDSS:!aECDSA" \
   /* Strong symmetric algorithms and forward secrecy first */ \
   ":kEDH+HIGH:kEECDH+HIGH:-SSLv3:kEDH+HIGH:kEECDH+HIGH" \
   ":-3DES:kEDH+HIGH:kEECDH+HIGH" \
   /* Strong symmetric algorithms and no forward secrecy next */ \
   ":AES256-GCM-SHA384:CAMELLIA256-SHA:AES128-SHA" \
   /* Weak symmetric algorithms and no forward secrecy last */ \
   ":RC4-MD5"

/*! \brief Additional signature algorithms for TLSv1.3 protocol
 *
 * This list contains the signature algorithms offered in order of decreasing
 * preference.
 *
 * For TLSv1.3 the signature scheme names as defined in RFC 8446 (Section 4.2.3)
 * are supported by OpenSSL. We use them here to make it obvious which codes
 * will be offered in the Client Hello message.
 *
 * \attention
 * RFC 8446 defines the algorithm ecdsa_secp256r1_sha256 as mandatory for the
 * TLSv1.3 protocol. Avoiding ECC is no longer allowed!
 *
 * \note
 * The algorithms for TLSv1.2 above use RSASSA-PKCS1-v1_5. For TLSv1.3 they
 * are no longer allowed except for certificates. We omit the separate
 * negotiation for the certificate signature algorithms. The old algorithms
 * below are appended to the end of this list (with lowest preference).
 */
#  define TLS_SIGALGS_TLS13 \
   "rsa_pss_rsae_sha512:rsa_pss_rsae_sha384:rsa_pss_rsae_sha256" \
   /* Offer ECC-based signatures for TLSv1.3 only */ \
   ":ecdsa_secp521r1_sha512:ecdsa_secp256r1_sha256"

/*! \brief Signature algorithms for TLSv1.2 protocol
 *
 * This list contains the signature algorithms offered in order of decreasing
 * preference.
 *
 * The signature algorithm cannot be negotiated with the TLSv1.0 and TLSv1.1
 * protocols.
 * This algorithm list is intended for all signatures in the TLSv1.2 protocol
 * (including key exchange and signatures in certificates).
 *
 * We only offer hash algorithms from the SHA-2 family (this means MD5 and SHA-1
 * are not used for negotiation).
 *
 * \attention
 * The asymmetric cipher must always be RSA because certificates with DSA or
 * ECDSA keys are not supported for TLSv1.2 in strong mode.
 */
#  define TLS_SIGALGS  "RSA+SHA512:RSA+SHA384:RSA+SHA256"

/*! \brief ECDHE groups for TLSv1.3 protocol */
#  define TLS_ECDHE_GROUPS_TLS13  "X448:X25519:P-256"

/*! \brief FFDHE groups for TLSv1.2 and TLSv1.3 protocols
 *
 * \attention
 * This list must not contain ECDHE groups because the group check code for
 * TLSv1.2 currently doesn't support ECC!
 */
#  define TLS_FFDHE_GROUPS  "ffdhe8192:ffdhe6144:ffdhe4096:ffdhe3072"


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

static posix_locale_t  tls_loc_ctype_posix = 0;
static SSL_CTX*  tls_ctx = NULL;  /* Global context for all connections */
#  if CFG_USE_OPENSSL_API_1_1
static SSL_CONF_CTX*  cctx;  /* Global configuration context */
#  endif  /* CFG_USE_OPENSSL_API_1_1 */
static const char*  tls_certpath = NULL;
#  if !CFG_TLS_CRLS_DISABLE
static const char*  tls_crlpath = NULL;
static int  tls_crl_update_skip = 0;  /* Don't update CRLs if nonzero */
#  endif  /* !CFG_TLS_CRLS_DISABLE */


/* ========================================================================== */
/* Check for root certificate
 *
 * \param[in]  cc       Pointer to certificate
 * \param[out] is_root  Result
 * \param[in]  bufsize  Size of name buffers \e buf_i and \e buf_s
 * \param[out] buf_i    Pointer to issuer name buffer
 * \param[out] buf_s    Pointer to subject name buffer
 *
 * \note
 * If the caller is not interested in the issuer or subject name, \e b_i and/or
 * \e b_s are allowed to be \c NULL . If both are \c NULL , \e bufsize is
 * ignored.
 */

static int  tls_cert_check_root(X509*  cc, int*  is_root, size_t  bufsize,
                                char*  buf_i, char*  buf_s)
{
   int  res = 0;
   BIO*  biop = BIO_new(BIO_s_mem());
   X509_NAME*  issuer;
   X509_NAME*  subject;
   char*  b_i = NULL;
   char*  b_s = NULL;
   char*  p = NULL;
   size_t  len = 0;

   /* Check BIO */
   if(NULL == biop)
   {
      PRINT_ERROR("Creating BIO failed");
      res = -1;
   }

   /* Get Issuer of certificate */
   if(!res)
   {
      issuer = X509_get_issuer_name(cc);
      if(NULL == issuer)
      {
         PRINT_ERROR("Extraction of issuer from X.509 certificate failed");
         res = -1;
      }
      else
      {
         X509_NAME_print_ex(biop, issuer, 0, XN_FLAG_ONELINE);
         len = (size_t) BIO_get_mem_data(biop, &p);
         if(!len)  { res = -1; }
         else
         {
            b_i = (char*) posix_malloc(len + (size_t) 1);
            if(NULL == b_i)  { res = -1; }
            else
            {
               memcpy((void*) b_i, (void*) p, len);
               b_i[len] = 0;
            }
         }
      }
   }

   /* Get Subject of certificate */
   if(!res)
   {
      if(1 != BIO_reset(biop))  { res = -1; }
      else
      {
         subject = X509_get_subject_name(cc);
         if(NULL == subject)
         {
            PRINT_ERROR("Extraction of subject from X.509 certificate "
                        "failed");
            res = -1;
         }
         else
         {
            X509_NAME_print_ex(biop, subject, 0, XN_FLAG_ONELINE);
            len = (size_t) BIO_get_mem_data(biop, &p);
            if(!len)  { res = -1; }
            else
            {
               b_s = (char*) posix_malloc(len + (size_t) 1);
               if(NULL == b_s)  { res = -1; }
               else
               {
                  memcpy((void*) b_s, (void*) p, len);
                  b_s[len] = 0;
               }
            }
         }
      }
   }

   /* Compare subject with issuer */
   if(!res)
   {
      *is_root = 0;
      if(!strcmp(b_i, b_s))
      {
         /* Self signed (root) certificate */
         *is_root = 1;
      }
   }

   /* Return strings (truncate them if necessary) */
   if(!res && bufsize)
   {
      if(NULL != buf_i)
      {
         len = strlen(b_i);
         if(bufsize - (size_t) 1 < len)  { len = bufsize - (size_t) 1; }
         memcpy((void*) buf_i, (void*) b_i, len);
         buf_i[len] = 0;
      }
      if(NULL != buf_s)
      {
         len = strlen(b_s);
         if(bufsize - (size_t) 1 < len)  { len = bufsize - (size_t) 1; }
         memcpy((void*) buf_s, (void*) b_s, len);
         buf_s[len] = 0;
      }
   }

   /* Release memory */
   posix_free((void*) b_i);
   posix_free((void*) b_s);
   if(NULL != biop)  { BIO_free(biop); }

   return(res);
}


/* ========================================================================== */
/* Get certificate directory path
 *
 * \param[out] certpath  Pointer to certificate path
 *
 * The caller is responsible to free the memory for the buffer on success.
 */

static int  tls_get_certpath(const char**  certpath)
{
   static const char  certdir[] = "certs";
   const char*  confdir = xdg_get_confdir(CFG_NAME);
   int  res = -1;
   int  rv;

   /* Init result so that 'free()' can be called in all cases */
   *certpath = NULL;

   if(NULL != confdir)
   {
      *certpath = confdir;
      rv = xdg_append_to_path(certpath, certdir);
      if(0 == rv)
      {
         /* Create database directory if it doesn't exist */
         res = fu_create_path(*certpath, (posix_mode_t) POSIX_S_IRWXU);
      }
   }

   /* Free memory on error */
   if(res)
   {
      PRINT_ERROR("Cannot create certificate directory");
      posix_free((void*) *certpath);
      *certpath = NULL;
   }

   return(res);
}


#  if !CFG_TLS_CRLS_DISABLE


/* ========================================================================== */
/* Check for elapsed CRL update interval
 *
 * \return
 * - 0 if CRLs are usable
 * - 1 for elapsed update interval
 * - -1 on error (if calculation failed)
 */

static int  tls_check_crl_age(void)
{
   int  res;
   posix_time_t  ts;
   core_time_t  pts_last;
   core_time_t  pts_current;
   core_time_t  age;
   core_time_t  interval = 0;

   res = enc_convert_iso8601_to_posix(&pts_last, config[CONF_CRL_UPD_TS].val.s);
   if(!res)
   {
      posix_time(&ts);
      if((posix_time_t) 0 > ts)  { res = -1; }
      else
      {
         pts_current = (core_time_t) ts;
         if(pts_last > pts_current)
         {
            PRINT_ERROR("Last CRL update timestamp is in the future");
            res = -1;
         }
         else
         {
            age = pts_current - pts_last;
            age /= (core_time_t) 3600;
            /* Clamp negative intervals to zero */
            if(0 > config[CONF_CRL_UPD_IV].val.i)
            {
               PRINT_ERROR("Negative CRL update interval was set to zero");
               config[CONF_CRL_UPD_IV].val.i = 0;
            }
            /* Clamp to 1 year upper limit */
            if(8760 < config[CONF_CRL_UPD_IV].val.i)
            {
               PRINT_ERROR("Too large CRL update interval was clamped");
               config[CONF_CRL_UPD_IV].val.i = 8760;
            }
            interval = (core_time_t) config[CONF_CRL_UPD_IV].val.i;
            if(interval < age)  { res = 1; }
         }
      }
   }

   /* Check for error */
   if(0 > res)  { PRINT_ERROR("CRL update interval check failed"); }

   return(res);
}


/* ========================================================================== */
/* Update CRL update timestamp */

static int  tls_set_crl_age(void)
{
   int  res;
   char  isodate[21];

   res = enc_get_iso8601_utc(isodate);
   if(!res)  { res = conf_string_replace(&config[CONF_CRL_UPD_TS], isodate); }

   return(res);
}


/* ========================================================================== */
/* Reset CRL update timestamp to force update */

static int  tls_reset_crl_age(void)
{
   int  res;

   res = conf_string_replace(&config[CONF_CRL_UPD_TS], "1970-01-01T00:00:00Z");

   return(res);
}


/* ========================================================================== */
/* Get CRL directory path
 *
 * \param[out] certpath  Pointer to CRL path
 *
 * The caller is responsible to free the memory for the buffer on success.
 */

static int  tls_get_crlpath(const char**  crlpath)
{
   static const char  crldir[] = "crls";
   const char*  confdir = xdg_get_confdir(CFG_NAME);
   int  res = -1;
   int  rv;

   /* Init result so that 'free()' can be called in all cases */
   *crlpath = NULL;

   if(NULL != confdir)
   {
      *crlpath = confdir;
      rv = xdg_append_to_path(crlpath, crldir);
      if(0 == rv)
      {
         /* Create database directory if it doesn't exist */
         res = fu_create_path(*crlpath, (posix_mode_t) POSIX_S_IRWXU);
      }
   }

   /* Free memory on error */
   if(res)
   {
      PRINT_ERROR("Cannot create CRL directory");
      posix_free((void*) *crlpath);
      *crlpath = NULL;
   }

   return(res);
}


/* ========================================================================== */
/* Fetch CRL from distribution point
 *
 * \param[in]  cci  Index in certificate chain
 * \param[in]  uri  URI or certificate CRL to download
 * \param[out] pn   Pathname of downloaded file
 *
 * Using the filenames from the URIs of the certificates may lead to filename
 * conflicts. Generic filenames are used (based on the index in the certificate
 * chain) instead.
 *
 * On success the pathname of the file is written to \e pn and the caller is
 * responsible to free the memory allocated for the buffer.
 */

static int  tls_download_crl(int  cci, const char*  uri, char**  pn)
{
   int  res = -1;
   int  rv;
   char  filename[8] = { '/', 'C', 'R', 'L', '_', '_', '_', 0 };
   char*  pathname = NULL;
   size_t  len;

   if(NULL == tls_crlpath)
   {
      PRINT_ERROR("Download of CRL failed, directory not available");
   }
   else
   {
      if(0 > cci || 999 < cci)
      {
         PRINT_ERROR("Download of CRL failed, invalid chain index");
      }
      else
      {
         rv = posix_snprintf(&filename[4], 4, "%d", cci);
         if(1 > rv || 3 < rv)
         {
            PRINT_ERROR("Download of CRL failed, filename invalid");
         }
         else
         {
            len = strlen(tls_crlpath);
            len += strlen(filename);
            pathname = (char*) posix_malloc(++len);
            if(NULL != pathname)
            {
               strcpy(pathname, tls_crlpath);
               strcat(pathname, filename);
               res = ext_download_file(pathname, uri);
            }
         }
      }
   }
   *pn = pathname;

   /* Release memory for buffer on error */
   if(res)
   {
      posix_free((void*) *pn);
      *pn = NULL;
   }

   return(res);
}


/* ========================================================================== */
/* Convert CRL from DER to PEM format
 *
 * \param[in]  pn_der  Pathname of CRL in DER format
 * \param[out] pn_pem  Pathname of CRL in PEM format
 *
 * On success the pathname of the PEM file is written to \e pn_pem and the
 * caller is responsible to free the memory allocated for the buffer.
 */

static int  tls_convert_crl_to_pem(const char*  pn_der, char**  pn_pem)
{
   int  res = 0;
   int  rv;
   BIO*  biop;
   X509_CRL*  crl = NULL;
   size_t  len;
   int  fd;
   char  pem_cmp[] = "-----BEGIN X509 CRL-----";  /* ASCII armor of PEM */
   char  pem_buf[] = "                        ";

   /* Prepare target filename */
   len = strlen(pn_der);
   len += 4;  /* for ".pem" Extension */
   *pn_pem = (char*) posix_malloc(++len);
   if(NULL == *pn_pem)
   {
      PRINT_ERROR("Memory allocation failed for filename");
      res = -1;
   }
   else
   {
      strcpy(*pn_pem, pn_der);
      strcat(*pn_pem, ".pem");
   }

   /* Load CRL */
   if(!res)
   {
      res = -1;
      biop = BIO_new(BIO_s_file());
      if(NULL != biop)
      {
         rv = BIO_read_filename(biop, pn_der);
         if(1 != rv)
         {
            PRINT_ERROR("BIO error while configuring DER filename of CRL");
         }
         else
         {
            /* Check whether CRL already is in PEM format */
            len = strlen(pem_cmp);
            rv = BIO_read(biop, pem_buf, len);
            if(len == rv)
            {
               if(!memcmp((void*) pem_cmp, (void*) pem_buf, len))
               {
                  /* CRL already in PEM format */
                  res = 1;
               }
               else
               {
                  /* Assume CRL is in DER format */
                  rv = BIO_seek(biop, 0);
                  if(0 != rv)
                  {
                     PRINT_ERROR("I/O error while loading CRL");
                  }
                  else
                  {
                     crl = d2i_X509_CRL_bio(biop, NULL);
                     if(NULL == crl)
                     {
                        PRINT_ERROR("Loading CRL to memory failed");
                     }
                     else
                     {
                        /* CRL loaded to memory */
                        res = 0;
                     }
                  }
               }
            }
         }
         BIO_free(biop);
      }
   }

   /* Convert and store CRL */
   if(!res)
   {
      res = -1;
      /* Create CRL file with desired permissions, then reopen it via BIO */
      /* Attention: Calling 'umask()' would affect other threads! */
      rv = fu_open_file(*pn_pem, &fd, POSIX_O_WRONLY | POSIX_O_CREAT,
                        POSIX_S_IRUSR | POSIX_S_IWUSR);
      if(!rv)
      {
         fu_close_file(&fd, NULL);
         biop = BIO_new(BIO_s_file());
         if(NULL != biop)
         {
            rv = BIO_write_filename(biop, *pn_pem);
            if(1 != rv)
            {
               PRINT_ERROR(""
                           "BIO error while configuring PEM filename of CRL");
            }
            else
            {
               rv = PEM_write_bio_X509_CRL(biop, crl);
               if(1 != rv)
               {
                  PRINT_ERROR("Converting and storing CRL failed");
               }
               else
               {
                  /* CRL stored in PEM format */
                  rv = BIO_get_fd(biop, NULL);
                  if(-1 != rv)  { res = fu_sync(rv, NULL); }
               }
            }
            BIO_free(biop);
         }
      }
   }

   /* Create symlink if CRL is already in PEM format */
   if(1 == res)
   {
      res = posix_symlink(pn_der, *pn_pem);
   }

   /* Release memory for buffer on error */
   if(res)
   {
      PRINT_ERROR("Conversion of CRL to PEM format failed");
      posix_free((void*) *pn_pem);
      *pn_pem = NULL;
   }

   return(res);
}


/* ========================================================================== */
/* Create hash symlink to CRL
 *
 * \param[in] pn_pem  Pathname of CRL in PEM format
 * \param[in] cert    Pointer to certificate
 *
 * The symlinks to CRLs need to have the following format for OpenSSL 1.0:
 * \c HHHHHHHH.rD
 *
 * The field \c H is the hash that is searched for. The field \c D is used to
 * distinguish duplicates (identical hash values for different files).
 *
 * \attention
 * The caller is responsible to delete old symlinks in the directory where
 * \e pn_pem resides before calling this function.
 */

static int  tls_create_hash_symlink(const char*  pn_pem, X509*  cert)
{
   int  res = -1;
   int  rv;
   unsigned long int  issuer_hash;
   size_t  len;
   char  name_symlink[13];
   char*  pn_symlink = NULL;
   unsigned int  i = 0;
   int  abort = 0;

   if(NULL == tls_crlpath)
   {
      PRINT_ERROR("Creating of symlink failed, directory not available");
   }
   else
   {
      /* Limit to 9 duplicate names */
      while(!abort && 9U >= i)
      {
         issuer_hash = X509_issuer_name_hash(cert);
         /* See 'c_rehash' manual for the name format */
         rv = posix_snprintf(name_symlink, 13, "/%08lx.r%u", issuer_hash, i);
         if(12 != rv)
         {
            PRINT_ERROR("Creating of symlink failed, name not valid");
            abort = 1;
         }
         else
         {
            len = strlen(tls_crlpath);
            len += strlen(name_symlink);
            pn_symlink = (char*) posix_malloc(++len);
            if(NULL == pn_symlink)  { abort = 1; }
            else
            {
               strcpy(pn_symlink, tls_crlpath);
               strcat(pn_symlink, name_symlink);
               res = posix_symlink(pn_pem, pn_symlink);
               if(!res)
               {
                  /* Success */
                  abort = 1;
               }
               else
               {
                  /* Check whether symlink already exist */
                  if(!(-1 == res && POSIX_EEXIST == posix_errno))
                  {
                     /* No => Creation failed => Abort */
                     PRINT_ERROR("Creating of symlink failed");
                     abort = 1;
                  }
               }
               posix_free((void*) pn_symlink);
            }
         }
         ++i;
      }
      /* Check whether duplicate numbers gone out */
      if(9 < i)
      {
         PRINT_ERROR("Creating of symlink failed, too many duplicates");
      }
   }

   return(res);
}


/* ========================================================================== */
/* Get mutex for CRL update
 *
 * \param[out] fd  Pointer to filedescriptor of lockfile
 */

static int  tls_crl_get_lock(int*  fd)
{
   int  res = -1;
   const char  crl_lockfile[] = ".crl_lock";
   const char*  lockpathname = NULL;
   int  rv;

   lockpathname = xdg_get_confdir(CFG_NAME);
   if(NULL != lockpathname)
   {
      rv = fu_create_path(lockpathname, (posix_mode_t) POSIX_S_IRWXU);
      if(0 == rv)
      {
         /* Store lock file pathname */
         rv = xdg_append_to_path(&lockpathname, crl_lockfile);
         if(0 == rv)
         {
            rv = fu_open_file(lockpathname, fd,
                              POSIX_O_WRONLY | POSIX_O_CREAT,
                              POSIX_S_IRUSR | POSIX_S_IWUSR);
            if(rv)
            {
               PRINT_ERROR("Cannot open mutex file for CRL directory");
            }
            else
            {
               if(fu_lock_file(*fd))
               {
                  PRINT_ERROR("Cannot lock mutex file for CRL directory");
               }
               else
               {
                  /* Mutex for CRL directory claimed */
                  res = 0;
               }
            }
         }
      }
   }
   posix_free((void*) lockpathname);

   return(res);
}


/* ========================================================================== */
/* Release mutex for CRL update
 *
 * \param[in,out] fd  Pointer to filedescriptor of lockfile
 */

static void  tls_crl_release_lock(int*  fd)
{
   fu_close_file(fd, NULL);
}


/* ========================================================================== */
/* Update CRLs for certificate chain
 *
 * \param[in] certificate_chain  Pointer to certificate chain
 *
 * \return
 * - 0 on success
 * - Positive value if no CRLs are required (self signed server certificate)
 * - -1 on unknown error
 * - -2 if the lock for the mutex file can't be acquired
 */

static int  tls_update_crls(STACK_OF(X509)*  certificate_chain)
{
   int  res = -1;
   int  rv;
   int  crl_valid = 0;
   int  crls_required = 0;
   int  ccl;  /* Length of certificate chain */
   int  cci;  /* Index in certificate chain */
   X509*  cc;  /* Current certificate */
   int  num, num2;
   int  i, i2;
   STACK_OF(DIST_POINT)*  dp = NULL;
   DIST_POINT*  dpi;
   GENERAL_NAME*  ani;
   const size_t  buflen = 256;
   char  b_i[buflen];
   int  is_root = 0;
   char*  p = NULL;
   char*  crl_pn_der;  /* Pathname to raw CRL (expected in DER format) */
   char*  crl_pn_pem;  /* Pathname of cooked CRL (in PEM format) */
   int  crlpath_ok = 0;
   int  lockfd = -1;

   /* Prepare and lock mutex file for CRL update */
   if(tls_crl_get_lock(&lockfd))  { res = -2; }
   else
   {
      /* Got lock => Automatic CRL update */
      printf("%s: %s-----------------------------------------------------\n",
             CFG_NAME, MAIN_ERR_PREFIX);
      printf("%s: %sAutomatic CRL maintenance\n", CFG_NAME, MAIN_ERR_PREFIX);
      /* Delete CRL directory */
      if(NULL != tls_crlpath)
      {
         if(!fu_delete_tree(tls_crlpath))
         {
            if(!posix_mkdir(tls_crlpath, (posix_mode_t) POSIX_S_IRWXU))
            {
               /* Empty CRL directory is now available */
               crlpath_ok = 1;
            }
         }
      }
      if(!crlpath_ok)
      {
         PRINT_ERROR("CRL update failed, directory not available");
      }
      else
      {
         /* Get length of certificate chain */
         ccl = sk_X509_num(certificate_chain);
         for(cci = 0; cci < ccl; ++cci)
         {
            /* Get next certificate */
            cc = sk_X509_value(certificate_chain, cci);
            if(NULL == cc)
            {
               PRINT_ERROR("Error while parsing certificate chain");
               break;
            }
            rv = tls_cert_check_root(cc, &is_root, buflen, b_i, NULL);
            if(rv)  { break; }
            /* Determine type of certificate */
            if(!cci)
            {
               if(is_root)
               {
                  printf("%s: %sSelf signed server certificate:\n",
                         CFG_NAME, MAIN_ERR_PREFIX);
               }
               else
               {
                  printf("%s: %sServer certificate:\n",
                         CFG_NAME, MAIN_ERR_PREFIX);
               }
            }
            else
            {
               if(is_root)
               {
                  printf("%s: %sRoot certificate:\n",
                         CFG_NAME, MAIN_ERR_PREFIX);
               }
               else
               {
                  printf("%s: %sIntermediate certificate:\n",
                         CFG_NAME, MAIN_ERR_PREFIX);
               }
            }
            /* Print issuer */
            printf("%s: %s   Issuer: %s\n", CFG_NAME, MAIN_ERR_PREFIX, b_i);
            /* Get CRL of certificate */
            crl_valid = 0;
            if(is_root)
            {
               printf("%s: %s   Always trusted if installed"
                      " (Must be uninstalled for revocation)\n",
                      CFG_NAME, MAIN_ERR_PREFIX);
               res = 0;
            }
            else
            {
               dp = X509_get_ext_d2i(cc, NID_crl_distribution_points, NULL, 
                                     NULL);
               if(NULL == dp)
               {
                  PRINT_ERROR("   No CRL distribution points found"
                              " in certificate");
               }
               else
               {
                  num2 = sk_DIST_POINT_num(dp);
                  for(i2 = 0; i2 < num2; ++i2)
                  {
                     dpi = sk_DIST_POINT_value(dp, i2);
                     if(NULL != dpi->distpoint)
                     {
                        num = sk_GENERAL_NAME_num(
                                 dpi->distpoint->name.fullname);
                        for(i = 0; i < num; ++i)
                        {
                           ani = sk_GENERAL_NAME_value(
                                    dpi->distpoint->name.fullname, i);
                           if(GEN_URI == ani->type)
                           {
                              p = (char*)
                                  ani->d.uniformResourceIdentifier->data;
                              printf("%s: %s   "
                                     "CRL distribution point %d: %s\n",
                                      CFG_NAME, MAIN_ERR_PREFIX, i2, p);
                              /* Download CRL */
                              crl_pn_der = NULL;
                              crl_pn_pem = NULL;
                              res = tls_download_crl(cci, p, &crl_pn_der);
                              if(!res)
                              {
                                 /* Convert CRL to PEM format */
                                 res = tls_convert_crl_to_pem(crl_pn_der,
                                                              &crl_pn_pem);
                                 if(!res)
                                 {
                                    /* Create symlink to CRL in PEM format */
                                    res = tls_create_hash_symlink(crl_pn_pem,
                                                                  cc);
                                 }
                                 /* Check for success */
                                 if(!res)
                                 {
                                    crl_valid = 1;
                                    crls_required = 1;
                                 }
                              }
                              /* Release memory allocated for pathnames */
                              posix_free((void*) crl_pn_pem);
                              posix_free((void*) crl_pn_der);
                              break;
                           }
                        }
                     }
                     if(crl_valid)  { break; }
                  }
               }
            }
            /* Check for error */
            if(res)  { break; }
         }
      }
      printf("%s: %s-----------------------------------------------------\n",
             CFG_NAME, MAIN_ERR_PREFIX);
      /* Release lock of mutex file */
      tls_crl_release_lock(&lockfd);
      /* Return positive value if no CRLs are required */
      if(!res && !crls_required)  { res = 1; }
   }

   /* Release memory */
   if(NULL != dp)  { CRL_DIST_POINTS_free(dp); }

   return(res);
}


#  endif  /* !CFG_TLS_CRLS_DISABLE */


/* ========================================================================== */
/* Check certificate chain for signing key length and signature hash algorithm
 *
 * \param[in] certificate_chain  Pointer to certificate chain
 * \param[in] proto              Flag indicating TLS protocol minor number
 *
 * \attention
 * Checks for non-RSA keys are not implemented.
 * This should never happen for TLSv1.2 because of the signature algorithm
 * negotiation (we offer only RSA-based signature algorithms for TLSv1.2).
 *
 * If the modulus size of a RSA key is less than 1024 bits, the key is rejected.
 * If the modulus size of a RSA key is less than 3072 bits, a warning message
 * is generated.
 *
 * The signature hash algorithms MD2, MD4 and MD5 are rejected except for the
 * self signature of the root certificate (signature is not used in this case).
 * If the signature hash algorithm is SHA-1 or SHA-224, a warning message is
 * generated. This should never happen for TLSv1.2 because of the signature
 * algorithm negotiation.
 *
 * \return
 * - 0 on success (skipped ECC keys are not counted as errors)
 * - -1 on error
 */

static int  tls_chain_check_sigalgs(STACK_OF(X509)*  certificate_chain,
                                    int  proto)
{
   int  res = -1;
   int  ccl;  /* Length of certificate chain */
   int  cci;  /* Index in certificate chain */
   X509*  cc;  /* Current certificate */
   int  rv;
   posix_ssize_t  rv2;
   int  is_root = 0;
   const char*  s;
   EVP_PKEY*  pkey = NULL;
#  if !CFG_USE_OPENSSL_API_3
   RSA*  rsa_key = NULL;
#  endif  /* !CFG_USE_OPENSSL_API_3 */
   int  rsa_mod_size;
   int  alg;
   const char*  md_s;
   int  md_ok;
   unsigned char*  buf;
   int  len;
   char  prefix[80];

   /* Get length of certificate chain */
   ccl = sk_X509_num(certificate_chain);
   for(cci = 0; cci < ccl; ++cci)
   {
      /* Get next certificate */
      cc = sk_X509_value(certificate_chain, cci);
      if(NULL == cc)
      {
         PRINT_ERROR("Error while parsing certificate chain");
         break;
      }
      /* Check for root certificate */
      rv = tls_cert_check_root(cc, &is_root, 0, NULL, NULL);
      if(rv)  { break; }
      s = "";
      if(!cci)  { s = "(Server) "; }
      if(is_root)  { s = "(Root) "; }
      printf("%s: %sCertificate level %d %sin chain:\n",
             CFG_NAME, MAIN_ERR_PREFIX, cci, s);
      /* Extract public key */
      pkey = X509_extract_key(cc);
      if(NULL == pkey)
      {
         PRINT_ERROR("Extraction of public key from X.509 certificate"
                     " failed");
         break;
      }
#  if  CFG_USE_OPENSSL_API_1_1 || CFG_USE_LIBRESSL_API_3_5
      if(EVP_PKEY_RSA != EVP_PKEY_base_id(pkey))
#  else   /* CFG_USE_OPENSSL_API_1_1 */
      if(EVP_PKEY_RSA != pkey->type)
#  endif   /* CFG_USE_OPENSSL_API_1_1 */
      {
         /* ECC keys are mandatory for TLSv1.3 (return no error in this case) */
         if(3 <= proto)
         {
            PRINT_ERROR("Certificate doesn't use RSA key (skipped)");
            continue;
         }
         PRINT_ERROR("Certificate doesn't use RSA key (not supported)");
         break;
      }
#  if !CFG_USE_OPENSSL_API_3
      rsa_key = EVP_PKEY_get1_RSA(pkey);
      if(NULL == rsa_key)
      {
         PRINT_ERROR("Extraction of key from certificate failed");
         break;
      }
#  endif  /* !CFG_USE_OPENSSL_API_3 */
#  if CFG_USE_OPENSSL_API_3
      rsa_mod_size = EVP_PKEY_bits(pkey);
      if(0 >= rsa_mod_size)
      {
         PRINT_ERROR("Calculation of key size failed");
         break;
      }
#  else  /* CFG_USE_OPENSSL_API_3 */
      rsa_mod_size = RSA_size(rsa_key);
      if(0 >= rsa_mod_size || 8192 < rsa_mod_size)
      {
         PRINT_ERROR("Calculation of key size failed");
         break;
      }
      rsa_mod_size *= 8;
#  endif  /* CFG_USE_OPENSSL_API_3 */
      printf("%s: %s   RSA key modulus size: %d bit\n",
             CFG_NAME, MAIN_ERR_PREFIX, rsa_mod_size);
      if(1024 > rsa_mod_size)
      {
         PRINT_ERROR("RSA key modulus size of certificate rejected");
         break;
      }
      else if(3072 > rsa_mod_size)
      {
         /* 3072 bit would match a 128 bit symmetric cipher */
         printf("%s: %s   Warning: RSA key modulus should be "
                "at least 3072 bit\n", CFG_NAME, MAIN_ERR_PREFIX);
      }
      if(!cci)
      {
         /* Print randomart image for key */
         buf = NULL;
#  if CFG_USE_OPENSSL_API_3
         len = i2d_PUBKEY(pkey, &buf);
#  else  /* CFG_USE_OPENSSL_API_3 */
         len = i2d_RSA_PUBKEY(rsa_key, &buf);
#  endif  /* CFG_USE_OPENSSL_API_3 */
         if(0 > len)
         {
            PRINT_ERROR("Extraction of key fingerprint failed");
            break;
         }
         else
         {
            /* 'SubjectPublicKeyInfo' structure now stored in buf */
            rv2 = posix_snprintf(prefix, 80, "%s: %s   ",
                                 CFG_NAME, MAIN_ERR_PREFIX);
            if(0 > rv2)
            {
               PRINT_ERROR("Creating message prefix failed");
               break;
            }
            rv = digest_randomart(prefix, (const char*) buf, (size_t) len);
            OPENSSL_free(buf);
            if(rv)
            {
               PRINT_ERROR("Creating randomart for key failed");
               break;
            }
         }
      }
#  if !CFG_USE_OPENSSL_API_3
      RSA_free(rsa_key);
#  endif  /* !CFG_USE_OPENSSL_API_3 */
      EVP_PKEY_free(pkey);
      /* Extract hash algorithm for signatures */
#  if CFG_USE_OPENSSL_API_1_1 || CFG_USE_LIBRESSL_API_3_5
      alg = X509_get_signature_nid(cc);
#  else   /* CFG_USE_OPENSSL_API_1_1 */
      alg = OBJ_obj2nid(cc->sig_alg->algorithm);
#  endif   /* CFG_USE_OPENSSL_API_1_1 */
      if(NID_undef == alg)
      {
         PRINT_ERROR("Extraction of hash algorithm failed");
         break;
      }
      md_s = OBJ_nid2sn(alg);
      if(is_root)
      {
         printf("%s: %s   Self signature is not used\n",
                CFG_NAME, MAIN_ERR_PREFIX);
      }
      else
      {
         printf("%s: %s   Signature algorithm: %s\n",
                CFG_NAME, MAIN_ERR_PREFIX, md_s);
         md_ok = 0;
         switch(alg)
         {
            case NID_md2WithRSAEncryption:
            case NID_md4WithRSAEncryption:
            case NID_md5WithRSAEncryption:
            {
               /* Reject weak algorithms */
               PRINT_ERROR("Hash algorithm of signature rejected");
               break;
            }
            case NID_sha1WithRSAEncryption:
            case NID_sha224WithRSAEncryption:
            {
               /* SHA-256 would match a 128 bit symmetric cipher */
               printf("%s: %s   Warning: At least SHA-256 should be "
                     "used for signatures\n", CFG_NAME, MAIN_ERR_PREFIX);
#  if CFG_USE_OPENSSL_API_1_1
               if(2 <= proto)
               {
                  /*
                   * RFC 5246 requires that the certificate chain must use one
                   * of the signature algorithms offered in negotiation.
                   * But we have not offered this algorithm!
                   */
                  PRINT_ERROR("   Server has violated RFC 5246");
               }
#  endif  /* CFG_USE_OPENSSL_API_1_1 */
               md_ok = 1;
               break;
            }
            case NID_sha256WithRSAEncryption:
            case NID_sha384WithRSAEncryption:
            case NID_sha512WithRSAEncryption:
            {
               /* This is what we have offered for negotiation with TLSv1.2 */
               md_ok = 1;
               break;
            }
            default:
            {
               PRINT_ERROR("   Unexpected hash algorithm rejected");
               if(2 <= proto)
               {
                  PRINT_ERROR("   Server has violated RFC 5246");
               }
               break;
            }
         }
         if(!md_ok)  { break; }
      }
   }
   if(cci == ccl)  { res = 0; }

   return(res);
}


/* ========================================================================== */
/* Verify that subject of certificate matches hostname
 *
 * \param[in] cn       Common name from certificate
 * \param[in] subject  Subject to match
 *
 * The comparison is done locale independent and case insensitive.
 *
 * \note
 * A wildcard for the first part of the subject is accepted if
 * \ref CFG_USE_TLS_WILDCARD_SUBJECT is defined to a nonzero value.
 */

static int  tls_subject_check(const char*  cn, const char*  subject)
{
   int  res = 0;
#  if CFG_USE_TLS_WILDCARD_SUBJECT
   int  wildcard = 0;
   const char*  p;

   /* Check for wildcard as first part of subject */
   if('*' == subject[0])
   {
      /* Yes => Compare only parts after first dot */
      wildcard = 1;
      res = -1;
      if('.' == subject[1])
      {
         subject = &subject[2];
         p = strchr(cn, (int) '.');
         if(NULL != p)
         {
            cn = ++p;
            res = 0;
         }
      }
   }
#  endif  /* CFG_USE_TLS_WILDCARD_SUBJECT */
   if(!res)
   {
      /* Compare using POSIX locale */
      res = posix_strcasecmp_l(cn, subject, tls_loc_ctype_posix);
   }
   if(0 < res)  { res = -res; }

   /* Print message for result */
   if(res)  { printf(" (doesn't match hostname)\n"); }
#  if CFG_USE_TLS_WILDCARD_SUBJECT
   else if(wildcard)  { printf(" (matches hostname using wildcard)\n"); }
#  endif  /* CFG_USE_TLS_WILDCARD_SUBJECT */
   else  { printf(" (matches hostname)\n"); }

   return(res);
}


/* ========================================================================== */
/* Create context for TLS connection
 *
 * \param[in]  weak  Offer weak TLS versions to server
 *
 * We negotiate for TLSv1.2 or higher connections according to RFC 8996.
 * If the server doesn't support at least TLSv1.2, \e weak must be set to a
 * nonzero value (to add TLSv1.1 and TLSv1.0 for negotiation).
 *
 * To provide forward secrecy, only cipher suites with ephemeral
 * Diffie-Hellmann-Merkle key exchange are offered by default.
 * Session tickets and compression are disabled.
 *
 * \attention
 * With the initial configuration the server certificate is not automatically
 * checked. The caller must explicitly check the certificate for authentication
 * using the \ref tls_cert_verify() function.
 */

static int  tls_create_context(int  weak)
{
   int  res = 0;
   long int  mode = SSL_MODE_ENABLE_PARTIAL_WRITE
                    | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER;
   long int  options = SSL_OP_NO_TICKET | SSL_OP_NO_COMPRESSION
                       | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
   unsigned long int  parameters = X509_V_FLAG_X509_STRICT;
   X509_VERIFY_PARAM*  para = NULL;
   long  f;
   int  rv;
   const char*  cpp = NULL;
#  if !CFG_TLS_CRLS_DISABLE
   X509_LOOKUP*  look = NULL;

   /* Configure context for CRL checks */
   if(config[CONF_CRL_CHECK].val.i)
   {
      parameters |= X509_V_FLAG_CRL_CHECK
                    | X509_V_FLAG_CRL_CHECK_ALL
                    | X509_V_FLAG_EXTENDED_CRL_SUPPORT
                    | X509_V_FLAG_USE_DELTAS;
   }
#  endif  /* !CFG_TLS_CRLS_DISABLE */

   /* Disable TLSv1.0 and TLSv1.1 according to RFC 8996 */
   if(!weak)
   {
       /* Negotiate TLSv1.1 and TLSv1.0 for weak mode only */
       options |= SSL_OP_NO_TLSv1;
#  ifdef SSL_OP_NO_TLSv1_1
       options |= SSL_OP_NO_TLSv1_1;
#  endif  /* SSL_OP_NO_TLSv1_1 */
   }

   /* Create TLS context for client side */
   tls_ctx = SSL_CTX_new(SSLv23_client_method());
   if(NULL == tls_ctx)
   {
      PRINT_ERROR("Cannot allocate context");
      res = -1;
   }
   if(!res)
   {
#  if CFG_USE_OPENSSL_API_1_1
      /* Disable all internal checks */
      SSL_CTX_set_security_level(tls_ctx, 0);
#  endif   /* CFG_USE_OPENSSL_API_1_1 */
      f = SSL_CTX_set_mode(tls_ctx, mode);
      if((f & mode) != mode)
      {
         PRINT_ERROR("Cannot configure context");
         res = -1;
      }
   }
   if(!res)
   {
      f = SSL_CTX_set_options(tls_ctx, options);
      if((f & options) != options)
      {
         PRINT_ERROR("Cannot set context options");
         res = -1;
      }
   }
#  if CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= 0x10101000L)
   if(!res)
   {
      /* This is used for TLSv1.3 negotiation only */
      rv = SSL_CTX_set_ciphersuites(tls_ctx, TLS_CIPHERS_TLS13);
      if(1 != rv)
      {
         PRINT_ERROR("No usable TLSv1.3 ciphers available, "
                     "check your OpenSSL configuration");
         res = -1;
      }
   }
#  endif   /* CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= xxx) */
   if(!res)
   {
      rv = SSL_CTX_set_cipher_list(tls_ctx, TLS_CIPHERS_DEFAULT);
      if(1 != rv)
      {
         PRINT_ERROR("No usable ciphers with EDH key agreement available, "
                     "check your OpenSSL configuration");
         res = -1;
      }
   }
   if(!res)
   {
      SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_NONE, NULL);  /* We do it manually */
      para = X509_VERIFY_PARAM_new();
      if(NULL == para)
      {
         PRINT_ERROR("Memory allocation for X.509 parameters failed ");
         res = -1;
      }
      else
      {
         rv = X509_VERIFY_PARAM_set_flags(para, parameters);
         if(1 != rv)
         {
            PRINT_ERROR("Cannot set X.509 parameters");
            res = -1;
         }
         if(!res)
         {
            rv = X509_VERIFY_PARAM_set_purpose(para, X509_PURPOSE_SSL_SERVER);
            if(1 != rv)
            {
               PRINT_ERROR("Cannot set X.509 verification purpose");
               res = -1;
            }
         }
         if(!res)
         {
            rv = SSL_CTX_set1_param(tls_ctx, para);
            if(1 != rv)
            {
               PRINT_ERROR("Cannot store X.509 parameters in TLS context");
               res = -1;
            }
         }
         X509_VERIFY_PARAM_free(para);
      }
   }
   if(!res)
   {
      /* OpenSSL up to 1.1.0 API has no SSL_CTX_set1_groups_list() function */
#  if CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= 0x10101000L)
#     if CFG_USE_OPENSSL_API_3
      /* DH group negotiation */
      rv = SSL_CTX_set1_groups_list(tls_ctx, TLS_FFDHE_GROUPS
                                    ":" TLS_ECDHE_GROUPS_TLS13);
#     else   /* CFG_USE_OPENSSL_API_3 */
      /* Workaround (OpenSSL 1.1.1 doesn't support FFDHE groups) */
      printf("%s: %sWorkaround for OpenSSL 1.1.1: "
             "Ignore FFDHE group configuration\n", CFG_NAME, MAIN_ERR_PREFIX);
      /* This is only used for key exchange in TLSv1.3 handshakes */
      rv = SSL_CTX_set1_groups_list(tls_ctx, TLS_ECDHE_GROUPS_TLS13);
#     endif   /* CFG_USE_OPENSSL_API_3 */
      if(1 != rv)
      {
         PRINT_ERROR("Cannot store DHE groups in TLS context");
         res = -1;
      }
#  else  /* CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= 1.1.1) */
#     if CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 0x30101000L)
      /* LibreSSL has client TLSv1.3 support since version 3.1.1 */
      /* Workaround (LibreSSL 3.1.1 doesn't support FFDHE groups) */
      printf("%s: %sWorkaround for LibreSSL: "
             "Ignore FFDHE group configuration\n", CFG_NAME, MAIN_ERR_PREFIX);
      /* The list TLS_ECDHE_GROUPS_TLS13 is rejected too (tested with 3.2.2) */
      rv = SSL_CTX_set1_groups_list(tls_ctx, "X25519:P-256");
      if(1 != rv)
      {
         PRINT_ERROR("Cannot store DHE groups in TLS context");
         res = -1;
      }
#     endif  /* CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 3.1.1) */
#  endif  /* CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= 1.1.1) */
   }

#  if CFG_USE_OPENSSL_API_1_1
   if(!res)
   {
      /* Create configuration context */
      cctx = SSL_CONF_CTX_new();
      if(NULL == cctx)
      {
         PRINT_ERROR("Cannot create TLS configuration context");
         res = -1;
      }
      else
      {
         /* Init configuration context */
         SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE | SSL_CONF_FLAG_CLIENT
                                | SSL_CONF_FLAG_SHOW_ERRORS);
         /* Assign current TLS context to configuration context */
         SSL_CONF_CTX_set_ssl_ctx(cctx, tls_ctx);
         /* Set signature algorithms for TLSv1.2 and higher */
#     if CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= 0x10101000L)
         /*
          * Append the list of signature algorithms for TLSv1.2
          * to allow PKCS1 v1_5 based signatures in certificates
          */
         rv = SSL_CONF_cmd(cctx, "SignatureAlgorithms", TLS_SIGALGS_TLS13
                           ":" TLS_SIGALGS);
#     else  /* CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= 1.1.1) */
         rv = SSL_CONF_cmd(cctx, "SignatureAlgorithms", TLS_SIGALGS);
#     endif  /* CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= 1.1.1) */
         if(2 != rv)
         {
            PRINT_ERROR("Cannot store TLS signature algorithms");
            res = -1;
         }
         else
         {
            /* Attention: The OpenSSL documentation is wrong! */
            rv = SSL_CONF_CTX_finish(cctx);
            if(1 != rv)
            {
               PRINT_ERROR("Cannot store configuration context");
               res = -1;
            }
         }
      }
   }
#  endif  /* CFG_USE_OPENSSL_API_1_1 */

   /* Use local root certificate on request (must be installed manually) */
   if(config[CONF_TLS_OWNCERTS].val.i)
   {
      if(!res)  { res = tls_get_certpath(&cpp); }
      if(!res)
      {
         tls_certpath = cpp;
         printf("%s: %sLocation of X.509 root certificates: %s/\n",
                CFG_NAME, MAIN_ERR_PREFIX, tls_certpath);
#  if CFG_USE_OPENSSL_API_3
         rv = SSL_CTX_load_verify_dir(tls_ctx, tls_certpath);
#  else   /* CFG_USE_OPENSSL_API_3 */
         rv = SSL_CTX_load_verify_locations(tls_ctx, NULL, tls_certpath);
#  endif   /* CFG_USE_OPENSSL_API_3 */
         if(1 != rv)
         {
            PRINT_ERROR("Setup of certificate verification failed");
            res = -1;
         }
      }
   }
   else
   {
      /* Use default certificate location of OpenSSL */
      if(!res)
      {
         rv = SSL_CTX_set_default_verify_paths(tls_ctx);
         if(1 != rv)
         {
            PRINT_ERROR("Setup of certificate verification failed");
            res = -1;
         }
      }
   }

#  if !CFG_TLS_CRLS_DISABLE
   if(!res && config[CONF_CRL_CHECK].val.i)
   {
      /* Use CRLs vor certificate revocation checks */
      res = tls_get_crlpath(&cpp);
      if(!res)
      {
         tls_crlpath = cpp;
         printf("%s: %sLocation of X.509 CRLs: %s/\n",
                CFG_NAME, MAIN_ERR_PREFIX, tls_crlpath);
      }
      if(!res)
      {
         look = X509_STORE_add_lookup(SSL_CTX_get_cert_store(tls_ctx),
                                      X509_LOOKUP_hash_dir());
         if(NULL == look)
         {
            PRINT_ERROR("Cannot add CRL lookup method to TLS context");
            res = -1;
         }
      }
      if(!res)
      {
         rv = X509_LOOKUP_add_dir(look, tls_crlpath, X509_FILETYPE_PEM);
         if(1 != rv)
         {
            PRINT_ERROR("Cannot set CRL path");
            res = -1;
         }
      }
   }
   else
   {
      printf("%s: %sWarning: X.509 certificate revocation checks disabled "
             "in configfile\n", CFG_NAME, MAIN_ERR_PREFIX);
   }
#  else  /* !CFG_TLS_CRLS_DISABLE */
   /* Do not check revocation of certificates */
   if(!res)
   {
      printf("%s: %sWarning: X.509 certificate revocation checks disabled "
             "by configuration\n", CFG_NAME, MAIN_ERR_PREFIX);
   }
#  endif  /* !CFG_TLS_CRLS_DISABLE */

   /* For code review: The Cert/CRL paths are destroyed with the context! */
   return(res);
}


/* ========================================================================== */
/* Destroy context for TLS connection */

static void  tls_destroy_context(void)
{
#  if CFG_USE_OPENSSL_API_1_1
   /* Note: 'cctx' is allowed to be 'NULL' here */
   SSL_CONF_CTX_free(cctx);
#  endif  /* CFG_USE_OPENSSL_API_1_1 */

   if(NULL != tls_ctx)
   {
      SSL_CTX_free(tls_ctx);
      tls_ctx = NULL;
   }

   posix_free((void*) tls_certpath);
   tls_certpath = NULL;
#  if !CFG_TLS_CRLS_DISABLE
   posix_free((void*) tls_crlpath);
   tls_crlpath = NULL;
#  endif  /* !CFG_TLS_CRLS_DISABLE */
}


/* ========================================================================== */
/*! \brief Init TLS subsystem
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  tls_init(void)
{
   int  res = 0;

   /* Create a locale object with LC_CTYPE == POSIX */
   tls_loc_ctype_posix = posix_newlocale(POSIX_LC_CTYPE_MASK, "POSIX",
                                         (posix_locale_t) 0);
   if((posix_locale_t) 0 == tls_loc_ctype_posix)
   {
      PRINT_ERROR("Cannot create locale object");
      res = -1;
   }

   if(!res)
   {
      /*
       * Since OpenSSL 1.1.0 explicit initialization is no longer required:
       * https://www.openssl.org/docs/manmaster/man3/OPENSSL_init_ssl.html
       */
#  if !CFG_USE_OPENSSL_API_1_1
      /* Load strings for human readable error messsages */
      SSL_load_error_strings();
      /* Register available ciphers and digests */
      SSL_library_init();
#     if OPENSSL_VERSION_NUMBER == 0x1000000FL
      /*
       * Quoted from the OpenSSL documentation:
       * OpenSSL [...] 1.0.0a and later added SHA2 algorithms to
       * SSL_library_init(). Applications which need to use SHA2 in earlier
       * versions of OpenSSL should call OpenSSL_add_all_algorithms() as well.
       */
      OpenSSL_add_all_algorithms();
#     endif   /* OPENSSL_VERSION_NUMBER == 1.0.0 */
#  endif  /* !CFG_USE_OPENSSL_API_1_1 */

      /* Ensure that Pseudo Random Number Generator (PRNG) is seeded */
      if(!RAND_status())
      {
         PRINT_ERROR("Seeding PRNG failed, "
                     "check your OpenSSL configuration");
         res = -1;
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Shutdown TLS subsystem */

void  tls_exit(void)
{
   if((posix_locale_t) 0 != tls_loc_ctype_posix)
   {
      posix_freelocale(tls_loc_ctype_posix);
   }

   /* Since OpenSSL 1.1.0 explicit cleanup is no longer required */
#  if !CFG_USE_OPENSSL_API_1_1
   EVP_cleanup();
#  endif  /* !CFG_USE_OPENSSL_API_1_1 */
}


/* ========================================================================== */
/*! \brief Check TLS subsystem for known vulnerabilities
 *
 * \param[in] check  Execute vulnerability checks
 *
 * If \e check is zero, only the library name and version number is printed
 *
 * \attention
 * Because the TLS subsystem may use dynamically linked OpenSSL libraries, this
 * check can't be done at compile time!
 *
 * OpenSSL version numbers since 0.9.5 are composed as follows:
 *
 * 0xMNNFFPPS (Major miNor Fix Patch Status)
 * <br>
 * <hr>
 * 0x00906023 (0.9.6b beta 3)
 * <br>
 * 0x1000107F (1.0.1g release)
 * <br>
 * 0x20000000 (2.0.0 dev, reported by LibreSSL)
 * <br>
 * 0x30000000 (3.0.0 dev, reported by OpenSSL Alpha versions)
 *
 * \note
 * LibreSSL always reports the same OpenSSL version.
 *
 * \return
 * - 0 on success
 * - Negative value if TLS subsystem may use code with known vulnerabilities
 */

int  tls_vulnerability_check(int  check)
{
   int  res = 0;
   /* Using function instead of the constant is required for shared library */
#  if CFG_USE_OPENSSL_API_1_1
   unsigned long int  version = OpenSSL_version_num();
#  else   /* CFG_USE_OPENSSL_API_1_1 */
#     if CFG_USE_LIBRESSL && defined(LIBRESSL_VERSION_NUMBER)
   /* Documentation says LIBRESSL_VERSION_NUMBER always ends with 00F */
   unsigned long int  version = LIBRESSL_VERSION_NUMBER;
#     else  /* CFG_USE_LIBRESSL && LIBRESSL_VERSION_NUMBER */
   unsigned long int  version = SSLeay();
#     endif  /* CFG_USE_LIBRESSL && LIBRESSL_VERSION_NUMBER */
#  endif   /* CFG_USE_OPENSSL_API_1_1 */
   unsigned int  major = (unsigned int) ((version & 0xF0000000UL) >> 28);
   unsigned int  minor = (unsigned int) ((version & 0x0FF00000UL) >> 20);
   unsigned int  fix   = (unsigned int) ((version & 0x000FF000UL) >> 12);
   unsigned int  patch = (unsigned int) ((version & 0x00000FF0UL) >> 4);
#  if ! CFG_USE_LIBRESSL && ! CFG_USE_OPENSSL_API_3
   char  patch_letter;
   char  patch_string[4] = { 0, 0, 0, 0 };

   if(patch)
   {
      if(25U >= patch)
      {
         /* Single character */
         patch_letter = (char) (0x60U + patch);
      }
      else if(50U >= patch)
      {
         /* Second character */
         patch_letter = (char) (0x60U + patch - 25U);
         patch_string[1] = patch_letter;
         /* First character */
         patch_letter = 'z';
      }
      else if(75U >= patch)
      {
         /* Third character */
         patch_letter = (char) (0x60U + patch - 50U);
         patch_string[2] = patch_letter;
         /* Second character */
         patch_string[1] = 'z';
         /* First character */
         patch_letter = 'z';
      }
      else
      {
         /* This will hopefully never be required */
         patch_letter = '?';
      }
      patch_string[0] = patch_letter;
   }
#  endif  /* ! CFG_USE_LIBRESSL && ! CFG_USE_OPENSSL_API_3 */

   /* Print version */
#  if CFG_USE_LIBRESSL && defined(LIBRESSL_VERSION_NUMBER)
   /* LibreSSL */
   printf("%s: %sCompiled for LibreSSL\n", CFG_NAME, MAIN_ERR_PREFIX);
   printf("%s: %sLibreSSL library version: %X.%X.%X (0x%08lX)\n",
          CFG_NAME, MAIN_ERR_PREFIX, major, minor, fix, version);
#  else   /* CFG_USE_LIBRESSL && LIBRESSL_VERSION_NUMBER */
   /* OpenSSL */
#     if CFG_USE_OPENSSL_API_3
   printf("%s: %sOpenSSL library version: %X.%X.%X (0x%08lX)\n",
          CFG_NAME, MAIN_ERR_PREFIX, major, minor, patch, version);
#     else  /* CFG_USE_OPENSSL_API_3 */
   printf("%s: %sOpenSSL library version: %X.%X.%X%s (0x%08lX)\n",
          CFG_NAME, MAIN_ERR_PREFIX, major, minor, fix, patch_string, version);
#     endif  /* CFG_USE_OPENSSL_API_3 */
#  endif  /* CFG_USE_LIBRESSL && LIBRESSL_VERSION_NUMBER */

   /* Print message if TLSv1.3 is avialable */
#  if CFG_USE_OPENSSL_API_1_1 && (OPENSSL_VERSION_NUMBER >= 0x10101000L) \
   || CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 0x30101000L)
   printf("%s: %sProtocol version TLSv1.3 available for negotiation\n",
          CFG_NAME, MAIN_ERR_PREFIX);
#  endif  /* CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 0x30101000L) */
          /* || CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 0x30101000L) */

   if(check)
   {
      /*
       * Check for potential certificate check "#OprahSSL" bug (CVE-2015-1793)
       * in OpenSSL (Vulnerable versions: 1.0.1n, 1.0.1o, 1.0.2b and 1.0.2c)
       */
      if(1U == major && 0U == minor)
      {
         if( (1U == fix && 0xEU <= patch && 0xFU >= patch)
             || (2U == fix && 0x2U <= patch && 0x3U >= patch) )
         {
            printf("%s: %sOpenSSL library possibly vulnerable to #OprahSSL!\n",
                   CFG_NAME, MAIN_ERR_PREFIX);
            res = -1;
         }
      }

      /*
       * Check for potential heartbeat "Heartbleed" bug (CVE-2014-0160) in
       * OpenSSL (Vulnerable versions: 1.0.1 to 1.0.1f)
       */
      if(1U == major && 0U == minor && 1U == fix && 7U > patch)
      {
         printf("%s: %sOpenSSL library possibly vulnerable to Heartbleed!\n",
                CFG_NAME, MAIN_ERR_PREFIX);
         res = -1;
      }

      /*
       * Check for potential MITM vulnerability (CVE-2014-0224) in OpenSSL
       * (Vulnerable versions: 1.0.0 to 1.0.0l and 1.0.1 to 1.0.1g)
       */
      if(1U == major && 0U == minor)
      {
         if( (0U == fix && 0x0DU > patch) || (1U == fix && 8U > patch) )
         {
            printf("%s: %s"
                   "OpenSSL library possibly vulnerable to MITM attacks!\n",
                   CFG_NAME, MAIN_ERR_PREFIX);
            res = -1;
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Check whether server name used for connection is not an IP address
 *
 * \param[in] sn  Pointer to server name string
 *
 * An IP address cannot be used for the SNI extension.
 * This function should be called to determine whether a TCP connection was
 * established based on DNS name or IP address.
 *
 * The result can be used as parameter \c sni for the function \ref tls_open() .
 *
 * \return
 * - \e sn on success
 * - \c NULL if \e sn is a valid ASCII representation of an IP address
 */

const char*  tls_sni(const char*  sn)
{
   const char*  res = NULL;
   unsigned char  buf[16];

   if(1 != posix_inet_pton(POSIX_AF_INET, sn, buf))
   {
#if CFG_USE_IP6
      if(1 != posix_inet_pton(POSIX_AF_INET6, sn, buf))
      {
         res = sn;
      }
#else  /* CFG_USE_IP6 */
      res = sn;
#endif  /* CFG_USE_IP6 */
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Establish TLS encryption layer on top of open network connection
 *
 * \param[in]  sd    Socket descriptor
 * \param[out] co    Pointer to connection object pointer
 * \param[in]  weak  Offer weak cipher suite(s) to server
 * \param[in]  sni   Hostname for SNI extension (or \c NULL )
 *
 * The socket descriptor \e sd must be assigned to an open connection.
 *
 * If \e sni is \c NULL, the SNI extension is not used (but this is forbidden
 * by RFC 8143).
 * If the caller wants to use the SNI extension, \e sni must point to the
 * hostname string used for DNS resolution when the connection associated
 * with \e sd was established.
 *
 * On success, a pointer to the connection object is stored at the location
 * pointed by \e co .
 *
 * \attention
 * RFC 5246 specifies the cipher suite RSA-RSA-AES128-SHA (0x00, 0x2F) as
 * mandatory.
 * RFC 4642 specified the cipher suite RSA-RSA-RC4-MD5 (0x00, 0x04) as mandatory
 * in the past (it is preserved for backward compatibility).
 * Both will provide no forward secrecy and the latter one in addition use the
 * weak RC4 symmetric encryption algorithm.
 * Therefore they are not included in the default list of ciphers we offer to
 * the server.
 *
 * To comply with RFC 5246 and nevertheless offer the disabled cipher suites, or
 * if the server doesn't support FFDHE key exchange, \e weak must be set to a
 * nonzero value.
 *
 * If OpenSSL API 1.1 is available, the FFDHE group from the server are verified
 * to be at least 1024 bits in size for FFDHE key exchange (if \e weak is set to
 * zero).
 *
 * If OpenSSL API 1.1 is available and at least TLSv1.2 is used, the signature
 * algorithm to use for TLS protocol is negotiated with the server.
 * The algorithms we offer are defined with \ref TLS_SIGALGS for TLSv1.2.
 * The algorithms we offer are defined with \ref TLS_SIGALGS_TLS13 for TLSv1.3.
 *
 * \note
 * TLSv1.3 allows to offer a separate list of signature algorithms for
 * certificates. We do not use this feature and the certificates must use
 * signature algorithms listed in \ref TLS_SIGALGS_TLS13 .
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  tls_open(int  sd, void**  co, int  weak, const char*  sni)
{
   int  res = -1;
   int  rv;
   long int  rv2;
   SSL**  ssl;
   unsigned long int  error;
   const char*  sp = NULL;
   const char*  tlsv = NULL;
   const char*  cp = NULL;
   const char*  kx = NULL;
   char  b[200];
#  if CFG_USE_OPENSSL_API_1_1
   EVP_PKEY*  key = NULL;
   int  length;
#  endif  /* CFG_USE_OPENSSL_API_1_1 */
#  if CFG_USE_OPENSSL_API_1_1
   const char*  dp = NULL;
#     if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
   int  id;
#     endif   /* (OPENSSL_VERSION_NUMBER >= 1.1.1) */
   int  id2;
#  endif  /* CFG_USE_OPENSSL_API_1_1 */

   if(NULL != co)
   {
      ssl = (SSL**) co;

      /* Create context */
      res = tls_create_context(weak);

      /* Create connection object */
      if(!res)
      {
         *ssl = SSL_new(tls_ctx);
         if(NULL == *ssl)
         {
            PRINT_ERROR("Cannot create connection object");
            res = -1;
         }
      }

      /* Configure RFC 6066 SNI extension */
      if(!res && NULL != sni)
      {
         printf("%s: %sUsing SNI extension with: %s\n",
                CFG_NAME, MAIN_ERR_PREFIX, sni);
         rv2 = SSL_set_tlsext_host_name(*ssl, sni);
         if(1 != rv2)
         {
            PRINT_ERROR("Adding SNI extension from RFC 6066 failed");
            res = -1;
         }
      }

      /* Add weak cipher suites on request */
      if(!res && weak)
      {
         rv = SSL_set_cipher_list(*ssl, TLS_CIPHERS_WEAK);
         if(1 != rv)
         {
            PRINT_ERROR("Adding weak cipher suites failed");
            res = -1;
         }
         else
         {
            printf("%s: %sWarning: Offering weak protocol versions "
                   "and cipher suites to server\n",
                   CFG_NAME, MAIN_ERR_PREFIX);
         }
      }

      /* Attach connection object to socket */
      if(!res)
      {
         rv = SSL_set_fd(*ssl, sd);
         if(1 != rv)
         {
            PRINT_ERROR("Cannot assign connection object to socket");
            res = -1;
         }
      }

      /* Initiate TLS handshake */
      if(!res)
      {
         rv = SSL_connect(*ssl);
         if(1 != rv)
         {
            PRINT_ERROR("Failed to establish TLS connection");
            /* Assignment in truth expression is intended */
            while((error = ERR_get_error()))
            {
               sp = ERR_reason_error_string(error);
               posix_snprintf(b, 200, "%sError: %s", MAIN_ERR_PREFIX, sp);
               print_error(b);
            }
            res = -1;
         }
         else
         {
            /* Print cipher suite */
            res = tls_get_ciphersuite((void**) ssl, &tlsv, &cp, &kx);
            if(res)  { PRINT_ERROR("Reading negotiation result failed"); }
            else
            {
               printf("%s: %sUsing protocol: %s with cipher suite %s\n",
                      CFG_NAME, MAIN_ERR_PREFIX, tlsv, cp);
#  if CFG_USE_OPENSSL_API_1_1
               if(SSL_get_peer_signature_nid(*ssl, &id2))
               {
                  dp = "rsaEncryption";
#     if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
                  if(SSL_get_peer_signature_type_nid(*ssl, &id))
                  {
                     dp = OBJ_nid2sn(id);
                  }
#     endif   /* (OPENSSL_VERSION_NUMBER >= 1.1.1) */
                  if(!strcmp("rsaEncryption", dp))  { dp = "RSASSA-PKCS1-v1_5"; }
                  printf("%s: %sPeer signature algorithm: %s+%s\n",
                         CFG_NAME, MAIN_ERR_PREFIX, dp, OBJ_nid2sn(id2));
               }
               else
               {
                  /* '!weak' indicates DHE key exchange here */
                  /* Up to TLSv1.1 the signature algorithm is not selectable */
                  if(!weak && strcmp("TLSv1", tlsv) && strcmp("TLSv1.1", tlsv))
                  {
                     PRINT_ERROR("Signature algorithm negotiation failed");
                     res = -1;
                  }
               }
#  else  /* CFG_USE_OPENSSL_API_1_1 */
               if(!weak && strcmp("TLSv1", tlsv) && strcmp("TLSv1.1", tlsv))
               {
                  printf("%s: %sWarning: Signature algorithm "
                         "negotiation not possible with OpenSSL API 1.0\n",
                         CFG_NAME, MAIN_ERR_PREFIX);
               }
#  endif  /* CFG_USE_OPENSSL_API_1_1 */
               if(strcmp("TLSv1", tlsv) && strcmp("TLSv1.1", tlsv)
                  && strcmp("TLSv1.2", tlsv))
               {
                  if(!strcmp("FFDHE", kx))
                  {
                     printf("%s: %sKey exchange algorithm: %s\n",
                            CFG_NAME, MAIN_ERR_PREFIX, kx);
                  }
                  else
                  {
                     printf("%s: %sKey exchange algorithm: ECDHE (%s)\n",
                            CFG_NAME, MAIN_ERR_PREFIX, kx);
                  }
               }
            }
#  if CFG_USE_OPENSSL_API_1_1
            if(!res && !weak)
            {
               /* Check FFDHE group (no negotiation possible before TLSv1.3) */
               rv = SSL_get_server_tmp_key(*ssl, &key);
               if(!rv)  { res = -1; }
               if(!res)
               {
                  if(EVP_PKEY_DH == EVP_PKEY_id(key))
                  {
                     length = EVP_PKEY_bits(key);
                     if(0 >= length)  { res = -1; }
                     else
                     {
                        printf("%s: %sFFDHE group size: %d bit\n",
                               CFG_NAME, MAIN_ERR_PREFIX, length);
                        if(1024 > length)
                        {
                           PRINT_ERROR("FFDHE group size rejected");
                           res = -1;
                        }
                        else if(3072 > length)
                        {
                           /* 3072 bit would match a 128 bit symmetric cipher */
                           printf("%s: %sWarning: FFDHE group size should be"
                                  " at least 3072 bit\n",
                                  CFG_NAME, MAIN_ERR_PREFIX);
                        }
                     }
                  }
                  EVP_PKEY_free(key);
                  key = NULL;
               }
               /* Close connection on error */
               if(res)
               {
                  PRINT_ERROR("FFDHE group size check failed");
                  tls_close((void*) ssl);
               }
            }
#  else  /* CFG_USE_OPENSSL_API_1_1 */
            if(!res)
            {
#     if CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 0x30101000L)
               /* LibreSSL supports TLSv1.3 since version 3.1.1, but no FFDHE */
               if(strcmp("TLSv1.3", tlsv))
               {
#     endif   /* CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 0x30101000L) */
                  printf("%s: %sWarning: FFDHE group size check not possible "
                         "with OpenSSL API 1.0\n", CFG_NAME, MAIN_ERR_PREFIX);
#     if CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 0x30101000L)
               }
#     endif   /* CFG_USE_LIBRESSL && (LIBRESSL_VERSION_NUMBER >= 0x30101000L) */
            }
#  endif  /* CFG_USE_OPENSSL_API_1_1 */
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Terminate TLS encryption layer on top of network connection
 *
 * \param[in,out] co  Pointer to connection object pointer
 *
 * \note
 * This function also destroys the TLS context that contains the certificate
 * CRLs. After CRLs are updated, it is not required to shutdown the whole module
 * via \ref tls_exit() . Calling this function is sufficient to invalidate any
 * CRLs in memory.
 *
 * \return
 * - 1 on success
 * - Negative value on error
 */

int  tls_close(void**  co)
{
   int  res = -1;

   if(NULL != co && NULL != *((SSL**) co))
   {
      /* Close connection */
      res = SSL_shutdown(*((SSL**) co));
      if(!res)  { res = SSL_shutdown(*((SSL**) co)); }
      if(0 <= res && 1 != res)
      {
         PRINT_ERROR("Error while closing TLS connection");
         res = -1;
      }

      /* Destroy connection object */
      SSL_free(*((SSL**) co));
      *((SSL**) co) = NULL;

      /* Destroy context (including buffered CRLs) */
      tls_destroy_context();
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Get protocol and cipher suite name that was negotiated for connection
 *
 * \param[in]  co  Pointer to connection object pointer
 * \param[out] pv  Pointer to protocol version string
 * \param[out] cs  Pointer to cipher suite string
 * \param[out] kx  Pointer to key exchange algorithm
 *
 * \attention
 * The value returned for \e kx is only valid when the protocol version returned
 * for \e pv is \c TLSv1.3 (for older protocol versions the key exchange
 * algorithm is negotiated as part of the cipher suite).
 *
 * \note
 * If the caller is not interested in the information, the parameters \e pv and
 * \e cs it is allowed to pass \c NULL .
 *
 * \return
 * - 0 on success
 * - -1 on error
 */

int  tls_get_ciphersuite(void**  co, const char**  pv, const char**  cs,
                         const char**  kx)
{
   int  res = -1;
   SSL**  ssl;
   const SSL_CIPHER*  cp;
#  if CFG_USE_OPENSSL_API_1_1
   EVP_PKEY*  tmp_key;
#  endif   /* CFG_USE_OPENSSL_API_1_1 */

   ssl = (SSL**) co;
   cp = SSL_get_current_cipher(*ssl);
   if(NULL == cp)
   {
      PRINT_ERROR("Reading negotiated protocol and cipher suite failed");
   }
   else
   {
      /*
       * Note:
       * Calling 'SSL_CIPHER_get_version()' is wrong, because this function
       * reports the TLS version in which the used cipher suite was defined.
       * But this function should return information about the protocol used for
       * the current connection corresponding to parameter 'co'.
       */
      if(NULL != pv)  { *pv = SSL_get_version(*ssl); }
      if(NULL != cs)  { *cs = SSL_CIPHER_get_name(cp); }
      if(NULL != kx)
      {
#  if CFG_USE_OPENSSL_API_1_1
         if(SSL_get_server_tmp_key(*ssl, &tmp_key))
         {
            switch(EVP_PKEY_id(tmp_key))
            {
               case EVP_PKEY_DH:
               {
                  *kx = "FFDHE";
                  break;
               }
               default:
               {
                  *kx = OBJ_nid2sn(EVP_PKEY_id(tmp_key));
                  break;
               }
            }
            EVP_PKEY_free(tmp_key);
            res = 0;
         }
#  else   /* CFG_USE_OPENSSL_API_1_1 */
         *kx = "Unknown";
         res = 0;
#  endif   /* CFG_USE_OPENSSL_API_1_1 */
      }
      else  { res = 0; }
      res = 0;
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Check whether server has presented a certificate and verify it
 *
 * \param[in]  co    Pointer to connection object pointer
 * \param[out] cert  Pointer to cerificate pointer
 * \param[in]  cn    Common name string the certificate should contain
 * \param[in]  weak  Disable some checks for better compatibility
 *
 * \attention
 * The parameter \e weak must be set to the same value that was used for the
 * call to \ref tls_open() .
 *
 * The verification executes the following steps:
 * - Ensure that the server has presented a certificate (chain)
 * - Optional: Check the signature algorithms and key lengths used in the
 *   certificate chain (Setting \e weak to a nonzero value disables this step)
 * - Follow certificate chain back to the anchor and verify that it is trusted
 * - Optional: Verify that none of the certificates in the chain was revoked
 *   (The CRLs for the revocation checks are downloaded automatically)
 * - Verify that the subject of the certificate contain \e cn
 *
 * According to RFC 8143 the following rules are applied:
 * - The matching of hostname \e cn must be case-insensitive => We do so.
 * - Wildcard \c * is allowed as left-most name component => We accept this.
 * - Hostnames listed via \c subjectAltName extension should be checked first
 *   => We do so.
 * - If the certificate contains multiple names (e.g., more than one \c dNSName
 *   field), then a match with any one of the fields is considered acceptable
 *   => We accept any match.
 *
 * If the return value is not negative, the server has presented a certificate
 * and a pointer to it was written to \e cert .
 *
 * \note
 * The returned value for \e cert stay in memory until the connection object
 * \e co is destroyed.
 *
 * The return value \c -2 should be interpreted as a request to close the
 * connection and re-establish it again (and then using the new CRLs).
 *
 * \return
 * - 0 on success (Certificate was presented, is valid and trusted)
 * - 1 if the certificate is not trusted
 * - -1 on error
 * - -2 if the verification was not executed because CRLs have been updated
 */

int  tls_cert_verify(void**  co, void**  cert, const char*  cn, int  weak)
{
   int  res = 0;
   SSL**  ssl = (SSL**) co;
   STACK_OF(X509)*  certificate_chain = NULL;
   X509*  certificate = NULL;
   X509_NAME*  subject;
   X509_NAME_ENTRY*  common_name;
   ASN1_STRING*  common_name_string;
   BIO*  biop = BIO_new(BIO_s_mem());
   long int  rv;
   const char*  sp = NULL;
   char  b[256];
   const char*  p = NULL;
   int  i = -1;
   GENERAL_NAMES*  an = NULL;
   GENERAL_NAME*  ani;
   int  num;
   int  match = 0;
   const char*  pv;
   int  proto = 0;

   /* Extract whole certificate chain from connection object */
   certificate_chain = SSL_get_peer_cert_chain(*ssl);
   if(NULL == certificate_chain)
   {
      PRINT_ERROR("Extracting certificate chain failed");
      res = -1;
   }
   if(!res && weak)
   {
      printf("%s: %sWarning: Using weak certificate chain checks\n",
             CFG_NAME, MAIN_ERR_PREFIX);
   }

   /* Update CRLs for certificate chain */
#  if !CFG_TLS_CRLS_DISABLE
   if(!res && config[CONF_CRL_CHECK].val.i)
   {
      if(!tls_crl_update_skip && tls_check_crl_age())
      {
         res = tls_update_crls(certificate_chain);
         if(0 > res)  { PRINT_ERROR("Updating of CRLs failed"); }
         else
         {
            /* CRLs successfully updated */
            tls_set_crl_age();
            if(0 < res)
            {
               /* Self signed root certificate */
               PRINT_ERROR("No CRLs required");
               res = 0;
            }
            else
            {
               /* Request to close and reopen connection */
               PRINT_ERROR("Verification of certificate aborted because "
                           "of CRL update");
               SSL_get_verify_result(*ssl);
               res = -2;
            }
         }
      }
   }
#  endif  /* !CFG_TLS_CRLS_DISABLE */

   /* Check algorithms and key length of signatures in certificate chain */
   if(!res && !weak)
   {
      res = tls_get_ciphersuite((void**) ssl, &pv, NULL, NULL);
      if(res)  { PRINT_ERROR("Reading protocol version failed"); }
      else
      {
         /* Check for TLS protocol minor number */
         if(!strcmp("TLSv1", pv))  { proto = 0; }
         else if(!strcmp("TLSv1.1", pv))  { proto = 1; }
         else if(!strcmp("TLSv1.2", pv))  { proto = 2; }
         else  { proto = 3; }
      }
      if(!res)
      {
         res = tls_chain_check_sigalgs(certificate_chain, proto);
      }
   }

   /* Verify server certificate */
   if(!res)
   {
#  if CFG_USE_OPENSSL_API_3
      certificate = SSL_get1_peer_certificate(*ssl);
#  else  /* CFG_USE_OPENSSL_API_3 */
      certificate = SSL_get_peer_certificate(*ssl);
#  endif  /* CFG_USE_OPENSSL_API_3 */
      if(NULL == certificate)
      {
         PRINT_ERROR("Server has presented no certificate");
         res = -1;
      }
      else
      {
         /* Certificate present */
         res = 1;
         *cert = (void*) certificate;
         /* Let OpenSSL check whether the certificate is trusted */
         rv = SSL_get_verify_result(*ssl);
         if(X509_V_OK != rv)
         {
            sp = X509_verify_cert_error_string(rv);
            /* printf("Errorcode: %d\n", ERR_GET_REASON(rv)); */
            posix_snprintf(b, 256,
                           "%sAlert: Server X.509 certificate verification "
                           "failed (%s)", MAIN_ERR_PREFIX, sp);
            print_error(b);
#  if !CFG_TLS_CRLS_DISABLE
            if(X509_V_ERR_UNABLE_TO_GET_CRL == rv
               || X509_V_ERR_CRL_HAS_EXPIRED == rv)
            {
               /* Reset CRL update timestamp to force new CRL update */
               PRINT_ERROR("Resetting CRL update timestamp");
               tls_reset_crl_age();
            }
#  endif  /* !CFG_TLS_CRLS_DISABLE */
         }
         else
         {
            /* Verify subject */
            subject = X509_get_subject_name(certificate);
            if(NULL == subject)
            {
               PRINT_ERROR("Extraction of subject from X.509 certificate "
                           "failed");
            }
            else
            {
               printf("%s: %sServer certificate:\n", CFG_NAME, MAIN_ERR_PREFIX);
               /* Extract alternative names first and compare with cn */
               an = X509_get_ext_d2i(certificate, NID_subject_alt_name,
                                     NULL, NULL);
               if(NULL != an)
               {
                  num = sk_GENERAL_NAME_num(an);
                  for(i = 0; i < num; ++i)
                  {
                     ani = sk_GENERAL_NAME_value(an, i);
                     if(GEN_DNS == ani->type)
                     {
#  if CFG_USE_OPENSSL_API_1_1
                        p = (const char*) ASN1_STRING_get0_data(ani->d.dNSName);
#  else   /* CFG_USE_OPENSSL_API_1_1 */
                        /* This function is deprecated since API 1.1 */
                        p = (const char*) ASN1_STRING_data(ani->d.dNSName);
#  endif   /* CFG_USE_OPENSSL_API_1_1 */
                        printf("%s: %s   Subject Alternative Name: %s",
                               CFG_NAME, MAIN_ERR_PREFIX, p);
                        if(!tls_subject_check(cn, p))  { match = 1;  break; }
                     }
                  }
                  GENERAL_NAMES_free(an);
               }
               /* Extract common names from subject and compare with cn */
               if(!match)
               {
                  while(1)
                  {
                     i = X509_NAME_get_index_by_NID(subject, NID_commonName, i);
                     if(-1 == i)  { break; }
                     common_name = X509_NAME_get_entry(subject, i);
                     common_name_string = X509_NAME_ENTRY_get_data(common_name);
#  if CFG_USE_OPENSSL_API_1_1
                     p = (const char*) ASN1_STRING_get0_data(common_name_string);
#  else   /* CFG_USE_OPENSSL_API_1_1 */
                     /* This function is deprecated since API 1.1 */
                     p = (const char*) ASN1_STRING_data(common_name_string);
#  endif   /* CFG_USE_OPENSSL_API_1_1 */
                     printf("%s: %s   Subject Name: %s",
                            CFG_NAME, MAIN_ERR_PREFIX, p);
                     if(!tls_subject_check(cn, p))  { match = 1;  break; }
                  }
               }
               if(!match)
               {
                  posix_snprintf(b, 256,
                                 "%sAlert: Server X.509 certificate "
                                 "verification failed "
                                 "(Subject doesn't match hostname %s)",
                                 MAIN_ERR_PREFIX, cn);
                  print_error(b);
               }
               else
               {
                  /* We trust the certificate and certificate match hostname */
#  if !CFG_TLS_CRLS_DISABLE
                  if(config[CONF_CRL_CHECK].val.i)
                  {
                     printf("%s: %sServer certificate verification "
                            "successful\n", CFG_NAME, MAIN_ERR_PREFIX);
                  }
                  else
                  {
                     printf("%s: %sServer certificate verification "
                            "succesful (revocation checks were skipped)\n",
                            CFG_NAME, MAIN_ERR_PREFIX);
                  }
#  else  /* !CFG_TLS_CRLS_DISABLE */
                  printf("%s: %sServer certificate verification successful "
                         "(revocation checks were skipped)\n",
                         CFG_NAME, MAIN_ERR_PREFIX);
#  endif  /* !CFG_TLS_CRLS_DISABLE */
                  res = 0;
               }
            }
         }
      }
   }

   /* Clean up */
   if(NULL != certificate)  { X509_free(certificate); }
   if(NULL != biop)  { BIO_free(biop); }

   return(res);
}


/* ========================================================================== */
/*! \brief Print certificate
 *
 * \param[in]  cert  Pointer to cerificate
 * \param[out] cbuf  Pointer buffer pointer
 *
 * On success, a memory block is allocated for the certificate and a pointer to
 * it is written to \e cbuf. The caller is responsible to free the memory block
 * allocated for the certificate.
 *
 * \return
 * - 0 on success
 * - -1 on error
 */

int  tls_cert_get_string(void*  cert, const char**  cbuf)
{
   int  res = -1;
   X509*  certificate;
   BIO*  biop = BIO_new(BIO_s_mem());
   int  rv;
   char*  p = NULL;
   size_t  len;
   char*  buf = NULL;

   certificate = (X509*) cert;
   rv = X509_print(biop, certificate);
   if(1 == rv)
   {
      len = (size_t) BIO_get_mem_data(biop, &p);
      if(NULL != p && len)
      {
         buf = (char*) posix_malloc(len + (size_t) 1);
         if(NULL != buf)
         {
            memcpy((void*) buf, (void*) p, len);
            buf[len] = 0;
            *cbuf = buf;
            res = 0;
         }
      }
   }

   /* Clean up */
   if(NULL != biop)  { BIO_free(biop); }
   if(res)  { posix_free((void*) buf); }

   return(res);
}


/* ========================================================================== */
/*! \brief Send data
 *
 * \param[in] co   Pointer to connection object pointer
 * \param[in] buf  Pointer to data buffer
 * \param[in] len  Number of octets to send
 *
 * \return
 * - Number of bytes sent
 * - -1 on error
 */

posix_ssize_t  tls_send(void*  co, const void*  buf, size_t  len)
{
   posix_ssize_t  res = -1;
   int  num;

   /* Check size */
   if(POSIX_INT_MAX < len)
   {
      PRINT_ERROR("tls_send(): Data length too large");
   }
   num = (int) len;

   res = (posix_ssize_t) SSL_write((SSL*) co, buf, num);
   if(!res)  { res = -1; }

   return(res);
}


/* ========================================================================== */
/*! \brief Receive data
 *
 * \param[in]  co    Pointer to connection object pointer
 * \param[out] buf   Pointer to data buffer
 * \param[in]  len   Number of octets to receive
 * \param[in]  peek  Data should stay available for reading if nonzero
 *
 * \attention
 * The \e peek flag doesn't work anymore since OpenSSL 1.1.0 and must be zero
 * in this case (otherwise a deadlock will occur).
 * This bug was fixed before OpenSSL 1.1.1 (works again now).
 *
 * \return
 * - Number of bytes received
 * - -1 on error
 */

posix_ssize_t  tls_recv(void*  co, void*  buf, size_t  len, int  peek)
{
   posix_ssize_t  res = -1;
   int  num;

   /* Check size */
   if(POSIX_INT_MAX < len)
   {
      PRINT_ERROR("tls_recv(): Data length too large");
   }
   num = (int) len;

   /* Check for peek flag */
   if(peek)
   {
#  if CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3
      PRINT_ERROR("tls_recv(): "
                  "Flag 'peek' not supported with OpenSSL 1.1.x");
#  else   /* CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3 */
      res = (posix_ssize_t) SSL_peek((SSL*) co, buf, num);
#  endif   /* CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3 */
   }
   else  { res = (posix_ssize_t) SSL_read((SSL*) co, buf, num); }
   if(!res)  { res = -1; }

   return(res);
}


/* ========================================================================== */
/*! \brief Check whether CRL update interval has elapsed
 *
 * This function is exported for the UI module to check the CRL update interval
 * and give the user a choice to delay the CRL updates.
 * The function \ref tls_crl_update_control() is intended to enable and disable
 * the automatic CRL updates.
 *
 * \note
 * The update choice can be disabled with configuration option \c CONF_CRL_UPD_C
 * (if configured to zero, this function will alays return zero).
 *
 * \return
 * - 0 Do nothing
 * - 1 CRL update interval elapsed. Give user a choice to suppress update
 * - -1 on error
 */

int  tls_crl_update_check(void)
{
   int  res = 0;

#  if !CFG_TLS_CRLS_DISABLE
   if(config[CONF_CRL_CHECK].val.i && config[CONF_CRL_UPD_C].val.i)
   {
      res = tls_check_crl_age();
   }
#  endif  /* !CFG_TLS_CRLS_DISABLE */

   return(res);
}


/* ========================================================================== */
/*! \brief Enable or disable automatic CRL updates
 *
 * \param[in] crl_upd_disable  Disable automatic CRL updates if nonzero
 *
 * \attention
 * If CRLs are used, they need to be present and valid! With this function the
 * update of CRLs can only be suppressed until they expire. After expiration of
 * the CRLs a TLS connection can no longer be established (if a certificate
 * chain is used for authentication).
 *
 * \note
 * When disabled, the CRL updates can be reenabled on the fly at any time by
 * calling this function again with \e crl_upd_disable set to zero.
 */

void  tls_crl_update_control(int crl_upd_disable)
{
#  if !CFG_TLS_CRLS_DISABLE
   /* Store CRL update flag */
   tls_crl_update_skip = crl_upd_disable;
#  endif  /* !CFG_TLS_CRLS_DISABLE */
}


/* ========================================================================== */
/*! \brief Free an object allocated by TLS module
 *
 * Use this function to release dynamic memory that was allocated by the
 * TLS module.
 *
 * \param[in] p  Pointer to object
 *
 * Release the memory for the object pointed to by \e p.
 *
 * \note
 * The pointer \e p is allowed to be \c NULL and no operation is performed in
 * this case.
 */

void  tls_free(void*  p)
{
   posix_free(p);
}


#endif  /* CFG_USE_TLS */


/*! @} */

/* EOF */
