/***************************************************************************
                          cssl.cpp  -  description
                             -------------------
    begin                : Sat Dec 7 2002
    copyright            : (C) 2002-2004 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cssl.h"

#include <stdio.h>
#include <string.h>

#include "cstring.h"
#include "cbase64.h"
#include "cbytearray.h"

/* cmutex.h includes <pthread.h> */
#include "cmutex.h"

/* init static members */
CMutex * CSSL::mutexes = NULL;

#if DCLIB_USES_OPENSSL == 1

/** free RSA */
CSSLObject::~CSSLObject()
{
	if ( m_pRSA )
	{
		RSA_free(m_pRSA);
	}
}


/** */
CSSL::CSSL()
{
	m_pRSA        = 0;
	m_pRandBuffer = 0;
}

/** */
CSSL::~CSSL()
{
	if ( m_pRSA )
		RSA_free(m_pRSA);
	if ( m_pRandBuffer )
		free(m_pRandBuffer);
}

/** */
void CSSL::InitSSLLibrary()
{
	mutexes = new CMutex[ CRYPTO_num_locks() ];
	
#ifndef WIN32
	CRYPTO_set_id_callback( &CSSL::thread_id );
#endif
	
	CRYPTO_set_locking_callback( &CSSL::locking_callback );
	
	SSL_load_error_strings();
	SSL_library_init();
}

/** */
void CSSL::DeInitSSLLibrary()
{
	CRYPTO_set_locking_callback( NULL );
	delete[] mutexes;
	mutexes = NULL;
	
	ERR_free_strings();
}

/** */
SSL_CTX * CSSL::InitClientCTX()
{
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
	const SSL_METHOD *method;
#else
	SSL_METHOD *method;
#endif
	SSL_CTX *ctx = NULL;

	method = SSLv23_client_method();		/* Create new client-method instance */
	
	// sanity check
	if ( method != NULL )
	{
		ctx = SSL_CTX_new(method);	/* Create new context */	
	}

	// sanity check
	if ( ctx == NULL )
	{
		ERR_print_errors_fp(stderr);
	}
	
	return ctx;
}

/** */
SSL_CTX * CSSL::InitServerCTX()
{
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
	const SSL_METHOD *method;
#else
	SSL_METHOD *method;
#endif
	SSL_CTX *ctx = NULL;

	method = SSLv23_server_method();		/* Create new client-method instance */
	
	if ( method != NULL )
	{
		ctx = SSL_CTX_new(method);	/* Create new context */
	}
	
	if ( ctx == NULL )
	{
		ERR_print_errors_fp(stderr);
	}

	return ctx;
}

/** */
SSL_CTX * CSSL::NewTLSv1ClientCTX()
{
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
	const SSL_METHOD *method;
#else
	SSL_METHOD *method;
#endif
	SSL_CTX * ctx = NULL;
	
	method = TLSv1_client_method();
	if ( method != NULL )
	{
		ctx = SSL_CTX_new(method);
	}
	
	if ( ctx == NULL )
	{
		ERR_print_errors_fp(stderr);
	}
	
	return ctx;
}

/** */
SSL_CTX * CSSL::NewTLSv1ServerCTX()
{
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
	const SSL_METHOD *method;
#else
	SSL_METHOD *method;
#endif
	SSL_CTX * ctx = NULL;
	
	method = TLSv1_server_method();
	if ( method != NULL )
	{
		ctx = SSL_CTX_new(method);
	}
	
	if ( ctx == NULL )
	{
		ERR_print_errors_fp(stderr);
	}
	
	return ctx;
}

/** */
bool CSSL::LoadCertificates( SSL_CTX * ctx, char * CertFile, char * KeyFile )
{
	bool res = false;

	// check
	if ( !ctx || !CertFile || !KeyFile )
	{
		return res;
	}

	// set the local certificate from CertFile
	if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
	{
		ERR_print_errors_fp(stderr);
	}
	// set the private key from KeyFile (may be the same as CertFile)
	else if ( SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0 )
	{
		ERR_print_errors_fp(stderr);
	}
	// verify private key
	else if ( !SSL_CTX_check_private_key(ctx) )
	{
		fprintf(stderr, "Private key does not match the public certificate\n");
	}
	// no error
	else
	{
		res = true;
	}

	return res;
}

