/*
 * @(#)SecureTokenDeviceHandler.java        1.0 09/06/1999
 * 
 * Copyright (c) 1994-1999 Sun Microsystems, Inc.
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 * 
 */
import javacard.framework.*;
import javacardx.crypto.MessageDigest;
import javacardx.crypto.Sha1MessageDigest;

/**
 * Common purse, authentication and storage methods
 * 
 * This code requires modification for commercial use.
 * 
 * @author Rinaldo Di Giorgio
 * @author Moshe Levy
 * @author Dallas Semiconductor
 * 
 * @author
 * @version   1.32, 08/23/99
 * <H2>Revision History</H2>
 * <li>09-07-99 RDG ADD RC4, SHA1, and reduce size </li>
 * <li>10-29-98 RDG Correct authenticate offset error</li>
 * <li>10-29-98 RDG Reduce transmit size b-card read (Gemplus limitation)</li>
 * 
 */
public class CorporateCard extends Applet {

    // Offset in buffer where data begins

    final static byte BUFFER_DATA_OFFSET = (byte) 5;

    // Main CLA supported by the CorporateCard applet

    final static byte CC_CLA = (byte) 0x80;

    // APDU header stuff used during applet selection

    final static byte SELECT_CLA = 0x00;
    final static byte SELECT_INS = (byte) 0xA4;
    final static short SZ = (short) 0;
    final static byte BZ = 0x00;

    // INS -- Instructions our purse comprehends

    final static byte PURSE_DEPOSIT = (byte) 0x30;
    final static byte PURSE_WITHDRAWL = (byte) 0x7C;
    final static byte PURSE_BALANCE_CHECK = (byte) 0x34;
    final static byte PURSE_GET_LOG_ENTRY = (byte) 0x36;
    final static byte PURSE_SET_USER_PIN = (byte) 0x38;
    final static byte PURSE_SET_ISSUER_PIN = (byte) 0x3A;

    // INS --Business Card Instructions supported by the CorporateCard applet

    final static byte BC_INS_STORE = (byte) 0x3C;
    final static byte BC_INS_GETSIZE = (byte) 0x3E;
    final static byte BC_INS_RETRIEVE = (byte) 0x40;
    final static byte BC_INS_RETRIEVE_NEXT = (byte) 0x42;

    // INS -- Certificate Instructions

    final static byte STORE_CERTIFICATE = (byte) 0x44;
    final static byte GET__CERTIFICATE = (byte) 0x46;

    // INS -- Certificate Instructions

    final static byte VERIFY_PERSONAL_CODE = (byte) 0x48;
    final static byte CHECK_PERSONAL_CODE = (byte) 0x4A;
    final static byte SET_SECRET = (byte) 0x4C;

    // INS -- Challenge response (PIN+Challenge)

    final static byte SHA1_HASH = (byte) 0x80;
    final static byte XOR_HASH = (byte) 0x82;
    final static byte XOR_CHALLENGE_RESPONSE = (byte) 0x50;
    final static byte XOR_VERIFY_RESPONSE = (byte) 0x52;
    final static byte SHA1_CHALLENGE_RESPONSE = (byte) 0x54;
    final static byte SHA1_VERIFY_RESPONSE = (byte) 0x4E;

    // INS -- Get the Device Indentifier

    final static byte GETID = (byte) 0x56;
    final static byte SETID = (byte) 0x58;
    final static byte ID_LENGTH = (byte) 0x10;

    // INS

    static final short NOT_DECIPHER = (byte) 0x5A;
    static final short RC4_DECIPHER = (byte) 0x7E;

    // INS -- PIN administration

    final static byte UNBLOCK_ISSUER_PIN = (byte) 0x74;     // WAS ISUUER
    final static byte UNBLOCK_USER_PIN = (byte) 0x76;
    final static byte RESET_ISSUER_PIN = (byte) 0x78;       // WAS ISUUER
    final static byte RESET_USER_PIN = (byte) 0x7A;

    // PX

    final static byte ISSUER_PIN = 
        (byte) 0x81;                                        // P2 byte for Master PIN in Verify
    final static byte USER_PIN = (byte) 0x82;       // P2 byte for User PIN in Verify

    // Paramters for BC_INS_STORE

    final static byte BC_P1_SET_LENGTH = (byte) 0x01;
    final static byte BC_P1_NEXT_PACKET = (byte) 0x02;
    final static byte BC_P1_LAST_PACKET = (byte) 0x03;
    final static short BC_SIZE = (short) 0x80;

