/*
 * Copyright (C) 2018 Frank Morgner <frankmorgner@gmail.com>
 *
 * This file is part of OpenSC.
 *
 * 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
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "fread_to_eof.h"
#include <string.h>
#include "goid-tool-cmdline.h"
#include "libopensc/asn1.h"
#include "libopensc/log.h"
#include "libopensc/opensc.h"
#include "sm/sm-eac.h"
#ifdef ENABLE_OPENPACE
#include <eac/eac.h>
#endif
#include <stdlib.h>
#include "util.h"
#include <ctype.h>

const unsigned char aid_soc_manager[] = {
    0xD2, 0x76, 0x00, 0x01, 0x72, 0x53, 0x6F, 0x43, 0x4D, 0x01
};
static const unsigned char paccess_aid[] = {
    0xD2, 0x76, 0x00, 0x01, 0x72, 0x50, 0x41, 0x63, 0x63, 0x01,
};
static const char *app_name = "goid-tool";

#define SOCM_AUTHOBJECT_PIN 0x80
#define SOCM_AUTHOBJECT_BIO 0x40
#define SOCM_AUTHOBJECT_GP  0x20

void
print_permissions(u8 permissions)
{
    size_t perms_printed = 0;
    if (permissions & SOCM_AUTHOBJECT_PIN) {
        printf("verification of PIN");
        perms_printed++;
    }
    if (permissions & SOCM_AUTHOBJECT_BIO) {
        printf("%s BIO", perms_printed ? " or" : "verification of");
        perms_printed++;
    }
    if (permissions & SOCM_AUTHOBJECT_GP) {
        printf("%s GP key", perms_printed ? " or" : "verification of");
        perms_printed++;
    }
    printf("\n");
}

void
soc_info(sc_context_t *ctx, sc_card_t *card)
{
    sc_apdu_t apdu;
    unsigned char rbuf[SC_MAX_APDU_RESP_SIZE];
    u8 information_applets[SC_MAX_APDU_RESP_SIZE];
    size_t information_applets_len = sizeof information_applets;
    int pin_initialized = 0, bio_initialized = 0;
    int pin_max_retries = 0, pin_cur_retries = 0, bio_max_retries = 0, bio_cur_retries = 0;
    int pin_length = 0;
    u8 pin_unblock = 0, pin_change = 0, bio_unblock = 0, bio_change = 0;
    size_t pin_change_len = sizeof pin_change, pin_unblock_len = sizeof pin_unblock,
           bio_change_len = sizeof bio_change, bio_unblock_len = sizeof bio_unblock;
    int bio_count = 0;
    u8 bio_initialized_templates[2];
    size_t bio_initialized_templates_len = sizeof bio_initialized_templates;

    struct sc_asn1_entry rapdu_get_information[] = {
        { "Sequence of (applet register)", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_SEQUENCE|SC_ASN1_CONS, 0, NULL, NULL },
        { "Initialized PIN",               SC_ASN1_STRUCT, SC_ASN1_APP|SC_ASN1_CONS|0x02, SC_ASN1_OPTIONAL, NULL, NULL },
        { "Initialized BIO",               SC_ASN1_STRUCT, SC_ASN1_APP|SC_ASN1_CONS|0x03, SC_ASN1_OPTIONAL, NULL, NULL },
        { NULL , 0 , 0 , 0 , NULL , NULL }
    };

    struct sc_asn1_entry rapdu_get_information_pin[] = {
        { "Initialization state of the PIN", SC_ASN1_BOOLEAN, SC_ASN1_TAG_BOOLEAN, 0, NULL, NULL },
        { "maximum remaining tries",         SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
        { "current remaining tries",         SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
        { "Unblock requirements Mask",       SC_ASN1_OCTET_STRING, SC_ASN1_APP|0x1, 0, NULL, NULL },
        { "Change requirements Mask",        SC_ASN1_OCTET_STRING, SC_ASN1_APP|0x2, 0, NULL, NULL },
        { "PIN size",                        SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
        { NULL , 0 , 0 , 0 , NULL , NULL }
    };

    struct sc_asn1_entry rapdu_get_information_bio[] = {
        { "Initialization state of the BIO",           SC_ASN1_BOOLEAN, SC_ASN1_TAG_BOOLEAN, 0, NULL, NULL },
        { "maximum remaining tries",                   SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
        { "current remaining tries",                   SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
        { "Unblock requirements Mask",                 SC_ASN1_OCTET_STRING, SC_ASN1_APP|0x1, 0, NULL, NULL },
        { "Change requirements Mask",                  SC_ASN1_OCTET_STRING, SC_ASN1_APP|0x2, 0, NULL, NULL },
        { "Min minutiae",                              SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
        { "Max minutiae",                              SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
        { "number of templates",                       SC_ASN1_INTEGER, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
        { "Bitmap of initialized templates",           SC_ASN1_BIT_STRING, SC_ASN1_TAG_BIT_STRING, 0, NULL, NULL },
        { "Algorithm parameters, allocation strategy", SC_ASN1_OCTET_STRING, SC_ASN1_TAG_OCTET_STRING, 0, NULL, NULL },
        { NULL , 0 , 0 , 0 , NULL , NULL }
    };

    sc_format_apdu_ex(&apdu, 0x00, 0x61, 0x00, 0x00, NULL, 0, rbuf, sizeof rbuf);
    apdu.cla = 0x80;

    if (sc_transmit_apdu(card, &apdu) != SC_SUCCESS) {
        return;
    }

    sc_format_asn1_entry(rapdu_get_information + 0, information_applets, &information_applets_len, 0);
    sc_format_asn1_entry(rapdu_get_information + 1, rapdu_get_information_pin, NULL, 0);
    sc_format_asn1_entry(rapdu_get_information + 2, rapdu_get_information_bio, NULL, 0);

    sc_format_asn1_entry(rapdu_get_information_pin + 0, &pin_initialized, NULL, 0);
    sc_format_asn1_entry(rapdu_get_information_pin + 1, &pin_max_retries, NULL, 0);
    sc_format_asn1_entry(rapdu_get_information_pin + 2, &pin_cur_retries, NULL, 0);
    sc_format_asn1_entry(rapdu_get_information_pin + 3, &pin_unblock, &pin_unblock_len, 0);
    sc_format_asn1_entry(rapdu_get_information_pin + 4, &pin_change, &pin_change_len, 0);
    sc_format_asn1_entry(rapdu_get_information_pin + 5, &pin_length, NULL, 0);

    sc_format_asn1_entry(rapdu_get_information_bio + 0, &bio_initialized, NULL, 0);
    sc_format_asn1_entry(rapdu_get_information_bio + 1, &bio_max_retries, NULL, 0);
    sc_format_asn1_entry(rapdu_get_information_bio + 2, &bio_cur_retries, NULL, 0);
    sc_format_asn1_entry(rapdu_get_information_bio + 3, &bio_unblock, &bio_unblock_len, 0);
    sc_format_asn1_entry(rapdu_get_information_bio + 4, &bio_change, &bio_change_len, 0);
    sc_format_asn1_entry(rapdu_get_information_bio + 7, &bio_count, NULL, 0);
    sc_format_asn1_entry(rapdu_get_information_bio + 8, bio_initialized_templates, &bio_initialized_templates_len, 0);

    if (sc_asn1_decode(ctx, rapdu_get_information,
                apdu.resp, apdu.resplen, NULL, NULL) != SC_SUCCESS) {
        return;
    }

    if (rapdu_get_information[0].flags & SC_ASN1_PRESENT && information_applets_len > 0) {
        const unsigned char *p = information_applets, *end = information_applets + information_applets_len;
        unsigned int cla = 0, tag = 0;
        size_t length = information_applets_len;

        if (SC_SUCCESS == sc_asn1_read_tag(&p, length, &cla, &tag, &length)
                && cla == SC_ASN1_TAG_UNIVERSAL && tag == SC_ASN1_TAG_INTEGER) {
            int applet_count = 0;
            /* number of applets */
            if (SC_SUCCESS == sc_asn1_decode_integer(p, length, &applet_count, 0)) {
                printf("SoCManager knows %d applet%s%s\n", applet_count,
                        applet_count == 1 ? "" : "s", applet_count == 0 ? "" : ":");
                /* AID of client applet #x */
                for (p += length, length = end - p;
                        p < end;
                        p += length, length = end - p) {
                    size_t i;
                    if (SC_SUCCESS != sc_asn1_read_tag(&p, length, &cla, &tag, &length)
                            || p == NULL || cla != SC_ASN1_TAG_CONTEXT) {
                        break;
                    }
                    putchar('\t');
                    util_hex_dump(stdout, p, length, "");
                    /* align with the maximum length of an AID */
                    for (i = length; i < 0x10 + 1; i++)
                        printf("  ");

                    /* i now counts the number of flags that were printed */
                    i = 0;
                    if (tag & 0x02) {
                        printf("default selected");
                        i++;
                    }
                    if (tag & 0x01) {
                        printf("%sinteracts with SoCManager", i ? ", " : "");
                        i++;
                    }
                    if (tag & 0x04) {
                        printf("%sBIO enabled", i ? ", " : "");
                        i++;
                    }
                    if (tag & 0x08) {
                        printf("%sPIN enabled", i ? ", " : "");
                        i++;
                    }
                    printf("\n");
                }
            }
        }
    }

    if (rapdu_get_information[1].flags & SC_ASN1_PRESENT) {
        if (pin_initialized) {
            printf("PIN is initialized with %d digits (%d of %d tries left).\n",
                    pin_length, pin_cur_retries, pin_max_retries);
        } else {
            printf("PIN is not initialized.\n");
        }
        printf("\tChanging PIN requires ");
        print_permissions(pin_change);
        printf("\tUnblocking PIN requires ");
        print_permissions(pin_unblock);
    }

    if (rapdu_get_information[2].flags & SC_ASN1_PRESENT) {
        if (bio_initialized) {
            int bio_used = 0;
            size_t i, j;
            for (i = 0; i < sizeof bio_initialized_templates; i++) {
                for (j = 0; j < 8; j++) {
                    if (bio_initialized_templates[i] >> j & 0x1)
                        bio_used++;
                }
            }
            printf("BIO is initialized with %d of %d templates (%d of %d tries left).\n",
                    bio_used, bio_count, bio_cur_retries, bio_max_retries);
        } else {
            printf("BIO is not initialized.\n");
        }
        printf("\tChanging BIO requires ");
        print_permissions(bio_change);
        printf("\tUnblocking BIO requires ");
        print_permissions(bio_unblock);
    }
}

