/*
 * Copyright 2002-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * XSEC
 *
 * XSECCryptoSymmetricKey := Bulk encryption algorithms should all be
 *							implemented via this interface
 *
 * Author(s): Berin Lautenbach
 *
 * $Id: OpenSSLCryptoSymmetricKey.cpp 566299 2007-08-15 18:51:47Z scantor $
 *
 */

#include <xsec/framework/XSECDefs.hpp>
#include <iostream>
#include <xsec/enc/OpenSSL/OpenSSLCryptoSymmetricKey.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/enc/XSECCryptoException.hpp>

#include <xercesc/util/Janitor.hpp>
XERCES_CPP_NAMESPACE_USE;

#if defined (HAVE_OPENSSL)

#include <string.h>

#include <openssl/rand.h>

// --------------------------------------------------------------------------------
//           Constructors and Destructors
// --------------------------------------------------------------------------------

OpenSSLCryptoSymmetricKey::OpenSSLCryptoSymmetricKey(XSECCryptoSymmetricKey::SymmetricKeyType type) :
m_keyType(type),
m_keyMode(MODE_CBC),
m_keyBuf(""),
m_keyLen(0),
m_initialised(false) {

	EVP_CIPHER_CTX_init(&m_ctx);
	m_keyBuf.isSensitive();

}

OpenSSLCryptoSymmetricKey::~OpenSSLCryptoSymmetricKey() {

	// Clean up the context

	EVP_CIPHER_CTX_cleanup(&m_ctx);
}

// --------------------------------------------------------------------------------
//           Basic Key interface methods
// --------------------------------------------------------------------------------

XSECCryptoSymmetricKey::SymmetricKeyType OpenSSLCryptoSymmetricKey::getSymmetricKeyType() const {

	return m_keyType;

}

const XMLCh * OpenSSLCryptoSymmetricKey::getProviderName() const {

	return DSIGConstants::s_unicodeStrPROVOpenSSL;

}

XSECCryptoKey * OpenSSLCryptoSymmetricKey::clone() const {

	OpenSSLCryptoSymmetricKey * ret;

	XSECnew(ret, OpenSSLCryptoSymmetricKey(m_keyType));
	ret->m_keyMode = m_keyMode;
	ret->m_keyLen = m_keyLen;
	ret->m_keyBuf = m_keyBuf;

	return ret;

}

// --------------------------------------------------------------------------------
//           Store the key value
// --------------------------------------------------------------------------------

void OpenSSLCryptoSymmetricKey::setKey(const unsigned char * key, unsigned int keyLen) {

	m_keyBuf.sbMemcpyIn(key, keyLen);
	m_keyLen = keyLen;

}

// --------------------------------------------------------------------------------
//           Decrypt
// --------------------------------------------------------------------------------

