/*
* Sample Java Card Applet:
*
* Purpose : An outline coding example to show the basic
* elements of a JavaCard Applet.
*
*/
package fr.ensieta.examples.cardid;

/* The javacard.framework package defines
* the basic building blocks for JavaCard
* programs. It has classes defined to handle
* APDUs, runtime and system services.
*/
import javacard.framework.*;
import visa.openplatform.*;
/* The CardID is an instance of an
* applet class which extends from
* javacard.framework.Applet.
*/
public class CardID extends javacard.framework.Applet
{
    
    /* Instruction bytes for the APDU header */
    public static final byte READ_INS =(byte) 0x30 ;
    public static final byte UPDATE_INS =(byte) 0x31 ;
    private final static byte INS_VERIFY_PIN = (byte)0x32 ;
    // the OP/VOP specific instruction set for mutual authentication
    private final static byte CLA_INIT_UPDATE           = (byte)0x80 ;
    private final static byte INS_INIT_UPDATE           = (byte)0x50 ;
    private final static byte CLA_EXTERNAL_AUTHENTICATE = (byte)0x84 ;
    private final static byte INS_EXTERNAL_AUTHENTICATE = (byte)0x82 ;
    
    
    /*varible for name keeping*/
    private static byte[] name ;
    
    // the PIN validity flag
    private boolean validPIN = false;
    
    //SW bytes for PIN Failed condition
    //The last nibble is replaced with the
    //number of remaining tries
    private final static short 	SW_PIN_FAILED = (short)0x63C0;
    
        
    /* Security part of declarations */

    // the Security Object necessary to credit the purse
    private ProviderSecurityDomain securityObject = null;

    // the security channel number
    byte secureChannel = (byte)0xFF;

    // the authentication status
    private boolean authenticationDone = false;

    // the secure channel status
    private boolean channelOpened = false;
    
    /**
     * Only this class's install method should create the applet object.
     */
    protected CardID(byte[] buffer, short offset, byte length) {
        
        // data offset is used for application specific parameter.
        // initialization with default offset (AID offset).
        short dataOffset = offset;

        if(length > 9) {
            // Install parameter detail. Compliant with OP 2.0.1.

            // | size | content
            // |------|---------------------------
            // |  1   | [AID_Length]
            // | 5-16 | [AID_Bytes]
            // |  1   | [Privilege_Length]
            // | 1-n  | [Privilege_Bytes] (normally 1Byte)
            // |  1   | [Application_Proprietary_Length]
            // | 0-m  | [Application_Proprietary_Bytes]

            // shift to privilege offset
            dataOffset += (short)( 1 + buffer[offset]);
            // finally shift to Application specific offset
            dataOffset += (short)( 1 + buffer[dataOffset]);
            // checks wrong data length
            //initializing name with ENSIETA = 7 letters
            if(buffer[dataOffset] != 7)
                // return received proprietary data length in the reason
                ISOException.throwIt((short)(ISO7816.SW_WRONG_LENGTH + offset + length - dataOffset));

            // go to proprietary data
            dataOffset++;
        }
        
        // Initializes the name with the "ENSIETA" value 
        //Util.arrayCopy( buffer, (short) ISO7816.OFFSET_CDATA , name, (short) 0, (short) buffer[ISO7816.OFFSET_LC]);
        //we better use the calculated value of data offset
        Util.arrayCopy( buffer, dataOffset , name, (short) 0, (short) 2);
        
        // register this instance
        register();
        
        // ask the system for the Security Object associated to the Applet
        securityObject = OPSystem.getSecurityDomain();

        // applet is personalized and its state can change
        OPSystem.setCardContentState(OPSystem.APPLET_PERSONALIZED);
    }
    
    /**
     * Method installing the applet.
     * @param installparam the array constaining installation parameters
     * @param offset the starting offset in installparam
     * @param length the length in bytes of the data parameter in installparam
     */
    public static void install(byte[] installparam, short offset, byte length )
    throws ISOException
    {
        // applet  instance creation with the initial name
        new CardID(installparam, offset, length );
    }
    
