/*
 * card-gids.c: Support for GIDS smart cards.
 *
 * Copyright (C) 2015 Vincent Le Toux (My Smart Logon) <vincent.letoux@mysmartlogon.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
 */

/*
The GIDS specification can be viewed here:
https://msdn.microsoft.com/en-us/library/windows/hardware/dn642100%28v=vs.85%29.aspx

and its formatting into the MS minidriver specification.
Some features are undocumented like the format used to store certificates. They have been reverse engineered.
*/


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <string.h>

#ifdef ENABLE_OPENSSL
/* openssl only needed for card administration */
#include <openssl/evp.h>
#include <openssl/rand.h>
#endif /* ENABLE_OPENSSL */

#include "internal.h"
#include "asn1.h"
#include "cardctl.h"
#include "iso7816.h"

#ifdef ENABLE_ZLIB

#include "compression.h"
// used for changing the default label if used twice
#include "../pkcs15init/pkcs15-init.h"
#include "card-gids.h"
#include "../common/compat_strlcat.h"
#include "../common/compat_strlcpy.h"

#define GIDS_STATE_NONE 0
#define GIDS_STATE_READ_DATA_PRESENT 1

#define INS_ACTIVATE_FILE 0x44
#define INS_CREATE_FILE 0xE0
#define INS_DELETE_FILE 0xE4
#define INS_GENERAL_AUTHENTICATE 0x87
#define INS_GENERATE_ASYMECTRIC_KEY_PAIR 0x47
#define INS_GET_DATA 0xCB
#define INS_MANAGE_SECURITY_ENVIRONMENT 0x22
#define INS_PUT_DATA 0xDB
#define INS_SELECT 0xA4
#define INS_VERIFY 0x20

#define P1_SELECT_DF_OR_EF_WITH_EFID 0x00
#define P1_SELECT_DF_BY_NAME 0x04
#define P1_DECIPHERMENT_INTERNAL_AUTHENTICATE_KEY_AGREEMENT 0x41

#define P2_SELECT_FIRST_OR_ONLY_OCCURENCE 0x00
#define P2_PIN_DEAUTHENTICATE 0x82
#define P2_DIGITAL_SIGNATURE 0xB6
#define P2_DECIPHERMENT 0xB8

#define GIDS_PIN_STATUS_OBJECT_IDENTIFIER 0x7F71
#define GIDS_PUK_STATUS_OBJECT_IDENTIFIER 0x7F73
#define GIDS_APPLET_EFID 0x3FFF
#define GIDS_PUT_KEY_DO 0x70
#define GIDS_RSA_1024_IDENTIFIER 0x06
#define GIDS_RSA_2048_IDENTIFIER 0x07
#define GIDS_RSA_3072_IDENTIFIER 0x08
#define GIDS_RSA_4096_IDENTIFIER 0x09
#define GIDS_ECC_192_IDENTIFIER 0x0A
#define GIDS_ECC_224_IDENTIFIER 0x0B
#define GIDS_ECC_256_IDENTIFIER 0x0C
#define GIDS_ECC_384_IDENTIFIER 0x0D
#define GIDS_ECC_521_IDENTIFIER 0x0E

#define GIDS_PUBKEY_TAG 0x7F49
#define GIDS_PUBKEY_TAG_MODULUS 0x81
#define GIDS_PUBKEY_TAG_EXPONENT 0x82

#define GIDS_FIRST_KEY_IDENTIFIER 0x81

#define GIDS_PIN_IDENTIFIER 0x80
#define GIDS_PUK_IDENTIFIER 0x81
#define GIDS_TRY_COUNTER_OLD_TAG 0x9F17
#define GIDS_TRY_COUNTER_TAG 0x97
#define GIDS_TRY_LIMIT_TAG 0x93
#define GIDS_APPLICATION_TEMPLATE_TAG 0x61
#define GIDS_APPLICATION_AID_TAG 0x4F

#define GIDS_KEY_TYPE_AT_KEYEXCHANGE 0x9A
#define GIDS_KEY_TYPE_AT_SIGNATURE 0x9C

static struct sc_card_operations *iso_ops;
static struct sc_card_operations gids_ops;
static struct sc_card_driver gids_drv = {
	"GIDS Smart Card",
	"gids",
	&gids_ops,
	NULL, 0, NULL
};

struct gids_aid {
	int enumtag;
	size_t len_short;	/* min length without version */
	size_t len_long;	/* With version and other stuff */
	u8 *value;
};

/* GIDS AID */
struct sc_aid gids_aid = { { 0xA0,0x00,0x00,0x03,0x97,0x42,0x54,0x46,0x59 }, 9 };

static struct gids_aid gids_aids[] = {
	{SC_CARD_TYPE_GIDS_V1,
		 9, 10, (u8 *) "\xA0\x00\x00\x03\x97\x42\x54\x46\x59\x01" },
	{SC_CARD_TYPE_GIDS_V2,
		 9, 10, (u8 *) "\xA0\x00\x00\x03\x97\x42\x54\x46\x59\x02" },
	{0,  9, 0, NULL }
};

// stolen from cardmod.h for the cardcf file
typedef struct _CARD_CACHE_FILE_FORMAT
{
	unsigned char bVersion;	      // Cache version
	unsigned char bPinsFreshness; // Card PIN
	unsigned short wContainersFreshness;
	unsigned short wFilesFreshness;
} CARD_CACHE_FILE_FORMAT, *PCARD_CACHE_FILE_FORMAT;

struct gids_private_data {
	u8 masterfile[MAX_GIDS_FILE_SIZE];
	size_t masterfilesize;
	u8 cmapfile[MAX_GIDS_FILE_SIZE];
	size_t cmapfilesize;
	unsigned short currentEFID;
	unsigned short currentDO;
	int state;
	u8 buffer[SC_MAX_EXT_APDU_BUFFER_SIZE];
	size_t buffersize;
};

// LOW LEVEL API
///////////////////////////////////////////

// find file identifier & DO identifier from the masterfile for a directory/file
static int gids_get_identifiers(sc_card_t* card, u8* masterfile, size_t masterfilesize, char *directory, char *filename, int *fileIdentifier, int *dataObjectIdentifier) {
	gids_mf_record_t *records = (gids_mf_record_t *) (masterfile+1);
	size_t recordcount = ((masterfilesize-1) / sizeof(gids_mf_record_t));
	size_t i;
	assert(masterfilesize >= 1);

	for (i = 0; i < recordcount; i++) {
		if (strcmp(directory, records[i].directory) == 0 && strcmp(filename, records[i].filename) == 0) {
			*fileIdentifier = records[i].fileIdentifier;
			*dataObjectIdentifier = records[i].dataObjectIdentifier;
			sc_log(card->ctx,
		"Identifiers of %s %s is fileIdentifier=%x, dataObjectIdentifier=%x\n", directory, filename, *fileIdentifier, *dataObjectIdentifier);
			return 0;
		}
	}
	sc_log(card->ctx, "file %s %s not found\n", directory, filename);
	return SC_ERROR_FILE_NOT_FOUND;
}

// used when storing a new certificates
static int gids_find_available_DO(sc_card_t *card, u8* masterfile, size_t masterfilesize, int* fileIdentifier, int *dataObjectIdentifier) {
	// find the first available DO from the masterfile since A010 DF21
	// A010 = read everyone, card user write
	gids_mf_record_t *records = (gids_mf_record_t *) (masterfile+1);
	size_t recordcount = (masterfilesize / sizeof(gids_mf_record_t));
	size_t i;

	assert(masterfilesize >= 1);

	*fileIdentifier = CERT_FI;

	for (*dataObjectIdentifier = CARDAPPS_DO; *dataObjectIdentifier < GIDS_MAX_DO; (*dataObjectIdentifier)++) {
		for (i = 0; i < recordcount; i++) {
			if (records[i].fileIdentifier == *fileIdentifier && records[i].dataObjectIdentifier == *dataObjectIdentifier) {
				break;
			}
		}
		if (i == recordcount) {
			return SC_SUCCESS;
		}
	}
	return SC_ERROR_NOT_ENOUGH_MEMORY;
}

// read a DO from the card
static int gids_get_DO(sc_card_t* card, int fileIdentifier, int dataObjectIdentifier, u8* response, size_t *responselen) {
	sc_apdu_t apdu;
	int r;
	u8 data[4] = {0x5C, 0x02, (dataObjectIdentifier&0xFF00)>>8, (dataObjectIdentifier&0xFF)};
	size_t datasize = 0;
	const u8* p;
	u8 buffer[MAX_GIDS_FILE_SIZE];
	size_t buffer_len = sizeof(buffer);

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(card->ctx,
		 "Got args: fileIdentifier=%x, dataObjectIdentifier=%x, response=%p, responselen=%"SC_FORMAT_LEN_SIZE_T"u\n",
		 fileIdentifier, dataObjectIdentifier, response,
		 responselen ? *responselen : 0);

	sc_format_apdu(card, &apdu,
		response == NULL ? SC_APDU_CASE_3_SHORT : SC_APDU_CASE_4_SHORT, INS_GET_DATA, (fileIdentifier&0xFF00)>>8, (fileIdentifier&0xFF));
	apdu.lc = 04;
	apdu.data = data;
	apdu.datalen = 04;
	apdu.resp = buffer;
	apdu.resplen = buffer_len;
	apdu.le = 256;

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "gids get data failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");
	buffer_len = apdu.resplen;

	p = sc_asn1_find_tag(card->ctx, buffer, buffer_len, dataObjectIdentifier, &datasize);
	if (!p) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_FILE_NOT_FOUND);
	}
	if (response && responselen) {
		if (datasize > *responselen) {
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_BUFFER_TOO_SMALL);
		}
		memcpy(response, p, datasize);
		*responselen = datasize;
	}
	return SC_SUCCESS;
}

// write a DO to the card
static int gids_put_DO(sc_card_t* card, int fileIdentifier, int dataObjectIdentifier, u8 *data, size_t datalen) {
	sc_apdu_t apdu;
	int r;
	u8 buffer[SC_MAX_EXT_APDU_BUFFER_SIZE];
	u8* p = buffer;
	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(card->ctx,
		 "Got args: fileIdentifier=%x, dataObjectIdentifier=%x, data=%p, datalen=%"SC_FORMAT_LEN_SIZE_T"u\n",
		 fileIdentifier, dataObjectIdentifier, data, datalen);

	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, INS_PUT_DATA, (fileIdentifier&0xFF00)>>8, (fileIdentifier&0xFF));

	r = sc_asn1_put_tag(dataObjectIdentifier, data, datalen, buffer, sizeof(buffer), &p);
	LOG_TEST_RET(card->ctx, r, "Error handling TLV.");

	apdu.data = buffer;
	apdu.datalen = (size_t) (p - buffer);
	apdu.lc = apdu.datalen;
	apdu.flags |= SC_APDU_FLAGS_CHAINING;

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "gids put data failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");

	return SC_SUCCESS;
}

// select the GIDS applet
static int gids_select_aid(sc_card_t* card, u8* aid, size_t aidlen, u8* response, size_t *responselen)
{
	sc_apdu_t apdu;
	int r;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(card->ctx,
		 "Got args: aid=%p, aidlen=%"SC_FORMAT_LEN_SIZE_T"u, response=%p, responselen=%"SC_FORMAT_LEN_SIZE_T"u\n",
		 aid, aidlen, response, responselen ? *responselen : 0);

	sc_format_apdu(card, &apdu,
		response == NULL ? SC_APDU_CASE_3_SHORT : SC_APDU_CASE_4_SHORT, INS_SELECT, P1_SELECT_DF_BY_NAME, P2_SELECT_FIRST_OR_ONLY_OCCURENCE);
	apdu.lc = aidlen;
	apdu.data = aid;
	apdu.datalen = aidlen;
	apdu.resp = response;
	apdu.resplen = responselen ? *responselen : 0;
	apdu.le = response == NULL ? 0 : 256; /* could be 21 for fci */

	r = sc_transmit_apdu(card, &apdu);
	if (responselen)
		*responselen = apdu.resplen;
	LOG_TEST_RET(card->ctx, r, "gids select failed");
	SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, sc_check_sw(card, apdu.sw1, apdu.sw2));
}

