/*
 *
 */
 
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef NO_INCLUDE_LOCL
#include "error.h"
#include "crd_ut10xx.h"
#endif

#ifndef NO_OSSL_ENGINE_INTERFACE

#include "openssl/ui.h"
#include "openssl/bio.h"
#include "openssl/pem.h"
#include "openssl/evp.h"
#include "openssl/ecdsa.h"
#include "openssl/objects.h"
#include "openssl/engine.h"
#include "openssl/x509.h"

/*
 * Error file generated by command:
 *
 *   perl ../util/mkerr.pl -conf e_stoken.ec -nostatic -staticloader -write e_stoken.c
 *
 * running in openssl/engines directory.
 */
#include "e_stoken_err.c"

#define LOG_LIB_ERROR(__err) \
	{ \
		if (stoken_log != NULL) \
		{ \
			fprintf(stoken_log, "! %s >> %s:%s:%s:%s\n", \
				stoken_time(), __FILE__, __func__, XSTR(__LINE__), \
				CRD_UT10XX_lib_error_desc(__err)); \
			fflush(stoken_log); \
		} \
	}

#define LOG_DEV_ERROR(__ut, __err) \
	{ \
		if (stoken_log != NULL) \
		{ \
			fprintf(stoken_log, "! %s >> %s:%s:%s:%s\n", \
				stoken_time(), __FILE__, __func__, XSTR(__LINE__), \
				CRD_UT10XX_lib_error_desc(__err)); \
			fprintf(stoken_log, "!\n"); \
			while (ERROR_count((__ut)->err)) \
				fprintf(stoken_log, "! \t%s\n", ERROR_pop((__ut)->err)); \
			fprintf(stoken_log, "!\n"); \
			fflush(stoken_log); \
		} \
	}

#define LOG_ERROR(__msg) \
	{ \
		if (stoken_log != NULL) \
		{ \
			fprintf(stoken_log, "! %s >> %s:%s:%s:%s\n", \
				stoken_time(), __FILE__, __func__, XSTR(__LINE__), \
				__msg); \
			fflush(stoken_log); \
		} \
	}

#define LOG_MSG(__msg) \
	{ \
		if (stoken_log != NULL) \
		{ \
			fprintf(stoken_log, "  %s >> %s\n", stoken_time(), __msg); \
			fflush(stoken_log); \
		} \
	}

/* 
 * This is temprorary (till OpenSSL implement final version of 
 * ECDSA_METHOD interface.
 */
typedef struct ecdsa_method 
{
	const char *name;
	ECDSA_SIG *(*ecdsa_do_sign)(const unsigned char *dgst, int dgst_len, 
		const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey);
	int (*ecdsa_sign_setup)(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, 
		BIGNUM **r);
	int (*ecdsa_do_verify)(const unsigned char *dgst, int dgst_len, 
		const ECDSA_SIG *sig, EC_KEY *eckey);
#if 0
	int (*init)(EC_KEY *eckey);
	int (*finish)(EC_KEY *eckey);
#endif
	int flags;
	char *app_data;
}
ecdsa_method;

static const char *stoken_id = "stoken";
static const char *stoken_name = "CMM Sigma sToken CRD-UT10xx engine support";

static const char *stokensoft_id = "stokensoft";
static const char *stokensoft_name = "CMM Sigma software sToken engine support";

static FILE *stoken_log = 0;
static char stoken_token_name[256];
static char stoken_key_name[256];
static char stoken_user_pin[9];

static char stokensoft_user_pin[128];