    // Constants to define PIN characteristics

    final static byte ISSUER_PIN_LENGTH = (byte) 8;
    final static byte ISSUER_TRIES = (byte) 3;
    final static byte USER_PIN_LENGTH = (byte) 8;
    final static byte USER_TRIES = (byte) 3;
    final static byte SHARED_SECRET_LENGTH = (byte) 8;

    // Cap the amount of money this purse can hold

    final static short PURSE_MAX_BALANCE = (short) 10000;

    // Offsets for extracting the PIN from the command APDU body

    final static short OFFSET_PIN_LENGTH = (short) ISO.OFFSET_CDATA;
    final static short OFFSET_PIN_DATA = (short) (OFFSET_PIN_LENGTH + 1);
    final static short SW_BAD_USER_PIN = (short) 0x9101;
    final static short SW_BAD_ISSUER_PIN = (short) 0x9102;
    final static short SW_INSUFFICIENT_FUNDS = (short) 0x9103;
    final static short SW_BAD_DEPOSIT = (short) 0x9104;
    final static short SW_BAD_INS_SEQUENCE = (short) 0x9105;
    final static short SW_CRYPTOSIGS_DIFFERENT = (short) 0x9106;

    /*
     * The following instance variables are used to hold state across
     * possible multiple APDUs for BC_INS_STORE.
     */
    final static short BcOutgoingLimit = 0x007f;
    final static short SCRATCH_LENGTH = (short) 256;
    final static byte HASH_SIZE = (byte) 20;
    short Offset;
    short totalLength;
    short BcDataLength;
    byte[] scratch;
    byte hashSize = HASH_SIZE;

    // Array to hold the business card info

    byte[] bcData;

    /*
     * Required for ordinary user transactions such as balance check,
     * withdrawl and deposit.
     */
    OwnerPIN userPIN;

    /*
     * Required for purse configuration APDUs such as setting the clock
     * offset and the secret used in generating hash signatures.
     */
    OwnerPIN issuerPIN;

    /*
     * Shared secKey
     */
    byte secKey[] = {
        (byte) 'J', (byte) 'a', (byte) 'v', (byte) 'a', (byte) 'O', 
        (byte) 'n', (byte) 'e', (byte) 43
    };
    byte initialPIN[] = {
        (byte) '$', (byte) '$', (byte) '$', (byte) '$', (byte) 'j', 
        (byte) 'a', (byte) 'v', (byte) 'a'
    };
    byte idNumber[] = {
        (byte) '1', (byte) '0', (byte) '0', (byte) '0', (byte) '2', 
        (byte) '0', (byte) '0', (byte) '0', (byte) '3', (byte) '0', 
        (byte) '0', (byte) '0', (byte) '4', (byte) '0', (byte) '0', (byte) '0'
    };
    short purseBalance;                                     // Current monetary balance
    byte[] hash = new byte[HASH_SIZE];
    byte[] ba;
    APDU apdu;
    short count;
    byte[] buffer;
    short rc4x, rc4y;
    Sha1MessageDigest sha1;

    /*
     * Contruct an instance of CorporateCard.
     */

    /**
     * Undocumented Class Constructor.
     * 
     * 
     * @see
     */
    public CorporateCard() {
        scratch = new byte[SCRATCH_LENGTH];     // share this
        userPIN = new OwnerPIN((byte) USER_TRIES, (byte) USER_PIN_LENGTH);
        issuerPIN = new OwnerPIN((byte) ISSUER_TRIES, 
                                 (byte) ISSUER_PIN_LENGTH);

        userPIN.updateAndUnblock(initialPIN, SZ, (byte) initialPIN.length);
        issuerPIN.updateAndUnblock(initialPIN, SZ, (byte) initialPIN.length);

        ba = new byte[4];
        bcData = new byte[BC_SIZE];     // Business card data
        sha1 = new Sha1MessageDigest();

        register();
    }

    /**
     * Initialize an RC4 Cipher
     * 
     * 
     * @param key
     * 
     */
    private void initRC4(byte[] key) {
        short index1, index2, counter;
        byte temp;

        for (counter = 0; counter < 256; ++counter) {
            scratch[counter] = (byte) counter;
        }

        rc4x = 0;
        rc4y = 0;
        index1 = 0;
        index2 = 0;

        for (counter = 0; counter < 256; ++counter) {
            index2 = (short) ((key[index1] + scratch[counter] + index2) 
                              & 0xff);
            temp = scratch[counter];
            scratch[counter] = scratch[index2];
            scratch[index2] = temp;
            index1 = (byte) ((index1 + 1) % key.length);
        }
    }

