/**
    cryptographic routines through crypto++ library, including symmetric cipher handshake

    Copyright (c) 2020-2022 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1

#include "../cryptopp/md5.h"
#include "../cryptopp/ripemd.h"
#include "../cryptopp/sha.h"
#include "../cryptopp/whrlpool.h"
#include "../cryptopp/sha3.h"
#include "../cryptopp/pwdbased.h"
#include "../cryptopp/hmac.h"
#include "../cryptopp/osrng.h"
#include "../cryptopp/eax.h"
#include "../cryptopp/cbcmac.h"
#include "../cryptopp/rsa.h"
#include "../cryptopp/nbtheory.h"
#include "../cryptopp/eccrypto.h"
#include "../cryptopp/oids.h"
#include "../cryptopp/hkdf.h"

#include "../cryptopp/serpent.h"
#include "../cryptopp/rijndael.h"
#include "../cryptopp/twofish.h"
#include "../cryptopp/rc6.h"
#include "../cryptopp/mars.h"
#include "../cryptopp/camellia.h"
#include "../cryptopp/cast.h"
#include "../cryptopp/idea.h"
#include "../cryptopp/des.h"

#include "aria.h"

#include <string.h>
#include <stdlib.h>
#include <assert.h>

#if defined(CRYPTOPP_CXX11) && ! defined(OVERRIDE)
#define OVERRIDE override
#elif ! defined(OVERRIDE)
#define OVERRIDE
#endif

#define RANDOM_KEY_PUBLIC "public"
#define RANDOM_KEY_SESSION "private"

#define RANDOM_HASHES 2  // number of hashes used for random generation
#define RANDOM_CIPHERS 8 // number of ciphers used for random generation

#define RANDOM_COUNT 4  // number of random generators available
#define RANDOM_BLOCKS 5 // maximal value of (keysize + blocksize) / blocksize

#define CRYPT_CIPHERS_RANDOM 8 // number of ciphers in the composite cipher

#if CRYPTOPP_NO_GLOBAL_BYTE
typedef CryptoPP::byte byte;
#endif

template <class CIPHER, int rounds>
void *CRYPT (const byte *key, unsigned length) {
  CIPHER *cipher = new CIPHER;
  if (rounds) {
    CryptoPP::ConstByteArrayParameter iv ((const byte *) NULL, 0);
    CryptoPP::AlgorithmParameters params =
      CryptoPP::MakeParameters (CryptoPP::Name::IV (), iv) (CryptoPP::Name::Rounds (), rounds);
    cipher->SetKey (key, length, params);
  } else
    cipher->SetKeyWithIV (key, length, NULL);
  return cipher;
}

class EAX_Base : public CryptoPP::EAX_Base {
public:
  void Decrypt (byte *output, const byte *input, unsigned length) {
    CryptoPP::EAX_Base::AccessSymmetricCipher ().ProcessData (output, input, length);
  }
  void Authenticate (const byte *input, unsigned length) {
    if (CryptoPP::EAX_Base::m_state == CryptoPP::EAX_Base::State_IVSet) {
      CryptoPP::EAX_Base::AuthenticateLastHeaderBlock ();
      CryptoPP::EAX_Base::m_state = CryptoPP::EAX_Base::State_AuthUntransformed;
    }
    CryptoPP::EAX_Base::AuthenticateData (input, length);
  }
};

template <class CIPHER>
class EAX : public EAX_Base {
  bool IsForwardTransformation () const OVERRIDE { return false; }
  CryptoPP::CMAC_Base &AccessMAC () OVERRIDE { return m_cmac; }
  CryptoPP::CMAC<CIPHER> m_cmac;
};

template <class CIPHER, int rounds>
void *RAW (const byte *key, unsigned length) {
  CIPHER *cipher = new CIPHER;
  if (rounds) {
    cipher->SetKeyWithRounds (key, length, rounds);
  } else
    cipher->SetKey (key, length);
  return (CryptoPP::BlockTransformation *) cipher;
}

#define CRYPT_STRUCT_INFO(NAME, blocksize, keysize, maxkeysize, step)                      \
  struct NAME##_info : public CryptoPP::FixedBlockSize<blocksize>,                         \
                       public CryptoPP::VariableKeyLength<keysize, 16, maxkeysize, step> { \
    static const char *StaticAlgorithmName () { return #NAME; }                            \
  }

CRYPT_STRUCT_INFO (rijndael, 16, 64, 96, 8);
CRYPT_STRUCT_INFO (aria, 16, 16, 32, 8);
CRYPT_STRUCT_INFO (composite, 16, 64, 64, 8);
CRYPT_STRUCT_INFO (whirlpool, 64, 64, 64, 1);
CRYPT_STRUCT_INFO (keccak, 64, 64, 64, 1);

namespace CryptoPP { // key length 96 for AES-256-EEE3, 64 for AES-256-EEE2, 32 for AES-256, 24 for AES-192, 16 for AES-128
  class rijndael : public rijndael_info, public BlockCipherDocumentation {
    template <class Encryption, class Decryption>
    class Triple : public BlockCipherImpl<rijndael_info> {
      Encryption aes1;
      Decryption aes2;
      Encryption aes3;
      bool triple;

    public:
      void UncheckedSetKey (const byte *userKey, unsigned length, const NameValuePairs &params) OVERRIDE {
        if ((triple = length > 32) != false) {
          if (length % 32)
            throw InvalidKeyLength (GetAlgorithm ().AlgorithmName (), length);
          aes2.UncheckedSetKey (userKey + 32, 32, params);
          if (IsForwardTransformation ()) {
            aes1.UncheckedSetKey (userKey, 32, params);
            aes3.UncheckedSetKey (length > 64 ? userKey + 64 : userKey, 32, params);
          } else {
            aes1.UncheckedSetKey (length > 64 ? userKey + 64 : userKey, 32, params);
            aes3.UncheckedSetKey (userKey, 32, params);
          }
        } else
          aes1.SetKey (userKey, length, params);
      }
      void ProcessAndXorBlock (const byte *inBlock, const byte *xorBlock, byte *outBlock) const OVERRIDE {
        if (triple) {
          FixedSizeAlignedSecBlock<byte, BLOCKSIZE * 2> tmp;
          aes1.ProcessAndXorBlock (inBlock, NULL, tmp.data ());
          aes2.ProcessAndXorBlock (tmp.data (), NULL, tmp.data () + BLOCKSIZE);
          aes3.ProcessAndXorBlock (tmp.data () + BLOCKSIZE, xorBlock, outBlock);
        } else
          aes1.ProcessAndXorBlock (inBlock, xorBlock, outBlock);
      }
    };

  public:
    typedef BlockCipherFinal<ENCRYPTION, Triple<Rijndael::Encryption, Rijndael::Encryption> > Encryption;
    typedef BlockCipherFinal<DECRYPTION, Triple<Rijndael::Decryption, Rijndael::Decryption> > Decryption;
  };

  class ARIA : public aria_info, public BlockCipherDocumentation {
    class Base : public BlockCipherImpl<aria_info> {
      FixedSizeAlignedSecBlock<Byte, 16 * 17> key;
      int rounds;

    public:
      void UncheckedSetKey (const byte *userKey, unsigned length, const NameValuePairs &params) OVERRIDE {
        FixedSizeAlignedSecBlock<byte, 32> tmp;
        AssertValidKeyLength (length);
        memcpy (tmp, userKey, length);
        if (IsForwardTransformation ()) {
          rounds = aria_EncKeySetup (tmp.data (), key.data (), length * 8);
        } else
          rounds = aria_DecKeySetup (tmp.data (), key.data (), length * 8);
      }
      void ProcessAndXorBlock (const byte *inBlock, const byte *xorBlock, byte *outBlock) const OVERRIDE {
        typedef BlockGetAndPut<word64, NativeByteOrder> Block;
        word64 words[2];
        Block::Get (inBlock) (words[0]) (words[1]);
        aria_Crypt ((byte *) words, rounds, key.data (), (byte *) words);
        Block::Put (xorBlock, outBlock) (words[0]) (words[1]);
      }
    };

  public:
    typedef BlockCipherFinal<ENCRYPTION, Base> Encryption;
    typedef BlockCipherFinal<DECRYPTION, Base> Decryption;
  };

  class composite : public composite_info, public BlockCipherDocumentation { // random permuntation of 8 ciphers
    class Base : public BlockCipherImpl<composite_info> {
      BlockTransformation *single[CRYPT_CIPHERS_RANDOM];

    public:
      Base () {
        int i;
        for (i = 0; i < CRYPT_CIPHERS_RANDOM; i++)
          single[i] = NULL;
      }
      ~Base () OVERRIDE {
        int i;
        for (i = 0; i < CRYPT_CIPHERS_RANDOM; i++)
          delete single[i];
      }
      void UncheckedSetKey (const byte *userKey, unsigned length, const NameValuePairs &params) OVERRIDE;
      void ProcessAndXorBlock (const byte *inBlock, const byte *xorBlock, byte *outBlock) const OVERRIDE;
    };

  public:
    typedef BlockCipherFinal<ENCRYPTION, Base> Encryption;
    typedef BlockCipherFinal<DECRYPTION, Base> Decryption;
  };

  template <class CIPHER, int keysize> // hash function based on any block cipher
  class HASH : public IteratedHashWithStaticTransform<word32, NativeByteOrder, keysize, CIPHER::BLOCKSIZE,
                                                      HASH<CIPHER, keysize>, 0, true> {
  public:
    static void InitState (word32 *state) {
      memset (state, 0, CIPHER::BLOCKSIZE); // not necessary because state size is always CIPHER::BLOCKSIZE (16)
      memcpy (state, "Merkle-Daamgard", CIPHER::BLOCKSIZE);
    }
    static void Transform (word32 *state, const word32 *data) {
      typename CIPHER::Encryption cipher;
      cipher.SetKey ((const byte *) data, keysize);
      cipher.ProcessAndXorBlock ((byte *) state, (byte *) state, (byte *) state);
    }
  };
} // namespace CryptoPP

template <class CIPHER, int keysize>
CryptoPP::HashTransformation *HASH () {
  return new CryptoPP::HASH<CIPHER, keysize>;
}

template <class HASH>
CryptoPP::HashTransformation *RAWHASH () {
  return new HASH;
}

class Whirlpool : public whirlpool_info, public CryptoPP::BlockCipherDocumentation {
  class Base : public CryptoPP::BlockCipherImpl<whirlpool_info> { // cipher based on HMAC-Whirlpool
    CryptoPP::HMAC<CryptoPP::Whirlpool> hmac;

  public:
    void UncheckedSetKey (const byte *userKey, unsigned length, const CryptoPP::NameValuePairs &params) OVERRIDE {
      hmac.SetKey (userKey, length, params);
    }
    void ProcessAndXorBlock (const byte *inBlock, const byte *xorBlock, byte *outBlock) const OVERRIDE {
      assert (! xorBlock);
      ((CryptoPP::MessageAuthenticationCode *) &hmac)->Update (inBlock, CryptoPP::Whirlpool::DIGESTSIZE);
      ((CryptoPP::MessageAuthenticationCode *) &hmac)->Final (outBlock);
    }
  };

public:
  typedef CryptoPP::BlockCipherFinal<CryptoPP::ENCRYPTION, Base> Encryption;
};

class SHA3_512 : public keccak_info, public CryptoPP::BlockCipherDocumentation {
  class Base : public CryptoPP::BlockCipherImpl<keccak_info> { // cipher based on HMAC-SHA3-512
    CryptoPP::FixedSizeAlignedSecBlock<byte, CryptoPP::SHA3_512::DIGESTSIZE> key;
    CryptoPP::SHA3_512 md;
    int keysize;

  public:
    void UncheckedSetKey (const byte *userKey, unsigned length, const CryptoPP::NameValuePairs &params) OVERRIDE {
      AssertValidKeyLength (length);
      memcpy (key, userKey, keysize = length);
    }
    void ProcessAndXorBlock (const byte *inBlock, const byte *xorBlock, byte *outBlock) const OVERRIDE {
      assert (! xorBlock);
      ((CryptoPP::SHA3_512 *) &md)->Update (key.data (), keysize); // keccak claims that this is its HMAC
      ((CryptoPP::SHA3_512 *) &md)->Update (inBlock, CryptoPP::SHA3_512::DIGESTSIZE);
      ((CryptoPP::SHA3_512 *) &md)->Final (outBlock);
    }
  };

public:
  typedef CryptoPP::BlockCipherFinal<CryptoPP::ENCRYPTION, Base> Encryption;
};

extern "C" {
#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "crypto.h"
#include "sssl.h"
#include "file.h"
#include "socket.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "proxy.h"

#define SIM_MODULE SIM_MODULE_CRYPTO

void *random_public[RANDOM_HASHES + RANDOM_CIPHERS], *random_session[RANDOM_HASHES + RANDOM_CIPHERS];
void *random_private[RANDOM_HASHES + RANDOM_CIPHERS], *random_seeded[RANDOM_HASHES + RANDOM_CIPHERS];
static simtype random_key_strings[RANDOM_COUNT][RANDOM_HASHES + RANDOM_CIPHERS];
static simtype random_file; /* keyed by RANDOM_KEY_xxx, value is a table as returned by random_get_state */
}