int OpenSSLCryptoSymmetricKey::decryptCtxInit(const unsigned char * iv) {

	// Returns amount of IV data used (in bytes)
	// Sets m_initialised iff the key is OK and the IV is OK.

	if (m_initialised)
		return 0;

	if (m_keyLen == 0) {

		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Cannot initialise without key"); 

	}

	// Set up the context according to the required cipher type

	switch (m_keyType) {

	case (XSECCryptoSymmetricKey::KEY_3DES_192) :

		// A 3DES key

		if (m_keyMode == MODE_CBC) {

			if (iv == NULL) {

				return 0;	// Cannot initialise without an IV

			}

			/* Do not use "_ex" calls yet - as want backwards compatibility
			   with 0.9.6 */

#if defined(XSEC_OPENSSL_CONST_BUFFERS)
			EVP_DecryptInit(&m_ctx, EVP_des_ede3_cbc(),m_keyBuf.rawBuffer(), iv);
#else
			EVP_DecryptInit(&m_ctx, EVP_des_ede3_cbc(),(unsigned char *) m_keyBuf.rawBuffer(), (unsigned char *) iv);
#endif
			m_ivSize = 8;
		}
		else {
#if defined(XSEC_OPENSSL_CONST_BUFFERS)
			EVP_DecryptInit(&m_ctx, EVP_des_ede3(), m_keyBuf.rawBuffer(), NULL);
#else
			EVP_DecryptInit(&m_ctx, EVP_des_ede3(), (unsigned char *) m_keyBuf.rawBuffer(), NULL);
#endif
			m_ivSize = 0;
		}


		m_blockSize = 8;
		break;

#if defined (XSEC_OPENSSL_HAVE_AES)

	case (XSECCryptoSymmetricKey::KEY_AES_128) :

		// An AES key

		if (m_keyMode == MODE_CBC) {

			if (iv == NULL) {

				return 0;	// Cannot initialise without an IV

			}

			EVP_DecryptInit_ex(&m_ctx, EVP_aes_128_cbc(), NULL, m_keyBuf.rawBuffer(), iv);

		}
		else {

			EVP_DecryptInit_ex(&m_ctx, EVP_aes_128_ecb(), NULL, m_keyBuf.rawBuffer(), NULL);

		}

		m_blockSize = 16;
		break;
	
	case (XSECCryptoSymmetricKey::KEY_AES_192) :

		// An AES key

		if (m_keyMode == MODE_CBC) {

			if (iv == NULL) {

				return 0;	// Cannot initialise without an IV

			}

			EVP_DecryptInit_ex(&m_ctx, EVP_aes_192_cbc(), NULL, m_keyBuf.rawBuffer(), iv);

		}
		else {

			EVP_DecryptInit_ex(&m_ctx, EVP_aes_192_ecb(), NULL, m_keyBuf.rawBuffer(), NULL);

		}

		m_blockSize = 16;
		break;

	case (XSECCryptoSymmetricKey::KEY_AES_256) :

		// An AES key

		if (m_keyMode == MODE_CBC) {

			if (iv == NULL) {

				return 0;	// Cannot initialise without an IV

			}

			EVP_DecryptInit_ex(&m_ctx, EVP_aes_256_cbc(), NULL, m_keyBuf.rawBuffer(), iv);

		}
		else {

			EVP_DecryptInit_ex(&m_ctx, EVP_aes_256_ecb(), NULL, m_keyBuf.rawBuffer(), NULL);

		}


		m_blockSize = 16;

		break;
#else 

	case (XSECCryptoSymmetricKey::KEY_AES_128) :
	case (XSECCryptoSymmetricKey::KEY_AES_192) :
	case (XSECCryptoSymmetricKey::KEY_AES_256) :

		throw XSECCryptoException(XSECCryptoException::UnsupportedAlgorithm,
			 "OpenSSL:SymmetricKey - AES not supported in this version of OpenSSL");

#endif /* XSEC_OPENSSL_HAVE_AES */
	
	default :

		// Cannot do this without an IV
		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Unknown key type"); 

	}

	// Setup ivSize
	m_ivSize = (m_keyMode == MODE_CBC ? m_blockSize : 0);

	// Reset some parameters
	m_initialised = true;
	m_bytesInLastBlock = 0;

	// Disable OpenSSL padding - The interop samples have broken PKCS padding - AARGHH

#if defined (XSEC_OPENSSL_CANSET_PADDING)
	EVP_CIPHER_CTX_set_padding(&m_ctx, 0);
#endif

	// Return number of bytes chewed up by IV
	return m_ivSize;
}


bool OpenSSLCryptoSymmetricKey::decryptInit(bool doPad, 
											SymmetricKeyMode mode,
											const unsigned char * iv) {

	m_doPad = doPad;
	m_keyMode = mode;
	m_initialised = false;
	decryptCtxInit(iv);
	return true;

}