/* The definitions for control commands specific to sToken CRD-UT10XX engine. */
#define STOKEN_CMD_SET_LOG_FILE ENGINE_CMD_BASE
#define STOKEN_CMD_SET_TOKEN_NAME (ENGINE_CMD_BASE + 1)
#define STOKEN_CMD_SET_KEY_NAME (ENGINE_CMD_BASE + 2)
#define STOKEN_CMD_SET_USER_PIN (ENGINE_CMD_BASE + 3)
static const ENGINE_CMD_DEFN stoken_cmd_defns[] =
{
	{
		STOKEN_CMD_SET_LOG_FILE,
		"log_file",
		"Set log file to write engine messages",
		ENGINE_CMD_FLAG_STRING
	},
	{
		STOKEN_CMD_SET_TOKEN_NAME,
		"token_name",
		"Set token name which will be used by default",
		ENGINE_CMD_FLAG_STRING
	},
	{
		STOKEN_CMD_SET_KEY_NAME,
		"key_name",
		"Set key name which will be used by default",
		ENGINE_CMD_FLAG_STRING
	},
	{
		STOKEN_CMD_SET_USER_PIN,
		"user_pin",
		"Set User PIN which will be used for authentication",
		ENGINE_CMD_FLAG_STRING
	},
	{
		0, 
		NULL, 
		NULL, 
		0
	}
};

/* The definitions for control commands specific to software sToken engine. */
#define STOKENSOFT_CMD_SET_LOG_FILE ENGINE_CMD_BASE
#define STOKENSOFT_CMD_SET_USER_PIN (ENGINE_CMD_BASE + 1)
static const ENGINE_CMD_DEFN stokensoft_cmd_defns[] = 
{
	{
		STOKENSOFT_CMD_SET_USER_PIN,
		"user_pin",
		"Set User PIN or password which will be used for authentication",
		ENGINE_CMD_FLAG_STRING
	},
	{
		0, 
		NULL, 
		NULL, 
		0
	}
};



static const char* stoken_time(void);
static int stoken_add_all_digests(void);


static ECDSA_SIG *stoken_ecdsa_do_sign(const unsigned char *dgst, int dgst_len, 
	const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey);
static int stoken_ecdsa_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, 
	BIGNUM **r);

static ECDSA_METHOD stoken_ecdsa_method = 
{
	.name = "ECDSA (sToken CRD-UT10xx)",
	.ecdsa_do_sign = stoken_ecdsa_do_sign,
	.ecdsa_sign_setup = stoken_ecdsa_sign_setup,
	.ecdsa_do_verify = NULL,
	.flags = 0,
	.app_data = NULL
};


static int stoken_init(ENGINE *e);
static int stoken_finish(ENGINE *e);
static int stoken_destroy(ENGINE *e);
static int stoken_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)(void));
static EVP_PKEY* stoken_load_private_key(ENGINE *e, const char *key_id, UI_METHOD *ui_method, 
	void *callback_data);
static EVP_PKEY* stoken_load_public_key(ENGINE *e, const char *key_id, UI_METHOD *ui_method, 
	void *callback_data);

static int stokensoft_init(ENGINE *e);
static int stokensoft_finish(ENGINE *e);
static int stokensoft_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)(void));
static EVP_PKEY* stokensoft_load_private_key(ENGINE *e, const char *key_id, UI_METHOD *ui_method, 
	void *callback_data);

