/*
 *
 *  Copyright (C) 1998-2018, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module: dcmtls
 *
 *  Author: Marco Eichelberg
 *
 *  Purpose:
 *    classes: DcmTLSTransportLayer
 *
 */

#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
#include "dcmtk/dcmtls/tlslayer.h"
#include "dcmtk/dcmtls/tlsdefin.h"

#ifdef WITH_OPENSSL

#define INCLUDE_CSTDLIB
#include "dcmtk/ofstd/ofstdinc.h"

BEGIN_EXTERN_C
#ifdef HAVE_WINDOWS_H
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winbase.h>
#endif
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/dh.h>
END_EXTERN_C

#include "dcmtk/dcmtls/tlslayer.h"
#include "dcmtk/dcmtls/tlstrans.h"
#include "dcmtk/dcmnet/dicom.h"

#ifdef HAVE_SSL_CTX_GET0_PARAM
#define DCMTK_SSL_CTX_get0_param SSL_CTX_get0_param
#else
#define DCMTK_SSL_CTX_get0_param(A) A->param;
#endif

#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
#define X509_get_signature_nid(x509) OBJ_obj2nid((x509)->sig_alg->algorithm)
#endif

#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
#define SSL_CTX_get_cert_store(ctx) (ctx)->cert_store
#define EVP_PKEY_base_id(key) EVP_PKEY_type((key)->type)
#define DH_bits(dh) BN_num_bits((dh)->p)
#define SSL_CTX_get_ciphers(ctx) (ctx)->cipher_list
#endif

extern "C" int DcmTLSTransportLayer_certificateValidationCallback(int ok, X509_STORE_CTX *storeContext);

OFLogger DCM_dcmtlsLogger = OFLog::getLogger("dcmtk.dcmtls");


/*  This static function creates a hard-coded set of Diffie-Hellman parameters
 *  with 2048 bits key size that is used for ephemeral Diffie-Hellmane
 *  (DHE_) ciphersuites unless the user replaces the parameter set
 *  by calling DcmTLSTransportLayer::setTempDHParameters().
 *  Using a hard-coded DH parameter set is safe because the DH key exchange
 *  does not require these parameters to be secret. It is, however, still
 *  preferable to use a user-generated set of parameters.
 *
 *  Generated by calling "openssl dhparam -C -noout 2048".
 */
