/*****************************************************************************
 * Author:   Valient Gough <vgough@pobox.com>
 *
 *****************************************************************************
 * Copyright (c) 2004, Valient Gough
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "easylogging++.h"
#include <cstring>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/ossl_typ.h>
#include <openssl/rand.h>
#include <pthread.h>
#include <string>
#include <sys/mman.h>
#include <sys/time.h>

#include "Cipher.h"
#include "Error.h"
#include "Interface.h"
#include "Mutex.h"
#include "Range.h"
#include "SSL_Cipher.h"
#include "SSL_Compat.h"
#include "intl/gettext.h"

using namespace std;

namespace encfs {

const int MAX_KEYLENGTH = 32;  // in bytes (256 bit)
const int MAX_IVLENGTH = 16;   // 128 bit (AES block size, Blowfish has 64)
const int KEY_CHECKSUM_BYTES = 4;

#ifndef MIN
inline int MIN(int a, int b) { return (a < b) ? a : b; }
#endif

/**
    This produces the same result as OpenSSL's EVP_BytesToKey.  The difference
    is that here we can explicitly specify the key size, instead of relying on
    the state of EVP_CIPHER struct.  EVP_BytesToKey will only produce 128 bit
    keys for the EVP Blowfish interface, which is not what we want.

    Eliminated the salt code, since we don't use it..  Reason is that we're
    using the derived key to encode random data.  Since there is no known
    plaintext, there is no ability for an attacker to pre-compute known
    password->data mappings, which is what the salt is meant to frustrate.
*/
int BytesToKey(int keyLen, int ivLen, const EVP_MD *md,
               const unsigned char *data, int dataLen, unsigned int rounds,
               unsigned char *key, unsigned char *iv) {
  if (data == nullptr || dataLen == 0) {
    return 0;  // OpenSSL returns nkey here, but why?  It is a failure..
  }

  unsigned char mdBuf[EVP_MAX_MD_SIZE];
  unsigned int mds = 0;
  int addmd = 0;
  int nkey = key != nullptr ? keyLen : 0;
  int niv = iv != nullptr ? ivLen : 0;

  EVP_MD_CTX *cx = EVP_MD_CTX_new();
  EVP_MD_CTX_init(cx);

  for (;;) {
    EVP_DigestInit_ex(cx, md, nullptr);
    if ((addmd++) != 0) {
      EVP_DigestUpdate(cx, mdBuf, mds);
    }
    EVP_DigestUpdate(cx, data, dataLen);
    EVP_DigestFinal_ex(cx, mdBuf, &mds);

    for (unsigned int i = 1; i < rounds; ++i) {
      EVP_DigestInit_ex(cx, md, nullptr);
      EVP_DigestUpdate(cx, mdBuf, mds);
      EVP_DigestFinal_ex(cx, mdBuf, &mds);
    }

    int offset = 0;
    int toCopy = MIN(nkey, mds - offset);
    if (toCopy != 0) {
      memcpy(key, mdBuf + offset, toCopy);
      key += toCopy;
      nkey -= toCopy;
      offset += toCopy;
    }
    toCopy = MIN(niv, mds - offset);
    if (toCopy != 0) {
      memcpy(iv, mdBuf + offset, toCopy);
      iv += toCopy;
      niv -= toCopy;
      // offset += toCopy;
    }
    if ((nkey == 0) && (niv == 0)) {
      break;
    }
  }
  EVP_MD_CTX_free(cx);
  OPENSSL_cleanse(mdBuf, sizeof(mdBuf));

  return keyLen;
}

long time_diff(const timeval &end, const timeval &start) {
  return (end.tv_sec - start.tv_sec) * 1000 * 1000 +
         (end.tv_usec - start.tv_usec);
}

int TimedPBKDF2(const char *pass, int passlen, const unsigned char *salt,
                int saltlen, int keylen, unsigned char *out,
                long desiredPDFTime) {
  int iter = 1000;
  timeval start, end;

  for (;;) {
    gettimeofday(&start, nullptr);
    int res =
        PKCS5_PBKDF2_HMAC_SHA1(pass, passlen, const_cast<unsigned char *>(salt),
                               saltlen, iter, keylen, out);
    if (res != 1) {
      return -1;
    }

    gettimeofday(&end, nullptr);

    long delta = time_diff(end, start);
    if (delta < desiredPDFTime / 8) {
      iter *= 4;
    } else if (delta < (5 * desiredPDFTime / 6)) {
      // estimate number of iterations to get close to desired time
      iter = (int)((double)iter * (double)desiredPDFTime / (double)delta);
    } else {
      return iter;
    }
  }
}