// DIRECT FILE MANIPULATION
///////////////////////////////////////////

// read a file given the masterfile
static int gids_read_gidsfile_without_cache(sc_card_t* card, u8* masterfile, size_t masterfilesize, char *directory, char *filename, u8* response, size_t *responselen) {
	int r;
	int fileIdentifier;
	int dataObjectIdentifier;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	r = gids_get_identifiers(card, masterfile, masterfilesize, directory, filename, &fileIdentifier, &dataObjectIdentifier);
	LOG_TEST_RET(card->ctx, r, "unable to get the identifier for the gids file");
	r = gids_get_DO(card, fileIdentifier, dataObjectIdentifier, response, responselen);
	LOG_TEST_RET(card->ctx, r, "unable to get the data from the file");
	return r;
}

// write a file given the masterfile
static int gids_write_gidsfile_without_cache(sc_card_t* card, u8* masterfile, size_t masterfilesize, char *directory, char *filename, u8* data, size_t datalen) {
	int r;
	int fileIdentifier;
	int dataObjectIdentifier;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	if (datalen > MAX_GIDS_FILE_SIZE) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_DATA);
	}

	r = gids_get_identifiers(card, masterfile, masterfilesize, directory, filename, &fileIdentifier, &dataObjectIdentifier);
	LOG_TEST_RET(card->ctx, r, "unable to get the identifier for the gids file");
	r = gids_put_DO(card, fileIdentifier, dataObjectIdentifier, data, datalen);
	LOG_TEST_RET(card->ctx, r, "unable to get the data from the file");
	return r;
}

// read the masterfile from the card
static int gids_read_masterfile(sc_card_t* card) {
	struct gids_private_data* data = (struct gids_private_data*) card->drv_data;
	int r = SC_SUCCESS;

	data->masterfilesize = sizeof(data->masterfile);
	r = gids_get_DO(card, MF_FI, MF_DO, data->masterfile, &data->masterfilesize);
	if (r<0) {
		data->masterfilesize = sizeof(data->masterfile);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_CARD);
	}
	if (data->masterfilesize < 1 || data->masterfile[0] != 1) {
		data->masterfilesize = sizeof(data->masterfile);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_CARD);
	}
	return r;
}

// signal to the windows minidriver that something changed on the card and that it should refresh its cache
// the format of this file is specified in the minidriver specification
static int gids_update_cardcf(sc_card_t* card, int file, int container) {
	struct gids_private_data* data = (struct gids_private_data*) card->drv_data;
	u8 cardcf[6];
	int r;
	size_t cardcfsize = sizeof(cardcf);
	r = gids_read_gidsfile_without_cache(card, data->masterfile, data->masterfilesize, "", "cardcf", cardcf, &cardcfsize);
	LOG_TEST_RET(card->ctx, r, "unable to get the cardcf");

	if (file) {
		short filefreshness = cardcf[4] + cardcf[5] * 0x100;
		filefreshness++;
		cardcf[4] = filefreshness & 0xFF;
		cardcf[5] = (filefreshness>>8) & 0xFF;
	}
	if (container) {
		short containerfreshness = cardcf[2] + cardcf[3] * 0x100;
		containerfreshness++;
		cardcf[2] = containerfreshness & 0xFF;
		cardcf[3] = (containerfreshness>>8) & 0xFF;
	}
	r = gids_write_gidsfile_without_cache(card, data->masterfile, data->masterfilesize, "", "cardcf", cardcf, 6);
	LOG_TEST_RET(card->ctx, r, "unable to update the cardcf file");
	return r;
}

// read a file
static int gids_read_gidsfile(sc_card_t* card, char *directory, char *filename, u8* response, size_t *responselen) {
	struct gids_private_data* privatedata = (struct gids_private_data*) card->drv_data;
	int r;
	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	if (privatedata->masterfilesize == sizeof(privatedata->masterfile)) {
		r = gids_read_masterfile(card);
		LOG_TEST_RET(card->ctx, r, "unable to get the masterfile");
	}
	r = gids_read_gidsfile_without_cache(card, privatedata->masterfile, privatedata->masterfilesize,
		directory, filename, response, responselen);
	LOG_TEST_RET(card->ctx, r, "unable to read the file");
	SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,r);
}

// check for the existence of a file
static int gids_does_file_exists(sc_card_t *card, char* directory, char* filename) {
	struct gids_private_data* privatedata = (struct gids_private_data*) card->drv_data;
	int fileIdentifier, dataObjectIdentifier;
	return gids_get_identifiers(card, privatedata->masterfile, privatedata->masterfilesize, directory, filename,
		&fileIdentifier, &dataObjectIdentifier);
}

// write a file already existing
static int gids_write_gidsfile(sc_card_t* card, char *directory, char *filename, u8* data, size_t datalen) {
	struct gids_private_data* privatedata = (struct gids_private_data*) card->drv_data;
	int r;
	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	r = gids_update_cardcf(card, 1, 0);
	LOG_TEST_RET(card->ctx, r, "unable to update the cache file");

	r = gids_write_gidsfile_without_cache(card, privatedata->masterfile, privatedata->masterfilesize,
		directory, filename, data, datalen);
	LOG_TEST_RET(card->ctx, r, "unable to write the file");
	if (strcmp(directory, "mscp") == 0 && strcmp(filename, "cmapfile") == 0) {
		// update the cmapfile cache
		privatedata->cmapfilesize = datalen;
		memcpy(privatedata->cmapfile, data, datalen);
	}
	SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,r);
}

// read the cmapfile (container description)
static int gids_read_cmapfile(sc_card_t* card) {
	struct gids_private_data* data = (struct gids_private_data*) card->drv_data;
	int r = SC_SUCCESS;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	data->cmapfilesize = sizeof(data->cmapfile);
	r = gids_read_gidsfile(card, "mscp", "cmapfile", data->cmapfile, &data->cmapfilesize);
	if (r<0) {
		data->cmapfilesize = sizeof(data->cmapfile);
	}
	LOG_TEST_RET(card->ctx, r, "unable to get the cmapfile");
	return r;
}

// create a file record in the masterfile
static int gids_create_file(sc_card_t *card, char* directory, char* filename) {
	int r;
	u8 masterfilebuffer[MAX_GIDS_FILE_SIZE];
	size_t masterfilebuffersize;
	struct gids_private_data* privatedata = (struct gids_private_data*) card->drv_data;
	int fileIdentifier, dataObjectIdentifier;
	size_t records;
	size_t offset;
	gids_mf_record_t* record;

	r = gids_find_available_DO(card, privatedata->masterfile, privatedata->masterfilesize, &fileIdentifier, &dataObjectIdentifier);
	LOG_TEST_RET(card->ctx, r, "unable to find an empty DO");

	memcpy(masterfilebuffer, privatedata->masterfile, privatedata->masterfilesize);
	masterfilebuffersize = privatedata->masterfilesize + sizeof(gids_mf_record_t);
	if (masterfilebuffersize > MAX_GIDS_FILE_SIZE) {
		SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ENOUGH_MEMORY);
	}

	records = ((privatedata->masterfilesize - 1) / sizeof(gids_mf_record_t));
	offset = 1 + sizeof(gids_mf_record_t) * records;
	memcpy(masterfilebuffer + offset + sizeof(gids_mf_record_t), masterfilebuffer + offset,
		privatedata->masterfilesize - offset);
	memset(masterfilebuffer + offset, 0, sizeof(gids_mf_record_t));
	record = (gids_mf_record_t*) (masterfilebuffer + offset);
	strncpy(record->directory, directory, 8);
	strlcpy(record->filename, filename, sizeof(record->filename));
	record->fileIdentifier = fileIdentifier;
	record->dataObjectIdentifier = dataObjectIdentifier;

	r = gids_update_cardcf(card, 1, 0);
	LOG_TEST_RET(card->ctx, r, "unable to update the cardcf");

	r = gids_put_DO(card, MF_FI, MF_DO, masterfilebuffer, masterfilebuffersize);
	LOG_TEST_RET(card->ctx, r, "unable to update the masterfile");

	memcpy(privatedata->masterfile, masterfilebuffer, masterfilebuffersize);
	privatedata->masterfilesize = masterfilebuffersize;
	return r;
}

// CERTIFICATE HANDLING FUNCTIONS
////////////////////////////////////////////////////

// prepare a sc_path structure given a file identifier & DO
// this will be an input of the gids_read_public_key function
static int gids_build_certificate_path(sc_card_t* card, unsigned char containerindex, unsigned char issignatureonly,sc_path_t* cpath) {
	struct gids_private_data* data = (struct gids_private_data*) card->drv_data;
	int r, fileIdentifier, dataObjectIdentifier;
	char file[9];
	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	if (issignatureonly) {
		snprintf(file, 9, "ksc%02X", containerindex);
	} else {
		snprintf(file, 9, "kxc%02X", containerindex);
	}
	r = gids_get_identifiers(card, data->masterfile, data->masterfilesize, "mscp", file, &fileIdentifier, &dataObjectIdentifier);
	if (r < 0) return SC_ERROR_OBJECT_NOT_FOUND;

	memset(cpath, 0, sizeof(sc_path_t));
	cpath->type = SC_PATH_TYPE_PATH;
	cpath->len = 4;
	cpath->value[0] = (u8) ((fileIdentifier >> 8) & 0xFF);
	cpath->value[1] = (u8) fileIdentifier & 0xFF;
	cpath->value[2] = (u8) ((dataObjectIdentifier >> 8) & 0xFF);
	cpath->value[3] = (u8) dataObjectIdentifier & 0xFF;
	cpath->count = -1;
	return SC_SUCCESS;
}

// PIN HANDLING FUNCTIONS
////////////////////////////////////////////////////

// get the pin status
static int gids_get_pin_status(sc_card_t *card, int pinreference, int *tries_left, int *max_tries) {
	int dataObjectIdentifier, r;
	u8 buffer[100];
	const u8* p;
	size_t buffersize = sizeof(buffer);
	size_t datasize;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	if (tries_left) *tries_left = -1;
	if (max_tries) *max_tries = -1;
	switch(pinreference) {
	case GIDS_PIN_IDENTIFIER:
		dataObjectIdentifier = GIDS_PIN_STATUS_OBJECT_IDENTIFIER;
		break;
	case GIDS_PUK_IDENTIFIER:
		dataObjectIdentifier = GIDS_PUK_STATUS_OBJECT_IDENTIFIER;
		break;
	default:
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_OBJECT_NOT_FOUND);
	}
	r = gids_get_DO(card, GIDS_APPLET_EFID, dataObjectIdentifier, buffer, &buffersize);
	LOG_TEST_RET(card->ctx, r, "unable to update the masterfile");

	buffersize = buffersize > sizeof(buffer) ? sizeof(buffer) : buffersize;

	p = sc_asn1_find_tag(card->ctx, buffer, buffersize, GIDS_TRY_COUNTER_OLD_TAG, &datasize);
	if (p && datasize == 1) {
		if (tries_left)
			*tries_left = p[0];
	}
	p = sc_asn1_find_tag(card->ctx, buffer, buffersize, GIDS_TRY_COUNTER_TAG, &datasize);
	if (p && datasize == 1) {
		if (tries_left)
			*tries_left = p[0];
	}
	p = sc_asn1_find_tag(card->ctx, buffer, buffersize , GIDS_TRY_LIMIT_TAG, &datasize);
	if (p && datasize == 1) {
		if (max_tries)
			*max_tries = p[0];
	}

	sc_log(card->ctx,
		"Pin information for PIN 0x%x: triesleft=%d trieslimit=%d\n", pinreference, (tries_left?*tries_left:-1), (max_tries?*max_tries:-1));
	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

