/*
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Initially written by David Mattes (david.mattes@boeing.com) */
 
#include "internal.h"
#include "pkcs15.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define MANU_ID			"GemPlus"
#define APPLET_NAME		"GemSAFE"
#define DRIVER_SERIAL_NUMBER	"v0.9"

typedef struct cdata_st {
	const char *label;
	int	    authority;
	const char *path;
	const char *id;
	int         obj_flags;
} cdata;

const cdata gemsafe_cert[] = {
	{"DS certificate", 0, "3F0016000004","45", SC_PKCS15_CO_FLAG_MODIFIABLE},
	{NULL, 0, NULL, 0, 0}
};

typedef struct pdata_st {
	const char *id;
	const char *label;
	const char *path;
	int         ref;
	int         type;
	unsigned int maxlen;
	unsigned int minlen;
	int         flags;	
	int         tries_left;
	const char  pad_char;
	int         obj_flags;
} pindata;  

const pindata gemsafe_pin[] = {
	{ "01", "DS pin", NULL, 0x01, SC_PKCS15_PIN_TYPE_BCD,
	  16, 6, SC_PKCS15_PIN_FLAG_NEEDS_PADDING | SC_PKCS15_PIN_FLAG_LOCAL, 
	  3, 0xFF, 
	  SC_PKCS15_CO_FLAG_MODIFIABLE | SC_PKCS15_CO_FLAG_PRIVATE },
	{ NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0}
};

typedef struct prdata_st {
	const char *id;
	const char *label;
	unsigned int modulus_len;
	int         usage;
	const char *path;
	int         ref;
	const char *auth_id;
	int         obj_flags;
} prdata;

#define USAGE_NONREP	SC_PKCS15_PRKEY_USAGE_NONREPUDIATION
#define USAGE_KE	SC_PKCS15_PRKEY_USAGE_ENCRYPT | \
			SC_PKCS15_PRKEY_USAGE_DECRYPT | \
			SC_PKCS15_PRKEY_USAGE_WRAP    | \
			SC_PKCS15_PRKEY_USAGE_UNWRAP
#define USAGE_AUT	SC_PKCS15_PRKEY_USAGE_ENCRYPT | \
			SC_PKCS15_PRKEY_USAGE_DECRYPT | \
			SC_PKCS15_PRKEY_USAGE_WRAP    | \
			SC_PKCS15_PRKEY_USAGE_UNWRAP  | \
			SC_PKCS15_PRKEY_USAGE_SIGN

const prdata gemsafe_prkeys[] = {
	{ "45", "DS key", 1024, USAGE_AUT, NULL,
	  0x03, "01", SC_PKCS15_CO_FLAG_PRIVATE},
	{ NULL, NULL, 0, 0, NULL, 0, NULL, 0}
};

int gemsafe_get_cert_len(sc_card_t *card, sc_path_t *path)
{
	const char *fn_name = "gemsafe_get_cert_len";
	int r;
	u8  ibuf[248];
	struct sc_file *file;
	size_t objlen, certlen;
	int block=0;
	int found = 0;
	unsigned int offset=0, index, i;

	r = sc_select_file(card, path, &file);
	if (r < 0)
		return 0;

	/* Apparently, the Applet max read "quanta" is 248 bytes */
	/* Initial read */
	r = sc_read_binary(card, offset, ibuf, 248, 0);
	if (r < 0)	
		return 0;

	/* Actual stored object size is encoded in first 2 bytes
	 * (allocated EF space is much greater!)
	 */
	objlen = (((size_t) ibuf[0]) << 8) | ibuf[1];
	//fprintf(stderr, "%s: Certificate object is of size: %d\n", fn_name, objlen);

	if (objlen < 1 || objlen > 10240) {
	    fprintf(stderr, "%s: Invalid object size: %d\n", fn_name, objlen);
	    return 0;
	}

	/* Using (block+1) in while loop avoids using final cert object data block */
	while (!found && ( (block+1) * 248 < objlen) ) {
	    /* Check current buffer */
	    for (i = 0; i < 248; i++) {
	    	if (ibuf[i] == 0x30 && ibuf[i+1] == 0x82) {
		    found = 1;
		    break;
		}
	    }

	    /* Grab another buffer */
	    if (!found) {
		block++;
		offset = block*248;
		r = sc_read_binary(card, offset, ibuf, 248, 0);
		if (r < 0) {
		    fprintf(stderr, "%s: Could not read cert object\n", fn_name);
		    return 0;
		}
	    }

	}

	index = block*248 + i;

	/* DER Cert len is encoded this way */
	certlen = ((((size_t) ibuf[i+2]) << 8) | ibuf[i+3]) + 4;
	//fprintf(stderr, "%s: certlen: %04X\n", fn_name, certlen);

	path->index = index;
	path->count = certlen;

	return 1;
	    
}

static int gemsafe_detect_card( sc_pkcs15_card_t *p15card)
{
    fprintf(stderr, "In gemsafe_detect_card\n");
    return SC_SUCCESS;
}

