//
// RSAKeyImpl.cpp
//
// $Id: //poco/1.3/Crypto/src/RSAKeyImpl.cpp#7 $
//
// Library: Crypto
// Package: RSA
// Module:  RSAKeyImpl
//
// Copyright (c) 2008, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
// 
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//


#include "Poco/Crypto/RSAKeyImpl.h"
#include "Poco/Crypto/X509Certificate.h"
#include "Poco/FileStream.h"
#include "Poco/StreamCopier.h"
#include "Poco/TemporaryFile.h"
#include <openssl/pem.h>
#include <openssl/rsa.h>
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
#include <openssl/bn.h>
#endif


namespace Poco {
namespace Crypto {


RSAKeyImpl::RSAKeyImpl(const X509Certificate& cert):
	_pRSA(0)
{
	const X509* pCert = cert.certificate();
	EVP_PKEY* pKey = X509_get_pubkey(const_cast<X509*>(pCert));
	
	RSA* pRSA = pKey->pkey.rsa;
	_pRSA = RSAPublicKey_dup(pRSA);
}


RSAKeyImpl::RSAKeyImpl(int keyLength, unsigned long exponent):
	_pRSA(0)
{
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
	_pRSA = RSA_new();
	int ret = 0;
	BIGNUM* bn = 0;
	try
	{
		bn = BN_new();
		BN_set_word(bn, exponent);
		ret = RSA_generate_key_ex(_pRSA, keyLength, bn, 0);
		BN_free(bn);
	}
	catch (...)
	{
		BN_free(bn);
		throw;
	}
	if (!ret) throw Poco::InvalidArgumentException("Failed to create RSA context");
#else
	_pRSA = RSA_generate_key(keyLength, exponent, 0, 0);
	if (!_pRSA) throw Poco::InvalidArgumentException("Failed to create RSA context");
#endif
}


RSAKeyImpl::RSAKeyImpl(
		const std::string& publicKeyFile, 
		const std::string& privateKeyFile, 
		const std::string& privateKeyPassphrase):
	_pRSA(0)
{
	init(publicKeyFile, privateKeyFile, privateKeyPassphrase);
}


RSAKeyImpl::RSAKeyImpl(std::istream* pPublicKeyStream, std::istream* pPrivateKeyStream, const std::string& privateKeyPassphrase):
	_pRSA(0)
{
	// due to C lib not supporting streams, we create two temporary files
	std::string publicKeyFile;
	Poco::TemporaryFile pubFile;
	if (pPublicKeyStream)
	{
		if (!pubFile.createFile())
			throw Poco::CreateFileException("Cannot create temporary file for writing public key");
		publicKeyFile = pubFile.path();
		Poco::FileOutputStream fout(publicKeyFile);
		Poco::StreamCopier::copyStream(*pPublicKeyStream, fout);
	}
	std::string privateKeyFile;
	Poco::TemporaryFile privFile;
	if (pPrivateKeyStream)
	{
		if (!privFile.createFile())
			throw Poco::CreateFileException("Cannot create temporary file for writing private key");
		privateKeyFile = privFile.path();
		Poco::FileOutputStream fout(privateKeyFile);
		Poco::StreamCopier::copyStream(*pPrivateKeyStream, fout);

	}
	init(publicKeyFile, privateKeyFile, privateKeyPassphrase);
}


void RSAKeyImpl::init(const std::string& publicKeyFile, const std::string& privateKeyFile, const std::string& privateKeyPassphrase)
{
	poco_assert_dbg(_pRSA == 0);
	
	_pRSA = RSA_new();
	if (!publicKeyFile.empty())
	{
		BIO* out = BIO_new(BIO_s_file());
		if (!out) throw Poco::IOException("Cannot create BIO for reading public key", publicKeyFile);
		int rc = BIO_read_filename(out, publicKeyFile.c_str());
		if (rc)
		{
			RSA* pubKey = PEM_read_bio_RSAPublicKey(out, &_pRSA, 0, 0);
			BIO_free(out);
			if (!pubKey)
			{
				freeRSA();
				throw Poco::FileException("Failed to load public key", publicKeyFile);
			}
		}
		else
		{
			freeRSA();
			throw Poco::FileNotFoundException("Public key file", publicKeyFile);
		}
	}

	if (!privateKeyFile.empty())
	{
		BIO* out = BIO_new(BIO_s_file());
		if (!out) throw Poco::IOException("Cannot create BIO for reading private key", privateKeyFile);
		int rc = BIO_read_filename(out, privateKeyFile.c_str());
		if (rc)
		{
			RSA* privKey = 0;
			if (privateKeyPassphrase.empty())
				privKey = PEM_read_bio_RSAPrivateKey(out, &_pRSA, 0, 0);
			else
				privKey = PEM_read_bio_RSAPrivateKey(out, &_pRSA, 0, const_cast<char*>(privateKeyPassphrase.c_str()));
			BIO_free(out);
			if (!privKey)
			{
				freeRSA();
				throw Poco::FileException("Failed to load private key", privateKeyFile);
			}
		}
		else
		{
			freeRSA();
			throw Poco::FileNotFoundException("Private key file", privateKeyFile);
		}
	}
}


RSAKeyImpl::~RSAKeyImpl()
{
	freeRSA();
}


void RSAKeyImpl::freeRSA()
{
	if (_pRSA)
		RSA_free(_pRSA);
	_pRSA = 0;
}


int RSAKeyImpl::size() const
{
	return RSA_size(_pRSA);
}


void RSAKeyImpl::save(const std::string& publicKeyFile, const std::string& privateKeyFile, const std::string& privateKeyPassphrase)
{
	if (!publicKeyFile.empty())
	{
		BIO* out = BIO_new(BIO_s_file());
		if (!out) throw Poco::IOException("Cannot create BIO for writing public key file", publicKeyFile);
		try
		{
			if (BIO_write_filename(out, const_cast<char*>(publicKeyFile.c_str())))
			{
				if (!PEM_write_bio_RSAPublicKey(out, _pRSA))
					throw Poco::WriteFileException("Failed to write public key to file", publicKeyFile);
			}
			else throw Poco::CreateFileException("Cannot create public key file");
		}
		catch (...)
		{
			BIO_free(out);
			throw;
		}
		BIO_free(out);
	}
	
	if (!privateKeyFile.empty())
	{
		BIO* out = BIO_new(BIO_s_file());
		if (!out) throw Poco::IOException("Cannot create BIO for writing private key file", privateKeyFile);
		try
		{
			if (BIO_write_filename(out, const_cast<char*>(privateKeyFile.c_str())))
			{
				int rc = 0;
				if (privateKeyPassphrase.empty())
					rc = PEM_write_bio_RSAPrivateKey(out, _pRSA, EVP_des_ede3_cbc(), 0, 0, 0, 0);
				else
					rc = PEM_write_bio_RSAPrivateKey(out, _pRSA, EVP_des_ede3_cbc(), 
						reinterpret_cast<unsigned char*>(const_cast<char*>(privateKeyPassphrase.c_str())), 
						static_cast<int>(privateKeyPassphrase.length()), 0, 0);
				if (!rc) throw Poco::FileException("Failed to write private key to file", privateKeyFile);
			}
			else throw Poco::CreateFileException("Cannot create private key file", privateKeyFile);
		}
		catch (...)
		{
			BIO_free(out);
			throw;
		}
		BIO_free(out);
	}
}


void RSAKeyImpl::save(std::ostream* pPublicKeyStream, std::ostream* pPrivateKeyStream, const std::string& privateKeyPassphrase)
{
	if (!pPublicKeyStream && !pPrivateKeyStream) return;
	
	// due to C lib not supporting streams, we create two temporary files
	std::string publicKeyFile;
	Poco::TemporaryFile pubFile;
	if (pPublicKeyStream)
	{
		publicKeyFile = pubFile.path();
		if (!pubFile.createFile())
			throw Poco::CreateFileException("Cannot create temporary public file");
	}
	std::string privateKeyFile;
	Poco::TemporaryFile privFile;
	if (pPrivateKeyStream)
	{
		privateKeyFile = privFile.path();
		if (!privFile.createFile())
			throw Poco::FileException("Cannot crate temporary private key file");
	}
	save(publicKeyFile, privateKeyFile, privateKeyPassphrase);
	
	// now copy everything from the temp files to the original streams
	if (pPublicKeyStream)
	{
		Poco::FileInputStream istr(publicKeyFile);
		Poco::StreamCopier::copyStream(istr, *pPublicKeyStream);
	}
	if (pPrivateKeyStream)
	{
		Poco::FileInputStream istr(privateKeyFile);
		Poco::StreamCopier::copyStream(istr, *pPrivateKeyStream);
	}
}


} } // namespace Poco::Crypto