static int gids_match_card(sc_card_t * card)
{
	u8 rbuf[SC_MAX_APDU_BUFFER_SIZE];
	int r,i;
	size_t resplen = sizeof(rbuf);
	const u8 *tag;
	size_t taglen;
	const u8 *aid;
	size_t aidlen;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	/* Detect by selecting applet */
	r = gids_select_aid(card, gids_aid.value, gids_aid.len, rbuf, &resplen);
	if (r<0) return 0;

	card->type = SC_CARD_TYPE_GIDS_GENERIC;
	if (resplen > 2) {
		tag = sc_asn1_find_tag(card->ctx, rbuf, resplen, GIDS_APPLICATION_TEMPLATE_TAG, &taglen);
		if (tag != NULL) {
			aid = sc_asn1_find_tag(card->ctx, tag, taglen, GIDS_APPLICATION_AID_TAG, &aidlen);
			if (aid != NULL ) {
				sc_log(card->ctx, "found AID");
				for (i = 0; gids_aids[i].len_long != 0; i++) {
					if ( aidlen > gids_aids[i].len_long && memcmp(aid, gids_aids[i].value,
									gids_aids[i].len_long) == 0) {
						card->type = gids_aids[i].enumtag;
						break;
					}
				}
			}
		}
	}

	return 1;
}


// extract the serial number from the cardid file
static int gids_get_serialnr(sc_card_t * card, sc_serial_number_t * serial)
{
	int r;
	u8 buffer[SC_MAX_EXT_APDU_BUFFER_SIZE];
	size_t buffersize;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	buffersize = sizeof(buffer);
	r = gids_read_gidsfile(card, "", "cardid", buffer, &buffersize);
	LOG_TEST_RET(card->ctx, r, "unable to read cardid");

	if (SC_MAX_SERIALNR < buffersize)
	{
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}

	/* cache serial number */
	card->serialnr.len = buffersize;
	memcpy(card->serialnr.value, buffer, card->serialnr.len);

	/* return cached serial number */
	if (serial)
		memcpy(serial, &card->serialnr, sizeof(*serial));

	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

// initialize the driver
static int gids_init(sc_card_t * card)
{
	unsigned long flags;
	struct gids_private_data *data;
	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	// cache some data in memory
	data = (struct gids_private_data*) calloc(1, sizeof(struct gids_private_data));
	if (!data) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
	}
	memset(data, 0, sizeof(struct gids_private_data));
	card->drv_data = data;
	// invalidate the master file and cmap file cache
	data->cmapfilesize = sizeof(data->cmapfile);
	data->masterfilesize = sizeof(data->masterfile);

	/* supported RSA keys and how padding is done */
	flags = SC_ALGORITHM_RSA_PAD_PKCS1 | SC_ALGORITHM_RSA_HASH_NONE | SC_ALGORITHM_ONBOARD_KEY_GEN;

	/* fix me: add other algorithms when the gids specification will tell how to extract the algo id from the FCP */
	_sc_card_add_rsa_alg(card, 1024, flags, 0);
	_sc_card_add_rsa_alg(card, 2048, flags, 0);
	_sc_card_add_rsa_alg(card, 3072, flags, 0);
	_sc_card_add_rsa_alg(card, 4096, flags, 0);

	return SC_SUCCESS;
}

// cleanup
static int gids_finish(sc_card_t *card)
{
	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	/* free the private data */
	if (card->drv_data) {
		free(card->drv_data);
		card->drv_data = NULL;
	}
	return 0;
}

//see 12.5.3.1 Cryptographic Mechanism Identifier for Key with CRT
// the cmap file is used to detect the key algorithm / size
static int gids_get_crypto_identifier_from_key_ref(sc_card_t *card, const unsigned char keyref, unsigned char *cryptoidentifier) {
	struct gids_private_data *data = (struct gids_private_data *) card->drv_data;
	PCONTAINER_MAP_RECORD records = (PCONTAINER_MAP_RECORD) data->cmapfile;
	int recordsnum = (int) (data->cmapfilesize / sizeof(CONTAINER_MAP_RECORD));
	int index = keyref - GIDS_FIRST_KEY_IDENTIFIER;
	if (index >= recordsnum) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
	}
	*cryptoidentifier = 0x00; /* initialize to zero */
	if (records[index].wKeyExchangeKeySizeBits == 1024 || records[index].wSigKeySizeBits == 1024) {
		*cryptoidentifier = GIDS_RSA_1024_IDENTIFIER;
		return SC_SUCCESS;
	}
	if (records[index].wKeyExchangeKeySizeBits == 2048 || records[index].wSigKeySizeBits == 2048) {
		*cryptoidentifier = GIDS_RSA_2048_IDENTIFIER;
		return SC_SUCCESS;
	}
	if (records[index].wKeyExchangeKeySizeBits == 3072 || records[index].wSigKeySizeBits == 3072) {
		*cryptoidentifier = GIDS_RSA_3072_IDENTIFIER;
		return SC_SUCCESS;
	}
	if (records[index].wKeyExchangeKeySizeBits == 4096 || records[index].wSigKeySizeBits == 4096) {
		*cryptoidentifier = GIDS_RSA_4096_IDENTIFIER;
		return SC_SUCCESS;
	}
	if (records[index].wKeyExchangeKeySizeBits == 192 || records[index].wSigKeySizeBits == 192) {
		*cryptoidentifier = GIDS_ECC_192_IDENTIFIER;
		return SC_SUCCESS;
	}
	if (records[index].wKeyExchangeKeySizeBits == 224 || records[index].wSigKeySizeBits == 224) {
		*cryptoidentifier = GIDS_ECC_224_IDENTIFIER;
		return SC_SUCCESS;
	}
	if (records[index].wKeyExchangeKeySizeBits == 256 || records[index].wSigKeySizeBits == 256) {
		*cryptoidentifier = GIDS_ECC_256_IDENTIFIER;
		return SC_SUCCESS;
	}
	if (records[index].wKeyExchangeKeySizeBits == 384 || records[index].wSigKeySizeBits == 384) {
		*cryptoidentifier = GIDS_ECC_384_IDENTIFIER;
		return SC_SUCCESS;
	}
	if (records[index].wKeyExchangeKeySizeBits == 521 || records[index].wSigKeySizeBits == 521) {
		*cryptoidentifier = GIDS_ECC_521_IDENTIFIER;
		return SC_SUCCESS;
	}
	LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
}

// same here
static u8 gids_get_crypto_identifier_from_prkey_info(struct sc_pkcs15_prkey_info *key_info) {
	if (key_info->modulus_length > 0) {
		if (key_info->modulus_length == 1024) {
			return GIDS_RSA_1024_IDENTIFIER;
		}
		if (key_info->modulus_length == 2048) {
			return GIDS_RSA_2048_IDENTIFIER;
		}
		if (key_info->modulus_length == 3072) {
			return GIDS_RSA_3072_IDENTIFIER;
		}
		if (key_info->modulus_length == 4096) {
			return GIDS_RSA_4096_IDENTIFIER;
		}
		return 0;
	} else {
		return 0;
	}
}

// GIDS implementation of set security environment
static int gids_set_security_env(sc_card_t *card,
                                     const sc_security_env_t *env,
                                     int se_num)
{
	struct sc_apdu apdu;
	u8 sbuf[SC_MAX_APDU_BUFFER_SIZE];
	u8 *p;
	int r, locked = 0;

	assert(card != NULL && env != NULL);

	LOG_FUNC_CALLED(card->ctx);
	memset(sbuf, 0, sizeof(sbuf));

	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, INS_MANAGE_SECURITY_ENVIRONMENT, P1_DECIPHERMENT_INTERNAL_AUTHENTICATE_KEY_AGREEMENT, 0);
	switch (env->operation) {
	case SC_SEC_OPERATION_DECIPHER:
		apdu.p2 = P2_DECIPHERMENT;
		break;
	case SC_SEC_OPERATION_SIGN:
		apdu.p2 = P2_DIGITAL_SIGNATURE;
		break;
	default:
		return SC_ERROR_INVALID_ARGUMENTS;
	}
	p = sbuf;
	if (env->flags & SC_SEC_ENV_ALG_REF_PRESENT) {
		return SC_ERROR_NOT_SUPPORTED;
	} else {
		// ALG REF is mandatory
		*p++ = 0x80;	/* algorithm reference */
		*p++ = 0x01;
		gids_get_crypto_identifier_from_key_ref(card,env->key_ref[0],p);
		if (env->operation == SC_SEC_OPERATION_DECIPHER) {
			*p++ |= 0x40;
		} else {
			*p++ |= 0x50;
		}
	}
	if (!(env->flags & SC_SEC_ENV_KEY_REF_PRESENT)) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
	}
	if (env->flags & SC_SEC_ENV_KEY_REF_SYMMETRIC)
		*p++ = 0x83;
	else
		*p++ = 0x84;
	*p++ = (u8) env->key_ref_len;
	assert(sizeof(sbuf) - (p - sbuf) >= env->key_ref_len);
	memcpy(p, env->key_ref, env->key_ref_len);
	p += env->key_ref_len;

	r = (int) (p - sbuf);
	apdu.lc = r;
	apdu.datalen = r;
	apdu.data = sbuf;
	if (se_num > 0) {
		r = sc_lock(card);
		LOG_TEST_RET(card->ctx, r, "sc_lock() failed");
		locked = 1;
	}
	if (apdu.datalen != 0) {
		r = sc_transmit_apdu(card, &apdu);
		if (r) {
			sc_log(card->ctx, "%s: APDU transmit failed", sc_strerror(r));
			goto err;
		}
		r = sc_check_sw(card, apdu.sw1, apdu.sw2);
		if (r) {
			sc_log(card->ctx, "%s: Card returned error", sc_strerror(r));
			goto err;
		}
	}
	if (se_num <= 0)
		return 0;
	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, INS_MANAGE_SECURITY_ENVIRONMENT, 0xF2, se_num);
	r = sc_transmit_apdu(card, &apdu);
	sc_unlock(card);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");

	return sc_check_sw(card, apdu.sw1, apdu.sw2);
err:
	if (locked)
		sc_unlock(card);
	return r;
}


static int
gids_decipher(struct sc_card *card,
		const u8 * crgram, size_t crgram_len,
		u8 * out, size_t outlen)
{
	int r;
	struct sc_apdu apdu;

	if (card == NULL || crgram == NULL || out == NULL) {
		return SC_ERROR_INVALID_ARGUMENTS;
	}
	LOG_FUNC_CALLED(card->ctx);
	sc_log(card->ctx,
	       "Gids decipher: in-len %"SC_FORMAT_LEN_SIZE_T"u, out-len %"SC_FORMAT_LEN_SIZE_T"u",
	       crgram_len, outlen);

	/* INS: 0x2A  PERFORM SECURITY OPERATION
	 * P1:  0x80  Resp: Plain value
	 * P2:  0x86  Cmd: Padding indicator byte followed by cryptogram
	 * Implementation by Microsoft indicates that Padding indicator
	 * must not be sent. It may only be needed if Secure Messaging
	 * is used. This driver does not support SM.
	 */
	sc_format_apdu(card, &apdu, SC_APDU_CASE_4, 0x2A, 0x80, 0x86);
	apdu.resp    = out;
	apdu.resplen = outlen;
	apdu.le      = outlen;

	apdu.data = crgram; /* Skip padding indication not needed unless SM */
	apdu.lc = crgram_len;
	apdu.datalen = crgram_len;

	iso7816_fixup_transceive_length(card, &apdu);
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");

	if (apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
		LOG_FUNC_RETURN(card->ctx, (int)apdu.resplen);

	LOG_FUNC_RETURN(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2));
}

// deauthenticate all pins
static int gids_logout(sc_card_t *card)
{
	struct sc_apdu apdu;
	int r;
	assert(card && card->ctx);

	LOG_FUNC_CALLED(card->ctx);

	// use the special PIN to deauthenticate
	sc_format_apdu(card, &apdu, SC_APDU_CASE_1, INS_VERIFY, 0x00, P2_PIN_DEAUTHENTICATE);
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");

	SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, sc_check_sw(card, apdu.sw1, apdu.sw2));
}