    /**
     * Make this applet as the currently selected applet in the Java iButton.
     * @param apdu APDU
     */

    // public boolean select(APDU apdu)

    public boolean select() {
        return true;
    }

    /**
     * Install the JiBlet on the "Card"
     * @param apdu APDU
     */
    public static void install(APDU apdu) {
        new CorporateCard();
    }

    /**
     * Dispatch APDUs
     * @param apdu APDU
     */
    public void process(APDU apdu) {
        this.apdu = apdu;
        this.buffer = apdu.getBuffer();

        if ((buffer[ISO.OFFSET_CLA] == SELECT_CLA) 
                && (buffer[ISO.OFFSET_INS] == SELECT_INS)) {
            return;
        }

        // We don't know what to do with this CLA.

        if (buffer[ISO.OFFSET_CLA] != CC_CLA) {
            ISOException.throwIt(ISO.SW_CLA_NOT_SUPPORTED);
        }
        if (dispatch()) {
            ISOException.throwIt(ISO.SW_INS_NOT_SUPPORTED);
        }
    }

    /**
     * Dispatch the APDU request from a user.
     * 
     * @return false if processed true it more processing required
     * 
     */
    public boolean dispatch() {
        switch (buffer[ISO.OFFSET_INS]) {

        case PURSE_DEPOSIT: 
            purseDeposit();

            break;

        case PURSE_WITHDRAWL: 
            purseWithdrawl();

            break;

        case PURSE_BALANCE_CHECK: 
            sendBalance();

            break;

        case PURSE_SET_USER_PIN: 
            purseSetPIN(apdu, userPIN, true);

            break;

        case PURSE_SET_ISSUER_PIN: 
            purseSetPIN(apdu, issuerPIN, false);

            break;

        case BC_INS_GETSIZE: 
            businessCardGetSize(apdu);

            break;

        case BC_INS_STORE: 
            businessCardStore(apdu);

            break;

        case BC_INS_RETRIEVE: 
            BcDataLength = (short) totalLength;
            Offset = SZ;

            businessCardRetrieve(apdu);

            break;

        case BC_INS_RETRIEVE_NEXT: 
            businessCardRetrieve(apdu);

            break;

        case GETID: 
            sendId(apdu);

            break;

        case SETID: 
            setId(apdu);

            break;

        case UNBLOCK_ISSUER_PIN: 
            updateAndUnblockPIN(apdu, issuerPIN);

            break;

        case UNBLOCK_USER_PIN: 
            updateAndUnblockPIN(apdu, userPIN);

            break;

        case RESET_ISSUER_PIN: 
            resetAndUnblockPIN(issuerPIN);

            break;

        case RESET_USER_PIN: 
            resetAndUnblockPIN(userPIN);

            break;

        case RC4_DECIPHER: 

        case NOT_DECIPHER: 
            decipher();

            break;

        case SHA1_HASH: 

        case XOR_HASH: 
            hash(buffer[ISO.OFFSET_INS]);

            break;

        case SHA1_CHALLENGE_RESPONSE: 

        case XOR_CHALLENGE_RESPONSE: 
            signCardID(buffer[ISO.OFFSET_INS]);

            break;

        case XOR_VERIFY_RESPONSE: 

        case SHA1_VERIFY_RESPONSE: 
            checkCardIdSignature(buffer[ISO.OFFSET_INS]);

            break;

        default: 
            return true;
        }

        return false;
    }

