// SoftEther VPN Source Code
// Mayaqua Kernel
// 
// SoftEther VPN Server, Client and Bridge are free software under GPLv2.
// 
// Copyright (c) 2012-2014 Daiyuu Nobori.
// Copyright (c) 2012-2014 SoftEther VPN Project, University of Tsukuba, Japan.
// Copyright (c) 2012-2014 SoftEther Corporation.
// 
// All Rights Reserved.
// 
// http://www.softether.org/
// 
// Author: Daiyuu Nobori
// Comments: Tetsuo Sugiyama, Ph.D.
// 
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// version 2 as published by the Free Software Foundation.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License version 2
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// 
// 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 AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 
// THE LICENSE AGREEMENT IS ATTACHED ON THE SOURCE-CODE PACKAGE
// AS "LICENSE.TXT" FILE. READ THE TEXT FILE IN ADVANCE TO USE THE SOFTWARE.
// 
// 
// THIS SOFTWARE IS DEVELOPED IN JAPAN, AND DISTRIBUTED FROM JAPAN,
// UNDER JAPANESE LAWS. YOU MUST AGREE IN ADVANCE TO USE, COPY, MODIFY,
// MERGE, PUBLISH, DISTRIBUTE, SUBLICENSE, AND/OR SELL COPIES OF THIS
// SOFTWARE, THAT ANY JURIDICAL DISPUTES WHICH ARE CONCERNED TO THIS
// SOFTWARE OR ITS CONTENTS, AGAINST US (SOFTETHER PROJECT, SOFTETHER
// CORPORATION, DAIYUU NOBORI OR OTHER SUPPLIERS), OR ANY JURIDICAL
// DISPUTES AGAINST US WHICH ARE CAUSED BY ANY KIND OF USING, COPYING,
// MODIFYING, MERGING, PUBLISHING, DISTRIBUTING, SUBLICENSING, AND/OR
// SELLING COPIES OF THIS SOFTWARE SHALL BE REGARDED AS BE CONSTRUED AND
// CONTROLLED BY JAPANESE LAWS, AND YOU MUST FURTHER CONSENT TO
// EXCLUSIVE JURISDICTION AND VENUE IN THE COURTS SITTING IN TOKYO,
// JAPAN. YOU MUST WAIVE ALL DEFENSES OF LACK OF PERSONAL JURISDICTION
// AND FORUM NON CONVENIENS. PROCESS MAY BE SERVED ON EITHER PARTY IN
// THE MANNER AUTHORIZED BY APPLICABLE LAW OR COURT RULE.
// 
// USE ONLY IN JAPAN. DO NOT USE IT IN OTHER COUNTRIES. IMPORTING THIS
// SOFTWARE INTO OTHER COUNTRIES IS AT YOUR OWN RISK. SOME COUNTRIES
// PROHIBIT ENCRYPTED COMMUNICATIONS. USING THIS SOFTWARE IN OTHER
// COUNTRIES MIGHT BE RESTRICTED.
// 
// 
// SOURCE CODE CONTRIBUTION
// ------------------------
// 
// Your contribution to SoftEther VPN Project is much appreciated.
// Please send patches to us through GitHub.
// Read the SoftEther VPN Patch Acceptance Policy in advance:
// http://www.softether.org/5-download/src/9.patch
// 
// 
// DEAR SECURITY EXPERTS
// ---------------------
// 
// If you find a bug or a security vulnerability please kindly inform us
// about the problem immediately so that we can fix the security problem
// to protect a lot of users around the world as soon as possible.
// 
// Our e-mail address for security reports is:
// softether-vpn-security [at] softether.org
// 
// Please note that the above e-mail address is not a technical support
// inquiry address. If you need technical assistance, please visit
// http://www.softether.org/ and ask your question on the users forum.
// 
// Thank you for your cooperation.


// Secure.c
// Security token management module

#include <GlobalConst.h>

#define	SECURE_C
#define	ENCRYPT_C

#ifdef	WIN32
#include <windows.h>
#endif	// WIN32

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <stdarg.h>
#include <time.h>
#include <errno.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/engine.h>
#include <openssl/bio.h>
#include <openssl/x509.h>
#include <openssl/pkcs7.h>
#include <openssl/pkcs12.h>
#include <openssl/rc4.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <Mayaqua/Mayaqua.h>
#include <Mayaqua/cryptoki.h>


#define	MAX_OBJ				1024		// Maximum number of objects in the hardware (assumed)

#define	A_SIZE(a, i)		(a[(i)].ulValueLen)
#define	A_SET(a, i, value, size)	(a[i].pValue = value;a[i].ulValueLen = size;)

#ifdef	OS_WIN32
// Code for Win32

// DLL reading for Win32
HINSTANCE Win32SecureLoadLibraryEx(char *dllname, DWORD flags)
{
	char tmp1[MAX_PATH];
	char tmp2[MAX_PATH];
	char tmp3[MAX_PATH];
	HINSTANCE h;
	// Validate arguments
	if (dllname == NULL)
	{
		return NULL;
	}

	Format(tmp1, sizeof(tmp1), "%s\\%s", MsGetSystem32Dir(), dllname);
	Format(tmp2, sizeof(tmp2), "%s\\JPKI\\%s", MsGetProgramFilesDir(), dllname);
	Format(tmp3, sizeof(tmp3), "%s\\LGWAN\\%s", MsGetProgramFilesDir(), dllname);

	h = LoadLibraryEx(dllname, NULL, flags);
	if (h != NULL)
	{
		return h;
	}

	h = LoadLibraryEx(tmp1, NULL, flags);
	if (h != NULL)
	{
		return h;
	}

	h = LoadLibraryEx(tmp2, NULL, flags);
	if (h != NULL)
	{
		return h;
	}

	h = LoadLibraryEx(tmp3, NULL, flags);
	if (h != NULL)
	{
		return h;
	}

	return NULL;
}

// Examine whether the specified device is installed
bool Win32IsDeviceSupported(SECURE_DEVICE *dev)
{
	HINSTANCE hInst;
	// Validate arguments
	if (dev == NULL)
	{
		return false;
	}

	// Check whether the DLL is readable
	hInst = Win32SecureLoadLibraryEx(dev->ModuleName, DONT_RESOLVE_DLL_REFERENCES);
	if (hInst == NULL)
	{
		return false;
	}

	FreeLibrary(hInst);

	return true;
}