static DH *get_dh2048()
{
    static unsigned char dh2048_p[] = {
	0xC8, 0x04, 0xF6, 0xBF, 0x4B, 0xA6, 0xBA, 0x24, 0xD8, 0x79,
	0xA9, 0x70, 0xFF, 0xA0, 0x6B, 0x9F, 0x9D, 0x56, 0x0F, 0x41,
	0x75, 0x70, 0x69, 0x17, 0xBC, 0x89, 0xB5, 0x38, 0xEE, 0x8A,
	0xA9, 0x2E, 0xFD, 0xC8, 0xD3, 0xBA, 0x43, 0x77, 0x51, 0x46,
	0xBF, 0x59, 0xE0, 0x57, 0xFA, 0x55, 0x6A, 0xC2, 0x4B, 0x63,
	0x24, 0xEE, 0x9E, 0x64, 0x96, 0xBE, 0x13, 0xF7, 0x0B, 0xEC,
	0x0E, 0xEA, 0xC8, 0x8B, 0x3A, 0x59, 0xB5, 0x28, 0xF6, 0x49,
	0x40, 0xC7, 0x89, 0x80, 0x39, 0x97, 0x66, 0x7A, 0xC5, 0x90,
	0xB7, 0x98, 0x3F, 0x11, 0x45, 0xEA, 0xA2, 0xF1, 0x77, 0x7B,
	0xBE, 0x3F, 0x5A, 0x5C, 0xD5, 0xA4, 0x5F, 0xBA, 0x96, 0x87,
	0x77, 0x2D, 0x23, 0xA0, 0x56, 0x5B, 0x14, 0x2D, 0xD6, 0x6C,
	0xF1, 0xCC, 0x0F, 0xD9, 0x7D, 0x42, 0x72, 0x9A, 0x8B, 0xBE,
	0x3E, 0xCB, 0xB4, 0xE3, 0xB9, 0xA8, 0xC2, 0x8F, 0xBA, 0xEB,
	0x12, 0xFE, 0x3E, 0x90, 0x4B, 0xDC, 0x8C, 0xA0, 0xD2, 0x26,
	0x1F, 0x26, 0x78, 0x6E, 0x89, 0x15, 0x59, 0xED, 0x8B, 0x7E,
	0x00, 0x5E, 0xFF, 0xDB, 0x55, 0x60, 0xE3, 0x52, 0x8A, 0x03,
	0x9C, 0xE1, 0x33, 0xE6, 0x9F, 0x17, 0x39, 0x42, 0xE7, 0x26,
	0xAE, 0x3D, 0xC0, 0x66, 0x9F, 0x3C, 0x97, 0xC6, 0x75, 0xAC,
	0x5B, 0xD1, 0xB2, 0x51, 0xCA, 0xB6, 0x4F, 0xFD, 0xAF, 0x41,
	0xF8, 0x8B, 0x5A, 0x8D, 0xC7, 0xCA, 0x3A, 0xB7, 0xE3, 0x00,
	0x7D, 0x20, 0xFA, 0xF1, 0xDE, 0xDA, 0x10, 0xBD, 0x85, 0x09,
	0xA0, 0xE1, 0x24, 0x18, 0x64, 0x38, 0xBA, 0x1C, 0x16, 0x15,
	0x71, 0xA6, 0xC2, 0x02, 0xBA, 0x27, 0xF4, 0xE3, 0x3F, 0xA2,
	0x2E, 0x89, 0xBA, 0xC9, 0xCD, 0x0B, 0x5A, 0x95, 0x26, 0x7D,
	0x10, 0xBE, 0xE3, 0x96, 0x99, 0x4A, 0x2F, 0xAB, 0x9B, 0xBD,
	0xD0, 0xB9, 0xDC, 0x43, 0xF9, 0xCB
    };
    static unsigned char dh2048_g[] = { 0x02 };

    DH *dh;
    if ((dh=DH_new()) == NULL) return(NULL);

#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
    dh->p=BN_bin2bn(dh2048_p,sizeof(dh2048_p),NULL);
    dh->g=BN_bin2bn(dh2048_g,sizeof(dh2048_g),NULL);
    if ((dh->p == NULL) || (dh->g == NULL))
    {
      DH_free(dh);
      return(NULL);
    }
#else
    // API change in OpenSSL 1.1.0: DH is now an opaque type
    BIGNUM *dhp_bn = BN_bin2bn(dh2048_p, sizeof (dh2048_p), NULL);
    BIGNUM *dhg_bn = BN_bin2bn(dh2048_g, sizeof (dh2048_g), NULL);
    if (dhp_bn == NULL || dhg_bn == NULL || !DH_set0_pqg(dh, dhp_bn, NULL, dhg_bn))
	{
        DH_free(dh);
        BN_free(dhp_bn);
        BN_free(dhg_bn);
        return NULL;
    }
#endif

    return dh;
}

int DcmTLSTransportLayer_certificateValidationCallback(int ok, X509_STORE_CTX * /* storeContext */)
{
  // this callback is called whenever OpenSSL has validated a X.509 certificate.
  // we could for example print it:
  // DcmTLSTransportLayer::printX509Certificate(cout, storeContext->cert);
  return ok;
}

/* buf     : buffer to write password into
 * size    : length of buffer in bytes
 * rwflag  : nonzero if the password will be used as a new password, i.e. user should be asked to repeat the password
 * userdata: arbitrary pointer that can be set with SSL_CTX_set_default_passwd_cb_userdata()
 * returns : number of bytes written to password buffer, -1 upon error
 */
extern "C" int DcmTLSTransportLayer_passwordCallback(char *buf, int size, int rwflag, void *userdata);

int DcmTLSTransportLayer_passwordCallback(char *buf, int size, int /* rwflag */, void *userdata)
{
  if (userdata == NULL) return -1;
  OFString *password = OFreinterpret_cast(OFString *, userdata);
  int passwordSize = OFstatic_cast(int, password->length());
  if (passwordSize > size) passwordSize = size;
  strncpy(buf, password->c_str(), passwordSize);
  return passwordSize;
}

DcmTLSTransportLayer::DcmTLSTransportLayer()
: DcmTransportLayer()
, transportLayerContext(NULL)
, canWriteRandseed(OFFalse)
, privateKeyPasswd()
, role(NET_ACCEPTORREQUESTOR)
{
}

