package pursejc;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.OwnerPIN;
import javacard.framework.PINException;
import javacard.framework.Util;
/**
*
* @author C Michoudet, A Vernotte
*
*/
public class PurseApplet extends Applet {
//apdu commands
public static final byte CLA_PURSEAPPLET = (byte) 0x25;
public static final byte INS_VERIFY = (byte) 0x01;
public static final byte INS_CREDIT = (byte) 0x02;
public static final byte INS_DEBIT = (byte) 0x03;
public static final byte INS_GET_BALANCE = (byte) 0x04;
private static final byte INS_GET_STATE = (byte) 0x05;
private static final byte INS_CONFIGURE = (byte) 0x06;
// maximum wallet balance
public static final short maxBalance = 10000;
// maximum transaction amount
public static final byte maxTrans = 100;
// maximum number of transaction
public static final byte maxNbTrans = 3;
// maximum number of incorrect tries before the
// PIN is blocked
public static final byte maxUserTries = (byte) 0x03;
public static final byte maxBankTries = (byte) 0x03;
// maximum size PIN
public static final byte MaxPINSize = (byte) 0x04;
// Applet-specific status words:
public static final short SW_VERIFICATION_FAILED = 0x6300;
public static final short SW_PIN_VERIFICATION_REQUIRED = 0x6301;
public static final short SW_INVALID_TRANSACTION_AMOUNT = 0x6A83;
public static final short SW_EXCEED_MAXIMUM_BALANCE = 0x6A84;
public static final short SW_NEGATIVE_BALANCE = 0x6A85;
public static final short SW_TOO_MUCH_TRANS = 0x6A86;
public static final short SW_PIN_VERIF_EXCEEDED = 0x6A87;
// LifeCycleState
public static final byte LFC_PRE_PERSO = (byte) 0x50;
public static final byte LFC_USE = (byte) 0x51;
public static final byte LFC_INVALID = (byte) 0x52;
public static final byte LFC_DEAD = (byte) 0x53;
/* Attributs */
private short balance;
private byte lifeCycleState;
private short nbTransLeft;
/* Pins */
OwnerPIN userPin;
OwnerPIN bankPin;
/* Constructeur */
private PurseApplet(byte bArray[], short bOffset, byte bLength) {
userPin = new OwnerPIN(maxUserTries, MaxPINSize);
bankPin = new OwnerPIN(maxBankTries, MaxPINSize);
this.balance = 0;
nbTransLeft = PurseApplet.maxNbTrans;
lifeCycleState = PurseApplet.LFC_PRE_PERSO;
userPin.update(new byte[]{0,0,0,0}, (short) 0, (byte)4);
bankPin.update(new byte[]{0,0,0,0}, (short) 0, (byte)4);
this.register();
}
public static void install(byte bArray[], short bOffset, byte bLength)
throws ISOException {
new PurseApplet(bArray, bOffset, bLength);
}
public boolean select() {
if (userPin.getTriesRemaining() == 0)
return false;
return true;
}
public void deselect() {
userPin.reset();
bankPin.reset();
}
public void process(APDU apdu) throws ISOException {
byte[] buffer = apdu.getBuffer();
if (this.selectingApplet())
return;
if (buffer[ISO7816.OFFSET_CLA] != CLA_PURSEAPPLET) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
switch (buffer[ISO7816.OFFSET_INS]) {
case INS_GET_STATE:
getState(apdu);
return;
case INS_GET_BALANCE:
getBalance(apdu);
return;
case INS_DEBIT:
debit(apdu);
return;
case INS_CREDIT:
credit(apdu);
return;
case INS_VERIFY:
verify(apdu);
return;
case INS_CONFIGURE:
configure(apdu);
return;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
/**
* La commande configure permet de personnaliser une carte
* Les deux pin sont envoy�s dans une seule commande
* les 4 premiers indices correspondent au pin user
* les 4 derniers indices correspondent au pin bank
* @param apdu
*/
private void configure(APDU apdu) {
byte[] buffer = apdu.getBuffer();
//verification longueur des pin
if (buffer[ISO7816.OFFSET_LC] != 8)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
//on verifie qu'il s'agit bien d'un chiffre
for (byte i = ISO7816.OFFSET_CDATA; i< (ISO7816.OFFSET_CDATA + ISO7816.OFFSET_LC);i++) {
if (buffer[i] < 0 || buffer[i] > 9)
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
lifeCycleState = PurseApplet.LFC_USE;
userPin.update(buffer, (short) ISO7816.OFFSET_CDATA, (byte)4);
bankPin.update(buffer, (short) (ISO7816.OFFSET_CDATA+4), (byte)4);
}
/**
* Commande qui retourne l'�tat (lifeCycleState)
* @param apdu
*/
private void getState(APDU apdu) {
byte[] buffer = apdu.getBuffer();
buffer[0] = lifeCycleState;
apdu.setOutgoingAndSend((short) 0, (short) 1);
}
/**
* permet de v�rifier le code pin de l'utilisateur ou de la banque
* L'utilisateur poss�de 3 essais
* la banque poss�de 4 essais
* @param apdu
*/
private void verify(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// receive the PIN data for validation.
byte byteRead = (byte) (apdu.setIncomingAndReceive());
// check pin
// the PIN data is read into the APDU buffer
// starting at the offset ISO7816.OFFSET_CDATA
// the PIN data length = byteRead
if (userPin.check(buffer, ISO7816.OFFSET_CDATA, byteRead) == false)
ISOException.throwIt(SW_VERIFICATION_FAILED);
}
/**
* permet de cr�diter le solde de la carte
* verifie d'abord que le nombre de transactions max n'est pas depasse.
* verifie aussi que le code pin est valide.
*
* @param apdu
*/
private void credit(APDU apdu) {
if (checkNbTrans()) {
if (!userPin.isValidated()) {
if (userPin.getTriesRemaining() == 0) {
lifeCycleState = LFC_INVALID;
ISOException.throwIt(SW_PIN_VERIF_EXCEEDED);
}
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
}
byte[] buffer = apdu.getBuffer();
// get the number of bytes in the
// data field of the command APDU
byte numBytes = buffer[ISO7816.OFFSET_LC];
// recieve data
// data are read into the apdu buffer
// at the offset ISO7816.OFFSET_CDATA
byte byteRead = (byte) (apdu.setIncomingAndReceive());
// error if the number of data bytes
// read does not match the number in the Lc byte
if ((numBytes != 1) || (byteRead != 1))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// get the credit amount
byte creditAmount = buffer[ISO7816.OFFSET_CDATA];
// check the credit amount
if ((creditAmount > maxTrans) || (creditAmount < 0))
ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);
// check the new balance
if ((short) (balance + creditAmount) > maxBalance)
ISOException.throwIt(SW_EXCEED_MAXIMUM_BALANCE);
// credit the amount
balance = (short) (balance + creditAmount);
nbTransLeft--;
return;
} else {
ISOException.throwIt(SW_TOO_MUCH_TRANS);
}
}
/**
* permet de debiter de l'argent stocke dans la carte
* verifie d'abord que le nombre de transactions max n'est pas depasse.
*
* @param apdu
*/
private void debit(APDU apdu) {
if (checkNbTrans()) {
byte[] buffer = apdu.getBuffer();
byte numBytes = (byte) (buffer[ISO7816.OFFSET_LC]);
byte byteRead = (byte) (apdu.setIncomingAndReceive());
if ((numBytes != 1) || (byteRead != 1))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// get debit amount
byte debitAmount = buffer[ISO7816.OFFSET_CDATA];
// check debit amount
if ((debitAmount > maxTrans) || (debitAmount < 0))
ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);
// check the new balance
if ((short) (balance - debitAmount) < (short) 0)
ISOException.throwIt(SW_NEGATIVE_BALANCE);
balance = (short) (balance - debitAmount);
nbTransLeft--;
}
}
/**
* verifie si le nombre de transition n'est pas maximal
* si c'est le cas, changement du statut de la carte en DEAD
*
* @return resultat test
*/
private boolean checkNbTrans() {
if (nbTransLeft < 0) {
lifeCycleState = LFC_DEAD;
ISOException.throwIt(SW_TOO_MUCH_TRANS);
return false;
} else return true;
}
/**
* Pour visualiser le solde de la carte
* @param apdu
*/
private void getBalance(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// inform system that the applet has finished
// processing the command and the system should
// now prepare to construct a response APDU
// which contains data field
short le = apdu.setOutgoing();
if ( le < 2 ) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
//informs the CAD the actual number of bytes
//returned
apdu.setOutgoingLength((byte)2);
// move the balance data into the APDU buffer
// starting at the offset 0
buffer[0] = (byte)(balance >> 8);
buffer[1] = (byte)(balance & 0xFF);
// send the 2-balance byte at the offset
// 0 in the apdu buffer
apdu.sendBytes((short)0, (short)2);
}
}