
/*---------------------------------------------------------------------------*/
/* ssl.cpp                                                                   */
/* copyright (c) innovaphone 2017                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>

#ifdef _MSC_VER
#pragma warning (push)
#pragma warning (disable : 4996)
#endif

#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/dh.h>
#include <openssl/ssl.h>
#include <openssl/conf.h>
#include <openssl/engine.h>

#include "common/ilib/base64.h"
#include "common/ilib/str.h"

#include <vector>

#include "sdk/common/interface/time.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/certificates.h"
#include "ssl.h"

// OPEN_SSL_CIPHER_LIST

// ECDHE - RSA - AES256 - GCM - SHA384
// ECDHE - RSA - AES256 - SHA384
// ECDHE - RSA - AES256 - SHA
// ECDHE - RSA - AES128 - GCM - SHA256
// ECDHE - RSA - AES128 - SHA256
// ECDHE - RSA - AES128 - SHA
// DHE - RSA - AES256 - GCM - SHA384
// DHE - RSA - AES256 - SHA256
// DHE - RSA - AES256 - SHA
// DHE - RSA - AES128 - GCM - SHA256
// DHE - RSA - AES128 - SHA256
// DHE - RSA - AES128 - SHA
// AES256 - GCM - SHA384
// AES256 - SHA256
// AES256 - SHA
// AES128 - GCM - SHA256
// AES128 - SHA256
// AES128 - SHA

#define OPEN_SSL_CIPHER_LIST    "kEECDH:kEDH:RSA:!MD5:!RC4:!DES:!EXPORT:!NULL:!CAMELLIA:!SEED:!IDEA:!3DES:!DSS:!ADH"

//void __ssl_info_callback(const SSL* ssl, int where, int ret)
//{
//
//    if (ret == 0) {
//        debug->printf("ERROR: __ssl_info_callback: error occured.\n");
//        return;
//    }
//
//    if (where & SSL_CB_LOOP)
//        DBG_TLS(("+ SSL Debug:             LOOP - %40.40s - %5.10s ", SSL_state_string_long(ssl), SSL_state_string(ssl)));
//   
//    if (where & SSL_CB_HANDSHAKE_START)
//        DBG_TLS(("+ SSL Debug:  HANDSHAKE START - %40.40s - %5.10s ", SSL_state_string_long(ssl), SSL_state_string(ssl)));
//    
//    if (where & SSL_CB_HANDSHAKE_DONE)
//        DBG_TLS(("+ SSL Debug:  HANDSHAKE DONE - %40.40s - %5.10s ", SSL_state_string_long(ssl), SSL_state_string(ssl)));
//}

class SSLCachedClientSession : public istd::listElement<class SSLCachedClientSession> {
public:
    SSLCachedClientSession();
    SSLCachedClientSession(const char * remoteAddress, class SSLSocketContext * sslSocketContext, SSL_SESSION * session);
    ~SSLCachedClientSession();

    SSLCachedClientSession * nextSession;
    SSLCachedClientSession * prevSession;
    class SSLSocketContext * sslSocketContext;
    SSL_SESSION * session;
};

class SSLClientSessionPeer : public SSLCachedClientSession, public btree {
    int btree_compare(void * key) { return (strcmp((const char *)key, remoteAddress)); };
    int btree_compare(class btree * b) { return (strcmp(((class SSLClientSessionPeer *)b)->remoteAddress, remoteAddress)); };
public:
    SSLClientSessionPeer(const char * remoteAddress) { this->remoteAddress = _strdup(remoteAddress); };
    ~SSLClientSessionPeer() { if (remoteAddress) free(remoteAddress); };

    char * remoteAddress;
};

class SSLSocketContext : public ISSLSocketContext {
    class IInstanceLog * log;
    byte * cert;
    size_t certLen;
    byte * certificateFingerprint;
    size_t certificateFingerprintLen;
    bool dtls;
    bool srtp;
    bool useClientCertificate;

    SSL_CTX * sharedClientContext;
    SSL_CTX * sharedServerContext;
    std::vector<byte> alpnProtocols;

    static void AppsSslInfoCallback(const SSL *ssl, int where, int ret);
    static int GenerateCookie(SSL *ssl, unsigned char *cookie, unsigned int *cookieLen);
#ifdef OPENSSL_1_0_2
    static int VerifyCookie(SSL *ssl, unsigned char *cookie, unsigned int cookieLen);
#else
    static int VerifyCookie(SSL *ssl, const unsigned char *cookie, unsigned int cookieLen);
#endif
    static int ServerNameCallback(SSL *s, int *ad, void *arg);
    SSL_CTX * CreateNewSSLContext(IInstanceLog * log, bool server, const byte * cert = NULL, size_t certLen = 0);

public:
    SSLSocketContext(class IInstanceLog * const log);
    ~SSLSocketContext();

    class btree * clientSessionCache;
    istd::list<class SSLCachedClientSession> cachedClientSessionList;
    int cachedClientSessionCount;

    void SetInstanceLog(class IInstanceLog * log) { this->log = log; };

    virtual void EnableDTLS(bool useSrtp) { this->dtls = true; this->srtp = useSrtp; };
    virtual bool isDTLS() { return this->dtls; };
    virtual void SetServerCertificate(const byte * cert, size_t certLen, const char * hostName = NULL);
    virtual void SetClientCertificate(const byte * cert, size_t certLen, const char * hostName = NULL);
    virtual const byte * GetCertificateFingerprint(size_t * length) { *length = this->certificateFingerprintLen; return this->certificateFingerprint; };
    virtual void DisableClientCertificate() { this->useClientCertificate = false; }
    virtual void SetALPNProcotols(const char * const * alpnList);

    SSL_CTX * CreateSSLContext(IInstanceLog * log, bool server);
};

SSLCachedClientSession::SSLCachedClientSession()
{
    nextSession = this;
    prevSession = this;
}

SSLCachedClientSession::SSLCachedClientSession(const char * remoteAddress, class SSLSocketContext * sslSocketContext, SSL_SESSION * session)
{
    SSLClientSessionPeer * peer;

    this->sslSocketContext = sslSocketContext;
    this->session = session;
    if (sslSocketContext->cachedClientSessionCount > 100) {
        SSLCachedClientSession * cached = sslSocketContext->cachedClientSessionList.pop_back();
        SSL_SESSION_free(cached->session);
        delete cached;
    }
    class btree * b = sslSocketContext->clientSessionCache->btree_find(remoteAddress);
    if (b) {
        peer = (SSLClientSessionPeer *)b;
    }
    else {
        peer = new SSLClientSessionPeer(remoteAddress);
        sslSocketContext->clientSessionCache = sslSocketContext->clientSessionCache->btree_put(peer);
    }
    nextSession = peer->nextSession;
    prevSession = peer;
    peer->nextSession->prevSession = this;
    peer->nextSession = this;
    sslSocketContext->cachedClientSessionList.push_front(this);
    (sslSocketContext->cachedClientSessionCount)++;
}

SSLCachedClientSession::~SSLCachedClientSession()
{
    if (prevSession != this) {
        if (prevSession->prevSession != this) {
            prevSession->nextSession = nextSession;
            nextSession->prevSession = prevSession;
        }
        else {
            SSLClientSessionPeer * peer = static_cast<class SSLClientSessionPeer *>(prevSession);
            prevSession->nextSession = nextSession;
            nextSession->prevSession = prevSession;
            sslSocketContext->clientSessionCache = sslSocketContext->clientSessionCache->btree_get(peer);
            delete peer;
        }
        (sslSocketContext->cachedClientSessionCount)--;
    }
}

/*-------------------------------------------------------------------------------*/
/* OpenSSLWrapper   	                                                         */
/* ==============   	                                                         */
/* The initialization and shutdown of OpenSSL could not made per socket, because */
/* OpenSSL internal uses globals. Because of this, this wrapper class will be    */
/* used as an singelton class to have an interface to OpenSSL that also acts     */
/* some kind of "global". Thanks for that to the developers of that library. :-/ */
/* Note that this wrapper only wrapps functions we need inside the TLSSocket     */
/* implementation. It"s not an general purpose OpenSSL wrapper.                  */
/*-------------------------------------------------------------------------------*/

class OpenSSL : public ISSLProvider {
    friend class ISSLProvider;
private:
    class SSLSocketContext defaultSSLSocketContext;

    bool VerifyCert(X509 * devCert, BIO * bio, class CertificateInformation * info);
    static bool AddX509Extension(STACK_OF(X509_EXTENSION) * sk, int nid, const char * value);
    long64 ConvertASN1_TimeToLong64(ASN1_TIME * time);

protected:

    OpenSSL();
    ~OpenSSL();

public:
    static void BuildX509Store(X509_STORE * x509_store, BIO * bio, class CertificateInformation * info);