    /**
     * NOT Encryption
     * CLA    INS            P1   P2  LC   Data
     * +------+--------------+----+----+--+-------+
     * |CC_CLA|NOT_DECIPHER| NA | NA |LC| Data  |
     * +------+--------------+----+----+--+-------+
     * 
     */
    protected void decipher() {

        // get expected data

        short totalBytes = (short) (buffer[ISO.OFFSET_LC] & 0x00FF);
        short bytesToRead = apdu.setIncomingAndReceive();
        short offset = 0;
        byte temp[] = new byte[64];

        while (bytesToRead > 0) {
            Util.arrayCopyNonAtomic(buffer, ISO.OFFSET_CDATA, temp, offset, 
                                    bytesToRead);

            offset += bytesToRead;
            bytesToRead = apdu.receiveBytes(ISO.OFFSET_CDATA);
        }

        if (buffer[ISO.OFFSET_INS] == NOT_DECIPHER) {
            for (byte i = 0; i < totalBytes; i++) {
                temp[i] = (byte) (~temp[i]);
            }
        } else if (buffer[ISO.OFFSET_INS] == RC4_DECIPHER) {
            initRC4(secKey);

            for (short i = 0; i < totalBytes; ++i) {
                temp[i] = (byte) (temp[i] ^ scratch[computeNextStateRC4()]);
            }
        } else {
            ISOException.throwIt(ISO.SW_INS_NOT_SUPPORTED);
        }

        sendMessage(apdu, temp, SZ, totalBytes);
    }

    /**
     * Compute the next state for RC$
     * 
     * 
     * @return
     * 
     */
    private short computeNextStateRC4() {
        byte temp;

        rc4x = (short) ((rc4x + 1) & 0x00ff);
        rc4y = (short) ((rc4y + scratch[rc4x]) & 0xff);
        temp = scratch[rc4x];
        scratch[rc4x] = scratch[rc4y];
        scratch[rc4y] = temp;

        return ((short) ((scratch[rc4x] + scratch[rc4y]) & 0xff));
    }

    /**
     * Send the ID number of the card
     * 
     * Response APDU body:
     * -------------------
     * | 16 Digit String |
     * -------------------
     * 
     * 
     * @param apdu APDU
     */
    protected void sendId(APDU apdu) {
        sendMessage(apdu, idNumber, SZ, (short) ID_LENGTH);
    }

    /**
     * Set the ID number of the card
     * 
     * Command APDU body:
     * --------------------+-----------------+----------+-----------------+
     * | CLA INS P1 P2 0x10|  Issuer PIN Len | PIN data |16 bytes of DATA |
     * --------------------+-----------------+----------+-----------------+
     * 
     * 
     * @param apdu APDU
     */
    protected void setId(APDU apdu) {
        short bytesToRead = apdu.setIncomingAndReceive();

        if (issuerPIN.check(buffer, OFFSET_PIN_DATA, 
                            buffer[OFFSET_PIN_LENGTH])) {
            short IdDataOffset = (short) (OFFSET_PIN_LENGTH 
                                          + buffer[OFFSET_PIN_LENGTH] + 1);

            Util.arrayCopyNonAtomic(buffer, IdDataOffset, idNumber, SZ, 
                                    (short) ID_LENGTH);
        } else {
            ISOException.throwIt(SW_BAD_ISSUER_PIN);
        }
    }

    /**
     * Perform a deposit using the issuer PIN for basic authentication
     * 
     * Command APDU body:
     * -------------------------------------
     * | IPL |          IPD             |        Amount  |
     * -------------------------------------
     * 
     * Response APDU body:
     * -----------
     * | Balance |
     * -----------
     * 
     * with:
     * IPL        = Issuer PIN length
     * IPD        = Issuer PIN data (UPL bytes in length)
     * Amount  = Amount to deposit
     * Balance = previous balance + Amount
     * 
     * @param apdu APDU
     */
    protected void purseDeposit() {
        short amount = getAmount();

        if ((amount > 0) && ((purseBalance + amount) < PURSE_MAX_BALANCE)) {
            purseBalance += amount;
        } else {
            ISOException.throwIt(SW_BAD_DEPOSIT);
        }

        // Return the new balace

        sendBalance();
    }

    /**
     * Get the purse Amount
     * 
     * 
     * @return
     * 
     */
    protected short getAmount() {
        short bytesToRead = apdu.setIncomingAndReceive();
        byte pinLength = checkPin();

        return (byteArrayToShort(buffer, 
                                 (short) (ISO.OFFSET_CDATA + pinLength)));
    }

    /**
     * Perform a withdraw using the user PIN for basic authentication
     * 
     * Command APDU body:
     * -------------------------------------
     * | UPL |          UPD             |        Amount  |
     * -------------------------------------
     * 
     * Response APDU body:
     * -----------
     * | Balance |
     * -----------
     * 
     * with:
     * UPL        = User PIN length
     * UPD        = User PIN data (UPL bytes in length)
     * Amount  = Amount to withdraw
     * Balance = previous balance - Amount
     * 
     * @param apdu APDU
     */
    protected void purseWithdrawl() {
        short amount = getAmount();

        if ((amount > 0) && ((purseBalance - amount) > 0)) {
            purseBalance -= amount;
        } else {
            ISOException.throwIt(SW_INSUFFICIENT_FUNDS);
        }

        // Return the new balace

        sendBalance();
    }

