jens-maus/yam

View on GitHub
src/tcp/ssl.c

Summary

Maintainability
Test Coverage
/***************************************************************************

 YAM - Yet Another Mailer
 Copyright (C) 1995-2000 Marcel Beck
 Copyright (C) 2000-2022 YAM Open Source Team

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 YAM Official Support Site :  http://www.yam.ch
 YAM OpenSource project    :  http://sourceforge.net/projects/yamos/

 $Id$

***************************************************************************/

#include <string.h>
#include <ctype.h>

#include <clib/alib_protos.h>

#include <proto/amissl.h>
#include <proto/amisslmaster.h>

// we include bsdsocket.h but make sure to not let
// it define a global SocketBase or ISocket so that
// our socket stuff is assured to not use a global socket
// library base
#define __NOLIBBASE__
#define __NOGLOBALIFACE__
#include <proto/bsdsocket.h>
#undef __NOGLOBALIFACE__
#undef __NOLIBBASE__

#if defined(__amigaos3__)
#include <inline/extended_macros.h>
#elif defined(__MORPHOS__)
#include <ppcinline/extended_macros.h>
#endif

#include <proto/exec.h>

#include "SDI_hook.h"

#include "YAM.h"
#include "YAM_error.h"
#include "YAM_utilities.h"

#include "tcp/Connection.h"
#include "tcp/ssl.h"

#include "Config.h"
#include "DynamicString.h"
#include "FileInfo.h"
#include "Locale.h"
#include "MailServers.h"
#include "Requesters.h"

#include "Debug.h"

// SocketBase must not be shared between tasks, hence we must not use the global
// SocketBase, but the one tied to the current connection.
#if defined(__amigaos4__)
#define GET_SOCKETBASE(conn)  struct SocketIFace *ISocket = (conn)->socketIFace
#else
#define GET_SOCKETBASE(conn)  struct Library *SocketBase = (conn)->socketBase
#endif

#define DEFAULT_CAPATH "PROGDIR:Resources/certificates"
#define DEFAULT_CAFILE "PROGDIR:Resources/certificates/ca-bundle.crt"

