/*!
	このクラスは、スレッドセーフではない。
	マルチスレッド不可
*/

#include "StdAfx.h"
#include "opensslutil.h"
#include "openssl/ssl.h"
#include "openssl/bio.h"
#include "openssl/err.h"
#include "openssl/bn.h"
#include "openssl/rsa.h"
#include "openssl/evp.h"
#include "openssl/x509.h"
#include "openssl/pem.h"
#include "openssl/rand.h"
#include "dir.h"

COpenSSLUtil::COpenSSLUtil(void)
{
	m_pkey = NULL;

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


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

COpenSSLUtil::~COpenSSLUtil(void)
{
	if(m_pkey)
		EVP_PKEY_free(m_pkey);
	m_pkey = NULL;


	ERR_free_strings();
	ERR_remove_state(0);
	EVP_cleanup();
	CRYPTO_cleanup_all_ex_data();
}

//////////////////////////////////////////////////////////////////////////////////////////
//	キー生成
//////////////////////////////////////////////////////////////////////////////////////////
/*!
	RSAのプライベートキーを生成する

	キーサイズ:1024ビット
	キー暗号化:DES暗号
*/
int COpenSSLUtil::GeneratePrivateKey(int bit)
{
	//	RSAキーの生成
	RSA *rsa = RSA_generate_key(bit, RSA_F4,Progress_cb,this);
	if(rsa == NULL)
		return(-1);

	m_pkey = EVP_PKEY_new();
	if(m_pkey == NULL)
	{
		RSA_free(rsa);
		return(-1);
	}

	//	キーの割り当て
	EVP_PKEY_assign_RSA(m_pkey,rsa);

	return(0);
}

/*!
	RSAのプライベートキーを保存する

	キー暗号化:DES暗号
*/
int COpenSSLUtil::SavePrivateKey(CString path)
{
	if(m_pkey == NULL)
		return(-1);
	
	CDir::MakeAllDirectory(CDir::PathToDir(path));
	FILE *out = NULL;
	if(fopen_s(&out, path,"w"))
		return(-1);

	PEM_write_PrivateKey(out,m_pkey,EVP_des_cbc(),NULL,0,Password_cb,this);
	fclose(out);

	return(0);
}

/*!
	CSR認証要求ファイルを保存する
*/
int COpenSSLUtil::SaveCSR(CString path,CX509Entry entry)
{
	if(m_pkey == NULL)
		return(-1);

	CDir::MakeAllDirectory(CDir::PathToDir(path));
	FILE *out = NULL;;
	if(fopen_s(&out, path,"wb"))
		return(-1);

	X509_REQ	*req = X509_REQ_new();
	if(req == NULL)
	{
		fclose(out);
		return(-1);
	}

	//	X509の基本情報
	X509_REQ_set_version(req,0);
	X509_REQ_set_pubkey(req, m_pkey);

	//	X509のSUBJECT
	X509_NAME *subj = X509_REQ_get_subject_name(req);
	X509_NAME_add_entry_by_txt(subj,"C"     ,MBSTRING_ASC, (unsigned char*)entry.m_countryName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"ST"    ,MBSTRING_ASC, (unsigned char*)entry.m_statName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"L"     ,MBSTRING_ASC, (unsigned char*)entry.m_localityName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"O"     ,MBSTRING_ASC, (unsigned char*)entry.m_organizationName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"OU"    ,MBSTRING_ASC, (unsigned char*)entry.m_organizationalUnitName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"CN"    ,MBSTRING_ASC, (unsigned char*)entry.m_commonName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_NID(subj,NID_pkcs9_emailAddress     ,MBSTRING_ASC, (unsigned char*)entry.m_email.GetBuffer(0), -1, -1, 0);

	//	署名
	X509_REQ_sign(req, m_pkey, EVP_md5());


	//	チェック
	if(X509_REQ_verify(req, m_pkey) == 0)
	{
		X509_REQ_free(req);
		fclose(out);
		return(-1);
	}


	PEM_write_X509_REQ(out,req);

	X509_REQ_free(req);
	fclose(out);
	return(0);
}


/*!
	サイト証明ファイルを保存する
*/
int COpenSSLUtil::SaveCRT(CString path,CX509Entry entry)
{
	if(m_pkey == NULL)
		return(-1);

	CDir::MakeAllDirectory(CDir::PathToDir(path));
	FILE *out = NULL;
	if(fopen_s(&out, path,"wb"))
		return(-1);

	X509	*x509 = X509_new();
	if(x509 == NULL)
	{
		fclose(out);
		return(-1);
	}

	//	X509の基本情報
	X509_set_version(x509,0);
	ASN1_INTEGER_set(X509_get_serialNumber(x509),entry.m_serialNumber);
	X509_gmtime_adj(X509_get_notBefore(x509),entry.m_notBeforeDays);
	X509_gmtime_adj(X509_get_notAfter(x509),entry.m_notAfterDays);
	X509_set_pubkey(x509, m_pkey);

	//	X509のSUBJECT
	X509_NAME *subj = X509_get_subject_name(x509);
	X509_NAME_add_entry_by_txt(subj,"C"     ,MBSTRING_ASC, (unsigned char*)entry.m_countryName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"ST"    ,MBSTRING_ASC, (unsigned char*)entry.m_statName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"L"     ,MBSTRING_ASC, (unsigned char*)entry.m_localityName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"O"     ,MBSTRING_ASC, (unsigned char*)entry.m_organizationName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"OU"    ,MBSTRING_ASC, (unsigned char*)entry.m_organizationalUnitName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_txt(subj,"CN"    ,MBSTRING_ASC, (unsigned char*)entry.m_commonName.GetBuffer(0), -1, -1, 0);
	X509_NAME_add_entry_by_NID(subj,NID_pkcs9_emailAddress     ,MBSTRING_ASC, (unsigned char*)entry.m_email.GetBuffer(0), -1, -1, 0);

	//	署名者情報
	X509_set_issuer_name(x509,subj);
	X509_sign(x509, m_pkey, EVP_md5());

	//	チェック
	if(X509_verify(x509, m_pkey) == 0)
	{
		X509_free(x509);
		fclose(out);
		return(-1);
	}


	PEM_write_X509(out,x509);

	X509_free(x509);
	fclose(out);
	return(0);
}


//	パスワード設定
void COpenSSLUtil::SetPassword(CString passwd)
{
	m_password = passwd;
}


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

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

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

//	作業コールバック
void COpenSSLUtil::Progress_cb(int p, int n, void *arg)
{
	TRACE(".");
}


//	パスワード取得
CString COpenSSLUtil::GetPassword()
{
	return(m_password);
}


//	ランダム文字列生成
CString COpenSSLUtil::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);
}

