/*
 * passportapplet - A reference implementation of the MRTD standards.
 *
 * Copyright (C) 2006  SoS group, Radboud University
 *
 * 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
 *
 * $Id$
 */

package josh.passport;

import javacard.framework.*;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;

// API for setATRHistBytes - requires Global Platform API gp211.jar
// Comment out the following line if API not available.
// import org.globalplatform.GPSystem;

/**
 * PassportApplet
 * 
 * @author ceesb (ceeesb@gmail.com)
 * @author woj (woj@cs.ru.nl)
 * @author martijno (martijn.oostdijk@gmail.com)
 * 
 * @version $Revision$
 */
public class PassportApplet extends Applet implements ISO7816, SharePIN {
    static byte volatileState[];

    static byte persistentState;

    /* values for volatile state */
    static final byte CHALLENGED = 1;

    static final byte MUTUAL_AUTHENTICATED = 2; // ie BAC

    static final byte FILE_SELECTED = 4;

    static final byte CHIP_AUTHENTICATED = 0x10;

    static final byte TERMINAL_AUTHENTICATED = 0x20;

    /* values for persistent state */
    static final byte HAS_MUTUALAUTHENTICATION_KEYS = 1;

    static final byte HAS_EXPONENT = 2;

    static final byte LOCKED = 4;

    static final byte HAS_MODULUS = 8;

    static final byte HAS_EC_KEY = 0x10;

    static final byte HAS_CVCERTIFICATE = 0x20;

    static final byte CHAIN_CLA = 0x10;

    /* for authentication */
    static final byte INS_EXTERNAL_AUTHENTICATE = (byte) 0x82;

    static final byte INS_GET_CHALLENGE = (byte) 0x84;

    static final byte CLA_PROTECTED_APDU = 0x0c;

    static final byte INS_INTERNAL_AUTHENTICATE = (byte) 0x88;

    /* for EAC */
    static final byte INS_PSO = (byte) 0x2A;

    static final byte INS_MSE = (byte) 0x22;

    static final byte P2_VERIFYCERT = (byte) 0xBE;

    static final byte P1_SETFORCOMPUTATION = (byte) 0x41;

    static final byte P1_SETFORVERIFICATION = (byte) 0x81;

    static final byte P2_KAT = (byte) 0xA6;

    static final byte P2_DST = (byte) 0xB6;

    static final byte P2_AT = (byte) 0xA4;

    /* for reading */
    static final byte INS_SELECT_FILE = (byte) 0xA4;

    static final byte INS_READ_BINARY = (byte) 0xB0;

    /* for writing */
    static final byte INS_UPDATE_BINARY = (byte) 0xd6;

    static final byte INS_CREATE_FILE = (byte) 0xe0;

    static final byte INS_PUT_DATA = (byte) 0xda;

    static final short KEY_LENGTH = 16;

    static final short KEYMATERIAL_LENGTH = 16;

    static final short RND_LENGTH = 8;

    static final short MAC_LENGTH = 8;

    private static final byte PRIVMODULUS_TAG = 0x60;

    private static final byte PRIVEXPONENT_TAG = 0x61;

    private static final byte MRZ_TAG = 0x62;

    private static final byte ECPRIVATEKEY_TAG = 0x63;

    private static final byte CVCERTIFICATE_TAG = 0x64;

    /* status words */
    private static final short SW_OK = (short) 0x9000;

    private static final short SW_REFERENCE_DATA_NOT_FOUND = (short) 0x6A88;

    static final short SW_INTERNAL_ERROR = (short) 0x6d66;

    private byte[] rnd;

    private byte[] ssc;

    private byte[] documentNumber;

    private FileSystem fileSystem;

    private RandomData randomData;

    private short selectedFile;

    private PassportCrypto passportCrypto;

    private PassportInit passportInit;

    static CVCertificate certificate;

    KeyStore keyStore;

    private byte[] lastINS;

    private short[] chainingOffset;

    private byte[] chainingTmp;

    static final byte INS_TESTING = (byte)0xff;

    // This is as long we suspect a card verifiable certifcate could be
    private static final short CHAINING_BUFFER_LENGTH = 400;

    // public ATRGlobal atrGlobal;

    /**
     * Creates a new passport applet.
     */
    public PassportApplet(byte mode) {

        fileSystem = new FileSystem();

        randomData = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM);