/// verify_callback
// callback function that is called by AmiSSL/OpenSSL for every certification
// verification step
CROSSCALL2(verify_callback, int, int, preverify_ok, X509_STORE_CTX *, x509_ctx)
{
  ENTER();

  D(DBF_NET, "verify_callback() called");
  SHOWVALUE(DBF_NET, preverify_ok);

  // if there is no error we do nothing but return
  if(preverify_ok == 0)
  {
    int sslIndex = SSL_get_ex_data_X509_STORE_CTX_idx();
    SSL *ssl = NULL;

    SHOWVALUE(DBF_NET, x509_ctx);

    // get SSL connection pointer
    if(sslIndex >= 0 && (ssl = X509_STORE_CTX_get_ex_data(x509_ctx, sslIndex)) != NULL)
    {
      struct Connection *conn;

      // get our (struct Connection) structure out of the app data
      if((conn = SSL_get_ex_data(ssl, G->sslDataIndex)) != NULL)
      {
        // get some information on the potential error
        int err = X509_STORE_CTX_get_error(x509_ctx);
        int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
        int failures = 0;

        W(DBF_NET, "ssl: verify callback @ %ld => %ld:'%s'", depth, err, X509_verify_cert_error_string(err));

        // map the specific X509 error codes to general failures
        // we cna query later on when checking the certificate chain later again
        switch(err)
        {
          // (0) the operation was successful.
          case X509_V_OK:
            // nothing
          break;

          // (2) unable to get issuer certificate
          // (18) self signed certificate
          // (19) self signed certificate in certificate chain
          // (20) unable to get local issuer certificate
          // (21) unable to verify the first certificate
          // (23) the certificate has been revoked.
          // (27) certificate not trusted
          // (28) the root CA is marked to reject the specified purpose.
          case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
          case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
          case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
          case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
          case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
          case X509_V_ERR_CERT_REVOKED:
          case X509_V_ERR_CERT_UNTRUSTED:
          case X509_V_ERR_CERT_REJECTED:
          {
            W(DBF_NET, "ssl: verify failure SSL_CERT_ERR_UNTRUSTED found");
            setFlag(failures, SSL_CERT_ERR_UNTRUSTED);
          }
          break;

          // (3) the CRL of a certificate could not be found.
          // (4) the certificate signature could not be decrypted.
          // (5) the CRL signature could not be decrypted.
          // (6) the public key in the certificate SubjectPublicKeyInfo could not be read.
          // (11) the CRL is not yet valid.
          // (12) the CRL has expired.
          // (15) the CRL lastUpdate field contains an invalid time.
          // (16) the CRL nextUpdate field contains an invalid time.
          // (22) the certificate chain length is greater than the supplied maximum depth. Unused.
          // (24) a CA certificate is invalid. Either it is not a CA or its extensions are not consistent with the supplied purpose.
          // (25) the basicConstraints pathlength parameter has been exceeded.
          // (26) the supplied certificate cannot be used for the specified purpose.
          // (29) subject issuer mismatch: the current candidate issuer certificate was rejected.
          // (30) authority and subject key identifier mismatch: the current candidate issuer certificate was rejected.
          // (31) authority and issuer serial number mismatch: the current candidate issuer certificate was rejected.
          // (32) key usage does not include certificate signing:  the current candidate issuer certificate was rejected.
          case X509_V_ERR_UNABLE_TO_GET_CRL:
          case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
          case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
          case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
          case X509_V_ERR_CRL_NOT_YET_VALID:
          case X509_V_ERR_CRL_HAS_EXPIRED:
          case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
          case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
          case X509_V_ERR_CERT_CHAIN_TOO_LONG:
          case X509_V_ERR_INVALID_CA:
          case X509_V_ERR_PATH_LENGTH_EXCEEDED:
          case X509_V_ERR_INVALID_PURPOSE:
          case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
          case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
          case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
          {
            W(DBF_NET, "ssl: other uncritical certificate problem. Signaling SSL_CERT_ERR_OTHER.");
            setFlag(failures, SSL_CERT_ERR_OTHER);
          }
          break;

          // (7) the signature of the certificate is invalid.
          // (8) the signature of the certificate is invalid.
          case X509_V_ERR_CERT_SIGNATURE_FAILURE:
          case X509_V_ERR_CRL_SIGNATURE_FAILURE:
          {
            W(DBF_NET, "ssl: cert signature failed, signaling SSL_CERT_ERR_SIGINVALID");
            setFlag(failures, SSL_CERT_ERR_SIGINVALID);
          }
          break;

          // (9) the certificate is not yet valid: the notBefore date is after the current time.
          // (13) the certificate notBefore field contains an invalid time.
          case X509_V_ERR_CERT_NOT_YET_VALID:
          case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
          {
            W(DBF_NET, "ssl: verify failure %s found", depth > 0 ? "SSL_CERT_ERR_BADCHAIN" : "SSL_CERT_ERR_NOTYETVALID");
            setFlag(failures, depth > 0 ? SSL_CERT_ERR_BADCHAIN : SSL_CERT_ERR_NOTYETVALID);
          }
          break;

          // (10) the certificate has expired: that is the notAfter date is before the current time.
          // (14) the certificate notAfter field contains an invalid time.
          case X509_V_ERR_CERT_HAS_EXPIRED:
          case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
          {
            W(DBF_NET, "ssl: verify failure %s found", depth > 0 ? "SSL_CERT_ERR_BADCHAIN" : "SSL_CERT_ERR_EXPIRED");
            setFlag(failures, depth > 0 ? SSL_CERT_ERR_BADCHAIN : SSL_CERT_ERR_EXPIRED);
          }
          break;

          // (17) X509_V_ERR_OUT_OF_MEM: an error occurred trying to allocate memory. This should never happen.
          // (50) X509_V_ERR_APPLICATION_VERIFICATION: an application specific error. Unused.
          default:
          {
            // clear the failures bitmask so check_certificates() knows this
            // is a bailout
            setFlag(conn->sslCertFailures, SSL_CERT_ERR_UNHANDLED);
            W(DBF_NET, "ssl: Unhandled certification verification error: SSL_CERT_ERR_UNHANDLED");

            // make sure we return an error and stop the
            // cert verification right away
            RETURN(0);
            return 0;
          }
        }

        // set the connection-wise sslCertFailure bitmask
        setFlag(conn->sslCertFailures, failures);
        D(DBF_NET, "ssl: verify failures |= %ld => %ld", failures, conn->sslCertFailures);

        // make sure we return 1 (success) so that
        // the verification process continues and collects more failures
        preverify_ok = 1;
      }
      else
        E(DBF_NET, "SSL_get_ex_data() returned invalid 'conn'");
    }
    else
      E(DBF_NET, "X509_STORE_CTX_get_ex_data() returned invalid SSL structure");
  }

  RETURN(preverify_ok);
  return preverify_ok;
}

///
/// MatchHostname
// This doesn't actually implement complete RFC 2818 logic; omits
// "f*.example.com" support for simplicity.
static int MatchHostname(const char *cn, size_t cnlen, const char *hostname)
{
  const char *dot;
  int result;

  ENTER();

  D(DBF_NET, "ssl: Match common name '%s' against '%s'", cn, hostname);

  // check for the commonName starting with a wildcard (*.) so that we
  // start matching against it starting after the wildcard
  if(strncmp(cn, "*.", 2) == 0 && cnlen > 2 &&
     (dot = strchr(hostname, '.')) != NULL)
  {
    int i;
    BOOL ipFound = TRUE;

    // Prevent wildcard CN matches against anything which can be
    // parsed as an IP address (i.e. a CN of "*.1.1.1" should not
    // be match 8.1.1.1).  draft-saintandre-tls-server-id-check
    // will require some more significant changes to cert ID
    // verification which will probably obviate this check, but
    // this is a desirable policy tightening in the mean time.

    // check that hostname is not only a pure IP address by scanning it
    // and searching for ?." and numbers only
    for(i = 0; hostname[i] != '\0'; i++)
    {
      // check for IPv4 and IPv6 addresses
      if(isdigit(hostname[i]) == 0 && hostname[i] != '.' && hostname[i] != ':')
      {
        ipFound = FALSE;
        break;
      }
    }

    if(ipFound == TRUE)
    {
      RETURN(0);
      return 0;
    }

    hostname = dot + 1;
    cn += 2;
    cnlen -= 2;
  }

  // now make the case insensitive string compare
  result = cnlen == strlen(hostname) && strcasecmp(cn, hostname) == 0;

  RETURN(result);
  return result;
}

