#include "test_common.h"

CK_RV test_crypto();
CK_RV test_gost28147_cbc(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_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 = 0x%lx\n", rc);
        }
        else
        {
            fprintf(stderr, "CKM_GOST28147 CBC 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;
}

// В данном тесте проверяется не только собственно шифрование
// в режиме CBC, но и организация этого шифрования с помощью
// объекта параметров домена.
CK_RV test_gost28147_cbc(CK_SESSION_HANDLE sess)
{
    CK_RV                  rc = CKR_OK;
    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) },
    };

/*
    static 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.
    static 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,
    };
    // OID и название не являются предопределенными в LCC
    static CK_BYTE  oid_cbc[] = {
        0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x41
    };
    CK_UTF8CHAR  *dp_label =
        (CK_UTF8CHAR *)"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,    sizeof(dp_cbc)       },
        { CKA_LABEL,     NULL,		0					 },
    };
    CK_OBJECT_HANDLE hDP_CBC = CK_INVALID_HANDLE;

	t_dp_cbc[5].pValue = dp_label;
	t_dp_cbc[5].ulValueLen = (CK_ULONG)strlen((char *)dp_label) + 1;
    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%lx\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%lx\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%lx\n", rc);
        goto end;
    }

    fprintf(stderr, "Plain text to encrypt:\n");
    print_bytes(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%lx\n", rc);
        goto end;
    }
    if (len != sizeof(et1))
    {
        fprintf(stderr, "Invalid ciphertext length: %ld\n", len);
        rc = CKR_FUNCTION_FAILED;
        goto end;
    }
    if (memcmp(value, et1, len) != 0)
    {
        fprintf(stderr, "Invalid ciphertext\n");
        rc = CKR_FUNCTION_FAILED;
        goto end;
    }
    fprintf(stderr, "CBC encryption result OK\n");
    print_bytes(value, len);
    rc = funcs->C_DecryptInit(sess, mechanism, keyh);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_DecryptInit failed: 0x%lx\n", rc);
        goto end;
    }

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

    if (len != sizeof(data))
    {
        fprintf(stderr, "Invalid decrypted text length: %ld\n", len);
        rc = CKR_FUNCTION_FAILED;
        goto end;
    }
    if (memcmp(value, data, len) != 0)
    {
        fprintf(stderr, "Invalid decrypted text\n");
        rc = CKR_FUNCTION_FAILED;
        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;
}