// - Version 1:0 used EVP_BytesToKey, which didn't do the right thing for
// Blowfish key lengths > 128 bit.
// - Version 2:0 uses BytesToKey.
// We support both 2:0 and 1:0, hence current:revision:age = 2:0:1
// - Version 2:1 adds support for Message Digest function interface
// - Version 2:2 adds PBKDF2 for password derivation
// - Version 3:0 adds a new IV mechanism
static Interface BlowfishInterface("ssl/blowfish", 3, 0, 2);
static Interface AESInterface("ssl/aes", 3, 0, 2);
static Interface CAMELLIAInterface("ssl/camellia", 3, 0, 2);

#ifndef OPENSSL_NO_CAMELLIA

static Range CAMELLIAKeyRange(128, 256, 64);
static Range CAMELLIABlockRange(64, 4096, 16);

static std::shared_ptr<Cipher> NewCAMELLIACipher(const Interface &iface,
                                                 int keyLen) {
  if (keyLen <= 0) {
    keyLen = 192;
  }

  keyLen = CAMELLIAKeyRange.closest(keyLen);

  const EVP_CIPHER *blockCipher = nullptr;
  const EVP_CIPHER *streamCipher = nullptr;

  switch (keyLen) {
    case 128:
      blockCipher = EVP_camellia_128_cbc();
      streamCipher = EVP_camellia_128_cfb();
      break;
    case 192:
      blockCipher = EVP_camellia_192_cbc();
      streamCipher = EVP_camellia_192_cfb();
      break;
    case 256:
    default:
      blockCipher = EVP_camellia_256_cbc();
      streamCipher = EVP_camellia_256_cfb();
      break;
  }

  return std::shared_ptr<Cipher>(new SSL_Cipher(
      iface, CAMELLIAInterface, blockCipher, streamCipher, keyLen / 8));
}

static bool CAMELLIA_Cipher_registered =
    Cipher::Register("CAMELLIA", "16 byte block cipher", CAMELLIAInterface,
                     CAMELLIAKeyRange, CAMELLIABlockRange, NewCAMELLIACipher);

#endif

#ifndef OPENSSL_NO_BF

static Range BFKeyRange(128, 256, 32);
static Range BFBlockRange(64, 4096, 8);

static std::shared_ptr<Cipher> NewBFCipher(const Interface &iface, int keyLen) {
  if (keyLen <= 0) {
    keyLen = 160;
  }

  keyLen = BFKeyRange.closest(keyLen);

  const EVP_CIPHER *blockCipher = EVP_bf_cbc();
  const EVP_CIPHER *streamCipher = EVP_bf_cfb();

  return std::shared_ptr<Cipher>(new SSL_Cipher(
      iface, BlowfishInterface, blockCipher, streamCipher, keyLen / 8));
}

static bool BF_Cipher_registered =
    Cipher::Register("Blowfish",
                     // xgroup(setup)
                     gettext_noop("8 byte block cipher"), BlowfishInterface,
                     BFKeyRange, BFBlockRange, NewBFCipher);
#endif

#ifndef OPENSSL_NO_AES

static Range AESKeyRange(128, 256, 64);
static Range AESBlockRange(64, 4096, 16);

static std::shared_ptr<Cipher> NewAESCipher(const Interface &iface,
                                            int keyLen) {
  if (keyLen <= 0) {
    keyLen = 192;
  }

  keyLen = AESKeyRange.closest(keyLen);

  const EVP_CIPHER *blockCipher = nullptr;
  const EVP_CIPHER *streamCipher = nullptr;

  switch (keyLen) {
    case 128:
      blockCipher = EVP_aes_128_cbc();
      streamCipher = EVP_aes_128_cfb();
      break;

    case 192:
      blockCipher = EVP_aes_192_cbc();
      streamCipher = EVP_aes_192_cfb();
      break;

    case 256:
    default:
      blockCipher = EVP_aes_256_cbc();
      streamCipher = EVP_aes_256_cfb();
      break;
  }

  return std::shared_ptr<Cipher>(new SSL_Cipher(
      iface, AESInterface, blockCipher, streamCipher, keyLen / 8));
}

