#include "test_common.h"

CK_RV test_crypto();
CK_RV test_gost3410_key_pair_gen(CK_SESSION_HANDLE sess);

int main(int argc, char *argv[])
{
    CK_RV rc = CKR_OK;

    rc = test_main(argc, argv);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "test_main failed: 0x%lx\n", rc);
        return rc;
    }
    rc = test_crypto();
    if (rc != CKR_OK)
    {
        fprintf(stderr, "test_crypto failed: 0x%lx\n", rc);
        return rc;
    }
    return CKR_OK;
}

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

    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 = 0x%lx\n", rc);
        goto out;
    }
    fprintf(stderr, "C_OpenSession success\n");

    rc = funcs->C_Login(hSession,
                        CKU_USER, user_pin, (CK_ULONG)strlen((char *)user_pin));
    if (rc != CKR_OK)
    {
        fprintf(stderr,
                "ERROR call to C_Login failed, rc = 0x%lx\n", rc);
        goto out_close;
    }
    fprintf(stderr, "C_Login success\n");
    rc = funcs->C_GetMechanismInfo(SlotId,
                                   CKM_GOSTR3410_KEY_PAIR_GEN, &minfo);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
                "\n======== Mechanism CKM_GOSTR3410_KEY_PAIR_GEN "
                "not supported ===========\n");
    }
    else
    {
        fprintf(stderr,
                "\n==================== CKM_GOSTR3410_KEY_PAIR_GEN "
                "test ==================\n");
        rc = test_gost3410_key_pair_gen(hSession);
        if (rc != CKR_OK)
        {
            fprintf(stderr,
                    "ERROR CKM_GOSTR3410_KEY_PAIR_GEN failed, rc = 0x%lx\n",
                    rc);
        }
        else
        {
            fprintf(stderr, "CKM_GOSTR3410_KEY_PAIR_GEN test passed.\n");
        }
    }


 out_close:
    if ((ret = funcs->C_CloseSession(hSession)) != CKR_OK)
    {
        fprintf(stderr,
                "Error: C_CloseSession failed with 0x%lx\n", ret);
        rc = ret;
    }
    else
    {
        fprintf(stderr, "C_CloseSession success\n");
    }

 out:
    return rc;
}

