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

void usage(void);
CK_RV test_crypto();
int test_gost28147_cbc(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;
	}
//	for (i=0; i<40; i++) {
		// test the crypto functions 
		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_GOST28147, &minfo);
	if (rc != CKR_OK) {
		fprintf(stderr, "\n======== Mechanism CKM_GOST28147 not supported ===========\n");
	} else {
		fprintf(stderr, "\n==================== CKM_GOST28147 CBC test ==================\n");
		rc = test_gost28147_cbc(hSession);
		if (rc != CKR_OK) {
			fprintf(stderr, "ERROR CKM_GOST28147 CBC failed, rc = %p\n", (void *)rc);
		} else {
			fprintf(stderr, "CKM_GOST28147 CBC 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;
}

// В данном тесте проверяется не только собственно шифрование
// в режиме CBC, но и организация этого шифрования с помощью
// объекта параметров домена.
int test_gost28147_cbc(CK_SESSION_HANDLE sess)
{
    int rc = 0;
	CK_BYTE value[256];
	CK_ULONG len;
	CK_OBJECT_HANDLE keyh = CK_INVALID_HANDLE;
   	CK_MECHANISM mechanism_desc = {CKM_GOST28147, NULL, 0};
   	CK_MECHANISM_PTR mechanism = &mechanism_desc;

	static CK_BBOOL ltrue = CK_TRUE;
	static CK_BBOOL lfalse = CK_FALSE;
	static CK_OBJECT_CLASS oclass = CKO_SECRET_KEY;
	static CK_OBJECT_CLASS dp_class = CKO_DOMAIN_PARAMETERS;
	static CK_KEY_TYPE key_type = CKK_GOST28147;

	static CK_BYTE key[] = {
        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
        0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
        0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
	};
	static CK_BYTE iv[] = {
        0x90, 0x4B, 0x9C, 0x7A, 0xBB, 0x85, 0x0E, 0x87,
	};
	// Данные для шифрования в блочных режимах должны 
	// поступать на вход порциями, длина которых 
	// кратна размеру блока, т.е. 8-ми байтам.
	// Паддинг в LCC не работает, поэтому его организация
	// должна производиться на прикладном уровне.
    static CK_BYTE data[] = {
        0x61, 0x62, 0x63, 0x64, 0x62, 0x63, 0x64, 0x65,
        0x63, 0x64, 0x65, 0x66, 0x64, 0x65, 0x66, 0x67,
        0x65, 0x66, 0x67, 0x68, 0x66, 0x67, 0x68, 0x69,
        0x67, 0x68, 0x69, 0x6a, 0x68, 0x69, 0x6a, 0x6b,
        0x69, 0x6a, 0x6b, 0x6c, 0x6a, 0x6b, 0x6c, 0x6d,
        0x6b, 0x6c, 0x6d, 0x6e, 0x6c, 0x6d, 0x6e, 0x6f,
        0x6d, 0x6e, 0x6f, 0x70, 0x6e, 0x6f, 0x70, 0x71,
        0x0a, 
		// PKCS#5 padding bytes
              0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    };

	static CK_BYTE et1[] = { 
        0xC2, 0xE2, 0x9F, 0x15, 0x98, 0x47, 0x87, 0x97,   
        0x91, 0x85, 0x29, 0x8D, 0x28, 0xBB, 0x37, 0x54,
        0xBC, 0x8E, 0x77, 0xC3, 0x82, 0x72, 0x4D, 0x60,   
        0x0B, 0x09, 0x0C, 0xEC, 0x2A, 0x26, 0x95, 0xE5,
        0xBF, 0x13, 0x01, 0xFA, 0x09, 0x51, 0xBE, 0x6F,   
        0x81, 0x73, 0xAB, 0xF3, 0xCB, 0x11, 0x20, 0xB8,
        0x61, 0x5F, 0xA8, 0x5B, 0xF0, 0x75, 0xB3, 0x98,   
        0x4B, 
		// encrypted PKCS#5 padding bytes
		      0x62, 0x89, 0x18, 0xBC, 0x34, 0xDE, 0xC8, 
	};
	static CK_ATTRIBUTE key_template[] = {
		{ CKA_CLASS, &oclass, sizeof(oclass) },
		{ CKA_KEY_TYPE, &key_type, sizeof(key_type) },
		{ CKA_GOST28147PARAMS, NULL, 0 },
		{ CKA_VALUE, key, sizeof(key) },
		{ CKA_ENCRYPT, &ltrue, sizeof(CK_BBOOL) },
		{ CKA_DECRYPT, &ltrue, sizeof(CK_BBOOL) },
	};

	const CK_OBJECT_CLASS oc_dp = CKO_DOMAIN_PARAMETERS;

	CK_ATTRIBUTE t_find_dp[] = {
		{CKA_CLASS,    (CK_VOID_PTR)&oc_dp, sizeof(oc_dp)},
		{CKA_KEY_TYPE, &key_type, sizeof(key_type)},
		{CKA_OBJECT_ID, NULL, 0},
	};	
	
	// Фактически это набор параметров A из RFC 4357,
	// в котором режим шифрования изменен на CBC.
	CK_BYTE dp_cbc[] =
	{
		0x30, 0x53,
			0x04, 0x40,
				// +4 (таблица замен)
				0x93, 0xee, 0xb3, 0x1b, 0x67, 0x47, 0x5a, 0xda,
				0x3e, 0x6a, 0x1d, 0x2f, 0x29, 0x2c, 0x9c, 0x95,
				0x88, 0xbd, 0x81, 0x70, 0xba, 0x31, 0xd2, 0xac,
				0x1f, 0xd3, 0xf0, 0x6e, 0x70, 0x89, 0x0b, 0x08,
				0xa5, 0xc0, 0xe7, 0x86, 0x42, 0xf2, 0x45, 0xc2,
				0xe6, 0x5b, 0x29, 0x43, 0xfc, 0xa4, 0x34, 0x59,
				0xcb, 0x0f, 0xc8, 0xf1, 0x04, 0x78, 0x7f, 0x37,
				0xdd, 0x15, 0xae, 0xbd, 0x51, 0x96, 0x66, 0xe4,	
			0x02, 0x01,
				// +70 (mode: 0-CNT, 1-CFB, 2-CBC)
				0x02,
			0x02, 0x01, 0x40,
			0x30, 0x09,
				0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x0e,
					// +84 (key meshing: 0-Null, 1-CryptoPro)
					0x01,
	};
	CK_ULONG uldp_cbc = sizeof(dp_cbc);
	// OID и название не являются предопределенными в LCC
	CK_BYTE oid_cbc[] = {
		0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x41
	};
	CK_CHAR *dp_label = 
		"id-Gost28147-89-CryptoPro-A-ParamSet-CBC";

	CK_ATTRIBUTE t_dp_cbc[] = {
		{CKA_CLASS, &dp_class, sizeof(dp_class)},
		{CKA_TOKEN, &lfalse, sizeof(lfalse)},
		{CKA_KEY_TYPE, &key_type, sizeof(key_type)},
		{CKA_OBJECT_ID, oid_cbc, sizeof(oid_cbc)},
		{CKA_VALUE, dp_cbc, uldp_cbc},
		{CKA_LABEL, dp_label, strlen(dp_label)+1},
	};
	CK_OBJECT_HANDLE hDP_CBC = CK_INVALID_HANDLE;

	rc = funcs->C_CreateObject(sess, t_dp_cbc, 
		sizeof(t_dp_cbc)/sizeof(CK_ATTRIBUTE),
		&hDP_CBC);
	if (rc != CKR_OK) {
		fprintf(stderr, "C_CreateObject failed: 0x%x\n", rc);
		goto end;
	}

	// Если бы объект параметров домена не был создан, 
	// то следующая операция создания ключа для такого OID 
	// завершилась бы с ошибкой.
	key_template[2].pValue = oid_cbc;
	key_template[2].ulValueLen = sizeof(oid_cbc);
	rc = funcs->C_CreateObject(sess, 
		key_template, sizeof(key_template)/sizeof(CK_ATTRIBUTE), 
		&keyh);
	if (rc != CKR_OK) {
		fprintf(stderr, "C_CreateObject failed: 0x%x\n", rc);
		goto end;
	}
	fprintf(stderr, "CBC domain parameters object created\n"); 

	mechanism->pParameter = iv;
	mechanism->ulParameterLen = sizeof(iv);
	rc = funcs->C_EncryptInit(sess, mechanism, keyh);
	if (rc != CKR_OK) {
		fprintf(stderr, "C_EncryptInit failed: 0x%x\n", rc);
		goto end;
	}

	fprintf(stderr, "Plain text to encrypt:\n");
	print_hex(data, sizeof(data));
	// Данные для шифрования в блочных режимах должны 
	// поступать на вход порциями, длина которых 
	// кратна размеру блока, т.е. 8-ми.
	len = sizeof(value);
	rc = funcs->C_Encrypt(sess, data, sizeof(data), value, &len);
	if (rc != CKR_OK) {
		fprintf(stderr, "C_Encrypt failed: 0x%x\n", rc);
		goto end;
	}
	if (len != sizeof(et1)) {
		fprintf(stderr, "Invalid ciphertext length: %d\n", len);
		rc = -1;
		goto end;
	}
	if (memcmp(value, et1, len) != 0) {
		fprintf(stderr, "Invalid ciphertext\n");
		rc = -2;
		goto end;
	}
	fprintf(stderr, "CBC encryption result OK\n");
	print_hex(value, len);
	rc = funcs->C_DecryptInit(sess, mechanism, keyh);
	if (rc != CKR_OK) {
		fprintf(stderr, "C_DecryptInit failed: 0x%x\n", rc);
		goto end;
	}

	rc = funcs->C_Decrypt(sess, value, len, value, &len);
	if (rc != CKR_OK) {
		fprintf(stderr, "C_Decrypt failed: 0x%x\n", rc);
		goto end;
	}
	fprintf(stderr, "Decrypted text:\n");
	print_hex(value, len);

	if (len != sizeof(data)) {
		fprintf(stderr, "Invalid decrypted text length: %d\n", len);
		rc = -3;
		goto end;
	}
	if (memcmp(value, data, len) != 0) {
		fprintf(stderr, "Invalid decrypted text\n");
		rc = -4;
		goto end;
	}
	fprintf(stderr, "CBC decryption result OK\n");

end:
	if (keyh != CK_INVALID_HANDLE) {
		funcs->C_DestroyObject(sess, keyh);
	}
	if (hDP_CBC != CK_INVALID_HANDLE) {
		funcs->C_DestroyObject(sess, hDP_CBC);
	}

	return rc;
}