///
/// GENERAL_NAME_free_wrapper
// Define a wrapper function for GENERAL_NAME_free.
// This is a function in AmiSSL.library and hence cannot
// be used as a function pointer directly.
CROSSCALL1NR(GENERAL_NAME_free_wrapper, void *, names)
{
  ENTER();

  GENERAL_NAME_free(names);

  LEAVE();
}

///
/// CheckCertificateIdentity
// Check certificate identity.  Returns zero if identity matches; 1 if
// identity does not match, or <0 if the certificate had no identity.
// If 'identity' is non-NULL, store the malloc-allocated identity in
// *identity.  Logic specified by RFC 2818 and RFC 3280.
static int CheckCertificateIdentity(const char *hostname, X509 *cert, char **identity)
{
  STACK_OF(GENERAL_NAME) *names;
  int match = 0;
  int found = 0;

  ENTER();

  if(hostname == NULL)
    hostname = "";

  // check for subjectAltName entries in the certificate and
  // prefer them for checking the identity.
  names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
  if(names != NULL)
  {
    int n;

    // subjectAltName contains a sequence of GeneralNames
    for(n = 0; n < sk_GENERAL_NAME_num(names) && match == 0; n++)
    {
      GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, n);

      switch(nm->type)
      {
        // DNS:
        case GEN_DNS:
        {
          char *name = (char *)ASN1_STRING_get0_data(nm->d.ia5);
          D(DBF_NET, "GEN_DNS: '%s'", name);

          if(identity != NULL && found == 0)
            *identity = strdup(name);

          match = MatchHostname(name, strlen(name), hostname);
          found = 1;
        }
        break;

        // "IP Address:"
        case GEN_IPADD:
        {
          char ipaddr[60];

          if(nm->d.iPAddress->length == 4) // IPv4 address
          {
            snprintf(ipaddr, sizeof(ipaddr), "%d.%d.%d.%d", nm->d.iPAddress->data[0],
                     nm->d.iPAddress->data[1],
                     nm->d.iPAddress->data[2],
                     nm->d.iPAddress->data[3]);
          }
          else if((nm->d.iPAddress->length == 16) || // IPv6 address
                  (nm->d.iPAddress->length == 20))
          {
            char *pos = ipaddr;
            int j;
            #define VAL2HEX(s)  ((s) + (((s) >= 10) ? ('a'-10) : '0'))

            for(j = 0; j < nm->d.iPAddress->length; ++j)
            {
              *pos++ = VAL2HEX(nm->d.iPAddress->data[j]);
              *pos++ = ':';
            }
            *pos = '\0';
          }
          else
            E(DBF_NET, "unexpected ip addr length %ld", nm->d.iPAddress->length);

          D(DBF_NET, "GEN_IPADD: '%s'", ipaddr);

          // compare IP address with server IP address
          match = MatchHostname(ipaddr, strlen(ipaddr), hostname);
          found = 1;
        }
        break;

        default:
          D(DBF_NET, "unknown/unsupported GEN type %ld", nm->type);
        break;
      }
    }

    // free all allocated names
    sk_GENERAL_NAME_pop_free(names, ENTRY(GENERAL_NAME_free_wrapper));
  }

  D(DBF_NET, "found: %ld", found);

  // Check against the commonName if no DNS alt. names were found,
  // as per RFC3280.
  if(found == 0)
  {
    X509_NAME *subj = X509_get_subject_name(cert);
    int idx = -1;
    int lastidx;
    char *cname = NULL;

    // find the most specific commonName attribute.
    do
    {
      lastidx = idx;
      idx = X509_NAME_get_index_by_NID(subj, NID_commonName, lastidx);
    }
    while(idx >= 0);

    if(lastidx < 0)
    {
      // no commonName at all
      RETURN(-1);
      return -1;
    }

    // extract the string from the entry
    cname = strdup((char *)ASN1_STRING_get0_data(X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subj, lastidx))));
    if(identity != NULL)
      *identity = strdup(cname);

    match = MatchHostname(cname, strlen(cname), hostname);

    free(cname);
  }

  D(DBF_NET, "Identity match for '%s': %s", hostname, match ? "good" : "bad");

  RETURN(match ? 0 : 1);
  return match ? 0 : 1;
}

