/*
 * Oberthur specific operation for PKCS #15 initialization
 *
 * Copyright (C) 2002  Juha Yrjölä <juha.yrjola@iki.fi>
 * Copyright (C) 2009  Viktor Tarasov <viktor.tarasov@opentrust.com>,
 *                     OpenTrust <www.opentrust.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 Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "pkcs15-oberthur.h"
#include <sys/types.h>
#include <ctype.h>

#include "libopensc/opensc.h"
#include "libopensc/cardctl.h"
#include "libopensc/log.h"
#include "profile.h"
#include "pkcs15-init.h"

#define COSM_TITLE "OberthurAWP"

#define TLV_TYPE_V	0
#define TLV_TYPE_LV	1
#define TLV_TYPE_TLV	2

/* Should be greater then SC_PKCS15_TYPE_CLASS_MASK */
#define SC_DEVICE_SPECIFIC_TYPE	 0x1000

#define COSM_TYPE_PRKEY_RSA (SC_DEVICE_SPECIFIC_TYPE | SC_PKCS15_TYPE_PRKEY_RSA)
#define COSM_TYPE_PUBKEY_RSA (SC_DEVICE_SPECIFIC_TYPE | SC_PKCS15_TYPE_PUBKEY_RSA)

#define COSM_TOKEN_FLAG_PRN_GENERATION		0x01
#define COSM_TOKEN_FLAG_LOGIN_REQUIRED		0x04
#define COSM_TOKEN_FLAG_USER_PIN_INITIALIZED	0x08
#define COSM_TOKEN_FLAG_TOKEN_INITIALIZED	0x0400

static int cosm_create_reference_data(struct sc_profile *, struct sc_pkcs15_card *,
		struct sc_pkcs15_auth_info *, const unsigned char *, size_t,
		const unsigned char *, size_t);
static int cosm_update_pin(struct sc_profile *, struct sc_pkcs15_card *,
		struct sc_pkcs15_auth_info *, const unsigned char *, size_t,
		const unsigned char *, size_t);

static int
cosm_write_tokeninfo (struct sc_pkcs15_card *p15card, struct sc_profile *profile,
		char *label, unsigned flags)
{
	struct sc_context *ctx;
	struct sc_file *file = NULL;
	int rv;
	size_t sz;
	char *buffer = NULL;

	if (!p15card || !p15card->card || !profile)
		return SC_ERROR_INVALID_ARGUMENTS;

	ctx = p15card->card->ctx;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	if (sc_profile_get_file(profile, COSM_TITLE"-token-info", &file)) {
		rv = SC_ERROR_INCONSISTENT_PROFILE;
		SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE, rv, "Cannot find "COSM_TITLE"-token-info");
	}

	if (file->size < 16) {
		rv = SC_ERROR_INCONSISTENT_PROFILE;
		SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE, rv, "Insufficient size of the "COSM_TITLE"-token-info file");
	}

	buffer = calloc(1, file->size);
	if (!buffer) {
		rv = SC_ERROR_OUT_OF_MEMORY;
		SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE, rv, "Allocation error in cosm_write_tokeninfo()");
	}

	if (label)
		strncpy(buffer, label, file->size - 4);
	else if (p15card->tokeninfo->label)
		snprintf(buffer, file->size - 4, "%s", p15card->tokeninfo->label);
	else if (profile->p15_spec && profile->p15_spec->tokeninfo->label)
		snprintf(buffer, file->size - 4, "%s", profile->p15_spec->tokeninfo->label);
	else
		snprintf(buffer, file->size - 4, "OpenSC-Token");

	sz = strlen(buffer);
	if (sz < file->size - 4)
		memset(buffer + sz, ' ', file->size - sz);

	sc_log(ctx,  "cosm_write_tokeninfo() token label '%s'; oberthur flags 0x%X", buffer, flags);

	memset(buffer + file->size - 4, 0, 4);
	*(buffer + file->size - 1) = flags & 0xFF;
	*(buffer + file->size - 2) = (flags >> 8) & 0xFF;

	rv = sc_pkcs15init_update_file(profile, p15card, file, buffer, file->size);
	if (rv > 0)
		rv = 0;

err:
	sc_file_free(file);
	free(buffer);
	LOG_FUNC_RETURN(ctx, rv);
}


