#include "StdAfx.h"
#include "SSLContext.h"
#include "ServerStartErrorException.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)
{
}

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

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

	//	コピー
	m_serverTool = serverTool;
	m_setting = m_serverTool.GetManagerSetting(m_setting.GetSettingName());


	//	SSL初期化
	InitOpenSSL();
	m_serverTool.WriteSystemLog(CLASS_NAME, SERVER_STATUS_NORMAL, _T("OpenSSLの初期化に成功しました"));
}

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

	//	SSL停止
	CloseOpenSSL();
}

/////////////////////////////////////////////////////////////////////////////////////
//	OpenSSL
/////////////////////////////////////////////////////////////////////////////////////
/*!
	初期化
*/
void CSSLContext::InitOpenSSL()
{
	//	設定取得
	CString	privateKey = m_setting.GetPrivateKey();
	CString	certificate = m_setting.GetCertificate();
	CString	certificateChain = m_setting.GetCertificateChain();
	m_password = m_setting.GetPrivateKeyPassword();

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

	//	コンテキスト生成
	m_method = SSLv23_server_method();	//	v2, v3
	m_context = SSL_CTX_new(m_method);

	//	証明書ロード
	if(!SSL_CTX_use_certificate_chain_file(m_context, certificate/*, SSL_FILETYPE_PEM*/))
		throw CServerStartErrorException(CLASS_NAME, "サーバ証明書のロードに失敗しました");
/*
	//	中間証明書ロード
	if(!certificateChain.IsEmpty())
		if(!SSL_CTX_add_extra_chain_cert(m_context, certificateChain, SSL_FILETYPE_PEM))
			throw CServerStartErrorException(CLASS_NAME, "サーバ中間証明書のロードに失敗しました");
*/
	//	パスワードコールバック初期化
	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 CServerStartErrorException(CLASS_NAME, "秘密鍵のロードに失敗しました");

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

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


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

	ENGINE_cleanup();
	CONF_modules_unload(1);
	CONF_modules_free();

	CRYPTO_cleanup_all_ex_data();
	EVP_cleanup();

	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;
	}
}