static simtype random_init_table; /* keyed by incremental index, value is anything (random data) */
static int random_cipher = 0;

class RNG_ : public CryptoPP::RandomNumberGenerator {
  void **m_generator;
  const char *m_event;
  int m_started;

public:
  simunsigned m_tick;
  simunsigned m_max_time;
  simunsigned m_all_time;

  RNG_ (simrandom generator, const char *event)
    : m_generator (generator)
    , m_event (event)
    , m_started (0)
    , m_tick (system_get_tick ())
    , m_max_time (0)
    , m_all_time (0) {}

  void GenerateBlock (simbyte *output, size_t size) OVERRIDE {
    pth_protect_ ();
    if (m_event)
      event_send_tick (0, m_event);
    if (output) {
      random_get (m_generator, pointer_new_len (output, size));
      m_started = abs (m_started);
    } else {
      if (m_started > 0) {
        simunsigned tick = system_get_tick (), elapsed = tick - m_tick + 1000;
        event_send_value (NULL, SIM_EVENT_KEYGEN, SIM_EVENT_KEYGEN_TIME, number_new (elapsed / 1000));
        m_tick = tick;
        if (elapsed > m_max_time)
          m_max_time = elapsed;
        m_all_time += elapsed / 1000;
      }
      m_started = -1;
    }
    pth_unprotect ();
  }
};

class RSAPrimeSelector : public CryptoPP::PrimeSelector {
  CryptoPP::RandomNumberGenerator &m_rng;
  CryptoPP::Integer m_e;

public:
  RSAPrimeSelector (const CryptoPP::Integer &e, CryptoPP::RandomNumberGenerator &rng)
    : m_rng (rng)
    , m_e (e) {}

  bool IsAcceptable (const CryptoPP::Integer &candidate) const OVERRIDE {
    m_rng.GenerateBlock (NULL, 0);
    return RelativelyPrime (m_e, candidate - CryptoPP::Integer::One ());
  }
};

class RSA : public CryptoPP::InvertibleRSAFunction { // this class allows to use two RNGs
public:
  void GenerateRandom1 (CryptoPP::RandomNumberGenerator &rng, unsigned size, CryptoPP::Integer *prime) {
    RSAPrimeSelector selector (m_e = CryptoPP::Integer (65537), rng);
    CryptoPP::AlgorithmParameters params =
      CryptoPP::MakeParametersForTwoPrimesOfEqualSize (size) (CryptoPP::Name::PointerToPrimeSelector (),
                                                              selector.GetSelectorPointer ());
    prime->GenerateRandom (rng, params);
  }

  simnumber GenerateRandom2 (simrandom generator, unsigned size, simnumber mseconds) {
    RNG_ rng1 (random_private, SIM_EVENT_KEYGEN);
    simnumber tick = rng1.m_tick, estimated = 0;
    LOG_INFO_ ("generate prime time = %lld ms\n", mseconds);
    GenerateRandom1 (rng1, size, &m_p);
    if (mseconds > 0) {
      simnumber elapsed = system_get_tick () - tick;
      LOG_INFO_ ("generate prime1 = %lld ms\n", elapsed);
      if ((estimated = rng1.m_max_time) == 0)
        estimated = elapsed;
      if (estimated <= mseconds) {
        estimated = mseconds;
      } else
        LOG_WARN_ ("generate prime time exceeded by %lld ms\n", estimated - mseconds);
      estimated = (estimated + elapsed - mseconds * 2) / 1000;
      if (estimated) {
        event_send_value (NULL, SIM_EVENT_KEYGEN, SIM_EVENT_KEYGEN_TIME, number_new (PTH_PROTECT_ (estimated)));
        pth_unprotect ();
      }
      tick += elapsed;
    }

    RNG_ rng2 (generator, SIM_EVENT_KEYGEN);
    GenerateRandom1 (rng2, size, &m_q);
    m_d = m_e.InverseMod (CryptoPP::LCM (m_p - 1, m_q - 1));
    m_dp = m_d % (m_p - 1);
    m_dq = m_d % (m_q - 1);
    m_n = m_p * m_q;
    m_u = m_q.InverseMod (m_p);
    if (mseconds)
      LOG_WARN_ ("generate prime2 = %lld ms\n", system_get_tick () - tick);
    return (estimated + rng1.m_all_time + rng2.m_all_time) * 1000;
  }
};

