/*
 * 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
 *
 * OpenSSLCryptoKeyRSA := RSA Keys
 *
 * Author(s): Berin Lautenbach
 *
 * $Id: OpenSSLCryptoKeyRSA.cpp 697146 2008-09-19 16:36:31Z scantor $
 *
 */

#include <xsec/framework/XSECDefs.hpp>
#if defined (HAVE_OPENSSL)

#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
#include <xsec/enc/OpenSSL/OpenSSLCryptoBase64.hpp>
#include <xsec/enc/XSECCryptoException.hpp>
#include <xsec/enc/XSECCryptoUtils.hpp>
#include <xsec/framework/XSECError.hpp>

#include <openssl/rsa.h>

#include <xercesc/util/Janitor.hpp>

XSEC_USING_XERCES(ArrayJanitor);

#include <memory.h>

OpenSSLCryptoKeyRSA::OpenSSLCryptoKeyRSA() :
mp_rsaKey(NULL),
mp_oaepParams(NULL),
m_oaepParamsLen(0) {
};

OpenSSLCryptoKeyRSA::~OpenSSLCryptoKeyRSA() {


	// If we have a RSA, delete it (OpenSSL will clear the memory)

	if (mp_rsaKey)
		RSA_free(mp_rsaKey);

	if (mp_oaepParams != NULL)
		delete[] mp_oaepParams;

};

void OpenSSLCryptoKeyRSA::setOAEPparams(unsigned char * params, unsigned int paramsLen) {

	if (mp_oaepParams != NULL) {
		delete[] mp_oaepParams;
	}

	m_oaepParamsLen = paramsLen;
	if (params != NULL) {
		XSECnew(mp_oaepParams, unsigned char[paramsLen]);
		memcpy(mp_oaepParams, params, paramsLen);
	}
	else
		mp_oaepParams = NULL;

}

unsigned int OpenSSLCryptoKeyRSA::getOAEPparamsLen(void) const {

	return m_oaepParamsLen;

}

const unsigned char * OpenSSLCryptoKeyRSA::getOAEPparams(void) const {

	return mp_oaepParams;

}

// Generic key functions

XSECCryptoKey::KeyType OpenSSLCryptoKeyRSA::getKeyType() const {

	// Find out what we have
	if (mp_rsaKey == NULL)
		return KEY_NONE;

	if (mp_rsaKey->n != NULL && mp_rsaKey->d != NULL)
		return KEY_RSA_PAIR;

	if (mp_rsaKey->d != NULL)
		return KEY_RSA_PRIVATE;

	if (mp_rsaKey->n != NULL)
		return KEY_RSA_PUBLIC;

	return KEY_NONE;

}

void OpenSSLCryptoKeyRSA::loadPublicModulusBase64BigNums(const char * b64, unsigned int len) {

	if (mp_rsaKey == NULL)
		mp_rsaKey = RSA_new();

	mp_rsaKey->n = OpenSSLCryptoBase64::b642BN((char *) b64, len);

}

void OpenSSLCryptoKeyRSA::loadPublicExponentBase64BigNums(const char * b64, unsigned int len) {

	if (mp_rsaKey == NULL)
		mp_rsaKey = RSA_new();

	mp_rsaKey->e = OpenSSLCryptoBase64::b642BN((char *) b64, len);

}

// "Hidden" OpenSSL functions

OpenSSLCryptoKeyRSA::OpenSSLCryptoKeyRSA(EVP_PKEY *k) {

	// Create a new key to be loaded as we go

	mp_oaepParams = NULL;
	m_oaepParamsLen = 0;

	mp_rsaKey = RSA_new();

	if (k == NULL || k->type != EVP_PKEY_RSA)
		return;	// Nothing to do with us

	if (k->pkey.rsa->n)
		mp_rsaKey->n = BN_dup(k->pkey.rsa->n);

	if (k->pkey.rsa->e)
		mp_rsaKey->e = BN_dup(k->pkey.rsa->e);

	if (k->pkey.rsa->d)
		mp_rsaKey->d = BN_dup(k->pkey.rsa->d);

	if (k->pkey.rsa->p)
		mp_rsaKey->p = BN_dup(k->pkey.rsa->p);

	if (k->pkey.rsa->q)
		mp_rsaKey->q = BN_dup(k->pkey.rsa->q);

	if (k->pkey.rsa->dmp1)
		mp_rsaKey->dmp1 = BN_dup(k->pkey.rsa->dmp1);

	if (k->pkey.rsa->dmq1)
		mp_rsaKey->dmq1 = BN_dup(k->pkey.rsa->dmq1);

	if (k->pkey.rsa->iqmp)
		mp_rsaKey->iqmp = BN_dup(k->pkey.rsa->iqmp);

}