int
cosm_delete_file(struct sc_pkcs15_card *p15card, struct sc_profile *profile,
		struct sc_file *df)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_path  path;
	struct sc_file  *parent;
	int rv = 0;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(ctx,  "id %04X", df->id);
	if (df->type==SC_FILE_TYPE_DF)   {
		rv = sc_pkcs15init_authenticate(profile, p15card, df, SC_AC_OP_DELETE);
		LOG_TEST_RET(ctx, rv, "Cannot authenticate SC_AC_OP_DELETE");
	}

	/* Select the parent DF */
	path = df->path;
	if (path.len < 2) {
		LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS);
	}
	path.len -= 2;

	rv = sc_select_file(p15card->card, &path, &parent);
	LOG_TEST_RET(ctx, rv, "Cannot select parent");

	rv = sc_pkcs15init_authenticate(profile, p15card, parent, SC_AC_OP_DELETE);
	sc_file_free(parent);
	LOG_TEST_RET(ctx, rv, "Cannot authenticate SC_AC_OP_DELETE");

	memset(&path, 0, sizeof(path));
	path.type = SC_PATH_TYPE_FILE_ID;
	path.value[0] = df->id >> 8;
	path.value[1] = df->id & 0xFF;
	path.len = 2;

	rv = sc_delete_file(p15card->card, &path);

	LOG_FUNC_RETURN(ctx, rv);
}


/*
 * Erase the card
 */
static int
cosm_erase_card(struct sc_profile *profile, struct sc_pkcs15_card *p15card)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_file  *df = profile->df_info->file, *dir;
	int rv;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	/* Delete EF(DIR). This may not be very nice
	 * against other applications that use this file, but
	 * extremely useful for testing :)
	 * Note we need to delete if before the DF because we create
	 * it *after* the DF.
	 * */
	if (sc_profile_get_file(profile, "DIR", &dir) >= 0) {
		sc_log(ctx,  "erase file dir %04X",dir->id);
		rv = cosm_delete_file(p15card, profile, dir);
		sc_file_free(dir);
		if (rv < 0 && rv != SC_ERROR_FILE_NOT_FOUND)
			goto done;
	}

	sc_log(ctx,  "erase file ddf %04X",df->id);
	rv = cosm_delete_file(p15card, profile, df);
	if (rv < 0 && rv != SC_ERROR_FILE_NOT_FOUND)
		goto done;

	if (sc_profile_get_file(profile, "private-DF", &dir) >= 0) {
		sc_log(ctx,  "erase file dir %04X",dir->id);
		rv = cosm_delete_file(p15card, profile, dir);
		sc_file_free(dir);
		if (rv < 0 && rv != SC_ERROR_FILE_NOT_FOUND)
			goto done;
	}

	if (sc_profile_get_file(profile, "public-DF", &dir) >= 0) {
		sc_log(ctx,  "erase file dir %04X",dir->id);
		rv = cosm_delete_file(p15card, profile, dir);
		sc_file_free(dir);
		if (rv < 0 && rv != SC_ERROR_FILE_NOT_FOUND)
			goto done;
	}

	rv = sc_profile_get_file(profile, COSM_TITLE"-AppDF", &dir);
	if (!rv) {
		sc_log(ctx,  "delete %s; r %i", COSM_TITLE"-AppDF", rv);
		rv = cosm_delete_file(p15card, profile, dir);
		sc_file_free(dir);
	}

	sc_free_apps(p15card->card);
done:
	if (rv == SC_ERROR_FILE_NOT_FOUND)
		rv = 0;

	LOG_FUNC_RETURN(ctx, rv);
}


static int
cosm_create_dir(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_file *df)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_file *file = NULL;
	size_t ii;
	int rv;
	static const char *create_dfs[] = {
		COSM_TITLE"-AppDF",
		"private-DF",
		"public-DF",
		COSM_TITLE"-token-info",
		COSM_TITLE"-puk-file",
		COSM_TITLE"-container-list",
		COSM_TITLE"-public-list",
		COSM_TITLE"-private-list",
		NULL
	};

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);

	/* Oberthur AWP file system is expected.*/
	/* Create private objects DF */
	for (ii = 0; create_dfs[ii]; ii++)   {
		if (sc_profile_get_file(profile, create_dfs[ii], &file))   {
			sc_log(ctx,  "Inconsistent profile: cannot find %s", create_dfs[ii]);
			LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_PROFILE, "Profile do not contains Oberthur AWP file");
		}

		rv = sc_pkcs15init_create_file(profile, p15card, file);
		sc_file_free(file);
		if (rv != SC_ERROR_FILE_ALREADY_EXISTS)
			LOG_TEST_RET(ctx, rv, "Failed to create Oberthur AWP file");
	}

	rv = cosm_write_tokeninfo(p15card, profile, NULL,
		COSM_TOKEN_FLAG_TOKEN_INITIALIZED | COSM_TOKEN_FLAG_PRN_GENERATION);

	LOG_FUNC_RETURN(ctx, rv);
}