extern "C" {

simtype random_new_ec_ (simrandom generator, simbool anonymous) {
  simtype key;
  CryptoPP::ByteQueue queue;
  RNG_ rng (PTH_UNPROTECT (generator), NULL);
  CryptoPP::ECIES<CryptoPP::ECP>::PrivateKey ec;
  ec.Initialize (rng, anonymous ? CryptoPP::ASN1::secp256r1 () : CryptoPP::ASN1::brainpoolP512r1 ());
  ec.DEREncodePrivateKey (queue);
  key = string_new (PTH_PROTECT_ ((unsigned) queue.CurrentSize ()));
  queue.Get (key.str, key.len);
  return key;
}

simtype random_new_rsa_ (simrandom generator, simnumber *mseconds, int size) {
  simtype key;
  CryptoPP::ByteQueue queue;
  RSA rsa;
  *mseconds = rsa.GenerateRandom2 (generator, PTH_UNPROTECT (size), *mseconds);
  rsa.DEREncodePrivateKey (queue);
  key = string_new (PTH_PROTECT_ ((unsigned) queue.CurrentSize ()));
  queue.Get (key.str, key.len);
  return key;
}

simbool random_test_key_ (simrandom generator, const simtype key, int level, int keyidx) {
  simbool ok;
  CryptoPP::ByteQueue queue;
  RNG_ rng (generator, keyidx == SSL_KEY_RSA ? SIM_EVENT_TICK : NULL);
  queue.Put (key.str, key.len);
  if (PTH_UNPROTECT (keyidx) == SSL_KEY_RSA) {
    CryptoPP::RSA::PublicKey pub;
    CryptoPP::RSA::PrivateKey rsa;
    rsa.BERDecodePrivateKey (queue, false, key.len);
    pub.AssignFrom (rsa);
    ok = rsa.Validate (rng, level) && pub.Validate (rng, level);
  } else {
    CryptoPP::ECIES<CryptoPP::ECP>::PublicKey pub;
    CryptoPP::ECIES<CryptoPP::ECP>::PrivateKey ec;
    ec.AccessGroupParameters ().Initialize (CryptoPP::ASN1::brainpoolP512r1 ());
    ec.BERDecodePrivateKey (queue, true, key.len);
    pub.AssignFrom (ec);
    ok = ec.Validate (rng, level) && pub.Validate (rng, level);
  }
  return PTH_PROTECT_ (ok);
}

#define RANDOM_ALIGN(keysize) ((keysize) & ~31)
#define CRYPT_FUNCTIONS_HASH(hash, keysize) keysize, keysize, RAWHASH<CryptoPP::hash>, RAW<hash::Encryption, 0>
#define CRYPT_FUNCTIONS_NOHASH(cipher, keysize)                 \
  keysize, cipher::BLOCKSIZE, NULL, RAW<cipher::Encryption, 0>, \
    CRYPT<CryptoPP::EAX<cipher>::Encryption, 0>, CRYPT<EAX<cipher>, 0>, RAW<CryptoPP::CBC_MAC<cipher>, 0>
#define CRYPT_FUNCTIONS_RANDOM(cipher, keysize, rounds)                                              \
  keysize, cipher::BLOCKSIZE, HASH<cipher, RANDOM_ALIGN (keysize)>, RAW<cipher::Encryption, rounds>, \
    CRYPT<CryptoPP::EAX<cipher>::Encryption, rounds>, CRYPT<EAX<cipher>, rounds>, RAW<CryptoPP::CBC_MAC<cipher>, rounds>
#define CRYPT_FUNCTIONS_EAX(cipher, keysize, rounds) \
  keysize, cipher::BLOCKSIZE, NULL, NULL,            \
    CRYPT<CryptoPP::EAX<cipher>::Encryption, rounds>, CRYPT<EAX<cipher>, rounds>, RAW<CryptoPP::CBC_MAC<cipher>, rounds>

static const struct crypt_cipher {
  const char *name;                             // name of cipher
  unsigned keysize;                             // used key size of cipher
  unsigned blocksize;                           // block size of cipher
  CryptoPP::HashTransformation *(*hash) ();     // constructor for using cipher as a one-way hash function
  void *(*crypt) (const simbyte *, unsigned);   // constructor for encryption in ECB-mode
  void *(*encrypt) (const simbyte *, unsigned); // constructor for encryption (EAX-mode)
  void *(*decrypt) (const simbyte *, unsigned); // constructor for decryption (EAX-mode)
  void *(*mac) (const simbyte *, unsigned);     // constructor for authentication using CBC-MAC with the cipher
  simnumber ident;                              // hash tag of zero-filled 128-bit block encrypted with a key derived from zero
} crypt_ciphers[] = {                           // do not change the order of the first eleven ciphers; it is used implicitly
  { "composite", CRYPT_FUNCTIONS_RANDOM (CryptoPP::composite, 64, 0), 4126266547136751569LL },
  { "whirlpool", CRYPT_FUNCTIONS_HASH (Whirlpool, 64), NULL, NULL, NULL, 0LL },
  { "keccak", CRYPT_FUNCTIONS_HASH (SHA3_512, 64), NULL, NULL, NULL, 0LL },
  { "rijndael", CRYPT_FUNCTIONS_NOHASH (CryptoPP::rijndael, 96), 4301513208221181460LL },
  { "serpent", CRYPT_FUNCTIONS_RANDOM (CryptoPP::Serpent, 32, 0), 4082581851629751081LL },
  { "twofish", CRYPT_FUNCTIONS_RANDOM (CryptoPP::Twofish, 32, 0), 3721097057867275516LL },
  { "rc6", CRYPT_FUNCTIONS_RANDOM (CryptoPP::RC6, 64, 40), 1555212550610553737LL },
  { "mars", CRYPT_FUNCTIONS_RANDOM (CryptoPP::MARS, 56, 0), 4116731343812436362LL },
  { "camellia", CRYPT_FUNCTIONS_RANDOM (CryptoPP::Camellia, 32, 0), 283065679927518280LL },
  { "aria", CRYPT_FUNCTIONS_RANDOM (CryptoPP::ARIA, 32, 0), 4460286464693948505LL },
  { "cast", CRYPT_FUNCTIONS_RANDOM (CryptoPP::CAST256, 32, 0), 601869149578493698LL },
  // new ciphers can be added after this line (but should only be added at the end of the table to avoid subtle problems)
  { "idea", CRYPT_FUNCTIONS_EAX (CryptoPP::IDEA, 16, 16), 4323233754760644375LL },
  { "des", CRYPT_FUNCTIONS_EAX (CryptoPP::DES_EDE3, 24, 0), 1818468541551533301LL }
};

#define CRYPT_LOOKUP_CIPHER(cipher) (&crypt_ciphers[(cipher) ? (cipher) + RANDOM_HASHES : 0])
#define CRYPT_CIPHERS (int) (SIM_ARRAY_SIZE (crypt_ciphers) - RANDOM_HASHES - 1)

static simbool crypt_preferred_flags[CRYPT_CIPHERS + 1];

void random_close (simrandom generator) {
  int i, j = RANDOM_CASE_INDEX (generator);
  if (generator == random_public && random_init_table.ptr)
    table_free (random_init_table), random_init_table = nil ();
  for (i = 0; i < RANDOM_HASHES + RANDOM_CIPHERS; i++) {
    if (random_key_strings[j][i].len)
      string_free (random_key_strings[j][i]);
    random_key_strings[j][i] = nil ();
    delete (CryptoPP::X917RNG *) generator[i], generator[i] = NULL;
  }
}

#define RANDOM_LOOKUP_CIPHER(cipher) (&crypt_ciphers[(cipher) == RANDOM_HASHES ? 0 : (cipher) + 1])
#define RANDOM_CRYPT_CIPHER(cipher, key) RANDOM_LOOKUP_CIPHER (cipher)->crypt ((key).str, (key).len)

static simtype sim_random_load (simbyte *seed, unsigned length, const simtype saved, int cipher) {
  simtype key, copy;
  const struct crypt_cipher *entry = RANDOM_LOOKUP_CIPHER (cipher);
  if (saved.typ == SIMNIL || (key = table_get_string (saved, entry->name)).typ == SIMNIL)
    return string_copy_len (seed, length);
  if ((int) (key.len -= entry->blocksize) <= 0)
    return string_copy_len (seed, length); // no key: impossible
  copy = string_new (key.len < length ? length : key.len);
  memset (copy.str, 0, copy.len);
  memcpy (copy.str, key.str + entry->blocksize, key.len); // copy saved key
  while (length--)                                        // merge supplied key into saved key
    copy.str[length] ^= seed[length];
  for (length = 0; length < entry->blocksize; length++) // merge saved seed into supplied seed
    seed[(RANDOM_BLOCKS - 1) * RANDOM_SIZE_BLOCK + length] ^= key.str[length];
  return copy;
}

void random_open (simrandom generator, const simtype saved, const simbyte *entropy, unsigned length) {
  unsigned i, j, keyblocks[RANDOM_HASHES + RANDOM_CIPHERS];
  simbyte zero[RANDOM_SIZE_HASH], *z = entropy && generator != random_seeded ? NULL : zero;
  simbyte seed[RANDOM_BLOCKS * RANDOM_SIZE_BLOCK + RANDOM_SIZE_HASH - RANDOM_SIZE_BLOCK];
  CryptoPP::HashTransformation *hashers[RANDOM_BLOCKS][RANDOM_HASHES + RANDOM_CIPHERS];
  LOG_INFO_ ("random open %d: %u bytes (%u saved)\n", RANDOM_CASE_INDEX (generator),
             length, saved.typ == SIMNIL ? 0 : table_size (saved, 0));
  for (j = 0; j < RANDOM_BLOCKS; j++)
    for (i = 0; i < RANDOM_HASHES + RANDOM_CIPHERS; i++)
      hashers[j][i] = RANDOM_LOOKUP_CIPHER (i)->hash ();
  memset (keyblocks, 0, sizeof (keyblocks));
  memset (zero, 0, sizeof (zero));
  while (length)
    for (j = 0; j < RANDOM_BLOCKS; j++)
      for (i = 0; i < RANDOM_HASHES + RANDOM_CIPHERS; i++) {
        unsigned keysize = RANDOM_ALIGN (RANDOM_LOOKUP_CIPHER (i)->keysize); /* use only 32 bytes for MARS although key size is 56 */
        if (j == RANDOM_BLOCKS - 1 || j < keysize / RANDOM_LOOKUP_CIPHER (i)->blocksize) {
          unsigned len = length;
          if (len > keysize)
            len = keysize;
          hashers[j][i]->Update (entropy, len);
          if (length && j < RANDOM_BLOCKS - 1 && keyblocks[i] <= j)
            keyblocks[i] = j + 1;
          entropy += len;
          length -= len;
        }
      }
  random_close (generator);
  for (i = 0; i < RANDOM_HASHES + RANDOM_CIPHERS; i++) {
    const simbyte *s = seed + (RANDOM_BLOCKS - 1) * RANDOM_SIZE_BLOCK;
    simtype key;
    for (j = 0; j < RANDOM_BLOCKS; j++) {
      if (i >= RANDOM_HASHES || ! j || j == RANDOM_BLOCKS - 1)
        hashers[j][i]->Final (seed + j * RANDOM_SIZE_BLOCK);
      delete hashers[j][i];
    }
    key = sim_random_load (seed, keyblocks[i] * RANDOM_LOOKUP_CIPHER (i)->blocksize, saved, i);
    random_key_strings[RANDOM_CASE_INDEX (generator)][i] = key;
    /*LOG_XTRA_SIMTYPES_ (pointer_new_len (s, RANDOM_LOOKUP_CIPHER (i)->blocksize), pointer_new_len (key.str, key.len),
                          LOG_BIT_BIN, "random %s ", RANDOM_LOOKUP_CIPHER (i)->name);*/
    generator[i] = new CryptoPP::X917RNG ((CryptoPP::BlockTransformation *) RANDOM_CRYPT_CIPHER (i, key), s, z);
  }
  memset (seed, 0, sizeof (seed));
  memset (keyblocks, 0, sizeof (keyblocks));
}

int random_set_cipher (const char *cipher) {
  int err = SIM_OK, i = crypt_cipher_find (cipher, -RANDOM_HASHES, NULL);
  if (i < -RANDOM_HASHES || i == 1 || i > RANDOM_CIPHERS) {
    if (! cipher || *cipher) {
      LOG_ERROR_ ("unknown random cipher: %s\n", cipher);
      err = SIM_CRYPT_BAD_CIPHER;
    }
    i = 0;
  } else if (! i)
    i = 1;
  random_cipher = i <= 0 ? -i : i + RANDOM_HASHES;
  return err;
}

const char *random_get_cipher (void) {
  return random_cipher ? crypt_ciphers[random_cipher == RANDOM_HASHES + 1 ? 0 : random_cipher].name : "";
}

void random_get (simrandom generator, simtype output) {
  int i = ! random_cipher || generator != random_public ? 0 : random_cipher - 1;
  CryptoPP::X917RNG **rng = (CryptoPP::X917RNG **) (generator ? generator : random_public);
  CryptoPP::ArraySink sink0 (output.str, output.len);
  rng[i]->GenerateIntoBufferedTransformation (sink0, CryptoPP::DEFAULT_CHANNEL, output.len);
  if (! random_cipher || generator != random_public) {
    simbyte *tmp = (simbyte *) sim_new (output.len);
    for (i = 1; i < RANDOM_HASHES + RANDOM_CIPHERS; i++) {
      CryptoPP::ArraySink sink (tmp, output.len);
      rng[i]->GenerateIntoBufferedTransformation (sink, CryptoPP::DEFAULT_CHANNEL, output.len);
      CryptoPP::xorbuf (output.str, tmp, output.len);
    }
    sim_free (tmp, output.len);
  }
}

simunsigned random_get_number (simrandom generator, simunsigned max) {
  simunsigned num;
  unsigned bits = 0;
  for (num = max; num; num >>= 1)
    bits++;
  do {
    random_get (generator, pointer_new_len (&num, sizeof (num)));
    if (bits < 64)
      num &= ((simunsigned) 1 << bits) - 1;
  } while (num > max);
  return num;
}

simtype random_get_state (simrandom generator) {
  simtype seeds = nil ();
  if (*generator) {
    int i, j = RANDOM_CASE_INDEX (generator);
    seeds = table_new (2);
    for (i = 0; i < RANDOM_HASHES + RANDOM_CIPHERS; i++) {
      const struct crypt_cipher *entry = RANDOM_LOOKUP_CIPHER (i);
      simtype seed = string_new (entry->blocksize + random_key_strings[j][i].len);
      CryptoPP::ArraySink sink (seed.str, entry->blocksize);
      CryptoPP::X917RNG **rng = (CryptoPP::X917RNG **) generator;
      rng[i]->GenerateIntoBufferedTransformation (sink, CryptoPP::DEFAULT_CHANNEL, entry->blocksize);
      memcpy (seed.str + entry->blocksize, random_key_strings[j][i].str, random_key_strings[j][i].len);
      table_add (seeds, entry->name, seed);
    }
  }
  return seeds;
}

int sim_random_get (simbyte *output, int length) {
  int err = SIM_CRYPT_NO_RANDOM;
  try {
    CryptoPP::NonblockingRng sys;
    sys.GenerateBlock (output, length);
    err = SIM_OK;
  } catch (CryptoPP::OS_RNG_Err &) {}
  return err;
}

int random_save (void) {
  int err = SIM_OK;
  simtype saved;
  if (random_file.len && (saved = random_get_state (random_public)).typ != SIMNIL) {
    table_set (random_file, RANDOM_KEY_PUBLIC, saved);
    if ((saved = random_get_state (random_session)).typ != SIMNIL) {
      table_set (random_file, RANDOM_KEY_SESSION, saved);
      err = file_save (random_file, FILE_RANDOM, FILE_TYPE_ENCRYPTED | FILE_TYPE_TEMPORARY);
    }
  }
  return event_test_error (NULL, SIM_EVENT_ERROR_FILE_SAVE, err);
}

static simbool sim_random_callback_write (void *context, simtype input) {
  simbyte **buf = (simbyte **) context;
  memcpy (*buf, input.str, input.len);
  *buf += input.len;
  return true;
}

void random_init_entropy (const simtype value) {
  unsigned count;
  if (! random_init_table.ptr)
    random_init_table = table_new (127);
  count = table_count (random_init_table);
  table_add_key (random_init_table, string_copy_len (&count, sizeof (count)), type_copy_strings (value));
}

int random_init (simrandom generator) {
  int err = SIM_OK;
  simbyte *buf;
  unsigned size;
  simtype entropy, saved = nil ();
  simbyte buffer[8192];
  if (random_file.len)
    table_free (random_file), random_file = nil ();
  if (generator == random_private)
    return SIM_OK;
  if (generator == random_session) {
    if ((err = file_load (FILE_RANDOM, FILE_TYPE_ENCRYPTED | FILE_TYPE_TEMPORARY, &random_file)) != SIM_OK)
      return err;
    saved = table_get_table (random_file, RANDOM_KEY_PUBLIC);
  }
  system_init_random ();
  if ((size = table_size (random_init_table, 1)) < RANDOM_SIZE_ENTROPY) {
    random_init_entropy (pointer_new_len (buffer, sizeof (buffer)));
    size = table_size (random_init_table, 1);
  }
  buf = (entropy = string_buffer_new (size)).str;
  table_write (random_init_table, sim_random_callback_write, &buf, 1);
  random_open (random_public, saved, entropy.str, entropy.len);
  string_buffer_free (entropy);
  if (generator == random_session) {
    const int SIZE = RANDOM_SIZE_BLOCK * RANDOM_CIPHERS + RANDOM_SIZE_HASH * RANDOM_HASHES;
    random_get (NULL, pointer_new_len (buffer, SIZE));
    if ((err = sim_random_get (buffer + SIZE, sizeof (buffer) - SIZE)) == SIM_OK)
      random_open (random_session, table_get_table (random_file, RANDOM_KEY_SESSION), buffer, sizeof (buffer));
    memset (buffer, 0, sizeof (buffer));
  }
  return err;
}

static int sim_random_permute (int idx) {
  static const int crypt_cipher_divisors[] = {
    7 * 6 * 5 * 4 * 3 * 2, 6 * 5 * 4 * 3 * 2, 5 * 4 * 3 * 2, 4 * 3 * 2, 3 * 2, 2, 1, 1
  };
  int n, val = 0, list = 16434824, pos;
  for (n = 0; n < 8; n++) {
    val = val * 8 + (list >> (pos = idx / crypt_cipher_divisors[n] * 3) & 7);
    list = (list & ~((8 << pos) - 1)) >> 3 | (list & ((1 << pos) - 1));
    idx %= crypt_cipher_divisors[n];
  }
  return val;
}

void CryptoPP::composite::Base::UncheckedSetKey (const simbyte *userKey, unsigned length,
                                                 const CryptoPP::NameValuePairs &params) {
  unsigned i, permutation;
  simunsigned permutationrand = 0;
  CryptoPP::SHA256 sha;
  FixedSizeAlignedSecBlock<simbyte, CryptoPP::SHA256::BLOCKSIZE> hash;
  AssertValidKeyLength (length);
  sha.Update (userKey, length);
  sha.Final (hash.data ());
  for (i = 0; i < sizeof (permutationrand); i++)
    permutationrand = permutationrand << 8 | hash.data ()[i];
  permutation = sim_random_permute ((int) (permutationrand % 40320));
  for (i = 0; i < CRYPT_CIPHERS_RANDOM; i++) {
    int shift = (IsForwardTransformation () ? i : CRYPT_CIPHERS_RANDOM - 1 - i) * 3; // CRYPT_CIPHERS_RANDOM == 1 << 3
    int j = (permutation >> shift) % CRYPT_CIPHERS_RANDOM + RANDOM_HASHES + 1;
    unsigned keysize = j > RANDOM_HASHES + 1 ? crypt_ciphers[j].keysize : /* rijndael */ 32;
    unsigned len = keysize < length ? keysize : length;
    delete single[i];
    assert (IsForwardTransformation ());
    single[i] = (BlockTransformation *) crypt_ciphers[j].crypt (userKey, len);
  }
}

void CryptoPP::composite::Base::ProcessAndXorBlock (const simbyte *inBlock, const simbyte *xorBlock,
                                                    simbyte *outBlock) const {
  int i = 1;
  FixedSizeAlignedSecBlock<simbyte, BLOCKSIZE * 2> tmp;
  single[0]->ProcessAndXorBlock (inBlock, NULL, tmp.data ());
  while (i < CRYPT_CIPHERS_RANDOM - 1) {
    single[i++]->ProcessAndXorBlock (tmp.data (), NULL, tmp.data () + BLOCKSIZE);
    single[i++]->ProcessAndXorBlock (tmp.data () + BLOCKSIZE, NULL, tmp.data ());
  }
  single[i]->ProcessAndXorBlock (tmp.data (), xorBlock, outBlock);
}

void *sim_crypt_md_new (int md) {
  switch (md) {
    case CRYPT_MD_RIPEMD:
      return (CryptoPP::HashTransformation *) new CryptoPP::RIPEMD160;
    case CRYPT_MD_SHA:
      return (CryptoPP::HashTransformation *) new CryptoPP::SHA256;
    case CRYPT_MD_SHA2:
      return (CryptoPP::HashTransformation *) new CryptoPP::SHA512;
    case CRYPT_MD_MD5:
      return (CryptoPP::HashTransformation *) new CryptoPP::Weak::MD5;
    case CRYPT_MD_WHIRLPOOL:
      return (CryptoPP::HashTransformation *) new CryptoPP::Whirlpool;
  }
  return NULL;
}

void sim_crypt_md_update (void *md, const simbyte *input, unsigned length) {
  ((CryptoPP::HashTransformation *) md)->Update (input, length);
}

simtype sim_crypt_md_free (void *md) {
  if (md) {
    simtype hash = string_new (((CryptoPP::HashTransformation *) md)->DigestSize ());
    ((CryptoPP::HashTransformation *) md)->Final (hash.str);
    delete (CryptoPP::HashTransformation *) md;
    return hash;
  }
  return nil ();
}

void *sim_crypt_md_copy (void *md) {
  return ((CryptoPP::HashTransformation *) md)->Clone ();
}

simtype sim_crypt_md_hash (void *md, const simtype ec, const simtype rsa, const simtype salt) {
  sim_crypt_md_update (md, ec.str, ec.len);
  sim_crypt_md_update (md, rsa.str, rsa.len);
  sim_crypt_md_update (md, salt.str, salt.len);
  return sim_crypt_md_free (md);
}

simtype sim_crypt_md_hash_address (void *md1, void *md2, const simtype ec, const simtype rsa) {
  simtype md = sim_crypt_md_hash (md1, ec, rsa, nil ()), hash = sim_crypt_md_hash (md2, md, nil (), nil ());
  string_free (md);
  return hash;
}

void *sim_crypt_new (int cipher, const simtype key, simbool encrypt) {
  const struct crypt_cipher *entry;
  unsigned len = key.len;
  if (cipher < 0 || cipher > CRYPT_CIPHERS)
    return NULL; // impossible
  if ((entry = CRYPT_LOOKUP_CIPHER (cipher))->keysize <= len) {
    len = entry->keysize;
  } else if (len < 64)
    return NULL; // any cipher should be able to work with a 512-bit key
  //LOG_XTRA_SIMTYPE_ (key, LOG_BIT_BIN, "%s %s key ", entry->name, encrypt ? "encrypt" : "decrypt");
  return encrypt ? entry->encrypt (key.str, len) : entry->decrypt (key.str, len);
}

void sim_crypt_encrypt (void *crypt, const simtype input, simbyte *output) {
  ((CryptoPP::EAX_Base *) crypt)->ProcessData (output, input.str, input.len);
}

void sim_crypt_decrypt (void *crypt, const simtype input, simbyte *output) {
  ((EAX_Base *) crypt)->Decrypt (output, input.str, input.len);
}

void sim_crypt_free (void *crypt, simbool encrypt) {
  delete (encrypt ? (CryptoPP::EAX_Base *) crypt : (EAX_Base *) crypt);
}

void sim_crypt_auth_encrypt (void *crypt, simtype buffer, const simtype iv, simnumber sequence, simbyte *digest) {
  sim_crypt_auth_start (crypt, buffer.len, iv.str, iv.len, sequence);
  sim_crypt_encrypt (crypt, buffer, buffer.str);
  sim_crypt_auth_stop (crypt, digest);
}

unsigned sim_crypt_auth_size (const void *crypt) {
  return ((const CryptoPP::EAX_Base *) crypt)->TagSize ();
}

void sim_crypt_auth_start (void *crypt, unsigned length, const simbyte *iv, unsigned ivlen, simnumber sequence) {
  unsigned i;
  CryptoPP::EAX_Base *tmp = (CryptoPP::EAX_Base *) crypt;
  unsigned maxlen = tmp->MaxIVLength ();
  simbyte buf[sizeof (sequence)];
  tmp->Resynchronize (iv, maxlen > 0 && ivlen > maxlen ? maxlen : ivlen);
  //tmp->SpecifyDataLengths (sizeof (buf), length, 0); // not needed for EAX mode
  for (i = 0; i < sizeof (sequence); i++) {
    buf[i] = (simbyte) sequence;
    sequence = (simunsigned) sequence >> 8;
  }
  tmp->Update (buf, sizeof (buf));
}

void sim_crypt_auth_update (void *crypt, const simbyte *input, unsigned length) {
  ((EAX_Base *) crypt)->Authenticate (input, length);
}

void sim_crypt_auth_stop (void *crypt, simbyte *digest) {
  ((CryptoPP::EAX_Base *) crypt)->Final (digest);
}

simbool sim_crypt_auth_verify (void *crypt, const simbyte *digest) {
  return ((CryptoPP::EAX_Base *) crypt)->Verify (digest);
}

simbool sim_crypt_auth_check (void *crypt, const simtype input, const simtype iv,
                              simnumber sequence, const simbyte *digest) {
  sim_crypt_auth_start (crypt, input.len, iv.str, iv.len, sequence);
  sim_crypt_auth_update (crypt, input.str, input.len);
  return sim_crypt_auth_verify (crypt, digest);
}

int sim_crypt_get_version (void) {
  return CRYPTOPP_VERSION;
}

static simtype crypt_get_ciphers (simbool preferred) {
  int i;
  simtype array = array_new_numbers (CRYPT_CIPHERS + 1);
  array.len = 0;
  for (i = ! preferred; i <= CRYPT_CIPHERS; i++)
    if (crypt_preferred_flags[i] == preferred)
      array.arr[++array.len] = number_new (CRYPT_LOOKUP_CIPHER (i)->ident);
  return array;
}

int crypt_set_ciphers (const simtype list, simbool force) {
  unsigned j;
  int err = SIM_OK, i;
  for (j = 0; j <= RANDOM_HASHES + CRYPT_CIPHERS; j++)
    if (crypt_ciphers[j].blocksize > 64)
      return SIM_CRYPT_BAD_CIPHER; // sanity check
  memset (crypt_preferred_flags, false, sizeof (crypt_preferred_flags));
  for (j = 1; j <= list.len; j++) {
    char *cipher = (char *) list.arr[j].str;
    if ((i = crypt_cipher_find (cipher, 0, NULL)) < 0) {
      LOG_ERROR_ ("unknown cipher: %s\n", cipher);
      if (! force)
        err = SIM_CRYPT_BAD_CIPHER;
    } else if (! crypt_preferred_flags[i]) {
      crypt_preferred_flags[i] = true;
    } else
      LOG_ERROR_ ("duplicate cipher: %s\n", cipher);
  }
  for (i = 0; i <= CRYPT_CIPHERS; i++)
    if (crypt_preferred_flags[i])
      return err;
  LOG_WARN_ ("no preferred ciphers\n");
  return SIM_CRYPT_NO_CIPHERS;
}

const char *sim_crypt_cipher_get (int cipher, unsigned *keysize, simnumber *identifier) {
  const struct crypt_cipher *entry;
  if (cipher < -RANDOM_HASHES || cipher > CRYPT_CIPHERS)
    return NULL;
  entry = cipher <= 0 ? &crypt_ciphers[-cipher] : CRYPT_LOOKUP_CIPHER (cipher);
  if (keysize)
    *keysize = entry->keysize;
  if (identifier)
    *identifier = entry->ident;
  return entry->name;
}

int crypt_cipher_find (const char *cipher, int idx, simbool *prefer) {
  int i = idx - 1;
  if (cipher) {
    const char *tmp;
    simtype copy = sim_convert_string_to_locase (cipher);
    while ((tmp = sim_crypt_cipher_get (++i, NULL, NULL)) != NULL && strcmp ((char *) copy.str, tmp)) {}
    string_free (copy);
    if (! tmp) {
      i = idx - 1;
    } else if (prefer)
      *prefer = i >= 0 ? crypt_preferred_flags[i] : false;
  }
  return i;
}

static int crypt_cipher_size_max_param (const char *param) {
  unsigned j, maxsize = param ? param_get_number (param) : 0;
  if (! maxsize) {
    maxsize = crypt_ciphers[0].blocksize;
    for (j = RANDOM_HASHES + 1; j <= RANDOM_HASHES + CRYPT_CIPHERS; j++)
      if (crypt_ciphers[j].blocksize > maxsize)
        maxsize = crypt_ciphers[j].blocksize;
  }
  return maxsize;
}

int crypt_cipher_size_max (const char *param) {
  int maxsize = crypt_cipher_size_max_param (NULL), tagsize = param ? param_get_number (param) : 0;
  return maxsize > tagsize ? maxsize : tagsize;
}

static int sim_crypt_cipher_find (const simtype supported, simnumber identifier) {
  unsigned i, j = 0;
  for (i = 1; i <= supported.len; i++)
    if (supported.arr[i].num == identifier)
      j = i;
  return j;
}

static int crypt_cipher_get_random (const simtype supported, simbool preferred) {
  int j = -1;
  unsigned i, k = 0;
  simbool common[CRYPT_CIPHERS + 1];
  for (i = 1; i <= CRYPT_CIPHERS; i++) {
    const struct crypt_cipher *entry = CRYPT_LOOKUP_CIPHER (i);
    k += common[i] = sim_crypt_cipher_find (supported, entry->ident) && (! preferred || crypt_preferred_flags[i]);
  }
  if (k)
    k = (unsigned) (random_get_number (random_session, k - 1));
  for (i = 1; i <= CRYPT_CIPHERS; i++)
    if (common[i] && ! k--)
      j = i;
  memset (common, 0, sizeof (common));
  return j;
}

simtype crypt_cipher_encrypt (int cipher, const struct _ssl_master *master, int counter) {
  simbool ok = false;
  int i = -1;
  simtype iv = string_buffer_new (crypt_cipher_size_max (counter ? "crypto.tagsize" : "file.tagsize") * 3);
  simtype ivtmp, tmp = string_buffer_new (iv.len), buf;
  random_get (random_public, buf = string_new (iv.len));
  memcpy (tmp.str, buf.str, buf.len);
  while (sim_crypt_cipher_get (++i, NULL, NULL)) {
    void *crypt = counter ? sim_crypt_open (i, master, counter, true) : sim_crypt_new (i, master->key, true);
    random_get (random_public, ivtmp = pointer_new_len (iv.str, CRYPT_LOOKUP_CIPHER (i)->blocksize));
    sim_crypt_auth_encrypt (crypt, pointer_new_len (tmp.str, ivtmp.len), ivtmp, 0, iv.str);
    sim_crypt_auth_encrypt (crypt, pointer_new_len (&tmp.str[ivtmp.len], tmp.len - ivtmp.len * 2),
                            pointer_new_len (tmp.str, ivtmp.len), 0, &tmp.str[tmp.len - ivtmp.len]);
    sim_crypt_free (crypt, true);
    if (cipher == i) {
      memcpy (buf.str, tmp.str, tmp.len);
      ok = true;
    } else
      memcpy (tmp.str, buf.str, buf.len);
  }
  string_buffer_free (iv);
  string_buffer_free (tmp);
  if (ok)
    return buf;
  string_free (buf);
  return nil ();
}

int sim_crypt_cipher_decrypt (const simtype input, const struct _ssl_master *master, int counter) {
  int i = -1, j = -1;
  while (sim_crypt_cipher_get (++i, NULL, NULL)) {
    unsigned blocksize = CRYPT_LOOKUP_CIPHER (i)->blocksize;
    void *crypt = counter ? sim_crypt_open (i, master, counter, false) : sim_crypt_new (i, master->key, false);
    if (input.len >= blocksize)
      if (sim_crypt_auth_check (crypt, pointer_new_len (&input.str[blocksize], input.len - blocksize * 2),
                                pointer_new_len (input.str, blocksize), 0, &input.str[input.len - blocksize]))
        j = i;
    sim_crypt_free (crypt, false);
  }
  return j;
}

simtype sim_crypt_convert_password (const char *password, const simtype salt) {
  simtype derived = string_new (CryptoPP::Whirlpool::DIGESTSIZE);
  CryptoPP::PKCS5_PBKDF2_HMAC<CryptoPP::Whirlpool> deriver;
  const int n = 12345;
  deriver.DeriveKey (derived.str, derived.len, 0, (const simbyte *) password, strlen (password), salt.str, salt.len, n);
  return derived;
}

static unsigned sim_crypt_xor (simbyte *buffer, const simbyte *input, unsigned length) {
  unsigned i;
  for (i = 0; i < length; i++)
    buffer[i] ^= input[i];
  return length;
}

void *sim_crypt_open (int cipher, const struct _ssl_master *master, int counter, simbool encrypt) {
  void *crypt = NULL;
  simbyte key[CryptoPP::Whirlpool::DIGESTSIZE * 64], derived[CryptoPP::Whirlpool::DIGESTSIZE * 2];
  if (master->key.len >= sizeof (derived) / 4 * (counter > 1 ? counter + 1 : 0)) {
    const struct crypt_cipher *entry = CRYPT_LOOKUP_CIPHER (cipher);
    unsigned keysize = entry->keysize > 32 ? 32 : entry->keysize;
    CryptoPP::MessageAuthenticationCode *mac =
      (CryptoPP::MessageAuthenticationCode *) entry->mac (master->premaster, keysize);
    CryptoPP::HKDF<CryptoPP::Whirlpool> hkdf;
    simtype digest = string_buffer_new (entry->blocksize);
    memset (derived, 0, sizeof (derived));
    if (counter <= 1) {
      if (string_check_diff (master->to, PROXY_ADDRESS_CONTROL)) {
        if (string_check_diff (master->to, PROXY_ADDRESS_TRAVERSER)) {
          simtype addr = sim_contact_convert_address ((char *) master->to.str, 'S');
          assert (addr.len);
          sim_crypt_xor (derived, addr.str, addr.len);
          string_free (addr);
        } else
          *derived ^= 1;
      }
    } else
      sim_crypt_xor (derived, master->key.str + (counter - 2) * sizeof (derived) / 4, sizeof (derived) * 3 / 4);
    mac->Update (master->random, sizeof (master->random));
    mac->Final (digest.str);
    sim_crypt_xor (derived, digest.str, digest.len < sizeof (derived) ? digest.len : sizeof (derived));
    string_buffer_free (digest);
    hkdf.DeriveKey (key, sizeof (key), master->premaster + keysize, sizeof (master->premaster) / 2 - keysize,
                    master->random, sizeof (master->random), NULL, 0);
    sim_crypt_xor (derived, key + counter * sizeof (derived) / 2, sizeof (derived) * 3 / 4);
    memset (key, 0, sizeof (key));
    crypt = sim_crypt_new (cipher, pointer_new_len (derived, sizeof (derived)), encrypt);
    memset (derived, 0, sizeof (derived));
    delete mac;
  }
  return crypt;
}

void crypt_open_socket (simsocket sock, int decrypt, int encrypt, simnumber decryptsize, int encryptsize,
                        simbool server, simbool proxy) {
  const struct crypt_cipher *encryptentry = CRYPT_LOOKUP_CIPHER (encrypt);
  const struct crypt_cipher *decryptentry = CRYPT_LOOKUP_CIPHER (decrypt);
  int encryptcount = ! encryptsize ? 0 : ! server || encrypt == decrypt ? 2 : 2 + (encryptentry->keysize + 31) / 32;
  int decryptcount = ! encryptsize ? 0 : server || encrypt == decrypt ? 2 : 2 + (decryptentry->keysize + 31) / 32;
  LOG_DEBUG_ ("deriving $%d %s%d:%s%d '%s'\n", sock->fd, decryptentry->name, encryptcount,
              encryptentry->name, decryptcount, CONTACT_GET_NICK ((char *) sock->master->to.str));
  assert (! sock->crypt.decrypt && ! sock->crypt.encrypt && sock->crypt.ivs.typ == SIMNIL);
  sock->crypt.decrypt = sim_crypt_open (decrypt, sock->master, encryptcount, false);
  sock->crypt.encrypt = sim_crypt_open (encrypt, sock->master, decryptcount, true);
  sock->crypt.padding = param_get_number ("socket.padding");
  sock->crypt.maxsize = param_get_number ("socket.packet") - SOCKET_OVERHEAD_SSL;
  sock->crypt.decryptsize = decryptsize < 0 ? 0 : decryptsize > 256 ? 256 : (unsigned) decryptsize;
  sock->crypt.encryptsize = encryptsize;
  sock->crypt.decryptseq = server ? (simnumber) 1 << 62 : 0;
  sock->crypt.encryptseq = server ? 0 : (simnumber) 1 << 62;
  sock->crypt.rcvcipher = decryptentry->name;
  sock->crypt.sndcipher = encryptentry->name;
  if (! param_get_number (proxy ? "proxy.ivs" : "socket.ivs")) {
    int blocksize = sim_crypt_auth_size (sock->crypt.encrypt);
    sock->crypt.ivs = string_buffer_new (blocksize + (blocksize < encryptsize ? encryptsize : blocksize));
    random_get (random_public, sock->crypt.ivs);
  }
}

void crypt_close_socket (simsocket sock, simbool proxy) {
  struct _socket_crypt *crypto = proxy ? &sock->proxy : &sock->crypt;
  sim_crypt_free (crypto->encrypt, true), crypto->encrypt = NULL;
  sim_crypt_free (crypto->decrypt, false), crypto->decrypt = NULL;
  string_buffer_free (crypto->ivs), crypto->ivs = nil ();
}

static int crypt_rsa_handshake_ (simsocket sock) {
  int err = SIM_CRYPT_BAD_RSA_KEY;
  unsigned size = ssl_rsa_size_ (nil ());
  unsigned len = sock->master->pub[SSL_KEY_RSA].typ != SIMNIL ? ssl_rsa_size_ (sock->master->pub[SSL_KEY_RSA]) : 0;
  simbyte key[(SSL_RSA_BITS_MAX + 7) / 8 + 1];
  simtype encrypted = pointer_new_len (key, len), table;
  if (len < 96 * 2 || len >= sizeof (key) || size < 96 * 2 || size >= sizeof (key))
    return err;
  random_get (random_session, encrypted);
  if ((encrypted = ssl_rsa_crypt_public_ (encrypted, sock->master->pub[SSL_KEY_RSA], nil ())).typ != SIMNIL) {
    table_add (table = table_new (1), SIM_HANDSHAKE_TYPE_RSA, encrypted);
    if ((err = socket_send_table_ (sock, table, SOCKET_SEND_TCP, NULL)) == SIM_OK) {
      simtype received, proto = table_new_const (1, SIM_NEW_STRING (SIM_HANDSHAKE_TYPE_RSA), NULL);
      if ((err = socket_recv_table_ (sock, proto, nil (), &received, NULL)) == SIM_OK) {
        simtype decrypted = table_get_string (received, SIM_HANDSHAKE_TYPE_RSA);
        if (size == decrypted.len && (decrypted = ssl_rsa_crypt_private_ (decrypted, true)).typ != SIMNIL) {
          unsigned l = sim_crypt_xor (key, decrypted.str, len < decrypted.len ? len : decrypted.len);
          sock->master->key = string_copy_len (key, l);
          string_free (decrypted);
        } else
          err = SIM_CRYPT_NO_RSA_DECRYPT;
      }
      table_free (received);
      table_free (proto);
    }
    table_free (table);
  }
  memset (key, 0, len);
  return err;
}

static int crypt_rsa_handshake_revoked_ (simsocket sock, simcontact contact, const simtype reason) {
  int err = SIM_OK;
  simtype received = nil (), encrypted, table, proto = table_new_const (1, SIM_NEW_STRING (SIM_REQUEST_REVOKE), NULL);
  if (reason.typ != SIMNIL && (err = socket_recv_table_ (sock, proto, nil (), &received, NULL)) == SIM_OK)
    if ((encrypted = table_get_string (received, SIM_REQUEST_REVOKE)).typ != SIMNIL) {
      simtype str = string_cat ((char *) reason.str, SIM_REPLY_REVOKE);
      simtype hash = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_WHIRLPOOL), contact->ec, contact->rsa, str);
      simtype verify = ssl_rsa_crypt_public_ (encrypted, sock->master->pub[SSL_KEY_RSA], hash);
      string_free (str);
      if (verify.typ == SIMNIL) {
        err = SIM_CRYPT_RSA_VERIFY;
      } else if (param_get_number ("crypto.revoke")) {
        err = contact_set_revocation (contact, reason);
        if (err == SIM_OK || err == SIM_CONTACT_REVOKED) {
          table_add_number (table = table_new (1), SIM_REPLY_REVOKE, SIM_OK);
          socket_send_table_ (sock, table, SOCKET_SEND_TCP, NULL);
          table_free (table);
          if (err == SIM_OK)
            event_send_value (contact, SIM_EVENT_CONTACT, SIM_EVENT_CONTACT, number_new (SIM_CONTACT_REVOKED));
          contact_cancel (contact, err = SIM_CONTACT_REVOKED);
        }
      }
      string_free (verify);
      string_free (hash);
    }
  table_free (received);
  table_free (proto);
  return err;
}

