#include "test_common.h"

CK_RV test_crypto();
CK_RV test_pba_gost3411_with_gost3411_hmac(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");
/*
        // 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 = 0x%lx\n", rc);
                goto out_close;
        }
        fprintf(stderr, "C_Login success\n");
 */
    rc = funcs->C_GetMechanismInfo(SlotId,
                                   CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC, &minfo);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
                "\n======== Mechanism CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC "
                "not supported ===========\n");
    }
    else
    {
        fprintf(stderr,
                "\n==================== CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC "
                "test ==================\n");
        rc = test_pba_gost3411_with_gost3411_hmac(hSession);
        if (rc != CKR_OK)
        {
            fprintf(stderr,
                    "ERROR CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC failed, "
                    "rc = 0x%lx\n",
                    rc);
        }
        else
        {
            fprintf(stderr,
                    "CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC test passed.\n");
        }
    }

    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;
}

// Заметим, что механизм CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC
// устарел, потому что ключ аутентификации для контейнера PKCS#12
// генерируется теперь на основе PKCS#5.
// Следовательно, можно обойтись механизмом CKM_PKCS5_PBKD2 и для
// генерации ключа шифрования, и для генерации ключа аутентификации.
// Старый механизм сохранен для совместимости со старыми контейнерами.
CK_RV test_pba_gost3411_with_gost3411_hmac(CK_SESSION_HANDLE sess)
{
    CK_RV rc = CKR_OK;
    CK_BYTE                 value[256];
    CK_ULONG                len;
    CK_MECHANISM            mechanism_desc =
    { CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC, 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;
    CK_GOSTR3411_PBE_PARAMS params;
    CK_ATTRIBUTE            attr;
    CK_KEY_TYPE             key_type = 0;
    // CryptoPro gostR3411 A Param Set
    static CK_BYTE          oid_default[] = {
        0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e, 0x01,
    };
    // Real PKCS#12 params and ethalon
    static CK_BYTE          salt4[] = {
        0xDF, 0x7C, 0x5C, 0x10, 0xA4, 0xD5, 0x22, 0x62,
    };
    static CK_ULONG         iter4 = 2048;
    // Use UTF-8 password here
    // Converting from UTF-8 to UTF-16 in soft token
    static CK_UTF8CHAR  password4[]   = { 0x34, 0x34, 0x34, 0x34 };
    static CK_ULONG     password4_len = 4;
    static CK_BYTE      et14[]        = {
        0x93, 0x88, 0x91, 0x11, 0x20, 0x43, 0xC4, 0xD1,
        0xCA, 0x23, 0x82, 0xEF, 0x86, 0x4A, 0x67, 0x31,
        0xBD, 0x29, 0xEF, 0x82, 0x94, 0xDD, 0x23, 0x50,
        0x63, 0x0B, 0xCB, 0x1E, 0x5A, 0xC9, 0x06, 0xC3,
    };
    static CK_ATTRIBUTE key_template[] = {
        { CKA_SENSITIVE,   &lfalse, sizeof(lfalse) },
        { CKA_EXTRACTABLE, &ltrue,  sizeof(ltrue)  }
    };

    params.pOID               = oid_default;
    params.ulOIDLen           = sizeof(oid_default);
    params.pSalt              = salt4;
    params.ulSaltLen          = sizeof(salt4);
    params.ulIteration        = iter4;
    params.pPassword          = password4;
    params.ulPasswordLen      = password4_len;
    mechanism->pParameter     = &params;
    mechanism->ulParameterLen = sizeof(params);
    // Механизм CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC вырабатывает
    // на пароле так называемый ключ аутентификации,
    // значение которого используется для проверки целостности
    // транспортного контейнера PKCS#12.
    rc = funcs->C_GenerateKey(sess, mechanism,
                              key_template,
                              sizeof(key_template) / sizeof(CK_ATTRIBUTE), &key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_GenerateKey failed: 0x%lx\n", rc);
        goto end;
    }
    attr.type       = CKA_KEY_TYPE;
    attr.pValue     = &key_type;
    attr.ulValueLen = sizeof(key_type);
    rc              = funcs->C_GetAttributeValue(sess, key, &attr, 1);
    len             = attr.ulValueLen;
    if (key_type != CKK_GENERIC_SECRET)
    {
        fprintf(stderr,
                "Key type 0x%lx != CKK_GENERIC_SECRET\n", key_type);
        rc = CKR_FUNCTION_FAILED;
        goto end;
    }
    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, "C_GetAttributeValue failed: 0x%lx\n", rc);
        goto end;
    }
    len = attr.ulValueLen;

    printf("Generated PBA key value:\n");
    print_bytes(value, len);

    CHECK(value, len, et14);

    printf("SUCCESS\n");
    rc = CKR_OK;
 end:
    if (key != CK_INVALID_HANDLE)
    {
        funcs->C_DestroyObject(sess, key);
    }
    return rc;
}