    class ISSLSocketContext * CreateSSLSocketContext(class IInstanceLog * const log) override;
    class ISSLInstance * CreateSSLInstance(IInstanceLog * log, bool server, const char * remoteAddress, class ISocketContext * socketContext, const char * serverName = NULL) override;
    static certificate_error_t ParseCertificate(class IInstanceLog * log, const byte * cert, size_t certLen, BIO *& bio, X509 *& x509, EVP_PKEY *& publicKey, EVP_PKEY *&privateKey, bool privateKeyRequired);
    certificate_error_t ValidateCertificate(class IInstanceLog * log, const byte * cert, size_t certLen, class ICertificateInformation * info = nullptr, bool privateKeyRequired = true, bool verifyChain = true) override;
    bool CreateCertificateSigningRequest(class IInstanceLog * log, class ICertificateSigningRequest * iCsr) override;
    byte * ConvertPEMToDER(class IInstanceLog * log, const char * pem, size_t & outLength) override;
    char * ConvertDERToPEM(class IInstanceLog * log, const byte * der, size_t derLength) override;
    byte * CertificateFingerprint(class IInstanceLog * log, const byte * cert, size_t certLen, size_t * length, fingerprint_t type) override;
    byte * CreateCertificate(class IInstanceLog * log, size_t * certLen, certificate_type_t type) override;
    void SetDefaultClientCertificate(class IInstanceLog * const log, const byte * cert, size_t certLen) override;
};

class ISSLProvider * ISSLProvider::SharedInstance()
{
    static OpenSSL instance;
    return &instance;
}

certificate_error_t ICertificates::ValidateCertificate(class IInstanceLog * log, const byte * cert, size_t certLen, class ICertificateInformation * info, bool privateKeyRequired, bool verifyChain)
{
    return ISSLProvider::SharedInstance()->ValidateCertificate(log, cert, certLen, info, privateKeyRequired, verifyChain);
}

bool ICertificates::CreateCertificateSigningRequest(class IInstanceLog * log, class ICertificateSigningRequest * iCsr)
{
    return ISSLProvider::SharedInstance()->CreateCertificateSigningRequest(log, iCsr);
}

byte * ICertificates::ConvertPEMToDER(class IInstanceLog * log, const char * pem, size_t & outLength)
{
    return ISSLProvider::SharedInstance()->ConvertPEMToDER(log, pem, outLength);
}

char * ICertificates::ConvertDERToPEM(class IInstanceLog * log, const byte * der, size_t derLength)
{
    return ISSLProvider::SharedInstance()->ConvertDERToPEM(log, der, derLength);
}

byte * CertificateFingerprint(class IInstanceLog * log, const byte * cert, size_t certLen, size_t * length, fingerprint_t type)
{
    return ISSLProvider::SharedInstance()->CertificateFingerprint(log, cert, certLen, length, type);
}

byte * CreateCertificate(class IInstanceLog * log, size_t * certLen, certificate_type_t type)
{
    return ISSLProvider::SharedInstance()->CreateCertificate(log, certLen, type);
}

/*-------------------------------------------------------------------------------*/
/* This class acts like a filter to en-/recode SSL data send to / received from  */
/* the TCPSocket and handeld by the TLS Socket.                                  */
/*-------------------------------------------------------------------------------*/

class OpenSSLInstance : public ISSLInstance {
private:
    IInstanceLog * const log;

protected:
    friend class OpenSSL;

    SSL * ssl;
    BIO * bioToRead;
    BIO * bioToWrite;
    SSL_CTX * sslContext;
    bool handshakeDone;
    bool isServer;
    char * remoteAddress;
    class SSLSocketContext * sslSocketContext;

    OpenSSLInstance(IInstanceLog * const log, SSL * ssl, bool server, const char * remoteAddress, class SSLSocketContext * sslSocketContext);
    int HandleSSLCallResult(int res);

public:
    virtual ~OpenSSLInstance();
    int DoHandshake() override;
    int DoShutdown() override;
    void SetShutdown() override;
    int ReadDecoded(const void * buf, size_t bufSize) override;
    int ReadEncoded(const void * buf, size_t bufSize) override;

    int WriteDecoded(const void * buf, size_t len) override;
    int WriteEncoded(const void * buf, size_t len) override;
    bool DataPending() override;
    size_t BytesToSend() override;
    void SetMtu(size_t mtu);

    void SetSSLContext(USSLSocketContext * ctx) override;

    void GetSelectedALPNProtocol(const byte * & protocolList, size_t & protocolListLength);
};

/*-------------------------------------------------------------------------------------------------------------------------*/
/*                                                    class SSLSocketContext                                               */
/*-------------------------------------------------------------------------------------------------------------------------*/

SSLSocketContext::SSLSocketContext(class IInstanceLog * const log)
{
    this->log = log;
    this->sharedClientContext = NULL;
    this->sharedServerContext = NULL;
    this->certificateFingerprint = NULL;
    this->certificateFingerprintLen = 0;
    this->cert = NULL;
    this->certLen = 0;
    this->dtls = false;
    this->srtp = false;
    this->clientSessionCache = NULL;
    this->cachedClientSessionCount = 0;
    this->useClientCertificate = true;
}

SSLSocketContext::~SSLSocketContext()
{
    if(sharedClientContext != NULL)
        SSL_CTX_free(sharedClientContext);
    if(sharedServerContext != NULL)
        SSL_CTX_free(sharedServerContext);
    if(this->cert) free(this->cert);
    if(this->certificateFingerprint) free(this->certificateFingerprint);
    while(!cachedClientSessionList.empty()) {
        SSLCachedClientSession * cached = cachedClientSessionList.pop_back();
        SSL_SESSION_free(cached->session);
        delete cached;
    }
}

void SSLSocketContext::SetServerCertificate(const byte * cert, size_t certLen, const char * hostName)
{
    if(log) log->Log(LOG_TLS, "SSLSocketContext::SetServerCertificate certLen:%u hostName:%s", certLen, hostName);
    if(this->certificateFingerprint) free(this->certificateFingerprint);
    this->certificateFingerprint = NULL;
    free(this->cert);
    this->cert = NULL;
    this->certLen = 0;
    if (cert && certLen) {
        this->cert = (byte *)malloc(certLen);
        this->certLen = certLen;
        memcpy(this->cert, cert, certLen);
        this->certificateFingerprint = ISSLProvider::SharedInstance()->CertificateFingerprint(log, this->cert, this->certLen, &this->certificateFingerprintLen, FINGERPRINT_SHA256);
    }
    if (sharedServerContext) SSL_CTX_free(sharedServerContext);                           // free the old context
    sharedServerContext = NULL;
    if (cert && certLen) {
        sharedServerContext = CreateNewSSLContext(log, true, cert, certLen);      // try to create new cert context with new certificate
    }
}

void SSLSocketContext::SetClientCertificate(const byte * cert, size_t certLen, const char * hostName)
{
    if(log) log->Log(LOG_TLS, "SSLSocketContext::SetClientCertificate certLen:%u hostName:%s", certLen, hostName);
    if(this->certificateFingerprint) free(this->certificateFingerprint);
    this->certificateFingerprint = NULL;
    if(this->cert) free(this->cert);
    this->cert = NULL;
    this->certLen = 0;
    if (cert && certLen) {
        this->cert = (byte *)malloc(certLen);
        this->certLen = certLen;
        memcpy(this->cert, cert, certLen);
        this->certificateFingerprint = ISSLProvider::SharedInstance()->CertificateFingerprint(log, this->cert, this->certLen, &this->certificateFingerprintLen, FINGERPRINT_SHA256);
    }
    if(sharedClientContext) SSL_CTX_free(sharedClientContext);                           // free the old context
    sharedClientContext = NULL;
    if (cert && certLen) {
        sharedClientContext = CreateNewSSLContext(log, false, cert, certLen);      // try to create new cert context with new certificate
    }
}

void SSLSocketContext::SetALPNProcotols(const char * const * alpnList)
{
    ASSERT(alpnList != nullptr && *alpnList != nullptr, "SSLSocketContext::SetALPNProcotols you must hand at least one protocol if you use ALPN");
    this->alpnProtocols.clear();
    if (alpnList != nullptr) {
        int p = 0;
        while (*alpnList != nullptr) {
            size_t len = strlen(*alpnList);

            if (len == 0) this->log->Log(LOG_TLS, "TLSSocket::SetALPNProcotols found empty item - ignoring.");
            else if (len > 255) this->log->Log(LOG_TLS, "TLSSocket::SetALPNProcotols the item '%s' is longer then 255 bytes which isn't allowed - will be ignored.", *alpnList);
            else {
                if (p + len + 1 > this->alpnProtocols.size()) {
                    this->alpnProtocols.resize(this->alpnProtocols.size() + len + 100);
                }

                this->alpnProtocols[p++] = static_cast<byte>(len);
                memcpy(&this->alpnProtocols[p], *alpnList, len);
                p += len;
            }

            ++alpnList;
        }
        this->alpnProtocols.resize(p);
        if (this->sharedClientContext) {
            SSL_CTX_set_alpn_protos(sharedClientContext, this->alpnProtocols.data(), this->alpnProtocols.size());
        }
        if (this->sharedServerContext) {
            SSL_CTX_set_alpn_protos(sharedServerContext, this->alpnProtocols.data(), this->alpnProtocols.size());
        }
    }
}