// read a public key
static int gids_read_public_key (struct sc_card *card , unsigned int algorithm,
			struct sc_path * path, unsigned key_reference, unsigned modulus_length,
			unsigned char **response, size_t *responselen) {

	struct sc_pkcs15_pubkey_rsa rsa_key;
	sc_apdu_t apdu;
	size_t tlen, len;
	const u8* keytemplate;
	const u8* keydata;
	int r;
	u8 data[] = {0x70, 0x08, // retrieve key
						0x84, 0x01, key_reference, // key reference
						0xA5, 0x03, 0x7F, 0x49, 0x80 // key value template: only public key
				};
	u8 buffer[SC_MAX_EXT_APDU_BUFFER_SIZE];
	size_t buffersize = sizeof(buffer);

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(card->ctx,
		 "Got args: key_reference=%x, response=%p, responselen=%"SC_FORMAT_LEN_SIZE_T"u\n",
		 key_reference, response, responselen ? *responselen : 0);

	sc_format_apdu(card, &apdu,
		response == NULL ? SC_APDU_CASE_3_SHORT : SC_APDU_CASE_4_SHORT, INS_GET_DATA, 0x3F, 0xFF);
	apdu.lc = sizeof(data);
	apdu.data = data;
	apdu.datalen = sizeof(data);
	apdu.resp = buffer;
	apdu.resplen = buffersize;
	apdu.le = 256;

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "gids read public key failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");
	buffersize = apdu.resplen;

	keytemplate = sc_asn1_find_tag(card->ctx, buffer, buffersize, GIDS_PUBKEY_TAG, &tlen);
	if (keytemplate == NULL) {
		sc_log(card->ctx, "invalid public key data: missing tag");
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}

	keydata = sc_asn1_find_tag(card->ctx, keytemplate, tlen, GIDS_PUBKEY_TAG_MODULUS, &len);
	if (keydata != NULL) {
		rsa_key.modulus.data = (u8*) keydata;
		rsa_key.modulus.len = len;
	} else {
		rsa_key.modulus.len = 0;
	}

	keydata = sc_asn1_find_tag(card->ctx, keytemplate, tlen, GIDS_PUBKEY_TAG_EXPONENT, &len);
	if (keydata != NULL) {
		rsa_key.exponent.data = (u8*) keydata;
		rsa_key.exponent.len = len;
	} else {
		rsa_key.exponent.len = 0;
	}

	if (rsa_key.exponent.len && rsa_key.modulus.len) {
		r = sc_pkcs15_encode_pubkey_rsa(card->ctx, &rsa_key, response, responselen);
		LOG_TEST_RET(card->ctx, r, "failed to read public key: cannot encode RSA public key");
	} else {
		sc_log(card->ctx, "it is not a known public key");
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}

	if (response && responselen)
		sc_log_hex(card->ctx, "encoded public key", *response, *responselen);

	return SC_SUCCESS;
}

// emulate a filesystem given EF and DO
static int gids_select_file(sc_card_t *card, const struct sc_path *in_path,
			   struct sc_file **file_out) {
	struct sc_file *file = NULL;
	struct sc_context *ctx = card->ctx;
	struct gids_private_data *data = (struct gids_private_data *) card->drv_data;

	LOG_FUNC_CALLED(card->ctx);

	data->state = GIDS_STATE_NONE;
	data->currentDO = 0;
	data->currentEFID = 0;
	if (in_path->len == 4 && in_path->value[0] == 0xA0) {
		// is it a DO pseudo file ?
		// yes, succeed
		data->currentEFID = in_path->value[1] + (in_path->value[0]<<8);
		data->currentDO = in_path->value[3] + (in_path->value[2]<<8);

		if (file_out) {
			file = sc_file_new();
			if (file == NULL)
				LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY);
			file->path = *in_path;
			file->type = SC_FILE_TYPE_WORKING_EF;
			file->ef_structure = SC_FILE_EF_TRANSPARENT;
			file->size = SC_MAX_EXT_APDU_BUFFER_SIZE;
			*file_out = file;
		}
		LOG_FUNC_RETURN(ctx, SC_SUCCESS);
	} else if (in_path->len == 4 && in_path->value[0] == 0x3F && in_path->value[1] == 0xFF &&  in_path->type == SC_PATH_TYPE_PATH) {
		// GIDS does not allow a select with a path containing a DF
		// replace the file selection from SC_PATH_TYPE_PATH to SC_PATH_TYPE_FILE_ID
		struct sc_path key_path;
		memset(&key_path, 0, sizeof(key_path));
		key_path.len = 2;
		key_path.value[0] = in_path->value[2];
		key_path.value[1] = in_path->value[3];
		key_path.type = SC_PATH_TYPE_FILE_ID;
		return iso_ops->select_file(card, &key_path, file_out);
	} else {
		return iso_ops->select_file(card, in_path, file_out);
	}
}

static int gids_get_pin_policy(struct sc_card *card, struct sc_pin_cmd_data *data) {
	int r;
	if (data->pin_type != SC_AC_CHV) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
	}
	r = gids_get_pin_status(card, data->pin_reference, &(data->pin1.tries_left), &(data->pin1.max_tries));
	LOG_TEST_RET(card->ctx, r, "gids_get_pin_status failed");
	data->pin1.max_length = 16;
	data->pin1.min_length = 4;
	data->pin1.encoding = SC_PIN_ENCODING_ASCII;
	data->pin1.offset = 5;
	data->pin1.logged_in = SC_PIN_STATE_UNKNOWN;
	return SC_SUCCESS;
}

static int
gids_pin_cmd(struct sc_card *card, struct sc_pin_cmd_data *data, int *tries_left) {
	if (data->cmd == SC_PIN_CMD_GET_INFO) {
		return gids_get_pin_policy(card, data);
	} else {
		return iso_ops->pin_cmd(card, data, tries_left);
	}
}

// used to read existing certificates
static int gids_read_binary(sc_card_t *card, unsigned int offset,
		unsigned char *buf, size_t count, unsigned long *flags) {
	struct gids_private_data *data = (struct gids_private_data *) card->drv_data;
	struct sc_context *ctx = card->ctx;
	int r;
	int size;

	LOG_FUNC_CALLED(card->ctx);

	if (! data->currentDO || ! data->currentEFID) {
		LOG_FUNC_RETURN(ctx, SC_ERROR_INTERNAL);
	}
	if (data->state != GIDS_STATE_READ_DATA_PRESENT) {
		// this function is called to read the certificate only
		u8 buffer[SC_MAX_EXT_APDU_BUFFER_SIZE];
		size_t buffersize = sizeof(buffer);
		r = gids_get_DO(card, data->currentEFID, data->currentDO, buffer, &(buffersize));
		if (r <0) return r;
		if (buffersize < 4) {
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_DATA);
		}
		if (buffer[0] == 1 && buffer[1] == 0) {
			if (flags)
				*flags |= SC_FILE_FLAG_COMPRESSED_ZLIB;
			/* compressed data are starting on position buffer + 4 */
			data->buffersize = sizeof(data->buffer) - 4;
			memcpy(data->buffer, buffer + 4, buffersize);
		} else {
			sc_log(card->ctx, "unknown compression method %d", buffer[0] + (buffer[1] << 8));
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_DATA);
		}
		data->state = GIDS_STATE_READ_DATA_PRESENT;
	}
	if (offset >= data->buffersize) {
		return 0;
	}
	size = (int) MIN((data->buffersize - offset), count);
	memcpy(buf, data->buffer + offset, size);
	return size;
}

// refresh the internal caches and return the number of containers
static int
gids_get_all_containers(sc_card_t* card, size_t *recordsnum) {
	int r;
	struct gids_private_data *privatedata = (struct gids_private_data *) card->drv_data;
	r = gids_read_masterfile(card);
	LOG_TEST_RET(card->ctx, r, "unable to read the masterfile");
	r = gids_read_cmapfile(card);
	LOG_TEST_RET(card->ctx, r, "unable to read the cmapfile");
	*recordsnum = (privatedata ->cmapfilesize / sizeof(CONTAINER_MAP_RECORD));
	return SC_SUCCESS;
}

// return the detail about a container to emulate a pkcs15 card
static int
gids_get_container_detail(sc_card_t* card, sc_cardctl_gids_get_container_t* container) {
	PCONTAINER_MAP_RECORD records = NULL;
	struct gids_private_data *privatedata = (struct gids_private_data *) card->drv_data;
	size_t recordsnum, num, i;
	records = (PCONTAINER_MAP_RECORD) privatedata ->cmapfile;
	recordsnum = (privatedata ->cmapfilesize / sizeof(CONTAINER_MAP_RECORD));

	num = container->containernum ;
	if (num >= recordsnum) {
		return SC_ERROR_OBJECT_NOT_FOUND;
	}
	memset(container, 0, sizeof(sc_cardctl_gids_get_container_t));
	container->containernum = num;

	if (!(records[num].bFlags & CONTAINER_MAP_VALID_CONTAINER)) {
		return SC_SUCCESS;
	}
	// ignore problematic containers
	if (records[num].wKeyExchangeKeySizeBits > 0 && records[num].wSigKeySizeBits > 0) {
		return SC_SUCCESS;
	}
	if (records[num].wKeyExchangeKeySizeBits == 0 && records[num].wSigKeySizeBits == 0) {
		return SC_SUCCESS;
	}
	for (i = 0; i < MAX_CONTAINER_NAME_LEN; i++) {
		container->label[i] = (char) records[num].wszGuid[i];
	}
	container->label[MAX_CONTAINER_NAME_LEN] = 0;

	container->module_length = MAX(records[num].wKeyExchangeKeySizeBits, records[num].wSigKeySizeBits);
	container->prvusage = SC_PKCS15_PRKEY_USAGE_SIGN;
	container->pubusage = SC_PKCS15_PRKEY_USAGE_VERIFY;
	if (records[num].wKeyExchangeKeySizeBits > 0) {
		 container->prvusage |= SC_PKCS15_PRKEY_USAGE_DECRYPT;
		 container->pubusage |= SC_PKCS15_PRKEY_USAGE_ENCRYPT;
	}

	// do not check for return code, typically if there is no certificate associated to the key
	gids_build_certificate_path(card, (unsigned char) num, (records[num].wSigKeySizeBits > 0), &(container->certificatepath));

	return SC_SUCCESS;
}

// find a new key reference
static int
gids_select_key_reference(sc_card_t *card, sc_pkcs15_prkey_info_t* key_info) {
	struct gids_private_data *data = (struct gids_private_data *) card->drv_data;
	PCONTAINER_MAP_RECORD records = (PCONTAINER_MAP_RECORD) data->cmapfile;
	size_t recordsnum;
	int r;
	char ch_tmp[10];

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	// refresh the cached data in case some thing has been modified
	r = gids_read_masterfile(card);
	LOG_TEST_RET(card->ctx, r, "gids read masterfile failed");
	r = gids_read_cmapfile(card);
	LOG_TEST_RET(card->ctx, r, "gids read cmapfile failed");

	recordsnum = (data->cmapfilesize / sizeof(CONTAINER_MAP_RECORD));

	if (!key_info->key_reference) {
		// new key
		size_t i;
		// search for a key number not used anymore
		for (i = 0; i < recordsnum; i++) {
			if (!(records[i].bFlags & CONTAINER_MAP_VALID_CONTAINER)) {
				key_info->key_reference = (int) (GIDS_FIRST_KEY_IDENTIFIER + i);
				return SC_SUCCESS;
			}
		}
		// use a new key number
		if (recordsnum > GIDS_MAX_CONTAINER) {
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_ENOUGH_MEMORY);
		}
		key_info->key_reference = (int) (GIDS_FIRST_KEY_IDENTIFIER + recordsnum);
	} else {
		// key was specified. Search if the key can be used
		size_t i = key_info->key_reference - GIDS_FIRST_KEY_IDENTIFIER;
		if (i > GIDS_MAX_CONTAINER) {
			sc_log(card->ctx, "invalid key ref %d", key_info->key_reference);
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
		}
		if (i > recordsnum) {
			sc_log(card->ctx,
				 "container num is not allowed %"SC_FORMAT_LEN_SIZE_T"u %"SC_FORMAT_LEN_SIZE_T"u",
				 i, recordsnum);
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
		}
	}
	snprintf(ch_tmp, sizeof(ch_tmp), "3FFFB0%02X", (u8) (0xFF & key_info->key_reference));
	sc_format_path(ch_tmp, &(key_info->path));
	return SC_SUCCESS;
}