        certificate = new CVCertificate();
////
        keyStore = new KeyStore(mode);
//        if(true)
//            return;
        switch (mode) {
        case PassportCrypto.CREF_MODE:
            passportCrypto = new CREFPassportCrypto(keyStore);
            break;
        case PassportCrypto.PERFECTWORLD_MODE:
            passportCrypto = new PassportCrypto(keyStore);
            break;
        case PassportCrypto.JCOP41_MODE:
            passportCrypto = new JCOP41PassportCrypto(keyStore);
            break;
        }
        passportInit = new PassportInit(passportCrypto);
////
        rnd = JCSystem.makeTransientByteArray(RND_LENGTH,
                JCSystem.CLEAR_ON_RESET);
        ssc = JCSystem
                .makeTransientByteArray((byte) 8, JCSystem.CLEAR_ON_RESET);
        volatileState = JCSystem.makeTransientByteArray((byte) 1,
                JCSystem.CLEAR_ON_RESET);
        lastINS = JCSystem.makeTransientByteArray((short) 1,
                JCSystem.CLEAR_ON_DESELECT);
        chainingOffset = JCSystem.makeTransientShortArray((short) 1,
                JCSystem.CLEAR_ON_DESELECT);
        chainingTmp = JCSystem.makeTransientByteArray(CHAINING_BUFFER_LENGTH,
                JCSystem.CLEAR_ON_DESELECT);
    }

    /**
     * Installs an instance of the applet. The default crypto mode is now
     * PERFECTWORLD_MODE as the new JCOP41 cards support all required crypto.
     * 
     * @param buffer
     * @param offset
     * @param length
     * @see javacard.framework.Applet#install(byte[], short, byte)
     */
    public static void install(byte[] buffer, short offset, byte length) {
//        (new PassportApplet(PassportCrypto.JCOP41_MODE)).register();
//        new PassportApplet(PassportCrypto.PERFECTWORLD_MODE).register();
        new PassportApplet(PassportCrypto.CREF_MODE).register();
    }

    /**
     * Processes incoming APDUs.
     * 
     * @param apdu
     * @see javacard.framework.Applet#process(javacard.framework.APDU)
     */
    public void process(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        byte cla = buffer[OFFSET_CLA];
        byte ins = buffer[OFFSET_INS];
        short sw1sw2 = SW_OK;
        boolean protectedApdu = (byte)(cla & CLA_PROTECTED_APDU)  == CLA_PROTECTED_APDU; // 0x0c
        short responseLength = 0;
        short le = 0;

        if (lastINS[0] != ins) {
            chainingOffset[0] = 0;
        }
        lastINS[0] = ins;

        /* Ignore APDU that selects this applet... */
        if (selectingApplet()) {
            // Set ATR Historical Bytes (ATS).
            // Requires gp211 API. Comment out the following line if API not
            // available.
            // org.globalplatform.GPSystem.setATRHistBytes(ATRGlobal.ATR_HIST,
            // (short) 0x00, ATRGlobal.ATR_HIST_LEN);
            byte[] hello = new byte[]{'H','E','L','L','O'};
            for (short i = 0; i < hello.length; i++) {
                buffer[i] = hello[i];
            }
            apdu.setOutgoingAndSend((short)0, (short)hello.length);
            return;
        }

        if (protectedApdu & hasMutuallyAuthenticated()) {
            try {
                le = passportCrypto.unwrapCommandAPDU(ssc, apdu);
            } catch (CardRuntimeException e) {
                sw1sw2 = e.getReason();
            }
        } else if (protectedApdu) {
            ISOException.throwIt(ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED); // 0x6882
        }

        if (sw1sw2 == SW_OK) {
            try {
                responseLength = processAPDU(apdu, cla, ins, protectedApdu, le);
            } catch (CardRuntimeException e) {
                sw1sw2 = e.getReason();
            }
        }

        if (protectedApdu && hasMutuallyAuthenticated()) {
            responseLength = passportCrypto.wrapResponseAPDU(ssc, apdu, passportCrypto
                    .getApduBufferOffset(responseLength), responseLength,
                    sw1sw2);
        }

        if (responseLength > 0) {
            if (apdu.getCurrentState() != APDU.STATE_OUTGOING)
                apdu.setOutgoing();
            if (apdu.getCurrentState() != APDU.STATE_OUTGOING_LENGTH_KNOWN)
                apdu.setOutgoingLength(responseLength);
            apdu.sendBytes((short) 0, responseLength);
        }

        if (sw1sw2 != SW_OK) {
            ISOException.throwIt(sw1sw2);
        }
    }

    /**
      * Processes incoming APDUs, excluding Secure Messaging.
      *
      * This method assumes SM protection has been removed from the
      * incoming APDU, and does not add SM protection to the response.
      * Handling of Secure Messaging is done in process(APDU apdu)
      *
      * @param protectedApdu true if Secure Messaging is used
      * @return length of the response APDU
     */
    public short processAPDU(APDU apdu, byte cla, byte ins,
            boolean protectedApdu, short le) {
        short responseLength = 0;

        switch (ins) {
        case INS_GET_CHALLENGE: //defined in 9303-11
            responseLength = processGetChallenge(apdu, protectedApdu, le);
            break;
        case INS_EXTERNAL_AUTHENTICATE: //defined in 9303-11
            responseLength = processMutualAuthenticate(apdu, protectedApdu);
            break;
        case INS_PSO:
            if (!protectedApdu) {
                ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
            }
            responseLength = processPSO(apdu);
            break;
        case INS_MSE:
            if (!protectedApdu) {
                ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
            }
            responseLength = processMSE(apdu);
            break;
        case INS_INTERNAL_AUTHENTICATE: //defined in 9303-11
            responseLength = processInternalAuthenticate(apdu, protectedApdu);
            break;
        case INS_SELECT_FILE: //defined in 9303-10 // 0xa4
            responseLength = processSelectFile(apdu);
            break;
        case INS_READ_BINARY: //defined in 9303-10
            responseLength = processReadBinary(apdu, le, protectedApdu);
            break;
        case INS_UPDATE_BINARY: //defined in 9303-10
            processUpdateBinary(apdu);
            break;
        case INS_CREATE_FILE:
            processCreateFile(apdu);
            break;
        case INS_PUT_DATA:
            responseLength = processPutData(apdu);
            break;
        case INS_TESTING:
            byte[] testing = new byte[]{'t','e','s','t','i','n','g'};
            Util.arrayCopyNonAtomic(testing,(byte)0, apdu.getBuffer(), (byte)0,(byte)testing.length);
            responseLength = (byte)testing.length;
            break;
        default:
            ISOException.throwIt(SW_INS_NOT_SUPPORTED);
            break;
        }
        return responseLength;
    }

    private short processPSO(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        if (!hasChipAuthenticated() || hasTerminalAuthenticated()) {
            ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
        }
        if (buffer[OFFSET_P1] != (byte) 0x00
                && buffer[OFFSET_P2] != P2_VERIFYCERT) {
            ISOException.throwIt(SW_INCORRECT_P1P2);
        }
        if (certificate.currentCertSubjectId[0] == 0) {
            ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
        }

        short lc = (short) (buffer[OFFSET_LC] & 0xFF);
        if (chainingOffset[0] > (short) (CHAINING_BUFFER_LENGTH - lc)) {
            ISOException.throwIt(SW_WRONG_LENGTH);
        }
        chainingOffset[0] = Util.arrayCopyNonAtomic(buffer, OFFSET_CDATA,
                chainingTmp, chainingOffset[0], lc);
        if (((byte) (buffer[OFFSET_CLA] & CHAIN_CLA) == CHAIN_CLA)) {
            return (short) 0;
        }
        short chainingTmpLength = chainingOffset[0];
        chainingOffset[0] = (short) 0;

        certificate.parseCertificate(chainingTmp, (short) 0, chainingTmpLength,
                false);
        if (!certificate.verify()) {
            ISOException.throwIt((short) 0x6300);
        }
        return (short) 0;
    }

    private short processMSE(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        byte p1 = (byte) (buffer[OFFSET_P1] & 0xff);
        byte p2 = (byte) (buffer[OFFSET_P2] & 0xff);
        short lc = (short) (buffer[OFFSET_LC] & 0xff);
        short buffer_p = OFFSET_CDATA;

        if (!hasEACKey() || !hasEACCertificate()) {
            ISOException.throwIt(SW_INS_NOT_SUPPORTED);
        }
        if (!hasMutuallyAuthenticated() || hasTerminalAuthenticated()) {
            ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
        }
        if (p1 == P1_SETFORCOMPUTATION && p2 == P2_KAT) {

            short lastOffset = (short) (lc + OFFSET_CDATA);
            if (buffer_p > (short) (lastOffset - 2)) {
                ISOException.throwIt(SW_WRONG_LENGTH);
            }
            if (buffer[buffer_p++] != (byte) 0x91) {
                ISOException.throwIt(SW_WRONG_DATA);
            }
            short pubKeyLen = (short) (buffer[buffer_p++] & 0xFF);
            short pubKeyOffset = buffer_p;
            if (pubKeyOffset > (short) (lastOffset - pubKeyLen)) {
                ISOException.throwIt(SW_WRONG_LENGTH);
            }
            buffer_p += pubKeyLen;
            short keyIdOffset = 0;
            short keyIdLength = 0;
            if (buffer_p != lastOffset) {
                if (buffer_p > (short) (lastOffset - 2)) {
                    ISOException.throwIt(SW_WRONG_LENGTH);
                }
                if (buffer[buffer_p++] != (byte) 0x84) {
                    ISOException.throwIt(SW_WRONG_DATA);
                }
                keyIdLength = (short) (buffer[buffer_p++] & 0xFF);
                keyIdOffset = buffer_p;
                if (keyIdOffset != (short) (lastOffset - keyIdLength)) {
                    ISOException.throwIt(SW_WRONG_LENGTH);
                }
                // ignore the key id, we don't use it for now
            }
            if (!passportCrypto.authenticateChip(buffer, pubKeyOffset, pubKeyLen)) {
                ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
            }
            volatileState[0] |= CHIP_AUTHENTICATED;
            return 0;
        } else if (p1 == P1_SETFORVERIFICATION && (p2 == P2_DST || p2 == P2_AT)) {
            if (!hasChipAuthenticated() || hasTerminalAuthenticated()) {
                ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
            }
            short lastOffset = (short) (lc + OFFSET_CDATA);
            if (buffer_p > (short) (lastOffset - 2)) {
                ISOException.throwIt(SW_WRONG_LENGTH);
            }
            if (buffer[buffer_p++] != (byte) 0x83) {
                ISOException.throwIt(SW_WRONG_DATA);
            }
            short subIdLen = (short) (buffer[buffer_p++] & 0xFF);
            if (buffer_p != (short) (lastOffset - subIdLen)) {
                ISOException.throwIt(SW_WRONG_LENGTH);
            }
            if (!certificate.selectSubjectId(buffer, buffer_p, subIdLen)) {
                ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
            }
        } else {
            ISOException.throwIt(SW_INCORRECT_P1P2);
        }
        return 0;
    }

    private short processPutData(APDU apdu) {
        if (isLocked()) {
            ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
        }

        byte[] buffer = apdu.getBuffer();
        short buffer_p = (short) (OFFSET_CDATA & 0xff);
        short lc = (short) (buffer[OFFSET_LC] & 0xff);
        short p1 = (short) (buffer[OFFSET_P1] & 0xff);
        short p2 = (short) (buffer[OFFSET_P2] & 0xff);
        short returnLength = 0;

        // sanity check
        if (buffer.length < (short) (buffer_p + lc)) {
            ISOException.throwIt(SW_INTERNAL_ERROR);
        }

        if (p1 == 0xde && p2 == 0xad) {
            persistentState |= LOCKED;
        }
        else if (p1 == 0 && p2 == PRIVMODULUS_TAG) {
            buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag ==
            // PRIVMODULUS_TAG
            buffer_p = BERTLVScanner.readLength(buffer, buffer_p); // length ==
            // 00
            buffer_p = BERTLVScanner.skipValue();
            buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == 04
            short modOffset = BERTLVScanner.readLength(buffer, buffer_p);
            short modLength = BERTLVScanner.valueLength;

            if (buffer[modOffset] == 0) {
                modLength--;
                modOffset++;
            }

            keyStore.rsaPrivateKey.setModulus(buffer, modOffset, modLength);
            persistentState |= HAS_MODULUS;
        }
        else if (p1 == 0 && p2 == PRIVEXPONENT_TAG) {
            buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag ==
            // PRIVEXP_TAG
            buffer_p = BERTLVScanner.readLength(buffer, buffer_p); // length ==
            // 00
            buffer_p = BERTLVScanner.skipValue();
            buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == 04
            short expOffset = BERTLVScanner.readLength(buffer, buffer_p);
            short expLength = BERTLVScanner.valueLength;

            // leading zero
            if (buffer[expOffset] == 0) {
                expLength--;
                expOffset++;
            }

            keyStore.rsaPrivateKey.setExponent(buffer, expOffset, expLength);
            persistentState |= HAS_EXPONENT;
        }
        else if (p1 == 0 && p2 == MRZ_TAG) {

            // data is BERTLV object with three objects; docNr, dataOfBirth,
            // dateOfExpiry
            buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
            buffer_p = BERTLVScanner.readLength(buffer, buffer_p);

            buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
            short docNumberOffset = BERTLVScanner.readLength(buffer, buffer_p);
            short docNumberLength = BERTLVScanner.valueLength;
            buffer_p = BERTLVScanner.skipValue();

            buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
            short birthOffset = BERTLVScanner.readLength(buffer, buffer_p);
            short birthLength = BERTLVScanner.valueLength;
            buffer_p = BERTLVScanner.skipValue();

            buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
            short expiryOffset = BERTLVScanner.readLength(buffer, buffer_p);
            short expiryLength = BERTLVScanner.valueLength;
            buffer_p = BERTLVScanner.skipValue();

//            ISOException.throwIt((short)0xfaaf);//break point

            documentNumber = new byte[(short)(docNumberLength+1)];
            Util.arrayCopyNonAtomic(buffer, docNumberOffset, documentNumber,
                    (short) 0, docNumberLength);
            documentNumber[docNumberLength] = PassportInit.checkDigit(documentNumber,(short)0, docNumberLength);

            short keySeed_offset = passportInit.computeKeySeed(buffer, docNumberOffset,
                    docNumberLength, birthOffset, birthLength, expiryOffset, expiryLength);

            short macKey_p = (short) (keySeed_offset + KEYMATERIAL_LENGTH);
            short encKey_p = (short) (keySeed_offset + KEYMATERIAL_LENGTH + KEY_LENGTH);
            passportCrypto.deriveKey(buffer, keySeed_offset, KEYMATERIAL_LENGTH, PassportCrypto.MAC_MODE,
                    macKey_p);
            passportCrypto.deriveKey(buffer, keySeed_offset, KEYMATERIAL_LENGTH, PassportCrypto.ENC_MODE,
                    encKey_p);
            keyStore.setMutualAuthenticationKeys(buffer, macKey_p, buffer,
                    encKey_p);
            persistentState |= HAS_MUTUALAUTHENTICATION_KEYS;


            byte[] mrz_tag = new byte[]{'m','r','z','_','t','a','g'};
            Util.arrayCopyNonAtomic(mrz_tag,(short)0,buffer,(short)0,(short)mrz_tag.length);
            returnLength = (short) mrz_tag.length;
        }
        else if (p1 == 0 && p2 == ECPRIVATEKEY_TAG) {
            short finish = (short) (buffer_p + lc);
            while (buffer_p < finish) {
                buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
                buffer_p = BERTLVScanner.readLength(buffer, buffer_p);
                short len = BERTLVScanner.valueLength;
                switch (BERTLVScanner.tag) {
                case (short) 0x81:
                    if (len == (short) 6) {
                        short e1 = Util.getShort(buffer, buffer_p);
                        short e2 = Util
                                .getShort(buffer, (short) (buffer_p + 2));
                        short e3 = Util
                                .getShort(buffer, (short) (buffer_p + 4));
                        keyStore.ecPrivateKey.setFieldF2M(e1, e2, e3);
                        keyStore.ecPublicKey.setFieldF2M(e1, e2, e3);
                    } else {
                        keyStore.ecPrivateKey.setFieldF2M(Util.getShort(buffer,
                                buffer_p));
                        keyStore.ecPublicKey.setFieldF2M(Util.getShort(buffer,
                                buffer_p));
                    }
                    break;
                case (short) 0x82:
                    keyStore.ecPrivateKey.setA(buffer, buffer_p, len);
                    keyStore.ecPublicKey.setA(buffer, buffer_p, len);
                    break;
                case (short) 0x83:
                    keyStore.ecPrivateKey.setB(buffer, buffer_p, len);
                    keyStore.ecPublicKey.setB(buffer, buffer_p, len);
                    break;
                case (short) 0x84:
                    keyStore.ecPrivateKey.setG(buffer, buffer_p, len);
                    keyStore.ecPublicKey.setG(buffer, buffer_p, len);
                    break;
                case (short) 0x85:
                    keyStore.ecPrivateKey.setR(buffer, buffer_p, len);
                    keyStore.ecPublicKey.setR(buffer, buffer_p, len);
                    break;
                case (short) 0x86:
                    if (len == (short) 20) {
                        buffer_p--;
                        len++;
                        buffer[buffer_p] = 0x00;
                    }
                    keyStore.ecPrivateKey.setS(buffer, buffer_p, len);
                    break;
                case (short) 0x87:
                    // This is the k, ignore it
                    // short k = Util.getShort(buffer, buffer_p);
                    break;
                default:
                    ISOException.throwIt(SW_WRONG_DATA);//6a80
                break;
            }
            buffer_p = BERTLVScanner.skipValue();
        }
        if (keyStore.ecPrivateKey.isInitialized()) {
            persistentState |= HAS_EC_KEY;
        } else {
            ISOException.throwIt(SW_WRONG_DATA);
        }
    }
        else if (p2 == CVCERTIFICATE_TAG) {
            if ((byte) (persistentState & HAS_CVCERTIFICATE) == HAS_CVCERTIFICATE) {
                // We already have the certificate initialized
                ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
            }
            certificate.parseCertificate(buffer, buffer_p, lc, true);
            certificate.setRootCertificate(buffer, p1);
            persistentState |= HAS_CVCERTIFICATE;
        }
        else {
            ISOException.throwIt(SW_INCORRECT_P1P2);
        }
        return returnLength;
    }

    /**
     * Processes INTERNAL_AUTHENTICATE apdus, ie Active Authentication (AA).
     * Receives a random and signs it.
     *
     * @param apdu
     * @param protectedApdu true if Secure Messaging was used
     * @return
     */
    private short processInternalAuthenticate(APDU apdu, boolean protectedApdu) {
        if (!hasInternalAuthenticationKeys() || !hasMutuallyAuthenticated()) {
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);  //6982
        }

        short buffer_p = (short) (OFFSET_CDATA & 0xff);
        short hdr_offset = 0;
        if (protectedApdu) {
            hdr_offset = passportCrypto.getApduBufferOffset((short) 128);
        }
        short hdr_len = 1;
        short m1_len = 106; // whatever
        short m1_offset = (short) (hdr_offset + hdr_len);
        short m2_len = 8;
        short m2_offset = (short) (m1_offset + m1_len);
        // we will write the hash over m2
        short m1m2hash_offset = (short) (m1_offset + m1_len);
        short m1m2hash_len = 20;
        short trailer_offset = (short) (m1m2hash_offset + m1m2hash_len);
        short trailer_len = 1;

        byte[] buffer = apdu.getBuffer();
        short bytesLeft = (short) (buffer[OFFSET_LC] & 0x00FF);

        if (bytesLeft != m2_len)
            ISOException.throwIt(SW_WRONG_LENGTH);

        // put m2 in place
        Util.arrayCopyNonAtomic(buffer, buffer_p, buffer, m2_offset, m2_len);

        // write some random data of m1_len
        // randomData.generateData(buffer, m1_offset, m1_length);
        for (short i = m1_offset; i < (short) (m1_offset + m1_len); i++) {
            buffer[i] = 0;
        }

        // calculate SHA1 hash over m1 and m2
        passportCrypto.shaDigest.doFinal(buffer, m1_offset, (short) (m1_len + m2_len), buffer,
                m1m2hash_offset);
        passportCrypto.shaDigest.reset();

        // write trailer
        buffer[trailer_offset] = (byte) 0xbc;

        // write header
        buffer[hdr_offset] = (byte) 0x6a;

        // encrypt the whole buffer with our AA private key
        short plaintext_len = (short) (hdr_len + m1_len + m1m2hash_len + trailer_len);
        // sanity check
        if (plaintext_len != 128) {
            ISOException.throwIt(SW_INTERNAL_ERROR);
        }
        passportCrypto.rsaCiph.init(keyStore.rsaPrivateKey, Cipher.MODE_ENCRYPT);
        short ciphertext_len = passportCrypto.rsaCiph.doFinal(buffer, hdr_offset,
                plaintext_len, buffer, hdr_offset);
        // sanity check
        if (ciphertext_len != 128) {
            ISOException.throwIt(SW_INTERNAL_ERROR);
        }

        return ciphertext_len;
    }

    /**
     * Processes incoming GET_CHALLENGE APDUs, as part of BAC or EAC.
     *
     * Generates random 8 bytes, sends back result and stores result in rnd.
     * A GET_CHALLENGE APDU can be sent as part of BAC, or as part of
     * EAC (more specifically, Terminal Authentication (TA).
     *
     * @param apdu
     *            is used for sending (8 bytes) only
     * @param protectedApdu true if Secure Messaging was used
     */
    private short processGetChallenge(APDU apdu, boolean protectedApdu, short le) {
        if (protectedApdu) {
            // we're doing TA
            if (!hasChipAuthenticated()
                    || certificate.cert1HolderReference[0] == (byte)0
                    || hasTerminalAuthenticated()) {
                ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
            }
        } else {
            // we're doing BAC
            if (!hasMutualAuthenticationKeys() || hasMutuallyAuthenticated()) {
                ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
            }
        }
        byte[] buffer = apdu.getBuffer();
        if (!protectedApdu) {
            le = apdu.setOutgoing();
        }
        // For the BAP challenge the length should be 8, for the EAP challenge
        // we guess other lenghts are fine too?
        if (le != 8) {
            ISOException.throwIt(SW_WRONG_LENGTH);
        }
        randomData.generateData(rnd, (short) 0, le);
        short bufferOffset = protectedApdu ? passportCrypto.getApduBufferOffset(le)
                : (short) 0;
        Util.arrayCopyNonAtomic(rnd, (short) 0, buffer, bufferOffset, le);

        volatileState[0] |= CHALLENGED;

        return le;
    }

    /**
     * Processes incoming EXTERNAL_AUTHENTICATE APDUs, as part of BAC or EAC.
     *
     * An EXTERNAL_AUTHENTICATE can be the last step of BAC, or the last
     * step of Terminal Authentication (TA) when doing EAC.
     *
     * @param apdu
     *            the APDU
     * @param protectedApdu true if Secure Messaging was used
     * @return length of response APDU
     */
    private short processMutualAuthenticate(APDU apdu, boolean protectedApdu) {
        if (protectedApdu) {
            // we're doing EAC
            if (!hasChipAuthenticated() || !isChallenged()
                    || certificate.currentCertSubjectId[0] == (byte)0
                    || hasTerminalAuthenticated()) {
                ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
            }
            byte[] buffer = apdu.getBuffer();
            short buffer_p = OFFSET_CDATA;
            short lc = (short) (buffer[OFFSET_LC] & 0xFF);
            setNoChallenged();
            if (!passportCrypto.eacVerifySignature(certificate.currentCertPublicKey, rnd,
                    documentNumber, buffer, buffer_p, lc)) {
                certificate.clear();
                ISOException.throwIt((short) 0x6300);
            }
            certificate.clear();
            volatileState[0] |= TERMINAL_AUTHENTICATED;
            return 0;
        } else {
        // we're doing BAC
        if (!isChallenged() || hasMutuallyAuthenticated()) {
               ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
           }

        byte[] buffer = apdu.getBuffer();
        short bytesLeft = (short) (buffer[OFFSET_LC] & 0x00FF);
        short e_ifd_length = RND_LENGTH + RND_LENGTH + KEYMATERIAL_LENGTH;

        // incoming message is e_ifd || m_ifd
        // where e_ifd == E_KENC(rnd_ifd || rnd_icc || k_ifd)
        if (bytesLeft != (short) (e_ifd_length + MAC_LENGTH))
            ISOException.throwIt(SW_WRONG_LENGTH);

        short e_ifd_p = OFFSET_CDATA;
        short m_ifd_p = (short) (e_ifd_p + e_ifd_length);

        if (apdu.getCurrentState() == APDU.STATE_INITIAL) {
            apdu.setIncomingAndReceive();
        }
        if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) {
            // need all data in one APDU.
            ISOException.throwIt(SW_INTERNAL_ERROR);
        }

        // buffer[OFFSET_CDATA ... +40] consists of e_ifd || m_ifd
        // verify checksum m_ifd of cryptogram e_ifd
        passportCrypto.initMac(Signature.MODE_VERIFY);
        if (!passportCrypto.verifyMacFinal(buffer, e_ifd_p, e_ifd_length, buffer,
                m_ifd_p))
            ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);

        // decrypt e_ifd into buffer[0] where buffer = rnd.ifd || rnd.icc ||
        // k.ifd
        passportCrypto.decryptInit();
        short plaintext_len = passportCrypto.decryptFinal(buffer, e_ifd_p,
                e_ifd_length, buffer, (short) 0);
        if (plaintext_len != e_ifd_length) // sanity check
            ISOException.throwIt(SW_INTERNAL_ERROR);

        short rnd_ifd_p = 0;
        short rnd_icc_p = RND_LENGTH;
        short k_ifd_p = (short) (rnd_icc_p + RND_LENGTH);

        /*
         * we use apdu buffer for writing intermediate data in buffer with
         * following pointers
         */
        short k_icc_p = (short) (k_ifd_p + KEYMATERIAL_LENGTH);
        short keySeed_p = (short) (k_icc_p + KEYMATERIAL_LENGTH);
        short keys_p = (short) (keySeed_p + KEYMATERIAL_LENGTH);

        // verify that rnd.icc equals value generated in getChallenge
        if (Util.arrayCompare(buffer, rnd_icc_p, rnd, (short) 0, RND_LENGTH) != 0)
            ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);

        // generate keying material k.icc
        randomData.generateData(buffer, k_icc_p, KEYMATERIAL_LENGTH);

        // calculate keySeed for session keys by xorring k_ifd and k_icc
        PassportUtil.xor(buffer, k_ifd_p, buffer, k_icc_p, buffer, keySeed_p,
                KEYMATERIAL_LENGTH);

        // calculate session keys
        passportCrypto.deriveKey(buffer, keySeed_p, KEYMATERIAL_LENGTH, PassportCrypto.MAC_MODE, keys_p);
        short macKey_p = keys_p;
        keys_p += KEY_LENGTH;
        passportCrypto.deriveKey(buffer, keySeed_p, KEYMATERIAL_LENGTH, PassportCrypto.ENC_MODE, keys_p);
        short encKey_p = keys_p;
        keys_p += KEY_LENGTH;
        keyStore.setSecureMessagingKeys(buffer, macKey_p, buffer, encKey_p);

        // compute ssc
        PassportCrypto.computeSSC(buffer, rnd_icc_p, buffer, rnd_ifd_p, ssc);

        // create response in buffer where response = rnd.icc || rnd.ifd ||
        // k.icc
        PassportUtil.swap(buffer, rnd_icc_p, rnd_ifd_p, RND_LENGTH);
        Util.arrayCopyNonAtomic(buffer, k_icc_p, buffer, (short) (2 * RND_LENGTH),
                KEYMATERIAL_LENGTH);

        // make buffer encrypted using k_enc
        passportCrypto.encryptInit();
        short ciphertext_len = passportCrypto.encryptFinal(buffer, (short) 0,
                (short) (2 * RND_LENGTH + KEYMATERIAL_LENGTH), buffer,
                (short) 0);

        // create m_icc which is a checksum of response
        passportCrypto.initMac(Signature.MODE_SIGN);
        passportCrypto.createMacFinal(buffer, (short) 0, ciphertext_len, buffer,
                ciphertext_len);

        setNoChallenged();
        volatileState[0] |= MUTUAL_AUTHENTICATED;

        return (short) (ciphertext_len + MAC_LENGTH);

        }
    }

    /**
     * Processes incoming SELECT_FILE APDUs.
     *
     * @param apdu
     *            where the first 2 data bytes encode the file to select.
     */
    private short processSelectFile(APDU apdu) {
        if (isLocked() & !hasMutuallyAuthenticated()) {
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }

        byte[] buffer = apdu.getBuffer();
        short lc = (short) (buffer[OFFSET_LC] & 0x00FF);

        if (lc != 2)
            ISOException.throwIt(SW_WRONG_LENGTH);

        if (apdu.getCurrentState() == APDU.STATE_INITIAL) {
            apdu.setIncomingAndReceive();
        }

        if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) {
            // need all data in one APDU.
            ISOException.throwIt(SW_INTERNAL_ERROR);
        }

        short fid = Util.getShort(buffer, OFFSET_CDATA);

        if (fileSystem.getFile(fid) != null) {
            selectedFile = fid;
            volatileState[0] |= FILE_SELECTED;
            byte[] select_file = new byte[]{'s','e','l','e','c','t','_','f','i','l','e'};
            Util.arrayCopyNonAtomic(select_file,(short) 0,buffer,(short)0,
                    (short)select_file.length);
            return (short) select_file.length;
        }
        setNoFileSelected();
        ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        return (short)0;
    }

    /**
     * Processes incoming READ_BINARY APDUs. Returns data of the currently
     * selected file.
     *
     * @param apdu
     *            where the offset is carried in header bytes p1 and p2.
     * @param le
     *            expected length by terminal
     * @return length of the response APDU
     */
    private short processReadBinary(APDU apdu, short le, boolean protectedApdu) {
        if (!hasMutuallyAuthenticated()) {
            ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
        }

        if (!hasFileSelected()) {
            ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
        }

        byte[] buffer = apdu.getBuffer();
        byte p1 = buffer[OFFSET_P1];
        byte p2 = buffer[OFFSET_P2];

        short offset = Util.makeShort(p1, p2);

        byte[] file = fileSystem.getFile(selectedFile);
        if (file == null) {
            ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        }

        short len;
        short fileSize = fileSystem.getFileSize(selectedFile);

        len = PassportUtil.min((short) (buffer.length - 37),
                (short) (fileSize - offset));
        // FIXME: 37 magic
        len = PassportUtil.min(len, (short) buffer.length);
        len = PassportUtil.min(le, len);
        short bufferOffset = 0;
        if (protectedApdu) {
            bufferOffset = passportCrypto.getApduBufferOffset(len);
        }
        Util.arrayCopyNonAtomic(file, offset, buffer, bufferOffset, len);

        return len;
    }

    /**
     * Processes and UPDATE_BINARY apdu. Writes data in the currently selected
     * file.
     *
     * @param apdu
     *            carries the offset where to write date in header bytes p1 and
     *            p2.
     */
    private void processUpdateBinary(APDU apdu) {
        if (!hasFileSelected() || isLocked()) {
            ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
        }

        byte[] buffer = apdu.getBuffer();
        byte p1 = buffer[OFFSET_P1];
        byte p2 = buffer[OFFSET_P2];
        short offset = Util.makeShort(p1, p2);

        short readCount = (short) (buffer[ISO7816.OFFSET_LC] & 0xff);
        readCount = apdu.setIncomingAndReceive();

        while (readCount > 0) {
            fileSystem.writeData(selectedFile, offset, buffer, OFFSET_CDATA,
                    readCount);
            offset += readCount;
            readCount = apdu.receiveBytes(ISO7816.OFFSET_CDATA);
        }
    }

    /**
     * Processes and CREATE_FILE apdu.
     *
     * This functionality is only partly implemented. Only non-directories
     * (files) can be created, all options for CREATE_FILE are ignored.
     *
     * @param apdu
     *            containing 6 bytes: 0x64 || (1 byte) || size (2) || fid (2)
     */
    private void processCreateFile(APDU apdu) {
        if (isLocked()) {
            ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
        }

        byte[] buffer = apdu.getBuffer();
        short lc = (short) (buffer[OFFSET_LC] & 0xff);

        if (apdu.getCurrentState() == APDU.STATE_INITIAL) {
            apdu.setIncomingAndReceive();
        }
        if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) {
            // need all data in one APDU.
            ISOException.throwIt(SW_INTERNAL_ERROR);
        }

        if (lc < (short) 6 || (buffer[OFFSET_CDATA + 1] & 0xff) < 4)
            ISOException.throwIt(SW_WRONG_LENGTH);

        if (buffer[OFFSET_CDATA] != 0x63)
            ISOException.throwIt(SW_DATA_INVALID);

        short size = Util.makeShort(buffer[(short) (OFFSET_CDATA + 2)],
                buffer[(short) (OFFSET_CDATA + 3)]);

        short fid = Util.makeShort(buffer[(short) (OFFSET_CDATA + 4)],
                buffer[(short) (OFFSET_CDATA + 5)]);

        if(fid == FileSystem.EF_CVCA_FID) {
           fileSystem.createFile(fid, size, certificate);
        }else{
           fileSystem.createFile(fid, size);
        }
    }

    public static boolean hasInternalAuthenticationKeys() {
        return (persistentState & (HAS_EXPONENT | HAS_MODULUS)) == (HAS_EXPONENT | HAS_MODULUS);
    }

    public static boolean hasMutualAuthenticationKeys() {
        return (persistentState & HAS_MUTUALAUTHENTICATION_KEYS) == HAS_MUTUALAUTHENTICATION_KEYS;
    }

    public static boolean hasEACKey() {
        return (persistentState & HAS_EC_KEY) == HAS_EC_KEY;
    }

    public static boolean hasEACCertificate() {
        return (persistentState & HAS_CVCERTIFICATE) == HAS_CVCERTIFICATE;
    }

    public static void setNoFileSelected() {
        if (hasFileSelected()) {
            volatileState[0] ^= FILE_SELECTED;
        }
    }

    public static void setNoChallenged() {
        if ((volatileState[0] & CHALLENGED) == CHALLENGED) {
            volatileState[0] ^= CHALLENGED;
        }
    }

    public static boolean hasFileSelected() {
        return (volatileState[0] & FILE_SELECTED) == FILE_SELECTED;
    }

    public static boolean isLocked() {
        return (persistentState & LOCKED) == LOCKED;
    }

    public static boolean isChallenged() {
        return (volatileState[0] & CHALLENGED) == CHALLENGED;
    }

	/** Has BAC been completed? */
    public static boolean hasMutuallyAuthenticated() {
        return (volatileState[0] & MUTUAL_AUTHENTICATED) == MUTUAL_AUTHENTICATED;
    }

    public static boolean hasChipAuthenticated() {
        return (volatileState[0] & CHIP_AUTHENTICATED) == CHIP_AUTHENTICATED;
    }

    public static boolean hasTerminalAuthenticated() {
        return (volatileState[0] & TERMINAL_AUTHENTICATED) == TERMINAL_AUTHENTICATED;
    }


    byte[] sharedPIN = new byte[]{'t','e','s','t','p','i','n',};
    final short sharedSecret = (short)87;

    @Override
    public Shareable getShareableInterfaceObject(AID aid, byte b) {
        if(aid.equals(new byte[]{(byte)0xa0,(byte)0x00,(byte)0x00,(byte)0x06,(byte)0x47,(byte)0x2f,(byte)0x00,(byte)0x01}, (short)0, (byte)0)){
            return this;
        }
        return this;
    }

    @Override
    public void setSharedPIN(byte[] apduBuffer, short offset, short length) {
        sharedPIN = new byte[length];
        JCSystem.requestObjectDeletion();
        Util.arrayCopy(apduBuffer, offset, sharedPIN, (short)0, length );
    }

    @Override
    public short getSharedPIN(byte[] apduBuffer, short offset) {
        Util.arrayCopy(sharedPIN, (short)0 , apduBuffer, offset, (short) sharedPIN.length );
        return (short)sharedPIN.length;
    }

    @Override
    public short getSecret() {
        return sharedSecret;
    }
}