///
/// GetCertFingerprint
// extracts the fingerprint of a SSL certificate
static int GetCertFingerprint(const struct Certificate *cert, char *digest)
{
  unsigned char sha1[EVP_MAX_MD_SIZE];
  unsigned int len;
  int result;
  char *p;

  ENTER();

  #if SHA_DIGEST_LENGTH != 20
   #error SHA digest length is not 20 bytes
  #endif

  if(X509_digest(cert->subject, EVP_sha1(), sha1, &len) == 0 || len != SHA_DIGEST_LENGTH)
  {
    ERR_clear_error();
    result = -1;
  }
  else
  {
    int j;

    #define HEX2ASC(x) ((char) ((x) > 9 ? ((x) - 10 + 'A') : ((x) + '0')))
    for(j = 0, p = digest; j < SHA_DIGEST_LENGTH; j++)
    {
      *p++ = HEX2ASC((sha1[j] >> 4) & 0x0f);
      *p++ = HEX2ASC(sha1[j] & 0x0f);
      *p++ = ':';
    }

    p[-1] = '\0'; // NUL terminate
    result = 0;
  }

  RETURN(result);
  return result;
}

///
/// ExtractReadableDN
// extract a readable string of all issuers DNAME information
static char *ExtractReadableDN(const X509_NAME *dname)
{
  char *result = NULL;
  int n;
  int flag = 0;
  const ASN1_OBJECT * const cname = OBJ_nid2obj(NID_commonName);
  const ASN1_OBJECT * const email = OBJ_nid2obj(NID_pkcs9_emailAddress);

  ENTER();

  for(n = X509_NAME_entry_count(dname); n > 0; n--)
  {
    X509_NAME_ENTRY *ent = X509_NAME_get_entry(dname, n-1);
    ASN1_OBJECT *obj = X509_NAME_ENTRY_get_object(ent);

    // Skip commonName or emailAddress except if there is no other
    // attribute in dname.
    if((OBJ_cmp(obj, cname) && OBJ_cmp(obj, email)) ||
       (!flag && n == 1))
    {
      if(flag++)
        dstrcat(&result, ", ");

      dstrcat(&result, (char *)ASN1_STRING_get0_data(X509_NAME_ENTRY_get_data(ent)));
    }
  }

  RETURN(result);
  return result;
}

///
/// ASN1Time2TimeVal
// convert an ASN1_TIME to a TimeVal
static BOOL ASN1Time2TimeVal(const ASN1_TIME *atm, struct TimeVal *tv)
{
  BOOL result = FALSE;

  ENTER();

  if(atm->length >= 12)
  {
    char datestring[] = "MM-DD-YY HH:MM:SS";

    // month
    datestring[0]  = atm->data[2];
    datestring[1]  = atm->data[3];
    // day
    datestring[3]  = atm->data[4];
    datestring[4]  = atm->data[5];
    // year
    datestring[6]  = atm->data[0];
    datestring[7]  = atm->data[1];
    // hour
    datestring[9]  = atm->data[6];
    datestring[10] = atm->data[7];
    // minute
    datestring[12] = atm->data[8];
    datestring[13] = atm->data[9];
    // second
    datestring[15] = atm->data[10];
    datestring[16] = atm->data[11];

    // now convert the temporary string to a TimeVal
    result = String2TimeVal(tv, datestring, DSS_USDATETIME, TZC_NONE);
  }

  RETURN(result);
  return result;
}

///
/// MakeCertificateChain
// takes a X509 certificate chain and creates an own one using
// struct Certificate
static struct Certificate *MakeCertificateChain(STACK_OF(X509) *chain)
{
  int n;
  int count = sk_X509_num(chain);
  struct Certificate *top = NULL;
  struct Certificate *current = NULL;

  ENTER();

  D(DBF_NET, "Certificate chain depth: %ld", count);

  for(n = 0; n < count; n++)
  {
    struct Certificate *cert = calloc(1, sizeof(*cert));
    X509 *x5 = X509_dup(sk_X509_value(chain, n));
    struct TimeVal tv;

    // populate cert
    cert->subject_dn = X509_get_subject_name(x5);
    cert->issuer_dn = X509_get_issuer_name(x5);
    cert->issuer = NULL;
    cert->subject = x5;
    // Retrieve the cert identity; pass a dummy hostname to match.
    cert->identity = NULL;
    CheckCertificateIdentity(NULL, x5, &cert->identity);
    GetCertFingerprint(cert, cert->fingerprint);
    cert->issuerStr = ExtractReadableDN(cert->issuer_dn);
    if(ASN1Time2TimeVal(X509_get_notBefore(cert->subject), &tv))
      TimeVal2String(cert->notBefore, sizeof(cert->notBefore), &tv, DSS_DATETIME, TZC_NONE);
    if(ASN1Time2TimeVal(X509_get_notAfter(cert->subject), &tv))
      TimeVal2String(cert->notAfter, sizeof(cert->notAfter), &tv, DSS_DATETIME, TZC_NONE);

    // now link the certificate to the issuer
    if(top == NULL)
      current = top = cert;
    else
    {
      current->issuer = cert;
      current = cert;
    }
  }

  RETURN(top);
  return top;
}

///
/// FreeCertificateChain
// frees a full certificate chain
static void FreeCertificateChain(struct Certificate *top_cert)
{
  struct Certificate *current;
  struct Certificate *next = top_cert;

  ENTER();

  do
  {
    current = next;
    next = current->issuer;

    dstrfree(current->issuerStr);

    if(current->identity != NULL)
      free(current->identity);

    free(current);
  }
  while(next != NULL);

  LEAVE();
}