int
soc_verify(sc_card_t *card, unsigned char p2)
{
    int ok = 0;
    sc_apdu_t apdu;
    sc_format_apdu_ex(&apdu, 0x00, 0x20, 0x00, p2, NULL, 0, NULL, 0);
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_transmit_apdu(card, &apdu),
            "Verification failed");
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_check_sw(card, apdu.sw1, apdu.sw2),
            "Verification failed");
    ok = 1;
err:
    return ok;
}

int
soc_reset(sc_card_t *card, unsigned char p2)
{
    int ok = 0;
    sc_apdu_t apdu;
    sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x20, 0xFF, p2);
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_transmit_apdu(card, &apdu),
            "Reset failed");
    if (apdu.sw1 != 0x63) {
        SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                sc_check_sw(card, apdu.sw1, apdu.sw2),
                "Reset failed");
    }
    ok = 1;
err:
    return ok;
}

int
soc_change(sc_card_t *card, unsigned char p1, unsigned char p2)
{
    int ok = 0;
    sc_apdu_t apdu;
    sc_format_apdu_ex(&apdu, 0x00, 0x24, p1, p2, NULL, 0, NULL, 0);
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_transmit_apdu(card, &apdu),
            "Changing secret failed");
    while (apdu.sw1 == 0x91 && apdu.sw2 == 0x00) {
        switch (p2) {
            case SOCM_AUTHOBJECT_PIN:
                sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                        "Verify your PIN on the card using the same position.");
                break;
            case SOCM_AUTHOBJECT_BIO:
                sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                        "Verify your finger print on the card using the same position.");
                break;
        }
        SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                sc_transmit_apdu(card, &apdu),
                "Changing secret failed");
    }
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_check_sw(card, apdu.sw1, apdu.sw2),
            "Verification failed");
    ok = 1;
