/*
 * Copyright (c) 2010 .SE (The Internet Infrastructure Foundation)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// TODO: Store context in securely allocated memory

/*****************************************************************************
 BotanSymmetricAlgorithm.cpp

 Botan symmetric algorithm implementation
 *****************************************************************************/

#include "config.h"
#include "BotanSymmetricAlgorithm.h"
#include "BotanUtil.h"
#include "salloc.h"
#include <iostream>

#include <botan/symkey.h>
#include <botan/parsing.h>
#include <botan/version.h>
#include <botan/filters.h>
#include <botan/aead.h>

#include "Botan_ecb.h"

#if BOTAN_VERSION_CODE <= BOTAN_VERSION_CODE_FOR(2,11,0)
   #include <botan/cipher_filter.h>
#endif

std::vector<std::string> split_on_delim(const std::string& str, char delim)
{
	std::vector<std::string> elems;
	if(str.empty()) return elems;

	std::string substr;
	for (auto i = str.begin(); i != str.end(); ++i)
	{
		if (*i == delim)
		{
			if (!substr.empty())
				elems.push_back(substr);
			substr.clear();
		}
		else
			substr += *i;
	}

	if (!substr.empty())
		elems.push_back(substr);

	return elems;
}


// Constructor
BotanSymmetricAlgorithm::BotanSymmetricAlgorithm()
{
	cryption = NULL;
	maximumBytes = Botan::BigInt(1);
	maximumBytes.flip_sign();
	counterBytes = Botan::BigInt(0);
}

// Destructor
BotanSymmetricAlgorithm::~BotanSymmetricAlgorithm()
{
	delete cryption;
	cryption = NULL;
}

// Encryption functions
bool BotanSymmetricAlgorithm::encryptInit(const SymmetricKey* key, const SymMode::Type mode /* = SymMode:CBC */, const ByteString& IV /* = ByteString()*/, bool padding /* = true */, size_t counterBits /* = 0 */, const ByteString& aad /* = ByteString() */, size_t tagBytes /* = 0 */)
{
	// Call the superclass initialiser
	if (!SymmetricAlgorithm::encryptInit(key, mode, IV, padding, counterBits, aad, tagBytes))
	{
		return false;
	}

	// Check the IV
	if (mode != SymMode::GCM && (IV.size() > 0) && (IV.size() != getBlockSize()))
	{
		ERROR_MSG("Invalid IV size (%d bytes, expected %d bytes)", IV.size(), getBlockSize());

		ByteString dummy;
		SymmetricAlgorithm::encryptFinal(dummy);

		return false;
	}

	ByteString iv;

	if (IV.size() > 0)
	{
		iv = IV;
	}
	else
	{
		iv.wipe(getBlockSize());
	}

	// Check the counter bits
	if (counterBits > 0)
	{
		Botan::BigInt counter = BotanUtil::byteString2bigInt(iv);
		counter.mask_bits(counterBits);

		// Reverse the bits
		while (counterBits > 0)
		{
			counterBits--;
			if (counter.get_bit(counterBits))
			{
				counter.clear_bit(counterBits);
			}
			else
			{
				counter.set_bit(counterBits);
			}
		}

		// Set the maximum bytes
		maximumBytes = (counter + 1) * getBlockSize();
		counterBytes = Botan::BigInt(0);
	}
	else
	{
		maximumBytes = Botan::BigInt(1);
		maximumBytes.flip_sign();
	}

	// Determine the cipher
	std::string cipherName = getCipher();

	if (cipherName == "")
	{
		ERROR_MSG("Invalid encryption cipher");

		ByteString dummy;
		SymmetricAlgorithm::encryptFinal(dummy);

		return false;
	}

	// Allocate the context
	try
	{
		Botan::SymmetricKey botanKey = Botan::SymmetricKey(key->getKeyBits().const_byte_str(), key->getKeyBits().size());
		if (mode == SymMode::ECB)
		{
			// ECB cipher mode was dropped in Botan 2.0
			const std::vector<std::string> algo_parts = split_on_delim(cipherName, '/');
			const std::string cipher_name = algo_parts[0];
			bool with_pkcs7_padding;
			if (algo_parts.size() == 3 && algo_parts[2] == "PKCS7")
			{
				with_pkcs7_padding = true;
			}
			else
			{
				with_pkcs7_padding = false;
			}
			std::unique_ptr<Botan::BlockCipher> bc(Botan::BlockCipher::create(cipher_name));
			Botan::Keyed_Filter* cipher = new Botan::Cipher_Mode_Filter(new Botan::ECB_Encryption(bc.release(), with_pkcs7_padding));
			cipher->set_key(botanKey);
			cryption = new Botan::Pipe(cipher);
		}
		else if (mode == SymMode::GCM)
		{
			Botan::AEAD_Mode* aead = Botan::get_aead(cipherName, Botan::ENCRYPTION);
			aead->set_key(botanKey);
			aead->set_associated_data(aad.const_byte_str(), aad.size());

			Botan::InitializationVector botanIV = Botan::InitializationVector(IV.const_byte_str(), IV.size());
			Botan::Keyed_Filter* filter = new Botan::Cipher_Mode_Filter(aead);
			filter->set_iv(botanIV);
			cryption = new Botan::Pipe(filter);
		}
		else
		{
			Botan::InitializationVector botanIV = Botan::InitializationVector(IV.const_byte_str(), IV.size());
			cryption = new Botan::Pipe(Botan::get_cipher(cipherName, botanKey, botanIV, Botan::ENCRYPTION));
		}
		cryption->start_msg();
	}
	catch (std::exception &e)
	{
		ERROR_MSG("Failed to create the encryption token: %s", e.what());

		ByteString dummy;
		SymmetricAlgorithm::encryptFinal(dummy);

		delete cryption;
		cryption = NULL;

		return false;
	}

	return true;
}