int crypt_rsa_handshake_server_ (simsocket sock, simcontact contact, const simtype handshake) {
  int err = SIM_CRYPT_BAD_CIPHER;
  int decrypt = crypt_cipher_find (sock->crypt.rcvcipher, 0, NULL);
  int encrypt = crypt_cipher_find (sock->crypt.sndcipher, 0, NULL);
  if (decrypt >= 0 && encrypt >= 0 && (err = crypt_rsa_handshake_ (sock)) == SIM_OK) {
    int count = encrypt == decrypt ? 2 : 2 + (CRYPT_LOOKUP_CIPHER (encrypt)->keysize + 31) / 32;
    simtype table = table_new (2);
    table_add_pointer (table, SIM_SHAKEHAND_TYPE, SIM_SHAKEHAND_TYPE_CRYPT);
    table_add_number (table, SIM_SHAKEHAND_TAG_SIZE, crypt_cipher_size_max_param ("crypto.tagsize"));
    table_add (table, SIM_SHAKEHAND_DECRYPT, crypt_cipher_encrypt (decrypt, sock->master, count));
    table_add (table, SIM_SHAKEHAND_ENCRYPT, crypt_cipher_encrypt (encrypt, sock->master, 2));
    err = socket_send_table_ (sock, table, SOCKET_SEND_TCP, NULL);
    crypt_close_socket (sock, false);
    if (err == SIM_OK) {
      simnumber tagsize = table_get_number (handshake, SIM_HANDSHAKE_TAG_SIZE);
      crypt_open_socket (sock, decrypt, encrypt, tagsize, crypt_cipher_size_max_param ("crypto.tagsize"), true, false);
      err = crypt_rsa_handshake_revoked_ (sock, contact, table_get_string (handshake, SIM_REQUEST_REVOKE));
    }
    table_free (table);
  }
  return err;
}