// --------------------------------------------------------------------------------
//           Verify a signature encoded as a Base64 string
// --------------------------------------------------------------------------------

bool OpenSSLCryptoKeyRSA::verifySHA1PKCS1Base64Signature(const unsigned char * hashBuf,
								 unsigned int hashLen,
								 const char * base64Signature,
								 unsigned int sigLen,
								 hashMethod hm = HASH_SHA1) {

	// Use the currently loaded key to validate the Base64 encoded signature

	if (mp_rsaKey == NULL) {

		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA - Attempt to validate signature with empty key");
	}

	unsigned char sigVal[1024];
	int sigValLen;

	EVP_ENCODE_CTX m_dctx;
	int rc;

	char * cleanedBase64Signature;
	unsigned int cleanedBase64SignatureLen = 0;

	cleanedBase64Signature =
		XSECCryptoBase64::cleanBuffer(base64Signature, sigLen, cleanedBase64SignatureLen);
	ArrayJanitor<char> j_cleanedBase64Signature(cleanedBase64Signature);

	EVP_DecodeInit(&m_dctx);
	rc = EVP_DecodeUpdate(&m_dctx,
						  sigVal,
						  &sigValLen,
						  (unsigned char *) cleanedBase64Signature,
						  cleanedBase64SignatureLen);

	if (rc < 0) {

		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA - Error during Base64 Decode");
	}
	int t = 0;

	EVP_DecodeFinal(&m_dctx, &sigVal[sigValLen], &t);

	sigValLen += t;

	// Now decrypt

	unsigned char * decryptBuf;

	// Decrypt will always be longer than (RSA_len(key) - 11)
	decryptBuf = new unsigned char [RSA_size(mp_rsaKey)];
	ArrayJanitor<unsigned char> j_decryptBuf(decryptBuf);

	// Note at this time only supports PKCS1 padding
	// As that is what is defined in the standard.
	// If this ever changes we will need to pass some paramaters
	// into this function to allow it to determine what the
	// padding should be and what the message digest OID should
	// be.

	int decryptSize = RSA_public_decrypt(sigValLen,
											 sigVal,
											 decryptBuf,
											 mp_rsaKey,
											 RSA_PKCS1_PADDING);

	if (decryptSize < 0) {

/*		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA::verify() - Error decrypting signature"); */
		// Really - this is a failed signature check, not an exception!
		return false;
	}

	/* Check the OID */
	int oidLen = 0;
	unsigned char * oid = getRSASigOID(hm, oidLen);

	if (oid == NULL) {
		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA::verify() - Unsupported HASH algorithm for RSA");
	}

	if (decryptSize != (int) (oidLen + hashLen) || hashLen != oid[oidLen-1]) {

		return false;

	}

	for (t = 0; t < oidLen; ++t) {

		if (oid[t] != decryptBuf[t]) {

			return false;

		}

	}

	for (;t < decryptSize; ++t) {

		if (hashBuf[t-oidLen] != decryptBuf[t]) {

			return false;

		}

	}

	// All OK
	return true;

}

// --------------------------------------------------------------------------------
//           Sign and encode result as a Base64 string
// --------------------------------------------------------------------------------


unsigned int OpenSSLCryptoKeyRSA::signSHA1PKCS1Base64Signature(unsigned char * hashBuf,
		unsigned int hashLen,
		char * base64SignatureBuf,
		unsigned int base64SignatureBufLen,
		hashMethod hm) {

	// Sign a pre-calculated hash using this key

	if (mp_rsaKey == NULL) {

		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA - Attempt to sign data with empty key");
	}

	// Build the buffer to be encrypted by prepending the SHA1 OID to the hash

	unsigned char * encryptBuf;
	unsigned char * preEncryptBuf;
	unsigned char * oid;
	int oidLen;
	int encryptLen;
	int preEncryptLen;

	oid = getRSASigOID(hm, oidLen);

	if (oid == NULL) {
		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA::sign() - Unsupported HASH algorithm for RSA");
	}

	if (hashLen != oid[oidLen-1]) {
		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA::sign() - hashLen incorrect for hash type");
	}

	preEncryptLen = hashLen + oidLen;
	preEncryptBuf = new unsigned char[preEncryptLen];
	encryptBuf = new unsigned char[RSA_size(mp_rsaKey)];

	memcpy(preEncryptBuf, oid, oidLen);
	memcpy(&preEncryptBuf[oidLen], hashBuf, hashLen);

	// Now encrypt

	encryptLen = RSA_private_encrypt(preEncryptLen,
								     preEncryptBuf,
									 encryptBuf,
									 mp_rsaKey,
									 RSA_PKCS1_PADDING);

	delete[] preEncryptBuf;

	if (encryptLen < 0) {

		delete[] encryptBuf;
		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA::sign() - Error encrypting hash");
	}

	// Now convert to Base 64

	BIO * b64 = BIO_new(BIO_f_base64());
	BIO * bmem = BIO_new(BIO_s_mem());

	BIO_set_mem_eof_return(bmem, 0);
	b64 = BIO_push(b64, bmem);

	// Translate signature to Base64

	BIO_write(b64, encryptBuf, encryptLen);
	BIO_flush(b64);

	unsigned int sigValLen = BIO_read(bmem, base64SignatureBuf, base64SignatureBufLen);

	BIO_free_all(b64);

	delete[] encryptBuf;

	if (sigValLen <= 0) {

		throw XSECCryptoException(XSECCryptoException::DSAError,
			"OpenSSL:RSA - Error base64 encoding signature");
	}

	return sigValLen;
}