static int
cosm_create_reference_data(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_pkcs15_auth_info *ainfo,
		const unsigned char *pin, size_t pin_len,
		const unsigned char *puk, size_t puk_len )
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_card *card = p15card->card;
	struct sc_pkcs15_auth_info profile_auth_pin = {0}, profile_auth_puk = {0};
	struct sc_cardctl_oberthur_createpin_info args;
	int rv;
	unsigned char oberthur_puk[16] = {
		0x6F, 0x47, 0xD9, 0x88, 0x4B, 0x6F, 0x9D, 0xC5,
		0x78, 0x33, 0x79, 0x8F, 0x5B, 0x7D, 0xE1, 0xA5
	};

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(ctx,
		 "pin lens %"SC_FORMAT_LEN_SIZE_T"u/%"SC_FORMAT_LEN_SIZE_T"u",
		 pin_len, puk_len);
	if (!pin || pin_len>0x40)
		return SC_ERROR_INVALID_ARGUMENTS;
	if (puk && !puk_len)
		return SC_ERROR_INVALID_ARGUMENTS;
	if (ainfo->auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return SC_ERROR_OBJECT_NOT_VALID;

	rv = sc_select_file(card, &ainfo->path, NULL);
	LOG_TEST_RET(ctx, rv, "Cannot select file");

	sc_profile_get_pin_info(profile, SC_PKCS15INIT_USER_PIN, &profile_auth_pin);
	sc_profile_get_pin_info(profile, SC_PKCS15INIT_USER_PUK, &profile_auth_puk);

	memset(&args, 0, sizeof(args));
	args.type = SC_AC_CHV;
	args.ref = ainfo->attrs.pin.reference;
	args.pin = pin;
	args.pin_len = pin_len;

	if (!(ainfo->attrs.pin.flags & SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN))   {
		args.pin_tries = profile_auth_pin.tries_left;
		if (profile_auth_puk.tries_left > 0)   {
			args.puk = oberthur_puk;
			args.puk_len = sizeof(oberthur_puk);
			args.puk_tries = 5;
		}
	}
	else   {
		args.pin_tries = profile_auth_puk.tries_left;
	}

	rv = sc_card_ctl(card, SC_CARDCTL_OBERTHUR_CREATE_PIN, &args);
	LOG_TEST_RET(ctx, rv, "'CREATE_PIN' card specific command failed");

	if (!(ainfo->attrs.pin.flags & SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN)
			&& (profile_auth_puk.tries_left > 0))   {
	        struct sc_file *file = NULL;

		if (sc_profile_get_file(profile, COSM_TITLE"-puk-file", &file))
			LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_PROFILE, "Cannot find PUKFILE");

		rv = sc_pkcs15init_update_file(profile, p15card, file, oberthur_puk, sizeof(oberthur_puk));
		LOG_TEST_RET(ctx, rv, "Failed to update pukfile");

		sc_file_free(file);
	}

	LOG_FUNC_RETURN(ctx, rv);
}


/*
 * Update PIN
 */