static int 
stoken_bind(ENGINE *e, const char *id)
{
	memset(stoken_token_name, 0, sizeof(stoken_token_name));
	memset(stoken_key_name, 0, sizeof(stoken_key_name));
	memset(stoken_user_pin, 0, sizeof(stoken_user_pin));
	
	memset(stokensoft_user_pin, 0, sizeof(stokensoft_user_pin));
	
	stoken_ecdsa_method.ecdsa_do_verify = 
		ECDSA_OpenSSL()->ecdsa_do_verify;
	
	/* Bind sToken CDR-UT10xx hardware engine. */
	if (id && (strcmp(id, stoken_id) == 0))
	{
		if ( !ENGINE_set_id(e, stoken_id) 
			|| !ENGINE_set_name(e, stoken_name) 
			|| !ENGINE_set_ECDSA(e, &stoken_ecdsa_method)
			|| !ENGINE_set_init_function(e, stoken_init) 
			|| !ENGINE_set_finish_function(e, stoken_finish) 
			|| !ENGINE_set_destroy_function(e, stoken_destroy) 
			|| !ENGINE_set_ctrl_function(e, stoken_ctrl)
			|| !ENGINE_set_load_privkey_function(e, stoken_load_private_key)
			|| !ENGINE_set_load_pubkey_function(e, stoken_load_public_key)
			|| !ENGINE_set_cmd_defns(e, stoken_cmd_defns) )
		{
			STOKENerr(STOKEN_F_STOKEN_BIND, STOKEN_R_COULD_NOT_SET_ENGINE);
			return (0);
		}
	}
	
    	/* Bind software sToken engine. */
	if (id && (strcmp(id, stokensoft_id) == 0))
	{
		if ( !ENGINE_set_id(e, stokensoft_id) 
			|| !ENGINE_set_name(e, stokensoft_name) 
			|| !ENGINE_set_ECDSA(e, ECDSA_OpenSSL())
			|| !ENGINE_set_init_function(e, stokensoft_init) 
			|| !ENGINE_set_finish_function(e, stokensoft_finish) 
			|| !ENGINE_set_destroy_function(e, stoken_destroy) 
			|| !ENGINE_set_ctrl_function(e, stokensoft_ctrl)
			|| !ENGINE_set_load_privkey_function(e, stokensoft_load_private_key)
			|| !ENGINE_set_cmd_defns(e, stokensoft_cmd_defns) )
		{
			STOKENerr(STOKEN_F_STOKEN_BIND, STOKEN_R_COULD_NOT_SET_ENGINE);
			return (0);
		}
	}
	
	/* Load error strings. */
	ERR_load_STOKEN_strings();
	
	return (1);
}

CRD_EXPORT_SYMBOL IMPLEMENT_DYNAMIC_CHECK_FN();
CRD_EXPORT_SYMBOL IMPLEMENT_DYNAMIC_BIND_FN(stoken_bind);

void ENGINE_load_stoken(void)
{
	ENGINE *e = 0;
	
	/* Add ECDSA with SHA-2 implemntations. */
	stoken_add_all_digests();
	
	/* Add sToken CRD-UT10xx. */
	if ( ((e = ENGINE_new()) == 0) || (stoken_bind(e, stoken_id) == 0) )
	{
		STOKENerr(STOKEN_F_ENGINE_LOAD_STOKEN, STOKEN_R_COULD_NOT_BIND_STOKEN_CRD_UT10XX);
		return;
	}
	
	ENGINE_add(e);
	ENGINE_free(e);
	
	/* Add software sToken. */
	if ( ((e = ENGINE_new()) == 0) || (stoken_bind(e, stokensoft_id) == 0) )
	{
		STOKENerr(STOKEN_F_ENGINE_LOAD_STOKEN, STOKEN_R_COULD_NOT_BIND_STOKEN_SOFT);
		return;
	}
	
	ENGINE_add(e);
	ENGINE_free(e);
	
	/* Clear error stack. */
	ERR_clear_error();
}

static const char*
stoken_time(void)
{
	static char at[32];
	time_t t = time(NULL);
	struct tm *utc = localtime(&t);
	
	strftime(at, sizeof(at), "[%d-%m-%Y] %H:%M:%S", utc);
	
	return (at);
}

static ECDSA_SIG *
stoken_ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, 
	const BIGNUM *rp, EC_KEY *eckey)
{
	unsigned char prv_tb[128];
	unsigned char *prv_ptr = prv_tb;
	
	unsigned char sig_tb[128];
	const unsigned char *sig_ptr = sig_tb;
	int sig_len = 0;
	
	char *dev_name = (char *)prv_tb + 8;;
	int key_num = -1;
	char *pin = (char *)prv_tb + 20;;
	
	CRD_UT10XX_t *ut = 0;
	
	ECDSA_SIG *sig = 0;
	
	int status = 0;
	
	if ( i2d_ECPrivateKey(eckey, &prv_ptr) == 0 )
	{
		LOG_ERROR("could not convert private key");
		return (0);
	}
	
	key_num = prv_tb[19];
	
	if ( (ut = CRD_UT10XX_find_by_name(dev_name)) == 0 )
	{
		LOG_ERROR("could not find device")
		return (0);
	}
	
	if ( (status = CRD_UT10XX_session_open(ut)) < 0 )
	{
		LOG_DEV_ERROR(ut, status);
		return (0);
	}
	
	if ( (status = sig_len = CRD_UT10XX_cmd_ecdsa_sign(ut, pin, key_num, (char *)sig_tb, 
		sizeof(sig_tb), (char *)dgst, dgst_len)) < 0)
	{
		memset(prv_tb, 0, sizeof(prv_tb));
		LOG_DEV_ERROR(ut, status);
		return (0);
	}
	
	memset(prv_tb, 0, sizeof(prv_tb));
	
	if ( (status = CRD_UT10XX_session_close(ut)) < 0 )
	{
		LOG_DEV_ERROR(ut, status);
		return (0);
	}
	
	if ( d2i_ECDSA_SIG(&sig, &sig_ptr, sig_len) == 0 )
	{
		LOG_ERROR("could not convert device signature to OpenSSL ECDSA_SIG form");
		return (0);
	}
	
	LOG_MSG("ECDSA signature with 'stoken_ecdsa_do_sign' finished successfully");
	
	return (sig);
}