int crypt_rsa_handshake_client_ (simsocket sock, simcontact contact, const simtype proto, const simtype shakehand) {
  simtype table = nil ();
  int err = crypt_rsa_handshake_ (sock);
  if (err == SIM_OK)
    err = socket_recv_table_ (sock, proto, nil (), &table, NULL);
  crypt_close_socket (sock, false);
  if (err == SIM_OK) {
    err = SIM_CRYPT_BAD_HANDSHAKE;
    if (! string_check_diff (table_get_string (table, SIM_SHAKEHAND_TYPE), SIM_SHAKEHAND_TYPE_CRYPT)) {
      simtype decrypts = table_get_string (table, SIM_SHAKEHAND_DECRYPT);
      int decrypt = sim_crypt_cipher_decrypt (decrypts, sock->master, 2);
      int encrypt = sim_crypt_cipher_decrypt (table_get_string (table, SIM_SHAKEHAND_ENCRYPT), sock->master, 2);
      const struct crypt_cipher *entry = CRYPT_LOOKUP_CIPHER (encrypt);
      int decrypt2 = sim_crypt_cipher_decrypt (decrypts, sock->master, 2 + (entry->keysize + 31) / 32);
      if (decrypt < 0)
        decrypt = decrypt2;
      if (encrypt >= 0 && decrypt >= 0) {
        simnumber size = table_get_number (table, SIM_SHAKEHAND_TAG_SIZE);
        int tagsize = crypt_cipher_size_max_param ("crypto.tagsize");
        crypt_open_socket (sock, encrypt, decrypt, size, tagsize, false, false);
        err = crypt_rsa_handshake_revoked_ (sock, contact, table_get_string (shakehand, SIM_REQUEST_REVOKE));
      }
    }
  }
  table_free (table);
  return err;
}