// --------------------------------------------------------------------------------
//           decrypt a buffer
// --------------------------------------------------------------------------------

unsigned int OpenSSLCryptoKeyRSA::privateDecrypt(const unsigned char * inBuf,
								 unsigned char * plainBuf,
								 unsigned int inLength,
								 unsigned int maxOutLength,
								 PaddingType padding,
								 hashMethod hm) {

	// Perform a decrypt
	if (mp_rsaKey == NULL) {

		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA - Attempt to decrypt data with empty key");
	}

#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

	int decryptSize;

	switch (padding) {

	case XSECCryptoKeyRSA::PAD_PKCS_1_5 :

		decryptSize = RSA_private_decrypt(inLength,
#if defined(XSEC_OPENSSL_CONST_BUFFERS)
							inBuf,
#else
						    (unsigned char *) inBuf,
#endif
							plainBuf,
							mp_rsaKey,
							RSA_PKCS1_PADDING);

		if (decryptSize < 0) {

			throw XSECCryptoException(XSECCryptoException::RSAError,
				"OpenSSL:RSA privateKeyDecrypt - Error Decrypting PKCS1_5 padded RSA encrypt");

		}

		break;

	case XSECCryptoKeyRSA::PAD_OAEP_MGFP1 :
		{

			unsigned char * tBuf;
			int num = RSA_size(mp_rsaKey);
			XSECnew(tBuf, unsigned char[inLength]);
			ArrayJanitor<unsigned char> j_tBuf(tBuf);

			decryptSize = RSA_private_decrypt(inLength,
#if defined(XSEC_OPENSSL_CONST_BUFFERS)
							    inBuf,
#else
						        (unsigned char *) inBuf,
#endif
								tBuf,
								mp_rsaKey,
								RSA_NO_PADDING);
			if (decryptSize < 0) {

				throw XSECCryptoException(XSECCryptoException::RSAError,
					"OpenSSL:RSA privateKeyDecrypt - Error doing raw decrypt of RSA encrypted data");

			}

			// Clear out the "0"s at the front
			int i;
			for (i = 0; i < num && tBuf[i] == 0; ++i)
				--decryptSize;

			decryptSize = RSA_padding_check_PKCS1_OAEP(plainBuf,
													   maxOutLength,
													   &tBuf[i],
													   decryptSize,
													   num,
													   mp_oaepParams,
													   m_oaepParamsLen);

			if (decryptSize < 0) {

				throw XSECCryptoException(XSECCryptoException::RSAError,
					"OpenSSL:RSA privateKeyDecrypt - Error removing OAEPadding");

			}

		}
		break;

	default :

		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA - Unknown padding method");

	}

#if 0

	/* normally commented out code to determine endian problems */
	int i;
	unsigned char t[512];
	if (decryptSize < 512) {
		memcpy(t, plainBuf, decryptSize);
		for (i = 0; i < decryptSize;++i) {
			plainBuf[i] = t[decryptSize - 1 - i];
		}
	}

#endif

	return decryptSize;

}

// --------------------------------------------------------------------------------
//           encrypt a buffer
// --------------------------------------------------------------------------------