err:
    return ok;
}

int soc_main(struct sc_context *ctx, sc_card_t *card, struct gengetopt_args_info *cmdline)
{
    int ok = 0;
    sc_file_t *file = NULL;
    struct sc_path path;
    unsigned char soc_manager_minor = 0;
    unsigned char soc_manager_major = 0;
    unsigned char soc_reset_authobject = 0;

    sc_path_set(&path, SC_PATH_TYPE_DF_NAME, aid_soc_manager, sizeof aid_soc_manager, 0, 0);
    SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_select_file(card, &path, &file), "SoCManager not found.");
    if (file && file->prop_attr && file->prop_attr_len) {
        size_t prop_len = 0;
        const u8 *prop = sc_asn1_find_tag(ctx, file->prop_attr,
                file->prop_attr_len, 0x81, &prop_len);
        if (prop && prop_len == 2) {
            soc_manager_major = prop[0];
            soc_manager_minor = prop[1];
            sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    "SoCManager version %u.%u",
                    soc_manager_major, soc_manager_minor);
        }
    }

    if (cmdline->info_given) {
        if ((soc_manager_major == 2 && soc_manager_minor < 7)
                || soc_manager_major < 2)
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    SC_ERROR_NOT_SUPPORTED, "Get Information only supported with version 2.07 and later.");
        soc_info(ctx, card);
    }
    if (cmdline->verify_pin_given) {
        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                "Verify PIN on the card.");
        if (!soc_verify(card, SOCM_AUTHOBJECT_PIN))
            goto err;
        soc_reset_authobject |= SOCM_AUTHOBJECT_PIN;
    }
    if (cmdline->verify_bio_given) {
        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                "Verify finger print on the card.");
        if (!soc_verify(card, SOCM_AUTHOBJECT_BIO))
            goto err;
        soc_reset_authobject |= SOCM_AUTHOBJECT_BIO;
    }
    if (cmdline->verify_pin_or_bio_given) {
        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                "Verify finger print or PIN on the card.");
        if (!soc_verify(card, SOCM_AUTHOBJECT_PIN|SOCM_AUTHOBJECT_BIO))
            goto err;
        soc_reset_authobject |= SOCM_AUTHOBJECT_PIN|SOCM_AUTHOBJECT_BIO;
    }

    if (cmdline->new_pin_given) {
        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                "Initialize the PIN on the card.");
        if (!soc_change(card, 0x00, SOCM_AUTHOBJECT_BIO))
            goto err;
    }
    if (cmdline->new_bio_given) {
        size_t i = 0;
        while (i < cmdline->new_bio_given) {
            sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    "Initialize finger print template %u on the card.",
                    (unsigned char) i);
            if (!soc_change(card, (unsigned char) i, SOCM_AUTHOBJECT_BIO))
                goto err;
            i++;
        }
    }

    ok = 1;