void SSLSocketContext::AppsSslInfoCallback(const SSL *ssl, int where, int ret)
{
    //debug->printf("SSLSocketContext::AppsSslInfoCallback where=%d ret=%d", where, ret);
    if (where & SSL_CB_HANDSHAKE_DONE) {
        X509 *cert = SSL_get_peer_certificate(ssl);
        if (cert) {
            unsigned char buf[32];
            const EVP_MD *digest = EVP_sha256();
            unsigned int len;

            int rc = X509_digest(cert, digest, buf, &len);
            if (rc == 0 || len != 32) {
                debug->printf("SSLSocketContext::AppsSslInfoCallback Failed to get fingerprint for client certificate %d %d", rc, len);
            }
            else {
#if 0
                if (fingerprint) {
                    char *my_trace;

                    if (memcmp(fingerprint, buf, 32)) {
                        trace("Fingerprint does not match!");
                    }
                    if ((my_trace = (char *)malloc(MAX_LOG_LENGTH)) != NULL) {
                        int tlen = _sprintf(my_trace, "Rx-Fingerprint = ");
                        for (int i = 0; i<32; i++) {
                            tlen += _sprintf(&my_trace[tlen], "%02x:", buf[i]);
                        }
                        trace(my_trace);
                        free(my_trace);
                    }
                }
#endif
            }
        }
        else {
            //debug->printf("SSLSocketContext::AppsSslInfoCallback No peer certificate available");
        }

        unsigned char dtlsBuffer[SRTP_MASTER_KEY_KEY_LEN * 2 + SRTP_MASTER_KEY_SALT_LEN * 2];
        SRTP_PROTECTION_PROFILE * srtp_profile = SSL_get_selected_srtp_profile((SSL *)ssl);
        const char * label = "EXTRACTOR-dtls_srtp";
        memset(dtlsBuffer, 0x0, sizeof(dtlsBuffer));
        int code = SSL_export_keying_material((SSL *)ssl, dtlsBuffer, sizeof(dtlsBuffer), label, strlen(label), NULL, 0, false);

        if (code == 1) {
            USSLSocketContext * user = (USSLSocketContext *)SSL_get_ex_data(ssl, 0);
            if (user) user->ExtractSrtpKeys(dtlsBuffer, ((srtp_profile && srtp_profile->name) ? srtp_profile->name : NULL));
        }
        else {
            debug->printf("SSLSocketContext::AppsSslInfoCallback SSL_export_keying_material failed %d", code);
        }
    }
}

int SSLSocketContext::GenerateCookie(SSL *ssl, unsigned char *cookie, unsigned int *cookieLen)
{
    unsigned char *buffer, result[EVP_MAX_MD_SIZE];
    unsigned int length = 0, resultlength;

    //debug->printf("SSLSocketContext::GenerateCookie");

    unsigned char cookieSecret[16];
    memset(cookieSecret, 0x0, 16);

    /* Create buffer with peer's address and port */
    length = 64;
    buffer = (unsigned char*)OPENSSL_malloc(length);
    if (buffer == NULL) {
        return 0;
    }
    memset(buffer, 0x0, length);

    /* Calculate HMAC of buffer using the secret */
    HMAC(EVP_sha1(), (const void*)cookieSecret, 16, (const unsigned char*)buffer, length, result, &resultlength);
    OPENSSL_free(buffer);

    memcpy(cookie, result, resultlength);
    *cookieLen = resultlength;

    return 1;
}

#ifdef OPENSSL_1_0_2
int SSLSocketContext::VerifyCookie(SSL *ssl, unsigned char *cookie, unsigned int cookieLen)
#else
int SSLSocketContext::VerifyCookie(SSL *ssl, const unsigned char *cookie, unsigned int cookieLen)
#endif
{
    unsigned char *buffer, result[EVP_MAX_MD_SIZE];
    unsigned int length = 0, resultlength;

    //debug->printf("SSLSocketContext::VerifyCookie");

    unsigned char cookieSecret[16];
    memset(cookieSecret, 0x0, 16);

    /* Create buffer with peer's address and port */
    length = 64;
    buffer = (unsigned char*)OPENSSL_malloc(length);
    if (buffer == NULL) {
        return 0;
    }
    memset(buffer, 0x0, length);

    /* Calculate HMAC of buffer using the secret */
    HMAC(EVP_sha1(), (const void*)cookieSecret, 16, (const unsigned char*)buffer, length, result, &resultlength);
    OPENSSL_free(buffer);

    if (cookieLen == resultlength && memcmp(result, cookie, resultlength) == 0) {
        return 1;
    }
    debug->printf("Verify cookie failed!");
    return 0;
}

int SSLSocketContext::ServerNameCallback(SSL *s, int *al, void *arg)
{
    SSLSocketContext * socketContext = (SSLSocketContext *)arg;
    const char * serverName = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
    if (socketContext->log) socketContext->log->Log(LOG_TLS, "SSLSocketContext::ServerNameCallback serverName:%s", serverName);

    // right now we do not use the serverName for anything, but we check if there is another default certificate
    SSL_CTX * context = socketContext->sharedServerContext;
    if (context == NULL) {   // the context for the given cert hasn't been created yet
        return SSL_TLSEXT_ERR_ALERT_FATAL;  // no new context created for own certificate, maybe cert is invalid etc.
    }
    if (context) {
        SSL_set_SSL_CTX(s, context);
        // copy over all the SSL_CTX options 
        SSL_clear_options(s, 0xFFFFFFFFL);
        SSL_set_options(s, SSL_CTX_get_options(context));
    }
    return SSL_TLSEXT_ERR_OK;
}