unsigned int OpenSSLCryptoKeyRSA::publicEncrypt(const unsigned char * inBuf,
								 unsigned char * cipherBuf,
								 unsigned int inLength,
								 unsigned int maxOutLength,
								 PaddingType padding,
								 hashMethod hm) {

	// Perform an encrypt
	if (mp_rsaKey == NULL) {

		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA - Attempt to encrypt data with empty key");
	}

	int encryptSize;

	switch (padding) {

	case XSECCryptoKeyRSA::PAD_PKCS_1_5 :

		encryptSize = RSA_public_encrypt(inLength,
#if defined(XSEC_OPENSSL_CONST_BUFFERS)
  					        inBuf,
#else
						    (unsigned char *) inBuf,
#endif
							cipherBuf,
							mp_rsaKey,
							RSA_PKCS1_PADDING);

		if (encryptSize < 0) {

			throw XSECCryptoException(XSECCryptoException::RSAError,
				"OpenSSL:RSA publicKeyEncrypt - Error performing PKCS1_5 padded RSA encrypt");

		}

		break;

	case XSECCryptoKeyRSA::PAD_OAEP_MGFP1 :
		{

			unsigned char * tBuf;
			unsigned int num = RSA_size(mp_rsaKey);
			if (maxOutLength < num) {
				throw XSECCryptoException(XSECCryptoException::RSAError,
					"OpenSSL:RSA publicKeyEncrypt - Not enough space in cipherBuf");
			}

			XSECnew(tBuf, unsigned char[num]);
			ArrayJanitor<unsigned char> j_tBuf(tBuf);

			// First add the padding

			encryptSize = RSA_padding_add_PKCS1_OAEP(tBuf,
													 num,
#if defined(XSEC_OPENSSL_CONST_BUFFERS)
  					                                 inBuf,
#else
						                             (unsigned char *) inBuf,
#endif
													 inLength,
													 mp_oaepParams,
													 m_oaepParamsLen);

			if (encryptSize <= 0) {

				throw XSECCryptoException(XSECCryptoException::RSAError,
					"OpenSSL:RSA publicKeyEncrypt - Error adding OAEPadding");

			}

			encryptSize = RSA_public_encrypt(num,
								tBuf,
								cipherBuf,
								mp_rsaKey,
								RSA_NO_PADDING);


			if (encryptSize < 0) {

				throw XSECCryptoException(XSECCryptoException::RSAError,
					"OpenSSL:RSA publicKeyEncrypt - Error encrypting padded data");

			}
		}
		break;

	default :

		throw XSECCryptoException(XSECCryptoException::RSAError,
			"OpenSSL:RSA - Unknown padding method");

	}


	return encryptSize;

}

// --------------------------------------------------------------------------------
//           Size in bytes
// --------------------------------------------------------------------------------

unsigned int OpenSSLCryptoKeyRSA::getLength(void) const {

	if (mp_rsaKey != NULL)
		return RSA_size(mp_rsaKey);

	return 0;

}

// --------------------------------------------------------------------------------
//           Clone this key
// --------------------------------------------------------------------------------

XSECCryptoKey * OpenSSLCryptoKeyRSA::clone() const {

	OpenSSLCryptoKeyRSA * ret;

	XSECnew(ret, OpenSSLCryptoKeyRSA);

	ret->mp_rsaKey = RSA_new();

	if (mp_oaepParams != NULL) {
		XSECnew(ret->mp_oaepParams, unsigned char[m_oaepParamsLen]);
		memcpy(ret->mp_oaepParams, mp_oaepParams, m_oaepParamsLen);
		ret->m_oaepParamsLen = m_oaepParamsLen;
	}
	else {
		ret->mp_oaepParams = NULL;
		ret->m_oaepParamsLen = 0;
	}

	// Duplicate parameters

	if (mp_rsaKey->n)
		ret->mp_rsaKey->n = BN_dup(mp_rsaKey->n);

	if (mp_rsaKey->e)
		ret->mp_rsaKey->e = BN_dup(mp_rsaKey->e);

	if (mp_rsaKey->d)
		ret->mp_rsaKey->d = BN_dup(mp_rsaKey->d);

	if (mp_rsaKey->p)
		ret->mp_rsaKey->p = BN_dup(mp_rsaKey->p);

	if (mp_rsaKey->q)
		ret->mp_rsaKey->q = BN_dup(mp_rsaKey->q);

	if (mp_rsaKey->dmp1)
		ret->mp_rsaKey->dmp1 = BN_dup(mp_rsaKey->dmp1);

	if (mp_rsaKey->dmq1)
		ret->mp_rsaKey->dmq1 = BN_dup(mp_rsaKey->dmq1);

	if (mp_rsaKey->iqmp)
		ret->mp_rsaKey->iqmp = BN_dup(mp_rsaKey->iqmp);

	return ret;

}

#endif /* HAVE_OPENSSL */