static bool AES_Cipher_registered =
    Cipher::Register("AES", "16 byte block cipher", AESInterface, AESKeyRange,
                     AESBlockRange, NewAESCipher);
#endif

class SSLKey : public AbstractCipherKey {
 public:
  pthread_mutex_t mutex;

  unsigned int keySize;  // in bytes
  unsigned int ivLength;

  // key data is first _keySize bytes,
  // followed by iv of _ivLength bytes,
  unsigned char *buffer;

  EVP_CIPHER_CTX *block_enc;
  EVP_CIPHER_CTX *block_dec;
  EVP_CIPHER_CTX *stream_enc;
  EVP_CIPHER_CTX *stream_dec;

  HMAC_CTX *mac_ctx;

  SSLKey(int keySize, int ivLength);

  // destructor
  ~SSLKey() override;

  SSLKey(const SSLKey &src) = delete; // copy constructor
  SSLKey(SSLKey&& other) = delete; // move constructor
  SSLKey& operator=(const SSLKey& other) = delete; // copy assignment
  SSLKey& operator=(SSLKey&& other) = delete; // move assignment
};

SSLKey::SSLKey(int keySize_, int ivLength_) {
  this->keySize = keySize_;
  this->ivLength = ivLength_;
  pthread_mutex_init(&mutex, nullptr);
  buffer = (unsigned char *)OPENSSL_malloc(keySize + ivLength);
  memset(buffer, 0, (size_t)keySize + (size_t)ivLength);

  // most likely fails unless we're running as root, or a user-page-lock
  // kernel patch is applied..
  mlock(buffer, (size_t)keySize + (size_t)ivLength);

  block_enc = EVP_CIPHER_CTX_new();
  EVP_CIPHER_CTX_init(block_enc);
  block_dec = EVP_CIPHER_CTX_new();
  EVP_CIPHER_CTX_init(block_dec);
  stream_enc = EVP_CIPHER_CTX_new();
  EVP_CIPHER_CTX_init(stream_enc);
  stream_dec = EVP_CIPHER_CTX_new();
  EVP_CIPHER_CTX_init(stream_dec);
  mac_ctx = HMAC_CTX_new();
  HMAC_CTX_reset(mac_ctx);
}

SSLKey::~SSLKey() {
  memset(buffer, 0, (size_t)keySize + (size_t)ivLength);

  OPENSSL_free(buffer);
  munlock(buffer, (size_t)keySize + (size_t)ivLength);

  keySize = 0;
  ivLength = 0;
  buffer = nullptr;

  EVP_CIPHER_CTX_free(block_enc);
  EVP_CIPHER_CTX_free(block_dec);
  EVP_CIPHER_CTX_free(stream_enc);
  EVP_CIPHER_CTX_free(stream_dec);
  HMAC_CTX_free(mac_ctx);

  pthread_mutex_destroy(&mutex);
}

inline unsigned char *KeyData(const std::shared_ptr<SSLKey> &key) {
  return key->buffer;
}
inline unsigned char *IVData(const std::shared_ptr<SSLKey> &key) {
  return key->buffer + key->keySize;
}

void initKey(const std::shared_ptr<SSLKey> &key, const EVP_CIPHER *_blockCipher,
             const EVP_CIPHER *_streamCipher, int _keySize) {
  Lock lock(key->mutex);
  // initialize the cipher context once so that we don't have to do it for
  // every block..
  EVP_EncryptInit_ex(key->block_enc, _blockCipher, nullptr, nullptr, nullptr);
  EVP_DecryptInit_ex(key->block_dec, _blockCipher, nullptr, nullptr, nullptr);
  EVP_EncryptInit_ex(key->stream_enc, _streamCipher, nullptr, nullptr, nullptr);
  EVP_DecryptInit_ex(key->stream_dec, _streamCipher, nullptr, nullptr, nullptr);

  EVP_CIPHER_CTX_set_key_length(key->block_enc, _keySize);
  EVP_CIPHER_CTX_set_key_length(key->block_dec, _keySize);
  EVP_CIPHER_CTX_set_key_length(key->stream_enc, _keySize);
  EVP_CIPHER_CTX_set_key_length(key->stream_dec, _keySize);

  EVP_CIPHER_CTX_set_padding(key->block_enc, 0);
  EVP_CIPHER_CTX_set_padding(key->block_dec, 0);
  EVP_CIPHER_CTX_set_padding(key->stream_enc, 0);
  EVP_CIPHER_CTX_set_padding(key->stream_dec, 0);

  EVP_EncryptInit_ex(key->block_enc, nullptr, nullptr, KeyData(key), nullptr);
  EVP_DecryptInit_ex(key->block_dec, nullptr, nullptr, KeyData(key), nullptr);
  EVP_EncryptInit_ex(key->stream_enc, nullptr, nullptr, KeyData(key), nullptr);
  EVP_DecryptInit_ex(key->stream_dec, nullptr, nullptr, KeyData(key), nullptr);

  HMAC_Init_ex(key->mac_ctx, KeyData(key), _keySize, EVP_sha1(), nullptr);
}