// perform the creation of the key file
// try to mimic the GIDS minidriver key permission
static int gids_perform_create_keyfile(sc_card_t *card, u8 keytype, u8 kid, u8 algid) {
	struct sc_apdu apdu;
	int r;
	u8 keyexchange[] = {0x62,0x47,
							0x82,0x01,0x18,  // file type
							0x83,0x02,0xB0,kid, // key id = 81
							0x8C,0x05,0x8F,0x10,0x10,0x10,0x00, // security
							0xA5,0x37,
								0xB8,0x09, // confidentiality
									0x80,0x01,algid, //algo: rsa without padding
									0x83,0x01,kid, // key id
									0x95,0x01,0x40, // usage
								0xB8,0x09, // confidentiality
									0x80,0x01,0x80 + algid, // RSAES-OAEP padding
									0x83,0x01,kid,
									0x95,0x01,0x40,
								0xB8,0x09, // confidentiality
									0x80,0x01,0x40 + algid, // RSAES-PKCS1-v1_5 padding
									0x83,0x01,kid,
									0x95,0x01,0x40,
								0xB6,0x09, // signature
									0x80,0x01,0x10 + algid, // Full SHA off-card authorized
									0x83,0x01,kid,
									0x95,0x01,0x40,
								0xB6,0x09, // signature
									0x80,0x01,0x50 + algid, // RSASSA PKCS1-v 1_5 padding scheme (for RSA only; otherwise, RFU)
									0x83,0x01,kid,
									0x95,0x01,0x40
	};
	u8 sign[] = {0x62,0x26,
					0x82,0x01,0x18,  // file type
					0x83,0x02,0xB0,kid, // key id = 81
					0x8C,0x05,0x8F,0x10,0x10,0x10,0x00, // security
					0xA5,0x16,
						0xB6,0x09, // signature
							0x80,0x01,0x10+ algid, // Full SHA off-card authorized
							0x83,0x01,0x83, // key id
							0x95,0x01,0x40, // usage
						0xB6,0x09, // signature
							0x80,0x01,0x50 + algid, // RSASSA PKCS1-v 1_5 padding scheme (for RSA only; otherwise, RFU)
							0x83,0x01,0x83,
							0x95,0x01,0x40};

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	// create the key file

	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, INS_CREATE_FILE, 0x00, 0x00);
	if (keytype == 1) {
		apdu.lc = sizeof(keyexchange);
		apdu.datalen = sizeof(keyexchange);
		apdu.data = keyexchange;
	} else if (keytype == 2) {
		apdu.lc = sizeof(sign);
		apdu.datalen = sizeof(sign);
		apdu.data = sign;
	} else {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
	}

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	r = sc_check_sw(card, apdu.sw1, apdu.sw2);
	LOG_TEST_RET(card->ctx, r, "Card returned error");

	// activate file
	sc_format_apdu(card, &apdu, SC_APDU_CASE_1, INS_ACTIVATE_FILE, 0x00, 0x00);

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");

	r = sc_check_sw(card, apdu.sw1, apdu.sw2);
	LOG_TEST_RET(card->ctx, r, "ACTIVATE_FILE returned error");

	LOG_FUNC_RETURN(card->ctx, r);
}

// perform the creation of the keyfile and its registration in the cmapfile and keymap file
static int gids_create_keyfile(sc_card_t *card, sc_pkcs15_object_t *object) {

	int r;
	u8 keytype;
	struct sc_pkcs15_prkey_info *key_info = (struct sc_pkcs15_prkey_info *) object->data;
	u8 kid = key_info->key_reference;
	u8 algid = gids_get_crypto_identifier_from_prkey_info(key_info);
	u8 cmapbuffer[MAX_GIDS_FILE_SIZE];
	size_t cmapbuffersize = 0;
	u8 keymapbuffer[MAX_GIDS_FILE_SIZE];
	size_t keymapbuffersize = 0;
	size_t keymaprecordnum = 0;
	struct gids_private_data *data = (struct gids_private_data *) card->drv_data;
	size_t recordnum;
	size_t containernum = key_info->key_reference - GIDS_FIRST_KEY_IDENTIFIER;
	PCONTAINER_MAP_RECORD records = ((PCONTAINER_MAP_RECORD) cmapbuffer) + containernum;
	struct gids_keymap_record* keymaprecord = NULL;
	int i;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	// sanity check
	assert((object->type & SC_PKCS15_TYPE_CLASS_MASK) == SC_PKCS15_TYPE_PRKEY);

	if (!algid) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
	}

	// masterfile & cmapfile have been refreshed in gids_perform_create_keyfile

	recordnum = (data->cmapfilesize / sizeof(CONTAINER_MAP_RECORD));

	// sanity check
	if (containernum > recordnum || containernum > GIDS_MAX_CONTAINER)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);

	// refresh the key map file
	keymapbuffersize = sizeof(keymapbuffer);
	r = gids_get_DO(card, KEYMAP_FI, KEYMAP_DO, keymapbuffer, &keymapbuffersize);
	if (r<0) {
		// the keymap DO should be present if the cmapfile is not empty
		if (recordnum > 0) {
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
		}
		// else can be empty if not record
		keymapbuffersize = 0;
	} else {
		keymaprecordnum = (keymapbuffersize - 1) / sizeof(struct gids_keymap_record);
		if (keymaprecordnum != recordnum) {
			sc_log(card->ctx , "Error: Unable to create the key file because the keymap and cmapfile are inconsistent");
			sc_log(card->ctx ,
				 "keymaprecordnum = %"SC_FORMAT_LEN_SIZE_T"u recordnum = %"SC_FORMAT_LEN_SIZE_T"u",
				 keymaprecordnum, recordnum);
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
		}
	}


	// prepare the cmap & keymap buffer
	if (containernum == recordnum) {
		// reserve space on the cmap file
		memset(cmapbuffer, 0, sizeof(cmapbuffer));
		memcpy(cmapbuffer, data->cmapfile, data->cmapfilesize);
		cmapbuffersize = data->cmapfilesize + sizeof(CONTAINER_MAP_RECORD);
		r = gids_write_gidsfile(card, "mscp", "cmapfile", cmapbuffer, cmapbuffersize);
		LOG_TEST_RET(card->ctx, r, "unable to reserve space on the cmapfile");

		if (keymapbuffersize == 0) {
			keymapbuffersize = 1;
			keymapbuffer[0] = 1;
		}
		keymapbuffersize += sizeof(struct gids_keymap_record);
	} else {
		memcpy(cmapbuffer, data->cmapfile, data->cmapfilesize);
		cmapbuffersize = data->cmapfilesize;
	}
	keymaprecord = ((struct gids_keymap_record*)(keymapbuffer +1)) + containernum;

	memset(records, 0, sizeof(CONTAINER_MAP_RECORD));
	memset(keymaprecord, 0, sizeof(struct gids_keymap_record));

	if (key_info->usage & SC_PKCS15_PRKEY_USAGE_DECRYPT) {
		keytype = 1; // AT_KEYEXCHANGE
		records->wKeyExchangeKeySizeBits = (unsigned short) key_info->modulus_length;
		keymaprecord->keytype = GIDS_KEY_TYPE_AT_KEYEXCHANGE;
	} else if (key_info->usage & SC_PKCS15_PRKEY_USAGE_SIGN) {
		keytype = 2; // AT_SIGNATURE
		records->wSigKeySizeBits = (unsigned short) key_info->modulus_length;
		keymaprecord->keytype = GIDS_KEY_TYPE_AT_SIGNATURE;
	} else {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
	}

	//the GIDS card must have unique container names
	// avoid the problem with the default label by making it unique
	if (strcmp(DEFAULT_PRIVATE_KEY_LABEL, object->label) == 0 && strlen(DEFAULT_PRIVATE_KEY_LABEL) + 3 < MAX_CONTAINER_NAME_LEN) {
		char addition[4] = " 00";
		addition[1] += containernum % 10;
		addition[2] += (containernum & 0xFF) / 10;
		strlcat(object->label, addition, SC_PKCS15_MAX_LABEL_SIZE);
	}

	// convert char to wchar
	for(i = 0; i < MAX_CONTAINER_NAME_LEN && object->label[i]; i++) {
		records->wszGuid[i] = object->label[i];
	}

	// TODO: check if a container with the same name already exists and prevent is creation or change its name

	records->bFlags = CONTAINER_MAP_VALID_CONTAINER;
	if (recordnum == 0) {
		records->bFlags |= CONTAINER_MAP_DEFAULT_CONTAINER;
	}
	keymaprecord->algid = algid;
	keymaprecord->state = 1;
	keymaprecord->unknownWithFFFF = (unsigned short) (-1);
	keymaprecord->keyref = 0xB000 + kid;
	r = gids_perform_create_keyfile(card, keytype, kid, algid);
	LOG_TEST_RET(card->ctx, r, "unable to create the key file");

	r = gids_update_cardcf(card, 0, 1);
	LOG_TEST_RET(card->ctx, r, "unable to update the cardcf file regarding container");
	r = gids_put_DO(card, KEYMAP_FI, KEYMAP_DO, keymapbuffer, keymapbuffersize);
	LOG_TEST_RET(card->ctx, r, "unable to write the keymap file");

	r = gids_write_gidsfile(card, "mscp", "cmapfile", cmapbuffer, cmapbuffersize);
	LOG_TEST_RET(card->ctx, r, "unable to write the cmap file after the container creation");

	LOG_FUNC_RETURN(card->ctx, r);
}

// generate a key on an existing container
static int gids_generate_key(sc_card_t *card, sc_pkcs15_object_t *object, struct sc_pkcs15_pubkey* pubkey) {

	struct sc_pkcs15_prkey_info *key_info = (struct sc_pkcs15_prkey_info *) object->data;
	u8 kid = key_info->key_reference;
	u8 algid = gids_get_crypto_identifier_from_prkey_info(key_info);
	struct sc_apdu apdu;
	u8 generatekey[] = {0xAC, 0x06, // CRT template
							0x80, 0x01, algid, // algorithm
							0x83, 0x01, kid // key reference
						};
	int r;
	u8 *buffer = NULL;
	size_t buffersize = 0;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	assert((object->type & SC_PKCS15_TYPE_CLASS_MASK) == SC_PKCS15_TYPE_PRKEY);

	if ((key_info->key_reference > GIDS_FIRST_KEY_IDENTIFIER + GIDS_MAX_CONTAINER) || (kid < GIDS_FIRST_KEY_IDENTIFIER)) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_DATA);
	}

	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, INS_GENERATE_ASYMECTRIC_KEY_PAIR, 0x00, 0x00);
	apdu.lc = sizeof(generatekey);
	apdu.datalen = sizeof(generatekey);
	apdu.data = generatekey;

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");

	r = sc_check_sw(card, apdu.sw1, apdu.sw2);
	LOG_TEST_RET(card->ctx, r, "generate key returned error");

	r = gids_read_public_key(card, 0, NULL, kid, 0, &buffer, &buffersize);
	LOG_TEST_RET(card->ctx, r, "read public key returned error");
	r = sc_pkcs15_decode_pubkey(card->ctx, pubkey, buffer, buffersize);
	if (buffer)
		free(buffer);
	LOG_FUNC_RETURN(card->ctx, r);
}