    /**
     * Perform a balance check
     * 
     * Command APDU body:
     * 
     * Response APDU body:
     * -----------
     * | Balance |
     * -----------
     * 
     * Balance = Current balance
     * 
     * @param apdu APDU
     * HA1.
     */
    protected void sendBalance() {
        sendMessage(apdu, shortToByteArray(purseBalance), SZ, (short) 2);
    }

    /**
     * Changes a PIN.
     * 
     * Command APDU body:
     * -----------------------------------------------
     * | UPL |          UPD             | NPL |           NPD     |
     * -----------------------------------------------
     * 
     * with:
     * UPL        = User PIN length
     * UPD        = User PIN data (UPL bytes in length)
     * NPL        = New user PIN length
     * NPD        = New user PIN data (UPL bytes in length)
     * 
     * @param apdu APDU
     */
    private void purseSetPIN(APDU apdu, OwnerPIN oldPIN, boolean isUserPIN) {
        short bytesToRead = apdu.setIncomingAndReceive();

        if (oldPIN.check(buffer, OFFSET_PIN_DATA, 
                         buffer[OFFSET_PIN_LENGTH])) {
            short newPINLengthOffset = (short) (OFFSET_PIN_DATA 
                                                + buffer[OFFSET_PIN_LENGTH]);

            oldPIN.updateAndUnblock(buffer, (short) (newPINLengthOffset + 1), 
                                    buffer[newPINLengthOffset]);
        } else {
            ISOException.throwIt(isUserPIN ? SW_BAD_USER_PIN 
                                 : SW_BAD_ISSUER_PIN);
        }
    }

    /**
     * Convert an integer to a 2 byte array
     */
    protected byte[] shortToByteArray(short i) {

        // Fill byte array using big-endian byte ordering

        ba[0] = (byte) ((i & (short) 0xFF00) >>> 8);
        ba[1] = (byte) (i & (short) 0x00FF);

        return ba;
    }

    /**
     * Convert a byte array to an integer
     */
    protected short byteArrayToShort(byte[] ba, short offset) {
        short i;

        // Byte array should be using big-endian byte ordering

        i = (short) ((ba[offset] << 8) & (short) 0xFF00);
        i |= (short) (ba[offset + 1] & (short) 0x00FF);

        return i;
    }

    /**
     * Store new business card info
     * This method copies business card data into a scratchorary byte
     * array in pieces using receiveBytes. Once we have all the business
     * card data in scratch it will be atomically copied to the bcData array.
     * This way we should never have partial data in bcData.
     * 
     * @param apdu APDU
     */
    protected void businessCardStore(APDU apdu) {
        short bytesToRead = apdu.setIncomingAndReceive();

        // byte error[] = {(byte)buffer[ISO.OFFSET_P1],(byte)BC_P1_SET_LENGTH};
        // byte error[] = {(byte)ISO.OFFSET_P1,(byte)BC_P1_SET_LENGTH};
        // sendMessage(apdu, error, (byte)0, (short)2);

        switch (buffer[ISO.OFFSET_P1]) {

        case BC_P1_SET_LENGTH: 

            // Reset offset into scratch array
            // byte error[] = {(byte)buffer[ISO.OFFSET_LC],(byte)bytesToRead};
            // sendMessage(apdu, error, (byte)0, (short)2);

            Offset = 0;

            // We're expecting a 2 byte length

            if (buffer[ISO.OFFSET_LC] == 2) {
                totalLength = (short) ((buffer[ISO.OFFSET_CDATA] << 8) 
                                       & (short) 0xFF00);
                totalLength |= (short) ((buffer[ISO.OFFSET_CDATA + 1] 
                                         & (short) 0x00FF));
            } else {
                ISOException.throwIt(ISO.SW_DATA_INVALID);
            }

            break;

        case BC_P1_LAST_PACKET: 

        // Do an atomic copy of the APDU body data to the bcData array
        // Util.arrayCopy(scratch, SZ, bcData,
        // SZ, (short) totalLength);

        case BC_P1_NEXT_PACKET: 
            if (totalLength > 0) {
                while (bytesToRead > 0) {
                    Util.arrayCopyNonAtomic(buffer, ISO.OFFSET_CDATA, bcData, 
                                            Offset, bytesToRead);

                    Offset += bytesToRead;

                    // Get the next chunk of business card info

                    bytesToRead = apdu.receiveBytes(ISO.OFFSET_CDATA);
                }
            } else {
                ISOException.throwIt(SW_BAD_INS_SEQUENCE);
            }

            break;

        default: 
            ISOException.throwIt(ISO.SW_WRONG_P1P2);
        }
    }