static int
cosm_update_pin(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_pkcs15_auth_info *ainfo, const unsigned char *pin, size_t pin_len,
		const unsigned char *puk, size_t puk_len )
{
	struct sc_context *ctx = p15card->card->ctx;
	int rv;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	if (ainfo->auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return SC_ERROR_OBJECT_NOT_VALID;

	sc_log(ctx,  "ref %i; flags 0x%X", ainfo->attrs.pin.reference, ainfo->attrs.pin.flags);

	if (ainfo->attrs.pin.flags & SC_PKCS15_PIN_FLAG_SO_PIN)   {
		if (ainfo->attrs.pin.reference != 4)
			LOG_TEST_RET(ctx, SC_ERROR_INVALID_PIN_REFERENCE, "cosm_update_pin() invalid SOPIN reference");
		sc_log(ctx,  "Update SOPIN ignored");
		rv = SC_SUCCESS;
	}
	else   {
		rv = cosm_create_reference_data(profile, p15card, ainfo, pin, pin_len, puk, puk_len);
		LOG_TEST_RET(ctx, rv, "cosm_update_pin() failed to change PIN");

		rv = cosm_write_tokeninfo(p15card, profile, NULL,
			COSM_TOKEN_FLAG_TOKEN_INITIALIZED
			| COSM_TOKEN_FLAG_PRN_GENERATION
			| COSM_TOKEN_FLAG_LOGIN_REQUIRED
			| COSM_TOKEN_FLAG_USER_PIN_INITIALIZED);
		LOG_TEST_RET(ctx, rv, "cosm_update_pin() failed to update tokeninfo");
	}

	LOG_FUNC_RETURN(ctx, rv);
}


static int
cosm_select_pin_reference(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_pkcs15_auth_info *auth_info)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_pkcs15_pin_attributes *pin_attrs;
	struct sc_file *pinfile;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	if (auth_info->auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return SC_ERROR_OBJECT_NOT_VALID;

	pin_attrs = &auth_info->attrs.pin;

	sc_log(ctx,  "ref %i; flags %X", pin_attrs->reference, pin_attrs->flags);
	if (sc_profile_get_file(profile, COSM_TITLE "-AppDF", &pinfile) < 0) {
		sc_log(ctx,  "Profile doesn't define \"%s\"", COSM_TITLE "-AppDF");
		return SC_ERROR_INCONSISTENT_PROFILE;
	}

	if (pin_attrs->flags & SC_PKCS15_PIN_FLAG_LOCAL)
		auth_info->path = pinfile->path;

	sc_file_free(pinfile);

	if (pin_attrs->reference <= 0)   {
		if (pin_attrs->flags & SC_PKCS15_PIN_FLAG_SO_PIN)
			pin_attrs->reference = 4;
		else if (pin_attrs->flags & SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN)
			pin_attrs->reference = 4;
		else
			pin_attrs->reference = 1;

		if (pin_attrs->flags & SC_PKCS15_PIN_FLAG_LOCAL)
			pin_attrs->reference |= 0x80;
	}

	LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}


/*
 * Store a PIN
 */
static int
cosm_create_pin(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_file *df, struct sc_pkcs15_object *pin_obj,
		const unsigned char *pin, size_t pin_len,
		const unsigned char *puk, size_t puk_len)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_pkcs15_auth_info *auth_info = (struct sc_pkcs15_auth_info *) pin_obj->data;
	struct sc_pkcs15_pin_attributes *pin_attrs;
	struct sc_file *pin_file;
	int rv = 0;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	if (auth_info->auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return SC_ERROR_OBJECT_NOT_VALID;

	pin_attrs = &auth_info->attrs.pin;

	sc_log(ctx,  "create '%.*s'; ref 0x%X; flags %X", (int) sizeof pin_obj->label, pin_obj->label, pin_attrs->reference, pin_attrs->flags);
	if (sc_profile_get_file(profile, COSM_TITLE "-AppDF", &pin_file) < 0)
		LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_PROFILE, "\""COSM_TITLE"-AppDF\" not defined");

	if (pin_attrs->flags & SC_PKCS15_PIN_FLAG_LOCAL)
		auth_info->path = pin_file->path;

	sc_file_free(pin_file);

	if (pin_attrs->flags & SC_PKCS15_PIN_FLAG_SO_PIN)   {
		if (pin_attrs->flags & SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN)   {
			LOG_TEST_RET(ctx, SC_ERROR_NOT_SUPPORTED, "SOPIN unblocking is not supported");
		}
		else   {
			if (pin_attrs->reference != 4)
				LOG_TEST_RET(ctx, SC_ERROR_INVALID_PIN_REFERENCE, "Invalid SOPIN reference");
		}
	}
	else {
		if (pin_attrs->flags & SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN)   {
			if (pin_attrs->reference != 0x84)
				LOG_TEST_RET(ctx, SC_ERROR_INVALID_PIN_REFERENCE, "Invalid User PUK reference");
		}
		else   {
			if (pin_attrs->reference != 0x81)
				LOG_TEST_RET(ctx, SC_ERROR_INVALID_PIN_REFERENCE, "Invalid User PIN reference");
		}
	}

	if (pin && pin_len)   {
		rv = cosm_update_pin(profile, p15card, auth_info, pin, pin_len,  puk, puk_len);
		LOG_TEST_RET(ctx, rv, "Update PIN failed");
	}

	LOG_FUNC_RETURN(ctx, rv);
}


/*
 * Allocate a file
 */
