/*
* Block Ciphers via OpenSSL
* (C) 1999-2010,2015 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/block_cipher.h>
#include <botan/internal/openssl.h>
#include <openssl/evp.h>

namespace Botan {

namespace {

class OpenSSL_BlockCipher final : public BlockCipher
   {
   public:
      OpenSSL_BlockCipher(const std::string& name,
                          const EVP_CIPHER* cipher);

      OpenSSL_BlockCipher(const std::string& name,
                          const EVP_CIPHER* cipher,
                          size_t kl_min, size_t kl_max, size_t kl_mod);

      ~OpenSSL_BlockCipher();

      void clear() override;
      std::string provider() const override { return "openssl"; }
      std::string name() const override { return m_cipher_name; }
      BlockCipher* clone() const override;

      size_t block_size() const override { return m_block_sz; }

      Key_Length_Specification key_spec() const override { return m_cipher_key_spec; }

      void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override
         {
         verify_key_set(m_key_set);
         int out_len = 0;
         if(!EVP_EncryptUpdate(m_encrypt, out, &out_len, in, blocks * m_block_sz))
            throw OpenSSL_Error("EVP_EncryptUpdate", ERR_get_error());
         }

      void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override
         {
         verify_key_set(m_key_set);
         int out_len = 0;
         if(!EVP_DecryptUpdate(m_decrypt, out, &out_len, in, blocks * m_block_sz))
            throw OpenSSL_Error("EVP_DecryptUpdate", ERR_get_error());
         }

      void key_schedule(const uint8_t key[], size_t key_len) override;

      size_t m_block_sz;
      Key_Length_Specification m_cipher_key_spec;
      std::string m_cipher_name;
      EVP_CIPHER_CTX *m_encrypt;
      EVP_CIPHER_CTX *m_decrypt;
      bool m_key_set;
   };

OpenSSL_BlockCipher::OpenSSL_BlockCipher(const std::string& algo_name,
                                         const EVP_CIPHER* algo) :
   m_block_sz(EVP_CIPHER_block_size(algo)),
   m_cipher_key_spec(EVP_CIPHER_key_length(algo)),
   m_cipher_name(algo_name),
   m_key_set(false)
   {
   if(EVP_CIPHER_mode(algo) != EVP_CIPH_ECB_MODE)
      throw Invalid_Argument("OpenSSL_BlockCipher: Non-ECB EVP was passed in");

   m_encrypt = EVP_CIPHER_CTX_new();
   m_decrypt = EVP_CIPHER_CTX_new();
   if (m_encrypt == nullptr || m_decrypt == nullptr)
      throw OpenSSL_Error("Can't allocate new context", ERR_get_error());

   EVP_CIPHER_CTX_init(m_encrypt);
   EVP_CIPHER_CTX_init(m_decrypt);

   if(!EVP_EncryptInit_ex(m_encrypt, algo, nullptr, nullptr, nullptr))
      throw OpenSSL_Error("EVP_EncryptInit_ex", ERR_get_error());
   if(!EVP_DecryptInit_ex(m_decrypt, algo, nullptr, nullptr, nullptr))
      throw OpenSSL_Error("EVP_DecryptInit_ex", ERR_get_error());

   if(!EVP_CIPHER_CTX_set_padding(m_encrypt, 0))
      throw OpenSSL_Error("EVP_CIPHER_CTX_set_padding encrypt", ERR_get_error());
   if(!EVP_CIPHER_CTX_set_padding(m_decrypt, 0))
      throw OpenSSL_Error("EVP_CIPHER_CTX_set_padding decrypt", ERR_get_error());
   }

OpenSSL_BlockCipher::OpenSSL_BlockCipher(const std::string& algo_name,
                                         const EVP_CIPHER* algo,
                                         size_t key_min,
                                         size_t key_max,
                                         size_t key_mod) :
   m_block_sz(EVP_CIPHER_block_size(algo)),
   m_cipher_key_spec(key_min, key_max, key_mod),
   m_cipher_name(algo_name),
   m_key_set(false)
   {
   if(EVP_CIPHER_mode(algo) != EVP_CIPH_ECB_MODE)
      throw Invalid_Argument("OpenSSL_BlockCipher: Non-ECB EVP was passed in");

   m_encrypt = EVP_CIPHER_CTX_new();
   m_decrypt = EVP_CIPHER_CTX_new();
   if (m_encrypt == nullptr || m_decrypt == nullptr)
      throw OpenSSL_Error("Can't allocate new context", ERR_get_error());

   EVP_CIPHER_CTX_init(m_encrypt);
   EVP_CIPHER_CTX_init(m_decrypt);

   if(!EVP_EncryptInit_ex(m_encrypt, algo, nullptr, nullptr, nullptr))
      throw OpenSSL_Error("EVP_EncryptInit_ex", ERR_get_error());
   if(!EVP_DecryptInit_ex(m_decrypt, algo, nullptr, nullptr, nullptr))
      throw OpenSSL_Error("EVP_DecryptInit_ex", ERR_get_error());

   if(!EVP_CIPHER_CTX_set_padding(m_encrypt, 0))
      throw OpenSSL_Error("EVP_CIPHER_CTX_set_padding encrypt", ERR_get_error());
   if(!EVP_CIPHER_CTX_set_padding(m_decrypt, 0))
      throw OpenSSL_Error("EVP_CIPHER_CTX_set_padding decrypt", ERR_get_error());
   }

OpenSSL_BlockCipher::~OpenSSL_BlockCipher()
   {
   EVP_CIPHER_CTX_cleanup(m_encrypt);
   EVP_CIPHER_CTX_cleanup(m_decrypt);

   EVP_CIPHER_CTX_free(m_encrypt);
   EVP_CIPHER_CTX_free(m_decrypt);
   }

/*
* Set the key
*/
void OpenSSL_BlockCipher::key_schedule(const uint8_t key[], size_t length)
   {
   secure_vector<uint8_t> full_key(key, key + length);

   if(m_cipher_name == "TripleDES" && length == 16)
      {
      full_key += std::make_pair(key, 8);
      }
   else
      {
      if(EVP_CIPHER_CTX_set_key_length(m_encrypt, length) == 0 ||
         EVP_CIPHER_CTX_set_key_length(m_decrypt, length) == 0)
         throw Invalid_Argument("OpenSSL_BlockCipher: Bad key length for " +
                                m_cipher_name);
      }

   if(!EVP_EncryptInit_ex(m_encrypt, nullptr, nullptr, full_key.data(), nullptr))
      throw OpenSSL_Error("EVP_EncryptInit_ex", ERR_get_error());
   if(!EVP_DecryptInit_ex(m_decrypt, nullptr, nullptr, full_key.data(), nullptr))
      throw OpenSSL_Error("EVP_DecryptInit_ex", ERR_get_error());

   m_key_set = true;
   }