CK_RV test_gost3410_key_pair_gen(CK_SESSION_HANDLE sess)
{
    CK_RV            rc = CKR_OK;
    CK_BYTE          value[1024];
    CK_ULONG         len;
    CK_OBJECT_HANDLE pub_key  = CK_INVALID_HANDLE;
    CK_OBJECT_HANDLE priv_key = CK_INVALID_HANDLE;
    CK_ATTRIBUTE     attr;
    CK_MECHANISM     m;
    CK_MECHANISM     mechanism_desc = { CKM_GOSTR3410_KEY_PAIR_GEN, NULL, 0 };
    CK_MECHANISM_PTR mechanism      = &mechanism_desc;

    static CK_BBOOL  ltrue               = CK_TRUE;
    static CK_BBOOL  lfalse              = CK_FALSE;
    static CK_BYTE   gostR3410params_A[] = {
        0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01
    };
    static CK_BYTE   gostR3411params[] = {
        0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e, 0x01
    };

    static CK_BYTE   data_hash[] = {
        0xF4, 0x21, 0x15, 0x4E, 0x51, 0x08, 0x09, 0xC9,
        0x41, 0xD8, 0xF3, 0xAF, 0xA1, 0x2E, 0x51, 0xDB,
        0x22, 0x44, 0x5D, 0x2E, 0xC9, 0x91, 0x91, 0x2E,
        0x6E, 0xBA, 0x91, 0xE8, 0x7C, 0xFE, 0xF5, 0xD1
    };
    // В шаблонах можно использовать минимально
    // необходимый набор атрибутов, а все остальные устанавливаются
    // либо из контекста, либо по умолчанию.
    // В прикладных программах рекомендуется явно устанавливать
    // требуемые значения атрибутов и не надеяться на умолчания,
    // которые могут измениться в другой версии библиотеки.
    // Вообще говоря, библиотека позволяет даже сгенерировать
    // ключевую пару с закрытым ключом на токене и
    // с сессионным открытым ключом.
    // Аппаратные токены гораздо строже подходят
    // к необходимому набору атрибутов при генерации ключевой пары,
    // причем у каждого токена - своя специфика.
    static CK_UTF8CHAR            *label_pub    = (CK_UTF8CHAR *)"Public Key";
	CK_ULONG label_pub_len = (CK_ULONG)strlen((char *)label_pub)+1;
    static CK_OBJECT_CLASS cko_priv       = CKO_PRIVATE_KEY;
    static CK_OBJECT_CLASS cko_publ       = CKO_PUBLIC_KEY;
    CK_ATTRIBUTE           pub_template[] = {
        { CKA_CLASS,           &cko_publ, sizeof(cko_publ)  },
        { CKA_PRIVATE,         &lfalse,   sizeof(CK_BBOOL)  },
        { CKA_TOKEN,           &ltrue,    sizeof(CK_BBOOL)  },
        { CKA_GOSTR3410PARAMS,
          gostR3410params_A, sizeof(gostR3410params_A) },
        { CKA_GOSTR3411PARAMS,
          gostR3411params, sizeof(gostR3411params) },
        { CKA_LABEL,           NULL, 0 },
    };
    static CK_UTF8CHAR            *label_priv    = (CK_UTF8CHAR *)"Private Key";
	CK_ULONG label_priv_len = (CK_ULONG)strlen((char *)label_priv)+1;
    CK_ATTRIBUTE           priv_template[] = {
        { CKA_CLASS,   &cko_priv,  sizeof(cko_priv)   },
        { CKA_PRIVATE, &ltrue,     sizeof(CK_BBOOL)   },
        { CKA_TOKEN,   &ltrue,     sizeof(CK_BBOOL)   },
        { CKA_LABEL,   NULL, 0 },
    };
    SYSTEMTIME             t1, t2;
    CK_ULONG               diff;

    GetSystemTime(&t1);

	pub_template[5].pValue = label_pub;
	pub_template[5].ulValueLen = label_pub_len;
	priv_template[3].pValue = label_priv;
	priv_template[3].ulValueLen = label_priv_len;

    rc = funcs->C_GenerateKeyPair(sess, mechanism,
                                  pub_template,
                                  sizeof(pub_template) / sizeof(CK_ATTRIBUTE),
                                  priv_template,
                                  sizeof(priv_template) / sizeof(CK_ATTRIBUTE),
                                  &pub_key, &priv_key);
    GetSystemTime(&t2);
    diff = process_time(t1, t2);
    fprintf(stderr, "C_GenerateKeyPair time:  %ld msec\n", diff);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_GenerateKeyPair failed, rc = 0x%lx\n", rc);
        goto end;
    }
    attr.type       = CKA_VALUE;
    attr.pValue     = value;
    attr.ulValueLen = sizeof(value);
    rc              = funcs->C_GetAttributeValue(sess, pub_key, &attr, 1);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
                "C_GetAttributeValue failed, rc = 0x%lx\n", rc);
        goto end;
    }

    // Проверяем результаты генерации: выдаем значение открытого ключа,
    // подписываем хеш и проверяем подпись.

    printf("%%%%%% PUBLIC KEY:\n");
    print_bytes(value, attr.ulValueLen);

    m.mechanism      = CKM_GOSTR3410;
    m.pParameter     = NULL_PTR;
    m.ulParameterLen = 0;
    GetSystemTime(&t1);
    rc = funcs->C_SignInit(sess, &m, priv_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_SignInit failed, rc = 0x%lx\n", rc);
        goto end;
    }

    len = sizeof(value);
    rc  = funcs->C_Sign(sess,
                        data_hash, sizeof(data_hash), value, &len);
    GetSystemTime(&t2);
    diff = process_time(t1, t2);
    fprintf(stderr, "C_SignInit + C_Sign time:  %ld msec\n", diff);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_Sign failed, rc = 0x%lx\n", rc);
        goto end;
    }

    printf("%%%%%% SIGNATURE:\n");
    print_bytes(value, len);

    GetSystemTime(&t1);
    rc = funcs->C_VerifyInit(sess, &m, pub_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_VerifyInit failed, rc = 0x%lx\n", rc);
        goto end;
    }

    rc = funcs->C_Verify(sess,
                         data_hash, sizeof(data_hash), value, len);
    GetSystemTime(&t2);
    diff = process_time(t1, t2);
    fprintf(stderr, "C_VerifyInit + C_Verify time:  %ld msec\n", diff);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_Verify failed, rc = 0x%lx\n", rc);
        goto end;
    }

 end:

    if (pub_key)
    {
        funcs->C_DestroyObject(sess, pub_key);
    }
    if (priv_key)
    {
        funcs->C_DestroyObject(sess, priv_key);
    }

    return rc;
}