SSL_Cipher::SSL_Cipher(const Interface &iface_, const Interface &realIface_,
                       const EVP_CIPHER *blockCipher,
                       const EVP_CIPHER *streamCipher, int keySize_) {
  this->iface = iface_;
  this->realIface = realIface_;
  this->_blockCipher = blockCipher;
  this->_streamCipher = streamCipher;
  this->_keySize = keySize_;
  this->_ivLength = EVP_CIPHER_iv_length(_blockCipher);

  rAssert(_ivLength == 8 || _ivLength == 16);

  VLOG(1) << "allocated cipher " << iface.name() << ", keySize " << _keySize
          << ", ivlength " << _ivLength;

  if ((EVP_CIPHER_key_length(_blockCipher) != (int)_keySize) &&
      iface.current() == 1) {
    RLOG(WARNING) << "Running in backward compatibilty mode for 1.0 - "
                     "key is really "
                  << EVP_CIPHER_key_length(_blockCipher) * 8 << " bits, not "
                  << _keySize * 8;
  }
}

SSL_Cipher::~SSL_Cipher() = default;

Interface SSL_Cipher::interface() const { return realIface; }

/**
    create a key from the password.
    Use SHA to distribute entropy from the password into the key.

    This algorithm must remain constant for backward compatibility, as this key
    is used to encipher/decipher the master key.
*/
CipherKey SSL_Cipher::newKey(const char *password, int passwdLength,
                             int &iterationCount, long desiredDuration,
                             const unsigned char *salt, int saltLen) {
  std::shared_ptr<SSLKey> key(new SSLKey(_keySize, _ivLength));

  if (iterationCount == 0) {
    // timed run, fills in iteration count
    int res =
        TimedPBKDF2(password, passwdLength, salt, saltLen, _keySize + _ivLength,
                    KeyData(key), 1000 * desiredDuration);
    if (res <= 0) {
      RLOG(WARNING) << "openssl error, PBKDF2 failed";
      return CipherKey();
    }
    iterationCount = res;

  } else {
    // known iteration length
    if (PKCS5_PBKDF2_HMAC_SHA1(
            password, passwdLength, const_cast<unsigned char *>(salt), saltLen,
            iterationCount, _keySize + _ivLength, KeyData(key)) != 1) {
      RLOG(WARNING) << "openssl error, PBKDF2 failed";
      return CipherKey();
    }
  }

  initKey(key, _blockCipher, _streamCipher, _keySize);

  return key;
}

CipherKey SSL_Cipher::newKey(const char *password, int passwdLength) {
  std::shared_ptr<SSLKey> key(new SSLKey(_keySize, _ivLength));

  int bytes = 0;
  if (iface.current() > 1) {
    // now we use BytesToKey, which can deal with Blowfish keys larger then
    // 128 bits.
    bytes =
        BytesToKey(_keySize, _ivLength, EVP_sha1(), (unsigned char *)password,
                   passwdLength, 16, KeyData(key), IVData(key));

    // the reason for moving from EVP_BytesToKey to BytesToKey function..
    if (bytes != (int)_keySize) {
      RLOG(WARNING) << "newKey: BytesToKey returned " << bytes << ", expecting "
                    << _keySize << " key bytes";
    }
  } else {
    // for backward compatibility with filesystems created with 1:0
    EVP_BytesToKey(_blockCipher, EVP_sha1(), nullptr, (unsigned char *)password,
                   passwdLength, 16, KeyData(key), IVData(key));
  }

  initKey(key, _blockCipher, _streamCipher, _keySize);

  return key;
}

