/*
 * p11test_case_ec_derive.c: Check the functionality of derive mechanisms
 *
 * Copyright (C) 2019 Red Hat, Inc.
 *
 * Author: Jakub Jelen <jjelen@redhat.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "p11test_case_ec_derive.h"
#include "libopensc/internal.h"

size_t
pkcs11_derive(test_cert_t *o, token_info_t * info,
	unsigned char *pub, size_t pub_len, test_mech_t *mech, unsigned char **secret)
{
	CK_RV rv;
	CK_FUNCTION_LIST_PTR fp = info->function_pointer;
	CK_ECDH1_DERIVE_PARAMS params = {CKD_NULL, 0, NULL_PTR, 0, NULL_PTR};
	CK_MECHANISM mechanism = { mech->mech, NULL_PTR, 0 };
	CK_OBJECT_HANDLE newkey;
	CK_OBJECT_CLASS newkey_class = CKO_SECRET_KEY;
	CK_KEY_TYPE newkey_type = CKK_GENERIC_SECRET;
	CK_ULONG newkey_len = BYTES4BITS(o->bits);
	CK_BYTE newkey_id[] = {0x00, 0xff, 0x31};
	CK_BYTE newkey_label[] = {"Derived key"};
	CK_BBOOL _true = TRUE;
	CK_BBOOL _false = FALSE;
	CK_ATTRIBUTE template[] = {
			{CKA_TOKEN, &_false, sizeof(_false)}, /* session only object */
			{CKA_CLASS, &newkey_class, sizeof(newkey_class)},
			{CKA_ID, &newkey_id, sizeof(newkey_id)},
			{CKA_LABEL, &newkey_label, sizeof(newkey_label)},
			{CKA_KEY_TYPE, &newkey_type, sizeof(newkey_type)},
			{CKA_VALUE_LEN, &newkey_len, sizeof(newkey_len)},
			{CKA_SENSITIVE, &_false, sizeof(_false)},
			{CKA_EXTRACTABLE, &_true, sizeof(_true)},
			{CKA_ENCRYPT, &_true, sizeof(_true)},
			{CKA_DECRYPT, &_true, sizeof(_true)},
			{CKA_WRAP, &_true, sizeof(_true)},
			{CKA_UNWRAP, &_true, sizeof(_true)},
	};
	CK_ATTRIBUTE get_value = {CKA_VALUE, NULL_PTR, 0};
	CK_ULONG template_len = 10;

	params.pSharedData = NULL;
	params.ulSharedDataLen = 0;
	params.pPublicData = pub;
	params.ulPublicDataLen = pub_len;

	mechanism.pParameter = &params;
	mechanism.ulParameterLen = sizeof(CK_ECDH1_DERIVE_PARAMS);

	rv = fp->C_DeriveKey(info->session_handle, &mechanism, o->private_handle,
		template, template_len, &newkey);
	if (rv != CKR_OK) {
		debug_print("  C_DeriveKey: rv = 0x%.8lX\n", rv);
		return 0;
	}

	/* Lets read the derived data now */
	rv = fp->C_GetAttributeValue(info->session_handle, newkey,
		&get_value, 1);
	if (rv != CKR_OK) {
		fail_msg("C_GetAttributeValue: rv = 0x%.8lX\n", rv);
		destroy_tmp_object(info, newkey);
		return 0;
	}

	get_value.pValue = malloc(get_value.ulValueLen);
	if (get_value.pValue == NULL) {
		fail_msg("malloc failed");
		destroy_tmp_object(info, newkey);
		return 0;
	}

	rv = fp->C_GetAttributeValue(info->session_handle, newkey,
		&get_value, 1);
	destroy_tmp_object(info, newkey);
	if (rv != CKR_OK) {
		fail_msg("C_GetAttributeValue: rv = 0x%.8lX\n", rv);
		return 0;
	}

	*secret = get_value.pValue;
	return get_value.ulValueLen;
}

