#include "test_common.h"

int main(int argc, char* argv[])
{
    CK_RV                rvResult;
#ifdef WIN32
    HMODULE              hPkcsLib  = NULL;
    HMODULE              hPkcsLib2 = NULL;
#else
    void                 *hPkcsLib  = NULL;
    void                 *hPkcsLib2 = NULL;
#endif
    CK_C_GetFunctionList pcGetFunctionList  = 0;
    CK_C_GetFunctionList pcGetFunctionList2 = 0;
    CK_FUNCTION_LIST_PTR Pkcs11FuncList     = NULL;
    CK_FUNCTION_LIST_PTR Pkcs11FuncList2    = NULL;
    CK_SLOT_ID_PTR       pSlotList          = NULL;
    CK_SLOT_ID_PTR       pSlotList2         = NULL;
    CK_SLOT_ID           SlotId = 0;
    CK_SLOT_ID           SlotId2 = 0;
    CK_ULONG             ulSlotCount;
    CK_ULONG             ulSlotCount2;
//    CK_ULONG             j;
    CK_SLOT_INFO         SlotInfo;
    CK_SLOT_INFO         SlotInfo2;
    CK_SESSION_HANDLE    hSessionSend, hSessionRecp;

//    static CK_UTF8CHAR_PTR      pcUserPIN   = (CK_UTF8CHAR_PTR) "01234567";
//    static CK_ULONG             ulPinLength = 8;       // PIN length

    static CK_BYTE              *pbPlainText =
        (CK_BYTE *)"This is plaintext for ecryption and decryption";
    static CK_ULONG             ulPlainTextSize = sizeof(pbPlainText);

    CK_BYTE_PTR          pbCipherText = NULL;
    CK_ULONG             ulCipherSize = 0;

    CK_BYTE_PTR          pbDecryptedText = NULL;
    CK_ULONG             ulDecryptedSize = 0;

    /**********************************************************/
    static CK_BBOOL blTrue              = CK_TRUE,
             blFalse             = CK_FALSE;
    static CK_ULONG ulKeyType_Gost28147 = CKK_GOST28147,
             ulClass_SecKey      = CKO_SECRET_KEY;
    // PAR ECC A OID
    static CK_BYTE      gostR3410params[] = {
        0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01
    };
    // PAR HASH 1 OID
    static CK_BYTE      gostR3411params[] = {
        0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e, 0x01
    };
    // PAR CIPHER A OID
    static CK_BYTE      gost28147params_A[] = {
        0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x01
    };
/*
    // PAR CIPHER B OID
    static CK_BYTE      gost28147params[] = {
        0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x02
    };
*/
    // template for GOST R 34.10-2001 public key
    CK_ATTRIBUTE caGOST_PublicKeyTemplate[] =
    {
        { CKA_TOKEN,            &blFalse,          sizeof(CK_BBOOL)          },
        { CKA_PRIVATE,          &blFalse,          sizeof(CK_BBOOL)          },
        { CKA_GOSTR3410_PARAMS, gostR3410params,   sizeof(gostR3410params)   },
        { CKA_GOSTR3411_PARAMS, gostR3411params,   sizeof(gostR3411params)   },
        { CKA_GOST28147_PARAMS, gost28147params_A, sizeof(gost28147params_A) },
    };
    // template for GOST R 34.10-2001 private key
    CK_ATTRIBUTE caGOST_PrivateKeyTemplate[] =
    {
        { CKA_TOKEN,   &blFalse, sizeof(CK_BBOOL) },
        { CKA_PRIVATE, &blFalse, sizeof(CK_BBOOL) },
        { CKA_DERIVE,  &blTrue,  sizeof(CK_BBOOL) },
    };
    // template for secret GOST key
    CK_ATTRIBUTE caGOST_SecretKeyTemplate[] =
    {
        { CKA_CLASS,            &ulClass_SecKey,      sizeof(CK_ULONG)          },
        { CKA_KEY_TYPE,         &ulKeyType_Gost28147, sizeof(CK_ULONG)          },
        { CKA_PRIVATE,          &blFalse,             sizeof(CK_BBOOL)          },
        { CKA_ENCRYPT,          &blTrue,              sizeof(CK_BBOOL)          },
        { CKA_DECRYPT,          &blTrue,              sizeof(CK_BBOOL)          },
        { CKA_WRAP,             &blTrue,              sizeof(CK_BBOOL)          },
        { CKA_EXTRACTABLE,      &blTrue,              sizeof(CK_BBOOL)          },
        { CKA_GOST28147_PARAMS, gost28147params_A,    sizeof(gost28147params_A) },
    };
    // template for derive key
    CK_ATTRIBUTE caDeriveKey[] =
    {
        { CKA_CLASS,            &ulClass_SecKey,      sizeof(CK_ULONG)          },
        { CKA_KEY_TYPE,         &ulKeyType_Gost28147, sizeof(CK_ULONG)          },
        { CKA_ENCRYPT,          &blTrue,              sizeof(CK_BBOOL)          },
        { CKA_DECRYPT,          &blTrue,              sizeof(CK_BBOOL)          },
        { CKA_WRAP,             &blTrue,              sizeof(CK_BBOOL)          },
        { CKA_UNWRAP,           &blTrue,              sizeof(CK_BBOOL)          },
        { CKA_GOST28147_PARAMS, gost28147params_A,    sizeof(gost28147params_A) },
    };
    CK_ULONG     ulPubKeyCount =
        sizeof(caGOST_PublicKeyTemplate) / sizeof(CK_ATTRIBUTE),
                 ulPriKeyCount =
        sizeof(caGOST_PrivateKeyTemplate) / sizeof(CK_ATTRIBUTE),
                 ulSecKeyCount =
        sizeof(caGOST_SecretKeyTemplate) / sizeof(CK_ATTRIBUTE),
                 ulDeriveKeyCount =
        sizeof(caDeriveKey) / sizeof(CK_ATTRIBUTE);
    CK_OBJECT_HANDLE hKeyForWU         = 0, // key which will be wrapped and unwrapped
                     hSendPubKey       = 0, // handle to public key of sender
                     hSendPriKey       = 0, // handle to private key of sender
                     hRecpPubKey       = 0, // handle to public key of recipient
                     hRecpPriKey       = 0, // handle to private key of recipient
                     hSendDH_Key       = 0, // Diffy-Hellman key of the sender
                     hRecpDH_Key       = 0, // Diffy-Hellman key of the recipient
                     hUnwrappedRecpKey = 0; // unwrapped key of the recipient

    CK_MECHANISM cmKeyGenMechanism,         // mechanism for key pair genaration
                 cmWrapMechanism,           // mechanism for key wrap/unwrap
                 cmDeriveMechanism,         // mechanism for key derivation
                 cmCryptMechanism;          // mechanism for encrypt/decrypt

    CK_BYTE_PTR pbWrappedKeySend;           // wrapped key

    CK_ULONG    ulWrappedKeyLen,            // length of wrapped key
                i;

    CK_BYTE_PTR 
                pbSend_PubKeyValue = NULL,
                pbRecp_PubKeyValue = NULL;

    CK_ATTRIBUTE
    // template for value of sender public key
        caSend_PubKeyValue = { CKA_VALUE, NULL, 0 },
    // template for value of recipient public key
        caRecp_PubKeyValue = { CKA_VALUE, NULL, 0 };
    // parameters for derivation mechanism
    CK_GOSTR3410_DERIVE_PARAMS_PTR DeriveParams;
    // UKM must be non-zero by RFC4357
    CK_BYTE                        UKM[8]   = {
		0x28, 0xaf, 0xc5, 0x50, 0x9d, 0x0c, 0x74, 0xb3 
	};
    CK_ULONG                       ulUKMLen = 8;
    // parameters for wrapping mechanism
    CK_BYTE                        pWrapIV[] = {
        0x56, 0xfa, 0x7d, 0x54, 0xb2, 0x90, 0x2c, 0x41
    };
    CK_BYTE                        iv[] = {
        0x37, 0x2a, 0x7f, 0x00, 0x2c, 0xea, 0x7d, 0x39
    };
    char							*api_path  = STRINGIFY(PKCS11_API_PATH);
//    CK_UTF8CHAR						*user_pin  = (CK_UTF8CHAR *)"01234567";
//    CK_ULONG						slot_num   = 0;
    char							*api_path2 = STRINGIFY(PKCS11_API_PATH);
//    CK_UTF8CHAR						*user_pin2 = (CK_UTF8CHAR *)"01234567";
//    CK_ULONG						slot_num2  = 0;

    printf("Starting CKM_GOST28147_KEY_WRAP test\n");

	caSend_PubKeyValue.pValue = pbSend_PubKeyValue;
	caRecp_PubKeyValue.pValue = pbRecp_PubKeyValue;

    for (i = 1; i < (CK_ULONG) argc; i++)
    {
        if (strcmp("-api", argv[i]) == 0)
        {
            ++i;
            api_path = argv[i];
        }
/*
        else if (strcmp("-slot", argv[i]) == 0)
        {
            ++i;
            slot_num = atoi(argv[i]);
        }
*/
        else if (strcmp("-user_pin", argv[i]) == 0)
        {
            ++i;
            user_pin = (CK_UTF8CHAR *)argv[i];
        }
    }

    api_path2 = api_path;
//    user_pin2 = user_pin;
//    slot_num2 = slot_num;

#ifdef WIN32
    hPkcsLib = LoadLibrary(api_path);
#else
#ifdef __APPLE__
    hPkcsLib = dlopen(api_path, RTLD_LOCAL|RTLD_LAZY);
#else
    hPkcsLib = dlopen(api_path, RTLD_LOCAL|RTLD_LAZY|RTLD_DEEPBIND);
#endif
#endif
    if (hPkcsLib == NULL)
    {
        printf(
            "Can't load PKCS#11 API library. "
            "Check API library path.\n");
#ifndef WIN32
        printf("dlerror: %s\n", dlerror());
#endif
        return -1;
    }
#ifdef WIN32
    pcGetFunctionList =
        (CK_C_GetFunctionList) GetProcAddress(
            hPkcsLib, "C_GetFunctionList");
#else
    pcGetFunctionList =
        (CK_C_GetFunctionList) dlsym(
            hPkcsLib, "C_GetFunctionList");
#endif

    // get PKCS #11 function list
    rvResult = pcGetFunctionList(&Pkcs11FuncList);
    printf("Load PKCS #11 function list result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    // initialize Cryptoki
    rvResult = Pkcs11FuncList->C_Initialize(NULL);
    printf("Initialize Cryptoki result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    // get slot list
    rvResult = Pkcs11FuncList->C_GetSlotList(
        CK_FALSE, NULL, &ulSlotCount);
    printf("Get slot list result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    if (ulSlotCount > 0)
    {       // allocate memory for slot list
        pSlotList = (CK_SLOT_ID_PTR) malloc(
            ulSlotCount * sizeof(CK_SLOT_ID));

        rvResult =
            Pkcs11FuncList->C_GetSlotList(CK_FALSE,
                                          pSlotList, &ulSlotCount);

        if (rvResult != CKR_OK)
            return rvResult;
        printf("Slot count: %ld\n", ulSlotCount);
    }
    else
        return -3;

    // get information about sender and recipient slots.
    for (i = 0; i < ulSlotCount; ++i)
    {
        rvResult = Pkcs11FuncList->C_GetSlotInfo(pSlotList[i], &SlotInfo);
        if (rvResult == CKR_OK)
        {          // if a token is present in this slot
            if ((SlotInfo.flags & CKF_TOKEN_PRESENT) == CKF_TOKEN_PRESENT)
            {
                SlotId = pSlotList[i];
                break;
            }
        }
    }
    if (i >= ulSlotCount)
    {
        printf("No slots with token present\n");
        return -3;
    }
    printf("Slot ID: sender %ld\n", SlotId);

    // load library PKCS #11
#ifdef WIN32
    hPkcsLib2 = LoadLibrary(api_path2);
#else
#ifdef __APPLE__
    hPkcsLib2 = dlopen(api_path2, RTLD_LOCAL|RTLD_LAZY);
#else
    hPkcsLib2 = dlopen(api_path2, RTLD_LOCAL|RTLD_LAZY|RTLD_DEEPBIND);
#endif
#endif
    if (hPkcsLib2 == NULL)
    {
        printf("Can't load PKCS#11 API library. "
               "Check API library path.\n");
#ifndef WIN32
        printf("dlerror: %s\n", dlerror());
#endif
        return FALSE;
    }
#ifdef WIN32
    pcGetFunctionList2 =
        (CK_C_GetFunctionList) GetProcAddress(
            hPkcsLib2, "C_GetFunctionList");
#else
    pcGetFunctionList2 =
        (CK_C_GetFunctionList) dlsym(
            hPkcsLib2, "C_GetFunctionList");
#endif

    // get PKCS #11 function list
    rvResult = pcGetFunctionList2(&Pkcs11FuncList2);
    printf("Load PKCS #11 function list result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    // initialize Cryptoki
    rvResult = Pkcs11FuncList2->C_Initialize(NULL);
    printf("Initialize Cryptoki result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK && rvResult
        != CKR_CRYPTOKI_ALREADY_INITIALIZED)
        return rvResult;

    // get slot list
    rvResult =
        Pkcs11FuncList2->C_GetSlotList(CK_TRUE, NULL, &ulSlotCount2);
    printf("Get slot list result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    if (ulSlotCount2 > 0)
    {       // allocate memory for slot list
        pSlotList2 = (CK_SLOT_ID_PTR) malloc(
            ulSlotCount2 * sizeof(CK_SLOT_ID));

        rvResult =
            Pkcs11FuncList2->C_GetSlotList(
                CK_TRUE, pSlotList2, &ulSlotCount2);

        if (rvResult != CKR_OK)
            return rvResult;
        printf("Slot count: %ld\n", ulSlotCount2);
    }
    else
        return -3;

    // get information about sender and recipient slots.
//    j = 0;
    for (i = 0; i < ulSlotCount2; ++i)
    {
        rvResult = Pkcs11FuncList2->C_GetSlotInfo(
            pSlotList2[i], &SlotInfo2);
        if (rvResult == CKR_OK)
        {          // if a token is present in this slot
            if ((SlotInfo2.flags & CKF_TOKEN_PRESENT)
                == CKF_TOKEN_PRESENT)
            {
                SlotId2 = pSlotList2[i];
                break;
            }
        }
    }
    if (i >= ulSlotCount2)
    {
        printf("No slots with token present\n");
        return -3;
    }
    printf("Slot ID: recipient %ld\n", SlotId2);

    // open session for slot with ID = SlotId[0]
    rvResult = Pkcs11FuncList->C_OpenSession(SlotId,
                                             (CKF_SERIAL_SESSION | CKF_RW_SESSION),
                                             NULL,
                                             0,
                                             &hSessionSend);
    printf("Sender open session result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;
    // open session for slot with ID = SlotId[1]
    rvResult = Pkcs11FuncList2->C_OpenSession(SlotId2,
                                              (CKF_SERIAL_SESSION | CKF_RW_SESSION),
                                              NULL,
                                              0,
                                              &hSessionRecp);
    printf("Recipient open session result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    cmKeyGenMechanism.mechanism      = CKM_GOST28147_KEY_GEN;
    cmKeyGenMechanism.pParameter     = NULL; //gost28147params_A;
    cmKeyGenMechanism.ulParameterLen = 0;    //sizeof(gost28147params_A);

    // generate secret key which will be wrapped and unwrapped
    printf(
        "Sender generate key for wrapping and unwrapping (hKeyForWU)\n");
    printf("   mechanism type: CKM_GOST28147_KEY_GEN\n");

    rvResult = Pkcs11FuncList->C_GenerateKey(hSessionSend,
                                             &cmKeyGenMechanism,
                                             caGOST_SecretKeyTemplate,
                                             ulSecKeyCount,
                                             &hKeyForWU);
    printf("   Sender generate key hKeyForWU "
           "for wrapping and unwrapping: result: 0x%lx\n",
           rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    printf("   key handle: %ld\n", (unsigned long) hKeyForWU);
    {
        CK_BYTE      value[32];
        CK_ATTRIBUTE attr_get[] = {
            { CKA_VALUE, NULL, 0 },
        };
        attr_get[0].pValue     = value;
        attr_get[0].ulValueLen = sizeof(value);
        rvResult               = Pkcs11FuncList->C_GetAttributeValue(
            hSessionSend, hKeyForWU,
            attr_get, sizeof(attr_get) / sizeof(CK_ATTRIBUTE));
        printf("   key to wrap:\n");
        print_bytes(value, attr_get[0].ulValueLen);
    }
    printf("   wrapping IV:\n");
    print_bytes(pWrapIV, sizeof(pWrapIV));

    // generate sender key pair with GOST R 34.10-2001
    printf("Generate key pair of sender\n");
    printf("   mechanism type: CKM_GOSTR3410_KEY_PAIR_GEN\n");

    cmKeyGenMechanism.mechanism      = CKM_GOSTR3410_KEY_PAIR_GEN;
    cmKeyGenMechanism.pParameter     = NULL;
    cmKeyGenMechanism.ulParameterLen = 0;
    rvResult                         = Pkcs11FuncList->C_GenerateKeyPair(hSessionSend,
                                                                         &cmKeyGenMechanism,
                                                                         caGOST_PublicKeyTemplate,
                                                                         ulPubKeyCount,
                                                                         caGOST_PrivateKeyTemplate,
                                                                         ulPriKeyCount,
                                                                         &hSendPubKey,
                                                                         &hSendPriKey);
    printf("   generate sender key pair: result: 0x%lx\n",
           rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    printf("   sender public key handle: %ld\n",
           (unsigned long) hSendPubKey);
    printf("   sender private key handle: %ld\n",
           (unsigned long) hSendPriKey);

    // generate recipient key pair with GOST R 34.10-2001
    printf("Generate key pair of recipient\n");
    printf("   mechanism type: CKM_GR3410_KEY_PAIR_GEN\n");

    rvResult = Pkcs11FuncList2->C_GenerateKeyPair(hSessionRecp,
                                                  &cmKeyGenMechanism,
                                                  caGOST_PublicKeyTemplate,
                                                  ulPubKeyCount,
                                                  caGOST_PrivateKeyTemplate,
                                                  ulPriKeyCount,
                                                  &hRecpPubKey,
                                                  &hRecpPriKey);
    printf("   generate recipient key pair: result: 0x%lx\n",
           rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    printf("   recipient public key handle: %ld\n",
           (unsigned long) hRecpPubKey);
    printf("   recipient private key handle: %ld\n",
           (unsigned long) hRecpPriKey);


    // get value of sender public key
    printf("Get value of sender public key\n");

    rvResult = Pkcs11FuncList->C_GetAttributeValue(hSessionSend,
                                                   hSendPubKey,
                                                   &caSend_PubKeyValue,
                                                   1);
    if (rvResult == CKR_OK)
    {
        caSend_PubKeyValue.pValue =
            (CK_BYTE_PTR) malloc(caSend_PubKeyValue.ulValueLen);
        rvResult = Pkcs11FuncList->C_GetAttributeValue(hSessionSend,
                                                       hSendPubKey,
                                                       &caSend_PubKeyValue,
                                                       1);
    }
    printf("   Get sender public key value: result: 0x%lx\n",
           rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    pbSend_PubKeyValue = (CK_BYTE_PTR) caSend_PubKeyValue.pValue;
    printf("   Sender public key value: \n");
    print_bytes(pbSend_PubKeyValue, caSend_PubKeyValue.ulValueLen);

    // get value of recipient public key
    printf("Get value of recipient public key\n");
    rvResult = Pkcs11FuncList2->C_GetAttributeValue(hSessionRecp,
                                                    hRecpPubKey,
                                                    &caRecp_PubKeyValue,
                                                    1);
    if (rvResult == CKR_OK)
    {
        caRecp_PubKeyValue.pValue =
            (CK_BYTE_PTR) malloc(caRecp_PubKeyValue.ulValueLen);
        rvResult = Pkcs11FuncList2->C_GetAttributeValue(hSessionRecp,
                                                        hRecpPubKey,
                                                        &caRecp_PubKeyValue,
                                                        1);
    }

    printf("   Get recipient public key value: result: 0x%lx\n",
           rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    printf("   Recipient public key value: \n");
    pbRecp_PubKeyValue = (CK_BYTE_PTR) caRecp_PubKeyValue.pValue;
    print_bytes(pbRecp_PubKeyValue, caRecp_PubKeyValue.ulValueLen);

    // fill parameters for derivation mechanism
    DeriveParams = (CK_GOSTR3410_DERIVE_PARAMS_PTR)
                   malloc(sizeof(CK_GOSTR3410_DERIVE_PARAMS));
    DeriveParams->kdf             = CKD_CPDIVERSIFY_KDF;
    DeriveParams->pPublicData     = (CK_BYTE_PTR) caRecp_PubKeyValue.pValue;
    DeriveParams->ulPublicDataLen = caRecp_PubKeyValue.ulValueLen;
    DeriveParams->pUKM            = UKM;
    DeriveParams->ulUKMLen        = ulUKMLen;

    cmDeriveMechanism.mechanism      = CKM_GOSTR3410_DERIVE;
    cmDeriveMechanism.pParameter     = DeriveParams;
    cmDeriveMechanism.ulParameterLen = sizeof(CK_GOSTR3410_DERIVE_PARAMS);

    printf("Derive Diffie-Hellman key\n");
    printf("   derivation mechanism: CKM_GOSTR3410_DERIVE\n");

    // derive Diffi-Hellman key of sender
    printf("   derive Diffie-Hellman key for sender\n");
    rvResult = Pkcs11FuncList->C_DeriveKey(hSessionSend,
                                           &cmDeriveMechanism,
                                           hSendPriKey,
                                           caDeriveKey,
                                           ulDeriveKeyCount,
                                           &hSendDH_Key);
    printf("   derive key result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    printf("   sender's Diffie-Hellman key handle: %ld\n",
           (unsigned long) hSendDH_Key);
    {
        CK_BYTE      value[32];
        CK_ATTRIBUTE attr_get[] = {
            { CKA_VALUE, NULL, 0 },
        };
        attr_get[0].pValue     = value;
        attr_get[0].ulValueLen = sizeof(value);
        rvResult               = Pkcs11FuncList->C_GetAttributeValue(hSessionSend,
                                                                     hSendDH_Key,
                                                                     attr_get, sizeof(attr_get) / sizeof(CK_ATTRIBUTE));
        printf("   sender KEK:\n");
        print_bytes(value, attr_get[0].ulValueLen);
    }

    // derive Diffi-Hellman key of recipient
    printf("   derive Diffie-Hellman key for recipient\n");
    DeriveParams->pPublicData     = (CK_BYTE_PTR) caSend_PubKeyValue.pValue;
    DeriveParams->ulPublicDataLen = caSend_PubKeyValue.ulValueLen;

    rvResult = Pkcs11FuncList2->C_DeriveKey(hSessionRecp,
                                            &cmDeriveMechanism,
                                            hRecpPriKey,
                                            caDeriveKey,
                                            ulDeriveKeyCount,
                                            &hRecpDH_Key);
    printf("   derive key result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    printf("   recipient's Diffie-Hellman key handle: %ld\n",
           (unsigned long) hRecpDH_Key);
    {
        CK_BYTE      value[32];
        CK_ATTRIBUTE attr_get[] = {
            { CKA_VALUE, NULL, 0 },
        };
        attr_get[0].pValue     = value;
        attr_get[0].ulValueLen = sizeof(value);
        rvResult               = Pkcs11FuncList2->C_GetAttributeValue(hSessionRecp, hRecpDH_Key,
                                                                      attr_get, sizeof(attr_get) / sizeof(CK_ATTRIBUTE));
        printf("   recipient KEK:\n");
        print_bytes(value, attr_get[0].ulValueLen);
    }

    // wrap key hKeyForWU with sender Diffi-Hellman key hSendDH_Key
    // Здесь используется UKM в качестве IV в соответствии с RFC 4357
    cmWrapMechanism.mechanism      = CKM_GOST28147_KEY_WRAP;
    cmWrapMechanism.pParameter     = NULL;	// Используется нулевой IV для MAC
    cmWrapMechanism.ulParameterLen = 0;

    rvResult = Pkcs11FuncList->C_WrapKey(hSessionSend,
                                         &cmWrapMechanism,
                                         hSendDH_Key,
                                         hKeyForWU,
                                         NULL,
                                         &ulWrappedKeyLen);
    if (rvResult == CKR_OK)
    {
		printf("Get wrapped key output buffer size = %ld\n", 
			ulWrappedKeyLen);
        pbWrappedKeySend = (CK_BYTE_PTR) malloc(ulWrappedKeyLen);
		printf("Wrap key with sender Diffi-Hellman key hSendDH_Key\n");
        rvResult         = Pkcs11FuncList->C_WrapKey(hSessionSend,
                                                     &cmWrapMechanism,
                                                     hSendDH_Key,
                                                     hKeyForWU,
                                                     pbWrappedKeySend,
                                                     &ulWrappedKeyLen);
    }

    printf("   wrap result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    printf("   wrapped key:\n");
    print_bytes(pbWrappedKeySend, ulWrappedKeyLen);

    // unwrap wrapped key with recipient Diffie-Hellman key
    printf("Unwrap key hUnwrappedRecpKey "
           "from pbWrappedKeySend with recipient Diffie-Hellman key\n");

    rvResult = Pkcs11FuncList2->C_UnwrapKey(hSessionRecp,
                                            &cmWrapMechanism,
                                            hRecpDH_Key,
                                            pbWrappedKeySend,
                                            ulWrappedKeyLen,
                                            caGOST_SecretKeyTemplate,
                                            ulSecKeyCount,
                                            &hUnwrappedRecpKey);
    printf("   unwrap key: result: 0x%lx\n", rvResult);
    if (rvResult != CKR_OK)
        return rvResult;

    printf("   key handle (hUnwrappedRecpKey): %ld\n",
           (unsigned long) hUnwrappedRecpKey);
    {
        CK_BYTE      value[32];
        CK_ATTRIBUTE attr_get[] = {
            { CKA_VALUE, NULL, 0 },
        };
        attr_get[0].pValue     = value;
        attr_get[0].ulValueLen = sizeof(value);
        rvResult               = Pkcs11FuncList2->C_GetAttributeValue(
            hSessionRecp, hUnwrappedRecpKey,
            attr_get, sizeof(attr_get) / sizeof(CK_ATTRIBUTE));
        printf("   recipient unvrapped key:\n");
        print_bytes(value, attr_get[0].ulValueLen);
    }

    printf("Sender encrypt with hKeyForWU:\n");
    cmCryptMechanism.mechanism      = CKM_GOST28147;
    cmCryptMechanism.pParameter     = iv;
    cmCryptMechanism.ulParameterLen = sizeof(iv);
    printf("   mechanism type: CKM_GOST28147\n");
    printf("   plain text:\n%s\n", pbPlainText);
    print_bytes(pbPlainText, (CK_ULONG)strlen((char *)pbPlainText));

    rvResult = Pkcs11FuncList->C_EncryptInit(hSessionSend,
                                             &cmCryptMechanism,
                                             hKeyForWU);
    printf("   encrypt initialization result: 0x%lx\n", rvResult);

    if (rvResult == CKR_OK)
    {
        rvResult = Pkcs11FuncList->C_Encrypt(hSessionSend,
                                             pbPlainText,
                                             ulPlainTextSize,
                                             NULL,
                                             &ulCipherSize);


        if (rvResult == CKR_OK)
        {
            printf("   cipher size: %ld\n", ulCipherSize);

            pbCipherText = (CK_BYTE_PTR)
                           malloc(ulCipherSize * sizeof(CK_BYTE));
            rvResult = Pkcs11FuncList->C_Encrypt(hSessionSend,
                                                 pbPlainText,
                                                 ulPlainTextSize,
                                                 pbCipherText,
                                                 &ulCipherSize);
        }
    }
    else
        return rvResult;

    printf("   encrypt result: 0x%lx\n", rvResult);

    if (rvResult != CKR_OK)
        return rvResult;

    printf("   cipher text: \n");
    print_bytes(pbCipherText, ulCipherSize);

    printf("Recipient decrypt with hUnwrappedRecpKey:\n");
    rvResult = Pkcs11FuncList2->C_DecryptInit(hSessionRecp,
                                              &cmCryptMechanism,
                                              hUnwrappedRecpKey);
    printf("   decrypt initialization result: 0x%lx\n", rvResult);

    if (rvResult == CKR_OK)
    {
        rvResult = Pkcs11FuncList2->C_Decrypt(hSessionRecp,
                                              pbCipherText,
                                              ulCipherSize,
                                              NULL,
                                              &ulDecryptedSize);


        if (rvResult == CKR_OK)
        {
            printf("   decrypted text size: %ld\n", ulDecryptedSize);

            pbDecryptedText = (CK_BYTE_PTR)
                              malloc(ulDecryptedSize * sizeof(CK_BYTE));
            rvResult = Pkcs11FuncList2->C_Decrypt(hSessionRecp,
                                                  pbCipherText,
                                                  ulCipherSize,
                                                  pbDecryptedText,
                                                  &ulDecryptedSize);
        }
    }
    else
        return rvResult;

    printf("   decrypt result: 0x%lx\n", rvResult);

    if (rvResult != CKR_OK)
        return rvResult;

    printf("   decrypted text: \n");
    print_bytes(pbDecryptedText, ulDecryptedSize);

    if (ulDecryptedSize != ulPlainTextSize)
    {
        fprintf(stderr, "Invalid decrypted text size\n");
        return -1;
    }

    if (memcmp(pbDecryptedText, pbPlainText, ulPlainTextSize) != 0)
    {
        fprintf(stderr, "Invalid decrypted text\n");
        return -1;
    }
    printf("CKM_GOST28147_KEY_WRAP test SUCCESS\n");

    rvResult =
        Pkcs11FuncList->C_DestroyObject(hSessionSend, hKeyForWU);
    printf("   C_DestroyObject result: 0x%lx\n", rvResult);
    rvResult =
        Pkcs11FuncList->C_DestroyObject(hSessionSend, hSendPubKey);
    printf("   C_DestroyObject result: 0x%lx\n", rvResult);
    rvResult =
        Pkcs11FuncList->C_DestroyObject(hSessionSend, hSendPriKey);
    printf("   C_DestroyObject result: 0x%lx\n", rvResult);
    rvResult =
        Pkcs11FuncList2->C_DestroyObject(hSessionRecp, hRecpPubKey);
    printf("   C_DestroyObject result: 0x%lx\n", rvResult);
    rvResult =
        Pkcs11FuncList2->C_DestroyObject(hSessionRecp, hRecpPriKey);
    printf("   C_DestroyObject result: 0x%lx\n", rvResult);
    rvResult =
        Pkcs11FuncList->C_DestroyObject(hSessionSend, hSendDH_Key);
    printf("   C_DestroyObject result: 0x%lx\n", rvResult);
    rvResult =
        Pkcs11FuncList2->C_DestroyObject(hSessionRecp, hRecpDH_Key);
    printf("   C_DestroyObject result: 0x%lx\n", rvResult);
    rvResult =
        Pkcs11FuncList2->C_DestroyObject(hSessionRecp, hUnwrappedRecpKey);
    printf("   C_DestroyObject result: 0x%lx\n", rvResult);
    rvResult = Pkcs11FuncList->C_CloseSession(hSessionSend);
    printf("Sender close session result: 0x%lx\n", rvResult);

    rvResult = Pkcs11FuncList2->C_CloseSession(hSessionRecp);
    printf("Recipient close session result: 0x%lx\n", rvResult);

    printf("SUCCESS\n");
    return 0;
}