// Load the device module
bool Win32LoadSecModule(SECURE *sec)
{
	SEC_DATA_WIN32 *w;
	HINSTANCE hInst;
	CK_FUNCTION_LIST_PTR api = NULL;
	CK_RV (*get_function_list)(CK_FUNCTION_LIST_PTR_PTR);
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}

	if (sec->Dev->Id == 9)
	{
		char username[MAX_SIZE];
		DWORD size;
		// Because the device driver of Juki-Net needs the contents 
		// of the Software\JPKI registry key on HKLU of SYSTEM,
		// if there is no key, copy the key from the value of other user
//		if (MsRegIsValue(REG_CURRENT_USER, "Software\\JPKI", "Name") == false ||
//			MsRegIsValue(REG_CURRENT_USER, "Software\\JPKI", "RWType") == false)
		size = sizeof(username);
		GetUserName(username, &size);
		if (StrCmpi(username, "System") == 0)
		{
			TOKEN_LIST *t = MsRegEnumKey(REG_USERS, NULL);

			if (t != NULL)
			{
				UINT i;

				for (i = 0;i < t->NumTokens;i++)
				{
					char tmp[MAX_PATH];

					if (StrCmpi(t->Token[i], ".DEFAULT") != 0 && StrCmpi(t->Token[i], "S-1-5-18") != 0)
					{
						Format(tmp, sizeof(tmp), "%s\\Software\\JPKI", t->Token[i]);

						if (MsRegIsValue(REG_USERS, tmp, "Name") && MsRegIsValue(REG_USERS, tmp, "RWType"))
						{
							char *name = MsRegReadStr(REG_USERS, tmp, "Name");
							char *port = MsRegReadStr(REG_USERS, tmp, "Port");
							UINT type = MsRegReadInt(REG_USERS, tmp, "RWType");

							MsRegWriteStr(REG_CURRENT_USER, "Software\\JPKI", "Name", name);
							MsRegWriteStr(REG_CURRENT_USER, "Software\\JPKI", "Port", port);
							MsRegWriteInt(REG_CURRENT_USER, "Software\\JPKI", "RWType", type);

							Free(name);
							Free(port);
							break;
						}
					}
				}

				FreeToken(t);
			}
		}
	}

	// Load the Library
	hInst = Win32SecureLoadLibraryEx(sec->Dev->ModuleName, 0);
	if (hInst == NULL)
	{
		// Failure
		return false;
	}

	// Get the API
	get_function_list = (CK_RV (*)(CK_FUNCTION_LIST_PTR_PTR))
		GetProcAddress(hInst, "C_GetFunctionList");

	if (get_function_list == NULL)
	{
		// Failure
		FreeLibrary(hInst);
		return false;
	}

	get_function_list(&api);
	if (api == NULL)
	{
		// Failure
		FreeLibrary(hInst);
		return false;
	}

	sec->Data = ZeroMalloc(sizeof(SEC_DATA_WIN32));
	w = sec->Data;

	w->hInst = hInst;
	sec->Api = api;

	return true;
}

// Unload the device module
void Win32FreeSecModule(SECURE *sec)
{
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}
	if (sec->Data == NULL)
	{
		return;
	}

	// Unload
	FreeLibrary(sec->Data->hInst);
	Free(sec->Data);

	sec->Data = NULL;
}

#endif	// OS_WIN32


// Whether the specified device is a JPKI
bool IsJPKI(bool id)
{
	if (id == 9 || id == 13)
	{
		return true;
	}

	return false;
}

// Sign with the private key which is specified by the name in the secure device
bool SignSec(SECURE *sec, char *name, void *dst, void *src, UINT size)
{
	SEC_OBJ *obj;
	UINT ret;
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (name == NULL || dst == NULL || src == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}

	obj = FindSecObject(sec, name, SEC_K);
	if (obj == NULL)
	{
		return false;
	}

	ret = SignSecByObject(sec, obj, dst, src, size);

	FreeSecObject(obj);

	return ret;
}

// Sign with the private key of the secure device
bool SignSecByObject(SECURE *sec, SEC_OBJ *obj, void *dst, void *src, UINT size)
{
	CK_MECHANISM mechanism = {CKM_RSA_PKCS, NULL, 0};
	UINT ret;
	UCHAR hash[SIGN_HASH_SIZE];
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (obj == NULL || dst == NULL || src == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}
	if (sec->LoginFlag == false && obj->Private)
	{
		sec->Error = SEC_ERROR_NOT_LOGIN;
		return false;
	}
	if (obj->Type != SEC_K)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}

	// Hash
	HashForSign(hash, sizeof(hash), src, size);

	// Signature initialization
	ret = sec->Api->C_SignInit(sec->SessionId, &mechanism, obj->Object);
	if (ret != CKR_OK)
	{
		// Failure
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		Debug("C_SignInit Error: 0x%x\n", ret);
		return false;
	}

	// Perform Signing
	size = 128;
	ret = sec->Api->C_Sign(sec->SessionId, hash, sizeof(hash), dst, &size);
	if (ret != CKR_OK || size != 128)
	{
		// Failure
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		Debug("C_Sign Error: 0x%x\n", ret);
		return false;
	}

	return true;
}

// Changing the PIN code
bool ChangePin(SECURE *sec, char *old_pin, char *new_pin)
{
	// Validate arguments
	if (sec == NULL || old_pin == NULL || new_pin == NULL)
	{
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}
	if (sec->LoginFlag == false)
	{
		sec->Error = SEC_ERROR_NOT_LOGIN;
		return false;
	}
	if (sec->IsReadOnly)
	{
		sec->Error = SEC_ERROR_OPEN_SESSION;
		return false;
	}

	// Change then PIN
	if (sec->Api->C_SetPIN(sec->SessionId, old_pin, StrLen(old_pin),
		new_pin, StrLen(new_pin)) != CKR_OK)
	{
		return false;
	}

	return true;
}