///
/// CheckCertificate
// Verifies an SSL server certificate
static int CheckCertificate(struct Connection *conn, struct Certificate *cert)
{
  X509 *x509_cert = cert->subject;
  int ret;

  ENTER();

  SHOWVALUE(DBF_NET, conn->sslCertFailures);

  // If the verification callback hit a case which can't be mapped
  // to one of the exported error bits, it's treated as a hard
  // failure rather than invoking the callback, which can't present
  // a useful error to the user.  "Um, something is wrong.  OK?" */
  if(isFlagSet(conn->sslCertFailures, SSL_CERT_ERR_UNHANDLED))
  {
    #if defined(DEBUG)
    long result = SSL_get_verify_result(conn->ssl);

    E(DBF_NET, "certificate verification error: %s", X509_verify_cert_error_string(result));
    #endif

    RETURN(1);
    return 1;
  }

  ret = CheckCertificateIdentity(conn->server->hostname, x509_cert, NULL);
  if(ret < 0)
  {
    E(DBF_NET, "server certificate was missing commonName attribute in subject name");

    RETURN(1);
    return 1;
  }
  else if(ret > 0)
  {
    D(DBF_NET, "ssl: verify failure SSL_CERT_ERR_IDMISMATCH found");
    setFlag(conn->sslCertFailures, SSL_CERT_ERR_IDMISMATCH);
  }

  // check if the certificate chain could be verified or if
  // we need to ask the user how to continue
  if(conn->sslCertFailures == SSL_CERT_ERR_NONE)
    ret = 0;
  else
  {
    #if defined(DEBUG)
    if((conn->sslCertFailures & ~conn->server->certFailures) != 0)
      W(DBF_NET, "different certificate failures %08lx vs %08lx", conn->sslCertFailures, conn->server->certFailures);

    if(stricmp(cert->fingerprint, conn->server->certFingerprint) != 0)
      W(DBF_NET, "mismatching finger prints '%s' vs '%s'", cert->fingerprint, conn->server->certFingerprint);
    #endif

    // now that we have identified cert failures we check
    // if the user has already accepted these failures and
    // the cert or if we have to ask him once again
    if((conn->sslCertFailures & ~conn->server->certFailures) != 0 || // check if any bits were added
       stricmp(cert->fingerprint, conn->server->certFingerprint) != 0)
    {
      // ask user how to proceed and react upon his request
      if(CertWarningRequest(conn, cert) == TRUE)
        ret = 0; // signal NO error and continue the SSL connection
      else
        ret = 1; // signal ERROR that aborts the SSL connection
    }
    else
    {
      W(DBF_NET, "user accepted cert permanently %08lx vs %08lx", conn->sslCertFailures, conn->server->certFailures);

      // signal NO error
      ret = 0;
    }
  }

  RETURN(ret);
  return ret;
}