err:
    if (soc_reset_authobject)
        soc_reset(card, soc_reset_authobject);

    return ok;
}

static int
paccess_construct_fci(struct sc_card *card, const sc_file_t *file,
		u8 *out, size_t *outlen)
{
	u8 *p = out;
	u8 buf[64];

	if (*outlen < 2)
		return SC_ERROR_BUFFER_TOO_SMALL;
	*p++ = 0x62;
	p++;

	buf[0] = (file->size >> 8) & 0xFF;
	buf[1] = file->size & 0xFF;
	sc_asn1_put_tag(0x80, buf, 2, p, *outlen - (p - out), &p);

	buf[0] = (file->id >> 8) & 0xFF;
	buf[1] = file->id & 0xFF;
	sc_asn1_put_tag(0x83, buf, 2, p, *outlen - (p - out), &p);

    memcpy(buf, file->sec_attr, file->sec_attr_len);
    sc_asn1_put_tag(0x86, buf, file->sec_attr_len,
            p, *outlen - (p - out), &p);

    buf[0] = file->sid & 0xFF;
	sc_asn1_put_tag(0x88, buf, 1, p, *outlen - (p - out), &p);

	out[1] = p - out - 2;

	*outlen = p - out;
	return 0;
}

int
paccess_create_file(struct sc_card *card, size_t size, int fid, u8 *sec_attr, size_t sec_attr_len, int sfid)
{
    int ok = 0;
    sc_file_t *file = sc_file_new();
    if (!file)
        goto err;

    file->size = size;
    file->id = fid;
    file->sid = sfid;
	file->sec_attr = sec_attr;
	file->sec_attr_len = sec_attr_len;

    card->ops->construct_fci = paccess_construct_fci;
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_create_file(card, file), "Create file failed.");

    ok = 1;