// Write the private key object
bool WriteSecKey(SECURE *sec, bool private_obj, char *name, K *k)
{
	UINT key_type = CKK_RSA;
	CK_BBOOL b_true = true, b_false = false, b_private_obj = private_obj;
	UINT obj_class = CKO_PRIVATE_KEY;
	UINT object;
	UINT ret;
	BUF *b;
	RSA *rsa;
	UCHAR modules[MAX_SIZE], pub[MAX_SIZE], pri[MAX_SIZE], prime1[MAX_SIZE], prime2[MAX_SIZE];
	CK_ATTRIBUTE a[] =
	{
		{CKA_MODULUS,			modules,		0},		// 0
		{CKA_PUBLIC_EXPONENT,	pub,			0},		// 1
		{CKA_PRIVATE_EXPONENT,	pri,			0},		// 2
		{CKA_PRIME_1,			prime1,			0},		// 3
		{CKA_PRIME_2,			prime2,			0},		// 4
		{CKA_CLASS,				&obj_class,		sizeof(obj_class)},
		{CKA_TOKEN,				&b_true,		sizeof(b_true)},
		{CKA_PRIVATE,			&b_private_obj,	sizeof(b_private_obj)},
		{CKA_LABEL,				name,			StrLen(name)},
		{CKA_KEY_TYPE,			&key_type,		sizeof(key_type)},
		{CKA_DERIVE,			&b_false,		sizeof(b_false)},
		{CKA_SUBJECT,			name,			StrLen(name)},
		{CKA_SENSITIVE,			&b_true,		sizeof(b_true)},
		{CKA_DECRYPT,			&b_true,		sizeof(b_true)},
		{CKA_SIGN,				&b_true,		sizeof(b_true)},
		{CKA_SIGN_RECOVER,		&b_false,		sizeof(b_false)},
		{CKA_EXTRACTABLE,		&b_false,		sizeof(b_false)},
		{CKA_MODIFIABLE,		&b_false,		sizeof(b_false)},
	};
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (name == NULL || k == NULL || k->private_key == false)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}
	if (sec->LoginFlag == false && private_obj)
	{
		sec->Error = SEC_ERROR_NOT_LOGIN;
		return false;
	}

	// Numeric data generation
	rsa = k->pkey->pkey.rsa;
	if (rsa == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	b = BigNumToBuf(rsa->n);
	ReadBuf(b, modules, sizeof(modules));
	A_SIZE(a, 0) = b->Size;
	FreeBuf(b);

	b = BigNumToBuf(rsa->e);
	ReadBuf(b, pub, sizeof(pub));
	A_SIZE(a, 1) = b->Size;
	FreeBuf(b);

	b = BigNumToBuf(rsa->d);
	ReadBuf(b, pri, sizeof(pri));
	A_SIZE(a, 2) = b->Size;
	FreeBuf(b);

	b = BigNumToBuf(rsa->p);
	ReadBuf(b, prime1, sizeof(prime1));
	A_SIZE(a, 3) = b->Size;
	FreeBuf(b);

	b = BigNumToBuf(rsa->q);
	ReadBuf(b, prime2, sizeof(prime2));
	A_SIZE(a, 4) = b->Size;
	FreeBuf(b);

	// Delete the old key if it exists
	if (CheckSecObject(sec, name, SEC_K))
	{
		DeleteSecKey(sec, name);
	}

	// Creating
	if ((ret = sec->Api->C_CreateObject(sec->SessionId, a, sizeof(a) / sizeof(a[0]), &object)) != CKR_OK)
	{
		// Failure
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		Debug("ret: 0x%x\n", ret);
		return false;
	}

	// Clear Cache
	EraseEnumSecObjectCache(sec);

	return true;
}

// Read the certificate object by specifying the name
X *ReadSecCert(SECURE *sec, char *name)
{
	SEC_OBJ *obj;
	X *x;
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}

	// Search
	obj = FindSecObject(sec, name, SEC_X);
	if (obj == NULL)
	{
		return false;
	}

	// Acquisition
	x = ReadSecCertFromObject(sec, obj);

	FreeSecObject(obj);

	return x;
}

// Read the certificate object
X *ReadSecCertFromObject(SECURE *sec, SEC_OBJ *obj)
{
	UINT size;
	X *x;
	UCHAR value[4096];
	BUF *b;
	CK_ATTRIBUTE get[] =
	{
		{CKA_VALUE,		value,		sizeof(value)},
	};
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}
	if (sec->LoginFlag == false && obj->Private)
	{
		sec->Error = SEC_ERROR_NOT_LOGIN;
		return false;
	}
	if (obj->Type != SEC_X)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}

	// Acquisition
	if (sec->Api->C_GetAttributeValue(
		sec->SessionId, obj->Object, get, sizeof(get) / sizeof(get[0])) != CKR_OK)
	{
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		return 0;
	}

	size = A_SIZE(get, 0);

	// Conversion
	b = NewBuf();
	WriteBuf(b, value, size);
	SeekBuf(b, 0, 0);

	x = BufToX(b, false);
	if (x == NULL)
	{
		sec->Error = SEC_ERROR_INVALID_CERT;
	}

	FreeBuf(b);

	return x;
}

// Write the certificate object
bool WriteSecCert(SECURE *sec, bool private_obj, char *name, X *x)
{
	UINT obj_class = CKO_CERTIFICATE;
	CK_BBOOL b_true = true, b_false = false, b_private_obj = private_obj;
	UINT cert_type = CKC_X_509;
	CK_DATE start_date, end_date;
	UCHAR subject[MAX_SIZE];
	UCHAR issuer[MAX_SIZE];
	wchar_t w_subject[MAX_SIZE];
	wchar_t w_issuer[MAX_SIZE];
	UCHAR serial_number[MAX_SIZE];
	UCHAR value[4096];
	UINT ret;
	BUF *b;
	UINT object;
	CK_ATTRIBUTE a[] =
	{
		{CKA_SUBJECT,			subject,		0},			// 0
		{CKA_ISSUER,			issuer,			0},			// 1
		{CKA_SERIAL_NUMBER,		serial_number,	0},			// 2
		{CKA_VALUE,				value,			0},			// 3
		{CKA_CLASS,				&obj_class,		sizeof(obj_class)},
		{CKA_TOKEN,				&b_true,		sizeof(b_true)},
		{CKA_PRIVATE,			&b_private_obj,	sizeof(b_private_obj)},
		{CKA_LABEL,				name,			StrLen(name)},
		{CKA_CERTIFICATE_TYPE,	&cert_type,		sizeof(cert_type)},
#if	0		// Don't use these because some tokens fail
		{CKA_START_DATE,		&start_date,	sizeof(start_date)},
		{CKA_END_DATE,			&end_date,		sizeof(end_date)},
#endif
	};
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (name == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}
	if (sec->LoginFlag == false && private_obj)
	{
		sec->Error = SEC_ERROR_NOT_LOGIN;
		return false;
	}

	// Copy the certificate to the buffer
	b = XToBuf(x, false);
	if (b == NULL)
	{
		sec->Error = SEC_ERROR_INVALID_CERT;
		return false;
	}
	if (b->Size > sizeof(value))
	{
		// Size is too large
		FreeBuf(b);
		sec->Error = SEC_ERROR_DATA_TOO_BIG;
		return false;
	}
	Copy(value, b->Buf, b->Size);
	A_SIZE(a, 3) = b->Size;
	FreeBuf(b);

	// Store the Subject and the Issuer by encoding into UTF-8
	GetPrintNameFromName(w_subject, sizeof(w_subject), x->subject_name);
	UniToUtf8(subject, sizeof(subject), w_subject);
	A_SIZE(a, 0) = StrLen(subject);
	if (x->root_cert == false)
	{
		GetPrintNameFromName(w_issuer, sizeof(w_issuer), x->issuer_name);
		UniToUtf8(issuer, sizeof(issuer), w_issuer);
		A_SIZE(a, 1) = StrLen(issuer);
	}

	// Copy the serial number
	Copy(serial_number, x->serial->data, MIN(x->serial->size, sizeof(serial_number)));
	A_SIZE(a, 2) = MIN(x->serial->size, sizeof(serial_number));

	// Expiration date information
	UINT64ToCkDate(&start_date, SystemToLocal64(x->notBefore));
	UINT64ToCkDate(&end_date, SystemToLocal64(x->notAfter));

	// Remove objects which have the same name
	if (CheckSecObject(sec, name, SEC_X))
	{
		DeleteSecCert(sec, name);
	}

	// Creating
	if ((ret = sec->Api->C_CreateObject(sec->SessionId, a, sizeof(a) / sizeof(a[0]), &object)) != CKR_OK)
	{
		// Failure
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		Debug("Error: 0x%02x\n", ret);
		return false;
	}

	// Clear Cache
	EraseEnumSecObjectCache(sec);

	return true;
}