// import the key in an existing container
static int gids_import_key(sc_card_t *card, sc_pkcs15_object_t *object, sc_pkcs15_prkey_t *key) {
	struct sc_pkcs15_prkey_info *key_info = (struct sc_pkcs15_prkey_info *) object->data;
	int version = 0;
	int keytype = 2; // RSA
	u8 kid = key_info->key_reference;
	size_t len = 1;
	int encryptkeyref = 0; //NONE
	int r;
	u8* buffer = NULL;
	size_t buflen = 0;

	struct sc_asn1_entry asn1_key_usage_template[] = {
		{ "keyReference", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_OCTET_STRING | SC_ASN1_CTX, 0, NULL, NULL },
		{ "KeyValueTemplate", SC_ASN1_STRUCT, SC_ASN1_TAG_NULL | SC_ASN1_CTX | SC_ASN1_CONS, 0, NULL, NULL },
		{ NULL, 0, 0, 0, NULL, NULL }
	};

	struct sc_asn1_entry asn1_key_value_template[] = {
		{ "keyType", SC_ASN1_INTEGER, SC_ASN1_TAG_BIT_STRING | SC_ASN1_CTX, 0, NULL, NULL },
		{ "encryptKeyRef", SC_ASN1_INTEGER, SC_ASN1_TAG_OCTET_STRING | SC_ASN1_CTX, 0, NULL, NULL },
		{ "keyValue", SC_ASN1_STRUCT, SC_ASN1_TAG_OBJECT_DESCRIPTOR | SC_ASN1_CTX, 0, NULL, NULL },
		{ NULL, 0, 0, 0, NULL, NULL }
	};

	struct sc_asn1_entry asn1_key_data[] = {
		{ "keyData", SC_ASN1_STRUCT, SC_ASN1_SEQUENCE | SC_ASN1_CONS, 0, NULL, NULL },
		{ NULL, 0, 0, 0, NULL, NULL }
	};

	struct sc_asn1_entry asn1_rsa_priv_coefficients_gids[] = {
		{ "version", SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
		{ "modulus", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, SC_ASN1_ALLOC, NULL, NULL },
		{ "publicExponent", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, SC_ASN1_ALLOC, NULL, NULL },
		{ "privateExponent", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, SC_ASN1_ALLOC, NULL, NULL },
		{ "p", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, SC_ASN1_ALLOC, NULL, NULL },
		{ "q", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, SC_ASN1_ALLOC, NULL, NULL },
		{ "dmp1", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, SC_ASN1_ALLOC, NULL, NULL },
		{ "dmq1", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, SC_ASN1_ALLOC, NULL, NULL },
		{ "iqmp", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, SC_ASN1_ALLOC, NULL, NULL },
		{ NULL, 0, 0, 0, NULL, NULL }
	};
	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	assert((object->type & SC_PKCS15_TYPE_CLASS_MASK) == SC_PKCS15_TYPE_PRKEY);

	if (object->type != SC_PKCS15_TYPE_PRKEY_RSA || key->algorithm != SC_ALGORITHM_RSA) {
		sc_log(card->ctx, "GIDS supports RSA keys only (but may support ECC one day).");
		return SC_ERROR_NOT_SUPPORTED;
	}
	if (!key->u.rsa.dmp1.len || !key->u.rsa.dmq1.len || !key->u.rsa.iqmp.len) {
		sc_log(card->ctx, "GIDS needs dmp1 & dmq1 & iqmp");
		return SC_ERROR_NOT_SUPPORTED;
	}
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 0, &version, NULL, 1);
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 1, key->u.rsa.modulus.data, &key->u.rsa.modulus.len, 1);
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 2, key->u.rsa.exponent.data, &key->u.rsa.exponent.len, 1);
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 3, key->u.rsa.d.data, &key->u.rsa.d.len, 1);
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 4, key->u.rsa.p.data, &key->u.rsa.p.len, 1);
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 5, key->u.rsa.q.data, &key->u.rsa.q.len, 1);
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 6, key->u.rsa.dmp1.data, &key->u.rsa.dmp1.len, 1);
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 7, key->u.rsa.dmq1.data, &key->u.rsa.dmq1.len, 1);
	sc_format_asn1_entry(asn1_rsa_priv_coefficients_gids + 8, key->u.rsa.iqmp.data, &key->u.rsa.iqmp.len, 1);

	sc_format_asn1_entry(asn1_key_data + 0, asn1_rsa_priv_coefficients_gids, NULL, 1);

	sc_format_asn1_entry(asn1_key_value_template + 0, &keytype, NULL, 1);
	sc_format_asn1_entry(asn1_key_value_template + 1, &encryptkeyref, NULL, 1);
	sc_format_asn1_entry(asn1_key_value_template + 2, asn1_key_data, NULL, 1);

	sc_format_asn1_entry(asn1_key_usage_template + 0, &kid, &len, 1);
	sc_format_asn1_entry(asn1_key_usage_template + 1, asn1_key_value_template, NULL, 1);

	r = sc_asn1_encode(card->ctx, asn1_key_usage_template, &buffer, &buflen);
	SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE, r, "unable to encode the private key");

	r = gids_put_DO(card, GIDS_APPLET_EFID, GIDS_PUT_KEY_DO, buffer, buflen);
	SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE, r, "unable to put the private key - key greater than 2048 bits ?");
	r = SC_SUCCESS;
err:
	sc_mem_clear(buffer, buflen);
	LOG_FUNC_RETURN(card->ctx, r);
}

// remove a crt file
static int gids_delete_key_file(sc_card_t *card, int containernum) {
	int r;
	char ch_tmp[10];
	sc_path_t cpath;
	snprintf(ch_tmp, sizeof(ch_tmp), "3FFFB0%02X", (u8) (0xFF & (containernum + GIDS_FIRST_KEY_IDENTIFIER)));
	sc_format_path(ch_tmp, &cpath);
	r = gids_select_file(card, &cpath, NULL);
	LOG_TEST_RET(card->ctx, r, "unable to select the key file");
	// delete current selected file
	memset(&cpath, 0, sizeof(cpath));
	r = iso_ops->delete_file(card, &cpath);
	LOG_TEST_RET(card->ctx, r, "unable to delete the key file");
	return r;
}

// encode a certificate using the minidriver compression
static int gids_encode_certificate(sc_card_t *card, u8* source, size_t sourcesize, u8* destination, size_t* destinationsize) {
	int r;
	size_t outlen;
	if (*destinationsize < 4) {
		return SC_ERROR_BUFFER_TOO_SMALL;
	}
	if (sourcesize > 0xFFFF) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
	}
	// format is:
	// 2 bytes for compression version
	// 2 bytes for uncompressed file size
	// ZLIB compression of the certificate
	destination[0] = 1;
	destination[1] = 0;
	destination[2] = sourcesize & 0xFF;
	destination[3] = (sourcesize & 0xFF00) >> 8;
	outlen = *destinationsize - 4;
	r = sc_compress(destination + 4, &outlen, source, sourcesize, COMPRESSION_ZLIB);
	LOG_TEST_RET(card->ctx, r, "unable to compress the certificate");
	*destinationsize = outlen + 4;
	return SC_SUCCESS;
}

// save a certificate associated to a container to the card
static int gids_save_certificate(sc_card_t *card, sc_pkcs15_object_t *certobject,
								sc_pkcs15_object_t *privkeyobject, struct sc_path *path) {
	int r;
	u8 certbuffer[MAX_GIDS_FILE_SIZE];
	size_t certbuffersize = sizeof(certbuffer);
	struct sc_pkcs15_cert_info *cert_info = (struct sc_pkcs15_cert_info *) certobject->data;
	struct sc_pkcs15_prkey_info *prkey_info = (struct sc_pkcs15_prkey_info *) privkeyobject->data;
	unsigned char containernum;
	char filename[9];
	assert((certobject->type & SC_PKCS15_TYPE_CLASS_MASK) == SC_PKCS15_TYPE_CERT);
	assert((privkeyobject->type & SC_PKCS15_TYPE_CLASS_MASK) == SC_PKCS15_TYPE_PRKEY);

	// refresh the cached data in case some thing has been modified
	r = gids_read_masterfile(card);
	LOG_TEST_RET(card->ctx, r, "gids read masterfile failed");
	r= gids_read_cmapfile(card);
	LOG_TEST_RET(card->ctx, r, "gids read cmapfile failed");

	// compress the certificate according to the minidriver specification
	r = gids_encode_certificate(card, cert_info->value.value, cert_info->value.len, certbuffer, &certbuffersize);
	LOG_TEST_RET(card->ctx, r, "unable to encode the certificate");

	// save it to a minidriver file
	containernum = prkey_info->key_reference - GIDS_FIRST_KEY_IDENTIFIER;
	if (!(prkey_info->usage & SC_PKCS15_PRKEY_USAGE_DECRYPT)) {
		snprintf(filename, sizeof(filename), "ksc%02X", containernum);
	} else {
		snprintf(filename, sizeof(filename), "kxc%02X", containernum);
	}

	r = gids_does_file_exists(card, "mscp", filename);
	if (r == SC_ERROR_FILE_NOT_FOUND) {
		r = gids_create_file(card, "mscp", filename);
		LOG_TEST_RET(card->ctx, r, "gids unable to create the certificate file");
	}
	r = gids_write_gidsfile(card, "mscp", filename, certbuffer, certbuffersize);
	LOG_TEST_RET(card->ctx, r, "gids unable to write the certificate data");

	// return the path to the DO
	r = gids_build_certificate_path(card, containernum, !(prkey_info->usage & SC_PKCS15_PRKEY_USAGE_DECRYPT), path);
	LOG_TEST_RET(card->ctx, r, "gids unable to build the certificate path");

	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

// remove a container and its registration in the cmapfile
static int gids_delete_container_num(sc_card_t *card, size_t containernum) {
	int r;
	u8 cmapbuffer[MAX_GIDS_FILE_SIZE];
	size_t cmapbuffersize = 0;
	u8 keymapbuffer[MAX_GIDS_FILE_SIZE];
	size_t keymapbuffersize = 0;
	size_t keymaprecordnum = 0;
	struct gids_private_data *data = (struct gids_private_data *) card->drv_data;
	size_t recordnum;
	PCONTAINER_MAP_RECORD records = ((PCONTAINER_MAP_RECORD) cmapbuffer) + containernum;
	struct gids_keymap_record* keymaprecord = NULL;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	// masterfile & cmapfile have been refreshed before

	recordnum = (data->cmapfilesize / sizeof(CONTAINER_MAP_RECORD));

	// sanity check
	if (containernum >= recordnum || recordnum > GIDS_MAX_CONTAINER)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);

	// refresh the key map file
	keymapbuffersize = sizeof(keymapbuffer);
	r = gids_get_DO(card, KEYMAP_FI, KEYMAP_DO, keymapbuffer, &keymapbuffersize);
	if (r<0) {
		// the keymap DO should be present if the cmapfile is not empty
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}
	keymaprecordnum = (keymapbuffersize - 1) / sizeof(struct gids_keymap_record);
	if (keymaprecordnum != recordnum) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}

	// update the key map file
	memcpy(cmapbuffer, data->cmapfile, data->cmapfilesize);
	cmapbuffersize = data->cmapfilesize;
	keymaprecord = ((struct gids_keymap_record*)(keymapbuffer +1)) + containernum;

	memset(records, 0, sizeof(CONTAINER_MAP_RECORD));
	memset(keymaprecord, 0, sizeof(struct gids_keymap_record));

	keymaprecord->unknownWithFFFF = (unsigned short) (-1);
	keymaprecord->keyref =(unsigned short) (-1);

	// remove the key, update the key map & cmap file and signal the change
	r = gids_delete_key_file(card, (int) containernum);
	LOG_TEST_RET(card->ctx, r, "unable to delete the key file");
	r = gids_update_cardcf(card, 0, 1);
	LOG_TEST_RET(card->ctx, r, "unable to update the cardcf file regarding container");
	r = gids_put_DO(card, KEYMAP_FI, KEYMAP_DO, keymapbuffer, keymapbuffersize);
	LOG_TEST_RET(card->ctx, r, "unable to write the keymap file");

	r = gids_write_gidsfile(card, "mscp", "cmapfile", cmapbuffer, cmapbuffersize);
	LOG_TEST_RET(card->ctx, r, "unable to write the cmap file after the container creation");

	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