void CSSL::InitRand()
{
	if ( m_pRandBuffer )
	{
		free(m_pRandBuffer);
	}

	m_pRandBuffer = (int *) malloc(sizeof(int)*1000);

	if ( !m_pRandBuffer )
	{
		perror("CSSL::InitRand: malloc");
		return;
	}

	InitRandArray((unsigned char*)m_pRandBuffer,sizeof(int)*1000);

	RAND_seed(m_pRandBuffer,sizeof(int)*1000);
}

/** */
bool CSSL::GenerateRsaKey()
{
	bool res = false;

	if ( m_pRSA == 0 )
	{
		InitRand();
		m_pRSA = RSA_generate_key(2048,65537,NULL,NULL);

		if ( m_pRSA )
		{
 			if ( RSA_check_key(m_pRSA) == 1 )
				res = true;
		}
	}

	return res;
}

/** */
void CSSL::InitRandArray( unsigned char * a, int len )
{
	int i;

	// sanity check
	if ( !a || (len <= 0) )
	{
		return;
	}
	
	if ( RAND_bytes(a,len) != 1 )
	{
		srand(time(NULL));

		for(i=0;i<len;i++)
			a[i]=(unsigned char)(rand()&0xff);
	}
}

/** */
CString CSSL::GetPublicRsaKey()
{
	int i;
	CByteArray bain,baout;
	CString s;
	unsigned char *buf;

	// sanity check
	if ( m_pRSA )
	{
		i = i2d_RSAPublicKey(m_pRSA,NULL);
		
		// sanity check
		if ( i > 0 )
		{
			bain.SetSize(i);
			
			buf = bain.Data();

			// sanity check
			if ( buf )
			{
				i = i2d_RSAPublicKey(m_pRSA,&buf);

				// sanity check
				if ( i > 0 )
				{
					CBase64::Encode(&baout,&bain);
					s.Set((const char*)baout.Data(),baout.Size());
				}
			}
		}
	}
	
	return s;
}

/** */
bool CSSL::SetPublicKey( CSSLObject * SSLObject, CString s )
{
	bool res = false;
	CByteArray bain,baout;
	unsigned char *buf;

	// sanity check
	if ( !SSLObject || (s.IsEmpty()) )
	{
		return res;
	}
	
	bain.SetSize(0);
	bain.Append(s.Data(),s.Length());
		
	if ( CBase64::Decode(&baout,&bain) > 0 )
	{
		if ( SSLObject->m_pRSA )
			RSA_free(SSLObject->m_pRSA);
		buf = baout.Data();

#if OPENSSL_VERSION_NUMBER >= 0x00907000L
		SSLObject->m_pRSA = d2i_RSAPublicKey(NULL,(const unsigned char**)&buf,baout.Size());
#else
		SSLObject->m_pRSA = d2i_RSAPublicKey(NULL,(unsigned char**)&buf,baout.Size());
#endif
		if ( SSLObject->m_pRSA )
			res = true;
	}

	return res;
}

/** */
void CSSL::InitSessionKey( CSSLObject * SSLObject )
{
	// sanity check
	if ( SSLObject )
	{
		InitRandArray( SSLObject->m_localkey, 16 );
		InitRandArray( SSLObject->m_localiv, 8 );
	}
}

/** */
CString CSSL::GetSessionKey( CSSLObject * SSLObject )
{
	int i;
	CByteArray bain,baout;
	CString s;

	// sanity check
	if ( !SSLObject )
	{
		return s;
	}
	
	bain.SetSize(0);
	bain.Append( SSLObject->m_localkey, 16);
	bain.Append( SSLObject->m_localiv, 8);

//	printf("LOCAL\n");
//	for(i=0;i<24;i++) printf("%02X ",bain.Data()[i]);
//	printf("\n");

	baout.SetSize(500);

	i = RSA_public_encrypt(bain.Size(),bain.Data(),baout.Data(),SSLObject->m_pRSA,RSA_PKCS1_OAEP_PADDING);

	if ( i != 0 )
	{
		bain.SetSize(0);
		bain.Append(baout.Data(),i);
		baout.SetSize(0);
		CBase64::Encode(&baout,&bain);
		s.Set((const char*)baout.Data(),baout.Size());
	}
	else
	{
		printf("LOCAL SK error %d\n",i);
	}

	return s;
}