// Delete the private key object
bool DeleteSecKey(SECURE *sec, char *name)
{
	return DeleteSecObjectByName(sec, name, SEC_K);
}

// Delete the certificate object
bool DeleteSecCert(SECURE *sec, char *name)
{
	return DeleteSecObjectByName(sec, name, SEC_X);
}

// Convert the the CK_DATE to the 64 bit time
UINT64 CkDateToUINT64(struct CK_DATE *ck_date)
{
	SYSTEMTIME st;
	char year[32], month[32], day[32];
	// Validate arguments
	if (ck_date == NULL)
	{
		return 0;
	}

	Zero(year, sizeof(year));
	Zero(month, sizeof(month));
	Zero(day, sizeof(day));

	Copy(year, ck_date->year, 4);
	Copy(month, ck_date->month, 2);
	Copy(day, ck_date->day, 2);

	st.wYear = ToInt(year);
	st.wMonth = ToInt(month);
	st.wDay = ToInt(day);

	return SystemToUINT64(&st);
}

// Convert the 64 bit time to the CK_DATE
void UINT64ToCkDate(void *p_ck_date, UINT64 time64)
{
	SYSTEMTIME st;
	char year[32], month[32], day[32];
	struct CK_DATE *ck_date = (CK_DATE *)p_ck_date;
	// Validate arguments
	if (ck_date == NULL)
	{
		return;
	}

	UINT64ToSystem(&st, time64);

	Format(year, sizeof(year), "%04u", st.wYear);
	Format(month, sizeof(month), "%04u", st.wMonth);
	Format(day, sizeof(day), "%04u", st.wDay);

	Zero(ck_date, sizeof(CK_DATE));

	Copy(ck_date->year, year, 4);
	Copy(ck_date->month, month, 2);
	Copy(ck_date->day, day, 2);
}

// Delete the object by specifying the name
bool DeleteSecObjectByName(SECURE *sec, char *name, UINT type)
{
	bool ret;
	SEC_OBJ *obj;
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (name == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}

	// Get the Object
	obj = FindSecObject(sec, name, type);
	if (obj == NULL)
	{
		// Failure
		return false;
	}

	// Delete the Object 
	ret = DeleteSecObject(sec, obj);

	// Memory release
	FreeSecObject(obj);

	return ret;
}

// Delete the Data
bool DeleteSecData(SECURE *sec, char *name)
{
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (name == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}

	return DeleteSecObjectByName(sec, name, SEC_DATA);
}

// Delete the secure object
bool DeleteSecObject(SECURE *sec, SEC_OBJ *obj)
{
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (obj == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}
	if (sec->LoginFlag == false && obj->Private)
	{
		sec->Error = SEC_ERROR_NOT_LOGIN;
		return false;
	}

	// Delete the Object
	if (sec->Api->C_DestroyObject(sec->SessionId, obj->Object) != CKR_OK)
	{
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		return false;
	}

	// Clear the Cache
	DeleteSecObjFromEnumCache(sec, obj->Name, obj->Type);

	return true;
}

// Remove the object which have the specified name from the cache
void DeleteSecObjFromEnumCache(SECURE *sec, char *name, UINT type)
{
	UINT i;
	// Validate arguments
	if (sec == NULL || name == NULL || sec->EnumCache == NULL)
	{
		return;
	}

	for (i = 0;i < LIST_NUM(sec->EnumCache);i++)
	{
		SEC_OBJ *obj = LIST_DATA(sec->EnumCache, i);

		if (StrCmpi(obj->Name, name) == 0)
		{
			if (obj->Type == type)
			{
				Delete(sec->EnumCache, obj);
				FreeSecObject(obj);
				break;
			}
		}
	}
}

// Read by searching a secure object by name
int ReadSecData(SECURE *sec, char *name, void *data, UINT size)
{
	UINT ret = 0;
	SEC_OBJ *obj;
	// Validate arguments
	if (sec == NULL || name == NULL || data == NULL)
	{
		return 0;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return 0;
	}

	// Read
	obj = FindSecObject(sec, name, SEC_DATA);
	if (obj == NULL)
	{
		// Not found
		return 0;
	}

	// Read
	ret = ReadSecDataFromObject(sec, obj, data, size);

	FreeSecObject(obj);

	return ret;
}

// Clear the cache
void EraseEnumSecObjectCache(SECURE *sec)
{
	// Validate arguments
	if (sec == NULL || sec->EnumCache == NULL)
	{
		return;
	}

	FreeEnumSecObject(sec->EnumCache);
	sec->EnumCache = NULL;
}

// Check for the existence of a secure object
bool CheckSecObject(SECURE *sec, char *name, UINT type)
{
	SEC_OBJ *obj;
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (name == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return 0;
	}

	obj = FindSecObject(sec, name, type);

	if (obj == NULL)
	{
		return false;
	}
	else
	{
		FreeSecObject(obj);
		return true;
	}
}

// Cloning a secure object structure
SEC_OBJ *CloneSecObject(SEC_OBJ *obj)
{
	SEC_OBJ *ret;
	// Validate arguments
	if (obj == NULL)
	{
		return NULL;
	}

	ret = ZeroMalloc(sizeof(SEC_OBJ));
	ret->Name = CopyStr(obj->Name);
	ret->Object = obj->Object;
	ret->Private = obj->Private;
	ret->Type = obj->Type;

	return ret;
}

// Search a secure object by the name
SEC_OBJ *FindSecObject(SECURE *sec, char *name, UINT type)
{
	LIST *o;
	UINT i;
	SEC_OBJ *ret = NULL;
	// Validate arguments
	if (sec == NULL)
	{
		return NULL;
	}
	if (name == NULL)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return NULL;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return 0;
	}

	// Enumeration
	o = EnumSecObject(sec);
	if (o == NULL)
	{
		return NULL;
	}
	for (i = 0;i < LIST_NUM(o);i++)
	{
		SEC_OBJ *obj = LIST_DATA(o, i);

		if (obj->Type == type || type == INFINITE)
		{
			if (StrCmpi(obj->Name, name) == 0)
			{
				ret = CloneSecObject(obj);
				break;
			}
		}
	}
	FreeEnumSecObject(o);

	if (ret == NULL)
	{
		sec->Error = SEC_ERROR_OBJ_NOT_FOUND;
	}

	return ret;
}