bool BotanSymmetricAlgorithm::encryptUpdate(const ByteString& data, ByteString& encryptedData)
{
	if (!SymmetricAlgorithm::encryptUpdate(data, encryptedData))
	{
		delete cryption;
		cryption = NULL;

		return false;
	}

	// Write data
	try
	{
		if (data.size() > 0)
			cryption->write(data.const_byte_str(), data.size());
	}
	catch (...)
	{
		ERROR_MSG("Failed to write to the encryption token");

		ByteString dummy;
		SymmetricAlgorithm::encryptFinal(dummy);

		delete cryption;
		cryption = NULL;

		return false;
	}

	// Count number of bytes written
	if (maximumBytes.is_positive())
	{
		counterBytes += data.size();
	}

	// Read data
	int bytesRead = 0;
	try
	{
		size_t outLen = cryption->remaining();
		encryptedData.resize(outLen);
		if (outLen > 0)
			bytesRead = cryption->read(&encryptedData[0], outLen);
	}
	catch (...)
	{
		ERROR_MSG("Failed to encrypt the data");

		ByteString dummy;
		SymmetricAlgorithm::encryptFinal(dummy);

		delete cryption;
		cryption = NULL;

		return false;
	}

	// Resize the output block
	encryptedData.resize(bytesRead);
	currentBufferSize -= bytesRead;

	return true;
}

bool BotanSymmetricAlgorithm::encryptFinal(ByteString& encryptedData)
{
	if (!SymmetricAlgorithm::encryptFinal(encryptedData))
	{
		delete cryption;
		cryption = NULL;

		return false;
	}

	// Read data
	int bytesRead = 0;
	try
	{
		cryption->end_msg();
		size_t outLen = cryption->remaining();
		encryptedData.resize(outLen);
		if (outLen > 0)
			bytesRead = cryption->read(&encryptedData[0], outLen);
	}
	catch (...)
	{
		ERROR_MSG("Failed to encrypt the data");

		delete cryption;
		cryption = NULL;

		return false;
	}

	// Clean up
	delete cryption;
	cryption = NULL;

	// Resize the output block
	encryptedData.resize(bytesRead);

	return true;
}

// Decryption functions
bool BotanSymmetricAlgorithm::decryptInit(const SymmetricKey* key, const SymMode::Type mode /* = SymMode::CBC */, const ByteString& IV /* = ByteString() */, bool padding /* = true */, size_t counterBits /* = 0 */, const ByteString& aad /* = ByteString() */, size_t tagBytes /* = 0 */)
{
	// Call the superclass initialiser
	if (!SymmetricAlgorithm::decryptInit(key, mode, IV, padding, counterBits, aad, tagBytes))
	{
		return false;
	}

	// Check the IV
	if (mode != SymMode::GCM && (IV.size() > 0) && (IV.size() != getBlockSize()))
	{
		ERROR_MSG("Invalid IV size (%d bytes, expected %d bytes)", IV.size(), getBlockSize());

		ByteString dummy;
		SymmetricAlgorithm::decryptFinal(dummy);

		return false;
	}

	ByteString iv;

	if (IV.size() > 0)
	{
		iv = IV;
	}
	else
	{
		iv.wipe(getBlockSize());
	}

	// Check the counter bits
	if (counterBits > 0)
	{
		Botan::BigInt counter = BotanUtil::byteString2bigInt(iv);
		counter.mask_bits(counterBits);

		// Reverse the bits
		while (counterBits > 0)
		{
			counterBits--;
			if (counter.get_bit(counterBits))
			{
				counter.clear_bit(counterBits);
			}
			else
			{
				counter.set_bit(counterBits);
			}
		}

		// Set the maximum bytes
		maximumBytes = (counter + 1) * getBlockSize();
		counterBytes = Botan::BigInt(0);
	}
	else
	{
		maximumBytes = Botan::BigInt(1);
		maximumBytes.flip_sign();
	}

	// Determine the cipher class
	std::string cipherName = getCipher();

	if (cipherName == "")
	{
		ERROR_MSG("Invalid decryption cipher");

		ByteString dummy;
		SymmetricAlgorithm::decryptFinal(dummy);

		return false;
	}

	// Allocate the context
	try
	{
		Botan::SymmetricKey botanKey = Botan::SymmetricKey(key->getKeyBits().const_byte_str(), key->getKeyBits().size());
		if (mode == SymMode::ECB)
		{
			// ECB cipher mode was dropped in Botan 2.0
			const std::vector<std::string> algo_parts = split_on_delim(cipherName, '/');
			const std::string cipher_name = algo_parts[0];
			bool with_pkcs7_padding;
			if (algo_parts.size() == 3 && algo_parts[2] == "PKCS7")
			{
				with_pkcs7_padding = true;
			}
			else
			{
				with_pkcs7_padding = false;
			}
			std::unique_ptr<Botan::BlockCipher> bc(Botan::BlockCipher::create(cipher_name));
			Botan::Keyed_Filter* cipher = new Botan::Cipher_Mode_Filter(new Botan::ECB_Decryption(bc.release(),with_pkcs7_padding));
			cipher->set_key(botanKey);
			cryption = new Botan::Pipe(cipher);
		}
		else if (mode == SymMode::GCM)
		{
			Botan::AEAD_Mode* aead = Botan::get_aead(cipherName, Botan::DECRYPTION);
			aead->set_key(botanKey);
			aead->set_associated_data(aad.const_byte_str(), aad.size());

			Botan::InitializationVector botanIV = Botan::InitializationVector(IV.const_byte_str(), IV.size());
			Botan::Keyed_Filter* filter = new Botan::Cipher_Mode_Filter(aead);
			filter->set_iv(botanIV);
			cryption = new Botan::Pipe(filter);
		}
		else
		{
			Botan::InitializationVector botanIV = Botan::InitializationVector(IV.const_byte_str(), IV.size());
			cryption = new Botan::Pipe(Botan::get_cipher(cipherName, botanKey, botanIV, Botan::DECRYPTION));
		}
		cryption->start_msg();
	}
	catch (...)
	{
		ERROR_MSG("Failed to create the decryption token");

		ByteString dummy;
		SymmetricAlgorithm::decryptFinal(dummy);

		delete cryption;
		cryption = NULL;

		return false;
	}

	return true;
}