static int
stoken_ecdsa_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, BIGNUM **r)
{
	return (1);
}

static int 
stoken_init(ENGINE *e)
{
	int status;
	
	if ( ((status = CRD_UT10XX_lib_init()) < 0)
		|| ((status = CRD_UT10XX_lib_find_devices()) < 0) 
		|| ((status = CRD_UT10XX_lib_actualize_devices()) < 0) )
	{
		LOG_LIB_ERROR(status);
		return (0);
	}
	
	LOG_MSG("engine initialization with 'stoken_init' finished successfully");
	
	return (1);
}

static int 
stoken_finish(ENGINE *e)
{
	int status;
	
	memset(stoken_token_name, 0, sizeof(stoken_token_name));
	memset(stoken_key_name, 0, sizeof(stoken_key_name));
	memset(stoken_user_pin, 0, sizeof(stoken_user_pin));
	
	if ( (status = CRD_UT10XX_lib_finish()) != 0 )
	{
		LOG_LIB_ERROR(status);
		return (0);
	}
	
	LOG_MSG("engine 'stoken' finished successfully");
	
	return (1);
}

static int
stoken_destroy(ENGINE *e)
{
	ERR_unload_STOKEN_strings();
	return (1);
}

static int
stoken_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)(void))
{
	int ret = 1;
	
	switch (cmd)
	{
		case STOKEN_CMD_SET_LOG_FILE:
			if (p == NULL || (stoken_log = fopen((const char *)p, "a+")) == NULL)
				ret = 0;
			break;
		
		case STOKEN_CMD_SET_TOKEN_NAME:
			if (p == NULL)
				ret = 0;
			else
				strncpy(stoken_token_name, (const char *)p, sizeof(stoken_token_name));
			
			break;
		
		case STOKEN_CMD_SET_KEY_NAME:
			if (p == NULL)
				ret = 0;
			else
				strncpy(stoken_key_name, (const char *)p, sizeof(stoken_key_name));
			
			break;
		
		case STOKEN_CMD_SET_USER_PIN:
			if (p == NULL)
				ret = 0;
			else
				strncpy(stoken_user_pin, (const char *)p, sizeof(stoken_user_pin));
			
			break;
		
		default:
			ret = 0;
			break;
	}
	
	return (ret);
}