    /**
     * Retrieve business card length
     * @param apdu APDU
     */
    protected void businessCardGetSize(APDU apdu) {
        byte b[] = shortToByteArray(totalLength);

        sendMessage(apdu, b, (byte) 0, (byte) 2);
    }

    /**
     * Retrieve business card info
     * @param apdu APDU
     * Sepcify P1 to get small block amounts
     */
    protected void businessCardRetrieve(APDU apdu) {
        byte amtToSend = buffer[ISO.OFFSET_P1];
        short len = 0;

        if (bcData != null) {

            // Return business card data to host

            if (amtToSend == 0) {
                len = BcDataLength > BcOutgoingLimit ? BcOutgoingLimit 
                      : BcDataLength;
            } else {
                len = amtToSend;
            }

            sendMessage(apdu, bcData, (short) Offset, (short) len);

            Offset += len;
            BcDataLength -= (short) len;
        }
    }

    /**
     * Copy two arrays.
     * 
     * 
     * @param a
     * @param aOff
     * @param aLen
     * @param b
     * @param bOff
     * @param bLen
     * 
     * @return
     * 
     */
    short twoArrayCpy(byte a[], short aOff, short aLen, byte b[], short bOff, 
                      short bLen) {
        Util.arrayCopyNonAtomic(a, aOff, scratch, SZ, aLen);
        Util.arrayCopyNonAtomic(b, bOff, scratch, aLen, bLen);

        return ((short) (aLen + bLen));
    }

    /**
     * Check the pin
     * 
     * 
     * @param buffer containing pin and other data
     * with P1 being the pin length
     * 
     * @return
     * 
     */
    byte checkPin() {
        if (buffer[ISO.OFFSET_P1] != USER_PIN_LENGTH) {
            ISOException.throwIt(ISO.SW_WRONG_LENGTH);
        }
        if (buffer[ISO.OFFSET_P2] == USER_PIN) {
            if (!userPIN.check(buffer, (short) (ISO.OFFSET_CDATA), 
                               buffer[ISO.OFFSET_P1])) {

                // The last nibble of return    code is number of remaining     tries

                ISOException.throwIt(SW_BAD_USER_PIN);
            }
        } else {
            ISOException.throwIt(ISO.SW_INCORRECT_P1P2);
        }

        return (buffer[ISO.OFFSET_P1]);
    }

    /**
     * Copy secret, Challenge and Id to scratch buffer
     * 
     * 
     * @param lPin  length of Pin
     * @param lChallenge Length 0f data
     * 
     * @return
     * 
     */
    short createDataForSigning() {
        short count = apdu.setIncomingAndReceive();
        byte lPin = checkPin();

        // Copy secKey then challenge then idNumber

        short cStart = (short) (ISO.OFFSET_CDATA + lPin);

        // Copy secret and challenge to scratch buffer

        short len = (short) twoArrayCpy(secKey, SZ, (short) secKey.length, 
                                        buffer, cStart, 
                                        (short) (count - lPin));

        // Copy idNumber to scratch buffer

        Util.arrayCopyNonAtomic(idNumber, SZ, scratch, len, 
                                (short) idNumber.length);

        return ((short) (len + idNumber.length));
    }

    /**
     * This method sets a new value for the PIN and resets the PIN try
     * counter to the value of the PIN try limit. It also resets the validated flag.
     * CLA    INS      P1   P2   Lc    Data
     * +----+---------+----+----+----+----+------------------+
     * |0x00|0x90/0x92|0x00|0x00|0x09|0x08| Pin data         |
     * +----+---------+----+----+----+----+------------------+
     * 
     * pin the bytearray containing the new pin value
     * offset the starting offset in the pin array
     * length the length of the new pin.
     * PINException.ILLEGAL_VALUE on illegal parameter
     */
    protected void updateAndUnblockPIN(APDU apdu, OwnerPIN pin) {
        short count = apdu.setIncomingAndReceive();
        byte DataLen = (byte) count;

        if (--DataLen > ISSUER_PIN_LENGTH) {
            ISOException.throwIt(ISO.SW_WRONG_LENGTH);
        }

        pin.updateAndUnblock(buffer, (short) (ISO.OFFSET_CDATA + 1), 
                             buffer[ISO.OFFSET_CDATA]);

        return;
    }