static int
cosm_new_file(struct sc_profile *profile, struct sc_card *card,
		unsigned int type, unsigned int num, struct sc_file **out)
{
	struct sc_file	*file;
	const char *_template = NULL, *desc = NULL;
	unsigned int structure = 0xFFFFFFFF;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(card->ctx,  "cosm_new_file() type %X; num %i",type, num);
	while (1) {
		switch (type) {
		case SC_PKCS15_TYPE_PRKEY_RSA:
		case COSM_TYPE_PRKEY_RSA:
			desc = "RSA private key";
			_template = "template-private-key";
			structure = SC_CARDCTL_OBERTHUR_KEY_RSA_CRT;
			break;
		case SC_PKCS15_TYPE_PUBKEY_RSA:
		case COSM_TYPE_PUBKEY_RSA:
			desc = "RSA public key";
			_template = "template-public-key";
			structure = SC_CARDCTL_OBERTHUR_KEY_RSA_PUBLIC;
			break;
		case SC_PKCS15_TYPE_CERT:
			desc = "certificate";
			_template = "template-certificate";
			break;
		case SC_PKCS15_TYPE_DATA_OBJECT:
			desc = "data object";
			_template = "template-public-data";
			break;
		}
		if (_template)
			break;
		/* If this is a specific type such as
		 * SC_PKCS15_TYPE_CERT_FOOBAR, fall back to
		 * the generic class (SC_PKCS15_TYPE_CERT)
		 */
		if (!(type & ~SC_PKCS15_TYPE_CLASS_MASK)) {
			sc_log(card->ctx,  "File type %X not supported by card driver",
				type);
			return SC_ERROR_INVALID_ARGUMENTS;
		}
		type &= SC_PKCS15_TYPE_CLASS_MASK;
	}

	sc_log(card->ctx,  "cosm_new_file() template %s; num %i",_template, num);
	if (sc_profile_get_file(profile, _template, &file) < 0) {
		sc_log(card->ctx,  "Profile doesn't define %s template '%s'",
				desc, _template);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
	}

	file->id |= (num & 0xFF);
	if (file->path.len) {
		file->path.value[file->path.len - 1] |= (num & 0xFF);
	}
	if (file->type == SC_FILE_TYPE_INTERNAL_EF)   {
		file->ef_structure = structure;
	}

	sc_log(card->ctx,
		 "cosm_new_file() file size %"SC_FORMAT_LEN_SIZE_T"u; ef type %i/%i; id %04X",
		 file->size, file->type, file->ef_structure, file->id);
	*out = file;

	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}


static int
cosm_get_temporary_public_key_file(struct sc_card *card,
		struct sc_file *prvkey_file, struct sc_file **pubkey_file)
{
	struct sc_context *ctx = card->ctx;
	const struct sc_acl_entry *entry = NULL;
	struct sc_file *file = NULL;
	int rv;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	if (!pubkey_file || !prvkey_file)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	file = sc_file_new();
	if (!file)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);

	file->status = SC_FILE_STATUS_ACTIVATED;
        file->type = SC_FILE_TYPE_INTERNAL_EF;
	file->ef_structure = SC_CARDCTL_OBERTHUR_KEY_RSA_PUBLIC;
	file->id = 0x1012;
	memcpy(&file->path, &prvkey_file->path, sizeof(file->path));
        file->path.value[file->path.len - 2] = 0x10;
	file->path.value[file->path.len - 1] = 0x12;
	file->size = prvkey_file->size;

	entry = sc_file_get_acl_entry(prvkey_file, SC_AC_OP_UPDATE);
	if (!entry) {
		sc_file_free(file);
		LOG_TEST_RET(ctx, SC_ERROR_OBJECT_NOT_FOUND, "Failed to find ACL entry");
	}
	rv = sc_file_add_acl_entry(file, SC_AC_OP_UPDATE, entry->method, entry->key_ref);
	if (!rv)
		rv = sc_file_add_acl_entry(file, SC_AC_OP_PSO_ENCRYPT, SC_AC_NONE, 0);
	if (!rv)
		rv = sc_file_add_acl_entry(file, SC_AC_OP_PSO_VERIFY_SIGNATURE, SC_AC_NONE, 0);
	if (!rv)
		rv = sc_file_add_acl_entry(file, SC_AC_OP_EXTERNAL_AUTHENTICATE, SC_AC_NONE, 0);
	if (rv < 0)
		sc_file_free(file);
	LOG_TEST_RET(ctx, rv, "Failed to add ACL entry to the temporary public key file");

	*pubkey_file = file;

	LOG_FUNC_RETURN(card->ctx, rv);
}