// Reading a secure object
int ReadSecDataFromObject(SECURE *sec, SEC_OBJ *obj, void *data, UINT size)
{
	UCHAR buf[MAX_SEC_DATA_SIZE];
	UINT i;
	CK_ATTRIBUTE get[] =
	{
		{CKA_VALUE,	 buf,	sizeof(buf)},
	};
	// Validate arguments
	if (sec == NULL)
	{
		return 0;
	}
	if (obj == NULL || data == NULL || size == 0)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return 0;
	}
	if (obj->Type != SEC_DATA)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return 0;
	}
	if (sec->LoginFlag == false && obj->Private)
	{
		sec->Error = SEC_ERROR_NOT_LOGIN;
		return 0;
	}

	// Acquisition
	if (sec->Api->C_GetAttributeValue(
		sec->SessionId, obj->Object, get, sizeof(get) / sizeof(get[0])) != CKR_OK)
	{
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		return 0;
	}

	// Return the result
	i = get[0].ulValueLen;
	if (i > MAX_SEC_DATA_SIZE || i > size)
	{
		// Data is too large
		sec->Error = SEC_ERROR_DATA_TOO_BIG;
		return 0;
	}

	// Memory copy
	Copy(data, buf, i);

	return i;
}

// Release of enumeration results of the secure object
void FreeEnumSecObject(LIST *o)
{
	UINT i;
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		SEC_OBJ *obj = LIST_DATA(o, i);

		FreeSecObject(obj);
	}

	ReleaseList(o);
}

// Release the secure object
void FreeSecObject(SEC_OBJ *obj)
{
	// Validate arguments
	if (obj == NULL)
	{
		return;
	}

	Free(obj->Name);
	Free(obj);
}

// Clone the secure object enumeration results
LIST *CloneEnumSecObject(LIST *o)
{
	LIST *ret;
	UINT i;
	// Validate arguments
	if (o == NULL)
	{
		return NULL;
	}

	ret = NewListFast(NULL);
	for (i = 0;i < LIST_NUM(o);i++)
	{
		SEC_OBJ *obj = LIST_DATA(o, i);

		Add(ret, CloneSecObject(obj));
	}

	return ret;
}

// Enumeration of the secure objects
LIST *EnumSecObject(SECURE *sec)
{
	CK_BBOOL b_true = true, b_false = false;
	UINT objects[MAX_OBJ];
	UINT i;
	UINT ret;
	LIST *o;
	CK_ATTRIBUTE dummy[1];
	CK_ATTRIBUTE a[] =
	{
		{CKA_TOKEN,		&b_true,		sizeof(b_true)},
	};
	UINT num_objects = MAX_OBJ;
	// Validate arguments
	if (sec == NULL)
	{
		return NULL;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return NULL;
	}

	Zero(dummy, sizeof(dummy));

	// If there is a cache, return it
	if (sec->EnumCache != NULL)
	{
		return CloneEnumSecObject(sec->EnumCache);
	}

	// Enumerate
//	if (sec->Dev->Id != 2 && sec->Dev->Id != 14)
//	{
		// Normal tokens
		ret = sec->Api->C_FindObjectsInit(sec->SessionId, a, sizeof(a) / sizeof(a[0]));
//	}
//	else
//	{
		// ePass and SafeSign
//		ret = sec->Api->C_FindObjectsInit(sec->SessionId, dummy, 0);
//	}

	if (ret != CKR_OK)
	{
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		return NULL;
	}
	if (sec->Api->C_FindObjects(sec->SessionId, objects, sizeof(objects) / sizeof(objects[0]), &num_objects) != CKR_OK)
	{
		sec->Api->C_FindObjectsFinal(sec->SessionId);
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		return NULL;
	}
	sec->Api->C_FindObjectsFinal(sec->SessionId);

	o = NewListFast(NULL);

	for (i = 0;i < num_objects;i++)
	{
		char label[MAX_SIZE];
		UINT obj_class = 0;
		bool priv = false;
		CK_ATTRIBUTE get[] =
		{
			{CKA_LABEL, label, sizeof(label) - 1},
			{CKA_CLASS, &obj_class, sizeof(obj_class)},
			{CKA_PRIVATE, &priv, sizeof(priv)},
		};

		Zero(label, sizeof(label));

		if (sec->Api->C_GetAttributeValue(sec->SessionId, objects[i],
			get, sizeof(get) / sizeof(get[0])) == CKR_OK)
		{
			UINT type = INFINITE;

			switch (obj_class)
			{
			case CKO_DATA:
				// Data
				type = SEC_DATA;
				break;

			case CKO_CERTIFICATE:
				// Certificate
				type = SEC_X;
				break;

			case CKO_PUBLIC_KEY:
				// Public key
				type = SEC_P;
				break;

			case CKO_PRIVATE_KEY:
				// Secret key
				type = SEC_K;
				break;
			}

			if (type != INFINITE)
			{
				SEC_OBJ *obj = ZeroMalloc(sizeof(SEC_OBJ));

				obj->Type = type;
				obj->Object = objects[i];
				obj->Private = (priv == false) ? false : true;
				EnSafeStr(label, '?');
				TruncateCharFromStr(label, '?');
				obj->Name = CopyStr(label);

				Add(o, obj);
			}
		}
	}

	// Creating a cache
	sec->EnumCache = CloneEnumSecObject(o);

	return o;
}

// Write the data
bool WriteSecData(SECURE *sec, bool private_obj, char *name, void *data, UINT size)
{
	UINT object_class = CKO_DATA;
	CK_BBOOL b_true = true, b_false = false, b_private_obj = private_obj;
	UINT object;
	CK_ATTRIBUTE a[] =
	{
		{CKA_TOKEN,		&b_true,		sizeof(b_true)},
		{CKA_CLASS,		&object_class,	sizeof(object_class)},
		{CKA_PRIVATE,	&b_private_obj,	sizeof(b_private_obj)},
		{CKA_LABEL,		name,			StrLen(name)},
		{CKA_VALUE,		data,			size},
	};
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;
	}
	if (private_obj && sec->LoginFlag == false)
	{
		sec->Error = SEC_ERROR_NOT_LOGIN;
		return false;
	}
	if (name == NULL || data == NULL || size == 0)
	{
		sec->Error = SEC_ERROR_BAD_PARAMETER;
		return false;
	}
	if (size > MAX_SEC_DATA_SIZE)
	{
		sec->Error = SEC_ERROR_DATA_TOO_BIG;
		return false;
	}

	// Delete any objects with the same name
	if (CheckSecObject(sec, name, SEC_DATA))
	{
		DeleteSecData(sec, name);
	}

	// Object creation
	if (sec->Api->C_CreateObject(sec->SessionId, a, sizeof(a) / sizeof(a[0]), &object) != CKR_OK)
	{
		sec->Error = SEC_ERROR_HARDWARE_ERROR;
		return false;
	}

	// Clear the cache
	EraseEnumSecObjectCache(sec);

	return true;
}

