/*! @file

    @id $Id$

    This product includes software developed by the OpenSSL Project
    for use in the OpenSSL Toolkit (http://www.openssl.org/)
    
    This product includes cryptographic software written by
    Eric Young (eay@cryptsoft.com)
*/
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

#ifndef __OPENSSL_HXX__
#define __OPENSSL_HXX__

#include <openssl/pkcs12.h>
#include <openssl/pkcs7.h>
#include <openssl/x509.h>
#include "openssl/bio.h"
#include "openssl/des.h"
#ifdef DATADIR // DATADIR is defined in autoconf/automake
#undef DATADIR // but DATADIR is also a variablename in ssl.h
#endif
#include "openssl/ssl.h"
#include <openssl/err.h>
#include <vector>

#include <openssl/x509v3.h> // BASIC_CONSTRAINTS
#include <openssl/bn.h>
#include <cryptaux.hxx>
#include <cstdio>
#include <cassert>
#include <openssl-compatibility.hxx>

/*! @defgroup gopenssl C++ Wrapper around OpenSSL API
    Support for SSL-connections, engines, keys and certificates. */
//@{
//! @defgroup openssllib OpenSSL C++ Library
//! @defgroup opensslcompat OpenSSL Compatibility Wrapper
//! @defgroup opensslexceptions OpenSSL Exceptions

//! @ref gopenssl @copydoc gpcsc
namespace openssl {

  //============================================================================
  //! @addtogroup opensslexceptions
  //@{