/** */
bool CSSL::SetSessionKey( CSSLObject * SSLObject, CString s )
{
	bool res = false;
	CByteArray bain,baout;
	int i;

	// sanity check
	if ( !SSLObject || (s.IsEmpty()) )
	{
		return res;
	}
	
	bain.SetSize(0);
	bain.Append(s.Data(),s.Length());
		
	if ( CBase64::Decode(&baout,&bain) > 0 )
	{
		bain.SetSize(baout.Size());
		i = RSA_private_decrypt(baout.Size(),baout.Data(),bain.Data(),m_pRSA,RSA_PKCS1_OAEP_PADDING);

		if ( i == 24 )
		{
//			printf("REMOTE\n");
//			for(i=0;i<24;i++) printf("%02X ",bain.Data()[i]);
//			printf("\n");
			memcpy( SSLObject->m_remotekey, bain.Data()+0, 16 );
			memcpy( SSLObject->m_remoteiv, bain.Data()+16, 8 );
			res = true;
		}
		else
		{
			printf("SK error %d\n",i);
		}
	}

	return res;
}

/** */
CString CSSL::EncryptData( CSSLObject * SSLObject, CString s )
{
	CString res;
	CByteArray bain,baout;
	int i,tmplen;
	EVP_CIPHER_CTX ctx;

	// sanity check
	if ( !SSLObject || (s.IsEmpty()) )
	{
		return res;
	}
	
	EVP_CIPHER_CTX_init(&ctx);
	EVP_EncryptInit(&ctx, EVP_bf_cbc(), SSLObject->m_remotekey, SSLObject->m_remoteiv);

	// init input array
	bain.SetSize(2);
	InitRandArray(bain.Data(),2);
	bain.Append(s.Data(),s.Length());

	// init output array
	// input size + cipher_block_size for EVP_EncryptUpdate
	// plus cipher_block_size for EVP_EncryptFinal
	baout.SetSize( bain.Size() + ( 2 * EVP_CIPHER_CTX_block_size(&ctx) ) );
	//printf("CSSL::EncryptData: wrong old size=%lu new size=%lu\n",bain.Size()*2,baout.Size());
	i = baout.Size();

	if ( EVP_EncryptUpdate(&ctx, baout.Data(), &i, bain.Data(), bain.Size() ) )
	{
		if ( EVP_EncryptFinal(&ctx, baout.Data()+i, &tmplen) )
		{
			i+=tmplen;
			bain.SetSize(0);
			bain.Append(baout.Data(),i);
			baout.SetSize(0);
			CBase64::Encode(&baout,&bain);
			res.Set((const char*)baout.Data(),baout.Size());
		}
	}

	EVP_CIPHER_CTX_cleanup(&ctx);

	return res;
}

/** */
CString CSSL::DecryptData( CSSLObject * SSLObject, CString s )
{
	CString res;
	CByteArray bain,baout;
	int i,tmplen;
	EVP_CIPHER_CTX ctx;

	// sanity check
	if ( !SSLObject || (s.IsEmpty()) )
	{
		return res;
	}
	
	EVP_CIPHER_CTX_init(&ctx);
	EVP_DecryptInit(&ctx, EVP_bf_cbc(), SSLObject->m_localkey, SSLObject->m_localiv);

	bain.SetSize(0);
	bain.Append(s.Data(),s.Length());

	if ( CBase64::Decode(&baout,&bain) > 0 )
	{
		bain.SetSize( baout.Size() + ( 2 * EVP_CIPHER_CTX_block_size(&ctx) ) );
		//printf("CSSL::DecryptData: wrong old size=%lu new size=%lu\n",baout.Size()*2,bain.Size());
		i = 0;

		if ( EVP_DecryptUpdate(&ctx, bain.Data(), &i, baout.Data(), (int)baout.Size() ) )
		{
			tmplen = 0;
			if ( EVP_DecryptFinal(&ctx, bain.Data()+i, &tmplen) )
			{
				i+=tmplen;
				res.Set((const char*)bain.Data()+2,i-2);
			}
		}
	}

	EVP_CIPHER_CTX_cleanup(&ctx);

	return res;
}

/** */
CString CSSL::GetSSLVersionString()
{
	return CString(OPENSSL_VERSION_TEXT);
}

/** */
void CSSL::locking_callback( int mode, int type, const char * /* file */, int /* line */ )
{
	if ( mode & CRYPTO_LOCK )
	{
		mutexes[type].Lock();
	}
	else
	{
		mutexes[type].UnLock();
	}
}

/*
 * Alternative implementations using other SSL libraries may go here but should probably be
 * moved into separate files.
 */