int crypt_handshake_server_ (simsocket sock, simtype shakehand, const simtype handshake) {
  int err = handshake.typ == SIMNIL ? SIM_OK : sim_crypt_handshake (sock->master), encrypt, decrypt, cipher;
  simtype handshakes = handshake.typ == SIMNIL ? nil () : table_get_array_string (handshake, SIM_HANDSHAKE_TYPE);
  if (err == SIM_OK && handshakes.typ == SIMNIL)
    return socket_send_table_ (sock, shakehand, SOCKET_SEND_TCP, NULL);
  if (err == SIM_OK) {
    simtype desired = table_get_array_number (handshake, SIM_HANDSHAKE_PREFERRED);
    simtype supported = table_get_array_number (handshake, SIM_HANDSHAKE_SUPPORTED);
    unsigned i = handshakes.len;
    while (i && string_check_diff (handshakes.arr[i], SIM_HANDSHAKE_TYPE_RSA))
      --i;
    err = SIM_CRYPT_BAD_HANDSHAKE;
    if (i && desired.typ != SIMNIL && supported.typ != SIMNIL) {
      table_add_pointer (shakehand, SIM_SHAKEHAND_TYPE, SIM_HANDSHAKE_TYPE_RSA);
      if ((encrypt = decrypt = crypt_cipher_get_random (desired, true)) < 0) {
        decrypt = 0;
        if (! *crypt_preferred_flags || ! sim_crypt_cipher_find (desired, crypt_ciphers[0].ident)) {
          if ((encrypt = crypt_cipher_get_random (supported, true)) < 0) {
            if (*crypt_preferred_flags || (encrypt = crypt_cipher_get_random (supported, false)) < 0) {
              encrypt = 0;
            } else
              decrypt = encrypt;
          }
          if ((cipher = crypt_cipher_get_random (desired, false)) < 0) {
            if (sim_crypt_cipher_find (desired, crypt_ciphers[0].ident)) {
              decrypt = 0;
            } else if ((cipher = crypt_cipher_get_random (supported, false)) < 0) {
              decrypt = encrypt;
            } else if (! decrypt)
              decrypt = cipher;
          } else
            decrypt = cipher;
        } else
          encrypt = 0;
      }
      sock->crypt.rcvcipher = CRYPT_LOOKUP_CIPHER (decrypt)->name;
      sock->crypt.sndcipher = CRYPT_LOOKUP_CIPHER (encrypt)->name;
      err = socket_send_table_ (sock, shakehand, SOCKET_SEND_TCP, NULL);
    }
  }
  return err;
}

