package com.st;
import java.io.IOException;
import java.util.Calendar;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;
public class CardApplet extends Applet {
/*IMPORTANT NOTE
* any class that is expected to be persistent
* throughly the applet life-cycle should
* implement serializable interface
* test that an applet complies at any time
* by running an /atr in the simulator, this
* forces serialization and will through an exception
* if there is a problem.
*
* If any library classes through this exception please
* notify support@simplytapp.com and copy/paste the
* exception trace in the email*/
/*IMPORTANT NOTE 2
*notice that the "main" function is located in
*cardWrapper.CardWrapper
*for simulator debugging to work properly, you must
*configure this in debug configuration*/
/*IMPORTANT NOTE 3 (simulation tutorial)
*using the simulator is simple, just click debug.
*some quick hints to installing and testing your
*applet from simulator:
*
* #reset card and select card manager
* /card
*
* #open a secure channel to card manager
* auth
*
* #install the PPSE and card applet in this SDK
* #the package converted to ascii hex is: com.st -> 636f6d2e7374
* #the PPSE applet module converted to ascii hex is: Ppse2Pay -> 5070736532506179
* #the card applet module converted to ascii hex is: CardApplet -> 436172644170706c6574
* #try to keep the package name and module name
* #to between 5 and 16 characters to make your life easier.
* #this example installs the PPSE module as "2PAY.SYS.DDF01" -> 325041592e5359532e4444463031
* #this example also installs the card applet as A0000000031010
* install -i 325041592e5359532e4444463031 -q C9#() 636f6d2e7374 5070736532506179
* install -i A0000000031010 -q C9#() 636f6d2e7374 436172644170706c6574
*
* #select them now for testing!
* /select |2PAY.SYS.DDF01
* /select A0000000031010
*
* #see website for more on gpjNG simulation
* */
private static final long serialVersionUID = 1L;
//these variables define the state of the profile
//the pre_perso state cannot be an ACTIVE profile
//the perso state means the card is in personalization mode
//the alive state means the card is personalized
public final byte PRE_PERSO = (byte)0x00;
public final byte PERSO = (byte)0x01;
public final byte ALIVE = (byte)0x02;
public byte STATE = PERSO;
//more state variables
private byte state = 0;
private final byte not_alive = (byte)0x01;
private final byte selected = (byte)0x02;
//RR - Card Record - this is card specific
public byte[] RR = new byte[] {
0x70, 0x02, 0x57, 0x00
};
//AID - Application Identifier -
public final byte[] AID = new byte[] {(byte)0xA0,(byte)0x00,(byte)0x00,(byte)0x00, //VisaContactless
(byte)0x03,(byte)0x10,(byte)0x10 };
//AL - Application Label -
public final byte[] AL = new byte[] {0x56,0x49,0x53,0x41,0x20,0x43,0x52,0x45,0x44,0x49,0x54}; //"VISACREDIT"
//DF - Dedicated File (AID) -
public final byte[] DF = new byte[] { (byte)0xA0,(byte)0x00,(byte)0x00,(byte)0x00, //VisaContactless
(byte)0x03,(byte)0x10,(byte)0x10 };
public static String bytArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder();
for(byte b: a)
sb.append(String.format("%02x", b&0xff));
return sb.toString();
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
public static void install(byte[] bArray, short bOffset, byte bLength){
new CardApplet().register();
}
public void process(APDU apdu) {
byte[] buf = apdu.getBuffer();
if (selectingApplet()) {
//verify that the class for this instruction is correct
if((short)(buf[ISO7816.OFFSET_CLA] & 0xFF) != 0x00)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
//get the rest of the apdu and check length
if((short)(buf[ISO7816.OFFSET_LC] & 0xFF) != apdu.setIncomingAndReceive())
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
//if the active profile state has not been pre-personalized
//this application cannot be used
short len;
if(STATE == PERSO)
{
for(short i=0;i<AL.length;i++)
buf[i] = AL[i];
len = (short)(AL.length);
state = not_alive;
}
else
{
//send Visa
buf[0]=(byte)0x6F; //FCI Template
buf[1]=(byte)(12 + DF.length + AL.length); //length
buf[2]=(byte)0x84; //DF
buf[3]=(byte)DF.length; //length
for(short i=0;i<DF.length;i++)
buf[4+i] = DF[i];
buf[4+DF.length]=(byte)0xA5; //FCI Proprietary Template
buf[5+DF.length]=(byte)(8+AL.length); //length
buf[6+DF.length]=(byte)0x50; //AL
buf[7+DF.length]=(byte)AL.length; //length
for(short i=0;i<AL.length;i++)
buf[8+DF.length+i] = AL[i];
buf[8+DF.length+AL.length] = (byte)0x9F;
buf[9+DF.length+AL.length] = (byte)0x38;
buf[10+DF.length+AL.length] = (byte)0x03;
buf[11+DF.length+AL.length] = (byte)0x9F;
buf[12+DF.length+AL.length] = (byte)0x66;
buf[13+DF.length+AL.length] = (byte)0x02;
len = (short)(14 + AL.length + DF.length);
state = selected;
}
apdu.setOutgoingAndSend((short)0,len);
return;
}
switch (buf[ISO7816.OFFSET_INS]) {
case (byte) 0xA4: //select AID
//verify that the class for this instruction is correct
if((short)(buf[ISO7816.OFFSET_CLA] & 0xFF) != 0x00)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
//check that P1 & P2 are correct
if(buf[ISO7816.OFFSET_P1] != (byte) 0x04 || buf[ISO7816.OFFSET_P2] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
//get the rest of the apdu and check length
if((short)(buf[ISO7816.OFFSET_LC] & 0xFF) != apdu.setIncomingAndReceive())
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
//otherwise, the file name was wrong for this select
else ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
case (byte) 0xA8: //get processing options
//verify that the class for this instruction is correct
if((short)(buf[ISO7816.OFFSET_CLA] & 0xFF) != 0x80)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
//check state - this command only works in selected state
if(state != selected)
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
//check that P1 & P2 are correct
if(buf[ISO7816.OFFSET_P1] != (byte) 0x00 || buf[ISO7816.OFFSET_P2] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
//check that LC is 0x02
if((short)(buf[ISO7816.OFFSET_LC] & 0xFF) != (short) 0x04)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
//get the rest of the apdu and check length
if((short)(buf[ISO7816.OFFSET_LC] & 0xFF) != apdu.setIncomingAndReceive())
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
//check PDOL data that it is '83028000'
if(buf[ISO7816.OFFSET_CDATA] != (byte) 0x83 || buf[ISO7816.OFFSET_CDATA+1] != (byte) 0x02)
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
if(buf[ISO7816.OFFSET_CDATA+2] != (byte) 0x80 || buf[ISO7816.OFFSET_CDATA+3] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
byte[] GPO = new byte[]{(byte)0x80,0x06,0x00,(byte)0x80,0x08,0x01,0x01,0x00};
for(short i=0;i<GPO.length;i++)
buf[i] = GPO[i];
apdu.setOutgoingAndSend((short)0, (short)GPO.length);
break;
case (byte) 0xE2:
apdu.setIncomingAndReceive();
String pan = "";
int num = 0;
short i;
boolean d=false;
short date = 0;
Calendar exp = Calendar.getInstance();
for(i=0;i<buf[ISO7816.OFFSET_LC];i++)
{
short nib = (short)((short)(buf[ISO7816.OFFSET_CDATA+i]&0xFF)>>4);
if(!d)
{
if(nib==0xd)
d=true;
else
{
nib+=0x30;
pan+=(char)(nib);
}
}
else
{
date++;
if(date==1 || date==3)
num=(int)nib;
if(date==2)
{
num=nib+num*10+2000;
exp.set(Calendar.YEAR, num);
}
if(date==4)
{
num=nib+num*10;
exp.set(Calendar.MONTH, num-1);
}
if(date==4)
break;
}
nib = (short)(buf[ISO7816.OFFSET_CDATA+i]&0xF);
if(!d)
{
if(nib==0xd)
d=true;
else
{
nib+=0x30;
pan+=(char)(nib);
}
}
else
{
date++;
d = true;
if(date==1 || date==3)
num=(int)nib;
if(date==2)
{
num=nib+num*10+2000;
exp.set(Calendar.YEAR, num);
}
if(date==4)
{
num=nib+num*10;
exp.set(Calendar.MONTH, num-1);
}
if(date==4)
break;
}
}
try {
setStatePerso();
setStatePersonalized(pan, exp, "", "");
} catch (IOException e) {
}
//store the data into the RR
RR = new byte[((buf[ISO7816.OFFSET_LC]&0xFF)+4)];
buf[0] = 0x70;
buf[1] = (byte)(2 + (short)(buf[ISO7816.OFFSET_LC]&0xFF));
buf[2] = 0x57;
buf[3] = (byte)((short)(buf[ISO7816.OFFSET_LC]&0xFF));
Util.arrayCopy(buf,ISO7816.OFFSET_CDATA,buf,(short)4,(short)(buf[ISO7816.OFFSET_LC]&0xFF));
Util.arrayCopy(buf,(short)0,RR,(short)0,(short)RR.length);
STATE = ALIVE;
break;
case (byte) 0xB2: //read record
//verify that the class for this instruction is correct
if((short)(buf[ISO7816.OFFSET_CLA] & 0xFF) != 0x00)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
//check state - this command only works in selected state
if(state != selected)
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
//check that P1 & P2 are correct
if(buf[ISO7816.OFFSET_P1] != (byte) 0x01 || buf[ISO7816.OFFSET_P2] != (byte) 0x0C)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
//check that LC is 0x02
if((short)(buf[ISO7816.OFFSET_LC] & 0xFF) != (short) 0x00)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
//get the rest of the apdu and check length
if((short)(buf[ISO7816.OFFSET_LC] & 0xFF) != apdu.setIncomingAndReceive())
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
for(i=0;i<RR.length;i++)
buf[i] = RR[i];
apdu.setOutgoingAndSend((short)0,(short)RR.length);
break;
default:
// good practice: If you don't know the INStruction, say so:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
}