SSL_CTX * SSLSocketContext::CreateNewSSLContext(IInstanceLog * log, bool server, const byte * cert, size_t certLen)
{
    // Using OpenSSL is hard. A lot of functions exists, but are not or not well
    // documented. For initializing, SSL_CTX_set_default_verify_paths()
    // is such a function. The location of the trusted store is in general for
    // Debian /etc/ssl/certs. But on an other distribution or maybe an other
    // version it could change. Because of this we use set-default-verify-path
    // here.

    SSL_CTX * context;
    if (dtls) {
        if (server) {
            context = SSL_CTX_new(DTLS_server_method());
            //SSL_CTX_set1_sigalgs_list(context, "RSA+SHA256");  //DTLSv1_2 update makes problem with telepohones because it uses rsa224
        }
        else {
            context = SSL_CTX_new(DTLS_client_method());
            //SSL_CTX_set1_client_sigalgs_list(context, "RSA+SHA256");
        }
        if (context) SSL_CTX_set_info_callback(context, AppsSslInfoCallback);
    }
    else {
        if (server) {
            context = SSL_CTX_new(SSLv23_server_method());
        }
        else {
            context = SSL_CTX_new(SSLv23_client_method());
        }
    }

    if ((context != NULL) &&
        (SSL_CTX_set_cipher_list(context, OPEN_SSL_CIPHER_LIST) == 1) &&
        //(SSL_CTX_load_verify_locations(sharedContext, NULL, "/etc/ssl/certs") == 1) &&
        (SSL_CTX_set_default_verify_paths(context) == 1))
    {
        SSL_CTX_set_security_level(context, 1);     // enforce a lower security level, as our old 1024bit certificate doesn't work anymore otherwise, as OpenSSL 3.2 enforce 2048bit per default
        SSL_CTX_set_verify(context, SSL_VERIFY_PEER, NULL); //__ssl_verify_peer);
        SSL_CTX_set_options(context, SSL_OP_NO_SSLv2);
        SSL_CTX_set_options(context, SSL_OP_NO_SSLv3);
        //SSL_CTX_set_options(context, SSL_OP_NO_TLSv1);
        //SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_1);

        if (!this->alpnProtocols.empty()) {
            SSL_CTX_set_alpn_protos(context, this->alpnProtocols.data(), this->alpnProtocols.size());
        }

        if (dtls) {
            SSL_CTX_set_options(context, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
            SSL_CTX_set_session_cache_mode(context, SSL_SESS_CACHE_OFF);
#ifdef OPENSSL_VERSION_MAJOR
            EVP_PKEY * ecdh = EVP_EC_gen("prime256v1");
            SSL_CTX_set_tmp_ecdh(context, ecdh);
            EVP_PKEY_free(ecdh);
#else
            EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
            SSL_CTX_set_tmp_ecdh(context, ecdh);
            EC_KEY_free(ecdh);
#endif
        }
        else {
#ifdef _IOS_
            SSL_CTX_set_session_cache_mode(context, SSL_SESS_CACHE_OFF);
#endif
        }
        if (srtp) {
            SSL_CTX_set_tlsext_use_srtp(context, "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32");
        }

        if (server) {
            SSL_CTX_set_tlsext_servername_callback(context, SSLSocketContext::ServerNameCallback);
            SSL_CTX_set_tlsext_servername_arg(context, this);
        }
        if (cert && certLen) {
            if (dtls) {
                BIO * bio;
                if ((bio = BIO_new_mem_buf((void *)cert, certLen)) == NULL) {
                    if (log) log->Log(LOG_TLS, "OpenSSL::CreateNewSSLContext BIO_new_mem_buf failed: %s", ERR_error_string(ERR_get_error(), NULL));
                    SSL_CTX_free(context);
                    return NULL;
                }
                else {
                    X509 * x509;
                    if ((x509 = PEM_read_bio_X509(bio, NULL, 0, NULL)) == NULL) {
                        if (log) log->Log(LOG_TLS, "OpenSSL::CreateNewSSLContext reading certificate failed: %s", ERR_error_string(ERR_get_error(), NULL));
                        BIO_free_all(bio);
                        SSL_CTX_free(context);
                        return NULL;
                    }
                    BIO_reset(bio); // reset the BIO mem, to start reading from the beginning
                    EVP_PKEY *pKey;
                    if ((pKey = PEM_read_bio_PrivateKey(bio, NULL, 0, NULL)) == NULL) {   // first try to read "normal" private key
                        if (log) log->Log(LOG_TLS, "OpenSSL::CreateNewSSLContext reading private key failed: %s", ERR_error_string(ERR_get_error(), NULL));
                        X509_free(x509);
                        BIO_free_all(bio);
                        SSL_CTX_free(context);
                        return NULL;
                    }
                    if ((SSL_CTX_use_certificate(context, x509) == 1) &&
                        (SSL_CTX_use_PrivateKey(context, pKey) == 1) &&
                        (SSL_CTX_check_private_key(context) == 1)) {
                        SSL_CTX_set_read_ahead(context, 1);
                        SSL_CTX_set_cookie_generate_cb(context, SSLSocketContext::GenerateCookie);
                        SSL_CTX_set_cookie_verify_cb(context, SSLSocketContext::VerifyCookie);
                        EVP_PKEY_free(pKey);
                        X509_free(x509);
                        BIO_free_all(bio);
                        return context;
                    }
                    if (log) log->Log(LOG_TLS, "OpenSSL::CreateNewSSLContext using certificate/key failed: %s", ERR_error_string(ERR_get_error(), NULL));
                    EVP_PKEY_free(pKey);
                    X509_free(x509);
                    BIO_free_all(bio);
                    SSL_CTX_free(context);
                    return NULL;
                }
            }
            else {
                BIO * bio = NULL;
                X509 * x509 = NULL;
                EVP_PKEY *publicKey = NULL;
                EVP_PKEY *privateKey = NULL;
                certificate_error_t ret = OpenSSL::ParseCertificate(log, cert, certLen, bio, x509, publicKey, privateKey, true);
                if (ret == CERTIFICATE_OK) {
                    ret = CERTIFICATE_CHAIN_INVALID;    // just use any false code here
                    if ((SSL_CTX_use_certificate(context, x509) == 1) &&
                        (privateKey && SSL_CTX_use_PrivateKey(context, privateKey) == 1) &&
                        (SSL_CTX_check_private_key(context) == 1)) {

                        X509_STORE *x509_store = NULL;
                        X509_STORE_CTX *x509_store_ctx = NULL;

                        x509_store = X509_STORE_new();
                        X509_STORE_set_flags(x509_store, 0);

                        OpenSSL::BuildX509Store(x509_store, bio, nullptr);

                        x509_store_ctx = X509_STORE_CTX_new();

                        X509_STORE_CTX_init(x509_store_ctx, x509_store, x509, NULL);

                        X509_STORE_CTX_set_purpose(x509_store_ctx, X509_PURPOSE_ANY);
                        X509_verify_cert(x509_store_ctx);
                        stack_st_X509 * chain = X509_STORE_CTX_get_chain(x509_store_ctx);
                        // the chain also contains the public key, so we delete this one first, otherwise it's send twice
                        for (int i = 0; i < sk_X509_num(chain); i++) {
                            X509 * current = sk_X509_value(chain, i);
                            if (X509_cmp(current, x509) == 0) {
                                sk_X509_delete(chain, i);
                                X509_free(current);
                                break;
                            }
                        }

                        if (SSL_CTX_set1_chain(context, chain) == 1) {
                            ret = CERTIFICATE_OK;
                        }
                        else {
                            debug->printf("SSLSocketContext::CreateNewSSLContext SSL_CTX_set1_chain failed");
                        }
                        X509_STORE_CTX_free(x509_store_ctx);
                        X509_STORE_free(x509_store);
                    }
                    else {
                        debug->printf("SSLSocketContext::CreateNewSSLContext using certificate failed privateKey:%p error:%s", privateKey, ERR_error_string(ERR_get_error(), NULL));
                    }
                }
                if (publicKey) EVP_PKEY_free(publicKey);
                if (privateKey) EVP_PKEY_free(privateKey);
                if (x509) X509_free(x509);
                if (bio) BIO_free_all(bio);
                if (ret == CERTIFICATE_OK) {
                    return context;
                }
            }
        }
        else {
            if (!this->useClientCertificate) {  // some servers do not like client certificates, ne idea why, e.g. https://secure-provisioning.snomlab.com/api/v1/
                return context;
            }
            if ((SSL_CTX_use_certificate_file(context, TLS_DEFAULT_CERTIFICATE_PATH, SSL_FILETYPE_PEM) == 1) &&
                (SSL_CTX_use_PrivateKey_file(context, TLS_DEFAULT_CERTIFICATE_PATH, SSL_FILETYPE_PEM) == 1) &&
                (SSL_CTX_check_private_key(context) == 1)) {
                return context;
            }
        }
    }

    if (log) log->Log(LOG_TLS, "OpenSSL::CreateNewSSLContext server:%s failed:%s (%p,%d)", (server ? "true" : "false"), ERR_error_string(ERR_get_error(), NULL), cert, certLen);

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

    return NULL;
}

SSL_CTX * SSLSocketContext::CreateSSLContext(IInstanceLog * log, bool server)
{
    if (server && sharedServerContext) return sharedServerContext;
    else if (!server && sharedClientContext) return sharedClientContext;

    SSL_CTX * context = CreateNewSSLContext(log, server, nullptr, 0);
    if (server) {
        sharedServerContext = context;
    }
    else {
        sharedClientContext = context;
    }
    return context;
}


/*-------------------------------------------------------------------------------------------------------------------------*/
/*                                                    class OpenSSL                                                        */
/*-------------------------------------------------------------------------------------------------------------------------*/

OpenSSL::OpenSSL() :
defaultSSLSocketContext(NULL)
{
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
#ifndef OPENSSL_VERSION_MAJOR
    ERR_load_BIO_strings();
#endif
    ERR_load_crypto_strings();
    SSL_library_init();
}


OpenSSL::~OpenSSL()
{
    ENGINE_cleanup();
    CONF_modules_unload(1);
    ERR_free_strings();
    EVP_cleanup();
    CRYPTO_cleanup_all_ex_data();
}

class ISSLSocketContext * OpenSSL::CreateSSLSocketContext(class IInstanceLog * const log)
{
    return new class SSLSocketContext(log);
}

void OpenSSL::SetDefaultClientCertificate(class IInstanceLog * const log, const byte * cert, size_t certLen)
{
    if (log) defaultSSLSocketContext.SetInstanceLog(log);
    defaultSSLSocketContext.SetClientCertificate(cert, certLen);
}

ISSLInstance * OpenSSL::CreateSSLInstance(IInstanceLog * log, bool server, const char * remoteAddress, class ISocketContext * socketContext, const char * serverName)
{
    class SSLSocketContext * sslSocketContext = (socketContext ? (SSLSocketContext *)socketContext : &defaultSSLSocketContext);
    SSL_CTX * context = sslSocketContext->CreateSSLContext(log, server);
    if (context == NULL) {
        if (context == NULL)
            return NULL;
    }

    OpenSSLInstance * ssl = new OpenSSLInstance(log, SSL_new(context), server, remoteAddress ? remoteAddress : "", sslSocketContext);

    if (ssl->ssl == NULL) {
        log->Log(LOG_TLS, "OpenSSL::CreateSSLInstance server:%s failed:%s", (server ? "true" : "false"), ERR_error_string(ERR_get_error(), NULL));
        delete ssl;
        return NULL;
    }
    ssl->sslContext = context;
    ssl->bioToRead = BIO_new(BIO_s_mem());
    if (ssl->bioToRead == NULL) {
        log->Log(LOG_TLS, "OpenSSL::CreateSSLInstance server:%s failed:%s", (server ? "true" : "false"), ERR_error_string(ERR_get_error(), NULL));
        delete ssl;
        return NULL;
    }

    ssl->bioToWrite = BIO_new(BIO_s_mem());
    if (ssl->bioToWrite == NULL) {
        log->Log(LOG_TLS, "OpenSSL::CreateSSLInstance server:%s failed:%s", (server ? "true" : "false"), ERR_error_string(ERR_get_error(), NULL));
        delete ssl;
        return NULL;
    }

    BIO_set_nbio(ssl->bioToRead, 1);
    BIO_set_mem_eof_return(ssl->bioToRead, -1);
    BIO_set_nbio(ssl->bioToWrite, 1);
    BIO_set_mem_eof_return(ssl->bioToWrite, -1);

    SSL_set_bio(ssl->ssl, ssl->bioToRead, ssl->bioToWrite);
    //#ifdef DEBUG
    //    SSL_set_info_callback(ssl->ssl, __ssl_info_callback);
    //#endif
    SSL_set_verify(ssl->ssl, SSL_VERIFY_NONE, NULL);

    if (server) {
        if (sslSocketContext->isDTLS()) SSL_set_options(ssl->ssl, SSL_OP_COOKIE_EXCHANGE);
        SSL_set_accept_state(ssl->ssl);
    }
    else {
        if (serverName && *serverName) {
            SSL_set_tlsext_host_name(ssl->ssl, serverName);
        }
        if (sslSocketContext->isDTLS() == false) {
            class btree * b = sslSocketContext->clientSessionCache->btree_find(remoteAddress ? remoteAddress : "");
            if (b) {
                SSLCachedClientSession * cached = ((SSLClientSessionPeer *)b)->nextSession;
                SSL_set_session(ssl->ssl, cached->session);
                SSL_SESSION_free(cached->session);
                delete cached;
            }
        }
        SSL_set_connect_state(ssl->ssl);
    }

    return ssl;
}

static int verify_cb(int ok, X509_STORE_CTX *ctx)
{
    if (!ok)
    {
        /* check the error code and current cert*/
        //X509 *currentCert = X509_STORE_CTX_get_current_cert(ctx);
        int certError = X509_STORE_CTX_get_error(ctx);
        int depth = X509_STORE_CTX_get_error_depth(ctx);
        STACK_OF(X509) * chain = X509_STORE_CTX_get_chain(ctx);
        int chainCount = (chain ? sk_X509_num(chain) : 0);
        // check if error is one of the missing issuer cert errors
        if (certError == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT || certError == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
            if ((chainCount - 1) == depth) {
                // if the complete chain together with the public certificate itself is the depth of the error, everything is ok
                ok = true;
            }
        }
        else {
            debug->printf("X509 certificate error %d (depth %d) chainCount:%d", certError, depth, chainCount);
        }
    }

    return(ok);
}

void OpenSSL::BuildX509Store(X509_STORE * x509_store, BIO * bio, class CertificateInformation * info)
{
    STACK_OF(X509_INFO) * inf = NULL;
    X509_INFO * itmp = NULL;
    int i, count = 0;

    inf = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
    for (i = 0; i < sk_X509_INFO_num(inf); i++) {
        itmp = sk_X509_INFO_value(inf, i);
        if (itmp->x509) {
            X509_STORE_add_cert(x509_store, itmp->x509);
            if (info) {
                BIO * bioPem = BIO_new(BIO_s_mem());
                if (bioPem) {
                    if (PEM_write_bio_X509(bioPem, itmp->x509) > 0) {
                        BUF_MEM * bioPemBuf = NULL;
                        BIO_get_mem_ptr(bioPem, &bioPemBuf);
                        if (bioPemBuf && bioPemBuf->length > 0) {
                            char * pem = (char *)malloc(bioPemBuf->length + 1);
                            memset(pem, 0, bioPemBuf->length + 1);
                            BIO_read(bioPem, pem, bioPemBuf->length);
                            info->AddChainCertificate(pem);
                        }
                    }
                    BIO_free(bioPem);
                }
            }
            count++;
        }
    } 
    sk_X509_INFO_pop_free(inf, X509_INFO_free);
}


bool OpenSSL::VerifyCert(X509 * devCert, BIO * bio, class CertificateInformation * info)
{
    int ret = 0;
    X509_STORE *x509_store = NULL;
    X509_STORE_CTX *x509_store_ctx = NULL;

    x509_store = X509_STORE_new();
    X509_STORE_set_verify_cb(x509_store, verify_cb);
    X509_STORE_set_flags(x509_store, 0);
    BuildX509Store(x509_store, bio, info);
    x509_store_ctx = X509_STORE_CTX_new();

    X509_STORE_CTX_init(x509_store_ctx, x509_store, devCert, NULL);

    X509_STORE_CTX_set_purpose(x509_store_ctx, X509_PURPOSE_ANY);
    ret = X509_verify_cert(x509_store_ctx);

    X509_STORE_CTX_free(x509_store_ctx);
    X509_STORE_free(x509_store);

    return ret == 1;
}

certificate_error_t OpenSSL::ParseCertificate(class IInstanceLog * log, const byte * cert, size_t certLen, BIO *& bio, X509 *& x509, EVP_PKEY *& publicKey, EVP_PKEY *&privateKey, bool privateKeyRequired)
{
    certificate_error_t ret = CERTIFICATE_OK;

    /* ---------------------------------------------------------- *
    * Create the Input/Output BIO's.                              *
    * ---------------------------------------------------------- */
    if ((bio = BIO_new_mem_buf((void *)cert, certLen)) == NULL) {
        log->Log(LOG_TLS, "OpenSSL::ParseCertificate BIO_new_mem_buf failed: %s", ERR_error_string(ERR_get_error(), NULL));
        ret = CERTIFICATE_PARSING_FAILED;
    }
    else {
        if ((x509 = PEM_read_bio_X509(bio, NULL, 0, NULL)) == NULL) {
            log->Log(LOG_TLS, "OpenSSL::ParseCertificate reading certificate failed: %s", ERR_error_string(ERR_get_error(), NULL));
            ret = CERTIFICATE_PARSING_FAILED;
        }
        else if ((publicKey = X509_get_pubkey(x509)) == NULL) {
            log->Log(LOG_TLS, "OpenSSL::ParseCertificate reading public key failed: %s", ERR_error_string(ERR_get_error(), NULL));
            ret = CERTIFICATE_PUBLIC_KEY_INVALID;
        }
        else if(privateKeyRequired) {
            BIO_reset(bio);
            if ((privateKey = PEM_read_bio_PrivateKey(bio, NULL, 0, NULL)) == NULL) {   // first try to read "normal" private key
                log->Log(LOG_TLS, "OpenSSL::ParseCertificate reading private key failed: %s", ERR_error_string(ERR_get_error(), NULL));
                ret = CERTIFICATE_PRIVATE_KEY_INVALID;
            }
            BIO_reset(bio);
        }
    }
    return ret;
}

byte * OpenSSL::CreateCertificate(class IInstanceLog * log, size_t * certLen, certificate_type_t type)
{
    if (type == CERTIFICATE_PEM) {
        EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);

        if (EVP_PKEY_keygen_init(ctx) <= 0 ||
            EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) {
            log->Log(LOG_TLS, "OpenSSL::CreateCertificate EVP_PKEY_keygen_init or EVP_PKEY_CTX_set_rsa_keygen_bits failed:%s", ERR_error_string(ERR_get_error(), NULL));
            EVP_PKEY_CTX_free(ctx);
            return NULL;
        }

        EVP_PKEY * pKey = NULL;
        if (EVP_PKEY_keygen(ctx, &pKey) <= 0) {
            log->Log(LOG_TLS, "OpenSSL::CreateCertificate EVP_PKEY_keygen failed:%s", ERR_error_string(ERR_get_error(), NULL));
            EVP_PKEY_CTX_free(ctx);
            return NULL;
        }

        EVP_PKEY_CTX_free(ctx);

        X509 * x509 = X509_new();
        if (!x509) {
            log->Log(LOG_TLS, "OpenSSL::CreateCertificate cannot create the X509 structure %s", ERR_error_string(ERR_get_error(), NULL));
            EVP_PKEY_free(pKey);
            return NULL;
        }

        /* Set the serial number. Some browsers don't accept the default one (0).*/
        long serial = (long)(ITime::TimeStampMilliseconds());
        if (serial == 0) {
            serial = 1;
        }
        ASN1_INTEGER_set(X509_get_serialNumber(x509), serial);

        /* The certificate is valid until 10 years from now. */
        X509_gmtime_adj(X509_get_notBefore(x509), 0);
        X509_time_adj_ex(X509_get_notAfter(x509), 10 * 365, 0, NULL);

        /* Set the certificate's public key from the private key object */
        X509_set_pubkey(x509, pKey);

        /* We want to copy the subject name to the issuer name. */
        X509_NAME* name = X509_get_subject_name(x509);
        if (!name) {
            log->Log(LOG_TLS, "OpenSSL::CreateCertificate cannot get x509 subject name %s", ERR_error_string(ERR_get_error(), NULL));
            EVP_PKEY_free(pKey);
            X509_free(x509);
            return NULL;
        }

        /* Set the country code and common name. */
        X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*)"DE", -1, -1, 0);
        X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)"innovaphone", -1, -1, 0);
        X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)"localhost", -1, -1, 0);

        /* Set the issuer name. */
        X509_set_issuer_name(x509, name);

        /* Sign the certificate with our key. */
        if (!X509_sign(x509, pKey, EVP_sha256())) {
            log->Log(LOG_TLS, "OpenSSL::CreateCertificate cannot sign the certificate %s", ERR_error_string(ERR_get_error(), NULL));
            EVP_PKEY_free(pKey);
            X509_free(x509);
            return NULL;
        }
        byte * output = NULL;
        BIO * bio = BIO_new(BIO_s_mem());
        PEM_write_bio_X509(bio, x509);
        BUF_MEM *bioBuf = NULL;
        BIO_get_mem_ptr(bio, &bioBuf);
        BIO *bioKey = BIO_new(BIO_s_mem());
        PEM_write_bio_PrivateKey(bioKey, pKey, NULL, NULL, 0, 0, NULL);
        BUF_MEM *bioKeyBuf = NULL;
        BIO_get_mem_ptr(bioKey, &bioKeyBuf);
        if (bioBuf && bioBuf->length && bioKeyBuf && bioKeyBuf->length) {
            output = (byte *)malloc(bioBuf->max + bioKeyBuf->max);
            memcpy(output, bioBuf->data, bioBuf->length);
            memcpy(&output[bioBuf->length], bioKeyBuf->data, bioKeyBuf->length);
            *certLen = bioBuf->length + bioKeyBuf->length;
        }
        EVP_PKEY_free(pKey);
        X509_free(x509);
        BIO_free_all(bio);
        BIO_free_all(bioKey);
        return output;
    }
    log->Log(LOG_TLS, "OpenSSL::CreateCertificate unknown certificate type %d", type);
    return NULL;
}