///
/// MakeSecureConnection
// Initialize an SSL/TLS session
BOOL MakeSecureConnection(struct Connection *conn)
{
  BOOL secure = FALSE;

  ENTER();

  if(conn != NULL && conn->isConnected == TRUE)
  {
    if(AmiSSLBase != NULL)
    {
      long rc;

      // 1) init AmiSSL
      rc = InitAmiSSL(AmiSSL_ErrNoPtr, &errno,
                      #if defined(__amigaos4__)
                      AmiSSL_ISocket, conn->socketIFace,
                      #else
                      AmiSSL_SocketBase, conn->socketBase,
                      #endif
                      TAG_DONE);

      if(rc != 0) // rc=0 signals NO error
      {
        E(DBF_NET, "InitAmiSSL() failed");
        ER_NewError(tr(MSG_ER_INITAMISSL));
      }
      else
      {
        // 2) check if we have enough entropy
        if((rc = RAND_status()) == 0) // rc=0 is error
          E(DBF_NET, "not enough entropy in the SSL pool");
        // 3) check if we are ready for creating the ssl connection
        else if((conn->ssl = SSL_new(G->sslCtx)) == NULL)
          E(DBF_NET, "can't create a new SSL structure for a connection");
        else if(SSL_set_ex_data(conn->ssl, G->sslDataIndex, conn) == 0)
          E(DBF_NET, "couldn't assign connection pointer");
        else
        {
          // output some debug information
          #if defined(DEBUG)
          {
            int i = 0;
            const char *next = NULL;

            // output all availble ciphers
            D(DBF_NET, "available SSL ciphers:");
            do
            {
              if((next = SSL_get_cipher_list(conn->ssl, i)) != NULL)
                D(DBF_NET, "%s", next);

              i++;
            }
            while(next != NULL);
          }
          #endif

          // 4) set the socket descriptor to the ssl context
          D(DBF_NET, "set socket descriptor %ld for context %08lx", conn->socket, conn->ssl);
          if(SSL_set_fd(conn->ssl, (int)conn->socket) != 1)
            E(DBF_NET, "SSL_set_fd() error, socket %ld", conn->socket);
          else
          {
            BOOL errorState = FALSE;
            int res;

            SSL_set_tlsext_host_name(conn->ssl, conn->server->hostname);

            // 5) establish the ssl connection and take care of non-blocking IO
            D(DBF_NET, "connect SSL context %08lx", conn->ssl);
            STARTCLOCK(DBF_NET);
            while(errorState == FALSE && (res = SSL_connect(conn->ssl)) <= 0)
            {
              #if defined(DEBUG)
              int errnosv = errno; // preserve errno directly after SSL_connect()
              SSL_SESSION *sslSession = NULL;
              #endif
              int err;

              #if defined(DEBUG)
              STOPCLOCK(DBF_NET, "SSL_connect()");
              sslSession = SSL_get_session(conn->ssl);
              D(DBF_NET, "SSL session timeout: %ld s", SSL_get_timeout(sslSession));
              D(DBF_NET, "SSL session times: %ld (%ld)", SSL_get_time(sslSession), time(NULL));
              #endif

              // get the reason why SSL_connect() returned an error
              switch((err = SSL_get_error(conn->ssl, res)))
              {
                case SSL_ERROR_WANT_READ:
                case SSL_ERROR_WANT_WRITE:
                {
                  // we are using non-blocking socket IO so an SSL_ERROR_WANT_READ
                  // signals that the SSL socket wants us to wait until data
                  // is available and reissue the SSL_connect() command.
                  LONG retVal;
                  GET_SOCKETBASE(conn);

                  D(DBF_NET, "SSL_get_error returned %s, running WaitSelect() with timeout %ld s", err == SSL_ERROR_WANT_READ ? "SSL_ERROR_WANT_READ" : "SSL_ERROR_WANT_WRITE", C->SocketTimeout);

                  // set SocketTimeout to our timeout variable
                  // so that we can let WaitSelect() timeout correctly.
                  conn->timeout.tv_sec = C->SocketTimeout;
                  conn->timeout.tv_usec = 0;

                  // now we put our socket handle into a descriptor set
                  // we can pass on to WaitSelect()
                  FD_ZERO(&conn->fdset);
                  FD_SET(conn->socket, &conn->fdset);

                  // depending on the SSL error (WANT_READ/WANT_WRITE)
                  // we either do a WaitSelect() on the read or write mode
                  // as with SSL both things can happen
                  // see http://www.openssl.org/docs/ssl/SSL_connect.html
                  STARTCLOCK(DBF_NET);

                  if(err == SSL_ERROR_WANT_READ)
                    retVal = WaitSelect(conn->socket+1, &conn->fdset, NULL, NULL, (APTR)&conn->timeout, NULL);
                  else
                    retVal = WaitSelect(conn->socket+1, NULL, &conn->fdset, NULL, (APTR)&conn->timeout, NULL);

                  STOPCLOCK(DBF_NET, "WaitSelect()");

                  // if WaitSelect() returns 1 we successfully waited for
                  // being able to write to the socket. So we go and do another
                  // iteration in the while() loop as the next SSL_connect() call should
                  // return EISCONN if the connection really succeeded.
                  if(retVal >= 1 && FD_ISSET(conn->socket, &conn->fdset))
                  {
                    // everything fine
                    STARTCLOCK(DBF_NET); // for the next iteration in calling SSL_connect()
                    continue;
                  }
                  else if(retVal == 0)
                  {
                    W(DBF_NET, "WaitSelect() socket timeout reached");
                    errorState = TRUE;
                  }
                  else
                  {
                    // the rest should signal an error
                    E(DBF_NET, "WaitSelect() returned %ld with SSL_get_error() = %d", retVal, err);
                    errorState = TRUE;
                  }
                }
                break;

                default:
                {
                  E(DBF_NET, "SSL_connect() returned %ld with SSL_get_error() = %ld", res, err);

                  // get more information on the error
                  #if defined(DEBUG)
                  {
                    GET_SOCKETBASE(conn);
                    char buf[255];
                    // save the errno(sv) values to avoid that they are modified by further function calls
                    int _errno = errno;
                    int _errnosv = errnosv;
                    LONG _bsderrno = Errno();
                    unsigned long errcode;

                    // query errno first
                    #if defined(__amigaos4__) || defined(__amigaos3__)
                    if(strerror_r(_errno, buf, sizeof(buf)) != 0)
                    {
                      E(DBF_NET, "strerror_r(errno=%ld) failed", _errno);
                      buf[0] = '\0';
                    }
                    E(DBF_NET, "errno(%ld) = '%s'", _errno, buf);
                    if(strerror_r(_errnosv, buf, sizeof(buf)) != 0)
                    {
                      E(DBF_NET, "strerror_r(errnosv=%ld) failed", _errnosv);
                      buf[0] = '\0';
                    }
                    E(DBF_NET, "errnosv(%ld) = '%s'", _errnosv, buf);
                    #else
                    E(DBF_NET, "errno(%ld) = '%s'", _errno, strerror(_errno));
                    E(DBF_NET, "errnosv(%ld) = '%s'", _errnosv, strerror(_errnosv));
                    #endif

                    E(DBF_NET, "bsderrno(%ld)", _bsderrno);
                    E(DBF_NET, "querying ERR_get_error() stack:");
                    while((errcode = ERR_get_error()) != 0)
                    {
                      ERR_error_string_n(errcode, buf, sizeof(buf));
                      E(DBF_NET, "ERR_get_error()=%ld stack: '%s'", errcode, buf);
                    }
                    E(DBF_NET, "done");
                  }
                  #endif

                  errorState = TRUE;
                }
                break;
              }
            }

            if(errorState == FALSE)
            {
              STACK_OF(X509) *chain;

              // 6) now we get the peer certificate chain
              D(DBF_NET, "get peer certificate chain");
              chain = SSL_get_peer_cert_chain(conn->ssl);
              if(chain == NULL || sk_X509_num(chain) == 0)
                E(DBF_NET, "SSL server did not present certificate, chain=%08lx", chain);
              else
              {
                struct Certificate *cert;

                // 7) make a local copy of the certificate chain so that
                //     we can bug the user with information on accepting/rejecting the certificate
                cert = MakeCertificateChain(chain);

                // 8) now check the certificate chain for any errors and ask the user
                //     how to proceed in case there were an certificate error found
                if(CheckCertificate(conn, cert) != 0)
                  E(DBF_NET, "SSL certificate checks failed");
                else
                {
                  // everything was successfully so lets set the result
                  // value of that function to true
                  secure = TRUE;

                  // Debug information on the certificate
                  #if defined(DEBUG)
                  {
                    char *x509buf;
                    const SSL_CIPHER *cipher;
                    X509 *server_cert;
                    char peer_CN[256] = "";

                    cipher = SSL_get_current_cipher(conn->ssl);
                    if(cipher != NULL)
                      D(DBF_NET, "%s connection using %s", SSL_CIPHER_get_version(cipher), SSL_get_cipher(conn->ssl));

                    D(DBF_NET, "Certificate verify result: %ld", SSL_get_verify_result(conn->ssl));

                    if((server_cert = SSL_get_peer_certificate(conn->ssl)) == NULL)
                      E(DBF_NET, "SSL_get_peer_certificate() error!");

                    D(DBF_NET, "Server public key is %ld bits", EVP_PKEY_bits(X509_get_pubkey(server_cert)));

                    X509_NAME_get_text_by_NID(X509_get_subject_name(server_cert), NID_commonName, peer_CN, sizeof(peer_CN));
                    D(DBF_NET, "peer_commonName: '%s'", peer_CN);

                    #define X509BUFSIZE 4096
                    if((x509buf = calloc(1, X509BUFSIZE)) != NULL)
                    {
                      D(DBF_NET, "Server certificate:");

                      if(!(X509_NAME_oneline(X509_get_subject_name(server_cert), x509buf, X509BUFSIZE)))
                        E(DBF_NET, "X509_NAME_oneline...[subject] error!");

                      D(DBF_NET, "subject: %s", x509buf);

                      if(!(X509_NAME_oneline(X509_get_issuer_name(server_cert), x509buf, X509BUFSIZE)))
                        E(DBF_NET, "X509_NAME_oneline...[issuer] error!");

                      D(DBF_NET, "issuer:  %s", x509buf);

                      free(x509buf);
                    }

                    if(server_cert != NULL)
                      X509_free(server_cert);
                  }
                  #endif
                }

                FreeCertificateChain(cert);
              }
            }
          }
        }
      }
    }
    else
      W(DBF_NET, "AmiSSLBase == NULL");

    // if we weren't ale to initialize the TLS/SSL stuff we have to clear it
    // before leaving
    if(secure == FALSE)
    {
      conn->ssl = NULL;
      conn->error = CONNECTERR_SSLFAILED;

      // tell the user if secure connection are impossible due to AmiSSL being unavailable
      if(AmiSSLBase == NULL)
        ER_NewError(tr(MSG_ER_UNUSABLEAMISSL));
    }
  }

  RETURN(secure);
  return secure;
}

