#include "test_common.h"
#ifdef WIN32
#include "getopt.h"
#else 
extern char *optarg;
#endif

void usage(void);
CK_RV test_crypto();
int test_pkcs5_pbkd2_key_gen(CK_SESSION_HANDLE sess);

CK_SLOT_ID SlotId;

CK_SLOT_ID_PTR SlotList = NULL;
CK_ULONG SlotCount = 0;

int main(int argc, char *argv[]) {
   CK_RV rc = 1;
   CK_FLAGS flags = 0;
   unsigned long slot_num = 0;  
   int i;

   // parse the command line parameters
	for (i=1; i<argc; i++) {
		if (strcmp("-api", argv[i]) == 0) {
			i++;
			api_path = argv[i];
		} else if (strcmp("-user_pin", argv[i]) == 0) {
			i++;
			user_pin = argv[i];
		}
	}

   // load the PKCS11 library 
	rc = init(api_path, &funcs, NULL);
   if (rc != CKR_OK) {
      fprintf(stderr, "ERROR calling init, rc = %p.\n", (void *)rc);
      return rc;
   }
	rc = get_slot_list(funcs, 
                    &SlotList, 
                    &SlotCount);
	if (rc != CKR_OK) {
		printf("get_slot_list failed, rc = 0x%x\n", rc );
		return rc;
	}
	rc = find_slot_with_token(funcs, 
                           SlotList, 
                           SlotCount, 
                           &SlotId);
	if (rc != CKR_OK) {
		printf("find_slot_with_token failed, rc = 0x%x\n", rc );
		return rc;
	}
		// test the crypto functions 
//	for (i=0; i<30; i++) {
		rc = test_crypto();
		if (rc != CKR_OK) {
			fprintf(stderr, "ERROR call to lissi failed.\n");
			return rc;
		}
//	}
   return CKR_OK;
}


void usage(void)
{
   fprintf(stderr, "Usage: lissi [-c <slotId>]\n"
      "Default slotID is 0\n"
      "To get a list of slotIDs, call pkcs11_cfg -s\n");
   exit(-1);
}

CK_RV test_crypto() 
{
   CK_RV rc, ret;
   CK_SESSION_HANDLE hSession;
	CK_MECHANISM_INFO minfo;

   // open a R/W cryptoki session, CKR_SERIAL_SESSION is a legacy bit we have to set
   rc = funcs->C_OpenSession(SlotId, CKF_RW_SESSION | CKF_SERIAL_SESSION, NULL_PTR,
         NULL_PTR, &hSession);
   if (rc != CKR_OK) {
      fprintf(stderr, "ERROR call to C_OpenSession failed, rc = %p\n", (void *)rc);
      goto out;
   }
  fprintf(stderr, "C_OpenSession success\n");
/*
   // log in as normal user 
   rc = funcs->C_Login(hSession, CKU_USER, user_pin, strlen(user_pin));
   if (rc != CKR_OK) {
      fprintf(stderr, "ERROR call to C_Login failed, rc = %p\n", (void *)rc);
      goto out_close;
   }
  fprintf(stderr, "C_Login success\n");
*/
	rc = funcs->C_GetMechanismInfo(SlotId, CKM_PKCS5_PBKD2, &minfo);
	if (rc != CKR_OK) {
		fprintf(stderr, "\n======== Mechanism CKM_PKCS5_PBKD2 not supported ===========\n");
	} else {
		fprintf(stderr, "\n==================== CKM_PKCS5_PBKD2 test ==================\n");
		rc = test_pkcs5_pbkd2_key_gen(hSession);
		if (rc != CKR_OK) {
			fprintf(stderr, "ERROR CKM_PKCS5_PBKD2 failed, rc = %p\n", (void *)rc);
		} else {
			fprintf(stderr, "CKM_PKCS5_PBKD2 test passed.\n");
		}
	}
  
//out_close:
   if( (ret = funcs->C_CloseSession(hSession)) != CKR_OK ) {
      fprintf(stderr, "Error: C_CloseSession failed with %p\n", (void *)ret);
	  rc = ret;
   }
   else {
      fprintf(stderr, "C_CloseSession success\n");
   }

out:
   return rc;
}