static EVP_PKEY*
stoken_load_private_key(ENGINE *e, const char *key_id, UI_METHOD *ui_method, void *callback_data)
{
	unsigned char prv_tb[128];
	const unsigned char *prv_ptr = prv_tb;
	
	char dev_key_id[512];
	char *dev_name = 0;
	char *dev_serial = 0;
	char *key_name = 0;
	char *key_num_str = 0;
	
	CRD_UT10XX_t *ut = 0;
	const CRD_UT10XX_key_info_t *key = 0;
	const CRD_UT10XX_ecc_params_t *params = 0;
	int key_num = -1;
	int status = 0;
	
	EVP_PKEY *evp_key = 0;
	
	UI *ui = 0;
	
	
	memset(prv_tb, 0, sizeof(prv_tb));
	memset(dev_key_id, 0, sizeof(dev_key_id));
	
	if (key_id)
	{
		strncpy(dev_key_id, key_id, sizeof(dev_key_id) - 1);
	}
	else
	{
		strcpy(dev_key_id, stoken_token_name);
		dev_key_id[strlen(stoken_token_name)] = '/';
		strcpy(dev_key_id + strlen(stoken_token_name) + 1, stoken_key_name);
	}
	
	dev_name = dev_key_id;
	key_name = strchr(dev_key_id, '/');
	
	if (key_name)
	{
		*key_name++ = '\0';
	}
	else
	{
		LOG_ERROR("key name is not in the form <dev_name>/<key_name>.");
		return (0);
	}
	
	dev_serial = strrchr(dev_name, ':');
	
	if (dev_serial)
	{
		*dev_serial++ = '\0';
	}
	
	key_num_str = strrchr(key_name, ':');
	
	if (key_num_str)
	{
		*key_num_str++ = '\0';
	}
	
	if (10 < strlen(dev_name))
	{
		LOG_ERROR("device name is too long, it must have at most 10 characters");
		return (0);
	}
	
	if (22 < strlen(key_name))
	{
		LOG_ERROR("key name is too long, it must have at most 22 characters");
		return (0);
	}
	
	if ( ((status = CRD_UT10XX_lib_find_devices()) < 0)
		|| ((status = CRD_UT10XX_lib_actualize_devices()) < 0) )
	{
		LOG_LIB_ERROR(status);
		return (0);
	}
	
	if ( ((ut = CRD_UT10XX_find_by_serial_number(dev_serial)) == 0)
		|| ((ut = CRD_UT10XX_find_by_name(dev_name)) == 0) )
	{
		LOG_ERROR("there is no device with specified name");
		return (0);
	}
	
	if (strcmp(dev_name, ut->info.name) != 0)
	{
		LOG_ERROR("device serial number and name does not match");
		return (0);
	}
	
	if (key_num_str)
	{
		key_num = atoi(key_num_str);
	}
	else if ( (status = key_num = CRD_UT10XX_find_key_by_name(ut, key_name)) < 0 )
	{
		LOG_DEV_ERROR(ut, status);
		return (0);
	}
	
	if ((key_num < 0) || (ut->info.keys_num <= key_num))
	{
		LOG_ERROR("key indsex is out of range");
		return (0);
	}
	
	key = (ut->keys) + key_num;
	params = key->ecc_params;
	
	if (stoken_user_pin[0] != '\0')
	{
		/* If ui_method is NULL, then ENGINE will try stoken_user_pin table. */
		memcpy(prv_tb + 20, stoken_user_pin, 8);
	}
	else
	{
		/* Get User PIN, to access device sign functionality (use UI method). */
		ui = UI_new_method(ui_method);
		UI_add_input_string(ui, "User PIN:", 0, (char *)prv_tb + 20, 0, 8);
		UI_process(ui);
		UI_free(ui);
	}
	
	/* Check if PIN is correct. */
	if ( (status = CRD_UT10XX_session_open(ut)) < 0 )
	{
		LOG_LIB_ERROR(status);
		return (0);
	}
	
	if ( (status = CRD_UT10XX_cmd_try_pin(ut, (char *)prv_tb + 20, CRD_UT10XX_USER_PIN_ID)) < 0 )
	{
		LOG_DEV_ERROR(ut, status);
		CRD_UT10XX_session_close(ut);
		return (0);
	}
	
	CRD_UT10XX_session_close(ut);
	
	/*
	 * Create DER encoding of EC private key.
	 */
	
	/* ECPrivateKey SEQUENCE */
	prv_tb[0] = 0x30;
	prv_tb[1] = 36 + 2*params->field_octets + params->asn1_named[1];
	/* version INTEGER */
	prv_tb[2] = 0x02;
	prv_tb[3] = 0x01;
	prv_tb[4] = 0x01;
	/* privateKey OCTET STRING (0x01 | <device name> | <key num> | <user PIN>) */
	prv_tb[5] = 0x04;
	prv_tb[6] = 0x15;
	prv_tb[7] = 0x01;
	memcpy(prv_tb + 8, ut->info.name, 11);
	prv_tb[19] = key_num;
	/* parameters [0] ECDomainParameters */
	prv_tb[28] = 0xA0;
	prv_tb[29] = 2 + params->asn1_named[1];
	memcpy(prv_tb + 30, params->asn1_named, prv_tb[29]);
	/* publicKey [1] BIT STRING */
	prv_tb[30 + prv_tb[29]] = 0xA1;
	prv_tb[31 + prv_tb[29]] = 4 + 2*params->field_octets;
	prv_tb[32 + prv_tb[29]] = 0x03;
	prv_tb[33 + prv_tb[29]] = 2 + 2*params->field_octets;
	prv_tb[34 + prv_tb[29]] = 0x00;
	prv_tb[35 + prv_tb[29]] = 0x04;
	memcpy(prv_tb + 36 + prv_tb[29], key->pub_x, params->field_octets);
	memcpy(prv_tb + 36 + prv_tb[29] + params->field_octets, key->pub_y, 
		params->field_octets);
	
	/* Convert private key DER encoding to OpenSSL internal form. */
	if (!d2i_PrivateKey(EVP_PKEY_EC, &evp_key, &prv_ptr, 2 + prv_tb[1]))
	{
		LOG_ERROR("OpenSSL internal error");
		memset(prv_tb, 0, sizeof(prv_tb));
		return (0);
	}
	
	LOG_MSG("key loading with 'stoken_load_private_key' finished successfully");

	/* Clear buffers. */
	memset(prv_tb, 0, sizeof(prv_tb));
	
	return (evp_key);
}