/**
    Create a random key.
    We use the OpenSSL library to generate random bytes, then take the hash of
    those bytes to use as the key.

    This algorithm can change at any time without affecting backward
    compatibility.
*/
CipherKey SSL_Cipher::newRandomKey() {
  const int bufLen = MAX_KEYLENGTH;
  unsigned char tmpBuf[bufLen];
  int saltLen = 20;
  unsigned char saltBuf[saltLen];

  if (!randomize(tmpBuf, bufLen, true) || !randomize(saltBuf, saltLen, true)) {
    return CipherKey();
  }

  std::shared_ptr<SSLKey> key(new SSLKey(_keySize, _ivLength));

  // doesn't need to be versioned, because a random key is a random key..
  // Doesn't need to be reproducable..
  if (PKCS5_PBKDF2_HMAC_SHA1((char *)tmpBuf, bufLen, saltBuf, saltLen, 1000,
                             _keySize + _ivLength, KeyData(key)) != 1) {
    RLOG(WARNING) << "openssl error, PBKDF2 failed";
    return CipherKey();
  }

  OPENSSL_cleanse(tmpBuf, bufLen);

  initKey(key, _blockCipher, _streamCipher, _keySize);

  return key;
}

/**
    compute a 64-bit check value for the data using HMAC.
*/
static uint64_t _checksum_64(SSLKey *key, const unsigned char *data,
                             int dataLen, const uint64_t *const chainedIV) {
  rAssert(dataLen > 0);
  Lock lock(key->mutex);

  unsigned char md[EVP_MAX_MD_SIZE];
  unsigned int mdLen = EVP_MAX_MD_SIZE;

  HMAC_Init_ex(key->mac_ctx, nullptr, 0, nullptr, nullptr);
  HMAC_Update(key->mac_ctx, data, dataLen);
  if (chainedIV != nullptr) {
    // toss in the chained IV as well
    uint64_t tmp = *chainedIV;
    unsigned char h[8];
    for (unsigned int i = 0; i < 8; ++i) {
      h[i] = tmp & 0xff;
      tmp >>= 8;
    }

    HMAC_Update(key->mac_ctx, h, 8);
  }

  HMAC_Final(key->mac_ctx, md, &mdLen);

  rAssert(mdLen >= 8);

  // chop this down to a 64bit value..
  unsigned char h[8] = {0, 0, 0, 0, 0, 0, 0, 0};
  for (unsigned int i = 0; i < (mdLen - 1); ++i) {
    h[i % 8] ^= (unsigned char)(md[i]);
  }

  auto value = (uint64_t)h[0];
  for (int i = 1; i < 8; ++i) {
    value = (value << 8) | (uint64_t)h[i];
  }

  return value;
}

/**
 * Write "len" bytes of random data into "buf"
 *
 * We ignore the @strongRandom parameter because OpenSSL
 * does not * offer a "weak" random generator
 */
bool SSL_Cipher::randomize(unsigned char *buf, int len,
                           bool /*strongRandom*/) const {
  // to avoid warnings of uninitialized data from valgrind
  memset(buf, 0, len);

  int result = RAND_bytes(buf, len);
  if (result != 1) {
    char errStr[120];  // specs require string at least 120 bytes long..
    unsigned long errVal = 0;
    if ((errVal = ERR_get_error()) != 0) {
      RLOG(WARNING) << "openssl error: " << ERR_error_string(errVal, errStr);
    }

    return false;
  }
  return true;
}

uint64_t SSL_Cipher::MAC_64(const unsigned char *data, int len,
                            const CipherKey &key, uint64_t *chainedIV) const {
  std::shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(key);
  uint64_t tmp = _checksum_64(mk.get(), data, len, chainedIV);

  if (chainedIV != nullptr) {
    *chainedIV = tmp;
  }

  return tmp;
}