DcmTLSTransportLayer::DcmTLSTransportLayer(T_ASC_NetworkRole networkRole, const char *randFile, OFBool initOpenSSL)
: DcmTransportLayer()
, transportLayerContext(NULL)
, canWriteRandseed(OFFalse)
, privateKeyPasswd()
, role(networkRole)
{
   if (initOpenSSL) initializeOpenSSL();
   if (randFile) seedPRNG(randFile);

#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
   // on versions of OpenSSL older than 1.1.0, we use the
   // SSLv23 methods and not the TLSv1 methods because the latter
   // only accept TLS 1.0 and prevent the negotiation of newer
   // versions of TLS.
   // We use SSL_CTX_set_options() to disable SSLv2 and SSLv3.
   switch (networkRole)
   {
     case NET_ACCEPTOR:
       transportLayerContext = SSL_CTX_new(SSLv23_server_method());
       break;
     case NET_REQUESTOR:
       transportLayerContext = SSL_CTX_new(SSLv23_client_method());
       break;
     case NET_ACCEPTORREQUESTOR:
       transportLayerContext = SSL_CTX_new(SSLv23_method());
       break;
   }
   if (transportLayerContext) SSL_CTX_set_options(transportLayerContext, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
#else
   // starting with OpenSSL 1.1.0, a new TLS_method() is offered
   // that automatically selects the highest version of the TLS
   // protocol supported by client and server.
   // The previous TLSv1_methods are now deprecated and generate
   // a warning.
   switch (networkRole)
   {
     case NET_ACCEPTOR:
       transportLayerContext = SSL_CTX_new(TLS_server_method());
       break;
     case NET_REQUESTOR:
       transportLayerContext = SSL_CTX_new(TLS_client_method());
       break;
     case NET_ACCEPTORREQUESTOR:
       transportLayerContext = SSL_CTX_new(TLS_method());
       break;
   }

   // starting with OpenSSL 1.1.0, we explicitly need to set the security level to 0
   // if we want to support any of the NULL ciphersuites. Since we manage the list
   // of supported ciphersuites ourselves and prevent a mix of NULL and non-NULL
   // ciphersuites, this is safe.
   if (transportLayerContext) SSL_CTX_set_security_level(transportLayerContext, 0);
 #endif

   if (transportLayerContext == NULL)
   {
      const char *result = ERR_reason_error_string(ERR_peek_error());
      if (result == NULL) result = "unknown error in SSL_CTX_new()";
      DCMTLS_ERROR("unable to create TLS transport layer: " << result);
   }
   else
   {
     // create default set of DH parameters
     DH *dhparams = get_dh2048();
     if (dhparams)
     {
       SSL_CTX_set_tmp_dh(transportLayerContext,dhparams);
       DH_free(dhparams); // safe because of reference counting
     }
     else DCMTLS_ERROR("unable to create Diffie-Hellman parameters.");

     // create Elliptic Curve DH parameters
#ifndef OPENSSL_NO_ECDH
#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
     // we create ECDH parameters for the NIST P-256 (secp256r1) curve
     // as recommended by BCP 195.
     EC_KEY  *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
     if (ecdh)
     {
       SSL_CTX_set_tmp_ecdh(transportLayerContext, ecdh);
       EC_KEY_free(ecdh); /* Safe because of reference counts */
     }
     else DCMTLS_ERROR("unable to create Elliptic-Curve Diffie-Hellman parameters.");
#else
    // OpenSSL 1.0.2 and newer have this function, which causes
    // the server to automatically select the most appropriate shared curve for each client.
    if (0 == SSL_CTX_set_ecdh_auto(transportLayerContext, 1))
    {
      DCMTLS_ERROR("unable to create Elliptic-Curve Diffie-Hellman parameters.");
    }
#endif /* OPENSSL_VERSION_NUMBER < 0x10002000L */
#endif /* OPENSSL_NO_ECDH */

    // set default certificate verification strategy
    setCertificateVerification(DCV_requireCertificate);

#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
    // The TLS 1.2 Signature Algorithms extension is only supported in OpenSSL 1.0.2 and newer.

    if (networkRole != NET_ACCEPTOR)
    {
      // BCP 195: Clients SHOULD indicate to servers that they request SHA-256,
      // by using the "Signature Algorithms" extension defined in TLS 1.2.
      // We implement this by requesting SHA-256 OR BETTER, i.e. we also indicate
      // support for SHA-384 and SHA-512.

      const int slist[] = {NID_sha256, EVP_PKEY_RSA, NID_sha256, EVP_PKEY_DSA, NID_sha256, EVP_PKEY_EC,
                           NID_sha384, EVP_PKEY_RSA, NID_sha384, EVP_PKEY_DSA, NID_sha384, EVP_PKEY_EC,
                           NID_sha512, EVP_PKEY_RSA, NID_sha512, EVP_PKEY_DSA, NID_sha512, EVP_PKEY_EC};

      if (0 == SSL_CTX_set1_sigalgs(transportLayerContext, slist, sizeof(slist)/sizeof(int)))
      {
        DCMTLS_ERROR("unable to configure the TLS 1.2 Signature Algorithms extension.");
      }
    }

    // The TLS Supported Elliptic Curves extension (RFC 4492) is only supported in OpenSSL 1.0.2 and newer.

    // BCP 195: Both clients and servers SHOULD include the "Supported Elliptic Curves" extension.
    // For interoperability, clients and servers SHOULD support the NIST P-256 (secp256r1) curve
    // (in OpenSSL this curve is called "prime256v1").

    // BCP 195: Curves of less than 192 bits SHOULD NOT be used.
    // Actually we only enable curves with at least 256 bits in DCMTK, following NIST and BSI recommendations.
    const int eclist[] = {
      // The list of elliptic curves actually supported by OpenSSL 1.0.2
      // seems to be undocumented. See implementation of tls1_ec_nid2curve_id()
      // for a list of supported NIDs. Here are all elliptic curves
      // supported by OpenSSL 1.0.2 that have 256 or more bits.
      NID_X9_62_prime256v1,  NID_secp256k1,         NID_secp384r1,
      NID_secp521r1,         NID_sect283k1,         NID_sect283r1,
      NID_sect409k1,         NID_sect409r1,         NID_sect571k1,
      NID_sect571r1,         NID_brainpoolP256r1,   NID_brainpoolP384r1,
      NID_brainpoolP512r1
    };

    if (0 == SSL_CTX_set1_curves(transportLayerContext, eclist, sizeof(eclist)/sizeof(int)))
    {
      DCMTLS_ERROR("unable to configure the TLS Supported Elliptic Curves extension.");
    }
#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */

    if (NET_REQUESTOR != networkRole)
    {
      // BCP 195: Servers MUST prefer TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 over weaker cipher suites whenever it is proposed, even if it is not the first proposal.
      // BCP 195: Servers SHOULD prefer stronger cipher suites unless there are compelling reasons to choose otherwise
      // BCP 195: Implementations MUST support and prefer to negotiate cipher suites offering forward secrecy
      // This all requires that when acting as a server we select the ciphersuites by our order of preference,
      // which implements all three recommendations by sorting the list of supported ciphersuites appropriately.
      if (0 == SSL_CTX_set_options(transportLayerContext, SSL_OP_CIPHER_SERVER_PREFERENCE))
      {
        DCMTLS_ERROR("unable to configure the TLS layer to select ciphersuites by server preference.");
      }
    }

  } /* transportLayerContext != NULL */
}

DcmTLSTransportLayer::DcmTLSTransportLayer(OFrvalue_ref(DcmTLSTransportLayer) rhs)
: DcmTransportLayer(OFrvalue_ref_upcast(DcmTransportLayer, rhs))
, transportLayerContext(rhs.transportLayerContext)
, canWriteRandseed(OFmove(OFrvalue_access(rhs).canWriteRandseed))
, privateKeyPasswd(OFmove(OFrvalue_access(rhs).privateKeyPasswd))
{
  OFrvalue_access(rhs).transportLayerContext = NULL;
}

DcmTLSTransportLayer& DcmTLSTransportLayer::operator=(OFrvalue_ref(DcmTLSTransportLayer) rhs)
{
  if (this != &rhs)
  {
    clear();
    DcmTransportLayer::operator=(OFrvalue_ref_upcast(DcmTransportLayer, rhs));
    transportLayerContext = rhs.transportLayerContext;
    canWriteRandseed = OFmove(OFrvalue_access(rhs).canWriteRandseed);
    privateKeyPasswd = OFmove(OFrvalue_access(rhs).privateKeyPasswd);
    OFrvalue_access(rhs).transportLayerContext = NULL;
  }
  return *this;
}

void DcmTLSTransportLayer::clear()
{
  if (transportLayerContext)
  {
    SSL_CTX_free(transportLayerContext);
    transportLayerContext = NULL;
    canWriteRandseed = OFFalse;
    privateKeyPasswd.clear();
  }
}

DcmTLSTransportLayer::operator OFBool() const
{
  return !!transportLayerContext;
}

OFBool DcmTLSTransportLayer::operator!() const
{
  return !transportLayerContext;
}

OFBool DcmTLSTransportLayer::setTempDHParameters(const char *filename)
{
  if ((filename==NULL)||(transportLayerContext==NULL)) return OFFalse;
  DH *dh = NULL;
  BIO *bio = BIO_new_file(filename,"r");
  if (bio)
  {
    dh = PEM_read_bio_DHparams(bio,NULL,NULL,NULL);
    BIO_free(bio);
    if (dh)
    {
      // check BCP 195 recommendation: With a key exchange based on modular
      // exponential (MODP) Diffie-Hellman groups ("DHE" cipher suites),
      // DH key lengths of at least 2048 bits are RECOMMENDED.
      if (DH_bits(dh) < 2048)
      {
        DCMTLS_WARN("Key length of Diffie-Hellman parameter file too short: RFC 7525 recommends at least 2048 bits, but the key in file '"
          << filename << "' is only " << DH_bits(dh) << " bits.");
      }
      SSL_CTX_set_tmp_dh(transportLayerContext,dh);
      DH_free(dh); /* Safe because of reference counts in OpenSSL */
      return OFTrue;
    }
  }
  return OFFalse;
}

void DcmTLSTransportLayer::setPrivateKeyPasswd(const char *thePasswd)
{
  if (thePasswd) privateKeyPasswd = thePasswd;
  else privateKeyPasswd.clear();
  if (transportLayerContext)
  {
    /* register callback that replaces console input */
    SSL_CTX_set_default_passwd_cb(transportLayerContext, DcmTLSTransportLayer_passwordCallback);
    SSL_CTX_set_default_passwd_cb_userdata(transportLayerContext, &privateKeyPasswd);
  }
  return;
}

void DcmTLSTransportLayer::setPrivateKeyPasswdFromConsole()
{
  privateKeyPasswd.clear();
  if (transportLayerContext)
  {
    /* deregister callback that replaces console input */
    SSL_CTX_set_default_passwd_cb(transportLayerContext, NULL);
    SSL_CTX_set_default_passwd_cb_userdata(transportLayerContext, NULL);
  }
  return;
}

void DcmTLSTransportLayer::setCertificateVerification(DcmCertificateVerification verificationType)
{
  if (transportLayerContext)
  {
    int vmode = 0;
    switch (verificationType)
    {
      case DCV_requireCertificate:
        vmode =  SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
        break;
      case DCV_checkCertificate:
        vmode =  SSL_VERIFY_PEER;
        break;
      case DCV_ignoreCertificate:
        break;
    }
    SSL_CTX_set_verify(transportLayerContext, vmode, DcmTLSTransportLayer_certificateValidationCallback);
  }
  return;
}

DcmTransportLayerStatus DcmTLSTransportLayer::activateCipherSuites()
{
  OFString cslist;
  ciphersuites.getListOfCipherSuitesForOpenSSL(cslist, (role != NET_REQUESTOR));
  if (transportLayerContext)
  {
    if (!SSL_CTX_set_cipher_list(transportLayerContext, cslist.c_str()))
    {
      const char *err = ERR_reason_error_string(ERR_peek_error());
      if (err) DCMTLS_ERROR("OpenSSL error: " << err);
      return TCS_tlsError;
    }

    SSL_CTX_set_options(transportLayerContext, ciphersuites.getTLSOptions());

  } else return TCS_illegalCall;

  return TCS_ok;
}

DcmTLSTransportLayer::~DcmTLSTransportLayer()
{
  clear();
}

DcmTransportLayerStatus DcmTLSTransportLayer::setPrivateKeyFile(const char *fileName, DcmKeyFileFormat fileType)
{
  if (transportLayerContext)
  {
    if (0 >= SSL_CTX_use_PrivateKey_file(transportLayerContext, fileName, lookupOpenSSLCertificateFormat(fileType)))
    {
      const char *err = ERR_reason_error_string(ERR_peek_error());
      if (err) DCMTLS_ERROR("OpenSSL error: " << err);
      return TCS_tlsError;
    }
  } else return TCS_illegalCall;
  return TCS_ok;
}

DcmTransportLayerStatus DcmTLSTransportLayer::setCertificateFile(const char *fileName, DcmKeyFileFormat fileType)
{
  if (transportLayerContext)
  {
    int result = 0;
    X509 *certificate = loadCertificateFile(fileName, fileType);
    if (certificate)
    {
      // TODO: Check if the certificate is RSA, and if so, if the public key is >= 2048 bits
      int bits = getRSAKeySize(certificate);
      if ((bits > 0) && (bits < 2048))
      {
        DCMTLS_WARN("Key length of RSA public key too short: RFC 7525 recommends at least 2048 bits for RSA keys, but the key in certificate file '"
          << fileName << "' is only " << bits << " bits.");
      }
      const char *hash = checkRSAHashKeyIsSHA2(certificate);
      if (hash)
      {
        DCMTLS_WARN("Certificate hash key not SHA-256: RFC 7525 recommends the use of SHA-256 for RSA certificates, but certificate file '"
          << fileName << "' uses '" << hash << "'.");
      }
      result = SSL_CTX_use_certificate(transportLayerContext, certificate); // copies certificate into context
      X509_free(certificate);
    } else result = -1;

    if (result <= 0)
    {
      const char *err = ERR_reason_error_string(ERR_peek_error());
      if (err) DCMTLS_ERROR("OpenSSL error: " << err);
      return TCS_tlsError;
    }
  } else return TCS_illegalCall;
  return TCS_ok;
}

OFBool DcmTLSTransportLayer::checkPrivateKeyMatchesCertificate()
{
  if (transportLayerContext)
  {
    if (SSL_CTX_check_private_key(transportLayerContext)) return OFTrue;
  }
  return OFFalse;
}

DcmTransportLayerStatus DcmTLSTransportLayer::addVerificationFlags(unsigned long flags)
{
  X509_VERIFY_PARAM* const parameter = DCMTK_SSL_CTX_get0_param(transportLayerContext);
  return parameter && X509_VERIFY_PARAM_set_flags(parameter,flags) ? TCS_ok : TCS_unspecifiedError;
}

DcmTransportLayerStatus DcmTLSTransportLayer::addTrustedCertificateFile(const char *fileName, DcmKeyFileFormat fileType)
{
  if (transportLayerContext)
  {
    X509_LOOKUP *x509_lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(transportLayerContext), X509_LOOKUP_file());
    if (x509_lookup == NULL)
    {
      const char *err = ERR_reason_error_string(ERR_peek_error());
      if (err) DCMTLS_ERROR("OpenSSL error: " << err);
      return TCS_tlsError;
    }
    if (! X509_LOOKUP_load_file(x509_lookup, fileName, lookupOpenSSLCertificateFormat(fileType)))
    {
      const char *err = ERR_reason_error_string(ERR_peek_error());
      if (err) DCMTLS_ERROR("OpenSSL error: " << err);
      return TCS_tlsError;
    }
  } else return TCS_illegalCall;
  return TCS_ok;
}

