#include "test_common.h"

CK_RV test_crypto();
CK_RV test_cka_always_authenticate(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(%s)\n",
                rc, get_ckr_name(rc));
        return rc;
    }
    rc = test_crypto();
    if (rc != CKR_OK)
    {
        fprintf(stderr, "test_crypto failed: 0x%lx(%s)\n",
                rc, get_ckr_name(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(%s)\n",
            rc, get_ckr_name(rc));
        goto out;
    }
    fprintf(stderr, "C_OpenSession success\n");

    // log in as normal user
    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(%s)\n",
            rc, get_ckr_name(rc));
        goto out_close;
    }
    fprintf(stderr, "C_Login success\n");
    rc = funcs->C_GetMechanismInfo(SlotId, CKM_GOSTR3410, &minfo);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
            "\n======== Mechanism CKM_GOSTR3410 not supported ===========\n");
        goto out_close;
    }
    rc = funcs->C_GetMechanismInfo(SlotId, CKM_GOSTR3410_PUBLIC_KEY_DERIVE, &minfo);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
            "\n======== Mechanism CKM_GOSTR3410_PUBLIC_KEY_DERIVE not supported ===========\n");
        goto out_close;
    }
    fprintf(stderr,
        "\n==================== CKA_ALWAYS_AUTHENTICATE test ==================\n");
    rc = test_cka_always_authenticate(hSession);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
            "ERROR CKM_GOSTR3410 failed, rc = 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
    }
    else
    {
        fprintf(stderr, "CKA_ALWAYS_AUTHENTICATE test SUCCESS.\n");
    }
 out_close:
    if ((ret = funcs->C_CloseSession(hSession)) != CKR_OK)
    {
        rc = ret;
        fprintf(stderr,
            "Error: C_CloseSession failed with 0x%lx(%s)\n", rc, get_ckr_name(rc));
    }
    else
    {
        fprintf(stderr, "C_CloseSession success\n");
    }

 out:
    return rc;
}