// Add the information of the newly created object to the cache
void AddSecObjToEnumCache(SECURE *sec, char *name, UINT type, bool private_obj, UINT object)
{
	SEC_OBJ *obj;
	// Validate arguments
	if (sec == NULL || name == NULL || sec->EnumCache == NULL)
	{
		return;
	}

	obj = ZeroMalloc(sizeof(SEC_OBJ));
	obj->Name = CopyStr(name);
	obj->Object = object;
	obj->Private = private_obj;
	obj->Type = type;

	Add(sec->EnumCache, obj);
}

// Display the token information
void PrintSecInfo(SECURE *sec)
{
	SEC_INFO *s;
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}

	s = sec->Info;
	if (s == NULL)
	{
		Print("No Token Info.\n");
		return;
	}

	Print(
		"               Label: %S\n"
		"      ManufacturerId: %S\n"
		"               Model: %S\n"
		"        SerialNumber: %S\n"
		"          MaxSession: %u\n"
		"        MaxRWSession: %u\n"
		"           MinPinLen: %u\n"
		"           MaxPinLen: %u\n"
		"   TotalPublicMemory: %u\n"
		"    FreePublicMemory: %u\n"
		"  TotalPrivateMemory: %u\n"
		"   FreePrivateMemory: %u\n"
		"     HardwareVersion: %s\n"
		"     FirmwareVersion: %s\n",
		s->Label, s->ManufacturerId, s->Model, s->SerialNumber,
		s->MaxSession, s->MaxRWSession, s->MinPinLen, s->MaxPinLen,
		s->TotalPublicMemory, s->FreePublicMemory, s->TotalPrivateMemory,
		s->FreePrivateMemory, s->HardwareVersion, s->FirmwareVersion
		);
}

// Get the token information
void GetSecInfo(SECURE *sec)
{
	CK_TOKEN_INFO token_info;
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}
	if (sec->Info != NULL)
	{
		return;
	}

	// Acquisition
	Zero(&token_info, sizeof(token_info));
	if (sec->Api->C_GetTokenInfo(sec->SlotIdList[sec->SessionSlotNumber], &token_info) != CKR_OK)
	{
		// Failure
		return;
	}

	sec->Info = TokenInfoToSecInfo(&token_info);
}

// Release the token information
void FreeSecInfo(SECURE *sec)
{
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}
	if (sec->Info == NULL)
	{
		return;
	}

	FreeSecInfoMemory(sec->Info);
	sec->Info = NULL;
}

// Convert the token information to the SEC_INFO
SEC_INFO *TokenInfoToSecInfo(void *p_t)
{
	SEC_INFO *s;
	char buf[MAX_SIZE];
	CK_TOKEN_INFO *t = (CK_TOKEN_INFO *)p_t;
	// Validate arguments
	if (t == NULL)
	{
		return NULL;
	}

	s = ZeroMalloc(sizeof(SEC_INFO));

	// Label
	Zero(buf, sizeof(buf));
	Copy(buf, t->label, sizeof(t->label));
	s->Label = ZeroMalloc(CalcUtf8ToUni(buf, 0));
	Utf8ToUni(s->Label, 0, buf, 0);

	// ManufacturerId
	Zero(buf, sizeof(buf));
	Copy(buf, t->manufacturerID, sizeof(t->manufacturerID));
	s->ManufacturerId = ZeroMalloc(CalcUtf8ToUni(buf, 0));
	Utf8ToUni(s->ManufacturerId, 0, buf, 0);

	// Model
	Zero(buf, sizeof(buf));
	Copy(buf, t->model, sizeof(t->model));
	s->Model = ZeroMalloc(CalcUtf8ToUni(buf, 0));
	Utf8ToUni(s->Model, 0, buf, 0);

	// SerialNumber
	Zero(buf, sizeof(buf));
	Copy(buf, t->serialNumber, sizeof(t->serialNumber));
	s->SerialNumber = ZeroMalloc(CalcUtf8ToUni(buf, 0));
	Utf8ToUni(s->SerialNumber, 0, buf, 0);

	// Numeric value
	s->MaxSession = t->ulMaxSessionCount;
	s->MaxRWSession = t->ulMaxRwSessionCount;
	s->MinPinLen = t->ulMinPinLen;
	s->MaxPinLen = t->ulMaxPinLen;
	s->TotalPublicMemory = t->ulTotalPublicMemory;
	s->FreePublicMemory = t->ulFreePublicMemory;
	s->TotalPrivateMemory = t->ulTotalPrivateMemory;
	s->FreePrivateMemory = t->ulFreePrivateMemory;

	// Hardware version
	Format(buf, sizeof(buf), "%u.%02u", t->hardwareVersion.major, t->hardwareVersion.minor);
	s->HardwareVersion = CopyStr(buf);

	// Firmware version
	Format(buf, sizeof(buf), "%u.%02u", t->firmwareVersion.major, t->firmwareVersion.minor);
	s->FirmwareVersion = CopyStr(buf);

	return s;
}

// Release the memory of the SEC_INFO
void FreeSecInfoMemory(SEC_INFO *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	Free(s->Label);
	Free(s->ManufacturerId);
	Free(s->Model);
	Free(s->SerialNumber);
	Free(s->HardwareVersion);
	Free(s->FirmwareVersion);
	Free(s);
}

// Log-out
void LogoutSec(SECURE *sec)
{
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}
	if (sec->LoginFlag == false)
	{
		return;
	}

	// Log-out
	sec->Api->C_Logout(sec->SessionId);

	// Clear Cache
	EraseEnumSecObjectCache(sec);

	sec->LoginFlag = false;
}

// Log-in
bool LoginSec(SECURE *sec, char *pin)
{
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (sec->SessionCreated == false)
	{
		sec->Error = SEC_ERROR_NO_SESSION;
		return false;

	}
	if (sec->LoginFlag)
	{
		sec->Error = SEC_ERROR_ALREADY_LOGIN;
		return false;
	}
	if (pin == NULL)
	{
		sec->Error = SEC_ERROR_NO_PIN_STR;
		return false;
	}

	// Log-in
	if (sec->Api->C_Login(sec->SessionId, CKU_USER, pin, StrLen(pin)) != CKR_OK)
	{
		// Login failure
		sec->Error = SEC_ERROR_BAD_PIN_CODE;
		return false;
	}

	// Clear the cache
	EraseEnumSecObjectCache(sec);

	sec->LoginFlag = true;

	return true;
}