DcmTransportLayerStatus DcmTLSTransportLayer::addTrustedCertificateDir(const char *pathName, DcmKeyFileFormat fileType)
{
  if (transportLayerContext)
  {
    X509_LOOKUP *x509_lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(transportLayerContext), X509_LOOKUP_hash_dir());
    if (x509_lookup == NULL)
    {
      const char *err = ERR_reason_error_string(ERR_peek_error());
      if (err) DCMTLS_ERROR("OpenSSL error: " << err);
      return TCS_tlsError;
    }
    if (! X509_LOOKUP_add_dir(x509_lookup, pathName, lookupOpenSSLCertificateFormat(fileType)))
    {
      const char *err = ERR_reason_error_string(ERR_peek_error());
      if (err) DCMTLS_ERROR("OpenSSL error: " << err);
      return TCS_tlsError;
    }
  } else return TCS_illegalCall;
  return TCS_ok;
}

DcmTransportLayerStatus DcmTLSTransportLayer::addTrustedClientCertificateFile(const char *fileName)
{
  if (transportLayerContext)
  {
    STACK_OF(X509_NAME) *caNames = sk_X509_NAME_dup(SSL_CTX_get_client_CA_list(transportLayerContext));
    if (caNames == NULL) caNames = sk_X509_NAME_new_null();
    STACK_OF(X509_NAME) *newCaNames = SSL_load_client_CA_file(fileName);
    for (int i = 0; i < sk_X509_NAME_num(newCaNames); ++i)
    {
      X509_NAME *newCaName = sk_X509_NAME_value(newCaNames,i);
      if (sk_X509_NAME_find(caNames,newCaName) == -1)
      {
        sk_X509_NAME_push(caNames,X509_NAME_dup(newCaName));
      }
    }
    sk_X509_NAME_pop_free(newCaNames,X509_NAME_free);
    SSL_CTX_set_client_CA_list(transportLayerContext,caNames);
  } else return TCS_illegalCall;
  return TCS_ok;
}