    /**
     * This method resets the validated flag and
     * resets the PIN try counter to the value of the PIN try limit.
     * This method is used by the owner to re-enable the blocked PIN.
     * CLA    INS      P1   P2
     * +----+---------+----+----+
     * |0x00|0x94/0x96|0x00|0x00|
     * +----+---------+----+----+
     * 
     */
    protected void resetAndUnblockPIN(OwnerPIN pin) {
        pin.resetAndUnblock();

        return;
    }

    /**
     * Send data back to the user.
     * 
     * 
     * @param apdu
     * @param buff
     * @param offset
     * @param length
     * 
     */
    protected void sendMessage(APDU apdu, byte buff[], short offset, 
                               short length) {
        apdu.setOutgoing();
        apdu.setOutgoingLength((short) length);
        apdu.sendBytesLong(buff, (short) offset, (short) length);
    }

    /**
     * Sign the data
     * 
     * 
     * @param bufferLength
     * 
     */
    void signData(short bufferLength, byte alg) {
        scratch[bufferLength] = (byte) 0x80;

        for (byte i = (byte) (bufferLength + 1); i < sha1.blockSize(); i++) {
            scratch[i] = (byte) 0x00;
        }

        byte bitL[] = 
            shortToByteArray((short) (8 * bufferLength));       // 60 bytes  max

        scratch[sha1.blockSize() - 2] = bitL[0];
        scratch[sha1.blockSize() - 1] = bitL[1];

        if ((alg == SHA1_HASH) || (alg == SHA1_CHALLENGE_RESPONSE)) {
            hashSize = (byte) sha1.hashSize();

            sha1.generateDigest(scratch, (short) 0, bufferLength, hash, 
                                (short) 0);
        } else if ((alg == XOR_HASH) || (alg == XOR_CHALLENGE_RESPONSE)) {
            hashSize = 20;

            toyXOR(sha1.blockSize());
        }
    }

    /**
     * A very simple digest operation
     * 
     * 
     * @param bufferLength
     * 
     */
    private void toyXOR(short bufferLength) {
        for (byte i = 0; i < 20; i++) {
            hash[i] = (byte) i;
        }
        for (byte i = 0; i < bufferLength; i++) {
            hash[i % hashSize] ^= (byte) scratch[i];
        }
    }

    /**
     * SHA1 Challenge/Response check
     * 
     * The input contains the challenge and the purse as well
     * as a signature.
     * 
     */
    void checkCardIdSignature(byte alg) {
        short count = apdu.setIncomingAndReceive();
        short challengePurseLength = count;
        short dataLength = 0;

        Util.arrayCopyNonAtomic(secKey, SZ, scratch, SZ, 
                                (short) secKey.length);

        dataLength += secKey.length;

        Util.arrayCopyNonAtomic(buffer, ISO.OFFSET_CDATA, scratch, 
                                (short) secKey.length, challengePurseLength);

        dataLength += challengePurseLength;

        signData(dataLength, alg);
        sendMessage(apdu, hash, (short) 0, hashSize);
    }

    /**
     * SHA1 Challenge/Response
     * CLA               INS                            P1                       P2       LC             Data
     * +------+------------------+--------------+--------+--+-------+---------+
     * |CC_CLA|CHALLENGE_RESPONSE|userPIN length|PIN type|LC|userPIN|CHALLENGE|
     * +------+------------------+--------------+--------+--+-------+---------+
     * 
     * The input data will be padded with zeros
     */
    void signCardID(byte alg) {
        signData(createDataForSigning(), alg);
        sendMessage(apdu, hash, (short) 0, hashSize);
    }

    /**
     * XOR hash  -- hash the supplied data using "TOY" XOR hash
     * 
     */
    void hash(byte alg) {
        short count = apdu.setIncomingAndReceive();

        Util.arrayCopyNonAtomic(buffer, ISO.OFFSET_CDATA, scratch, (short) 0, 
                                count);
        signData(count, alg);
        sendMessage(apdu, hash, (short) 0, hashSize);
    }

}