/*
* Return a clone of this object
*/
BlockCipher* OpenSSL_BlockCipher::clone() const
   {
   return new OpenSSL_BlockCipher(m_cipher_name,
                                  EVP_CIPHER_CTX_cipher(m_encrypt),
                                  m_cipher_key_spec.minimum_keylength(),
                                  m_cipher_key_spec.maximum_keylength(),
                                  m_cipher_key_spec.keylength_multiple());
   }

/*
* Clear memory of sensitive data
*/
void OpenSSL_BlockCipher::clear()
   {
   const EVP_CIPHER* algo = EVP_CIPHER_CTX_cipher(m_encrypt);

   m_key_set = false;

   if(!EVP_CIPHER_CTX_cleanup(m_encrypt))
      throw OpenSSL_Error("EVP_CIPHER_CTX_cleanup encrypt", ERR_get_error());
   if(!EVP_CIPHER_CTX_cleanup(m_decrypt))
      throw OpenSSL_Error("EVP_CIPHER_CTX_cleanup decrypt", ERR_get_error());
   EVP_CIPHER_CTX_init(m_encrypt);
   EVP_CIPHER_CTX_init(m_decrypt);
   if(!EVP_EncryptInit_ex(m_encrypt, algo, nullptr, nullptr, nullptr))
      throw OpenSSL_Error("EVP_EncryptInit_ex", ERR_get_error());
   if(!EVP_DecryptInit_ex(m_decrypt, algo, nullptr, nullptr, nullptr))
      throw OpenSSL_Error("EVP_DecryptInit_ex", ERR_get_error());
   if(!EVP_CIPHER_CTX_set_padding(m_encrypt, 0))
      throw OpenSSL_Error("EVP_CIPHER_CTX_set_padding encrypt", ERR_get_error());
   if(!EVP_CIPHER_CTX_set_padding(m_decrypt, 0))
      throw OpenSSL_Error("EVP_CIPHER_CTX_set_padding decrypt", ERR_get_error());
   }

}

std::unique_ptr<BlockCipher>
make_openssl_block_cipher(const std::string& name)
   {
#define MAKE_OPENSSL_BLOCK(evp_fn) \
   std::unique_ptr<BlockCipher>(new OpenSSL_BlockCipher(name, evp_fn()))
#define MAKE_OPENSSL_BLOCK_KEYLEN(evp_fn, kl_min, kl_max, kl_mod)       \
   std::unique_ptr<BlockCipher>(new OpenSSL_BlockCipher(name, evp_fn(), kl_min, kl_max, kl_mod))

#if defined(BOTAN_HAS_AES) && !defined(OPENSSL_NO_AES)
   if(name == "AES-128")
      return MAKE_OPENSSL_BLOCK(EVP_aes_128_ecb);
   if(name == "AES-192")
      return MAKE_OPENSSL_BLOCK(EVP_aes_192_ecb);
   if(name == "AES-256")
      return MAKE_OPENSSL_BLOCK(EVP_aes_256_ecb);
#endif

#if defined(BOTAN_HAS_CAMELLIA) && !defined(OPENSSL_NO_CAMELLIA)
   if(name == "Camellia-128")
      return MAKE_OPENSSL_BLOCK(EVP_camellia_128_ecb);
   if(name == "Camellia-192")
      return MAKE_OPENSSL_BLOCK(EVP_camellia_192_ecb);
   if(name == "Camellia-256")
      return MAKE_OPENSSL_BLOCK(EVP_camellia_256_ecb);
#endif

#if defined(BOTAN_HAS_DES) && !defined(OPENSSL_NO_DES)
   if(name == "TripleDES")
      return MAKE_OPENSSL_BLOCK_KEYLEN(EVP_des_ede3_ecb, 16, 24, 8);
#endif

   return nullptr;
   }

}