static int
cosm_generate_key(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_pkcs15_object *object,
		struct sc_pkcs15_pubkey *pubkey)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_pkcs15_prkey_info *key_info = (struct sc_pkcs15_prkey_info *)object->data;
	struct sc_cardctl_oberthur_genkey_info args;
	struct sc_file *prkf = NULL, *tmpf = NULL;
	struct sc_path path;
	int rv = 0;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);

	if (object->type != SC_PKCS15_TYPE_PRKEY_RSA)
		LOG_TEST_RET(ctx, SC_ERROR_NOT_SUPPORTED, "Generate key failed: RSA only supported");

	path = key_info->path;
	path.len -= 2;

	rv = sc_select_file(p15card->card, &path, &tmpf);
	LOG_TEST_RET(ctx, rv, "Cannot generate key: failed to select private object DF");

	rv = sc_pkcs15init_authenticate(profile, p15card, tmpf, SC_AC_OP_CRYPTO);
	if (rv != SC_SUCCESS) {
		sc_file_free(tmpf);
		LOG_TEST_RET(ctx, rv, "Cannot generate key: 'CRYPTO' authentication failed");
	}

	rv = sc_pkcs15init_authenticate(profile, p15card, tmpf, SC_AC_OP_CREATE);
	sc_file_free(tmpf);
	tmpf = NULL;
	LOG_TEST_RET(ctx, rv, "Cannot generate key: 'CREATE' authentication failed");

	rv = sc_select_file(p15card->card, &key_info->path, &prkf);
	LOG_TEST_RET(ctx, rv, "Failed to generate key: cannot select private key file");

	/* In the private key DF create the temporary public RSA file. */
	rv = cosm_get_temporary_public_key_file(p15card->card, prkf, &tmpf);
	if (rv != SC_SUCCESS) {
		sc_file_free(prkf);
		LOG_TEST_RET(ctx, rv, "Error while getting temporary public key file");
	}

	rv = sc_pkcs15init_create_file(profile, p15card, tmpf);
	if (rv != SC_SUCCESS) {
		sc_file_free(prkf);
		sc_file_free(tmpf);
		LOG_TEST_RET(ctx, rv, "cosm_generate_key() failed to create temporary public key EF");
	}

	memset(&args, 0, sizeof(args));
	args.id_prv = prkf->id;
	args.id_pub = tmpf->id;
	args.exponent = 0x10001;
	args.key_bits = key_info->modulus_length;
	args.pubkey_len = key_info->modulus_length / 8;
	args.pubkey = malloc(key_info->modulus_length / 8);
	if (!args.pubkey) {
		sc_file_free(prkf);
		sc_file_free(tmpf);
		LOG_TEST_RET(ctx, SC_ERROR_OUT_OF_MEMORY, "cosm_generate_key() cannot allocate pubkey");
	}

	rv = sc_card_ctl(p15card->card, SC_CARDCTL_OBERTHUR_GENERATE_KEY, &args);
	if (rv != SC_SUCCESS) {
		sc_file_free(prkf);
		sc_file_free(tmpf);
		free(args.pubkey);
	}
	LOG_TEST_RET(ctx, rv, "cosm_generate_key() CARDCTL_OBERTHUR_GENERATE_KEY failed");

	/* extract public key */
	pubkey->algorithm = SC_ALGORITHM_RSA;
	pubkey->u.rsa.modulus.len   = key_info->modulus_length / 8;
	pubkey->u.rsa.modulus.data  = malloc(key_info->modulus_length / 8);
	if (!pubkey->u.rsa.modulus.data) {
		sc_file_free(prkf);
		sc_file_free(tmpf);
		free(args.pubkey);
		LOG_TEST_RET(ctx, SC_ERROR_OUT_OF_MEMORY, "cosm_generate_key() cannot allocate modulus buf");
	}

	/* FIXME and if the exponent length is not 3? */
	pubkey->u.rsa.exponent.len  = 3;
	pubkey->u.rsa.exponent.data = malloc(3);
	if (!pubkey->u.rsa.exponent.data) {
		sc_file_free(prkf);
		sc_file_free(tmpf);
		free(args.pubkey);
		free(pubkey->u.rsa.modulus.data);
		LOG_TEST_RET(ctx, SC_ERROR_OUT_OF_MEMORY, "cosm_generate_key() cannot allocate exponent buf");
	}
	memcpy(pubkey->u.rsa.exponent.data, "\x01\x00\x01", 3);
	memcpy(pubkey->u.rsa.modulus.data, args.pubkey, args.pubkey_len);

	key_info->key_reference = prkf->path.value[prkf->path.len - 1] & 0xFF;
	key_info->path = prkf->path;

	sc_log(ctx,  "cosm_generate_key() now delete temporary public key");
	rv =  cosm_delete_file(p15card, profile, tmpf);

	sc_file_free(tmpf);
	sc_file_free(prkf);
	free(args.pubkey);

	LOG_FUNC_RETURN(ctx, rv);
}