bool OpenSSL::AddX509Extension(STACK_OF(X509_EXTENSION) * sk, int nid, const char * value)
{
    X509_EXTENSION * ex;
    ex = X509V3_EXT_conf_nid(nullptr, nullptr, nid, value);
    if (!ex) return false;
    sk_X509_EXTENSION_push(sk, ex);
    return true;
}

ICertificateSigningRequest * ICertificateSigningRequest::Create(const char * commonName, certificate_key_length_t keyLength)
{
    return new class CertificateSigningRequest(commonName, keyLength);
}

ICertificateInformation * ICertificateInformation::Create()
{
    return new class CertificateInformation();
}

bool OpenSSL::CreateCertificateSigningRequest(class IInstanceLog * log, class ICertificateSigningRequest * iCsr)
{
    class CertificateSigningRequest * csr = (class CertificateSigningRequest *)iCsr;
    if (!csr->GetCommonName() || strlen(csr->GetCommonName()) > 64) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest commonName invalid (must be set, max length 64):%s", csr->GetCommonName());
        return false;
    }
    if (csr->GetDnsNames().empty()) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest you must hand at least one DNS name");
        return false;
    }

    struct Members {    // helper struct to cleanup at the end
        X509_REQ * request = nullptr;
        EVP_PKEY * pKey = nullptr;
        BIO * bioRequest = nullptr;
        BIO * bioKey = nullptr;
        BIGNUM * bne = nullptr;
        STACK_OF(X509_EXTENSION) * exts = nullptr;

        ~Members() {
            X509_REQ_free(request);
            BIO_free_all(bioRequest);
            BIO_free_all(bioKey);
            EVP_PKEY_free(pKey);
            BN_free(bne);
            sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
        }

    } members;

    int ret = 0;
    X509_NAME * name = nullptr;
    int nVersion = 0;
    int bits = 2048;
    unsigned long e = RSA_F4;    

    switch (csr->GetKeyLength()) {
    case certificate_key_length_t::CERTIFICAFTE_KEY_LENGTH_2048: bits = 2048; break;
    case certificate_key_length_t::CERTIFICAFTE_KEY_LENGTH_3072: bits = 3072; break;
    case certificate_key_length_t::CERTIFICAFTE_KEY_LENGTH_4096: bits = 4096; break;
    }
    
    EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);

    if (EVP_PKEY_keygen_init(ctx) <= 0 ||
        EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest EVP_PKEY_keygen_init or EVP_PKEY_CTX_set_rsa_keygen_bits failed:%s", ERR_error_string(ERR_get_error(), NULL));
        EVP_PKEY_CTX_free(ctx);
        return false;
    }

    if (EVP_PKEY_keygen(ctx, &members.pKey) <= 0) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest EVP_PKEY_keygen failed:%s", ERR_error_string(ERR_get_error(), NULL));
        EVP_PKEY_CTX_free(ctx);
        return false;
    }

    EVP_PKEY_CTX_free(ctx);

    members.bne = BN_new();
    ret = BN_set_word(members.bne, e);
    if (ret != 1) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest BN_set_word failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }

    members.request = X509_REQ_new();
    ret = X509_REQ_set_version(members.request, nVersion);
    if (ret != 1) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest X509_REQ_set_version failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }

    name = X509_REQ_get_subject_name(members.request);
    ret = X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)csr->GetCommonName(), -1, -1, 0);
    if (ret != 1) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest X509_NAME_add_entry_by_txt failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }
    
    ret = X509_REQ_set_pubkey(members.request, members.pKey);
    if (ret != 1) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest X509_REQ_set_pubkey failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }

    members.exts = sk_X509_EXTENSION_new_null();
    if (!OpenSSL::AddX509Extension(members.exts, NID_basic_constraints, "CA:FALSE")) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest AddX509Extension NID_basic_constraints failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }
    if (!OpenSSL::AddX509Extension(members.exts, NID_key_usage, "digitalSignature, keyEncipherment")) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest AddX509Extension NID_key_usage failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }
    if (!OpenSSL::AddX509Extension(members.exts, NID_ext_key_usage, "serverAuth, clientAuth")) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest AddX509Extension NID_ext_key_usage failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }
    std::string dnsNames = std::string("");
    for (auto dnsName : csr->GetDnsNames()) {
        if (dnsNames.length() > 0) dnsNames.append(",");
        dnsNames.append(dnsName);
    }
    if (!OpenSSL::AddX509Extension(members.exts, NID_subject_alt_name, dnsNames.data())) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest AddX509Extension NID_key_usage failed:%s dnsNames:%s", ERR_error_string(ERR_get_error(), nullptr), dnsNames.data());
        return false;
    }
    
    X509_REQ_add_extensions(members.request, members.exts);

    ret = X509_REQ_sign(members.request, members.pKey, EVP_sha256());
    if (ret <= 0) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest X509_REQ_sign failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }

    members.bioRequest = BIO_new(BIO_s_mem());
    members.bioKey = BIO_new(BIO_s_mem());
    
    ret = PEM_write_bio_X509_REQ(members.bioRequest, members.request);
    if (ret <= 0) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest PEM_write_bio_X509_REQ failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }    
    
    ret = PEM_write_bio_PrivateKey(members.bioKey, members.pKey, nullptr, nullptr, 0, 0, nullptr);
    if (ret <= 0) {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest PEM_write_bio_RSAPrivateKey failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }

    BUF_MEM * bioRequestBuf = nullptr;
    BUF_MEM * bioKeyBuf = nullptr;
    BIO_get_mem_ptr(members.bioKey, &bioKeyBuf);
    BIO_get_mem_ptr(members.bioRequest, &bioRequestBuf);
    if (bioRequestBuf && bioRequestBuf->length > 0 && bioKeyBuf && bioKeyBuf->length > 0) {
        csr->SetPrivateKeyPEM(bioKeyBuf->data, bioKeyBuf->length);
        csr->SetRequestPEM(bioRequestBuf->data, bioRequestBuf->length);
        return true;
    }
    else {
        log->Log(LOG_TLS, "OpenSSL::CreateCertificateSigningRequest BIO_get_mem_ptr failed:%s", ERR_error_string(ERR_get_error(), nullptr));
        return false;
    }
}