static EVP_PKEY*
stoken_load_public_key(ENGINE *e, const char *key_id, UI_METHOD *ui_method, void *callback_data)
{
	unsigned char pub_tb[128];
	const unsigned char *pub_ptr = pub_tb;
	
	char dev_key_name[11 + 23];
	char *dev_name = stoken_token_name;
	char *key_name = stoken_key_name;
	
	CRD_UT10XX_t *ut = 0;
	const CRD_UT10XX_key_info_t *key = 0;
	const CRD_UT10XX_ecc_params_t *params = 0;
	int key_num = -1;
	int status = 0;
	
	EVP_PKEY *evp_key = 0;
	
	memset(pub_tb, 0, sizeof(pub_tb));
	memset(dev_key_name, 0, sizeof(dev_key_name));
	
	if (key_id)
	{
		strncpy(dev_key_name, key_id, sizeof(dev_key_name) - 1);
		dev_name = dev_key_name;
		key_name = strchr(dev_key_name, '/');
		
		if (*key_name) 
		{
			*key_name++ = '\0';
		}
		else 
		{
			LOG_ERROR("key name is not in the form <dev_name>/<key_name>, where "
				"<dev_name> has at most 10 characters and <key_name> has at "
				"most 22 characters");
			return (0);
		}
		
		if (10 < strlen(dev_name))
		{
			LOG_ERROR("device name is too long, it must have at most 10 characters");
			return (0);
		}
		
		if (22 < strlen(key_name))
		{
			LOG_ERROR("key name is too long, it must have at most 22 characters");
			return (0);
		}
	}
	
	CRD_UT10XX_lib_find_devices();
	CRD_UT10XX_lib_actualize_devices();
	
	if ( (ut = CRD_UT10XX_find_by_name(dev_name)) == 0 )
	{
		LOG_ERROR("there is no device with specified name");
		return (0);
	}
	
	if ( (status = key_num = CRD_UT10XX_find_key_by_name(ut, key_name)) < 0 )
	{
		LOG_DEV_ERROR(ut, status);
		return (0);
	}
	
	key = (ut->keys) + key_num;
	params = key->ecc_params;
	
	/*
	 * Create DER encoding of EC public key.
	 */
	
	/* SubjectPublicKeyInfo SEQUENCE */
	pub_tb[0] = 0x30;
	pub_tb[1] = 6 + params->asn1_named_alg[1] + 2*params->field_octets;
	/* algorithm AlgorithmIdentifier */
	memcpy(pub_tb + 2, params->asn1_named_alg, 2 + params->asn1_named_alg[1]);
	/* subjectPublicKey BIT STRING */
	pub_tb[4 + pub_tb[3]] = 0x03;
	pub_tb[5 + pub_tb[3]] = 2 + 2*params->field_octets;
	pub_tb[6 + pub_tb[3]] = 0x00;
	pub_tb[7 + pub_tb[3]] = 0x04;
	memcpy(pub_tb + 8 + pub_tb[3], key->pub_x, params->field_octets);
	memcpy(pub_tb + 8 + pub_tb[3] + params->field_octets, key->pub_y, 
		params->field_octets);
	
	/* Convert public key DER encoding to OpenSSL internal form. */
	if (!d2i_PUBKEY(&evp_key, &pub_ptr, 2 + pub_tb[1]))
	{
		LOG_ERROR("OpenSSL internal error");
		return (0);
	}
	
	LOG_MSG("key loading with 'stoken_load_public_key' finished successfully");

	return (evp_key);
}