int test_pkcs5_pbkd2_key_gen(CK_SESSION_HANDLE sess)
{
	CK_RV rc = CKR_OK;
	CK_BYTE value[256];
	CK_ULONG len;
   	CK_MECHANISM mechanism_desc = {CKM_PKCS5_PBKD2, NULL, 0};
   	CK_MECHANISM_PTR mechanism = &mechanism_desc;

	CK_OBJECT_HANDLE key = CK_INVALID_HANDLE;
	static CK_BBOOL ltrue = CK_TRUE;
	static CK_BBOOL lfalse = CK_FALSE;
	static CK_BYTE gost28147_A[] = { 
		0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x01 
	};
   CK_OBJECT_CLASS lclass = CKO_SECRET_KEY;
   CK_KEY_TYPE keyType = CKK_GOST28147;
   CK_CHAR label[] = "A GOST28147 secret key object";
   CK_PKCS5_PBKD2_PARAMS params;
	// CryptoPro gostR3411 A Param Set
	static CK_BYTE oid_default[] = { 
		0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e, 0x01 
	};
	CK_ATTRIBUTE attr;

	static CK_BYTE salt1[] = { 0x12, 0x34, 0x56, 0x78, 0x78, 0x56, 0x34 };
	static CK_ULONG iter1 = 5;
	static CK_UTF8CHAR_PTR password1 = "password+";
	static CK_ULONG password1_len = 9;
	static CK_BYTE et11[] = { 
		0x35,0x0e,0x07,0xc5,0xb7,0xfc,0xe8,0xe2,
		0xa0,0xa8,0xdf,0xc3,0x92,0xc0,0x49,0x96,
		0x4c,0xb1,0x66,0x31,0x9b,0x58,0x6e,0x9f,
		0x46,0x22,0x86,0x76,0x5d,0x63,0xdb,0xf3,
	};
	static CK_BYTE salt2[] = { 
		0xbf, 0xb2, 0xd3, 0x23, 0x3e, 0xe6, 0x21, 0xd8 
	};
	static CK_ULONG iter2 = 2048;
	static CK_UTF8CHAR_PTR password2 = "1111";
	static CK_ULONG password2_len = 4;
	static CK_BYTE et12[] = { 
		0x76,0xf1,0xf3,0x45,0x6b,0x43,0x48,0x99,
		0xe0,0xfc,0xda,0xf8,0x49,0xc2,0x68,0x46,
		0x7b,0xd5,0x34,0x5b,0x25,0x09,0x74,0x4e,
		0x0b,0x32,0x7b,0xb9,0x1e,0x8f,0xfa,0x1a,
	};
	static CK_BYTE salt3[] = { 
		0x8C, 0x5B, 0x3E, 0xE5, 0xCD, 0x27, 0xFA, 0x28 
	};
	static CK_ULONG iter3 = 2048;
	static CK_UTF8CHAR_PTR password3 = "4444";
	static CK_ULONG password3_len = 4;
	static CK_BYTE et13[] = { 
		0xDB, 0xD1, 0x28, 0x79, 0xF6, 0xFC, 0x2C, 0x0C, 
        0x8D, 0xC8, 0x06, 0x18, 0x15, 0xDB, 0xF9, 0x8B,
        0x58, 0xA8, 0x9C, 0x3F, 0x55, 0x96, 0x13, 0xDC,
        0xDB, 0x18, 0xD9, 0x0A, 0x84, 0xF2, 0x53, 0x8E,
	};
	CK_ATTRIBUTE key_template[] = {
        { CKA_CLASS, &lclass, sizeof(lclass)},
        { CKA_KEY_TYPE, &keyType, sizeof(keyType) },
        { CKA_TOKEN, &lfalse, sizeof(lfalse) },
        { CKA_LABEL, label, sizeof(label) },
        { CKA_ENCRYPT, &ltrue, sizeof(ltrue) },
		{ CKA_GOST28147_PARAMS, gost28147_A, sizeof(gost28147_A) },
		{ CKA_SENSITIVE, &lfalse, sizeof(lfalse) },
		{ CKA_EXTRACTABLE, &ltrue, sizeof(ltrue) }
	};
/*
typedef struct CK_PKCS5_PBKD2_PARAMS {
        CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE           saltSource;
        CK_VOID_PTR                                pSaltSourceData;
        CK_ULONG                                   ulSaltSourceDataLen;
        CK_ULONG                                   iterations;
        CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE prf;
        CK_VOID_PTR                                pPrfData;
        CK_ULONG                                   ulPrfDataLen;
        CK_UTF8CHAR_PTR                            pPassword;
        CK_ULONG_PTR                               ulPasswordLen;
} CK_PKCS5_PBKD2_PARAMS;
*/

	params.saltSource = CKZ_SALT_SPECIFIED;
	params.pSaltSourceData = salt1;
	params.ulSaltSourceDataLen = sizeof(salt1);
	params.iterations = iter1;
	params.prf = CKP_PKCS5_PBKD2_HMAC_GOSTR3411;
    params.pPrfData = oid_default;
    params.ulPrfDataLen = sizeof(oid_default);
	params.pPassword = password1;
	params.ulPasswordLen = &password1_len;

	mechanism->pParameter = &params;
	mechanism->ulParameterLen = sizeof(params);
	rc = funcs->C_GenerateKey(sess, mechanism, 
		key_template, sizeof(key_template)/sizeof(CK_ATTRIBUTE), &key);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_GenerateKey failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}

	attr.type = CKA_VALUE; 
	attr.pValue = value; 
	attr.ulValueLen = sizeof(value);
	rc = funcs->C_GetAttributeValue(sess, key, &attr, 1);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_GetAttributeValue failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}
	len = attr.ulValueLen;

	rc = funcs->C_DestroyObject(sess, key);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_DestroyObject failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}

	if (len != sizeof(et11)) {
		fprintf(stderr, 
			"%4d: Invalid result length: %d\n", __LINE__, len);
		return -1;
	}
	if (memcmp(value, et11, len) != 0) {
		fprintf(stderr, "%4d: Invalid result value\n", __LINE__);
		return -2;
	}

	params.saltSource = CKZ_SALT_SPECIFIED;
	params.pSaltSourceData = salt2;
	params.ulSaltSourceDataLen = sizeof(salt2);
	params.iterations = iter2;
	params.prf = CKP_PKCS5_PBKD2_HMAC_GOSTR3411;
    params.pPrfData = oid_default;
    params.ulPrfDataLen = sizeof(oid_default);
	params.pPassword = password2;
	params.ulPasswordLen = &password2_len;

	mechanism->pParameter = &params;
	mechanism->ulParameterLen = sizeof(params);
	rc = funcs->C_GenerateKey(sess, mechanism, 
		key_template, sizeof(key_template)/sizeof(CK_ATTRIBUTE), &key);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_GenerateKey failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}

	attr.type = CKA_VALUE; 
	attr.pValue = value; 
	attr.ulValueLen = sizeof(value);
	rc = funcs->C_GetAttributeValue(sess, key, &attr, 1);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_GetAttributeValue failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}
	len = attr.ulValueLen;

	rc = funcs->C_DestroyObject(sess, key);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_DestroyObject failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}

	if (len != sizeof(et12)) {
		fprintf(stderr, 
			"%4d: Invalid result length: %d\n", __LINE__, len);
		return -1;
	}
	if (memcmp(value, et12, len) != 0) {
		fprintf(stderr, "%4d: Invalid result value\n", __LINE__);
		return -2;
	}

	params.saltSource = CKZ_SALT_SPECIFIED;
	params.pSaltSourceData = salt3;
	params.ulSaltSourceDataLen = sizeof(salt3);
	params.iterations = iter3;
	params.prf = CKP_PKCS5_PBKD2_HMAC_GOSTR3411;
    params.pPrfData = oid_default;
    params.ulPrfDataLen = sizeof(oid_default);
	params.pPassword = password3;
	params.ulPasswordLen = &password3_len;

	mechanism->pParameter = &params;
	mechanism->ulParameterLen = sizeof(params);
	rc = funcs->C_GenerateKey(sess, mechanism, 
		key_template, sizeof(key_template)/sizeof(CK_ATTRIBUTE), &key);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_GenerateKey failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}

	attr.type = CKA_VALUE; 
	attr.pValue = value; 
	attr.ulValueLen = sizeof(value);
	rc = funcs->C_GetAttributeValue(sess, key, &attr, 1);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_GetAttributeValue failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}
	len = attr.ulValueLen;

	rc = funcs->C_DestroyObject(sess, key);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"%4d: C_DestroyObject failed, rc = 0x%x\n", __LINE__, rc);
		return rc;
	}

	if (len != sizeof(et13)) {
		fprintf(stderr, 
			"%4d: Invalid result length: %d\n", __LINE__, len);
		return -1;
	}
	if (memcmp(value, et13, len) != 0) {
		fprintf(stderr, "%4d: Invalid result value\n", __LINE__);
		return -2;
	}

	printf("SUCCESS\n");
	rc = CKR_OK;

	return rc;
}