// Close the session
void CloseSecSession(SECURE *sec)
{
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}
	if (sec->SessionCreated == false)
	{
		return;
	}

	// Close the session
	sec->Api->C_CloseSession(sec->SessionId);

	sec->SessionCreated = false;
	sec->SessionId = 0;
	sec->SessionSlotNumber = 0;

	FreeSecInfo(sec);

	// Clear the cache
	EraseEnumSecObjectCache(sec);
}

// Open the session
bool OpenSecSession(SECURE *sec, UINT slot_number)
{
	UINT err = 0;
	UINT session;
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}
	if (sec->SessionCreated)
	{
		// Already been created
		sec->Error = SEC_ERROR_SESSION_EXISTS;
		return false;
	}
	if (slot_number >= sec->NumSlot)
	{
		// Slot number is invalid
		sec->Error = SEC_ERROR_INVALID_SLOT_NUMBER;
		return false;
	}

	// Create a session
	if ((err = sec->Api->C_OpenSession(sec->SlotIdList[slot_number],
		CKF_RW_SESSION | CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK)
	{
		// Failed to initialize session in read / write mode
		// Read-only mode?
		if ((err = sec->Api->C_OpenSession(sec->SlotIdList[slot_number],
			CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK)
		{
			// Failure to create
			sec->Error = SEC_ERROR_OPEN_SESSION;
			return false;
		}
		else
		{
			sec->IsReadOnly = true;
		}
	}

	sec->SessionCreated = true;
	sec->SessionId = session;
	sec->SessionSlotNumber = slot_number;

	// Get the token information
	GetSecInfo(sec);

	return true;
}

// Close the secure device
void CloseSec(SECURE *sec)
{
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}

	// Log out
	LogoutSec(sec);

	// Close the session
	CloseSecSession(sec);

	// Release the token information
	FreeSecInfo(sec);

	// Release of the slot list memory
	if (sec->SlotIdList != NULL)
	{
		Free(sec->SlotIdList);
		sec->SlotIdList = NULL;
	}

	// Unload the module
	FreeSecModule(sec);

	// Memory release
	DeleteLock(sec->lock);
	Free(sec);
}

// Open a secure device
SECURE *OpenSec(UINT id)
{
	SECURE_DEVICE *dev = GetSecureDevice(id);
	SECURE *sec;
	UINT err;

	if (dev == NULL)
	{
		return NULL;
	}

	sec = ZeroMalloc(sizeof(SECURE));

	sec->lock = NewLock();
	sec->Error = SEC_ERROR_NOERROR;
	sec->Dev = dev;

	// Get whether it's a ePass or not
	if (SearchStrEx(dev->DeviceName, "epass", 0, false) != INFINITE)
	{
		sec->IsEPass1000 = true;
	}

	// Load the module
	if (LoadSecModule(sec) == false)
	{
		CloseSec(sec);
		return NULL;
	}

	// Get the slot list
	sec->NumSlot = 0;
	if ((err = sec->Api->C_GetSlotList(true, NULL, &sec->NumSlot)) != CKR_OK || sec->NumSlot == 0)
	{
		// Failure
		FreeSecModule(sec);
		CloseSec(sec);
		return NULL;
	}

	sec->SlotIdList = (UINT *)ZeroMalloc(sizeof(UINT *) * sec->NumSlot);

	if (sec->Api->C_GetSlotList(TRUE, sec->SlotIdList, &sec->NumSlot) != CKR_OK)
	{
		// Failure
		Free(sec->SlotIdList);
		sec->SlotIdList = NULL;
		FreeSecModule(sec);
		CloseSec(sec);
		return NULL;
	}

	return sec;
}

// Load the module of the secure device
bool LoadSecModule(SECURE *sec)
{
	bool ret = false;
	// Validate arguments
	if (sec == NULL)
	{
		return false;
	}

#ifdef	OS_WIN32
	ret = Win32LoadSecModule(sec);
#endif	// OS_WIN32

	// Initialization
	if (sec->Api->C_Initialize(NULL) != CKR_OK)
	{
		// Initialization Failed
		FreeSecModule(sec);
		return false;
	}

	sec->Initialized = true;

	return ret;
}

// Unload the module of the secure device
void FreeSecModule(SECURE *sec)
{
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}

	if (sec->Initialized)
	{
		// Release because it is initialized
		sec->Api->C_Finalize(NULL);
		sec->Initialized = false;
	}

#ifdef	OS_WIN32
	Win32FreeSecModule(sec);
#endif	// OS_WIN32

}


// Get a secure device
SECURE_DEVICE *GetSecureDevice(UINT id)
{
	UINT i;

	if (id == 0)
	{
		return NULL;
	}

	for (i = 0;i < LIST_NUM(SecureDeviceList);i++)
	{
		SECURE_DEVICE *dev = LIST_DATA(SecureDeviceList, i);

		if (dev->Id == id)
		{
			return dev;
		}
	}

	return NULL;
}

// Confirm the ID of the secure device
bool CheckSecureDeviceId(UINT id)
{
	UINT i;

	for (i = 0;i < LIST_NUM(SecureDeviceList);i++)
	{
		SECURE_DEVICE *dev = LIST_DATA(SecureDeviceList, i);

		if (dev->Id == id)
		{
			return true;
		}
	}

	return false;
}

// Get a list of supported devices
LIST *GetSecureDeviceList()
{
	return GetSupportedDeviceList();
}

// Get a list of supported devices
LIST *GetSupportedDeviceList()
{
	// Increase the reference count
	AddRef(SecureDeviceList->ref);

	return SecureDeviceList;
}

// Examine whether the specified device is installed and available
bool IsDeviceSupported(SECURE_DEVICE *dev)
{
	bool b = false;
#ifdef	OS_WIN32
	b = Win32IsDeviceSupported(dev);
#endif	// OS_WIN32
	return b;
}

// Initialization of the secure device list
void InitSecureDeviceList()
{
	UINT i, num_supported_list;
	SecureDeviceList = NewList(NULL);

	num_supported_list = sizeof(SupportedList) / sizeof(SECURE_DEVICE);
	for (i = 0; i < num_supported_list;i++)
	{
		SECURE_DEVICE *dev = &SupportedList[i];

		// Support Checking
		if (IsDeviceSupported(dev))
		{
			// Add the device to the list because it is supported
			Add(SecureDeviceList, dev);
		}
	}
}

// Test main procedure
void TestSecMain(SECURE *sec)
{
	char *test_str = CEDAR_PRODUCT_STR " VPN";
	K *public_key, *private_key;
	// Validate arguments
	if (sec == NULL)
	{
		return;
	}

	Print("test_str: \"%s\"\n", test_str);

	Print("Writing Data...\n");
	if (WriteSecData(sec, true, "test_str", test_str, StrLen(test_str)) == false)
	{
		Print("WriteSecData() Failed.\n");
	}
	else
	{
		char data[MAX_SIZE];
		Zero(data, sizeof(data));
		Print("Reading Data...\n");
		if (ReadSecData(sec, "test_str", data, sizeof(data)) == false)
		{
			Print("ReadSecData() Failed.\n");
		}
		else
		{
			Print("test_str: \"%s\"\n", data);
		}
		Print("Deleting Data...\n");
		DeleteSecData(sec, "test_str");
	}

	Print("Generating Key...\n");
	if (RsaGen(&private_key, &public_key, 1024) == false)
	{
		Print("RsaGen() Failed.\n");
	}
	else
	{
		X *cert;
		NAME *name;
		X_SERIAL *serial;
		UINT num = 0x11220000;

		Print("Creating Cert...\n");
		serial = NewXSerial(&num, sizeof(UINT));
		name = NewName(L"Test", L"Test", L"Test", L"JP", L"Test", L"Test");
		cert = NewRootX(public_key, private_key, name, 365, NULL);
		FreeXSerial(serial);
		if (cert == NULL)
		{
			Print("NewRootX() Failed.\n");
		}
		else
		{
			Print("Writing Cert...\n");
			DeleteSecData(sec, "test_cer");
			if (WriteSecCert(sec, true, "test_cer", cert) == false)
			{
				Print("WriteSecCert() Failed.\n");
			}
			else
			{
				X *x;
				Print("Reading Cert...\n");
				x = ReadSecCert(sec, "test_cer");
				if (x == NULL)
				{
					Print("ReadSecCert() Failed.\n");
				}
				else
				{
					Print("Checking two Certs... ");
					if (CompareX(x, cert) == false)
					{
						Print("[FAILED]\n");
					}
					else
					{
						Print("Ok.\n");
					}
					FreeX(x);
				}
				if (cert != NULL)
				{
					X *x;
					XToFile(cert, "cert_tmp.cer", true);
					x = FileToX("cert_tmp.cer");
					if (CompareX(x, cert) == false)
					{
						Print("[FAILED]\n");
					}
					else
					{
						Print("Ok.\n");
						Print("Writing Private Key...\n");
						DeleteSecKey(sec, "test_key");
						if (WriteSecKey(sec, true, "test_key", private_key) == false)
						{
							Print("WriteSecKey() Failed.\n");
						}
						else
						{
							UCHAR sign_cpu[128];
							UCHAR sign_sec[128];
							K *pub = GetKFromX(cert);
							Print("Ok.\n");
							Print("Signing Data by CPU...\n");
							if (RsaSign(sign_cpu, test_str, StrLen(test_str), private_key) == false)
							{
								Print("RsaSign() Failed.\n");
							}
							else
							{
								Print("Ok.\n");
								Print("sign_cpu: ");
								PrintBin(sign_cpu, sizeof(sign_cpu));
								Print("Signing Data by %s..\n", sec->Dev->DeviceName);
								if (SignSec(sec, "test_key", sign_sec, test_str, StrLen(test_str)) == false)
								{
									Print("SignSec() Failed.\n");
								}
								else
								{
									Print("Ok.\n");
									Print("sign_sec: ");
									PrintBin(sign_sec, sizeof(sign_sec));
									Print("Compare...");
									if (Cmp(sign_sec, sign_cpu, sizeof(sign_cpu)) == 0)
									{
										Print("Ok.\n");
										Print("Verify...");
										if (RsaVerify(test_str, StrLen(test_str),
											sign_sec, pub) == false)
										{
											Print("[FAILED]\n");
										}
										else
										{
											Print("Ok.\n");
										}
									}
									else
									{
										Print("[DIFFIRENT]\n");
									}
								}
							}
							Print("Deleting test_key...\n");
//							DeleteSecKey(sec, "test_key");
							FreeK(pub);
						}
					}
					FreeX(x);
				}
			}
			Print("Deleting Cert..\n");
//			DeleteSecCert(sec, "test_cer");
			FreeX(cert);
		}
		FreeName(name);
		FreeK(private_key);
		FreeK(public_key);
	}
}

// Test the security device
void TestSec()
{
	UINT i;
	LIST *secure_device_list;
	Print("Secure Device Test Program\n"
		"Copyright (c) SoftEther Corporation. All Rights Reserved.\n\n");

	// Get the secure device list
	secure_device_list = GetSecureDeviceList();
	if (secure_device_list != NULL)
	{
		UINT use_device_id;
		char tmp[MAX_SIZE];
		Print("--- Secure Device List ---\n");
		for (i = 0;i < LIST_NUM(secure_device_list);i++)
		{
			SECURE_DEVICE *dev = LIST_DATA(secure_device_list, i);
			Print("%2u - %s\n", dev->Id, dev->DeviceName);
		}
		Print("\n");
		Print("Device ID >");
		GetLine(tmp, sizeof(tmp));
		use_device_id = ToInt(tmp);
		if (use_device_id == 0)
		{
			Print("Canceled.\n");
		}
		else
		{
			SECURE *sec = OpenSec(use_device_id);
			Print("Opening Device...\n");
			if (sec == NULL)
			{
				Print("OpenSec() Failed.\n");
			}
			else
			{
				Print("Opening Session...\n");
				if (OpenSecSession(sec, 0) == false)
				{
					Print("OpenSecSession() Failed.\n");
				}
				else
				{
					while (true)
					{
						char pin[MAX_SIZE];
						Print("PIN Code >");
						GetLine(pin, sizeof(pin));
						Trim(pin);
						if (StrLen(pin) == 0)
						{
							Print("Canceled.\n");
							break;
						}
						else
						{
							Print("Login...\n");
							if (LoginSec(sec, pin))
							{
								TestSecMain(sec);
								Print("Logout...\n");
								LogoutSec(sec);
								break;
							}
							else
							{
								Print("Login Failed. Please Try Again.\n");
							}
						}
					}
					Print("Closing Session...\n");
					CloseSecSession(sec);
				}
				Print("Closing Device...\n");
				CloseSec(sec);
			}
		}
		ReleaseList(secure_device_list);
	}
	else
	{
		Print("GetSecureDeviceList() Error.\n");
	}
}

// Release of the secure device list
void FreeSecureDeviceList()
{
	ReleaseList(SecureDeviceList);
}

// Initialization of the security token module
void InitSecure()
{
	// Initialization of the secure device list
	InitSecureDeviceList();
}

// Release of the security token module
void FreeSecure()
{
	// Release of the secure device list
	FreeSecureDeviceList();
}



// Developed by SoftEther VPN Project at University of Tsukuba in Japan.
// Department of Computer Science has dozens of overly-enthusiastic geeks.
// Join us: http://www.tsukuba.ac.jp/english/admission/