int sc_pkcs15emu_gemsafe_init( sc_pkcs15_card_t *p15card)
{
    const char *fn_name = "sc_pkcs15emu_gemsafe_init";

    int    r, i;
    struct sc_path path;
    struct sc_file *file = NULL;
    struct sc_card *card = p15card->card;
    struct sc_apdu apdu;
    u8     rbuf[SC_MAX_APDU_BUFFER_SIZE];
    char * endptr;
    float version=0.0;

    fprintf(stderr, "%s: Setting pkcs15 parameters\n", fn_name);

    if (p15card->label)
    	free(p15card->label);
    p15card->label = malloc(strlen(APPLET_NAME) + 1);
    if (!p15card->label)
    	return SC_ERROR_INTERNAL;
    strcpy(p15card->label, APPLET_NAME);

    if (p15card->serial_number)
	    free(p15card->serial_number);
    p15card->serial_number = malloc(strlen(DRIVER_SERIAL_NUMBER) + 1);
    if (!p15card->serial_number)
	    return SC_ERROR_INTERNAL;
    strcpy(p15card->serial_number, DRIVER_SERIAL_NUMBER);

    /* the GemSAFE applet version number */
    sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xca, 0xdf, 0x03);
    apdu.cla = 0x80;
    apdu.resp = rbuf;
    apdu.resplen = sizeof(rbuf);
    /* Manual says Le=0x05, but should be 0x08 to return full version numer */
    apdu.le = 0x08;  
    apdu.lc = 0;
    apdu.datalen = 0;
    r = sc_transmit_apdu(card, &apdu);
    SC_TEST_RET(card->ctx, r, "APDU transmit failed");
    if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00)
	    return SC_ERROR_INTERNAL;
    if (r != SC_SUCCESS)
	    return SC_ERROR_INTERNAL;
    endptr = (char *)(apdu.resp + apdu.resplen);
    version = strtod( (const char *)(apdu.resp + 4), &endptr);
    fprintf(stderr, "%s: version (float): %f, version (int): %d\n", 
    	    fn_name, version, (int)version);
    p15card->version = (int)version;

    /* the manufacturer ID, in this case GemPlus */
    if (p15card->manufacturer_id)
	    free(p15card->manufacturer_id);
    p15card->manufacturer_id = malloc(strlen(MANU_ID) + 1);
    if (!p15card->manufacturer_id)
	    return SC_ERROR_INTERNAL;
    strcpy(p15card->manufacturer_id, MANU_ID);

    /* Add RSA algorithm */
    card->algorithm_count = 0;
    _sc_card_add_rsa_alg( card, 1024, SC_ALGORITHM_RSA_PAD_PKCS1, 0);

    /* set certs */
    fprintf(stderr, "%s: Setting certificate\n", fn_name);
    for (i = 0; gemsafe_cert[i].label; i++) {
	    struct sc_pkcs15_id  p15Id;

	    sc_format_path(gemsafe_cert[i].path, &path);
	    if (!gemsafe_get_cert_len(card, &path))
		    /* skip errors */
		    continue;
	    sc_pkcs15_format_id(gemsafe_cert[i].id, &p15Id);
	    sc_pkcs15emu_add_cert(p15card, SC_PKCS15_TYPE_CERT_X509,
			    gemsafe_cert[i].authority, &path, &p15Id,
			    gemsafe_cert[i].label, gemsafe_cert[i].obj_flags);
    }
    /* set gemsafe_pin */
    fprintf(stderr, "%s: Setting PIN\n", fn_name);
    for (i = 0; gemsafe_pin[i].label; i++) {
	    struct sc_pkcs15_id  p15Id;

	    sc_pkcs15_format_id(gemsafe_pin[i].id, &p15Id);
	    sc_pkcs15emu_add_pin(p15card, &p15Id, gemsafe_pin[i].label,
			    &path, gemsafe_pin[i].ref, gemsafe_pin[i].type,
			    gemsafe_pin[i].minlen, gemsafe_pin[i].maxlen, 
			    gemsafe_pin[i].flags,
			    gemsafe_pin[i].tries_left, gemsafe_pin[i].pad_char,
			    gemsafe_pin[i].obj_flags);
    }
    /* set private keys */
    fprintf(stderr, "%s: Setting private key\n", fn_name);
    for (i = 0; gemsafe_prkeys[i].label; i++) {
	    struct sc_pkcs15_id p15Id, 
				authId, *pauthId;
	    sc_pkcs15_format_id(gemsafe_prkeys[i].id, &p15Id);
	    if (gemsafe_prkeys[i].auth_id) {
		    sc_pkcs15_format_id(gemsafe_prkeys[i].auth_id, &authId);
		    pauthId = &authId;
	    } else	
		    pauthId = NULL;
	    sc_pkcs15emu_add_prkey(p15card, &p15Id, gemsafe_prkeys[i].label,
			    SC_PKCS15_TYPE_PRKEY_RSA,
			    gemsafe_prkeys[i].modulus_len, gemsafe_prkeys[i].usage,
			    &path, gemsafe_prkeys[i].ref, pauthId,
			    gemsafe_prkeys[i].obj_flags);
    }
	    
    /* select the application DF */
    fprintf(stderr,"%s: Selecting application DF\n", fn_name);
    sc_format_path("3F001600", &path);
    r = sc_select_file(card, &path, &file);
    if (r != SC_SUCCESS || !file)
	    return SC_ERROR_INTERNAL;
    /* set the application DF */
    if (p15card->file_app)
	    free(p15card->file_app);
    p15card->file_app = file;

    return SC_SUCCESS;

}

//int sc_pkcs15_init_func_ex( sc_pkcs15_card_t *p15card,
int sc_pkcs15emu_gemsafe_init_ex( sc_pkcs15_card_t *p15card,
			sc_pkcs15emu_opt_t *opts)
{
    if (opts && opts->flags & SC_PKCS15EMU_FLAGS_NO_CHECK)
	    return sc_pkcs15emu_gemsafe_init(p15card);
    else {
	    int r = gemsafe_detect_card(p15card);
	    if (r)
		    return SC_ERROR_WRONG_CARD;
	    return sc_pkcs15emu_gemsafe_init(p15card);
    }

}

//SC_IMPLEMENT_DRIVER_VERSION("0.9.4")