    /**
     * Select method returning true if applet selection is supported.
     * @return boolean status of selection.
     */
    public boolean select()
    {
        validPIN = false;
        // reset security if used.
        // In case of reset deselect is not called
        reset_security();
        // return status of selection
        return true;
    }

    /**
     * Deselect method.
     */
    public void deselect()
    {
        // reset security if used.
        reset_security();
        return;
    }
    
    
    /* The method process() is invoked by JCRE
    * to handle incoming APDUs.
    */
    public void process(APDU apdu) throws ISOException
    {
        //get APDU buffer
        byte[] apduBuffer = apdu.getBuffer();
        switch( (byte) apduBuffer[ISO7816.OFFSET_INS])
        {
            case INS_VERIFY_PIN :
        	verifyPIN(apdu);
                        
            case READ_INS : 
                readName(apdu);
            break;
             
            case UPDATE_INS: 
                updateName(apdu);
            break;
            
            case INS_INIT_UPDATE :
                    if(apduBuffer[ISO7816.OFFSET_CLA] == CLA_INIT_UPDATE)
                        // call initialize/update security method
        	            init_update(apdu) ;
                    else
                        // wrong CLA received
                        ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
            break ;
                
            case INS_EXTERNAL_AUTHENTICATE :
                    if(apduBuffer[ISO7816.OFFSET_CLA] == CLA_EXTERNAL_AUTHENTICATE)
                        // call external/authenticate security method
            	        external_authenticate(apdu) ;
                    else
                        // wrong CLA received
                        ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
            break ;
                
            case ISO7816.INS_SELECT:
            break;
                
            default:
                // The INS code is not supported by the dispatcher
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
            break;
        }  //end of the switch
    }
    /**
     * Handles Verify Pin APDU.
     *
     * @param apdu APDU object
     */
    private void verifyPIN(APDU apdu)
    {
           // get APDU data
	   apdu.setIncomingAndReceive();
           // get APDU buffer
           byte[] apduBuffer = apdu.getBuffer();
           // check that the PIN is not blocked
           if(OPSystem.getTriesRemaining() == 0)
              OPSystem.setCardContentState(OPSystem.APPLET_BLOCKED);

           // Pin format for OP specification
           //
           // |type(2),length|nible(1),nible(2)|nible(3),nible(4)|...|nible(n-1),nible(n)|
           //
           // get Pin length
           byte length = (byte)(apduBuffer[ISO7816.OFFSET_LC] & 0x0F);
           // pad the PIN ASCII value
           for(byte i=length; i<0x0E; i++)
           {
               // only low nibble of padding is used
               apduBuffer[ISO7816.OFFSET_CDATA + i] = 0x3F;
           }
           // fill header TAG
           apduBuffer[0] = (byte)((0x02 << 4) | length);
           // parse ASCII Pin code
           for(byte i=0; i<0x0E; i++)
           {
                // fill bytes with ASCII Pin nibbles
                if((i & 0x01) == 0)
                    // high nibble
                    apduBuffer[(i >> 1)+1] = (byte)((apduBuffer[ISO7816.OFFSET_CDATA + i] & 0x0F) << 4);
                else
                    // low nibble
                    apduBuffer[(i >> 1)+1] |= (byte)(apduBuffer[ISO7816.OFFSET_CDATA + i] & 0x0F);
           }
           // verify the received PIN
           // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
           // !!! WARNING PIN HAS TO BE INITIALIZED BEFORE USE !!!
           // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
           if(OPSystem.verifyPin(apdu, (byte)0))
           {
               // set PIN validity flag
               validPIN = true;
               // if applet state is BLOCKED then restore previous state (PERSONALIZED)
               if(OPSystem.getCardContentState() == OPSystem.APPLET_BLOCKED)
                    OPSystem.setCardContentState(OPSystem.APPLET_PERSONALIZED);
               return;
           }
	
           // the last nibble of returned code is the number of remaining tries
	   ISOException.throwIt((short)(SW_PIN_FAILED + OPSystem.getTriesRemaining()));
    }
    
    
    /**
     * Performs the "init_update" security operation.
     *
     * @param apdu The APDU to process.
     */
    private void init_update( APDU apdu )
    {
        // receives data
        apdu.setIncomingAndReceive();
        // checks for existing active secure channel
        if(channelOpened)
        {
            // close the openned security channel
            try
            {
                securityObject.closeSecureChannel(secureChannel);
            }
            catch(CardRuntimeException cre2)
            {
                // channel number is invalid. this case is ignored
            }
            // set the channel flag to close
            channelOpened = false;
        }
        try
        {
            // open a new security channel
            secureChannel = securityObject.openSecureChannel(apdu);
            // set the channel flag to open
            channelOpened = true;
            // get expected length
            short expected = apdu.setOutgoing();
            // send authentication result
            // expected length forced to 0x1C
            apdu.setOutgoingLength((byte)0x1C);
            apdu.sendBytes(ISO7816.OFFSET_CDATA, (byte)0x1c);
        }
        catch(CardRuntimeException cre)
        {
            // no available channel or APDU is invalid
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
    }

    
    
    /**
     * Performs the "external_authenticate" security operation.
     *
     * @param apdu The APDU to process.
     */
    private void external_authenticate( APDU apdu )
    {
        // receives data
        apdu.setIncomingAndReceive();
        // checks for existing active secure channel
        if(channelOpened)
        {
            try
            {
                // try to authenticate the client
                securityObject.verifyExternalAuthenticate(secureChannel, apdu);
                // authentication succeed
                authenticationDone = true;
            }
            catch(CardRuntimeException cre)
            {
                // authentication fails
                // set authentication flag to fails
                authenticationDone = false;
                // close the openned security channel
                try {
                    securityObject.closeSecureChannel(secureChannel);
                } catch(CardRuntimeException cre2) {
                    // channel number is invalid. this case is ignored
                }
                // set the channel flag to close
                channelOpened = false;
                // send authentication result
                ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
            }
            // send authentication result
            ISOException.throwIt(ISO7816.SW_NO_ERROR);
        }
        else
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
    }

    
    
    /**
     * The "reset_security" method close an opened secure channel if exist.
     * @return void.
     */
    public void reset_security()
    {
        // close the secure channel if openned.
        if(secureChannel != (byte)0xFF)
        {
            try
            {
                // close the openned security channel
                securityObject.closeSecureChannel(secureChannel);
            }
            catch(CardRuntimeException cre2)
            {
                // channel number is invalid. this case is ignored
            }
            // reset security parameters
            secureChannel = (byte)0xFF;
            channelOpened = false;
            authenticationDone = false;
        }
        return;
    }
			  
    
        
    /* The readName method copies the
    * cardholder's name to the APDU buffer
    * and send the data. JCRE takes care of
    * forming the correct APDU response
    * with the appropriate status word; in this
    * case, normal completion (0x9000).
    */
    public void readName(APDU apdu)
    {
        byte[] buffer = apdu.getBuffer();
        Util.arrayCopy( name, (short) 0, buffer, (short) 0, (short) name.length);
        /* Send data equal to length of name
        * starting at offset 0
        */
        apdu.setOutgoingAndSend((short)0, (short) name.length);
        return;
    }
    
    
    /* The updateName method expects incoming data
    * consisting of the cardholder's name
    * and receives the data starting at offset
    * ISO7816.OFFSET_CDATA. (Code to permit only
    * restricted updating is not shown here).
    */
    public void updateName(APDU apdu) throws ISOException
    {
        byte[] buffer = apdu.getBuffer();
        // The operation is allowed only if master pin is validated
	if (!validPIN)
	    ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        Util.arrayCopy( buffer, (short) ISO7816.OFFSET_CDATA , name, (short) 0, (short) buffer[ISO7816.OFFSET_LC]);
        return;
    }
}