static int 
stokensoft_init(ENGINE *e)
{
	return (1);
}

static int 
stokensoft_finish(ENGINE *e)
{
	memset(stokensoft_user_pin, 0, sizeof(stokensoft_user_pin));
	return (1);
}

static int 
stokensoft_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)(void))
{
	int ret = 1;
	
	switch (cmd)
	{
		case STOKENSOFT_CMD_SET_USER_PIN:
			if (p == NULL)
				ret = 0;
			else
				strncpy(stokensoft_user_pin, (const char *)p, sizeof(stokensoft_user_pin));
			
			break;
		
		default:
			ret = 0;
			break;
	}
	
	return (ret);
}

static EVP_PKEY*
stokensoft_load_private_key(ENGINE *e, const char *key_id, UI_METHOD *ui_method, void *callback_data)
{
	BIO *in = 0;
	EVP_PKEY *key = 0;
	
	if ((in = BIO_new_file(key_id, "r")) == 0) {
		STOKENerr(STOKEN_F_STOKENSOFT_LOAD_PRIVATE_KEY, STOKEN_R_COULD_NOT_READ_KEY_FILE);
		return 0;
	}
	
	key = PEM_read_bio_PrivateKey(in, 0, 0, stokensoft_user_pin);
	
	BIO_free(in);
	
	return key;
}

#ifndef OPENSSL_NO_ECDSA
static const char *SN_ecdsaWithSha1 = "ecdsa-with-sha1";
static const char *LN_ecdsaWithSha1 = "ecdsa-with-sha1";
static const char *OBJ_ecdsaWithSha1 = "1.2.840.10045.4.1";

static int 
init(EVP_MD_CTX *ctx)
{
	return SHA1_Init(ctx->md_data);
}

static int 
update(EVP_MD_CTX *ctx, const void *data, size_t count)
{
	return SHA1_Update(ctx->md_data, data, count);
}

static int 
final(EVP_MD_CTX *ctx, unsigned char *md)
{
	return SHA1_Final(md, ctx->md_data);
}

static EVP_MD ecdsaWithSha1_md = 
{
	NID_undef,
	NID_undef,
	SHA_DIGEST_LENGTH,
	0,
	init,
	update,
	final,
	NULL,
	NULL,
	EVP_PKEY_ECDSA_method,
	SHA_CBLOCK,
	sizeof(EVP_MD *) + sizeof(SHA_CTX),
};

#ifndef OPENSSL_NO_SHA256
static const char *SN_ecdsaWithSha224 = "ecdsa-with-sha224";
static const char *LN_ecdsaWithSha224 = "ecdsa-with-sha224";
static const char *OBJ_ecdsaWithSha224 = "1.2.840.10045.4.3.1";

static const char *SN_ecdsaWithSha256 = "ecdsa-with-sha256";
static const char *LN_ecdsaWithSha256 = "ecdsa-with-sha256";
static const char *OBJ_ecdsaWithSha256 = "1.2.840.10045.4.3.2";

static int 
init224(EVP_MD_CTX *ctx)
{
	return SHA224_Init(ctx->md_data);
}