byte * OpenSSL::ConvertPEMToDER(class IInstanceLog * log, const char * pem, size_t & outLength)
{
    // just supporting simple PEMs with a single certificate and without certificate data description, just with ----- lines and base64 encoding
    byte * out = nullptr;
    outLength = 0;
    if (pem && *pem) {
        const char * tmp = pem;
        char * base64 = (char *)malloc(strlen(pem) + 1);
        size_t base64Len = 0;
        while (*tmp) {
            const char * lineBreak = strstr(tmp, "\r\n");   // try windows linebreak first
            size_t lineBreakLength = 0;
            if (lineBreak) {
                lineBreakLength = 2;
            }
            else {
                lineBreak = strchr(tmp, '\n');              // then unix linebreak
                if (lineBreak) {
                    lineBreakLength = 1;
                }
            }
            size_t copyLen = 0;
            if (lineBreak) {
                if (*tmp == '-') {  // ignore complete line, as this is something like -----BEGIN CERTIFICATE REQUEST-----
                    tmp = lineBreak + lineBreakLength;
                    continue;
                }
                else {
                    copyLen = lineBreak - tmp;
                }
            }
            else {
                copyLen = strlen(tmp);
            }
            if (copyLen > 0) {
                memcpy(&base64[base64Len], tmp, copyLen);
                base64Len += copyLen;
            }
            if (!lineBreak) break; 
            else {
                tmp = lineBreak + lineBreakLength;
            }
        }
        base64[base64Len] = '\0';
        size_t length = (base64Len / 4) * 3 + 1;
        out = (byte *)malloc(length);
        outLength = decode_base64(base64, out, length);
        free(base64);
    }
    return out;
}