  //----------------------------------------------------------------------------
  class exception: public std::exception {
    public:
      exception(const std::string& reason) throw():
          _what("openssl: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
      ~exception() throw() {}
      const char* what() const throw() {
        return _what.c_str();
      }
    private:
      std::string _what;
  };
  //----------------------------------------------------------------------------
  class openssl_error: public exception {
    public:
      openssl_error(const std::string& reason) throw():
          exception(reason+' '+ERR_error_string(ERR_get_error(), 0)) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class cannot_init: public openssl_error {
    public:
      cannot_init(const std::string& reason) throw():
          openssl_error("openssl initialization: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class encryption_error: public openssl_error {
    public:
      encryption_error(const std::string& reason) throw():
          openssl_error("encryption: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs12_error: public openssl_error {
    public:
      pkcs12_error(const std::string& reason) throw():
          openssl_error("pkcs12: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs7_error: public openssl_error {
    public:
      pkcs7_error(const std::string& reason) throw():
          openssl_error("pkcs7: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class x509_error: public openssl_error {
    public:
      x509_error(const std::string& reason) throw():
          openssl_error("x509: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class key_error: public openssl_error {
    public:
      key_error(const std::string& reason) throw():
          openssl_error("private key: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class tcp_error: public openssl_error {
    public:
      tcp_error(const std::string& reason) throw():
          openssl_error("tcp "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class ssl_error: public openssl_error {
    public:
      ssl_error(const std::string& reason) throw():
          openssl_error("ssl: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class cannot_encrypt: public encryption_error {
    public:
      cannot_encrypt(std::string reason) throw():
          encryption_error("cannot encrypt text: "+reason) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class allocation_failed: public x509_error {
    public:
      allocation_failed() throw():
          x509_error("memory allocation failed") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class x509_decoding_failed: public x509_error {
    public:
      x509_decoding_failed(const std::string& der) throw():
          x509_error("certificate decoding failed: "+crypto::readable(der)) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class undefined_certificate: public x509_error {
    public:
      undefined_certificate() throw():
          x509_error("certificate must not be 0") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class x509_parsing_failed: public x509_error {
    public:
      x509_parsing_failed() throw():
          x509_error("parsing DER encoded certificate failed") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class x509_copy_failed: public x509_error {
    public:
      x509_copy_failed() throw():
          x509_error("certificate object copy failed") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class key_copy_failed: public key_error {
    public:
      key_copy_failed() throw():
          key_error("key object copy failed") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class undefined_key: public key_error {
    public:
      undefined_key() throw():
          key_error("private key must not be 0") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs12_reading_failed: public pkcs12_error {
    public:
      pkcs12_reading_failed(const std::string& file) throw():
          pkcs12_error("reading DER encoded p12 file failed: "+file) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs12_parsing_failed: public pkcs12_error {
    public:
      pkcs12_parsing_failed(const std::string& file) throw():
          pkcs12_error("parsing DER encoded p12 file failed: "+file) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs12_no_private_key: public pkcs12_error {
    public:
      pkcs12_no_private_key() throw(): pkcs12_error("no private key") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs12_no_x509: public pkcs12_error {
    public:
      pkcs12_no_x509() throw(): pkcs12_error("no x509 certificate") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs7_reading_failed: public pkcs7_error {
    public:
      pkcs7_reading_failed(const std::string& file) throw():
          pkcs7_error("reading DER encoded p7 file failed: "+file) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs7_parsing_failed: public pkcs7_error {
    public:
      pkcs7_parsing_failed() throw():
          pkcs7_error("parsing DER encoded p7 failed") {
        CRYPTOLOG("**** exception ****");
      }
      pkcs7_parsing_failed(const std::string& file) throw():
          pkcs7_error("parsing DER encoded p7 file failed: "+file) {
      }
  };
  //----------------------------------------------------------------------------
  class pkcs7_unsupported_format: public pkcs7_error {
    public:
      pkcs7_unsupported_format() throw(): pkcs7_error("format not supported") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class pkcs7_no_x509: public pkcs7_error {
    public:
      pkcs7_no_x509() throw(): pkcs7_error("no x509 certificate") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class cannot_open_file: public exception {
    public:
      cannot_open_file(const std::string& file) throw():
          exception("cannot open file: "+file) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class tcp_connection_failed: public tcp_error {
    public:
      tcp_connection_failed(const std::string& hostPort) throw():
          tcp_error("connection failed to: "+hostPort) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class tcp_server_not_specified: public tcp_error {
    public:
      tcp_server_not_specified() throw():
          tcp_error("reconnect without prior connect") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class tcp_closed_connection: public tcp_error {
    public:
      tcp_closed_connection() throw(): tcp_error("closed connection") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class tcp_read_error: public tcp_error {
    public:
      tcp_read_error() throw(): tcp_error("read error") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class tcp_write_error: public tcp_error {
    public:
      tcp_write_error() throw(): tcp_error("write error") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class ssl_cannot_create_context: public ssl_error {
    public:
      ssl_cannot_create_context() throw(): ssl_error("cannot create context") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class ssl_certificate_file_not_loaded: public ssl_error {
    public:
      ssl_certificate_file_not_loaded(const std::string& file) throw():
          ssl_error("certificate file not loaded: "+file) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class ssl_certificate_folder_not_loaded: public ssl_error {
    public:
      ssl_certificate_folder_not_loaded(const std::string& path) throw():
          ssl_error("certificate folder not loaded: "+path) {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class ssl_no_connection: public ssl_error {
    public:
      ssl_no_connection() throw():
          ssl_error("no ssl connection") {
        CRYPTOLOG("**** exception ****");
      }
  };
  //----------------------------------------------------------------------------
  class ssl_verification_failed: public ssl_error {
    public:
      ssl_verification_failed(long num) throw():
          ssl_error("certificate verification failed: "+reason(num)) {
        CRYPTOLOG("**** exception ****");
      }
      static std::string reason(long num) {
        switch (num) {
          case X509_V_OK:
            return "X509_V_OK: Ok the operation was successful.";
          case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
            return "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: Unable to get issuer"
              " certificate the issuer certificate could not be found: this"
              " occurs if the issuer certificate of an untrusted certificate"
              " cannot be found.";
          case X509_V_ERR_UNABLE_TO_GET_CRL:
            return "X509_V_ERR_UNABLE_TO_GET_CRL:"
              " unable to get certificate CRL the CRL of a certificate could"
              " not be found. Unused.";
          case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
            return "4 X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: Unable to"
              " decrypt certificate's signature the certificate signature"
              " could not be decrypted. This means that the actual signature"
              " value could not be determined rather than it not matching the"
              " expected value, this is only meaningful for RSA keys.";
          case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
            return "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: Unable to"
              " decode issuer public key the public key in the certificate"
              " SubjectPublicKeyInfo could not be read.";
          case X509_V_ERR_CERT_SIGNATURE_FAILURE:
            return "X509_V_ERR_CERT_SIGNATURE_FAILURE: Certificate signature"
              " failure the signature of the certificate is invalid.";
          case X509_V_ERR_CRL_SIGNATURE_FAILURE:
            return "X509_V_ERR_CRL_SIGNATURE_FAILURE: Crl signature failure"
              " the signature of the certificate is invalid. Unused.";
          case X509_V_ERR_CERT_NOT_YET_VALID:
            return "X509_V_ERR_CERT_NOT_YET_VALID: Certificate is not yet"
              " valid the certificate is not yet valid: the notBefore date is"
              " after the current time.";
          case X509_V_ERR_CERT_HAS_EXPIRED:
            return "X509_V_ERR_CERT_HAS_EXPIRED: Certificate has expired the"
              " certificate has expired: that is the notAfter date is before"
              " the current time.";
          case X509_V_ERR_CRL_NOT_YET_VALID:
            return "X509_V_ERR_CRL_NOT_YET_VALID: Crl is not yet valid the CRL"
              " is not yet valid. Unused.";
          case X509_V_ERR_CRL_HAS_EXPIRED:
            return "X509_V_ERR_CRL_HAS_EXPIRED: Crl has expired the CRL has"
              " expired. Unused.";
          case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
            return "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: Format error in"
              " certificate's notBefore field the certificate notBefore field"
              " contains an invalid time.";
          case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
            return "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: Format error in"
              " certificate's notAfter field the certificate notAfter field"
              " contains an invalid time.";
          case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
            return "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: Format error in"
              " CRL's lastUpdate field the CRL lastUpdate field contains an"
              " invalid time. Unused.";
          case X509_V_ERR_OUT_OF_MEM:
            return "X509_V_ERR_OUT_OF_MEM: Out of memory an error occurred"
              " trying to allocate memory. This should never happen.";
          case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
            return "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: Self signed"
              " certificate the passed certificate is self signed and the same"
              " certificate cannot be found in the list of trusted"
              " certificates.";
          case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
            return "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: Self signed"
              " certificate in certificate chain the certificate chain could"
              " be built up using the untrusted certificates but the root"
              " could not be found locally.";
          case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
            return "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: Unable to"
              " get local issuer certificate the issuer certificate of a"
              " locally looked up certificate could not be found. This"
              " normally means the list of trusted certificates is not"
              " complete.";
          case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
            return "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: Unable to"
              " verify the first certificate no signatures could be verified"
              " because the chain contains only one certificate and it is not"
              " self signed.";
          case X509_V_ERR_CERT_CHAIN_TOO_LONG:
            return "X509_V_ERR_CERT_CHAIN_TOO_LONG: Certificate chain too long"
              " the certificate chain length is greater than the supplied"
              " maximum depth. Unused.";
          case X509_V_ERR_CERT_REVOKED:
            return "X509_V_ERR_CERT_REVOKED: Certificate revoked the"
              " certificate has been revoked. Unused.";
          case X509_V_ERR_INVALID_CA:
            return "X509_V_ERR_INVALID_CA: Invalid CA certificate a CA"
              " certificate is invalid. Either it is not a CA or its"
              " extensions are not consistent with the supplied purpose.";
          case X509_V_ERR_INVALID_PURPOSE:
            return "X509_V_ERR_INVALID_PURPOSE: Unsupported certificate"
              " purpose the supplied certificate cannot be used for the"
              " specified purpose.";
          case X509_V_ERR_CERT_UNTRUSTED:
            return "X509_V_ERR_CERT_UNTRUSTED: Certificate not trusted the"
              " root CA is not marked as trusted for the specified purpose.";
          case X509_V_ERR_CERT_REJECTED:
            return "X509_V_ERR_CERT_REJECTED: Certificate rejected the root CA"
              " is marked to reject the specified purpose.";
          case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
            return "X509_V_ERR_SUBJECT_ISSUER_MISMATCH: Subject issuer"
              " mismatch the current candidate issuer certificate was rejected"
              " because its subject name did not match the issuer name of the"
              " current certificate. Only displayed when the -issuer_checks"
              " option is set.";
          case X509_V_ERR_AKID_SKID_MISMATCH:
            return "X509_V_ERR_AKID_SKID_MISMATCH: Authority and subject key"
              " identifier mismatch the current candidate issuer certificate"
              " was rejected because its subject key identifier was present"
              " and did not match the authority key identifier current"
              " certificate. Only displayed when the -issuer_checks option is"
              " set.";
          case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
            return "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: Authority and"
              " issuer serial number mismatch the current candidate issuer"
              " certificate was rejected because its issuer name and serial"
              " number was present and did not match the authority key"
              " identifier of the current certificate. Only displayed when the"
              " -issuer_checks option is set.";
          case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
            return "X509_V_ERR_KEYUSAGE_NO_CERTSIGN: Ey usage does not include"
              " certificate signing the current candidate issuer certificate"
              " was rejected because its keyUsage extension does not permit"
              " certificate signing.";
          default: {
            std::stringstream ss;
            ss<<"Unknown certificate validation error code: "<<num;
            return ss.str();
          }
        }
      }
  };
  //@}

  //! @addtogroup openssllib
  //@{
  
  //============================================================================
  //! Initializes OpenSSL. Must be instanciated exactly once.
  class Init {
    public:
      Init() {
        CRYPTOLOG("log");
        SSL_load_error_strings();
        ERR_load_BIO_strings();
        OpenSSL_add_all_algorithms();
        if (!SSL_library_init()) throw cannot_init("cannot init SSL library");
      }
  };

  //! Bignumber operations
  class BigNum {
    public:
      BigNum():
          _bn(BN_new()) {
        CRYPTOLOG("log");
      }
      BigNum(const std::string& num):
          _bn(BN_bin2bn((const unsigned char*)num.data(), (int)num.size(), 0)) {
        CRYPTOLOG("log");
      }
      ~BigNum() {
        CRYPTOLOG("log");
        BN_free(_bn);
      }
      int size() const {
        CRYPTOLOG("log");
        return size(_bn);
      }
      static int size(BIGNUM* n) {
        CRYPTOLOG("log");
        return BN_num_bytes(n);
      }
      std::string string() const {
        CRYPTOLOG("log");
        return string(_bn);
      }
      static std::string string(const BIGNUM* a) {
        CRYPTOLOG("log");
        std::string res(BN_num_bytes(a), '0');
        BN_bn2bin(a, (unsigned char*)res.begin().operator->());
        return res;
      }
    private:
      BigNum(const BigNum& o);
      BigNum& operator=(const BigNum& o);
    private:
      BIGNUM* _bn;
  };

  class CBlock8 {
    public:
      CBlock8() {
        _cb[0] = _cb[1] = _cb[2] = _cb[3] =
          _cb[4] = _cb[5] = _cb[6] = _cb[7] = 0;
      }
      CBlock8(unsigned char c1, unsigned char c2, unsigned char c3,
              unsigned char c4, unsigned char c5, unsigned char c6,
              unsigned char c7, unsigned char c8) {
        _cb[0] = c1;
        _cb[1] = c2;
        _cb[2] = c3;
        _cb[3] = c4;
        _cb[4] = c5;
        _cb[5] = c6;
        _cb[6] = c7;
        _cb[7] = c8;
      }
      //! String must contain exactly 8 char.
      CBlock8(const std::string& s) {
        assert(s.size()==8);
        _cb[0] = s[0];
        _cb[1] = s[1];
        _cb[2] = s[3];
        _cb[3] = s[4];
        _cb[4] = s[5];
        _cb[5] = s[6];
        _cb[6] = s[7];
        _cb[7] = s[8];
      }
      operator DES_cblock&() {
        return _cb;
      }
      operator DES_cblock*() {
        return &_cb;
      }
      operator std::string() const {
        return std::string((const std::string::value_type*)_cb, 8);
      }
    private:
      DES_cblock _cb;
  };

  //! Encrypt a string using DES.
  /*! @param txt text to encrypt - size must be a multiple of 8
      @param key1 DES key for encryption */
  inline std::string desEnc(const std::string& txt, CBlock8 key1) {
    CRYPTOLOG("log");
    std::string res(txt);
    if (txt.size()%8!=0)
      throw cannot_encrypt("text size must be a multiple of eight");
    DES_key_schedule ks1;
    DES_set_key_unchecked(key1, &ks1);
    for (std::string::size_type pos(0); pos<txt.size(); pos+=8) {
      union {
          DES_cblock array;
          const char* text;
      } in, out;
      in.text = txt.begin().operator->()+pos;
      out.text = res.begin().operator->()+pos;
      DES_ecb_encrypt(&in.array, &out.array, &ks1, DES_ENCRYPT);
    }
    return res;
  }

  //! Decrypt a DES encrypted string.
  /*! @param txt text to decrypt - size must be a multiple of 8
      @param key1 DES key for decryption */
  inline std::string desDec(const std::string& txt, CBlock8 key1) {
    CRYPTOLOG("log");
    std::string res(txt);
    if (txt.size()%8!=0)
      throw cannot_encrypt("text size must be a multiple of eight");
    DES_key_schedule ks1;
    DES_set_key_unchecked(key1, &ks1);
    for (std::string::size_type pos(0); pos<txt.size(); pos+=8) {
      union {
          DES_cblock array;
          const char* text;
      } in, out;
      in.text = txt.begin().operator->()+pos;
      out.text = res.begin().operator->()+pos;
      DES_ecb_encrypt(&in.array, &out.array, &ks1, DES_DECRYPT);
    }
    return res;
  }

  //! DES CBC Encryption
  /*! @param txt  If the length is not an integral multiple of eight
                  bytes, it is zero filled. The output is always an
                  integral multiple of eight bytes.
      @param key  block key
      @param ivec vector */
  inline std::string desCbcEnc(std::string txt, CBlock8 key, CBlock8& ivec) {
    CRYPTOLOG("log");
    if (txt.size()%8!=0) txt.resize((txt.size()/8+1)*8, 0);
    std::string res(txt.size(), 0);
    DES_key_schedule ks;
    DES_set_key_unchecked(key, &ks);
    DES_ncbc_encrypt((unsigned char*)&txt[0], (unsigned char*)&res[0],
                     txt.size(), &ks, ivec, DES_ENCRYPT);
    return res;
  }

  //! DES CBC Encryption with empty vector
  /*! @param txt  If the length is not an integral multiple of eight
                  bytes, it is zero filled. The output is always an
                  integral multiple of eight bytes.
      @param key  block key */
  inline std::string desCbcEnc(const std::string& txt, const CBlock8& key) {
    CRYPTOLOG("log");
    CBlock8 ivec;
    return desCbcEnc(txt, key, ivec);
  }

  //! DES CBC Decryption
  /*! @param txt  If the length is not an integral multiple of eight
                  bytes, it is zero filled. The output is always an
                  integral multiple of eight bytes.
      @param key  block key
      @param ivec vector */
  inline std::string desCbcDec(std::string txt, CBlock8 key, CBlock8& ivec) {
    CRYPTOLOG("log");
    if (txt.size()%8!=0) txt.resize((txt.size()/8+1)*8, 0);
    std::string res(txt.size(), 0);
    DES_key_schedule ks;
    DES_set_key_unchecked(key, &ks);
    DES_ncbc_encrypt((unsigned char*)&txt[0], (unsigned char*)&res[0],
                     txt.size(), &ks, ivec, DES_DECRYPT);
    return res;
  }

  //! DES CBC Decryption with empty vector
  /*! @param txt If the length is not an integral multiple of eight
                 bytes, it is zero filled. The output is always an
                 integral multiple of eight bytes.
      @param key block key */
  inline std::string desCbcDec(const std::string& txt, const CBlock8& key) {
    CRYPTOLOG("log");
    CBlock8 ivec;
    return desCbcDec(txt, key, ivec);
  }

  /*! @param txt  If the length is not an integral multiple of eight
                  bytes, it is zero filled. The output is always an
                  integral multiple of eight bytes.
      @param key1 block key 1
      @param key2 block key 2
      @param ivec vector */
  inline std::string des2edeCbcEnc(std::string txt,
                                   CBlock8 key1, CBlock8 key2,
                                   CBlock8 ivec = CBlock8()) {
    CRYPTOLOG("log");
    if (txt.size()%8!=0) txt.resize((txt.size()/8+1)*8, 0);
    std::string res(txt.size(), 0);
    DES_key_schedule ks1, ks2;
    DES_set_key_unchecked(key1, &ks1);
    DES_set_key_unchecked(key2, &ks2);
    DES_ede2_cbc_encrypt((unsigned char*)&txt[0], (unsigned char*)&res[0],
                         txt.size(), &ks1, &ks2, ivec, DES_ENCRYPT);
    return res;
  }

  /*! @param txt  If the length is not an integral multiple of eight
                  bytes, it is zero filled. The output is always an
                  integral multiple of eight bytes.
      @param key1 block key 1
      @param key2 block key 2
      @param ivec vector */
  inline std::string des2edeCbcDec(std::string txt,
                                   CBlock8 key1, CBlock8 key2,
                                   CBlock8 ivec = CBlock8()) {
    CRYPTOLOG("log");
    if (txt.size()%8!=0) txt.resize((txt.size()/8+1)*8, 0);
    std::string res(txt.size(), 0);
    DES_key_schedule ks1, ks2;
    DES_set_key_unchecked(key1, &ks1);
    DES_set_key_unchecked(key2, &ks2);
    DES_ede2_cbc_encrypt((unsigned char*)&txt[0], (unsigned char*)&res[0],
                         txt.size(), &ks1, &ks2, ivec, DES_DECRYPT);
    return res;
  }

  //! @todo untested
  inline std::string des2ecbEnc(std::string txt,
                                CBlock8 key1, CBlock8 key2) {
    CRYPTOLOG("log");
    std::string res;
    if (txt.size()%8!=0)
      throw cannot_encrypt("text size must be a multiple of eight");
    DES_key_schedule ks1, ks2;
    DES_set_key_unchecked(key1, &ks1);
    DES_set_key_unchecked(key2, &ks2);
    for (std::string::size_type pos(0); pos<txt.size(); pos+=8) {
      CBlock8 out, in(txt.substr(pos, 8));
      DES_ecb2_encrypt(in, out, &ks1, &ks2, DES_ENCRYPT);
      res+=out;
    }
    return res;
  }

  //! @todo untested
  inline std::string des2ecbDec(std::string txt,
                                CBlock8 key1, CBlock8 key2) {
    CRYPTOLOG("log");
    std::string res;
    if (txt.size()%8!=0)
      throw cannot_encrypt("text size must be a multiple of eight");
    DES_key_schedule ks1, ks2;
    DES_set_key_unchecked(key1, &ks1);
    DES_set_key_unchecked(key2, &ks2);
    for (std::string::size_type pos(0); pos<txt.size(); pos+=8) {
      CBlock8 out, in(txt.substr(pos, 8));
      DES_ecb2_encrypt(in, out, &ks1, &ks2, DES_DECRYPT);
      res+=out;
    }
    return res;
  }

  //============================================================================
  //! X509 Certificate
  class X509 {
    public:
      //! Construct empty certificate.
      X509(): _x509(X509_new()) {
        CRYPTOLOG("log");
        if (!_x509) throw allocation_failed();
      }
      //! Initialize from DER encoded cerificate.
      X509(const std::string& der): _x509(0) {
        CRYPTOLOG("log");
        V0_CONST unsigned char* c
          ((V0_CONST unsigned char*)der.begin().operator->());
        if (!(_x509=d2i_X509(0, &c, der.size())) ||
            (const char*)c!=der.begin().operator->()+der.size())
          throw x509_decoding_failed(der);
      }
      X509(const X509& o): _x509(0) {
        CRYPTOLOG("log");
        _x509 = X509_dup(o._x509);
        // unsigned char* d(0);
        // int len(i2d_X509(o._x509, &d));
        // if (!len) throw x509_copy_failed();
        // V0_CONST unsigned char* d2(d);
        // _x509 = d2i_X509(0, &d2, len);
        // OPENSSL_free(d);
        if (!_x509) throw x509_copy_failed();
      }
      //! Take over OpenSSL allocated certificate.
      X509(::X509 *x509): _x509(x509) {
        CRYPTOLOG("log");
        if (!_x509) throw undefined_certificate();
      }
      ~X509() {
        CRYPTOLOG("log");
        X509_free(_x509);
      }
      X509& operator=(const X509& o) {
        CRYPTOLOG("log");
        X509_free(_x509);
        _x509 = 0;
        _x509 = X509_dup(o._x509);
        // unsigned char* d(0);
        // int len(i2d_X509(o._x509, &d));
        // if (!len) throw x509_copy_failed();
        // V0_CONST unsigned char* d2(d);
        // _x509 = d2i_X509(0, &d2, len);
        // OPENSSL_free(d);
        if (!_x509) throw x509_copy_failed();
        return *this;
      }
      //! Get a copy of the OpenSSL low level certificate
      /*! @note you are responsible to free the certificate */
      ::X509* lowLevelCopy() const {
        return X509_dup(_x509);
      }
      //! Get DER encoded subject.
      std::string subjectDER() const {
        CRYPTOLOG("log");
        unsigned char* c(0);
        int len(i2d_X509_NAME(X509_get_subject_name(_x509), &c));
        std::string res((char*)c, len);
        OPENSSL_free(c);
        return res;
      }
      //! Get DER encoded issuer.
      std::string issuerDER() const {
        CRYPTOLOG("log");
        unsigned char* c(0);
        int len(i2d_X509_NAME(X509_get_issuer_name(_x509), &c));
        std::string res((char*)c, len);
        OPENSSL_free(c);
        return res;
      }
      //! Get DER encoded value.
      std::string valueDER() const {
        CRYPTOLOG("log");
        unsigned char* c(0);
        int len(i2d_X509(_x509, &c));
        std::string res((char*)c, len);
        OPENSSL_free(c);
        return res;
      }
      //! Get DER encoded serial number.
      std::string serialDER() const {
        CRYPTOLOG("log");
        unsigned char* c(0);
        int len(i2d_ASN1_INTEGER(X509_get_serialNumber(_x509), &c));
        std::string res((char*)c, len);
        OPENSSL_free(c);
        return res;
      }
      //! Get serial number.
      std::string serial() const {
        CRYPTOLOG("log");
        unsigned char* c(0);
        int len(i2d_X509(_x509, &c));
        if (len<15 || len<15+c[14]) {
          OPENSSL_free(c);
          return std::string();
        }
        std::string res((char*)c+15, c[14]);
        OPENSSL_free(c);
        return res;
      }
      //! Get id.
      std::string id() const {
        CRYPTOLOG("log");
        unsigned char c[SHA_DIGEST_LENGTH];
        const unsigned char *pk(0);
        int ppklen(0);
        X509_PUBKEY_get0_param(0, &pk, &ppklen, 0, X509_get_X509_PUBKEY(_x509));
        SHA1(pk, ppklen, c);
        return std::string((char*)c, SHA_DIGEST_LENGTH);
      }
      //! Get common name.
      std::string commonName() const {
        CRYPTOLOG("log");
        X509_NAME *name(X509_get_subject_name(_x509));
        ASN1_STRING* cn
          (X509_NAME_ENTRY_get_data
           (X509_NAME_get_entry
            (name, X509_NAME_get_index_by_NID(name, NID_commonName, -1))));
        return std::string((char*)ASN1_STRING_data(cn),
                           ASN1_STRING_length(cn));
      }
      //! Get country name.
      std::string countryName() const {
        CRYPTOLOG("log");
        X509_NAME *name(X509_get_subject_name(_x509));
        ASN1_STRING* cn
          (X509_NAME_ENTRY_get_data
           (X509_NAME_get_entry
            (name, X509_NAME_get_index_by_NID(name, NID_countryName, -1))));
        return std::string((char*)ASN1_STRING_data(cn),
                           ASN1_STRING_length(cn));
      }
      //! Get locality name.
      std::string localityName() const {
        CRYPTOLOG("log");
        X509_NAME *name(X509_get_subject_name(_x509));
        ASN1_STRING* cn
          (X509_NAME_ENTRY_get_data
           (X509_NAME_get_entry
            (name, X509_NAME_get_index_by_NID(name, NID_localityName, -1))));
        return std::string((char*)ASN1_STRING_data(cn),
                           ASN1_STRING_length(cn));
      }
      //! Get state or province name.
      std::string stateOrProvinceName() const {
        CRYPTOLOG("log");
        X509_NAME *name(X509_get_subject_name(_x509));
        ASN1_STRING* cn
          (X509_NAME_ENTRY_get_data
           (X509_NAME_get_entry
            (name, X509_NAME_get_index_by_NID
             (name, NID_stateOrProvinceName, -1))));
        return std::string((char*)ASN1_STRING_data(cn),
                           ASN1_STRING_length(cn));
      }
      //! Get organization name.
      std::string organizationName() const {
        CRYPTOLOG("log");
        X509_NAME *name(X509_get_subject_name(_x509));
        ASN1_STRING* cn
          (X509_NAME_ENTRY_get_data
           (X509_NAME_get_entry
            (name, X509_NAME_get_index_by_NID
             (name, NID_organizationName, -1))));
        return std::string((char*)ASN1_STRING_data(cn),
                           ASN1_STRING_length(cn));
      }
      //! Check whether it's a CA certificate.
      bool isCa() {
        CRYPTOLOG("log");
        BASIC_CONSTRAINTS* bc(0);
        int pos(X509_get_ext_by_NID(_x509, NID_basic_constraints, -1));
        if (pos>=0)
          bc = (BASIC_CONSTRAINTS*)X509V3_EXT_d2i(X509_get_ext(_x509, pos));
        return bc&&bc->ca;
      }
      //! Get organizational unit name.
      std::string organizationalUnitName() const {
        CRYPTOLOG("log");
        X509_NAME *name(X509_get_subject_name(_x509));
        ASN1_STRING* cn
          (X509_NAME_ENTRY_get_data
           (X509_NAME_get_entry
            (name, X509_NAME_get_index_by_NID
             (name, NID_organizationalUnitName, -1))));
        return std::string((char*)ASN1_STRING_data(cn),
                           ASN1_STRING_length(cn));
      }
      //! Get key usage flags.
      int keyUsageFlags() const {
        CRYPTOLOG("log");
        int res(X509v3_KU_UNDEF);
        int pos(X509_get_ext_by_NID(_x509, NID_key_usage, -1));
        if (pos>=0) {
          ASN1_BIT_STRING* ku((ASN1_BIT_STRING*)X509V3_EXT_d2i
                              (X509_get_ext(_x509, pos)));
          std::string val((char*)ASN1_STRING_data(ku),
                          ASN1_STRING_length(ku));
          if (val.size()<=sizeof(int))
            val = std::string(sizeof(int)-val.size(), '\0')+val;
          assert(val.size()==sizeof(int));
          res = *((int*)val.begin().operator->());
        }
        return res;
      }
    private:
      ::X509* _x509;
  };

  //============================================================================
  //! Private certificate key
  class PrivateKey {
    public:
      PrivateKey(): _key(EVP_PKEY_new()) {
        CRYPTOLOG("log");
        if (!_key) throw allocation_failed();
      }
      PrivateKey(const PrivateKey& o): _key(0) {
        CRYPTOLOG("log");
        copy(o);
      }
      PrivateKey(EVP_PKEY* k): _key(k) {
        CRYPTOLOG("log");
        if (!_key) throw undefined_key();
      }
      ~PrivateKey() {
        CRYPTOLOG("log");
        EVP_PKEY_free(_key);
      }
      PrivateKey& operator=(const PrivateKey& o) {
        CRYPTOLOG("log");
        copy(o);
        return *this;
      }
      std::string modulus() const {
        CRYPTOLOG("log");
        const BIGNUM **n(0);
        RSA_get0_key(rsa(), n, 0, 0);
        return BigNum::string(*n);
      }
      std::string publicExponent() const {
        CRYPTOLOG("log");
        const BIGNUM **e(0);
        RSA_get0_key(rsa(), 0, e, 0);
        return BigNum::string(*e);
      }
      std::string privateExponent() const {
        CRYPTOLOG("log");
        const BIGNUM **d(0);
        RSA_get0_key(rsa(), 0, 0, d);
        return BigNum::string(*d);
      }
      std::string prime1() const {
        CRYPTOLOG("log");
        const BIGNUM **p(0);
        RSA_get0_factors(rsa(), p, 0);
        return BigNum::string(*p);
      }
      std::string prime2() const {
        CRYPTOLOG("log");
        const BIGNUM **q(0);
        RSA_get0_factors(rsa(), 0, q);
        return BigNum::string(*q);
      }
      std::string exponent1() const {
        CRYPTOLOG("log");
        const BIGNUM **dmp1(0);
        RSA_get0_crt_params(rsa(), dmp1, 0, 0);
        return BigNum::string(*dmp1);
      }
      std::string exponent2() const {
        CRYPTOLOG("log");
        const BIGNUM **dmq1(0);
        RSA_get0_crt_params(rsa(), 0, dmq1, 0);
        return BigNum::string(*dmq1);
      }
      std::string coefficient() const {
        CRYPTOLOG("log");
        const BIGNUM **iqmp(0);
        RSA_get0_crt_params(rsa(), 0, 0, iqmp);
        return BigNum::string(*iqmp);
      }
    private:
      void copy(const PrivateKey& o) {
        CRYPTOLOG("log");
        EVP_PKEY_free(_key);
        if (!(_key=EVP_PKEY_new())) throw allocation_failed();
        rsa(o);
        dsa(o);
        dh(o);
        /*ec(o);*/
      }
      void rsa(const PrivateKey& o) {
        CRYPTOLOG("log");
        //! @todo throw exception if 0?
        RSA* tmp(o.rsa());
        if (tmp&&!EVP_PKEY_set1_RSA(_key, tmp)) throw key_copy_failed();
      }
      void dsa(const PrivateKey& o) {
        CRYPTOLOG("log");
        DSA* tmp(o.dsa());
        if (tmp&&!EVP_PKEY_set1_DSA(_key, tmp)) throw key_copy_failed();
      }
      void dh(const PrivateKey& o) {
        CRYPTOLOG("log");
        DH* tmp(o.dh());
        if (tmp&&!EVP_PKEY_set1_DH(_key, tmp)) throw key_copy_failed();
      }
    /* Not available on mac osx
      void ec(const PrivateKey& o) {
        CRYPTOLOG("log");
        EC_KEY* tmp(o.ec());
        if (tmp&&!EVP_PKEY_set1_EC_KEY(_key, tmp)) throw key_copy_failed();
      }
    */
      RSA* rsa() const {
        CRYPTOLOG("log");
        //! @todo throw exception if 0?
        return EVP_PKEY_get0_RSA(_key);
      }
      DSA* dsa() const {
        CRYPTOLOG("log");
        return EVP_PKEY_get0_DSA(_key);
      }
      DH* dh() const {
        CRYPTOLOG("log");
        return EVP_PKEY_get0_DH(_key);
      }
    /* Not available on mac osx
      EC_KEY* ec() const {
        CRYPTOLOG("log");
        return EVP_PKEY_get0_EC_KEY(_key);
        }*/
      EVP_PKEY* _key;
  };

  //============================================================================
  //! PKCS#12 certificate file handler
  class PKCS12 {
      
      //...............................................................typedefs
    public:
      typedef std::vector<X509*> X509List;
      
      //................................................................methods
    public:

      //! Read from a PKCS#12 (.p12) file.
      PKCS12(std::string filename, std::string password):
          _key(0), _cert(0) {
        CRYPTOLOG("log");
        FILE* file(fopen(filename.c_str(), "rb"));
        if (!file) throw cannot_open_file(filename);
        ::PKCS12 *p12(d2i_PKCS12_fp(file, 0));
        fclose(file);
        if (!p12) throw pkcs12_reading_failed(filename);
        try {
          EVP_PKEY *pkey(0);
          ::X509 *cert(0);
          STACK_OF(X509) *cal(0);
          if (!PKCS12_parse(p12, password.c_str(), &pkey, &cert, &cal))
            throw pkcs12_parsing_failed(filename);
          if (pkey) _key = new PrivateKey(pkey);
          if (cert) _cert = new X509(cert);
          for (int i(sk_num(CV_STACK cal)); i>0; --i)
            _ca.push_back(new X509((::X509*)sk_pop(CV_STACK cal)));
          PKCS12_free(p12);
        } catch (...) {
          PKCS12_free(p12);
          throw;
        }
      }
      
      ~PKCS12() {
        CRYPTOLOG("log");
        delete _key;
        delete _cert;
        for (X509List::iterator it(_ca.begin()); it!=_ca.end(); ++it)
          delete *it;
      }

      bool hasPrivateKey() const {
        CRYPTOLOG("log");
        return _key;
      }

      bool hasCert() const {
        CRYPTOLOG("log");
        return _cert;
      }

      const PrivateKey& privateKey() const {
        CRYPTOLOG("log");
        if (!_key) throw pkcs12_no_private_key();
        return *_key;
      };

      const X509& x509() const {
        CRYPTOLOG("log");
        if (!_cert) throw pkcs12_no_x509();
        return *_cert;
      };

      const X509List& ca() const {
        CRYPTOLOG("log");
        return _ca;
      }
      
    private:
      PrivateKey *_key;
      X509 *_cert;
      X509List _ca;
  };

  //============================================================================
  //! PKCS#7 certificate file handler
  class PKCS7 {
      
      //...............................................................typedefs
    public:
      typedef std::vector<X509*> X509List;
      
      //................................................................methods
    public:

      /*
      //! Read from a PKCS#7 (.p7) file.
      PKCS7(std::string filename) {
        FILE* file(fopen(filename.c_str(), "rb"));
        if (!file) throw cannot_open_file(filename);
        ::PKCS7 *p7(d2i_PKCS7_fp(file, 0));
        fclose(file);
        if (!p7) throw pkcs7_reading_failed(filename);
        try {
          if (PKCS7_type_is_signed(p7)) while (p7->d.sign->cert->num>0)
            _certs.push_back(new X509((::X509*)sk_pop(p7->d.sign->cert)));
          else //! @todo to be implemented: check for other types
            throw pkcs7_unsupported_format();
          PKCS7_free(p7);
        } catch (...) {
          PKCS7_free(p7);
          throw;
        }
          }*/
      
      //! Read PKCS#7 from memory.
      PKCS7(const std::string& memory) {
        CRYPTOLOG("log");
        BIO* mem(BIO_new_mem_buf(const_cast<void*>((const void*)memory.data()),
                                 (int)memory.size()));
        ::PKCS7 *p7(d2i_PKCS7_bio(mem, 0));
        BIO_free(mem);
        if (!p7) throw pkcs7_parsing_failed();
        try {
          if (PKCS7_type_is_signed(p7)) while (OPENSSL_sk_num(CV_STACK p7->d.sign->cert)>0)
            _certs.push_back(new X509((::X509*)sk_pop(CV_STACK p7->d.sign->cert)));
          else //! @todo to be implemented: check for other types
            throw pkcs7_unsupported_format();
          PKCS7_free(p7);
        } catch (...) {
          PKCS7_free(p7);
          throw;
        }
      }
      
      ~PKCS7() {
        CRYPTOLOG("log");
        for (X509List::iterator it(_certs.begin()); it!=_certs.end(); ++it)
          delete *it;
      }

      const X509List& certs() const {
        CRYPTOLOG("log");
        return _certs;
      }
      
    private:
      X509List _certs;
  };

  //============================================================================
  //! TCP Connection
  class TCP {

    private:

      TCP(const TCP&);
      TCP& operator=(const TCP&);
      
    public:
      
      TCP(): _bio(0) {
        CRYPTOLOG("log");
      }
      
      TCP(const std::string& hostPort): _bio(0) {
        CRYPTOLOG("log");
        connect(hostPort);
      }
      
      virtual ~TCP() {
        CRYPTOLOG("log");
        try {
          close();
        } catch (...) {
          if (!std::uncaught_exception()) throw;
        }
      }
      
      virtual TCP& connect(const std::string& hostPort) {
        CRYPTOLOG("log");
        close();
        if (!(_bio=BIO_new_connect(const_cast<char*>(hostPort.c_str()))) ||
            BIO_do_connect(_bio)<=0)
          throw tcp_connection_failed(hostPort);
        _hostPort = hostPort;
        return *this;
      }

      virtual TCP& connect() {
        if (!_hostPort.size()) throw tcp_server_not_specified();
        close();
        connect(_hostPort);
        return *this;
      }

      operator bool() const {
        CRYPTOLOG("log");
        return _bio;
      }
      
      TCP& operator>>(std::string& s) {
        CRYPTOLOG("log");
        s += read();
        return *this;
      }
      
      TCP& operator<<(const std::string& s) {
        CRYPTOLOG("log");
        return write(s);
      }

      /*! @todo How can I find out, whether there's still data
          available, or whether a response has been finished and
          server is waiting for next request, but connection is still
          open? */
      std::string read() {
        CRYPTOLOG("log");
        if (!_bio) throw tcp_closed_connection();
        const int BUFF_SZ(1024);
        char buff[BUFF_SZ];
        int x(BIO_read(_bio, buff, BUFF_SZ));
        if (x<=0)
          if (BIO_should_retry(_bio)) return read();
          else {
            close();
            if (x==0) return std::string();
            else throw tcp_read_error();
          }
        else if (x==1024) return std::string(buff, x)+read();
        return std::string(buff, x);
      }

      TCP& write(const std::string& s) {
        CRYPTOLOG("log");
        if (!_bio) throw tcp_closed_connection();
        unsigned int x(BIO_write(_bio, s.begin().operator->(), (int)s.size()));
        if (x<=0)
          if (BIO_should_retry(_bio)) return write(s);
          else {
            close();
            throw tcp_write_error();
          }
        else if (x<s.size()) return write(s.substr(x));
        return *this;
      }
      
      virtual TCP& close() {
        CRYPTOLOG("log");
        if (_bio) BIO_free_all(_bio);
        _bio = 0;
        return *this;
      }
      
    protected:
      
      ::BIO* _bio;
      std::string _hostPort;
      
  };

  //! Read authorized certificate from a single pem file.
  class TrustStore {
    public:
      TrustStore(const std::string& pathToPemFile):
          _file(pathToPemFile) {
        CRYPTOLOG("log");
      }
      const std::string& file() const {
        CRYPTOLOG("log");
        return _file;
      }
    private:
      std::string _file;
  };

  class CertificateFolder {
    public:
      CertificateFolder(const std::string& certificateFolder):
          _path(certificateFolder) {
        CRYPTOLOG("log");
      }
      const std::string& path() const {
        CRYPTOLOG("log");
        return _path;
      }
    private:
      std::string _path;
  };

  class SSL: public TCP {
    private:
      SSL();
      SSL(const SSL&);
      SSL& operator=(const SSL&);
    public:
      SSL(const TrustStore& file):
          _ctx(SSL_CTX_new(SSLv23_client_method())),
          _ssl(0) {
        CRYPTOLOG("log");
        if (!_ctx) throw ssl_cannot_create_context();
        if (!SSL_CTX_load_verify_locations(_ctx, file.file().c_str(), 0))
          throw ssl_certificate_file_not_loaded(file.file());
      }
      SSL(const CertificateFolder& folder):
          _ctx(SSL_CTX_new(SSLv23_client_method())),
          _ssl(0) {
        CRYPTOLOG("log");
        if (!_ctx) throw ssl_cannot_create_context();
        if (!SSL_CTX_load_verify_locations(_ctx, 0, folder.path().c_str()))
          throw ssl_certificate_folder_not_loaded(folder.path());
      }
      ~SSL() {
        CRYPTOLOG("log");
        close();
        SSL_CTX_free(_ctx);
      }
      virtual SSL& connect(const std::string& hostPort) {
        CRYPTOLOG("log");
        close();
        if (!(_bio=BIO_new_ssl_connect(_ctx)))
          throw tcp_connection_failed(hostPort);
        BIO_get_ssl(_bio, &_ssl);
        if (!_ssl) throw ssl_no_connection();
        SSL_set_mode(_ssl, SSL_MODE_AUTO_RETRY);
        BIO_set_conn_hostname(_bio, const_cast<char*>(hostPort.c_str()));
        if (BIO_do_connect(_bio)<=0)
          throw tcp_connection_failed(hostPort);
        verify();
        return *this;
      }
      virtual SSL& close() {
        CRYPTOLOG("log");
        TCP::close();
        _ssl = 0; //! @todo is this correct? <--
        return *this;
      }
    protected:
      void verify() {
        CRYPTOLOG("log");
        if (!_ssl) throw ssl_no_connection();
        long res(SSL_get_verify_result(_ssl));
        if (res!=X509_V_OK) throw ssl_verification_failed(res);
      }
    private:
      SSL_CTX *_ctx;
      ::SSL *_ssl;
  };
  
  //@}
  
}

inline std::ostream& operator<<(std::ostream& os, openssl::TCP& is) {
  return os<<is.read();
}

inline std::istream& operator>>(std::istream& is, openssl::TCP& os) {
  std::string s;
  is>>s;
  os<<s;
  return is;
}

//@}

#endif