bool BotanSymmetricAlgorithm::decryptUpdate(const ByteString& encryptedData, ByteString& data)
{
	if (!SymmetricAlgorithm::decryptUpdate(encryptedData, data))
	{
		delete cryption;
		cryption = NULL;

		return false;
	}

	// AEAD ciphers should not return decrypted data until final is called
	if (currentCipherMode == SymMode::GCM)
	{
		data.resize(0);
		return true;
	}

	// Write data
	try
	{
		if (encryptedData.size() > 0)
			cryption->write(encryptedData.const_byte_str(), encryptedData.size());
	}
	catch (...)
	{
		ERROR_MSG("Failed to write to the decryption token");

		ByteString dummy;
		SymmetricAlgorithm::decryptFinal(dummy);

		delete cryption;
		cryption = NULL;

		return false;
	}

	// Count number of bytes written
	if (maximumBytes.is_positive())
	{
		counterBytes += encryptedData.size();
	}

	// Read data
	int bytesRead = 0;
	try
	{
		size_t outLen = cryption->remaining();
		data.resize(outLen);
		if (outLen > 0)
			bytesRead = cryption->read(&data[0], outLen);
	}
	catch (...)
	{
		ERROR_MSG("Failed to decrypt the data");

		ByteString dummy;
		SymmetricAlgorithm::decryptFinal(dummy);

		delete cryption;
		cryption = NULL;

		return false;
	}

	// Resize the output block
	data.resize(bytesRead);
	currentBufferSize -= bytesRead;

	return true;
}

bool BotanSymmetricAlgorithm::decryptFinal(ByteString& data)
{
	SymMode::Type mode = currentCipherMode;
	ByteString aeadBuffer = currentAEADBuffer;

	if (!SymmetricAlgorithm::decryptFinal(data))
	{
		delete cryption;
		cryption = NULL;

		return false;
	}

	if (mode == SymMode::GCM)
	{
		// Write data
		try
		{
			if (aeadBuffer.size() > 0)
				cryption->write(aeadBuffer.const_byte_str(), aeadBuffer.size());
		}
		catch (...)
		{
			ERROR_MSG("Failed to write to the decryption token");

			delete cryption;
			cryption = NULL;

			return false;
		}
	}

	// Read data
	int bytesRead = 0;
	try
	{
		cryption->end_msg();
		size_t outLen = cryption->remaining();
		data.resize(outLen);
		if (outLen > 0)
			bytesRead = cryption->read(&data[0], outLen);
	}
	catch (std::exception &e)
	{
		ERROR_MSG("Failed to decrypt the data: %s", e.what());

		delete cryption;
		cryption = NULL;

		return false;
	}

	// Clean up
	delete cryption;
	cryption = NULL;

	// Resize the output block
	data.resize(bytesRead);

	return true;
}

// Check if more bytes of data can be encrypted
bool BotanSymmetricAlgorithm::checkMaximumBytes(unsigned long bytes)
{
	if (maximumBytes.is_negative()) return true;

	if (maximumBytes.cmp(counterBytes + bytes) >= 0) return true;

	return false;
}