// delete a certificate associated to a container
static int gids_delete_cert(sc_card_t *card, sc_pkcs15_object_t* object) {
	int r;
	struct gids_private_data *privatedata = (struct gids_private_data *) card->drv_data;
	struct sc_pkcs15_cert_info *cert_info = (struct sc_pkcs15_cert_info *) object->data;
	unsigned short fileIdentifier, DO;
	u8 masterfilebuffer[MAX_GIDS_FILE_SIZE];
	size_t masterfilebuffersize = 0;
	gids_mf_record_t *records = (gids_mf_record_t *) (masterfilebuffer+1);
	size_t recordcount, recordnum = (size_t) -1;
	size_t i;


	assert((object->type & SC_PKCS15_TYPE_CLASS_MASK) == SC_PKCS15_TYPE_CERT);
	// refresh the cached data in case some thing has been modified
	r = gids_read_masterfile(card);
	LOG_TEST_RET(card->ctx, r, "gids read masterfile failed");
	r= gids_read_cmapfile(card);
	LOG_TEST_RET(card->ctx, r, "gids read cmapfile failed");

	// remove the file reference from the masterfile
	if (cert_info->path.len != 4) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}
	fileIdentifier = cert_info->path.value[0] * 0x100 + cert_info->path.value[1];
	DO = cert_info->path.value[2] * 0x100 + cert_info->path.value[3];

	memcpy(masterfilebuffer, privatedata->masterfile, privatedata->masterfilesize);
	masterfilebuffersize = privatedata->masterfilesize;

	recordcount = ((masterfilebuffersize-1) / sizeof(gids_mf_record_t));
	for (i = 0; i < recordcount; i++) {
		if (records[i].fileIdentifier == fileIdentifier && records[i].dataObjectIdentifier == DO) {
			recordnum = i;
			break;
		}
	}
	if (recordnum == (size_t) -1) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_FILE_NOT_FOUND);
	}

	for (i = 1 + (recordnum+1) * sizeof(gids_mf_record_t); i < masterfilebuffersize; i++) {
		masterfilebuffer[i - sizeof(gids_mf_record_t)] = masterfilebuffer[i];
	}
	masterfilebuffersize -= sizeof(gids_mf_record_t);

	// remove the DO, update the masterfile, and signal the change
	r = gids_update_cardcf(card, 1, 0);
	LOG_TEST_RET(card->ctx, r, "unable to update the cache file");

	r = gids_put_DO(card, fileIdentifier, DO, NULL, 0);
	LOG_TEST_RET(card->ctx, r, "gids unable to delete the certificate DO");

	r = gids_put_DO(card, MF_FI, MF_DO, masterfilebuffer, masterfilebuffersize);
	LOG_TEST_RET(card->ctx, r, "gids unable to update the masterfile");

	memcpy(privatedata->masterfile, masterfilebuffer, masterfilebuffersize);
	privatedata->masterfilesize = masterfilebuffersize;

	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

static int gids_delete_key(sc_card_t *card, sc_pkcs15_object_t* object) {
	int r;
	size_t containernum;
	struct sc_pkcs15_prkey_info *key_info = (struct sc_pkcs15_prkey_info *) object->data;

	assert((object->type & SC_PKCS15_TYPE_CLASS_MASK) == SC_PKCS15_TYPE_PRKEY);
	// refresh the cached data in case some thing has been modified
	r = gids_read_masterfile(card);
	LOG_TEST_RET(card->ctx, r, "gids read masterfile failed");
	r = gids_read_cmapfile(card);
	LOG_TEST_RET(card->ctx, r, "gids read cmapfile failed");
	containernum = key_info->key_reference - GIDS_FIRST_KEY_IDENTIFIER;

	r = gids_delete_container_num(card, containernum);
	LOG_TEST_RET(card->ctx, r, "gids unable to delete the container");
	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

// used by gids_initialize to create the filesystem
static int gids_initialize_create_file(sc_card_t *card, u8* command, size_t commandsize) {
	int r;
	sc_apdu_t apdu;
	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, INS_CREATE_FILE, 0x00, 0x00);
	apdu.lc = commandsize;
	apdu.data = command;
	apdu.datalen = commandsize;

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU1 transmit failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");

	// activate file
	sc_format_apdu(card, &apdu, SC_APDU_CASE_1, INS_ACTIVATE_FILE, 0x00, 0x00);

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU2 transmit failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");
	LOG_FUNC_RETURN(card->ctx, r);
}

// used by gids_initialize to set the admin key
static int gids_set_administrator_key(sc_card_t *card, u8* key) {
	int r;
	u8 adminKeyData[] = {0x84,0x01,0x80, // key reference
						 0xA5,0x1F, // key template
						 // key value
							0x87,0x18,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,
									0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,
									0x03,0x04,0x05,0x06,0x07,0x08,
						// key file
							0x88, 0x03,0xB0,0x73,0xDC};

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	memcpy(adminKeyData+7, key, 24);
	r = gids_put_DO(card, GIDS_APPLET_EFID, GIDS_PUT_KEY_DO, adminKeyData, sizeof(adminKeyData));
	sc_mem_clear(adminKeyData, sizeof(adminKeyData));
	LOG_TEST_RET(card->ctx, r, "gids unable to set the admin key");
	return SC_SUCCESS;
}

static int gids_check_that_card_is_new(sc_card_t *card) {
	int r;
	// retrieve the masterfile
	// if it succeed, the card has already been initialized
	r = gids_read_masterfile(card);
	if (r == SC_SUCCESS) {
		r = SC_ERROR_INVALID_CARD;
		LOG_TEST_RET(card->ctx, r, "unable to read the masterfile");
	}
	return SC_SUCCESS;
}

// initialize a card
// see the minidriver specification annex for the details about this
static int gids_initialize(sc_card_t *card, sc_cardctl_gids_init_param_t* param) {
	sc_apdu_t apdu;
	int r;
#ifdef ENABLE_OPENSSL
	int i;
#endif
	// hardcoded file setting
	// File type=39=TLV structure for BER-TLV DOs then ACL varies depending on the file)
	// this DO EF are used like DF file so the permission has to be set only once
	u8 UserCreateDeleteDirAc[] = {0x62,0x0C,0x82,0x01,0x39,0x83,0x02,0xA0,0x00,0x8C,0x03,0x03,0x30,0x00};
	u8 EveryoneReadUserWriteAc[] = {0x62,0x0C,0x82,0x01,0x39,0x83,0x02,0xA0,0x10,0x8C,0x03,0x03,0x30,0x00};
	u8 UserWriteExecuteAc[] = {0x62,0x0C,0x82,0x01,0x39,0x83,0x02,0xA0,0x11,0x8C,0x03,0x03,0x30,0xFF};
	u8 EveryoneReadAdminWriteAc[] = {0x62,0x0C,0x82,0x01,0x39,0x83,0x02,0xA0,0x12,0x8C,0x03,0x03,0x20,0x00};
	u8 UserReadWriteAc[] = {0x62,0x0C,0x82,0x01,0x39,0x83,0x02,0xA0,0x13,0x8C,0x03,0x03,0x30,0x30};
	u8 AdminReadWriteAc[] = {0x62,0x0C,0x82,0x01,0x39,0x83,0x02,0xA0,0x14,0x8C,0x03,0x03,0x20,0x20};
	// File type=18=key file ; type = symmetric key
	u8 AdminKey[] = {0x62,0x1A,0x82,0x01,0x18,0x83,0x02,0xB0,0x80,0x8C,0x04,0x87,0x00,0x20,0xFF,0xA5,
											0x0B,0xA4,0x09,0x80,0x01,0x02,0x83,0x01,0x80,0x95,0x01,0xC0};
	// file used to store other file references. Format undocumented.
	u8 masterfile[] = {0x01,0x6d,0x73,0x63,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
					   0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
					   0x00,0x00,0x00,0x00,0x63,0x61,0x72,0x64,0x69,0x64,0x00,0x00,0x00,0x00,0x00,0x20,0xdf,
					   0x00,0x00,0x12,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x61,
					   0x72,0x64,0x61,0x70,0x70,0x73,0x00,0x00,0x00,0x21,0xdf,0x00,0x00,0x10,0xa0,0x00,0x00,
					   0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x61,0x72,0x64,0x63,0x66,0x00,0x00,
					   0x00,0x00,0x00,0x22,0xdf,0x00,0x00,0x10,0xa0,0x00,0x00,0x6d,0x73,0x63,0x70,0x00,0x00,
					   0x00,0x00,0x00,0x63,0x6d,0x61,0x70,0x66,0x69,0x6c,0x65,0x00,0x00,0x00,0x23,0xdf,0x00,
					   0x00,0x10,0xa0,0x00,0x00};
	// list the application on the card - defined in the minidriver specification
	u8 cardapps[] = {0x6d,0x73,0x63,0x70,0x00,0x00,0x00,0x00};
	// used to detect if modifications have been done outside of the minidriver - defined in the minidriver specification
	u8 cardcf[] = {0x00,0x00,0x00,0x00,0x00,0x00};
	struct sc_pin_cmd_data pindata;
	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	// avoid multiple initialization
	r = gids_check_that_card_is_new(card);
	LOG_TEST_RET(card->ctx, r, "card seems to have been already initialized");

	memset(&pindata, 0, sizeof(pindata));
	// create PIN & PUK
	pindata.cmd = SC_PIN_CMD_CHANGE;
	pindata.pin_type = SC_AC_CHV;
	pindata.pin2.len = param->user_pin_len;
	pindata.pin2.data = param->user_pin;
	pindata.pin_reference = 0x80;

	r = sc_pin_cmd(card, &pindata, NULL);
	LOG_TEST_RET(card->ctx, r, "gids set pin");

	// create file
	r = gids_initialize_create_file(card, UserCreateDeleteDirAc, sizeof(UserCreateDeleteDirAc));
	LOG_TEST_RET(card->ctx, r, "gids to create the file UserCreateDeleteDirAc");
	r = gids_initialize_create_file(card, EveryoneReadUserWriteAc, sizeof(EveryoneReadUserWriteAc));
	LOG_TEST_RET(card->ctx, r, "gids to create the file EveryoneReadUserWriteAc");
	r = gids_initialize_create_file(card, UserWriteExecuteAc, sizeof(UserWriteExecuteAc));
	LOG_TEST_RET(card->ctx, r, "gids to create the file UserWriteExecuteAc");
	r = gids_initialize_create_file(card, EveryoneReadAdminWriteAc, sizeof(EveryoneReadAdminWriteAc));
	LOG_TEST_RET(card->ctx, r, "gids to create the file EveryoneReadAdminWriteAc");
	r = gids_initialize_create_file(card, UserReadWriteAc, sizeof(UserReadWriteAc));
	LOG_TEST_RET(card->ctx, r, "gids to create the file UserReadWriteAc");
	r = gids_initialize_create_file(card, AdminReadWriteAc, sizeof(AdminReadWriteAc));
	LOG_TEST_RET(card->ctx, r, "gids to create the file AdminReadWriteAc");

	//admin key
	r = gids_initialize_create_file(card, AdminKey, sizeof(AdminKey));
	LOG_TEST_RET(card->ctx, r, "gids to create the file AdminKey");

	r = gids_set_administrator_key(card, param->init_code);
	LOG_TEST_RET(card->ctx, r, "gids unable to set the admin key");

	// create the filesystem
	r = gids_put_DO(card, MF_FI, MF_DO, masterfile, sizeof(masterfile));
	LOG_TEST_RET(card->ctx, r, "gids unable to save the masterfile");
	r = gids_put_DO(card, CARDAPPS_FI, CARDAPPS_DO, cardapps, sizeof(cardapps));
	LOG_TEST_RET(card->ctx, r, "gids unable to save the cardapps");
	r = gids_put_DO(card, CARDCF_FI, CARDCF_DO, cardcf, sizeof(cardcf));
	LOG_TEST_RET(card->ctx, r, "gids unable to save the cardcf");
	r = gids_put_DO(card, CMAP_FI, CMAP_DO, NULL, 0);
	LOG_TEST_RET(card->ctx, r, "gids unable to save the cmapfile");
#ifdef ENABLE_OPENSSL
	for (i = sizeof(param->cardid) -1; i >= 0; i--) {
		if (param->cardid[i]) break;
	}
	if (i < 0) {
		// set a random cardid if not set
		if (RAND_bytes(param->cardid, sizeof(param->cardid)) != 1) {
			sc_log_openssl(card->ctx);
			LOG_TEST_RET(card->ctx, SC_ERROR_INTERNAL, "unable to set a random serial number");
		}
	}
#endif
	r = gids_put_DO(card, CARDID_FI, CARDID_DO, param->cardid, sizeof(param->cardid));
	LOG_TEST_RET(card->ctx, r, "gids unable to save the cardid");

	//select applet
	sc_format_apdu(card, &apdu, SC_APDU_CASE_3, INS_SELECT, 0x00, 0x0C);
	apdu.lc = 2;
	apdu.data = (const unsigned char *) "\x3F\xFF";
	apdu.datalen = 2;

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");
	// activate file
	sc_format_apdu(card, &apdu, SC_APDU_CASE_1, INS_ACTIVATE_FILE, 0x00, 0x00);
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");
	LOG_FUNC_RETURN(card->ctx, r);
}