DcmTransportConnection *DcmTLSTransportLayer::createConnection(DcmNativeSocketType openSocket, OFBool useSecureLayer)
{
  if (useSecureLayer)
  {
    if (transportLayerContext)
    {
      SSL *newConnection = SSL_new(transportLayerContext);
      if (newConnection)
      {
        // On Win64, the following line will cause a warning because the native
        // type for sockets there is 64-bit, and OpenSSL uses a 32-bit int file descriptor.
        // This should be fixed in OpenSSL, there is nothing we can do here.
        SSL_set_fd(newConnection, openSocket);
        return new DcmTLSConnection(openSocket, newConnection);
      }
    }
    return NULL;
  }
  else return DcmTransportLayer::createConnection(openSocket, useSecureLayer);
}

void DcmTLSTransportLayer::seedPRNG(const char *randFile)
{
#ifdef _WIN32
  RAND_screen();
#endif
  if (randFile)
  {
#ifdef HAVE_RAND_EGD
    if (RAND_egd(randFile) <= 0)
#endif
    {
      RAND_load_file(randFile ,-1);
    }
  }
  if (RAND_status()) canWriteRandseed = OFTrue;
  else
  {
    /* warn user */
    DCMTLS_WARN("PRNG for TLS not seeded with sufficient random data.");
  }
}