#else // DCLIB_USES_OPENSSL

/*
 * The without SSL implementation of each method does nothing / returns 0 or empty
 * but will print a warning since they should not be used. Check for support with
 * DCLIB_HAS_SSL at dclib compile time or dclibSupportsSSL() at valknut runtime.
 */

/** */
CSSLObject::~CSSLObject()
{
	// nothing
}

/** */
CSSL::CSSL()
{
	m_pRSA        = 0;
	m_pRandBuffer = 0;
}

/** */
CSSL::~CSSL()
{
	// nothing
}

/** */
void CSSL::InitSSLLibrary()
{
	// nothing
}

/** */
void CSSL::DeInitSSLLibrary()
{
	// nothing
}

/** */
SSL_CTX * CSSL::InitClientCTX()
{
	printf("dclib was compiled without SSL support so CSSL::InitClientCTX always returns 0\n");
	return 0;
}

/** */
SSL_CTX * CSSL::InitServerCTX()
{
	printf("dclib was compiled without SSL support so CSSL::InitServerCTX always returns 0\n");
	return 0;
}

/** */
SSL_CTX * CSSL::NewTLSv1ClientCTX()
{
	printf("dclib was compiled without SSL support so CSSL::NewTLSv1ClientCTX always returns 0\n");
	return 0;
}

/** */
SSL_CTX * CSSL::NewTLSv1ServerCTX()
{
	printf("dclib was compiled without SSL support so CSSL::NewTLSv1ServerCTX always returns 0\n");
	return 0;
}

/** */
bool CSSL::LoadCertificates( SSL_CTX * /* ctx */, char * /* certfile */, char * /* keyfile */ )
{
	printf("dclib was compiled without SSL support so CSSL::LoadCertificates always returns false\n");
	return false;
}

/** */
void CSSL::InitRand()
{
	printf("dclib was compiled without SSL support so CSSL::InitRand does nothing\n");
}

/** */
void CSSL::InitRandArray( unsigned char * /* a */, int /* len */ )
{
	printf("dclib was compiled without SSL support so CSSL::InitRandArray does nothing\n");
}

/** */
bool CSSL::GenerateRsaKey()
{
	printf("dclib was compiled without SSL support so CSSL::GenerateRsaKey always returns false\n");
	return false;
}

/** */
CString CSSL::GetPublicRsaKey()
{
	printf("dclib was compiled without SSL support so CSSL::GetPublicRsaKey always returns an empty string\n");
	return CString();
}

/** */
bool CSSL::SetPublicKey( CSSLObject * /* SSLObject */, CString /* s */ )
{
	printf("dclib was compiled without SSL support so CSSL::SetPublicKey always returns false\n");
	return false;
}

/** */
void CSSL::InitSessionKey( CSSLObject * /* SSLObject */ )
{
	printf("dclib was compiled without SSL support so CSSL::InitSessionKey does nothing\n");
}

/** */
CString CSSL::GetSessionKey( CSSLObject * /* SSLObject */ )
{
	printf("dclib was compiled without SSL support so CSSL::GetSessionKey always returns an empty string\n");
	return CString();
}

/** */
bool CSSL::SetSessionKey( CSSLObject * /* SSLObject */, CString /* s */ )
{
	printf("dclib was compiled without SSL support so CSSL::SetSessionKey always returns false\n");
	return false;
}

/** */
CString CSSL::EncryptData( CSSLObject * /* SSLObject */, CString /* s */ )
{
	printf("dclib was compiled without SSL support so CSSL::EncryptData always returns an empty string\n");
	return CString();
}

/** */
CString CSSL::DecryptData( CSSLObject * /* SSLObject */, CString /* s */ )
{
	printf("dclib was compiled without SSL support so CSSL::DecryptData always returns an empty string\n");
	return CString();
}

/** */
CString CSSL::GetSSLVersionString()
{
	return CString("None");
}

/** */
void CSSL::locking_callback( int /* mode */, int /* type */, const char * /* file */, int /* line */ )
{
	return;
}

#endif // DCLIB_USES_OPENSSL

/** This function does not use OpenSSL */
#ifndef WIN32
unsigned long CSSL::thread_id()
{
	/*
	 * This is bad because we are not supposed to know
	 * or rely on what a pthread_t is, however it is an unsigned long,
	 * and the OpenSSL docs don't provide any other code.
	 */
	return (unsigned long) pthread_self();
}
#endif
