#include "StdAfx.h"
#include "SSLContext.h"
#include "ServerStartException.h"
#include "openssl/rand.h"
#include "openssl/err.h"
#include "openssl/evp.h"
#include "openssl/engine.h"
#include "openssl/conf.h"

#define	CLASS_NAME	"SSLContext"

//!	OpenSSL用ミューテックス
HANDLE	*g_lock_cs = NULL;

CSSLContext::CSSLContext(void)
{
	m_init = FALSE;
}

CSSLContext::~CSSLContext(void)
{
	Stop();
}

/////////////////////////////////////////////////////////////////////////////////////
//	初期化
/////////////////////////////////////////////////////////////////////////////////////
/*!
	初期化
*/
void CSSLContext::Start(CWorkspaceAccess setting)
{
	//	初期化済み？
	if(m_init)
		return;
	m_init = TRUE;

	//	コピー
	m_setting = setting;

	//	SSL初期化
	InitOpenSSL();
}

/*!
	停止
*/
void CSSLContext::Stop()
{
	//	初期化チェック
	if(!m_init)
		return;
	m_init = FALSE;

	//	SSL停止
	CloseOpenSSL();
}


/////////////////////////////////////////////////////////////////////////////////////
//	取得
/////////////////////////////////////////////////////////////////////////////////////
/*!
	コンテキスト取得
*/
SSL_CTX *CSSLContext::GetCTX()
{
	SCOPE_LOCK();

	return m_context;
};


/////////////////////////////////////////////////////////////////////////////////////
//	OpenSSL
/////////////////////////////////////////////////////////////////////////////////////
/*!
	初期化
*/
void CSSLContext::InitOpenSSL()
{
	//	設定取得
	CWorkspaceAccess	SSLSetting = m_setting.GetAccess(_T("SSL"));
	CString				privateKey = SSLSetting.GetKeyStr(_T("PrivateKey"), _T("%ServerRoot%SSLKey\\key.pem"));
	CString				certificate = SSLSetting.GetKeyStr(_T("Certificate"), _T("%ServerRoot%SSLKey\\ca.crt"));

	//	パスワードコピー
	m_password = SSLSetting.GetKeyStr(_T("Password"), _T("test"));

	//	ライブラリ初期化
	SSL_library_init();
	SSL_load_error_strings();

	//	コンテキスト生成
	m_method = SSLv23_server_method();	//	v2, v3, TLS
	m_context = SSL_CTX_new(m_method);
//	SSL_CTX_set_options(m_context, SSL_OP_NO_SSLv2);	//	v2使用禁止

	//	証明書ロード
	if(!SSL_CTX_use_certificate_chain_file(m_context,certificate))
		throw CServerStartException( _T("サーバ証明書のロードに失敗しました"));

	//	パスワードコールバック初期化
	SSL_CTX_set_default_passwd_cb(m_context, Password_cb);
	SSL_CTX_set_default_passwd_cb_userdata(m_context, this);

	//	キーロード
    if(!SSL_CTX_use_PrivateKey_file(m_context, privateKey, SSL_FILETYPE_PEM))
		throw CServerStartException( _T("秘密鍵のロードに失敗しました"));

	//	ランダム初期化
	RAND_seed(GetRandomString(10240),10240);

	//	ミューテックス初期化
	InitLock();

	//	別スレッドからコールされるので、エラーキューを削除しておく
	ERR_remove_state(0);
}


/*!
	停止
*/
void CSSLContext::CloseOpenSSL()
{
	SSL_CTX_free(m_context);

	ENGINE_cleanup();
	CONF_modules_unload(TRUE);
	CONF_modules_free();

	CRYPTO_cleanup_all_ex_data();
	EVP_cleanup();

	ERR_clear_error();
	ERR_free_strings();
	ERR_remove_state(0);

	EndLock();
}

/*!
	ランダム文字列取得
*/
CString CSSLContext::GetRandomString(int count)
{
	DWORD	seed = GetTickCount();
	seed += (DWORD)CTime::GetCurrentTime().GetTime();
	srand(seed);

	CString	random;
	for(int i=0;i<count;i++)
		random += (char)((rand() % 254) + 1);

	return(random);
}

/////////////////////////////////////////////////////////////////////////////////////
//	コールバック関数
/////////////////////////////////////////////////////////////////////////////////////
/*!
	ロックコールバック
*/
void CSSLContext::Locking_cb(int mode, int type, const char *file, int line)
{
	if (mode & CRYPTO_LOCK)
		WaitForSingleObject(g_lock_cs[type],INFINITE);
	else
		ReleaseMutex(g_lock_cs[type]);
}

/*!
	パスワードコールバック
*/
int CSSLContext::Password_cb(char *buf, int num, int rwflag, void *userdata)
{
	CString password = ((CSSLContext *)userdata)->GetPassword();

	//	長さチェック
	if(num < password.GetLength())
		return(0);

	//	パスワード取得
	strcpy_s(buf, num, password);
	return(password.GetLength());
}

/*!
	スレッドコールバック
*/
unsigned long CSSLContext::GetThreadId_cb()
{
	return ::GetCurrentThreadId();
}

/*!
	パスワード取得
*/
CString CSSLContext::GetPassword()
{
	SCOPE_LOCK();

	return(m_password);
}


//////////////////////////////////////////////////////////////////////
//	Mutex
//////////////////////////////////////////////////////////////////////
/*!
	ミューテックス初期化
*/
void CSSLContext::InitLock()
{
	if(g_lock_cs == NULL)
	{
		g_lock_cs=(HANDLE*)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(HANDLE));

		//	Mutext生成
		for(int i=0; i<CRYPTO_num_locks(); i++)
			g_lock_cs[i]=CreateMutex(NULL,FALSE,NULL);

		CRYPTO_set_id_callback(GetThreadId_cb);
		CRYPTO_set_locking_callback(Locking_cb);
	}
}

/*!
	ミューテックス削除
*/
void CSSLContext::EndLock()
{
	if(g_lock_cs)
	{
		CRYPTO_set_id_callback(NULL);
		CRYPTO_set_locking_callback(NULL);

		//	Mutex解放
		for(int i=0; i<CRYPTO_num_locks(); i++)
			CloseHandle(g_lock_cs[i]);

		OPENSSL_free(g_lock_cs);
		g_lock_cs = NULL;
	}
}