/*
 * Create private key file
 */
static int
cosm_create_key(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_pkcs15_object *object)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_pkcs15_prkey_info *key_info = (struct sc_pkcs15_prkey_info *)object->data;
	struct sc_file *file = NULL;
	int rv = 0;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	if (object->type != SC_PKCS15_TYPE_PRKEY_RSA)
		LOG_TEST_RET(ctx, SC_ERROR_NOT_SUPPORTED, "Create key failed: RSA only supported");

	if (key_info->path.len < 2)
		LOG_TEST_RET(ctx, SC_ERROR_OBJECT_NOT_VALID, "The path needs to be at least to bytes long");

	sc_log(ctx,  "create private key ID:%s",  sc_pkcs15_print_id(&key_info->id));
	/* Here, the path of private key file should be defined.
	 * Nevertheless, we need to instantiate private key to get the ACLs. */
	rv = cosm_new_file(profile, p15card->card, SC_PKCS15_TYPE_PRKEY_RSA, key_info->key_reference, &file);
	LOG_TEST_RET(ctx, rv, "Cannot create key: failed to allocate new key object");

	file->size = key_info->modulus_length;
	memcpy(&file->path, &key_info->path, sizeof(file->path));
	file->id = file->path.value[file->path.len - 2] * 0x100
				+ file->path.value[file->path.len - 1];

	sc_log(ctx,  "Path of private key file to create %s", sc_print_path(&file->path));

	rv = sc_select_file(p15card->card, &file->path, NULL);
	if (rv == 0)   {
		rv = cosm_delete_file(p15card, profile, file);
		SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE, rv, "Failed to delete private key file");
	}
	else if (rv != SC_ERROR_FILE_NOT_FOUND)    {
		SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE, rv, "Select private key file error");
	}

	rv = sc_pkcs15init_create_file(profile, p15card, file);
	SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE, rv, "Failed to create private key file");

	key_info->key_reference = file->path.value[file->path.len - 1];

err:
	sc_file_free(file);

	LOG_FUNC_RETURN(ctx, rv);
}


/*
 * Store a private key
 */
static int
cosm_store_key(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_pkcs15_object *object,
		struct sc_pkcs15_prkey *prkey)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_pkcs15_prkey_info *key_info = (struct sc_pkcs15_prkey_info *)object->data;
	struct sc_file *file = NULL;
	struct sc_cardctl_oberthur_updatekey_info update_info;
	int rv = 0;

	SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
	if (object->type != SC_PKCS15_TYPE_PRKEY_RSA || prkey->algorithm != SC_ALGORITHM_RSA)
		LOG_TEST_RET(ctx, SC_ERROR_NOT_SUPPORTED, "Store key failed: RSA only supported");

	sc_log(ctx,  "store key with ID:%s and path:%s", sc_pkcs15_print_id(&key_info->id),
		       	sc_print_path(&key_info->path));

	rv = sc_select_file(p15card->card, &key_info->path, &file);
	LOG_TEST_RET(ctx, rv, "Cannot store key: select key file failed");

	rv = sc_pkcs15init_authenticate(profile, p15card, file, SC_AC_OP_UPDATE);
	LOG_TEST_RET(ctx, rv, "No authorisation to store private key");

	if (key_info->id.len > sizeof(update_info.id))
		 LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS);

	memset(&update_info, 0, sizeof(update_info));
	update_info.type = SC_CARDCTL_OBERTHUR_KEY_RSA_CRT;
	update_info.data = (void *)&prkey->u.rsa;
	update_info.data_len = sizeof(void *);
	update_info.id_len = key_info->id.len;
	memcpy(update_info.id, key_info->id.value, update_info.id_len);

	rv = sc_card_ctl(p15card->card, SC_CARDCTL_OBERTHUR_UPDATE_KEY, &update_info);
	LOG_TEST_RET(ctx, rv, "Cannot update private key");

	sc_file_free(file);

	LOG_FUNC_RETURN(ctx, rv);
}