void DcmTLSTransportLayer::addPRNGseed(void *buf, size_t bufSize)
{
  RAND_seed(buf,OFstatic_cast(int, bufSize));
}

OFBool DcmTLSTransportLayer::writeRandomSeed(const char *randFile)
{
  if (canWriteRandseed && randFile)
  {
    if (RAND_write_file(randFile)) return OFTrue;
  }
  return OFFalse;
}

OFString DcmTLSTransportLayer::dumpX509Certificate(X509 *peerCertificate)
{
  if (peerCertificate)
  {
    long certVersion = 0;                     /* certificate type */
    long certSerialNumber = -1;               /* certificate serial number */
    OFString certValidNotBefore;              /* certificate validity - not before */
    OFString certValidNotAfter;               /* certificate validity - not after */
    char certSubjectName[1024];               /* certificate subject name (DN) */
    char certIssuerName[1024];                /* certificate issuer name (DN) */
    const char *certPubKeyType = "unknown";   /* certificate public key type */
    int certPubKeyBits = 0;                   /* certificate number of bits in public key */
    certSubjectName[0]= '\0';
    certIssuerName[0]= '\0';
    certVersion = X509_get_version(peerCertificate) +1;
    certSerialNumber = ASN1_INTEGER_get(X509_get_serialNumber(peerCertificate));
    BIO *certValidNotBeforeBIO = BIO_new(BIO_s_mem());
    char *bufptr = NULL;
    if (certValidNotBeforeBIO)
    {
      ASN1_UTCTIME_print(certValidNotBeforeBIO, X509_get_notBefore(peerCertificate));
      BIO_write(certValidNotBeforeBIO,"\0",1);
      BIO_get_mem_data(certValidNotBeforeBIO, OFreinterpret_cast(char *, &bufptr));
      if (bufptr) certValidNotBefore = bufptr;
      BIO_free(certValidNotBeforeBIO);
    }
    bufptr = NULL;
    BIO *certValidNotAfterBIO  = BIO_new(BIO_s_mem());
    if (certValidNotAfterBIO)
    {
      ASN1_UTCTIME_print(certValidNotAfterBIO, X509_get_notAfter(peerCertificate));
      BIO_write(certValidNotAfterBIO,"\0",1);
      BIO_get_mem_data(certValidNotAfterBIO, OFreinterpret_cast(char *, &bufptr));
      if (bufptr) certValidNotAfter = bufptr;
      BIO_free(certValidNotAfterBIO);
    }
    X509_NAME_oneline(X509_get_subject_name(peerCertificate), certSubjectName, 1024);
    X509_NAME_oneline(X509_get_issuer_name(peerCertificate), certIssuerName, 1024);
    EVP_PKEY *pubkey = X509_get_pubkey(peerCertificate); // creates copy of public key
    if (pubkey)
    {
      switch (EVP_PKEY_base_id(pubkey))
      {
        case EVP_PKEY_RSA:
          certPubKeyType = "RSA";
          break;
        case EVP_PKEY_DSA:
          certPubKeyType = "DSA";
          break;
        case EVP_PKEY_DH:
          certPubKeyType = "DH";
          break;
        default:
          /* nothing */
          break;
      }
      certPubKeyBits = EVP_PKEY_bits(pubkey);
      EVP_PKEY_free(pubkey);
    }
    OFOStringStream out;
    out << "Peer X.509v" << certVersion << " Certificate" << OFendl
        << "  Subject     : " << certSubjectName << OFendl
        << "  Issued by   : " << certIssuerName << OFendl
        << "  Serial no.  : " << certSerialNumber << OFendl
        << "  Validity    : not before " << certValidNotBefore << ", not after " << certValidNotAfter << OFendl
        << "  Public key  : " << certPubKeyType << ", " << certPubKeyBits << " bits" << OFStringStream_ends;
    OFSTRINGSTREAM_GETOFSTRING(out, ret)
    return ret;
  } else {
    return "Peer did not provide a certificate or certificate verification is disabled.";
  }
}