int crypt_handshake_client_ (simsocket sock, simtype *handshake, const simtype proto, const char *type) {
  int err = type ? sim_crypt_handshake (sock->master) : SIM_OK;
  if (err == SIM_OK) {
    crypt_open_socket (sock, type ? 0 : 3, type ? 0 : 3, 0, 0, false, ! type);
    if (type && strcmp (type, SIM_HANDSHAKE_TYPE_REVERSE))
      sock->crypt.padding = param_get_number_min ("crypto.padding", param_get_number ("socket.padding"));
    if (type && ! strcmp (type, SIM_HANDSHAKE_TYPE_RSA)) {
      table_add_pointer (*handshake, SIM_HANDSHAKE_TYPE, SIM_HANDSHAKE_TYPE_RSA);
      table_add_number (*handshake, SIM_HANDSHAKE_TAG_SIZE, crypt_cipher_size_max_param ("crypto.tagsize"));
      table_add (*handshake, SIM_HANDSHAKE_PREFERRED, crypt_get_ciphers (true));
      table_add (*handshake, SIM_HANDSHAKE_SUPPORTED, crypt_get_ciphers (false));
    } else
      type = NULL;
    err = socket_send_table_ (sock, *handshake, SOCKET_SEND_TCP, NULL);
  }
  table_free (*handshake), *handshake = nil ();
  if (err == SIM_OK)
    err = socket_recv_table_ (sock, proto, nil (), handshake, NULL);
  if (type && err == SIM_OK && string_check_diff (table_get_string (*handshake, SIM_SHAKEHAND_TYPE), type))
    err = SIM_CRYPT_BAD_HANDSHAKE;
  if (err != SIM_OK)
    table_free (*handshake), *handshake = nil ();
  return err;
}