char * OpenSSL::ConvertDERToPEM(class IInstanceLog * log, const byte * der, size_t derLength)
{
    size_t length = ((4 * derLength) / 3) + 4;
    char * base64 = (char *)malloc(length);
    char * tmp = base64;
    length = encode_base64(der, base64, derLength);
    std::string out;
    out.append("-----BEGIN CERTIFICATE-----\r\n");
    while (length >= 64) {
        out.append(tmp, 64);
        out.append("\r\n");
        tmp += 64;
        length -= 64;
    }
    if (length > 0) {
        out.append(tmp);
        out.append("\r\n");
    }
    out.append("-----END CERTIFICATE-----\r\n");
    free(base64);
    return _strdup(out.c_str());
}

byte * OpenSSL::CertificateFingerprint(class IInstanceLog * log, const byte * cert, size_t certLen, size_t * length, fingerprint_t type)
{
    BIO * bio;
    X509 * x509;

    if (cert && certLen) {
        if ((bio = BIO_new_mem_buf((void *)cert, certLen)) == NULL) {
            log->Log(LOG_TLS, "OpenSSL::CertificateFingerprint BIO_new_mem_buf failed: %s", ERR_error_string(ERR_get_error(), NULL));
            return NULL;
        }
        if ((x509 = PEM_read_bio_X509(bio, NULL, 0, NULL)) == NULL) {
            log->Log(LOG_TLS, "OpenSSL::CertificateFingerprint reading certificate failed: %s", ERR_error_string(ERR_get_error(), NULL));
            BIO_free_all(bio);
            return NULL;
        }
    }
    else {
        bio = BIO_new(BIO_s_file());
        int ret = BIO_read_filename(bio, TLS_DEFAULT_CERTIFICATE_PATH);
        if (!(x509 = PEM_read_bio_X509(bio, NULL, 0, NULL))) {
            log->Log(LOG_TLS, "OpenSSL::CertificateFingerprint Error loading cert into memory %d", ret);
            BIO_free_all(bio);
            return NULL;
        }
    }

    unsigned int fprintSize = EVP_MAX_MD_SIZE;
    byte * fprint = (byte *)malloc(EVP_MAX_MD_SIZE);
    const EVP_MD * fprintType;
    switch (type) {
    case FINGERPRINT_MD5: fprintType = EVP_md5(); break;
    case FINGERPRINT_SHA1: fprintType = EVP_sha1(); break;
    case FINGERPRINT_SHA224: fprintType = EVP_sha224(); break;
    case FINGERPRINT_SHA256: fprintType = EVP_sha256(); break;
    case FINGERPRINT_SHA384: fprintType = EVP_sha384(); break;
    case FINGERPRINT_SHA512: fprintType = EVP_sha512(); break;
    default: free(fprint); X509_free(x509); BIO_free_all(bio); debug->printf("OpenSSL::CertificateFingerprintError unsupported method"); return NULL;
    }

    memset(fprint, 0x00, EVP_MAX_MD_SIZE);
    if (!X509_digest(x509, fprintType, fprint, &fprintSize)) {
        log->Log(LOG_TLS, "OpenSSL::CertificateFingerprintError creating the certificate fingerprint.");
        X509_free(x509);
        BIO_free_all(bio);
        free(fprint);
        return NULL;
    }

    *length = (size_t)fprintSize;
    X509_free(x509);
    BIO_free_all(bio);
    return fprint;
}

long64 OpenSSL::ConvertASN1_TimeToLong64(ASN1_TIME * time)
{
    struct tm tm;
    ASN1_TIME_to_tm(time, &tm);
    time_tm_t tmT;
    tmT.tmHour = tm.tm_hour;
    tmT.tmMin = tm.tm_min;
    tmT.tmSec = tm.tm_sec;
    tmT.tmYear = tm.tm_year;
    tmT.tmMon = tm.tm_mon;
    tmT.tmMDay = tm.tm_mday;
    tmT.tmIsDst = tm.tm_isdst;
    tmT.tmWDay = tm.tm_wday;
    tmT.tmYDay = tm.tm_yday;
    return ITime::TimeStructToMilliseconds(&tmT);
}

certificate_error_t OpenSSL::ValidateCertificate(class IInstanceLog * log, const byte * cert, size_t certLen, class ICertificateInformation * info, bool privateKeyRequired, bool verifyChain)
{
    BIO * bio = NULL;
    X509 * x509 = NULL;
    EVP_PKEY *publicKey = NULL;
    EVP_PKEY *privateKey = NULL;
    
    certificate_error_t ret = ParseCertificate(log, cert, certLen, bio, x509, publicKey, privateKey, privateKeyRequired);
    class CertificateInformation * infoCert = (class CertificateInformation *)info;
    if (ret == CERTIFICATE_OK) {
        if (verifyChain && !VerifyCert(x509, bio, infoCert)) {
            log->Log(LOG_TLS, "OpenSSL::ValidateCertificate couldn't verify certificate chain error:%s", ERR_error_string(ERR_get_error(), NULL));
            ret = CERTIFICATE_CHAIN_INVALID;
        }
        else {
            SSL_CTX * context = SSL_CTX_new(SSLv23_server_method());
            SSL_CTX_set_security_level(context, 1);     // enforce a lower security level, as our old 1024bit certificate doesn't work anymore otherwise, as OpenSSL 3.2 enforce 2048bit per default
            if (!((SSL_CTX_use_certificate(context, x509) == 1) &&
                (privateKey && SSL_CTX_use_PrivateKey(context, privateKey) == 1) &&
                (SSL_CTX_check_private_key(context) == 1)) && privateKeyRequired) {
                log->Log(LOG_TLS, "OpenSSL::ValidateCertificate couldn't load cert/key error:%s", ERR_error_string(ERR_get_error(), NULL));
                ret = CERTIFICATE_LOAD_FAILED;
            }
            else if (infoCert != nullptr) {
                int keyLength = 0;
                if (privateKey) {
                    keyLength = EVP_PKEY_bits(privateKey);
                    infoCert->SetKeyLength(keyLength);
                }
                else if (publicKey) {
                    keyLength = EVP_PKEY_bits(publicKey);
                    infoCert->SetKeyLength(keyLength);
                }

                unsigned char md[EVP_MAX_MD_SIZE];
                unsigned cb;
                X509_digest(x509, EVP_sha256(), md, &cb);
                char * fingerprint = (char *)malloc(cb * 2 + 1);
                str::from_hexmem(md, cb, fingerprint);
                infoCert->SetFingerprintSHA256(fingerprint);
                
                int signature = X509_get_signature_nid(x509);
                const char * signatureAlgorithm = OBJ_nid2ln(signature);
                infoCert->SetSignatureAlgorithm(signatureAlgorithm);

                X509_NAME * name = X509_get_subject_name(x509);
                infoCert->SetCommonName(X509_NAME_oneline(name, nullptr, 0));
                name = X509_get_issuer_name(x509);
                infoCert->SetIssuer(X509_NAME_oneline(name, nullptr, 0));
                ASN1_TIME * time = X509_get_notAfter(x509);
                if (time) {
                    infoCert->SetNotAfter(this->ConvertASN1_TimeToLong64(time));
                }
                time = X509_get_notBefore(x509);
                if (time) {
                    infoCert->SetNotBefore(this->ConvertASN1_TimeToLong64(time));
                }
                // Extract Subject Alternative Names (SAN)
                STACK_OF(GENERAL_NAME) * sanNames = (STACK_OF(GENERAL_NAME) *)X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL);

                if (sanNames)
                {
                    int sanCount = sk_GENERAL_NAME_num(sanNames);
                    for (int i = 0; i < sanCount; ++i)
                    {
                        GENERAL_NAME * nameEntry = sk_GENERAL_NAME_value(sanNames, i);

                        if (nameEntry->type == GEN_DNS)
                        {
                            const char * dnsName = (const char *)ASN1_STRING_get0_data(nameEntry->d.dNSName);
                            infoCert->AddDnsName(dnsName);
                        }
                        else if (nameEntry->type == GEN_IPADD)
                        {
                            const unsigned char * ipAddress = nameEntry->d.iPAddress->data;
                            int ipLength = nameEntry->d.iPAddress->length;

                            // Convert binary IP address to string
                            char ipStr[INET6_ADDRSTRLEN];
                            if (inet_ntop((ipLength == 4) ? AF_INET : AF_INET6, ipAddress, ipStr, sizeof(ipStr)) != NULL)
                            {
                                infoCert->AddIpAddress(ipStr);
                            }
                        }
                    }
                    sk_GENERAL_NAME_pop_free(sanNames, GENERAL_NAME_free);
                }
            }
            SSL_CTX_free(context);
        }
    }
    if (publicKey) EVP_PKEY_free(publicKey);
    if (privateKey) EVP_PKEY_free(privateKey);
    if (x509) X509_free(x509);
    if (bio) BIO_free_all(bio);
    return ret;
}