///
///
BOOL InitSSLConnections(void)
{
  BOOL result = FALSE;
  ENTER();

  // try to open amisslmaster.library first
  if((AmiSSLMasterBase = OpenLibrary("amisslmaster.library", AMISSLMASTER_VERSION)) != NULL &&
     /* LIB_VERSION_IS_AT_LEAST(AmiSSLMasterBase, AMISSLMASTER_VERSION, AMISSLMASTER_REVISION) && */
     GETINTERFACE("main", 1, IAmiSSLMaster, AmiSSLMasterBase))
  {
    if(OpenAmiSSLTags(AMISSL_VERSION,
                      AmiSSL_UsesOpenSSLStructs, TRUE,
                      AmiSSL_GetAmiSSLBase, &AmiSSLBase,
                      #if !defined(__amigaos4__)
                      AmiSSL_GetAmiSSLExtBase, &AmiSSLExtBase,
                      #else
                      AmiSSL_GetIAmiSSL, &IAmiSSL,
                      #endif
                      TAG_DONE) == 0) // 0 signals NO error
    {
      char tmp[24+1];

      D(DBF_STARTUP, "successfully opened AmiSSL library %d.%d (%s)", AmiSSLBase->lib_Version, AmiSSLBase->lib_Revision, AmiSSLBase->lib_IdString);

      // initialize AmiSSL/OpenSSL related stuff that
      // needs to be initialized before each threads spans
      // own initializations
      OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, NULL); // Initialize OpenSSL's SSL libraries

      // seed the random number generator with some valuable entropy
      D(DBF_NET, "AmiSSL: seeding random number generator");
      snprintf(tmp, sizeof(tmp), "%08lx%08lx%08lx", (unsigned long)time((time_t *)NULL), (unsigned long)FindTask(NULL), (unsigned long)rand());
      RAND_seed(tmp, strlen(tmp));

      // 1) now we create a common SSL_CTX object which all our SSL connections will share
      if((G->sslCtx = SSL_CTX_new(TLS_client_method())) == NULL)
        E(DBF_NET, "AmiSSL: can't create SSL_CTX object!");
      // 2) set minimum allowed protocol version to SSL3 (SSLv2 is deprecated/insecure)
      else if(SSL_CTX_set_min_proto_version(G->sslCtx, SSL3_VERSION) == 0)
        E(DBF_NET, "AmiSSL: couldn't set minimum protocol version to SSL3. SSL: %s", ERR_error_string(ERR_get_error(), NULL));
      else
      {
        int rc = 0; // make sure set_default_verify_paths() is called

        D(DBF_NET, "AmiSSL: SSL ctx timeout: %ld s", SSL_CTX_get_timeout(G->sslCtx));

        if(FileExists(DEFAULT_CAPATH) == TRUE)
        {
          D(DBF_NET, "AmiSSL: CAfile = '%s', CApath = '%s'", DEFAULT_CAFILE, DEFAULT_CAPATH);

          if(FileExists(DEFAULT_CAFILE) == FALSE)
            ER_NewError(tr(MSG_ER_WARN_CAFILE), DEFAULT_CAFILE);

          // 3) load the certificates (e.g. CA) from either a file or a directory path
          if(FileExists(DEFAULT_CAFILE) == TRUE)
            rc = SSL_CTX_load_verify_locations(G->sslCtx, DEFAULT_CAFILE, DEFAULT_CAPATH);
          else
            rc = SSL_CTX_load_verify_locations(G->sslCtx, NULL, DEFAULT_CAPATH);

          if(rc == 0)
          {
            W(DBF_NET, "AmiSSL: setting default verify locations failed!");

            ER_NewError(tr(MSG_ER_WARN_LOADCAPATH), DEFAULT_CAFILE, DEFAULT_CAPATH);
          }
        }
        else
          ER_NewError(tr(MSG_ER_WARN_CAPATH), DEFAULT_CAPATH);

        // 4) if no CA file or path is given we set the default pathes
        if(rc == 0 && (rc = SSL_CTX_set_default_verify_paths(G->sslCtx)) == 0)
          E(DBF_NET, "AmiSSL: setting default verify locations failed");

        // 5) get a new ssl Data Index for storing application specific data
        if(rc != 0 && (G->sslDataIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL)) < 0)
        {
          E(DBF_NET, "AmiSSL: SSL_get_ex_new_index() failed");
          rc = 0; // error
        }

        // 6) set SSL_VERIFY_PEER so that we later can decide on our own in the verify_callback
        //    function wheter the connection should continue or if it should be terminated right away.
        SSL_CTX_set_verify(G->sslCtx, SSL_VERIFY_PEER, ENTRY(verify_callback));

        // 7) set the ciphers we want to use and exclude unwanted ones
        if(rc != 0 && (rc = SSL_CTX_set_cipher_list(G->sslCtx, C->DefaultSSLCiphers)) == 0)
           E(DBF_NET, "AmiSSL: SSL_CTX_set_cipher_list() error!");
        else
        {
          D(DBF_STARTUP, "AmiSSL: successfully initialized");

          result = TRUE;
        }
      }
    }
    else
      E(DBF_NET, "AmiSSL: OpenAmiSSLTags() returned an error");
  }
  else
    W(DBF_NET, "AmiSSL: Couldn't open required amisslmaster lib version %d.%d", AMISSLMASTER_VERSION, AMISSLMASTER_REVISION);

  // if an error occurred make sure to have everything cleaned up correctly.
  if(result == FALSE)
    CleanupSSLConnections();

  RETURN(result);
  return result;
}

///
///
void CleanupSSLConnections(void)
{
  ENTER();

  // cleanup the SSL connection context
  if(G->sslCtx != NULL)
  {
    SSL_CTX_free(G->sslCtx);
    G->sslCtx = NULL;
  }

  // close amissl
  if(AmiSSLBase != NULL)
  {
    CloseAmiSSL();
    AmiSSLBase = NULL;
  }

  // close amisslmaster
  if(AmiSSLMasterBase != NULL)
  {
    DROPINTERFACE(IAmiSSLMaster);
    CloseLibrary(AmiSSLMasterBase);
    AmiSSLMasterBase = NULL;
  }

  LEAVE();
}

///