CK_RV test_cka_always_authenticate(CK_SESSION_HANDLE sess)
{
    CK_RV                  rc = CKR_OK;
    CK_BYTE                value[256];
    CK_ULONG               len;
    CK_OBJECT_HANDLE       pub_key            = CK_INVALID_HANDLE;
    CK_OBJECT_HANDLE       priv_key           = CK_INVALID_HANDLE;
    CK_OBJECT_HANDLE       der_pub_key           = CK_INVALID_HANDLE;
    CK_MECHANISM           mechanism_desc     = { CKM_GOSTR3410, NULL, 0 };
    CK_MECHANISM_PTR       mechanism          = &mechanism_desc;
    CK_MECHANISM           mechanism_gen_desc = { CKM_GOSTR3410_KEY_PAIR_GEN, NULL, 0 };
    CK_MECHANISM_PTR       mechanism_gen      = &mechanism_gen_desc;
    CK_MECHANISM  mechanism_pub_der_desc	  = { CKM_GOSTR3410_PUBLIC_KEY_DERIVE, NULL, 0 };
    CK_MECHANISM_PTR       mechanism_pub_der  = &mechanism_pub_der_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 oclass_pub  = CKO_PUBLIC_KEY;
//    static CK_OBJECT_CLASS oclass_priv = CKO_PRIVATE_KEY;
    static CK_KEY_TYPE     key_type    = CKK_GOSTR3410;

    static CK_BYTE         gostR3410params[] = {
        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[] = {
        0xee, 0x8c, 0xd9, 0x40, 0x23, 0xdd, 0x9a, 0xf8,
        0x17, 0xdd, 0xe8, 0x1f, 0x02, 0x25, 0x5b, 0xb3,
        0xaa, 0x6b, 0xd3, 0x21, 0x09, 0x41, 0x95, 0x7e,
        0xea, 0x7e, 0x0e, 0x3c, 0x35, 0x9f, 0x04, 0x5a
    };
//    static CK_BYTE         pub_key_value[64];
    CK_UTF8CHAR     *label         = (CK_UTF8CHAR *)"Always Auth Priv Key";
    CK_ATTRIBUTE           pub_gen_template[] = {
        { CKA_TOKEN,           &ltrue,          sizeof(ltrue)           },
        { CKA_GOSTR3410PARAMS, gostR3410params, sizeof(gostR3410params) },
        { CKA_GOSTR3411PARAMS, gostR3411params, sizeof(gostR3411params) },
        { CKA_VERIFY,          &ltrue,          sizeof(CK_BBOOL)        },
    };
    CK_ATTRIBUTE           priv_gen_template[] = {
		{ CKA_LABEL, NULL, 0 },
        { CKA_TOKEN,   &ltrue, sizeof(ltrue)    },
        { CKA_PRIVATE, &ltrue, sizeof(ltrue)    },
        { CKA_SIGN,    &ltrue, sizeof(CK_BBOOL) },
		{ CKA_DERIVE,  &ltrue, sizeof(CK_BBOOL) },
		{ CKA_ALWAYS_AUTHENTICATE, &ltrue, sizeof(CK_BBOOL) },
    };
    CK_ATTRIBUTE           pub_der_template[] = {
		{ CKA_LABEL, NULL, 0 },
    };
	priv_gen_template[0].pValue = label;
	priv_gen_template[0].ulValueLen = (CK_ULONG)strlen((char *)label) + 1;
	pub_der_template[0].pValue = label;
	pub_der_template[0].ulValueLen = (CK_ULONG)strlen((char *)label) + 1;

    rc = funcs->C_GenerateKeyPair(sess, mechanism_gen,
        pub_gen_template, sizeof(pub_gen_template) / sizeof(CK_ATTRIBUTE),
        priv_gen_template, sizeof(priv_gen_template) / sizeof(CK_ATTRIBUTE),
        &pub_key, &priv_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_GenerateKeyPair failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }
    fprintf(stderr, "C_GenerateKeyPair OK\n");
    fprintf(stderr, "Private key has CKA_ALWAYS_AUTHENTICATE value == CK_TRUE\n");
    fprintf(stderr, "Need to login every time before private key usage\n");

    rc = funcs->C_SignInit(sess, mechanism, priv_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_SignInit failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }
	fprintf(stderr, "C_SignInit OK\n");
	fprintf(stderr, "Private key usage set to CK_TRUE\n");

    len = sizeof(value);
    rc  = funcs->C_Sign(sess, data, sizeof(data), value, &len);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_Sign failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }
    fprintf(stderr, "C_Sign OK\n");

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

    rc = funcs->C_VerifyInit(sess, mechanism, pub_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_VerifyInit failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }

    rc = funcs->C_Verify(sess, data, sizeof(data), value, len);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_Verify failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }
    fprintf(stderr, "C_Verify OK\n");

	// Try to use private key with CKA_ALWAYS_AUTHENTICATE == CK_TRUE once more
    rc = funcs->C_SignInit(sess, mechanism, priv_key);
    fprintf(stderr, 
		"Second C_SignInit: 0x%lx(%s)\n",
        rc, get_ckr_name(rc));
    if (rc != CKR_USER_NOT_LOGGED_IN)
    {
		rc = CKR_FUNCTION_FAILED;
        return rc;
	}
	rc = CKR_OK;
    fprintf(stderr, "It's OK\n");

    rc = funcs->C_Login(sess, CKU_USER, user_pin, (CK_ULONG)strlen((char *)user_pin));
	if (rc != CKR_USER_ALREADY_LOGGED_IN) {
        fprintf(stderr, "C_Login: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
	}
	fprintf(stderr, "Second C_Login OK (CKR_USER_ALREADY_LOGGED_IN)\n");
	fprintf(stderr, "Private keys usage set to CK_FALSE\n");
	rc = CKR_OK;
	// Try to use private key once more
    rc = funcs->C_SignInit(sess, mechanism, priv_key);
    fprintf(stderr, 
		"C_SignInit just after second login: 0x%lx(%s)\n",
        rc, get_ckr_name(rc));
    if (rc != CKR_OK)
    {
		rc = CKR_FUNCTION_FAILED;
        return rc;
	}
    fprintf(stderr, "C_SignInit OK\n");
	fprintf(stderr, "Private key usage set to CK_TRUE\n");
    len = sizeof(value);
    rc  = funcs->C_Sign(sess, data, sizeof(data), value, &len);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_Sign failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }
    fprintf(stderr, "C_Sign OK\n");

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

	// Try to use private key once more
    rc = funcs->C_SignInit(sess, mechanism, priv_key);
    fprintf(stderr, 
		"Second C_SignInit: 0x%lx(%s)\n",
        rc, get_ckr_name(rc));
    if (rc != CKR_USER_NOT_LOGGED_IN)
    {
		rc = CKR_FUNCTION_FAILED;
        return rc;
	}
    fprintf(stderr, "It's OK\n");
	rc = CKR_OK;

	// Logout made private object handles invalid 
	rc = funcs->C_Logout(sess);
	if (rc != CKR_OK) {
        fprintf(stderr, "C_Logout failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
	}
	fprintf(stderr, "C_Logout OK\n");
	// Private key handle became invalid
    rc = funcs->C_Login(sess, CKU_USER, user_pin, (CK_ULONG)strlen((char *)user_pin));
	if (rc != CKR_OK) {
        fprintf(stderr, "C_Login failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
	}
	fprintf(stderr, "C_Login OK\n");
	// Need to find private key now 
	priv_key = CK_INVALID_HANDLE;
	rc = find_obj(sess, 
		priv_gen_template, sizeof(priv_gen_template) / sizeof(CK_ATTRIBUTE), &priv_key); 
    if (rc != CKR_OK)
    {
		fprintf(stderr, "Private key not found");
		rc = CKR_FUNCTION_FAILED;
        return rc;
	}
	fprintf(stderr, "Find private key OK\n");
	// Try to use private key once more
    rc = funcs->C_SignInit(sess, mechanism, priv_key);
    fprintf(stderr, 
		"C_SignInit after logout, login and find private key: 0x%lx(%s)\n",
        rc, get_ckr_name(rc));
    if (rc != CKR_OK)
    {
		rc = CKR_FUNCTION_FAILED;
        return rc;
	}
    fprintf(stderr, "C_SignInit OK\n");
	fprintf(stderr, "Private key usage set to CK_TRUE\n");
    len = sizeof(value);
    rc  = funcs->C_Sign(sess, data, sizeof(data), value, &len);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_Sign failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }
    fprintf(stderr, "C_Sign OK\n");

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

    // Получаем открытый ключ по закрытому
	printf("Try to derive public key from private key\n");
    rc = funcs->C_DeriveKey(sess, mechanism_pub_der, priv_key,
		pub_der_template, sizeof(pub_der_template)/sizeof(CK_ATTRIBUTE),
        &der_pub_key);
    fprintf(stderr, 
		"C_DeriveKey after C_SignInit: 0x%lx(%s)\n",
        rc, get_ckr_name(rc));
    if (rc != CKR_USER_NOT_LOGGED_IN)
    {
		rc = CKR_FUNCTION_FAILED;
        return rc;
	}
    fprintf(stderr, "It's OK\n");
	rc = CKR_OK;

    rc = funcs->C_Login(sess, CKU_USER, user_pin, (CK_ULONG)strlen((char *)user_pin));
	if (rc != CKR_USER_ALREADY_LOGGED_IN) {
        fprintf(stderr, "C_Login: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
	}
	fprintf(stderr, "C_Login OK (CKR_USER_ALREADY_LOGGED_IN)\n");
	fprintf(stderr, "Private keys usage set to CK_FALSE\n");
    rc = funcs->C_DeriveKey(sess, mechanism_pub_der, priv_key,
		pub_der_template, sizeof(pub_der_template)/sizeof(CK_ATTRIBUTE),
        &der_pub_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_DeriveKey: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
	}
    fprintf(stderr, "C_DeriveKey OK\n");

    rc = funcs->C_DestroyObject(sess, priv_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_DestroyObject failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }
    rc = funcs->C_DestroyObject(sess, pub_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_DestroyObject failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }
    rc = funcs->C_DestroyObject(sess, der_pub_key);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "C_DestroyObject failed: 0x%lx(%s)\n",
            rc, get_ckr_name(rc));
        return rc;
    }

    return rc;
}