/*-------------------------------------------------------------------------------------------------------------------------*/
/*                                              class CertificateSigningRequest                                            */
/*-------------------------------------------------------------------------------------------------------------------------*/

CertificateSigningRequest::CertificateSigningRequest(const char * commonName, certificate_key_length_t keyLength)
{
    this->commonName = _strdup(commonName);
    this->keyLength = keyLength;
    this->privateKey = nullptr;
    this->request = nullptr;
}

CertificateSigningRequest::~CertificateSigningRequest()
{
    free(this->commonName);
    free(this->privateKey);
    free(this->request);
}

void CertificateSigningRequest::SetPrivateKeyPEM(const char * privateKey, size_t privateKeyLength)
{
    free(this->privateKey);
    this->privateKey = (char *)malloc(privateKeyLength + 1);
    memcpy(this->privateKey, privateKey, privateKeyLength);
    this->privateKey[privateKeyLength] = '\0';
}

void CertificateSigningRequest::SetRequestPEM(const char * request, size_t requestLength) 
{
    free(this->request);
    this->request = (char *)malloc(requestLength + 1);
    memcpy(this->request, request, requestLength);
    this->request[requestLength] = '\0';
}

void CertificateSigningRequest::AddDnsName(const char * dnsName)
{
    std::string dns = std::string(dnsName);
    dns.insert(0, "DNS:");
    this->dnsNames.push_back(dns);
}


/*-------------------------------------------------------------------------------------------------------------------------*/
/*                                              class CertificateSigningRequest                                            */
/*-------------------------------------------------------------------------------------------------------------------------*/

CertificateInformation::CertificateInformation()
{
    this->issuer = nullptr;
    this->commonName = nullptr;
    this->fingerprintSHA256 = nullptr;
    this->signatureAlgorithm = nullptr;
    this->keyLength = 0;
    this->notAfter = 0;
    this->notBefore = 0;
}

CertificateInformation::~CertificateInformation()
{
    free(this->issuer);
    free(this->commonName);
    free(this->fingerprintSHA256);
    free(this->signatureAlgorithm);
    for (auto pem : this->chainCertificates) {
        free(pem);
    }
    for (auto dnsName : this->dnsNames) {
        free(dnsName);
    }
    for (auto ipAddress : this->ipAddresses) {
        free(ipAddress);
    }
    this->chainCertificates.clear();
    this->dnsNames.clear();
    this->ipAddresses.clear();
}

void CertificateInformation::SetCommonName(char * commonName)
{
    free(this->commonName);
    this->commonName = commonName;
}

void CertificateInformation::SetIssuer(char * issuer)
{
    free(this->issuer);
    this->issuer = issuer;
}

void CertificateInformation::SetFingerprintSHA256(char * fingerprintSHA256)
{
    free(this->fingerprintSHA256);
    this->fingerprintSHA256 = fingerprintSHA256;
}

void CertificateInformation::SetSignatureAlgorithm(const char * signatureAlgorithm)
{
    free(this->signatureAlgorithm);
    this->signatureAlgorithm = _strdup(signatureAlgorithm);
}

void CertificateInformation::SetKeyLength(int keyLength)
{
    this->keyLength = keyLength;
}

void CertificateInformation::SetNotBefore(long64 notBefore)
{
    this->notBefore = notBefore;
}

void CertificateInformation::SetNotAfter(long64 notAfter)
{
    this->notAfter = notAfter;
}

void CertificateInformation::AddChainCertificate(char * pem)
{
    this->chainCertificates.push_back(pem);
}

void CertificateInformation::AddDnsName(const char * dnsName)
{
    this->dnsNames.push_back(_strdup(dnsName));
}

void CertificateInformation::AddIpAddress(const char * ipAddress)
{
    this->ipAddresses.push_back(_strdup(ipAddress));
}

/*-------------------------------------------------------------------------------------------------------------------------*/
/*                                                    class OpenSSLInstance                                                */
/*-------------------------------------------------------------------------------------------------------------------------*/

OpenSSLInstance::OpenSSLInstance(IInstanceLog * const log, SSL * ssl, bool server, const char * remoteAddress, class SSLSocketContext * sslSocketContext)
    : log(log),
    ssl(ssl),
    bioToRead(NULL),
    bioToWrite(NULL),
    sslContext(NULL),
    handshakeDone(false),
    isServer(server),
    remoteAddress(_strdup(remoteAddress)),
    sslSocketContext(sslSocketContext)
{
}

OpenSSLInstance::~OpenSSLInstance()
{
    if (!isServer) {
        SSL_SESSION * session = SSL_get1_session(ssl);
        if (session) {
            new SSLCachedClientSession(remoteAddress, sslSocketContext, session);
        }
    }
    if (remoteAddress) free(remoteAddress);
    // SSL free will also release the BIOs...
    if (ssl) SSL_free(ssl);
}


void OpenSSLInstance::GetSelectedALPNProtocol(const byte * & protocolList, size_t & protocolListLength)
{
    SSL_get0_alpn_selected(this->ssl, &protocolList, (unsigned int *) &protocolListLength);
}


int OpenSSLInstance::HandleSSLCallResult(int res)
{
    int code, err;

    code = SSL_get_error(ssl, res);
    switch (code)
    {
    case SSL_ERROR_NONE:
        handshakeDone = true;
        return OP_SSL_DONE;

    case SSL_ERROR_WANT_WRITE:
        return OP_SSL_NEED_WRITE;

    case SSL_ERROR_WANT_READ:
        return OP_SSL_NEED_READ;

    default:
        err = ERR_get_error();
        if (err == 0) {
            log->Log(LOG_TLS, "OpenSSLInstance::HandleSSLCallResult OpenSSL close %d %d", res, code);
            return OP_SSL_CLOSE;
        }

        while (err != 0) {
            if(log->LogFlagSet(LOG_TLS) || log->LogFlagSet(LOG_DTLS)) debug->printf("OpenSSLInstance::HandleSSLCallResult OpenSSL error %d %d %d:%s", res, code, err, ERR_error_string(err, NULL));
            err = ERR_get_error();
        }
        return OP_SSL_ERROR;
    }
}

int OpenSSLInstance::DoHandshake()
{
    if (handshakeDone) return true;

    if (SSL_is_init_finished(ssl)) {
        handshakeDone = true;
        return OP_SSL_DONE;
    }
    return this->HandleSSLCallResult((isServer ? SSL_accept(ssl) : SSL_connect(ssl)));
}

int OpenSSLInstance::DoShutdown()
{
    int res = SSL_shutdown(ssl);
    return (res >= 0 ? res : this->HandleSSLCallResult(res));
}

void OpenSSLInstance::SetShutdown()
{
    SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
}

int OpenSSLInstance::ReadDecoded(const void * buf, size_t bufSize)
{
    int res = SSL_read(ssl, const_cast<void *>(buf), bufSize);
    return (res > 0 ? res : this->HandleSSLCallResult(res));
}

int OpenSSLInstance::ReadEncoded(const void * buf, size_t bufSize)
{
    int res = BIO_read(bioToWrite, const_cast<void *>(buf), bufSize);
    return (res > 0 ? res : this->HandleSSLCallResult(res));
}

int OpenSSLInstance::WriteDecoded(const void * buf, size_t len)
{
    int res = SSL_write(ssl, buf, len);
    return (res > 0 ? res : this->HandleSSLCallResult(res));
}

int OpenSSLInstance::WriteEncoded(const void * buf, size_t len)
{
    int res = BIO_write(bioToRead, const_cast<void *>(buf), len);
    return (res > 0 ? res : this->HandleSSLCallResult(res));
}

bool OpenSSLInstance::DataPending()
{
    return (SSL_pending(ssl) > 0 || BIO_pending(bioToRead) > 0);
}

size_t OpenSSLInstance::BytesToSend()
{
    return BIO_pending(bioToWrite);
}

void OpenSSLInstance::SetSSLContext(USSLSocketContext * ctx)
{
    SSL_set_ex_data(ssl, 0, (void *)ctx);
}

void OpenSSLInstance::SetMtu(size_t mtu)
{
    // Disable automatic MTU discovery.
    if(sslContext) SSL_CTX_set_options(sslContext, SSL_OP_NO_QUERY_MTU);
    // Set MTU of datagrams so it fits in an UDP packet.
    SSL_set_mtu(ssl, mtu);
    BIO_ctrl(bioToWrite, BIO_CTRL_DGRAM_SET_MTU, mtu, NULL);
}

#ifdef _MSC_VER
#pragma warning (pop)
#endif
