
#include "sec.h"
#include "common.h"
#include "tests.h"
#include "tests_cmd.h"

#include "openssl/ec.h"
#include "openssl/ecdsa.h"
#include "openssl/evp.h"

int
TEST_OSSL_gen_order_reduction(CRD_UT10XX_t *ut, const TEST_conf_t *conf)
{
	const unsigned char *ec_asn1 = ut->info.ecc_params->asn1_specified;
	int ec_asn1_octets = ut->info.ecc_params->asn1_specified_octets;
	unsigned char in[128];
	unsigned char out[128];
	char *hex = 0;

	EC_GROUP *ec = NULL;

	BN_CTX *ctx = NULL;
	BIGNUM *r = BN_new();
	BIGNUM *x = BN_new();
	BIGNUM *y = BN_new();
	BIGNUM *z = BN_new();
	
	int bits = 0;
	int i;

	if (!d2i_ECPKParameters(&ec, &ec_asn1, ec_asn1_octets)) 
		return (-1);

	ctx = BN_CTX_new();
	EC_GROUP_get_order(ec, r, ctx);

	TEST_session_open(ut);

	if (conf->gen_order_reduction)
		fprintf(stdout, "\nTest of generator order reduction\n");

	for (i = 0; i < conf->gen_order_reduction; i++, bits += 8)
	{
		if ( (bits > 16*BN_num_bytes(r)) || (bits < 8*BN_num_bytes(r)) )
			bits = 8*BN_num_bytes(r);
		
		BN_rand(x, bits, 0, 0);
		
		out[0] = HF_CMD;
		out[1] = TCMD_GEN_ORD_RED;
		out[2] = bits / 8;
		BN_bn2bin(x, out + 3);

		TEST_session_wr_rd(ut, out, 3 + bits/8, in, sizeof(in));
		
		BN_mod(z, x, r, ctx);
		BN_bin2bn(in + 1, BN_num_bytes(r), y);
		
		if (BN_cmp(y, z))
		{
			fprintf(stderr, "\nTest of generator order reduction failed (domain: "
				"%s, vec: %d)\n", ut->info.ecc_params->name, i);
			
			if (conf->details)
			{
				hex = BN_bn2hex(r);
				fprintf(stderr, "modulus (%3d): %s\n", 8*BN_num_bytes(r), hex); OPENSSL_free(hex);
				hex = BN_bn2hex(x);
				fprintf(stderr, "to reduction : %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(z);
				fprintf(stderr, "vector       : %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(y);
				fprintf(stderr, "actual       : %s\n", hex); OPENSSL_free(hex);
			}
		}
		
		fprintf(stdout, "\r%d%% done", 100 * (i + 1) / conf->gen_order_reduction);
		fflush(stdout);
	}

	TEST_session_close(ut);
	
	if (conf->gen_order_reduction)
		fprintf(stdout, "\n");
	
	if (ec) EC_GROUP_free(ec);
	if (ctx) BN_CTX_free(ctx);
	if (r) BN_free(r);
	if (x) BN_free(x);
	if (y) BN_free(y);
	if (z) BN_free(z);

	return (0);
}

int
TEST_OSSL_gen_order_inverse(CRD_UT10XX_t *ut, const TEST_conf_t *conf)
{
	const unsigned char *ec_asn1 = ut->info.ecc_params->asn1_specified;
	int ec_asn1_octets = ut->info.ecc_params->asn1_specified_octets;
	unsigned char in[128];
	unsigned char out[128];
	char *hex = 0;

	EC_GROUP *ec = NULL;

	BN_CTX *ctx = NULL;
	BIGNUM *r = BN_new();
	BIGNUM *x = BN_new();
	BIGNUM *y = BN_new();
	BIGNUM *z = BN_new();
	
	int i;

	if (!d2i_ECPKParameters(&ec, &ec_asn1, ec_asn1_octets)) 
		return (-1);

	ctx = BN_CTX_new();
	EC_GROUP_get_order(ec, r, ctx);

	TEST_session_open(ut);

	if (conf->gen_order_inverse)
		fprintf(stdout, "\nTest of generator order inversion\n");

	for (i = 0; i < conf->gen_order_inverse; i++)
	{
		BN_rand_range(x, r);
		
		if (BN_is_zero(x)) 
		{
			i--;
			continue;
		}
		
		memset(out, 0, sizeof(out));
		out[0] = HF_CMD;
		out[1] = TCMD_GEN_ORD_INV;
		BN_bn2bin(x, out + 2 + BN_num_bytes(r) - BN_num_bytes(x));

		TEST_session_wr_rd(ut, out, 2 + BN_num_bytes(r), in, sizeof(in));
		
		BN_mod_inverse(z, x, r, ctx);
		BN_bin2bn(in + 1, BN_num_bytes(r), y);
		
		if (BN_cmp(y, z))
		{
			fprintf(stderr, "\nTest of generator order inversion failed (domain: "
				"%s, vec: %d)\n", ut->info.ecc_params->name, i);
			fflush(stderr);
			
			if (conf->details)
			{
				hex = BN_bn2hex(r);
				fprintf(stderr, "modulus (%3d): %s\n", 8*BN_num_bytes(r), hex); OPENSSL_free(hex);
				hex = BN_bn2hex(x);
				fprintf(stderr, "to inverse   : %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(z);
				fprintf(stderr, "vector       : %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(y);
				fprintf(stderr, "actual       : %s\n", hex); OPENSSL_free(hex);
			}
		}
		
		fprintf(stdout, "\r%d%% done", 100 * (i + 1) / conf->gen_order_inverse);
		fflush(stdout);
	}

	TEST_session_close(ut);
	
	if (conf->gen_order_inverse)
		fprintf(stdout, "\n");
	
	if (ec) EC_GROUP_free(ec);
	if (ctx) BN_CTX_free(ctx);
	if (r) BN_free(r);
	if (x) BN_free(x);
	if (y) BN_free(y);
	if (z) BN_free(z);

	return (0);
}

int
TEST_OSSL_gen_multiples(CRD_UT10XX_t *ut, const TEST_conf_t *conf)
{
	const unsigned char *ec_asn1 = ut->info.ecc_params->asn1_specified;
	int ec_asn1_octets = ut->info.ecc_params->asn1_specified_octets;
	unsigned char in[128];
	unsigned char out[128];
	char *hex = 0;
	
	EC_GROUP *ec = NULL;
	EC_POINT *P = NULL;
	
	BN_CTX *ctx = NULL;
	BIGNUM *r = BN_new();
	BIGNUM *k = BN_new();
	BIGNUM *t = BN_new();
	BIGNUM *x = BN_new();
	BIGNUM *y = BN_new();
	BIGNUM *xP = BN_new();
	BIGNUM *yP = BN_new();
	
	int i;

	if (!d2i_ECPKParameters(&ec, &ec_asn1, ec_asn1_octets)) 
		return (-1);
	
	P = EC_POINT_new(ec);
	
	ctx = BN_CTX_new();
	EC_GROUP_get_order(ec, r, ctx);
	
	TEST_session_open(ut);
	
	BN_set_word(k, 0);
	BN_set_word(t, 1);

	if (conf->gen_multiples_small_ints)
		fprintf(stdout, "\nTest of generator multiples: small integers\n");

	for (i = 0; i < conf->gen_multiples_small_ints; i++)
	{
		BN_mod_add(k, k, t, r, ctx);
		EC_POINT_copy(P, EC_GROUP_get0_generator(ec));
		EC_POINT_mul(ec, P, k, NULL, NULL, ctx);
		
		memset(out, 0, sizeof(out));
		out[0] = HF_CMD;
		out[1] = TCMD_GEN_MULT;
		BN_bn2bin(k, out + 2 + BN_num_bytes(r) - BN_num_bytes(k));
		
		TEST_session_wr_rd(ut, out, 2 + BN_num_bytes(r), in, sizeof(in));
	
		BN_bin2bn(in + 1, (EC_GROUP_get_degree(ec) + 7) / 8, x);
		BN_bin2bn(in + 1 + (EC_GROUP_get_degree(ec) + 7) / 8, (EC_GROUP_get_degree(ec) + 7) / 8, y);
		EC_POINT_get_affine_coordinates_GFp(ec, P, xP, yP, ctx);
		
		if (BN_cmp(x, xP) || BN_cmp(y, yP))
		{
			fprintf(stderr, "\nTest of generator multiples failed (domain: "
				"%s, k: %d)\n", ut->info.ecc_params->name, i + 1);
			
			if (conf->details)
			{
				hex = BN_bn2hex(k);
				fprintf(stderr, "k: %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(xP);
				fprintf(stderr, "vector X(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(x);
				fprintf(stderr, "actual X(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(yP);
				fprintf(stderr, "vector Y(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(y);
				fprintf(stderr, "actual Y(P): %s\n", hex); OPENSSL_free(hex);
			}
		}

		fprintf(stdout, "\r%d%% done", 100 * (i + 1) / conf->gen_multiples_small_ints);
		fflush(stdout);
	}
	
	BN_set_word(k, 1);
	BN_set_word(t, 2);
	
	if (conf->gen_multiples_powers_of_2)
		fprintf(stdout, "\nTest of generator multiples: powers of 2\n");

	for (i = 0; i < conf->gen_multiples_powers_of_2; i++)
	{
		BN_mod_mul(k, k, t, r, ctx);
		EC_POINT_copy(P, EC_GROUP_get0_generator(ec));
		EC_POINT_mul(ec, P, k, NULL, NULL, ctx);
		
		memset(out, 0, sizeof(out));
		out[0] = HF_CMD;
		out[1] = TCMD_GEN_MULT;
		BN_bn2bin(k, out + 2 + BN_num_bytes(r) - BN_num_bytes(k));
		
		TEST_session_wr_rd(ut, out, 2 + BN_num_bytes(r), in, sizeof(in));
		
		BN_bin2bn(in + 1, (EC_GROUP_get_degree(ec) + 7) / 8, x);
		BN_bin2bn(in + 1 + (EC_GROUP_get_degree(ec) + 7) / 8, (EC_GROUP_get_degree(ec) + 7) / 8, y);
		EC_POINT_get_affine_coordinates_GFp(ec, P, xP, yP, ctx);
		
		if (BN_cmp(x, xP) || BN_cmp(y, yP))
		{
			fprintf(stderr, "Test of generator multiples failed (domain: "
				"%s, k: 2^%d)\n", ut->info.ecc_params->name, i + 1);
			
			if (conf->details)
			{
				hex = BN_bn2hex(k);
				fprintf(stderr, "k: %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(xP);
				fprintf(stderr, "vector X(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(x);
				fprintf(stderr, "actual X(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(yP);
				fprintf(stderr, "vector Y(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(y);
				fprintf(stderr, "actual Y(P): %s\n", hex); OPENSSL_free(hex);
			}
		}

		fprintf(stdout, "\r%d%% done", 100 * (i + 1) / conf->gen_multiples_powers_of_2);
		fflush(stdout);
	}
	
	BN_set_word(k, 1);
	BN_set_word(t, 31);
	
	if (conf->gen_multiples_powers_of_31)
		fprintf(stdout, "\nTest of generator multiples: powers of 31\n");

	for (i = 0; i < conf->gen_multiples_powers_of_31; i++)
	{
		BN_mod_mul(k, k, t, r, ctx);
		EC_POINT_copy(P, EC_GROUP_get0_generator(ec));
		EC_POINT_mul(ec, P, k, NULL, NULL, ctx);
		
		memset(out, 0, sizeof(out));
		out[0] = HF_CMD;
		out[1] = TCMD_GEN_MULT;
		BN_bn2bin(k, out + 2 + BN_num_bytes(r) - BN_num_bytes(k));
		
		TEST_session_wr_rd(ut, out, 2 + BN_num_bytes(r), in, sizeof(in));
		
		BN_bin2bn(in + 1, (EC_GROUP_get_degree(ec) + 7) / 8, x);
		BN_bin2bn(in + 1 + (EC_GROUP_get_degree(ec) + 7) / 8, (EC_GROUP_get_degree(ec) + 7) / 8, y);
		EC_POINT_get_affine_coordinates_GFp(ec, P, xP, yP, ctx);
		
		if (BN_cmp(x, xP) || BN_cmp(y, yP))
		{
			fprintf(stderr, "\nTest of generator multiples failed (domain: "
				"%s, k: 31^%d)\n", ut->info.ecc_params->name, i + 1);
			
			if (conf->details)
			{
				hex = BN_bn2hex(k);
				fprintf(stderr, "k: %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(xP);
				fprintf(stderr, "vector X(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(x);
				fprintf(stderr, "actual X(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(yP);
				fprintf(stderr, "vector Y(P): %s\n", hex); OPENSSL_free(hex);
				hex = BN_bn2hex(y);
				fprintf(stderr, "actual Y(P): %s\n", hex); OPENSSL_free(hex);
			}
		}

		fprintf(stdout, "\r%d%% done", 100 * (i + 1) / conf->gen_multiples_powers_of_31);
		fflush(stdout);
	}
	
	TEST_session_close(ut);
	
	if (conf->gen_multiples_small_ints || conf->gen_multiples_powers_of_2 
		|| conf->gen_multiples_powers_of_31) fprintf(stdout, "\n");
	
	if (ec) EC_GROUP_free(ec);
	if (P) EC_POINT_free(P);
	if (ctx) BN_CTX_free(ctx);
	if (r) BN_free(r);
	if (k) BN_free(k);
	if (t) BN_free(t);
	if (x) BN_free(x);
	if (y) BN_free(y);
	if (xP) BN_free(xP);
	if (yP) BN_free(yP);
	
	return (0);
}

static int
test_ossl_ecdsa_specific_hash(CRD_UT10XX_t *ut, const TEST_conf_t *conf, const EVP_MD *md, int tests)
{
	const unsigned char *ec_asn1 = ut->info.ecc_params->asn1_specified;
	int ec_asn1_octets = ut->info.ecc_params->asn1_specified_octets;

	unsigned char dgst[64];
	unsigned char out[128];
	unsigned char in[128];
	
	char *hex;
	
	int dgst_octets = 0;
	int octets = 0;
	
	EC_GROUP *ec = NULL;
	EC_POINT *P = NULL;
	EC_KEY *key = EC_KEY_new();
	
	BN_CTX *ctx = BN_CTX_new();
	BIGNUM *r = BN_new();
	BIGNUM *k = BN_new();
	
	int ret;
	int i;
	int j;

	if (!d2i_ECPKParameters(&ec, &ec_asn1, ec_asn1_octets)) 
		return (-1);

	BN_hex2bn(&k, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
	EC_GROUP_get_order(ec, r, ctx);
	BN_nnmod(k, k, r, ctx);
	P = EC_POINT_new(ec);
	EC_POINT_copy(P, EC_GROUP_get0_generator(ec));
	EC_POINT_mul(ec, P, k, NULL, NULL, ctx);
	
	TEST_session_open(ut);

	memset(dgst, 0, sizeof(dgst));	
	memset(out, 0, sizeof(out));
		
	fprintf(stdout, "\nTest of ECDSA: hash function %s\n", EVP_MD_name(md));
	
	EC_KEY_set_group(key, ec);
	EC_KEY_generate_key(key);
	EC_KEY_set_private_key(key, k);
	EC_KEY_set_public_key(key, P);
		
	for (i = 0; i < tests; i++)
	{
		out[0] = HF_CMD;
		out[1] = TCMD_ECDSA;
		BN_bn2bin(k, out + 2 + BN_num_bytes(r) - BN_num_bytes(k));
		EVP_Digest(dgst, sizeof(dgst), dgst, &dgst_octets, md, 0);
		memcpy(out + 2 + BN_num_bytes(r), dgst, dgst_octets);
		
		octets = TEST_session_wr_rd(ut, out, 2 + BN_num_bytes(r) + dgst_octets, in, sizeof(in));
		
		if ((ret = ECDSA_verify(0, dgst, dgst_octets, in + 1, octets - 1, key)) <= 0)
		{
			fprintf(stderr, "\nTest of ECDSA with %s function failed (domain: %s, vec: %d, "
				"ret: %d)\n", EVP_MD_name(md), ut->info.ecc_params->name, i, ret);
			
			if (conf->details)
			{
				hex = BN_bn2hex(k);
				fprintf(stderr, "k: %s\n", hex); OPENSSL_free(hex);
				fprintf(stderr, "signature: ");
				for (j = 1; j < octets; j++)
					fprintf(stderr, ":%02X", in[j]);
				fprintf(stderr, "\n");
			}
		}

		fprintf(stdout, "\r%d%% done", 100 * (i + 1) / tests);
		fflush(stdout);
	}
		
	TEST_session_close(ut);
	
	EC_GROUP_free(ec);
	EC_POINT_free(P);
	EC_KEY_free(key);
	BN_CTX_free(ctx);
	BN_free(r);
	BN_free(k);
	
	return (0);
}

int
TEST_OSSL_ecdsa(CRD_UT10XX_t *ut, const TEST_conf_t *conf)
{
	if (conf->ecdsa_with_sha1)
		test_ossl_ecdsa_specific_hash(ut, conf, EVP_sha1(), conf->ecdsa_with_sha1);
		
	if (conf->ecdsa_with_sha224)
		test_ossl_ecdsa_specific_hash(ut, conf, EVP_sha224(), conf->ecdsa_with_sha224);
		
	if (conf->ecdsa_with_sha256)
		test_ossl_ecdsa_specific_hash(ut, conf, EVP_sha256(), conf->ecdsa_with_sha256);
		
	if (conf->ecdsa_with_sha384)
		test_ossl_ecdsa_specific_hash(ut, conf, EVP_sha384(), conf->ecdsa_with_sha384);
		
	if (conf->ecdsa_with_sha512)
		test_ossl_ecdsa_specific_hash(ut, conf, EVP_sha512(), conf->ecdsa_with_sha512);
	
	if (conf->ecdsa_with_sha1 || conf->ecdsa_with_sha224 || conf->ecdsa_with_sha256
		|| conf->ecdsa_with_sha384 || conf->ecdsa_with_sha512) fprintf(stdout, "\n");
	
	return (0);
}