err:
    return ok;
}

int
paccess_delete_file(struct sc_card *card, int fid)
{
    int ok = 0;
    u8 buf[2];
    struct sc_path path;
	buf[0] = (fid >> 8) & 0xFF;
	buf[1] = fid & 0xFF;
    sc_path_set(&path, SC_PATH_TYPE_FILE_ID, buf, sizeof buf, 0, 0);
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_delete_file(card, &path), "Delete file failed.");
    ok = 1;
err:
    return ok;
}

int
paccess_get_security_attributes(struct sc_context *ctx, const char *ac, short *chatbits, size_t chatbits_len, u8 sec_attr[2])
{
    int ok = 0;
    memset(sec_attr, 0, 2);
    if (!ac || 0 == strcmp(ac, "never")) {
        sec_attr[0] |= 0xFF;
    } else if (0 == strcmp(ac, "always")) {
        /* nothing else to do */
    } else {
        size_t i;
        if (0 == strcmp(ac, "ta")) {
            sec_attr[0] |= 0xA0;
        } else if (0 == strcmp(ac, "sm")) {
            sec_attr[0] |= 0xC0;
        } else {
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    SC_ERROR_INVALID_ARGUMENTS, "unknown access condition.");
        }
        for (i = 0; i < chatbits_len; i++) {
            u8 byte = chatbits[i] / 8;
            u8 bit = chatbits[i] % 8 + 1;
            if (byte > 5)
                SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                        SC_ERROR_INVALID_ARGUMENTS, "Only CHAT bits with index 0..39 are available.");
            sec_attr[0] |= 0x8 | byte;
            sec_attr[1] |= bit;
        }
    }
    ok = 1;
err:
    return ok;
}

#define PXS_AUTHOBJECT_PIN 0x80
#define PXS_AUTHOBJECT_BIO 0x40

int
paccess_verify(sc_card_t *card, unsigned char p2)
{
    int ok = 0;
    sc_apdu_t apdu;
    sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x2E, 0x24, p2);
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_transmit_apdu(card, &apdu),
            "Verification failed");
    SC_TEST_GOTO_ERR(card->ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_check_sw(card, apdu.sw1, apdu.sw2),
            "Verification failed");
    ok = 1;
err:
    return ok;
}