CipherKey SSL_Cipher::readKey(const unsigned char *data,
                              const CipherKey &masterKey, bool checkKey) {
  std::shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(masterKey);
  rAssert(mk->keySize == _keySize);

  unsigned char tmpBuf[MAX_KEYLENGTH + MAX_IVLENGTH];

  // First N bytes are checksum bytes.
  unsigned int checksum = 0;
  for (int i = 0; i < KEY_CHECKSUM_BYTES; ++i) {
    checksum = (checksum << 8) | (unsigned int)data[i];
  }

  memcpy(tmpBuf, data + KEY_CHECKSUM_BYTES, (size_t)_keySize + (size_t)_ivLength);
  streamDecode(tmpBuf, _keySize + _ivLength, checksum, masterKey);

  // check for success
  unsigned int checksum2 = MAC_32(tmpBuf, _keySize + _ivLength, masterKey);
  if (checksum2 != checksum && checkKey) {
    VLOG(1) << "checksum mismatch: expected " << checksum << ", got "
            << checksum2;
    VLOG(1) << "on decode of " << _keySize + _ivLength << " bytes";
    memset(tmpBuf, 0, sizeof(tmpBuf));
    return CipherKey();
  }

  std::shared_ptr<SSLKey> key(new SSLKey(_keySize, _ivLength));

  memcpy(key->buffer, tmpBuf, (size_t)_keySize + (size_t)_ivLength);
  memset(tmpBuf, 0, sizeof(tmpBuf));

  initKey(key, _blockCipher, _streamCipher, _keySize);

  return key;
}

void SSL_Cipher::writeKey(const CipherKey &ckey, unsigned char *data,
                          const CipherKey &masterKey) {
  std::shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
  rAssert(key->keySize == _keySize);
  rAssert(key->ivLength == _ivLength);

  std::shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(masterKey);
  rAssert(mk->keySize == _keySize);
  rAssert(mk->ivLength == _ivLength);

  unsigned char tmpBuf[MAX_KEYLENGTH + MAX_IVLENGTH];

  int bufLen = _keySize + _ivLength;
  memcpy(tmpBuf, key->buffer, bufLen);

  unsigned int checksum = MAC_32(tmpBuf, bufLen, masterKey);

  streamEncode(tmpBuf, bufLen, checksum, masterKey);
  memcpy(data + KEY_CHECKSUM_BYTES, tmpBuf, bufLen);

  // first N bytes contain HMAC derived checksum..
  for (int i = 1; i <= KEY_CHECKSUM_BYTES; ++i) {
    data[KEY_CHECKSUM_BYTES - i] = checksum & 0xff;
    checksum >>= 8;
  }

  memset(tmpBuf, 0, sizeof(tmpBuf));
}

bool SSL_Cipher::compareKey(const CipherKey &A, const CipherKey &B) const {
  std::shared_ptr<SSLKey> key1 = dynamic_pointer_cast<SSLKey>(A);
  std::shared_ptr<SSLKey> key2 = dynamic_pointer_cast<SSLKey>(B);

  rAssert(key1->keySize == _keySize);
  rAssert(key2->keySize == _keySize);

  return memcmp(key1->buffer, key2->buffer, (size_t)_keySize + (size_t)_ivLength) == 0;
}

int SSL_Cipher::encodedKeySize() const {
  return _keySize + _ivLength + KEY_CHECKSUM_BYTES;
}

int SSL_Cipher::keySize() const { return _keySize; }

int SSL_Cipher::cipherBlockSize() const {
  return EVP_CIPHER_block_size(_blockCipher);
}

/**
 * Generate the initialization vector that will actually be used for
 * AES/Blowfish encryption and decryption in {stream,block}{Encode,Decode}
 *
 * It is derived from
 *  1) a "seed" value that is passed from the higher layer, for the default
 *     configuration it is "block_number XOR per_file_IV_header" from
 *     CipherFileIO
 *  2) The IV that is used for encrypting the master key, "IVData(key)"
 *  3) The master key
 * using
 *  ivec = HMAC(master_key, IVData(key) CONCAT seed)
 *
 * As an HMAC is unpredictable as long as the key is secret, the only
 * requirement for "seed" is that is must be unique.
 */
void SSL_Cipher::setIVec(unsigned char *ivec, uint64_t seed,
                         const std::shared_ptr<SSLKey> &key) const {
  if (iface.current() >= 3) {
    memcpy(ivec, IVData(key), _ivLength);

    unsigned char md[EVP_MAX_MD_SIZE];
    unsigned int mdLen = EVP_MAX_MD_SIZE;

    for (int i = 0; i < 8; ++i) {
      md[i] = (unsigned char)(seed & 0xff);
      seed >>= 8;
    }

    // combine ivec and seed with HMAC
    HMAC_Init_ex(key->mac_ctx, nullptr, 0, nullptr, nullptr);
    HMAC_Update(key->mac_ctx, ivec, _ivLength);
    HMAC_Update(key->mac_ctx, md, 8);
    HMAC_Final(key->mac_ctx, md, &mdLen);
    rAssert(mdLen >= _ivLength);

    memcpy(ivec, md, _ivLength);
  } else {
    setIVec_old(ivec, seed, key);
  }
}