int
test_derive_montgomery(test_cert_t *o, token_info_t *info, test_mech_t *mech)
{
#ifdef EVP_PKEY_X25519
	unsigned char *secret = NULL, *pkcs11_secret = NULL;
	EVP_PKEY_CTX *pctx = NULL;
	EVP_PKEY *pkey = NULL; /* This is peer key */
	unsigned char *pub = NULL;
	size_t pub_len = 0, secret_len = o->bits / 8, pkcs11_secret_len = 0;
	int rc;

	if (o->private_handle == CK_INVALID_HANDLE) {
		debug_print(" [SKIP %s ] Missing private key", o->id_str);
		return 1;
	}

	switch (o->type) {
	case EVP_PKEY_X25519:
#ifdef EVP_PKEY_X448
	case EVP_PKEY_X448:
#endif
		/* OK */
		break;
	default:
		debug_print(" [ KEY %s ] Skip non-montgomery key for derive", o->id_str);
		return 1;
	}

	/* First, we need to generate our key */
	pctx = EVP_PKEY_CTX_new_id(o->type, NULL);
	if (pctx == NULL) {
		debug_print(" [ KEY %s ] EVP_PKEY_CTX_new_id failed", o->id_str);
		return 1;
	}

	rc = EVP_PKEY_keygen_init(pctx);
	if (rc != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_keygen_init failed", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		return 1;
	}

	rc = EVP_PKEY_keygen(pctx, &pkey);
	EVP_PKEY_CTX_free(pctx);
	if (rc != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_keygen failed", o->id_str);
		return 1;
	}

	/* Just here we start with key derivation in OpenSSL */
	pctx = EVP_PKEY_CTX_new(pkey, NULL);
	if (pctx == NULL) {
		debug_print(" [ KEY %s ] EVP_PKEY_CTX_new failed", o->id_str);
		EVP_PKEY_free(pkey);
		return 1;
	}

	rc = EVP_PKEY_derive_init(pctx);
	if (rc != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_derive_init failed", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		EVP_PKEY_free(pkey);
		return 1;
	}

	rc = EVP_PKEY_derive_set_peer(pctx, o->key);
	if (rc != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_derive_set_peer failed", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		EVP_PKEY_free(pkey);
		return 1;
	}

	/* Allocate the memory for the shared secret */
	if ((secret = malloc(secret_len)) == NULL) {
		debug_print(" [ KEY %s ] Failed to allocate memory for secret", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		EVP_PKEY_free(pkey);
		return 1;
	}

	rc = EVP_PKEY_derive(pctx, secret, &secret_len);
	EVP_PKEY_CTX_free(pctx);
	if (rc != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_derive failed", o->id_str);
		EVP_PKEY_free(pkey);
		free(secret);
		return 1;
	}

	/* Try to do the same with the card key */
	rc = EVP_PKEY_get_raw_public_key(pkey, pub, &pub_len);
	if (rc != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_get_raw_public_key failed", o->id_str);
		EVP_PKEY_free(pkey);
		free(secret);
		return 1;
	}
	pub = malloc(pub_len);
	if (pub == NULL) {
		debug_print(" [ KEY %s ] failed to allocate memory", o->id_str);
		EVP_PKEY_free(pkey);
		free(secret);
		return 1;
	}
	rc = EVP_PKEY_get_raw_public_key(pkey, pub, &pub_len);
	if (rc != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_get_raw_public_key failed", o->id_str);
		free(pub);
		EVP_PKEY_free(pkey);
		free(secret);
		return 1;
	}

	pkcs11_secret_len = pkcs11_derive(o, info, pub, pub_len, mech, &pkcs11_secret);

	if (secret_len == pkcs11_secret_len && memcmp(secret, pkcs11_secret, secret_len) == 0) {
		mech->result_flags |= FLAGS_DERIVE;
		debug_print(" [ OK %s ] Derived secrets match", o->id_str);
		free(pub);
		EVP_PKEY_free(pkey);
		free(secret);
		free(pkcs11_secret);
		return 0;
	}

	debug_print(" [ KEY %s ] Derived secret does not match", o->id_str);
	free(pub);
	EVP_PKEY_free(pkey);
	free(secret);
	free(pkcs11_secret);
	return 1;
#else
	return 0;
#endif
}

int test_derive(test_cert_t *o, token_info_t *info, test_mech_t *mech)
{
	unsigned char *secret = NULL, *pkcs11_secret = NULL;
	unsigned char *pub = NULL;
	size_t pub_len = 0, secret_len = 0, pkcs11_secret_len = 0;
	int rv = 1;

#if OPENSSL_VERSION_NUMBER < 0x30000000L
	int nid = 0;
	EC_GROUP *group = NULL;
	const EC_POINT *publickey = NULL;
	EC_KEY *key = NULL;
#endif
	EVP_PKEY_CTX *pctx = NULL;
	EVP_PKEY *evp_pkey = NULL;

	if (o->private_handle == CK_INVALID_HANDLE) {
		debug_print(" [SKIP %s ] Missing private key", o->id_str);
		return 1;
	}

	if (o->type != EVP_PKEY_EC) {
		debug_print(" [ KEY %s ] Skip non-EC key for derive", o->id_str);
		return 1;
	}

	debug_print(" [ KEY %s ] Trying EC derive using CKM_%s and %lu-bit key",
	o->id_str, get_mechanism_name(mech->mech), o->bits);
#if OPENSSL_VERSION_NUMBER < 0x30000000L
	if (o->bits == 256)
		nid = NID_X9_62_prime256v1;
	else if (o->bits == 384)
		nid = NID_secp384r1;
	else if (o->bits == 521)
		nid = NID_secp521r1;
	else {
		debug_print(" [ KEY %s ] Skip key of unknown size", o->id_str);
		return 1;
	}
#endif

	/* Generate the peer private key */
	if ((pctx = EVP_PKEY_CTX_new(o->key, NULL)) == NULL) {
		debug_print(" [ KEY %s ] EVP_PKEY_CTX_new_id failed", o->id_str);
		return 1;
	}

	if (EVP_PKEY_keygen_init(pctx) != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_keygen_init failed", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		return 1;
	}

	if (EVP_PKEY_keygen(pctx, &evp_pkey) != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_keygen failed", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		return 1;
	}
	EVP_PKEY_CTX_free(pctx);

	/* Start with key derivation in OpenSSL*/
	pctx = EVP_PKEY_CTX_new(evp_pkey, NULL);
	if (pctx == NULL ||
		EVP_PKEY_derive_init(pctx) != 1 ||
		EVP_PKEY_derive_set_peer(pctx, o->key) != 1) {
		debug_print(" [ KEY %s ] Cannot derive key", o->id_str);
		EVP_PKEY_free(evp_pkey);
		return 1;
	}

	/* Get buffer length */
	if (EVP_PKEY_derive(pctx, NULL, &secret_len) != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_derive failed", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		EVP_PKEY_free(evp_pkey);
		return 1;
	}
	/* Allocate the memory for the shared secret */
	if ((secret = malloc(secret_len)) == NULL) {
		debug_print(" [ KEY %s ] Failed to allocate memory for secret", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		EVP_PKEY_free(evp_pkey);
		return 1;
	}

	if (EVP_PKEY_derive(pctx, secret, &secret_len) != 1) {
		debug_print(" [ KEY %s ] EVP_PKEY_derive failed", o->id_str);
		EVP_PKEY_CTX_free(pctx);
		EVP_PKEY_free(evp_pkey);
		free(secret);
		return 1;
	}
	EVP_PKEY_CTX_free(pctx);

	/* Try to do the same with the card key */

	/* Get length of pub */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
	group = EC_GROUP_new_by_curve_name(nid);
	key = EVP_PKEY_get0_EC_KEY(evp_pkey);
	publickey = EC_KEY_get0_public_key(key);

	pub_len = EC_POINT_point2oct(group, publickey,
		POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
#else
	EVP_PKEY_get_octet_string_param(evp_pkey, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0, &pub_len);
#endif
	/* Allocate memory for public key*/
	if (pub_len == 0) {
		debug_print(" [ KEY %s ] Failed to allocate memory for secret", o->id_str);
		free(secret);
		EVP_PKEY_free(evp_pkey);
		return 1;
	}

	pub = malloc(pub_len);
	if (pub == NULL) {
		debug_print(" [ OK %s ] Failed to allocate memory", o->id_str);
		free(secret);
		EVP_PKEY_free(evp_pkey);
		return 1;
	}
#if OPENSSL_VERSION_NUMBER < 0x30000000L
	pub_len = EC_POINT_point2oct(group, publickey,
		POINT_CONVERSION_UNCOMPRESSED, pub, pub_len, NULL);
	EC_GROUP_free(group);
	if (pub_len == 0) {
#else
	if (EVP_PKEY_get_octet_string_param(evp_pkey, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, pub, pub_len, NULL) != 1) {
#endif
		debug_print(" [ KEY %s ] Cannot get public key", o->id_str);
		EVP_PKEY_free(evp_pkey);
		free(secret);
		free(pub);
		return 1;
	}
	EVP_PKEY_free(evp_pkey);

	pkcs11_secret_len = pkcs11_derive(o, info, pub, pub_len, mech, &pkcs11_secret);

	if (secret_len == pkcs11_secret_len && memcmp(secret, pkcs11_secret, secret_len) == 0) {
		mech->result_flags |= FLAGS_DERIVE;
		debug_print(" [ OK %s ] Derived secrets match", o->id_str);
		rv = 0;
	} else {
		debug_print(" [ KEY %s ] Derived secret does not match", o->id_str);
	}

	free(pub);
	free(secret);
	free(pkcs11_secret);
	return rv;
}


void derive_tests(void **state) {
	unsigned int i;
	size_t j;
	int errors = 0;
	token_info_t *info = (token_info_t *) *state;
	test_certs_t objects;

	test_certs_init(&objects);

	P11TEST_START(info);
	search_for_all_objects(&objects, info);

	debug_print("Check if the key derivation works.\n");
	for (i = 0; i < objects.count; i++) {
		test_cert_t *o = &objects.data[i];
		/* Ignore if there is missing private key */
		if (o->private_handle == CK_INVALID_HANDLE) {
			continue;
		}

		for (j = 0; j < o->num_mechs; j++) {
			if ((o->mechs[j].usage_flags & CKF_DERIVE) == 0 || !o->derive_priv) {
				continue;
			}

			switch (o->key_type) {
			case CKK_EC:
				errors += test_derive(o, info, &(o->mechs[j]));
				break;
			case CKK_EC_MONTGOMERY:
				errors += test_derive_montgomery(o, info, &(o->mechs[j]));
				break;
			default:
				/* Other keys do not support derivation */
				break;
			}
		}
	}

	/* print summary */
	printf("[KEY ID] [LABEL]\n");
	printf("[ TYPE ] [ SIZE ] [ PUBLIC ] [  DERIVE  ]\n");
	P11TEST_DATA_ROW(info, 3,
		's', "KEY ID",
		's', "MECHANISM",
		's', "DERIVE WORKS");
	for (i = 0; i < objects.count; i++) {
		if (objects.data[i].key_type != CKK_EC &&
			objects.data[i].key_type != CKK_EC_MONTGOMERY)
			continue;

		test_cert_t *o = &objects.data[i];
		printf("\n[%-6s] [%s]\n", o->id_str, o->label);
		printf("[%s] [%6lu] [  %s  ] [ %s%s ]\n",
				(o->key_type == CKK_EC ? "ECDSA " :
					o->key_type == CKK_EC_MONTGOMERY ? "XEDDSA" : " ?? "),
				o->bits,
				o->verify_public == 1 ? " ./ " : "    ",
				o->derive_pub ? "[./]" : "[  ]",
				o->derive_priv ? "[./]" : "[  ]");
		if (!o->derive_pub && !o->derive_priv) {
			printf("  no usable attributes found ... ignored\n");
			continue;
		}
		if (objects.data[i].private_handle == CK_INVALID_HANDLE) {
			continue;
		}
		for (j = 0; j < o->num_mechs; j++) {
			test_mech_t *mech = &o->mechs[j];
			if ((mech->usage_flags & CKF_DERIVE) == 0) {
				/* not applicable mechanisms are skipped */
				continue;
			}
			printf("  [ %-22s ] [   %s   ]\n",
				get_mechanism_name(mech->mech),
				mech->result_flags & FLAGS_DERIVE ? "[./]" : "    ");
			if ((mech->result_flags & FLAGS_DERIVE) == 0)
				continue; /* skip empty rows for export */
			P11TEST_DATA_ROW(info, 3,
				's', o->id_str,
				's', get_mechanism_name(mech->mech),
				's', mech->result_flags & FLAGS_DERIVE ? "YES" : "");
		}
	}
	printf(" Public == Cert -----^            ^\n");
	printf(" ECDH Derive functionality -------'\n");

	clean_all_objects(&objects);
	if (errors > 0)
		P11TEST_FAIL(info, "Not all the derive mechanisms worked.");
	P11TEST_PASS(info);
}