DcmTransportLayerStatus DcmTLSTransportLayer::setTLSProfile(DcmTLSSecurityProfile profile)
{
  return ciphersuites.setTLSProfile(profile);
}

void DcmTLSTransportLayer::clearTLSProfile()
{
  ciphersuites.clearTLSProfile();
}

DcmTransportLayerStatus DcmTLSTransportLayer::addCipherSuite(const char *suite)
{
  return ciphersuites.addCipherSuite(suite);
}

DcmTLSTransportLayer::native_handle_type DcmTLSTransportLayer::getNativeHandle()
{
  return transportLayerContext;
}

int DcmTLSTransportLayer::lookupOpenSSLCertificateFormat(DcmKeyFileFormat fileType)
{
  int result = -1;
  switch (fileType)
  {
    case DCF_Filetype_PEM:
      result = SSL_FILETYPE_PEM;
      break;
    case DCF_Filetype_ASN1:
      result = SSL_FILETYPE_ASN1;
      break;
  }
  return result;
}


void DcmTLSTransportLayer::printSupportedCiphersuites(STD_NAMESPACE ostream& os) const
{
  ciphersuites.printSupportedCiphersuites(os);
}

void DcmTLSTransportLayer::getListOfCipherSuitesForOpenSSL(OFString& cslist) const
{
  ciphersuites.getListOfCipherSuitesForOpenSSL(cslist, (role != NET_REQUESTOR));
}

