/** * Copyright (c) 1999 GEMPLUS group. All Rights Reserved. *------------------------------------------------------------------------------ * Project name: GemXpresso RAD II Environment * - Javacard Template: applet part - * * Platform : Java virtual machine * Language : JAVA 1.1.x * Devl tool : Symantec VisualCafe 3.0C * * Original author: Gemplus Java Card Group Software Environment *------------------------------------------------------------------------------ */ /* * Package name */ package com.gemplus.examples.purse; /* * Imported packages */ import javacard.framework.*; public class Purse extends javacard.framework.Applet { // The APDU constants for all the commands. private final static byte CLASS_PURSE = (byte)0x90; private final static byte COMMAND_GET_BALANCE = (byte)0x10; private final static byte COMMAND_DEBIT = (byte)0x12; private final static byte COMMAND_CREDIT = (byte)0x14; private final static byte COMMAND_VERIFY_PIN = (byte)0x16; //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; // The illegal amount value for the exceptions. private final static short ILLEGAL_AMOUNT = 1 ; // The maximum balance in this purse. private static final short maximumBalance = 10000 ; //The initial balance value. This is the amount of purse at creation private static final short initialBalance = 100 ; // The current balance in this card applet "Purse". private short balance ; // The master pin code validation is necessary to credit the purse private OwnerPIN masterPIN; /** * Only this class's install method should create the applet object. */ protected Purse(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 if(buffer[dataOffset] != 4) // return received proprietary data length in the reason ISOException.throwIt((short)(ISO7816.SW_WRONG_LENGTH + offset + length - dataOffset)); // go to proprietary data dataOffset++; } else { // Install parameter compliant with OP 2.0. if(length != 4) ISOException.throwIt((short)(ISO7816.SW_WRONG_LENGTH + length)); } // creates and initialize the master pin: try limit is 3 and PIN length is 4 masterPIN = new OwnerPIN ((byte)3, (byte)4); masterPIN.update(buffer, dataOffset,(byte)4); // Initializes this counter with a balance set to 100. balance = initialBalance; // register this instance register(buffer, (short)(offset + 1), (byte)buffer[offset]); } /** * Method installing the applet. * @param bArray the array constaining installation parameters * @param bOffset the starting offset in bArray * @param bLength the length in bytes of the data parameter in bArray */ public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException { /* applet instance creation */ new Purse (bArray, bOffset, (byte)bLength); } /** * Select method returning true if applet selection is supported. * @return boolean status of selection. */ public boolean select() { /* return status of selection */ return true; } /** * Deselect method. */ public void deselect() { //Action before applet deselection masterPIN.reset(); return; } /** * Method processing an incoming APDU. * @see APDU * @param apdu the incoming APDU * @exception ISOException with the response bytes defined by ISO 7816-4 */ public void process(APDU apdu) throws ISOException { // get the APDU buffer byte[] apduBuffer = apdu.getBuffer(); byte dataLength = apduBuffer[ISO7816.OFFSET_LC]; // the APDU data is available in 'apduBuffer' //checks the Instruction Class if( apduBuffer[ISO7816.OFFSET_CLA] != this.CLASS_PURSE && apduBuffer[ISO7816.OFFSET_CLA] != 0) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); // This "try" is mandatory because a method (debit) // can throw a javacard.framework.UserException try { switch(apdu.getBuffer()[ISO7816.OFFSET_INS]) { case COMMAND_VERIFY_PIN: processVerifyPIN(apdu); break; case COMMAND_GET_BALANCE: getBalance(apdu); break; case COMMAND_DEBIT: debit(apdu); break; case COMMAND_CREDIT: credit(apdu); 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 } // end of the try catch (UserException e) { // Translates the UserException in an ISOException. if (e.getReason() == ILLEGAL_AMOUNT) // illegal amount throw new ISOException (ISO7816.SW_DATA_INVALID) ; } } //------------------------------------------------------------------------- //- P R I V A T E M E T H O D S - //------------------------------------------------------------------------- /** * Handles Verify Pin APDU. * * @param apdu APDU object */ private void processVerifyPIN(APDU apdu) { byte[] buffer = apdu.getBuffer(); byte pinLength = buffer[ISO7816.OFFSET_LC]; byte triesRemaining = (byte)0; short count = apdu.setIncomingAndReceive(); // get expected data if(count> 8); apduBuffer[6] = (byte)balance; // Sends the APDU response apdu.setOutgoing(); // Switches to output mode apdu.setOutgoingLength((short)2); // 2 bytes to return // Offset and length of bytes to return in the APDU buffer apdu.sendBytes((short)5, (short)2); } /** * Performs the "debit" operation on this counter. * * @param apdu The APDU to process. * @exception ISOException If the APDU is invalid. * @exception UserException If the amount to debit is invalid. */ private void debit(APDU apdu) throws ISOException, UserException { // The operation is allowed only if master pin is validated if (!masterPIN.isValidated()) ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); // Gets the APDU buffer zone byte[] apduBuffer = apdu.getBuffer(); // Gets the length of bytes to recieved from the terminal and receives them // If does not receive 4 bytes throws an ISO.SW_WRONG_LENGTH exception if(apduBuffer[4] != 2 || apdu.setIncomingAndReceive() != 2) { // A first way to throw an exception in Java Card ISOException.throwIt(ISO7816.SW_WRONG_LENGTH) ; } // Reads the debit amount from the APDU buffer // Starts at offset 5 in the APDU buffer since the 5 first bytes // are used by the APDU command part short amount = (short)(((apduBuffer[6]) & (short)0x00FF) | ((apduBuffer[5] << 8) & (short)0xFF00)); // Tests if the debit is valid if((balance >= amount) && (amount > 0)) { // Does the debit operation balance -= amount ; // Writes the new balance into the APDU buffer // (Writes after the debit amount in the APDU buffer) apduBuffer[9] = (byte)(balance >> 8); apduBuffer[10] = (byte)balance; // Sends the APDU response apdu.setOutgoing(); // Switches to output mode apdu.setOutgoingLength((short)2); // 4 bytes to return // Offset and length of bytes to return in the APDU buffer apdu.sendBytes((short)9, (short)2); } else { // An other way to throw an exception in JavaCard. throw new UserException(ILLEGAL_AMOUNT); // Makes no real sense here: just to illustrate the "try...catch" in process! } } /** * Performs the "credit" operation on this counter. The operation is allowed only * if master pin is validated * * @param apdu The APDU to process. * @exception ISOException If the APDU is invalid or if the amount to credit * is invalid. */ private void credit(APDU apdu) throws ISOException { // The operation is allowed only if master pin is validated if (!masterPIN.isValidated()) ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); // Gets the APDU buffer zone byte[] apduBuffer = apdu.getBuffer(); // Gets the length of bytes to recieved from the terminal and receives them // If does not receive 4 bytes throws an ISO.SW_WRONG_LENGTH exception if( apduBuffer[4] != 2 || apdu.setIncomingAndReceive() != 2 ) { // A second way to throw an exception in Java Card throw new ISOException( ISO7816.SW_WRONG_LENGTH ); } // Reads the credit amount from the APDU buffer // Starts at offset 5 in the APDU buffer since the 5 first bytes // are used by the APDU command part short amount = (short)(((apduBuffer[6] ) & (short)0x00FF) | ((apduBuffer[5] << 8) & (short)0xFF00)); // Tests if the credit is valid if(((short)(balance + amount) > maximumBalance) || (amount <= (short)0)) throw new ISOException(ISO7816.SW_DATA_INVALID); else // does the credit operation balance += amount; } }