// execute an admin authentication based on a secret key
// this is a 3DES authentication with a secret key
// the card mechanism is described in the GIDS specification and the computer side on the minidriver specification
// the minidriver specification is incorrect because it is not ECB but CBC
// then the GIDS specification is incorrect because the z1 key should be 8 bytes instead of 7
// this data comes from the reverse of the GIDS minidriver.
static int gids_authenticate_admin(sc_card_t *card, u8* key) {
#ifndef ENABLE_OPENSSL
	LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
#else
	EVP_CIPHER_CTX *ctx = NULL;
	int r;
	u8 apduSetRandom[20] = {0x7C,0x12,0x81,0x10,0};
	u8* randomR1 = apduSetRandom + 4;
	u8 apduSetRandomResponse[256];
	u8* randomR2 = apduSetRandomResponse+4;
	u8 apduSendReponse[40 + 4] = {0x7C,0x2A,0x82,0x28};
	u8 z1[8];
	u8 buffer[16+16+8];
	u8* buffer2 = apduSendReponse + 4;
	int buffer2size = 40;
	u8 apduSendResponseResponse[256];
	u8 buffer3[16+16+8];
	int buffer3size = 40;
	sc_apdu_t apdu;
	EVP_CIPHER *cipher;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	// select the admin key
	sc_format_apdu(card, &apdu, SC_APDU_CASE_3, INS_MANAGE_SECURITY_ENVIRONMENT, 0xC1, 0xA4);
	apdu.lc = 3;
	apdu.data = (const unsigned char *) "\x83\x01\x80";
	apdu.datalen = 3;
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");

	// generate a challenge
	if (RAND_bytes(randomR1, 16) != 1) {
		sc_log_openssl(card->ctx);
		LOG_TEST_RET(card->ctx, SC_ERROR_INTERNAL, "unable to set computer random");
	}

	// send it to the card
	sc_format_apdu(card, &apdu, SC_APDU_CASE_4, INS_GENERAL_AUTHENTICATE, 0x00, 0x00);
	apdu.lc = sizeof(apduSetRandom);
	apdu.data = apduSetRandom;
	apdu.datalen = sizeof(apduSetRandom);
	apdu.resp = apduSetRandomResponse;
	apdu.resplen = sizeof(apduSetRandomResponse);
	apdu.le = 256;
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");

	// compute the half size of the mutual authentication secret
	if (RAND_bytes(z1, 7) != 1) {
		sc_log_openssl(card->ctx);
		LOG_TEST_RET(card->ctx, SC_ERROR_INTERNAL, "unable to set computer random");
	}

	// set the padding
	z1[7] = 0x80;

	// Encrypt R2||R1||Z1
	memcpy(buffer, randomR2, 16);
	memcpy(buffer+16, randomR1, 16);
	memcpy(buffer+32, z1, sizeof(z1));
	// init crypto
	ctx = EVP_CIPHER_CTX_new();
	if (ctx == NULL) {
		sc_log_openssl(card->ctx);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}
	cipher = sc_evp_cipher(card->ctx, "DES-EDE3-CBC");
	if (!EVP_EncryptInit(ctx, cipher, key, NULL)) {
		sc_log_openssl(card->ctx);
		EVP_CIPHER_CTX_free(ctx);
		sc_evp_cipher_free(cipher);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}
	EVP_CIPHER_CTX_set_padding(ctx,0);
	if (!EVP_EncryptUpdate(ctx, buffer2, &buffer2size, buffer, sizeof(buffer))) {
		sc_log_openssl(card->ctx);
		EVP_CIPHER_CTX_free(ctx);
		sc_evp_cipher_free(cipher);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}

	if (!EVP_EncryptFinal(ctx, buffer2 + buffer2size, &buffer2size)) {
		sc_log_openssl(card->ctx);
		EVP_CIPHER_CTX_free(ctx);
		sc_evp_cipher_free(cipher);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}
	EVP_CIPHER_CTX_free(ctx);
	ctx = NULL;
	sc_evp_cipher_free(cipher);
	cipher = NULL;
	// send it to the card
	sc_format_apdu(card, &apdu, SC_APDU_CASE_4, INS_GENERAL_AUTHENTICATE, 0x00, 0x00);
	apdu.lc = sizeof(apduSendReponse);
	apdu.data = apduSendReponse;
	apdu.datalen = sizeof(apduSendReponse);
	apdu.resp = apduSendResponseResponse;
	apdu.resplen = sizeof(apduSendResponseResponse);
	apdu.le = 256;
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "invalid return");

	if (apdu.resplen != 44)
	{
		sc_log(card->ctx, "Expecting a response len of 44 - found %d", (int)apdu.resplen);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}
	// init crypto
	ctx = EVP_CIPHER_CTX_new();
	if (ctx == NULL) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}
	cipher = sc_evp_cipher(card->ctx, "DES-EDE3-CBC");
	if (!EVP_DecryptInit(ctx, cipher, key, NULL)) {
		sc_log_openssl(card->ctx);
		sc_evp_cipher_free(cipher);
		EVP_CIPHER_CTX_free(ctx);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
	}
	EVP_CIPHER_CTX_set_padding(ctx,0);
	if (!EVP_DecryptUpdate(ctx, buffer3, &buffer3size, apdu.resp + 4, (int)apdu.resplen - 4)) {
		sc_log_openssl(card->ctx);
		sc_log(card->ctx, "unable to decrypt data");
		sc_evp_cipher_free(cipher);
		EVP_CIPHER_CTX_free(ctx);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	}
	if (!EVP_DecryptFinal(ctx, buffer3 + buffer3size, &buffer3size)) {
		sc_log_openssl(card->ctx);
		sc_log(card->ctx, "unable to decrypt final data");
		sc_evp_cipher_free(cipher);
		EVP_CIPHER_CTX_free(ctx);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	}
	sc_log(card->ctx, "data has been decrypted using the key");
	if (memcmp(buffer3, randomR1, 16) != 0) {
		sc_log_openssl(card->ctx);
		sc_log(card->ctx, "R1 doesn't match");
		sc_evp_cipher_free(cipher);
		EVP_CIPHER_CTX_free(ctx);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	}
	if (memcmp(buffer3 + 16, randomR2, 16) != 0) {
		sc_log_openssl(card->ctx);
		sc_log(card->ctx, "R2 doesn't match");
		sc_evp_cipher_free(cipher);
		EVP_CIPHER_CTX_free(ctx);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	}
	if (buffer[39] != 0x80) {
		sc_log_openssl(card->ctx);
		sc_log(card->ctx, "Padding not found");
		sc_evp_cipher_free(cipher);
		EVP_CIPHER_CTX_free(ctx);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	}
	EVP_CIPHER_CTX_free(ctx);
	ctx = NULL;
	sc_evp_cipher_free(cipher);
	cipher = NULL;

	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
#endif
}

static int gids_card_ctl(sc_card_t * card, unsigned long cmd, void *ptr)
{
	LOG_FUNC_CALLED(card->ctx);
	switch (cmd) {
		case SC_CARDCTL_GET_SERIALNR:
			return gids_get_serialnr(card, (sc_serial_number_t *) ptr);
		case SC_CARDCTL_GIDS_GET_ALL_CONTAINERS:
			return gids_get_all_containers(card, (size_t*) ptr);
		case SC_CARDCTL_GIDS_GET_CONTAINER_DETAIL:
			return gids_get_container_detail(card, (sc_cardctl_gids_get_container_t*) ptr);
		case SC_CARDCTL_GIDS_SELECT_KEY_REFERENCE:
			return gids_select_key_reference(card, (sc_pkcs15_prkey_info_t*) ptr);
		case SC_CARDCTL_GIDS_CREATE_KEY:
			return gids_create_keyfile(card, (sc_pkcs15_object_t*) ptr);
		case SC_CARDCTL_GIDS_GENERATE_KEY:
			return gids_generate_key(card, ((struct sc_cardctl_gids_genkey*) ptr)->object, ((struct sc_cardctl_gids_genkey*) ptr)->pubkey);
		case SC_CARDCTL_GIDS_IMPORT_KEY:
			return gids_import_key(card, ((struct sc_cardctl_gids_importkey*) ptr)->object, ((struct sc_cardctl_gids_importkey*) ptr)->key);
		case SC_CARDCTL_GIDS_SAVE_CERT:
			return gids_save_certificate(card, ((struct sc_cardctl_gids_save_cert*) ptr)->certobject,
										((struct sc_cardctl_gids_save_cert*) ptr)->privkeyobject, ((struct sc_cardctl_gids_save_cert*) ptr)->path);
		case SC_CARDCTL_GIDS_DELETE_CERT:
			return gids_delete_cert(card, (sc_pkcs15_object_t*) ptr);
		case SC_CARDCTL_GIDS_DELETE_KEY:
			return gids_delete_key(card, (sc_pkcs15_object_t*) ptr);
		case SC_CARDCTL_GIDS_INITIALIZE:
			return gids_initialize(card, (sc_cardctl_gids_init_param_t*) ptr);
		case SC_CARDCTL_GIDS_SET_ADMIN_KEY:
			return gids_set_administrator_key(card, (u8*) ptr);
		case SC_CARDCTL_GIDS_AUTHENTICATE_ADMIN:
			return gids_authenticate_admin(card, (u8*) ptr);
		default:
			return SC_ERROR_NOT_SUPPORTED;
	}
}

static int gids_card_reader_lock_obtained(sc_card_t *card, int was_reset)
{
	int r = SC_SUCCESS;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	if (was_reset > 0) {
		u8 rbuf[SC_MAX_APDU_BUFFER_SIZE];
		size_t resplen = sizeof(rbuf);
		r = gids_select_aid(card, gids_aid.value, gids_aid.len, rbuf, &resplen);
	}

	LOG_FUNC_RETURN(card->ctx, r);
}

static struct sc_card_driver *sc_get_driver(void)
{

	if (iso_ops == NULL)
		iso_ops = sc_get_iso7816_driver()->ops;

	gids_ops.match_card = gids_match_card;
	gids_ops.init = gids_init;
	gids_ops.finish = gids_finish;
	gids_ops.read_binary = gids_read_binary;
	gids_ops.write_binary = NULL;
	gids_ops.update_binary = NULL;
	gids_ops.erase_binary = NULL;
	gids_ops.read_record = NULL;
	gids_ops.write_record = NULL;
	gids_ops.append_record = NULL;
	gids_ops.update_record = NULL;
	gids_ops.select_file = gids_select_file;
	gids_ops.get_response = iso_ops->get_response;
	gids_ops.get_challenge = NULL;
	gids_ops.verify = NULL; // see pin_cmd
	gids_ops.logout = gids_logout;
	gids_ops.restore_security_env = NULL;
	gids_ops.set_security_env = gids_set_security_env;
	gids_ops.decipher = gids_decipher;
	gids_ops.compute_signature = iso_ops->compute_signature;
	gids_ops.change_reference_data = NULL; // see pin_cmd
	gids_ops.reset_retry_counter = NULL; // see pin_cmd
	gids_ops.create_file = iso_ops->create_file;
	gids_ops.delete_file = NULL;
	gids_ops.list_files = NULL;
	gids_ops.check_sw = iso_ops->check_sw;
	gids_ops.card_ctl = gids_card_ctl;
	gids_ops.process_fci = iso_ops->process_fci;
	gids_ops.construct_fci = iso_ops->construct_fci;
	gids_ops.pin_cmd = gids_pin_cmd;
	gids_ops.get_data = NULL;
	gids_ops.put_data = NULL;
	gids_ops.delete_record = NULL;
	gids_ops.read_public_key = gids_read_public_key;
	gids_ops.card_reader_lock_obtained = gids_card_reader_lock_obtained;

	return &gids_drv;
}

struct sc_card_driver *sc_get_gids_driver(void)
{
	return sc_get_driver();
}

#else

struct sc_card_driver *sc_get_gids_driver(void)
{
	return NULL;
}

#endif