/** For backward compatibility.
    A watermark attack was discovered against this IV setup.  If an attacker
    could get a victim to store a carefully crafted file, they could later
    determine if the victim had the file in encrypted storage (without
    decrypting the file).
  */
void SSL_Cipher::setIVec_old(unsigned char *ivec, unsigned int seed,
                             const std::shared_ptr<SSLKey> &key) const {
  /* These multiplication constants chosen as they represent (non optimal)
     Golumb rulers, the idea being to spread around the information in the
     seed.

     0x060a4011 : ruler length 26, 7 marks, 21 measurable lengths
     0x0221040d : ruler length 25, 7 marks, 21 measurable lengths
  */
  unsigned int var1 = 0x060a4011 * seed;
  unsigned int var2 = 0x0221040d * (seed ^ 0xD3FEA11C);

  memcpy(ivec, IVData(key), _ivLength);

  ivec[0] ^= (var1 >> 24) & 0xff;
  ivec[1] ^= (var2 >> 16) & 0xff;
  ivec[2] ^= (var1 >> 8) & 0xff;
  ivec[3] ^= (var2)&0xff;
  ivec[4] ^= (var2 >> 24) & 0xff;
  ivec[5] ^= (var1 >> 16) & 0xff;
  ivec[6] ^= (var2 >> 8) & 0xff;
  ivec[7] ^= (var1)&0xff;

  if (_ivLength > 8) {
    ivec[8 + 0] ^= (var1)&0xff;
    ivec[8 + 1] ^= (var2 >> 8) & 0xff;
    ivec[8 + 2] ^= (var1 >> 16) & 0xff;
    ivec[8 + 3] ^= (var2 >> 24) & 0xff;
    ivec[8 + 4] ^= (var1 >> 24) & 0xff;
    ivec[8 + 5] ^= (var2 >> 16) & 0xff;
    ivec[8 + 6] ^= (var1 >> 8) & 0xff;
    ivec[8 + 7] ^= (var2)&0xff;
  }
}

static void flipBytes(unsigned char *buf, int size) {
  unsigned char revBuf[64];

  int bytesLeft = size;
  while (bytesLeft != 0) {
    int toFlip = MIN(sizeof(revBuf), bytesLeft);

    for (int i = 0; i < toFlip; ++i) {
      revBuf[i] = buf[toFlip - (i + 1)];
    }

    memcpy(buf, revBuf, toFlip);
    bytesLeft -= toFlip;
    buf += toFlip;
  }
  memset(revBuf, 0, sizeof(revBuf));
}

static void shuffleBytes(unsigned char *buf, int size) {
  for (int i = 0; i < size - 1; ++i) {
    buf[i + 1] ^= buf[i];
  }
}

static void unshuffleBytes(unsigned char *buf, int size) {
  for (int i = size - 1; i != 0; --i) {
    buf[i] ^= buf[i - 1];
  }
}

/** Partial blocks are encoded with a stream cipher.  We make multiple passes on
 the data to ensure that the ends of the data depend on each other.
*/
bool SSL_Cipher::streamEncode(unsigned char *buf, int size, uint64_t iv64,
                              const CipherKey &ckey) const {
  rAssert(size > 0);
  std::shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
  rAssert(key->keySize == _keySize);
  rAssert(key->ivLength == _ivLength);

  Lock lock(key->mutex);

  unsigned char ivec[MAX_IVLENGTH];
  int dstLen = 0, tmpLen = 0;

  shuffleBytes(buf, size);

  setIVec(ivec, iv64, key);
  EVP_EncryptInit_ex(key->stream_enc, nullptr, nullptr, nullptr, ivec);
  EVP_EncryptUpdate(key->stream_enc, buf, &dstLen, buf, size);
  EVP_EncryptFinal_ex(key->stream_enc, buf + dstLen, &tmpLen);

  flipBytes(buf, size);
  shuffleBytes(buf, size);

  setIVec(ivec, iv64 + 1, key);
  EVP_EncryptInit_ex(key->stream_enc, nullptr, nullptr, nullptr, ivec);
  EVP_EncryptUpdate(key->stream_enc, buf, &dstLen, buf, size);
  EVP_EncryptFinal_ex(key->stream_enc, buf + dstLen, &tmpLen);

  dstLen += tmpLen;
  if (dstLen != size) {
    RLOG(ERROR) << "encoding " << size << " bytes, got back " << dstLen << " ("
                << tmpLen << " in final_ex)";
    return false;
  }

  return true;
}