unsigned int OpenSSLCryptoSymmetricKey::decrypt(const unsigned char * inBuf, 
								 unsigned char * plainBuf, 
								 unsigned int inLength,
								 unsigned int maxOutLength) {



	// NOTE: This won't actually stop OpenSSL blowing the buffer, so the onus is
	// on the caller.

#if 0

	/* normally commented out code to determine endian problems */
	unsigned int i;
	unsigned char e[2048];
	unsigned char * inBuf1 = (unsigned char *) inBuf;
	if (inLength < 2048) {
		memcpy(e, inBuf, inLength);
		for (i = 0; i < inLength;++i) {
			inBuf1[i] = e[inLength - 1 - i];
		}
	}

#endif

	unsigned int offset = 0;
	if (!m_initialised) {
		offset = decryptCtxInit(inBuf);
		if (offset > inLength) {
			throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Not enough data passed in to get IV");
		}
	}

	int outl = maxOutLength;

	if (inLength - offset + m_bytesInLastBlock > maxOutLength) {
		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSLSymmetricKey::decrypt - Not enough space in output buffer");
	}

#if defined (XSEC_OPENSSL_CONST_BUFFERS)
	if (EVP_DecryptUpdate(&m_ctx, &plainBuf[m_bytesInLastBlock], &outl, &inBuf[offset], inLength - offset) == 0) {
#else
	if (EVP_DecryptUpdate(&m_ctx, &plainBuf[m_bytesInLastBlock], &outl, (unsigned char *) &inBuf[offset], inLength - offset) == 0) {
#endif
		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Error during OpenSSL decrypt"); 

	}

	// Store the last block if we are padding
	if (m_doPad && m_blockSize > 0 && outl >= m_blockSize) {

		// Output will always be *at least* the blocksize

		// Copy the previous last block into the start of the output buffer
		memcpy(plainBuf, m_lastBlock, m_bytesInLastBlock);

		// Copy the tail into the buffer
		memcpy(m_lastBlock, &plainBuf[outl + m_bytesInLastBlock - m_blockSize], m_blockSize);

		outl = outl + m_bytesInLastBlock - m_blockSize;
		m_bytesInLastBlock = m_blockSize;

	}

	return outl;

}

unsigned int OpenSSLCryptoSymmetricKey::decryptFinish(unsigned char * plainBuf,
													  unsigned int maxOutLength) {

	int outl = maxOutLength;
	m_initialised = false;

#if defined (XSEC_OPENSSL_CANSET_PADDING)

	if (EVP_DecryptFinal(&m_ctx, plainBuf, &outl) == 0) {

		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Error during OpenSSL decrypt finalisation"); 

	}
	
	if (outl > 0) {
	
		// Should never see any bytes output, as we are not padding

		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Unexpectedly recieved bytes from EVP_DecryptFinal_ex"); 

	}

	// Calculate any padding issues
	if (m_doPad && m_bytesInLastBlock == m_blockSize) {

		outl = m_blockSize - m_lastBlock[m_blockSize - 1];

		if (outl > m_blockSize || outl < 0) {
			
			throw XSECCryptoException(XSECCryptoException::SymmetricError,
				"OpenSSL:SymmetricKey::decryptFinish - Out of range padding value in final block"); 
	
		}

		memcpy(plainBuf, m_lastBlock, outl);

	}

	if ((unsigned int) outl > maxOutLength) {
		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSLSymmetricKey::decryptFinish - **WARNING** - Plaintext output > maxOutLength!"); 
	}

	return outl;

#else

	/* Working with a version of OpenSSL that *always* performs padding
	   so we need to work around it */
	unsigned char *scrPlainBuf;
	unsigned char *cipherBuf;
	scrPlainBuf = new unsigned char[3 * m_blockSize];
	ArrayJanitor<unsigned char> j_scrPlainBuf(scrPlainBuf);
	cipherBuf = new unsigned char[m_blockSize];
	ArrayJanitor<unsigned char> j_cipherBuf(cipherBuf);

	/* Zeroise the cipher buffer */
	memset(cipherBuf, 0, m_blockSize);

	unsigned int offset = 0;

	/* Get any previous bytes from the m_lastBlock */
	if (m_bytesInLastBlock > 0 && m_bytesInLastBlock <= m_blockSize) {
		memcpy(scrPlainBuf, m_lastBlock, m_bytesInLastBlock);
		offset = m_bytesInLastBlock;
	}

	outl = m_blockSize;

	/* This is really ugly - but we have to trick OpenSSL into thinking there
       is more, so that it sends us the lasts block with the padding in it.
       We can then clean that up ourselves
	*/

	if (EVP_DecryptUpdate(&m_ctx, &scrPlainBuf[offset], &outl, cipherBuf, m_blockSize) == 0) {
		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Error cecrypting final block during OpenSSL");
	} 

	outl += offset;

	if (m_doPad && outl > 0) {

		/* Strip any padding */
		outl -= scrPlainBuf[outl - 1];

		if (outl > (2 * m_blockSize) || outl < 0) {
			
			throw XSECCryptoException(XSECCryptoException::SymmetricError,
				"OpenSSL:SymmetricKey::decryptFinish - Out of range padding value in final block"); 
	
		}

	}

	if (outl > (int) maxOutLength) {
		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSLSymmetricKey::decryptFinish - **WARNING** - Plaintext output > maxOutLength!"); 
	}

	if (outl > 0) {

		memcpy(plainBuf, scrPlainBuf, outl);

	}

	return outl;
#endif

}

// --------------------------------------------------------------------------------
//           Encrypt
// --------------------------------------------------------------------------------

bool OpenSSLCryptoSymmetricKey::encryptInit(bool doPad, 
											SymmetricKeyMode mode,
											const unsigned char * iv) {

	if (m_initialised == true)
		return true;

	m_doPad = doPad;
	m_keyMode = mode;
	
	if (m_keyLen == 0) {

		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Cannot initialise without key"); 

	}

	// Do some parameter initialisation
	m_initialised = true;
	m_bytesInLastBlock = 0;

	// Set up the context according to the required cipher type

	const unsigned char * usedIV = NULL;
	unsigned char genIV[256];

	// Tell the library that the IV still has to be sent

	m_ivSent = false;

	switch (m_keyType) {

	case (XSECCryptoSymmetricKey::KEY_3DES_192) :

		// A 3DES key

		if (m_keyMode == MODE_CBC) {

			if (iv == NULL) {
				
				bool res = ((RAND_status() == 1) && (RAND_bytes(genIV, 8) == 1));
				if (res == false) {
					throw XSECCryptoException(XSECCryptoException::SymmetricError,
						"OpenSSL:SymmetricKey - Error generating random IV");
				}

				usedIV = genIV;

			}
			else
				usedIV = iv;

#if defined (XSEC_OPENSSL_CONST_BUFFERS)
			EVP_EncryptInit(&m_ctx, EVP_des_ede3_cbc(), m_keyBuf.rawBuffer(), usedIV);
#else
			EVP_EncryptInit(&m_ctx, EVP_des_ede3_cbc(), (unsigned char *) m_keyBuf.rawBuffer(), (unsigned char *) usedIV);
#endif
		}
		else {
#if defined (XSEC_OPENSSL_CONST_BUFFERS)
			EVP_EncryptInit(&m_ctx, EVP_des_ede3_ecb(), m_keyBuf.rawBuffer(), NULL);
#else
			EVP_EncryptInit(&m_ctx, EVP_des_ede3(), (unsigned char *) m_keyBuf.rawBuffer(), NULL);
#endif
		}


		m_blockSize = 8;
		break;

#if defined (XSEC_OPENSSL_HAVE_AES)

	case (XSECCryptoSymmetricKey::KEY_AES_128) :

		// An AES key

		if (m_keyMode == MODE_CBC) {

			if (iv == NULL) {
				
				bool res = ((RAND_status() == 1) && (RAND_bytes(genIV, 16) == 1));
				if (res == false) {
					throw XSECCryptoException(XSECCryptoException::SymmetricError,
						"OpenSSL:SymmetricKey - Error generating random IV");
				}

				usedIV = genIV;

			}
			else
				usedIV = iv;

			EVP_EncryptInit_ex(&m_ctx, EVP_aes_128_cbc(), NULL, m_keyBuf.rawBuffer(), usedIV);
		}
		else {

			EVP_EncryptInit_ex(&m_ctx, EVP_aes_128_ecb(), NULL, m_keyBuf.rawBuffer(), NULL);

		}

		m_blockSize = 16;
		break;

	case (XSECCryptoSymmetricKey::KEY_AES_192) :

		// An AES key

		if (m_keyMode == MODE_CBC) {

			if (iv == NULL) {
				
				bool res = ((RAND_status() == 1) && (RAND_bytes(genIV, 16) == 1));
				if (res == false) {
					throw XSECCryptoException(XSECCryptoException::SymmetricError,
						"OpenSSL:SymmetricKey - Error generating random IV");
				}

				usedIV = genIV;

			}
			else
				usedIV = iv;

			EVP_EncryptInit_ex(&m_ctx, EVP_aes_192_cbc(), NULL, m_keyBuf.rawBuffer(), usedIV);

		}

		else {

			EVP_EncryptInit_ex(&m_ctx, EVP_aes_192_ecb(), NULL, m_keyBuf.rawBuffer(), NULL);
		}

		m_blockSize = 16;
		break;

	case (XSECCryptoSymmetricKey::KEY_AES_256) :

		// An AES key

		if (m_keyMode == MODE_CBC) {
			if (iv == NULL) {
				
				bool res = ((RAND_status() == 1) && (RAND_bytes(genIV, 16) == 1));
				if (res == false) {
					throw XSECCryptoException(XSECCryptoException::SymmetricError,
						"OpenSSL:SymmetricKey - Error generating random IV");
				}

				usedIV = genIV;

			}
			else
				usedIV = iv;

			EVP_EncryptInit_ex(&m_ctx, EVP_aes_256_cbc(), NULL, m_keyBuf.rawBuffer(), usedIV);

		}
		else {

			EVP_EncryptInit_ex(&m_ctx, EVP_aes_256_ecb(), NULL, m_keyBuf.rawBuffer(), NULL);

		}

		m_blockSize = 16;
		break;

#else 

	case (XSECCryptoSymmetricKey::KEY_AES_128) :
	case (XSECCryptoSymmetricKey::KEY_AES_192) :
	case (XSECCryptoSymmetricKey::KEY_AES_256) :

		throw XSECCryptoException(XSECCryptoException::UnsupportedAlgorithm,
			 "OpenSSL:SymmetricKey - AES not supported in this version of OpenSSL");

#endif /* XSEC_OPENSSL_HAVE_AES */

	default :

		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Unknown key type"); 

	}

	// Clear up any read padding

	if (m_keyMode == MODE_CBC) {
		m_ivSize = m_blockSize;
		memcpy(m_lastBlock, usedIV, m_ivSize);
	}
	else
		m_ivSize = 0;

#if defined (XSEC_OPENSSL_CANSET_PADDING)
	// Setup padding
	if (m_doPad) {
		EVP_CIPHER_CTX_set_padding(&m_ctx, 1);
	}
	else {
		EVP_CIPHER_CTX_set_padding(&m_ctx, 0);
	}
#endif

	return true;

}

unsigned int OpenSSLCryptoSymmetricKey::encrypt(const unsigned char * inBuf, 
								 unsigned char * cipherBuf, 
								 unsigned int inLength,
								 unsigned int maxOutLength) {

	if (m_initialised == false) {

		encryptInit();

	}

	// NOTE: This won't actually stop OpenSSL blowing the buffer, so the onus is
	// on the caller.

	unsigned int offset = 0;
	if (m_ivSent == false && m_ivSize > 0) {

		memcpy(cipherBuf, m_lastBlock, m_ivSize);
		m_ivSent = true;

		offset = m_ivSize;

	}

	int outl = maxOutLength - offset;

	if (inLength + offset > maxOutLength) {

		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Not enough space in output buffer for encrypt"); 

	}
#if defined (XSEC_OPENSSL_CONST_BUFFERS)
	if (EVP_EncryptUpdate(&m_ctx, &cipherBuf[offset], &outl, inBuf, inLength) == 0) {
#else
	if (EVP_EncryptUpdate(&m_ctx, &cipherBuf[offset], &outl, (unsigned char *) inBuf, inLength) == 0) {
#endif

		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSL:SymmetricKey - Error during OpenSSL encrypt"); 

	}

	return outl + offset;

}

unsigned int OpenSSLCryptoSymmetricKey::encryptFinish(unsigned char * cipherBuf,
													  unsigned int maxOutLength) {

	int outl = maxOutLength;
	m_initialised = false;

	if (EVP_EncryptFinal(&m_ctx, cipherBuf, &outl) == 0) {

		throw XSECCryptoException(XSECCryptoException::SymmetricError,
		  "OpenSSLSymmetricKey::encryptFinish - Error during OpenSSL decrypt finalisation"); 

	}

	if ((unsigned int) outl > maxOutLength) {
		throw XSECCryptoException(XSECCryptoException::SymmetricError,
			"OpenSSLSymmetricKey::encryptFinish - **WARNING** - Cipheroutput > maxOutLength!"); 
	}
	
#if !defined (XSEC_OPENSSL_CANSET_PADDING)
	if (!m_doPad) {

		if (outl < m_blockSize) {
			throw XSECCryptoException(XSECCryptoException::SymmetricError,
			   "OpenSSLSymmetricKey::encryptFinish - cannot remove padding!"); 
		}

		outl -= m_blockSize;

	}

#endif

	return outl;

}

#endif /* HAVE_OPENSSL */