#define SSL_MASTER_SECRET "master secret"

int sim_crypt_handshake (const struct _ssl_master *master) {
#ifdef DONOT_DEFINE
  simbool nok;
  CryptoPP::HMAC<CryptoPP::SHA384> md1 (master->premaster, master->length);
  CryptoPP::HMAC<CryptoPP::SHA384> md2 (master->premaster, master->length);
  simtype hash = string_buffer_new (md2.DigestSize ());
  md1.Update ((const simbyte *) SSL_MASTER_SECRET, SIM_STRING_GET_LENGTH_CONST (SSL_MASTER_SECRET));
  md1.Update (master->random, sizeof (master->random));
  md1.Final (hash.str);
  md2.Update (hash.str, hash.len);
  md2.Update ((const simbyte *) SSL_MASTER_SECRET, SIM_STRING_GET_LENGTH_CONST (SSL_MASTER_SECRET));
  md2.Update (master->random, sizeof (master->random));
  md2.Final (hash.str);
  nok = ! master->length || string_check_diff_len (hash, master->master, sizeof (master->master));
  string_buffer_free (hash);
  return nok ? SIM_SSL_NO_MASTER : SIM_OK; // openssl generated something unexpected, so abort the handshake
#else
  return master->length == sizeof (master->premaster) / 2 ? SIM_OK : SIM_SSL_NO_MASTER;
#endif
}

} // extern "C"