int DcmTLSTransportLayer::getRSAKeySize(X509 *certificate)
{
  if (certificate)
  {
    EVP_PKEY *pubkey = X509_get_pubkey(certificate); // creates a copy of the public key
    if (pubkey && (EVP_PKEY_base_id(pubkey) == EVP_PKEY_RSA))
    {
      int certPubKeyBits = EVP_PKEY_bits(pubkey); // RSA public key size, in bits
      EVP_PKEY_free(pubkey);
      return certPubKeyBits;
    }
  }
  return 0; // certificate not present or not RSA
}

const char *DcmTLSTransportLayer::checkRSAHashKeyIsSHA2(X509 *certificate)
{
  if (certificate)
  {
    EVP_PKEY *pubkey = X509_get_pubkey(certificate); // creates copy of public key
    if (pubkey && (EVP_PKEY_base_id(pubkey) == EVP_PKEY_RSA))
    {
      int nid = X509_get_signature_nid(certificate);
      EVP_PKEY_free(pubkey);
      switch (nid)
      {
          case NID_sha256WithRSAEncryption:
          case NID_sha384WithRSAEncryption:
          case NID_sha512WithRSAEncryption:
          return NULL; // hash key uses SHA256 (or better)
        default:
          return OBJ_nid2sn(nid); // hash key does not follow BCP 195 recommendation to use SHA256
      }
    }
  }
  return NULL; // default: everything is OK
}

X509 *DcmTLSTransportLayer::loadCertificateFile(const char *fileName, DcmKeyFileFormat fileType)
{
  X509 *result = NULL;
  BIO *in=BIO_new_file(fileName, "rb");
  if (in)
  {
    if (fileType == DCF_Filetype_ASN1)
    {
      result=d2i_X509_bio(in,NULL);
    }
    else if (fileType == DCF_Filetype_PEM)
    {
      result=PEM_read_bio_X509(in, NULL, NULL, NULL);
    }
    BIO_free(in);
  }
  return result;
}

void DcmTLSTransportLayer::initializeOpenSSL()
{
  // the call to SSL_library_init was not needed in OpenSSL versions prior to 0.9.8,
  // but the API has been available at least since 0.9.5.
  SSL_library_init();
  SSL_load_error_strings();
  OpenSSL_add_all_algorithms();
}

const char *DcmTLSTransportLayer::getOpenSSLVersionName()
{
  return OPENSSL_VERSION_TEXT;
}

#else  /* WITH_OPENSSL */

/* make sure that the object file is not completely empty if compiled
 * without OpenSSL because some linkers might fail otherwise.
 */
DCMTK_DCMTLS_EXPORT void tlslayer_dummy_function()
{
  return;
}

#endif /* WITH_OPENSSL */