int paccess_main(struct sc_context *ctx, sc_card_t *card, struct gengetopt_args_info *cmdline)
{
    int ok = 0, r;
    sc_file_t *file = NULL;
    struct sc_path path;
    size_t i, ef_cardsecurity_len = 0, privkey_len = 0, *certs_lens = NULL;
    unsigned char *ef_cardsecurity = NULL, *privkey = NULL,
                  **certs = NULL;
    unsigned char auxiliary_data[] = {0x67, 0x00};
    unsigned char paccess_minor = 0;
    unsigned char paccess_major = 0;
    int pxs_reset_authobjects = 0;

    sc_path_set(&path, SC_PATH_TYPE_DF_NAME, paccess_aid, sizeof paccess_aid, 0, 0);
    SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
            sc_select_file(card, &path, &file), "PAccess not found.");
    if (file && file->prop_attr && file->prop_attr_len) {
        const unsigned char *p, *end;
        unsigned int cla = 0, tag = 0;
        size_t length;

        for (p = file->prop_attr, length = file->prop_attr_len, end = file->prop_attr + file->prop_attr_len;
                p < end;
                p += length, length = end - p) {
            if (SC_SUCCESS != sc_asn1_read_tag(&p, length, &cla, &tag, &length)
                    || p == NULL) {
                break;
            }
            switch (cla | tag) {
                case 0x81:
                    if (p && length == 2) {
                        paccess_major = p[0];
                        paccess_minor = p[1];
                        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                                "PAccess version %u.%u",
                                paccess_major, paccess_minor);
                    }
                    break;
                case 0x82:
                    if (p && length == 1) {
                        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                                "Number of Session Contexts %u",
                                p[0]);
                    }
                    break;
                case 0x87:
                    sc_debug_hex(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                            "Certificate Authority Reference of the primary CVCA trust anchor",
                            p, length);
                    break;
                case 0x88:
                    sc_debug_hex(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                            "Certificate Authority Reference of the secondary CVCA trust anchor",
                            p, length);
                    break;
                case 0x1fe5:
                case 0x9F65:
                    if (p && length == 2) {
                        size_t max_command_size = (p[0]<<8)|p[1];
                        card->max_recv_size = max_command_size;
                        card->max_send_size = max_command_size;
                        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                                "Maximum data length in command message %"SC_FORMAT_LEN_SIZE_T"u bytes",
                                max_command_size);
                    }
                    break;
            }
        }
    }

    if (cmdline->certificate_given || cmdline->key_given) {
        if (!fread_to_eof(cmdline->key_arg,
                    &privkey, &privkey_len)) {
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    SC_ERROR_INVALID_ARGUMENTS, "Could not parse private key.\n");
        }

        certs = calloc(cmdline->certificate_given + 1, sizeof *certs);
        certs_lens = calloc(cmdline->certificate_given + 1,
                sizeof *certs_lens);
        if (!certs || !certs_lens) {
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, SC_ERROR_NOT_ENOUGH_MEMORY,
                    "Internal error.");
        }
        for (i = 0; i < cmdline->certificate_given; i++) {
            if (!fread_to_eof(cmdline->certificate_arg[i],
                        (unsigned char **) &certs[i], &certs_lens[i])) {
                SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                        SC_ERROR_INVALID_ARGUMENTS, "Could not read certificate.\n");
            }
        }

        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                perform_terminal_authentication(card,
                    (const unsigned char **) certs, certs_lens,
                    privkey, privkey_len,
                    auxiliary_data, sizeof auxiliary_data),
                "Terminal authentication failed.");

        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                perform_chip_authentication(card,
                    &ef_cardsecurity, &ef_cardsecurity_len),
                "Chip authentication failed.");
    }

    if (cmdline->verify_pin_given) {
        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                "Verify PIN on the card.");
        if (!paccess_verify(card, PXS_AUTHOBJECT_PIN))
            goto err;
        pxs_reset_authobjects++;
    }
    if (cmdline->verify_bio_given) {
        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                "Verify finger print on the card.");
        if (!paccess_verify(card, PXS_AUTHOBJECT_BIO))
            goto err;
        pxs_reset_authobjects++;
    }
    if (cmdline->verify_pin_or_bio_given) {
        sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                "Verify finger print or PIN on the card.");
        if (!paccess_verify(card, PXS_AUTHOBJECT_PIN|PXS_AUTHOBJECT_BIO))
            goto err;
        pxs_reset_authobjects++;
    }

    for (i = 0; i < cmdline->delete_dg_given; i++) {
        int fid = 0x0100 | cmdline->delete_dg_arg[i];

        if ((paccess_major == 2 && paccess_minor < 6)
                || paccess_major < 2)
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    SC_ERROR_NOT_SUPPORTED, "Create File only supported with version 2.06 and later.");
        if (!paccess_delete_file(card, fid))
            goto err;
    }

    for (i = 0; i < cmdline->create_dg_given; i++) {
        u8 sec_attr[4];
        int fid = 0x0100 | cmdline->create_dg_arg[i];

        if ((paccess_major == 2 && paccess_minor < 6)
                || paccess_major < 2)
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    SC_ERROR_NOT_SUPPORTED, "Create File only supported with version 2.06 and later.");

        if (cmdline->new_size_arg < 0)
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    SC_ERROR_INVALID_ARGUMENTS, "`--new-size' needs a positive size.\n");
        if (!paccess_get_security_attributes(ctx, cmdline->new_read_ac_arg,
                    cmdline->new_read_ac_chatbit_arg,
                    cmdline->new_read_ac_chatbit_given, sec_attr + 0)
                || !paccess_get_security_attributes(ctx, cmdline->new_write_ac_arg,
                    cmdline->new_write_ac_chatbit_arg,
                    cmdline->new_write_ac_chatbit_given, sec_attr + 2)
                || !paccess_create_file(card, cmdline->new_size_arg, fid,
                    sec_attr, sizeof sec_attr, cmdline->create_dg_arg[i]))
            goto err;
    }

    if (cmdline->out_file_given > 0 && cmdline->out_file_given != cmdline->read_dg_given) {
        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                SC_ERROR_INVALID_ARGUMENTS, "If `--out-file' is specified, it must be used as many times as `--read-dg'.\n");
    }

    for (i = 0; i < cmdline->read_dg_given; i++) {
        u8 *ef = NULL;
        size_t ef_len = 0;
        r = iso7816_read_binary_sfid(card, cmdline->read_dg_arg[i],
                &ef, &ef_len);
        if (r >= 0) {
            if (cmdline->out_file_given == cmdline->read_dg_given) {
                FILE *f = fopen(cmdline->out_file_arg[i], "wb");
                if (f) {
                    fwrite(ef, ef_len, 1, f);
                    fclose(f);
                } else {
                    sc_debug(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                            "Error opening %s: %s\n",
                            cmdline->out_file_arg[i], strerror(errno));
                    r = SC_ERROR_FILE_NOT_FOUND;
                }
            } else {
                char label[32];
                snprintf(label, sizeof label, "Data Group %u", (unsigned char) cmdline->read_dg_arg[i]);
                sc_debug_hex(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, label, ef, ef_len);
            }
            free(ef);
        }
        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, r,
                "Error reading data group.");
    }

    if (cmdline->print_cardid_given) {
        u8 *ef = NULL;
        size_t ef_len = 0;
        r = iso7816_read_binary_sfid(card, 0x1E, &ef, &ef_len);
        if (r >= 0) {
            const u8 *p = ef;
            unsigned int cla = 0, tag = 0;
            if (SC_SUCCESS == sc_asn1_read_tag(&p, ef_len,
                        &cla, &tag, &ef_len)
                    && (tag | cla) == 0x7E
                    && SC_SUCCESS == sc_asn1_read_tag(&p, ef_len,
                        &cla, &tag, &ef_len)
                    && (tag | cla) == 0x13) {
                const unsigned char *cardid = (const unsigned char *) p;
                while (cardid && ef_len) {
                    if (isprint(*cardid)) {
                        printf("%c", *cardid);
                    } else {
                        printf(".");
                    }
                    cardid++;
                    ef_len--;
                }
                if (cardid)
                    printf("\n");
            } else {
                r = SC_ERROR_INVALID_DATA;
            }
        }
        free(ef);
        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, r,
                "Error reading card ID.");
    }

    if (cmdline->print_paccessid_given) {
        u8 *ef = NULL;
        size_t ef_len = 0;
        r = iso7816_read_binary_sfid(card, 0x06, &ef, &ef_len);
        if (r >= 0) {
            const u8 *p = ef;
            unsigned int cla = 0, tag = 0;
            if (SC_SUCCESS == sc_asn1_read_tag(&p, ef_len,
                        &cla, &tag, &ef_len)
                    && (tag | cla) == 0x66
                    && SC_SUCCESS == sc_asn1_read_tag((const u8 **) &p, ef_len,
                        &cla, &tag, &ef_len)
                    && (tag | cla) == 0x13) {
                const unsigned char *paccessid = (const unsigned char *) p;
                while (paccessid && ef_len) {
                    if (isprint(*paccessid)) {
                        printf("%c", *paccessid);
                    } else {
                        printf(".");
                    }
                    paccessid++;
                    ef_len--;
                }
                if (paccessid)
                    printf("\n");
            } else {
                r = SC_ERROR_INVALID_DATA;
            }
        }
        free(ef);
        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, r,
                "Error reading card ID.");
    }

    if (cmdline->in_file_given != cmdline->write_dg_given) {
        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                SC_ERROR_INVALID_ARGUMENTS, "If `--in-file' is specified, it must be used as many times as `--write-dg'.\n");
    }

    for (i = 0; i < cmdline->write_dg_given; i++) {
        u8 *ef = NULL;
        size_t ef_len = 0;
        if (!fread_to_eof(cmdline->in_file_arg[i],
                    &ef, &ef_len)) {
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL,
                    SC_ERROR_INVALID_ARGUMENTS, "Could not read input file.\n");
        }
        r = iso7816_update_binary_sfid(card, cmdline->write_dg_arg[i], ef, ef_len);
        free(ef);
        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, r,
                "Error writing data group.");
    }

    if (cmdline->write_cardid_arg) {
        size_t cardid_len = strlen(cmdline->write_cardid_arg);
        u8 ef[256];
        if (cardid_len > (sizeof ef) - 4) {
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, SC_ERROR_INVALID_ARGUMENTS,
                    "Card ID too long.");
        }
        ef[0] = 0x7E;
        ef[1] = 2 + cardid_len;
        ef[2] = 0x13;
        ef[3] = cardid_len;
        memcpy(ef + 4, cmdline->write_cardid_arg, cardid_len);
        r = iso7816_update_binary_sfid(card, 0x1E, ef, 4 + cardid_len);
        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, r,
                "Error writing card ID.");
    }

    if (cmdline->write_paccessid_arg) {
        size_t paccessid_len = strlen(cmdline->write_paccessid_arg);
        u8 ef[256];
        if (paccessid_len > (sizeof ef) - 4) {
            SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, SC_ERROR_INVALID_ARGUMENTS,
                    "Card ID too long.");
        }
        ef[0] = 0x66;
        ef[1] = 2 + paccessid_len;
        ef[2] = 0x13;
        ef[3] = paccessid_len;
        memcpy(ef + 4, cmdline->write_paccessid_arg, paccessid_len);
        r = iso7816_update_binary_sfid(card, 0x06, ef, 4 + paccessid_len);
        SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, r,
                "Error writing PAccess ID.");
    }

    ok = 1;