static int 
init256(EVP_MD_CTX *ctx)
{
	return SHA256_Init(ctx->md_data);
}

static int 
update256(EVP_MD_CTX *ctx, const void *data, size_t count)
{
	return SHA256_Update(ctx->md_data, data, count);
}

static int 
final256(EVP_MD_CTX *ctx, unsigned char *md)
{
	return SHA256_Final(md,ctx->md_data);
}

static EVP_MD ecdsaWithSha224_md =
{
	NID_undef,
	NID_undef,
	SHA224_DIGEST_LENGTH,
	0,
	init224,
	update256,
	final256,
	NULL,
	NULL,
	EVP_PKEY_ECDSA_method,
	SHA256_CBLOCK,
	sizeof(EVP_MD *) + sizeof(SHA256_CTX),
};

static EVP_MD ecdsaWithSha256_md =
{
	NID_undef,
	NID_undef,
	SHA256_DIGEST_LENGTH,
	0,
	init256,
	update256,
	final256,
	NULL,
	NULL,
	EVP_PKEY_ECDSA_method,
	SHA256_CBLOCK,
	sizeof(EVP_MD *) + sizeof(SHA256_CTX),
};

#endif /* ifndef OPENSSL_NO_SHA256 */

#ifndef OPENSSL_NO_SHA512
static const char *SN_ecdsaWithSha384 = "ecdsa-with-sha384";
static const char *LN_ecdsaWithSha384 = "ecdsa-with-sha384";
static const char *OBJ_ecdsaWithSha384 = "1.2.840.10045.4.3.3";

static const char *SN_ecdsaWithSha512 = "ecdsa-with-sha512";
static const char *LN_ecdsaWithSha512 = "ecdsa-with-sha512";
static const char *OBJ_ecdsaWithSha512 = "1.2.840.10045.4.3.4";

static int 
init384(EVP_MD_CTX *ctx)
{
	return SHA384_Init(ctx->md_data);
}

static int 
init512(EVP_MD_CTX *ctx)
{
	return SHA512_Init(ctx->md_data);
}

static int 
update512(EVP_MD_CTX *ctx, const void *data, size_t count)
{
	return SHA512_Update(ctx->md_data, data, count);
}

static int 
final512(EVP_MD_CTX *ctx, unsigned char *md)
{
	return SHA512_Final(md, ctx->md_data);
}

static EVP_MD ecdsaWithSha384_md =
{
	NID_undef,
	NID_undef,
	SHA384_DIGEST_LENGTH,
	0,
	init384,
	update512,
	final512,
	NULL,
	NULL,
	EVP_PKEY_ECDSA_method,
	SHA512_CBLOCK,
	sizeof(EVP_MD *) + sizeof(SHA512_CTX),
};

static EVP_MD ecdsaWithSha512_md =
{
	NID_undef,
	NID_undef,
	SHA512_DIGEST_LENGTH,
	0,
	init512,
	update512,
	final512,
	NULL,
	NULL,
	EVP_PKEY_ECDSA_method,
	SHA512_CBLOCK,
	sizeof(EVP_MD *) + sizeof(SHA512_CTX),
};

#endif /* ifndef OPENSSL_NO_SHA512 */

#define STOKEN_add_digest(nid, name) \
	if ( (nid = OBJ_create(OBJ_##name, SN_##name, LN_##name)) != NID_undef ) \
	{ \
		name##_md.type = nid; \
		name##_md.pkey_type = nid; \
		EVP_add_digest(&name##_md); \
	}

static int
stoken_add_all_digests(void)
{
	int nid;
	
	STOKEN_add_digest(nid, ecdsaWithSha1);
	STOKEN_add_digest(nid, ecdsaWithSha224);
	STOKEN_add_digest(nid, ecdsaWithSha256);
	STOKEN_add_digest(nid, ecdsaWithSha384);
	STOKEN_add_digest(nid, ecdsaWithSha512);
	
	return (1);
}

#endif /* ifndef OPENSSL_NO_ECDSA */

#endif /* ifndef NO_OSSL_ENGINE_INTERFACE */