bool SSL_Cipher::streamDecode(unsigned char *buf, int size, uint64_t iv64,
                              const CipherKey &ckey) const {
  rAssert(size > 0);
  std::shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
  rAssert(key->keySize == _keySize);
  rAssert(key->ivLength == _ivLength);

  Lock lock(key->mutex);

  unsigned char ivec[MAX_IVLENGTH];
  int dstLen = 0, tmpLen = 0;

  setIVec(ivec, iv64 + 1, key);
  EVP_DecryptInit_ex(key->stream_dec, nullptr, nullptr, nullptr, ivec);
  EVP_DecryptUpdate(key->stream_dec, buf, &dstLen, buf, size);
  EVP_DecryptFinal_ex(key->stream_dec, buf + dstLen, &tmpLen);

  unshuffleBytes(buf, size);
  flipBytes(buf, size);

  setIVec(ivec, iv64, key);
  EVP_DecryptInit_ex(key->stream_dec, nullptr, nullptr, nullptr, ivec);
  EVP_DecryptUpdate(key->stream_dec, buf, &dstLen, buf, size);
  EVP_DecryptFinal_ex(key->stream_dec, buf + dstLen, &tmpLen);

  unshuffleBytes(buf, size);

  dstLen += tmpLen;
  if (dstLen != size) {
    RLOG(ERROR) << "decoding " << size << " bytes, got back " << dstLen << " ("
                << tmpLen << " in final_ex)";
    return false;
  }

  return true;
}

bool SSL_Cipher::blockEncode(unsigned char *buf, int size, uint64_t iv64,
                             const CipherKey &ckey) const {
  rAssert(size > 0);
  std::shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
  rAssert(key->keySize == _keySize);
  rAssert(key->ivLength == _ivLength);

  // data must be integer number of blocks
  const int blockMod = size % EVP_CIPHER_CTX_block_size(key->block_enc);
  if (blockMod != 0) {
    RLOG(ERROR) << "Invalid data size, not multiple of block size";
    return false;
  }

  Lock lock(key->mutex);

  unsigned char ivec[MAX_IVLENGTH];

  int dstLen = 0, tmpLen = 0;
  setIVec(ivec, iv64, key);

  EVP_EncryptInit_ex(key->block_enc, nullptr, nullptr, nullptr, ivec);
  EVP_EncryptUpdate(key->block_enc, buf, &dstLen, buf, size);
  EVP_EncryptFinal_ex(key->block_enc, buf + dstLen, &tmpLen);
  dstLen += tmpLen;

  if (dstLen != size) {
    RLOG(ERROR) << "encoding " << size << " bytes, got back " << dstLen << " ("
                << tmpLen << " in final_ex)";
    return false;
  }

  return true;
}

bool SSL_Cipher::blockDecode(unsigned char *buf, int size, uint64_t iv64,
                             const CipherKey &ckey) const {
  rAssert(size > 0);
  std::shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
  rAssert(key->keySize == _keySize);
  rAssert(key->ivLength == _ivLength);

  // data must be integer number of blocks
  const int blockMod = size % EVP_CIPHER_CTX_block_size(key->block_dec);
  if (blockMod != 0) {
    RLOG(ERROR) << "Invalid data size, not multiple of block size";
    return false;
  }

  Lock lock(key->mutex);

  unsigned char ivec[MAX_IVLENGTH];

  int dstLen = 0, tmpLen = 0;
  setIVec(ivec, iv64, key);

  EVP_DecryptInit_ex(key->block_dec, nullptr, nullptr, nullptr, ivec);
  EVP_DecryptUpdate(key->block_dec, buf, &dstLen, buf, size);
  EVP_DecryptFinal_ex(key->block_dec, buf + dstLen, &tmpLen);
  dstLen += tmpLen;

  if (dstLen != size) {
    RLOG(ERROR) << "decoding " << size << " bytes, got back " << dstLen << " ("
                << tmpLen << " in final_ex)";
    return false;
  }

  return true;
}

bool SSL_Cipher::Enabled() { return true; }

}  // namespace encfs