err:
    if (pxs_reset_authobjects)
        sc_reset(card, 0);
    if (certs) {
        for (i = 0; certs[i]; i++) {
            free((unsigned char *) certs[i]);
        }
        free(certs);
    }
    free(ef_cardsecurity);
    free(certs_lens);
    free(privkey);
    sc_file_free(file);

    return ok;
}

int
main(int argc, char **argv)
{
    struct gengetopt_args_info cmdline;
    struct sc_context *ctx = NULL;
    struct sc_card *card = NULL;
    int r, fail = 1;
    sc_context_param_t ctx_param;

    if (cmdline_parser(argc, argv, &cmdline) != 0)
        exit(1);

    memset(&ctx_param, 0, sizeof(ctx_param));
    ctx_param.ver      = 0;
    ctx_param.app_name = app_name;
    ctx_param.debug    = cmdline.verbose_given;
    if (cmdline.verbose_given > 1)
        ctx_param.debug_file = stderr;

    r = sc_context_create(&ctx, &ctx_param);
    if (r) {
        fprintf(stderr, "Failed to establish context: %s\n", sc_strerror(r));
        exit(1);
    }

    r = sc_set_card_driver(ctx, "default");
    SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, r,
            "Error selecting card driver.");

    r = util_connect_card_ex(ctx, &card, cmdline.reader_arg, 0, 0);
    SC_TEST_GOTO_ERR(ctx, SC_LOG_DEBUG_VERBOSE_TOOL, r,
            "Error connecting to card.");

	if (cmdline.soc_mode_counter && !soc_main(ctx, card, &cmdline))
        goto err;
    if (cmdline.pxs_mode_counter && !paccess_main(ctx, card, &cmdline))
        goto err;
    if (cmdline.soc_mode_counter == 0 && cmdline.pxs_mode_counter == 0
            && (cmdline.verify_pin_given
                || cmdline.verify_bio_given
                || cmdline.verify_pin_or_bio_given)
            && !soc_main(ctx, card, &cmdline))
        goto err;

    fail = 0;

err:
    sc_disconnect_card(card);
    sc_release_context(ctx);
    cmdline_parser_free (&cmdline);

    return fail;
}
/*printf("%s:%d\n", __FILE__, __LINE__);*/