#ifdef ENABLE_OPENSSL
static int
cosm_emu_update_dir (struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_app_info *info)
{
	SC_FUNC_CALLED(p15card->card->ctx, 1);
	/* No DIR file in the native Oberthur card */
	SC_FUNC_RETURN(p15card->card->ctx, 1, SC_SUCCESS);
}


static int
cosm_emu_update_any_df(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		unsigned op, struct sc_pkcs15_object *object)
{
	struct sc_context *ctx = p15card->card->ctx;
	int rv = SC_ERROR_NOT_SUPPORTED;

	SC_FUNC_CALLED(ctx, 1);
	switch(op)   {
	case SC_AC_OP_ERASE:
		sc_log(ctx,  "Update DF; erase object('%.*s',type:%X)", (int) sizeof object->label, object->label, object->type);
		rv = awp_update_df_delete(p15card, profile, object);
		break;
	case SC_AC_OP_CREATE:
		sc_log(ctx,  "Update DF; create object('%.*s',type:%X)", (int) sizeof object->label, object->label, object->type);
		rv = awp_update_df_create(p15card, profile, object);
		break;
	}
	SC_FUNC_RETURN(ctx, 1, rv);
}


static int
cosm_emu_update_tokeninfo(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_pkcs15_tokeninfo *tinfo)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_file *file = NULL;
	int rv, flags = 0;
	size_t label_len;
	unsigned char *buf = NULL;

	SC_FUNC_CALLED(ctx, 1);

	if (sc_profile_get_file(profile, COSM_TITLE"-token-info", &file))
		LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_PROFILE, "cannot find "COSM_TITLE"-token-info");

	buf = calloc(1, file->size);
	if (!buf) {
		sc_file_free(file);
		SC_FUNC_RETURN(ctx, 1, SC_ERROR_OUT_OF_MEMORY);
	}

	label_len = strlen(tinfo->label) > (file->size - 4) ? (file->size - 4) : strlen(tinfo->label);
	memcpy(buf, tinfo->label, label_len);
	memset(buf  + label_len, ' ', file->size - 4 - label_len);

	/*  current PKCS#11 flags should be read from the token,
	 *  but for simplicity assume that user-pin is already initialised -- Andre 2010-10-05
	 */
	flags = COSM_TOKEN_FLAG_TOKEN_INITIALIZED
		| COSM_TOKEN_FLAG_USER_PIN_INITIALIZED
		| COSM_TOKEN_FLAG_LOGIN_REQUIRED
		| COSM_TOKEN_FLAG_PRN_GENERATION;

	memset(buf + file->size - 4, 0, 4);
	*(buf + file->size - 1) = flags % 0x100;
	*(buf + file->size - 2) = (flags % 0x10000) / 0x100;

	sc_log(ctx,  "Update token info (label:'%s',flags:%X,p15card->flags:%X)", buf, flags, p15card->flags);
	rv = sc_pkcs15init_update_file(profile, p15card, file, buf, file->size);
	free(buf);
	sc_file_free(file);

	if (rv > 0)
		rv = 0;

	SC_FUNC_RETURN(ctx, 1, rv);
}


static int
cosm_emu_write_info(struct sc_profile *profile, struct sc_pkcs15_card *p15card,
		struct sc_pkcs15_object *pin_obj)
{
	SC_FUNC_CALLED(p15card->card->ctx, 1);
	/* No OpenSC Info file in the native Oberthur card */
	SC_FUNC_RETURN(p15card->card->ctx, 1, SC_SUCCESS);
}
#endif


static struct sc_pkcs15init_operations
sc_pkcs15init_oberthur_operations = {
	cosm_erase_card,
	NULL,				/* init_card  */
	cosm_create_dir,		/* create_dir */
	NULL,				/* create_domain */
	cosm_select_pin_reference,
	cosm_create_pin,
	NULL,				/* select_key_reference */
	cosm_create_key,		/* create_key */
	cosm_store_key,			/* store_key */
	cosm_generate_key,		/* generate_key */
	NULL,
	NULL,				/* encode private/public key */
	NULL,				/* finalize_card */
	NULL,				/* delete_object */
#ifdef ENABLE_OPENSSL
	cosm_emu_update_dir,
	cosm_emu_update_any_df,
	cosm_emu_update_tokeninfo,
	cosm_emu_write_info,
	NULL,
	NULL
#else
	NULL, NULL, NULL, NULL, NULL,
	NULL
#endif
};

struct sc_pkcs15init_operations *
sc_pkcs15init_get_oberthur_ops(void)
{
	return &sc_pkcs15init_oberthur_operations;
}
