/**
*
*/
package com.st;
import java.io.IOException;
import java.io.Serializable;
import java.util.Calendar;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.APDU;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacardx.crypto.Cipher;
/**
*
*/
public class PayPass extends Applet {
/**
*
*/
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
private final byte PRE_PERSO = (byte) 0x00;
private final byte PERSO = (byte) 0x01;
private final byte ALIVE = (byte) 0x02;
// this class is the main storage class for card profile storage
private class Profile implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
// upon successful pre-personalization,
// the profile can be considered valid.
// until that data is filled in, the profile
// cannot be used and should be considered invalid
public byte STATE = PRE_PERSO;
/**
* ****************************************************
* **************Pre-Personalization Data*****************
* ******************************************************
*/
// ATC - Application Transaction Counter -
public byte[] ATC = {(byte) 0x00, (byte) 0x00};
// CSN - Chip Serial Number -
public byte[] CSN = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
// NEED ISSUER TO SUPPLY THESE VALUES FOR
// VER_KMC
// KMC_ID
// KD_PERSO
//
// VER_KMC - DES Master Key Version -
public byte VER_KMC; //populated during install of applet
// KMC_ID - DES Master Key ID -
public byte[] KMC_ID = new byte[6]; //populated during install of applet
// KD_PERSO - **DERIVED FROM DES MASTER KEY (KMC)**
public byte[] KD_PERSO = new byte[16]; //populated during install of applet
/**
* ****************************************************
* ****************Personalization Data*******************
* ******************************************************
*/
/////////////////
// DGI 0101 DATA//
/////////////////
public byte[] DGI0101 = new byte[176];
public byte DGI0101_LEN = (byte) 0x00;
/////////////////
// DGI A001 DATA//
/////////////////
// AC - Application Control
public byte[] AC = new byte[3];
// CVC3_T1 - Static CVC3 Track 1 -
public byte[] CVC3_T1 = new byte[2];
// CVC3_T2 - Static CVC3 Track 2 -
public byte[] CVC3_T2 = new byte[2];
// IVCVC3_T1 - CVC3 Track 1 -
public byte[] IVCVC3_T1 = new byte[2];
// IVCVC3_T2 - CVC3 Track 2 -
public byte[] IVCVC3_T2 = new byte[2];
/////////////////
// DGI A002 DATA//
/////////////////
// KD_CVC3 - 3DES Key For CVC3 Generation -
public byte[] KD_CVC3 = new byte[16];
//private encryption key objects used
public DESKey DESKEY_KD_CVC3_L_EN = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);
public DESKey DESKEY_KD_CVC3_R_DE = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);
// private cipher objects used
public Cipher CIPHER_KD_CVC3_L_EN = Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD, false);
public Cipher CIPHER_KD_CVC3_R_DE = Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD, false);
}
public Profile PROFILE;
/**
* ****************************************************
* **************MasterCard Specific Data*****************
* ******************************************************
*/
// AID - Application Identifier -
public final byte[] AID = {(byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, //MasterCard
(byte) 0x04, (byte) 0x10, (byte) 0x10};
// public static byte[] AID = {(byte)0xA0,(byte)0x00,(byte)0x00,(byte)0x00, //Maestro
// (byte)0x04,(byte)0x30,(byte)0x60 };
// AL - Application Label - 'MasterCard' or 'Maestro'
public final byte[] AL = {(byte) 0x4D, (byte) 0x61, (byte) 0x73, (byte) 0x74, //MasterCard
(byte) 0x65, (byte) 0x72, (byte) 0x43, (byte) 0x61,
(byte) 0x72, (byte) 0x64};
// public static byte[] AL = {(byte)0x4D,(byte)0x61,(byte)0x65,(byte)0x73, //Maestro
// (byte)0x74,(byte)0x72,(byte)0x6F };
// DF - Dedicated File (AID) -
public final byte[] DF = {(byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, //MasterCard
(byte) 0x04, (byte) 0x10, (byte) 0x10};
// public static byte[] DF = {(byte)0xA0,(byte)0x00,(byte)0x00,(byte)0x00, //Maestro
// (byte)0x04,(byte)0x30,(byte)0x60 };
// AFL - Application File Locator -
public final byte[] AFL = {(byte) 0x08, (byte) 0x01, (byte) 0x01, (byte) 0x00};
// AIP - Application Interchange Profile -
public final byte[] AIP = {(byte) 0x00, (byte) 0x00};
/**
* ****************************************************
* *************Pre-Defined Global Variables**************
* ******************************************************
*/
// private encryption key objects used
private DESKey DESKEY_KD_PERSO_L_EN;
private DESKey DESKEY_KD_PERSO_R_DE;
private DESKey DESKEY_KD_PERSO_L_DE;
private DESKey DESKEY_KD_PERSO_R_EN;
// private cipher objects used
private Cipher CIPHER_KD_PERSO_L_EN;
private Cipher CIPHER_KD_PERSO_R_DE;
private Cipher CIPHER_KD_PERSO_L_DE;
private Cipher CIPHER_KD_PERSO_R_EN;
// create buffer to put data to encrypt
private byte[] CVC3_DATA;
//create buffer to put long command strings in
private byte[] CMD_BUF;
// create buffer to put a calculated MAC in
private byte[] MAC;
// state variables
private byte state;
private final byte not_alive = (byte) 0x00;
private final byte selected = (byte) 0x01;
private final byte initiated = (byte) 0x02;
public PayPass(byte[] bArray, short bOffset, byte bLength) {
if (bLength != 27)
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
// transaction starts
JCSystem.beginTransaction();
// set up and initialize all the DES encryption/descrytion ciphers used in the app
DESKEY_KD_PERSO_L_EN = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);
DESKEY_KD_PERSO_R_DE = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);
DESKEY_KD_PERSO_L_DE = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);
DESKEY_KD_PERSO_R_EN = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);
CIPHER_KD_PERSO_L_EN = Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD, false);
CIPHER_KD_PERSO_R_DE = Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD, false);
CIPHER_KD_PERSO_L_DE = Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD, false);
CIPHER_KD_PERSO_R_EN = Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD, false);
// transaction ends
JCSystem.commitTransaction();
// define RAM buffers for faster operation
CVC3_DATA = JCSystem.makeTransientByteArray((short) 16, JCSystem.CLEAR_ON_DESELECT);
CMD_BUF = JCSystem.makeTransientByteArray((short) 261, JCSystem.CLEAR_ON_DESELECT);
MAC = JCSystem.makeTransientByteArray((short) 8, JCSystem.CLEAR_ON_DESELECT);
// on initialize the current state is not_alive
state = not_alive;
PROFILE = new Profile();
// testing area
// pre-personalization data
// issuer supply
PROFILE.VER_KMC = (byte) 0x01; // MC version
PROFILE.VER_KMC = bArray[bOffset]; // MC version
PROFILE.KMC_ID[0] = (byte) 0x54; // key id
PROFILE.KMC_ID[1] = (byte) 0x13;
PROFILE.KMC_ID[2] = (byte) 0x12;
PROFILE.KMC_ID[3] = (byte) 0xFF;
PROFILE.KMC_ID[4] = (byte) 0xFF;
PROFILE.KMC_ID[5] = (byte) 0xFF;
Util.arrayCopyNonAtomic(bArray, (short) (bOffset + 1), PROFILE.KMC_ID, (short) 0, (short) 6);
PROFILE.KD_PERSO[0] = (byte) 0xA8; // personalization key
PROFILE.KD_PERSO[1] = (byte) 0x6A;
PROFILE.KD_PERSO[2] = (byte) 0x3D;
PROFILE.KD_PERSO[3] = (byte) 0x06;
PROFILE.KD_PERSO[4] = (byte) 0xCA;
PROFILE.KD_PERSO[5] = (byte) 0xE7;
PROFILE.KD_PERSO[6] = (byte) 0x04;
PROFILE.KD_PERSO[7] = (byte) 0x6A;
PROFILE.KD_PERSO[8] = (byte) 0x10;
PROFILE.KD_PERSO[9] = (byte) 0x63;
PROFILE.KD_PERSO[10] = (byte) 0x58;
PROFILE.KD_PERSO[11] = (byte) 0xD5;
PROFILE.KD_PERSO[12] = (byte) 0xB8;
PROFILE.KD_PERSO[13] = (byte) 0x23;
PROFILE.KD_PERSO[14] = (byte) 0x9C;
PROFILE.KD_PERSO[15] = (byte) 0xBE;
Util.arrayCopyNonAtomic(bArray, (short) (bOffset + 7), PROFILE.KD_PERSO, (short) 0, (short) 16);
PROFILE.CSN[0] = (byte) 0x89;
PROFILE.CSN[1] = (byte) 0xAA;
PROFILE.CSN[2] = (byte) 0x7F;
PROFILE.CSN[3] = (byte) 0x00;
Util.arrayCopyNonAtomic(bArray, (short) (bOffset + 23), PROFILE.CSN, (short) 0, (short) 4);
// end issuer supply
// profile can now be considered in personalization state
PROFILE.STATE = PERSO;
}
public static void install(byte[] bArray, short bOffset, byte bLength) {
// GP-compliant JavaCard applet registration
new PayPass(bArray, bOffset, bLength).register(bArray, (short) (bOffset + 1),
bArray[bOffset]);
}
public void get_data(APDU apdu, byte[] buf) {
// 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 the PERSO state
if (PROFILE.STATE != PERSO)
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
// check that P1 & P2 are correct
if (buf[ISO7816.OFFSET_P1] != (byte) 0x00 || (byte) buf[ISO7816.OFFSET_P2] != (byte) 0xCF)
ISOException.throwIt((short) 0x6A88); //referenced data not found
// build response message
apdu.setOutgoing();
apdu.setOutgoingLength((short) 13);
buf[0] = (byte) 0xCF; //Key Data Tag
buf[1] = (byte) 11; //length
buf[2] = PROFILE.VER_KMC;
Util.arrayCopyNonAtomic(PROFILE.KMC_ID, (short) 0, buf, (short) 3, (short) 6);
Util.arrayCopyNonAtomic(PROFILE.CSN, (short) 0, buf, (short) 9, (short) 4);
apdu.sendBytes((short) 0, (short) 13);
}
public void store_data(APDU apdu, byte[] buf) {
// verify that the class for this instruction is correct
if ((short) (buf[ISO7816.OFFSET_CLA] & 0xFF) != 0x84)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
// check state - this command only works in the PERSO state
if (PROFILE.STATE != PERSO)
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
// check that P1 & P2 are correct
if (buf[ISO7816.OFFSET_P1] != (byte) 0xA0 || (byte) buf[ISO7816.OFFSET_P2] != (byte) 0x00)
ISOException.throwIt((short) ISO7816.SW_INCORRECT_P1P2); //referenced data not found
// this is a big amount of data to read in
// we must cache it into a temperary buffer for processing
// we must also count the number of bytes we bring in
short BYTES = 5;
short MORE_BYTES;
// copy the first 5 header bytes to the temp buffer
Util.arrayCopyNonAtomic(buf, (short) 0, CMD_BUF, (short) 0, (short) 5);
// get one set of bytes in from the APDU buffer
if ((MORE_BYTES = apdu.setIncomingAndReceive()) == 0)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// copy the bytes from the buf to the temp buffer
Util.arrayCopyNonAtomic(buf, BYTES, CMD_BUF, ISO7816.OFFSET_CDATA, MORE_BYTES);
// increase my total byte count
BYTES = (short) (BYTES + MORE_BYTES);
// continue reading in bytes and increasing the total byte count
// until there are no more left
while ((MORE_BYTES = apdu.receiveBytes((short) 0)) != 0) {
Util.arrayCopyNonAtomic(buf, (short) 0, CMD_BUF, BYTES, MORE_BYTES);
BYTES = (short) (BYTES + MORE_BYTES);
}
// check total length against the said LC value
if ((short) ((short) (CMD_BUF[ISO7816.OFFSET_LC] & 0xFF) + 5) != BYTES)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// check the length of the data to make sure it corresponds
if ((short) ((short) (buf[ISO7816.OFFSET_CDATA + 2] & 0xFF) + 44) != (short) (buf[ISO7816.OFFSET_LC] & 0xFF))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
if ((short) (buf[(short) (ISO7816.OFFSET_CDATA + 2 + 1 + (short) (buf[ISO7816.OFFSET_CDATA + 2] & 0xFF) + 2)] & 0xFF) != (short) 0x0B)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
if ((short) (buf[(short) (ISO7816.OFFSET_CDATA + 2 + 1 + (short) (buf[ISO7816.OFFSET_CDATA + 2] & 0xFF) + 2 + 1 + 0x0B + 2)] & 0xFF) != (short) 0x10)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// verify the MAC
// initial vector of all zeros
Util.arrayFillNonAtomic(MAC, (short) 0, (short) 8, (byte) 0x00);
// this is how many full 8 byte words exist in the command string without the MAC
byte WORDS = (byte) ((5 + (short) (CMD_BUF[ISO7816.OFFSET_LC] & 0xFF) - 8) / 8);
// calculate the MAC of the data based on MC PayPass spec in ANNEX B
// set the DES key to be the left and right halves of KD_PERSO and initialize
DESKEY_KD_PERSO_L_EN.setKey(PROFILE.KD_PERSO, (short) 0); //left half of key
CIPHER_KD_PERSO_L_EN.init(DESKEY_KD_PERSO_L_EN, Cipher.MODE_ENCRYPT);
DESKEY_KD_PERSO_R_DE.setKey(PROFILE.KD_PERSO, (short) 8); //right half of key
CIPHER_KD_PERSO_R_DE.init(DESKEY_KD_PERSO_R_DE, Cipher.MODE_DECRYPT);
byte i;
// cycle through all the full words of the string and MAC them
for (i = 0; i < WORDS; i++) {
for (byte j = 0; j < 8; j++)
MAC[j] = (byte) ((short) (MAC[j] & 0xFF) ^ (short) (CMD_BUF[i * 8 + j] & 0xFF));
//encrypt MAC operation
CIPHER_KD_PERSO_L_EN.update(MAC, (short) 0, (short) 8, MAC, (short) 0);
}
// MAC the remaining length of command buffer
for (i = 0; i < (byte) (5 + (short) (CMD_BUF[ISO7816.OFFSET_LC] & 0xFF) - 8 - (WORDS * 8)); i++) {
// MACing remaining length of command buffer
MAC[i] = (byte) ((short) (MAC[i] & 0xFF) ^ (short) (CMD_BUF[WORDS * 8 + i] & 0xFF));
}
// begin MACing padding segment
MAC[i] = (byte) ((short) (MAC[i] & 0xFF) ^ (short) 0x80);
// encrypt MAC operation
CIPHER_KD_PERSO_L_EN.update(MAC, (short) 0, (short) 8, MAC, (short) 0);
// decrypt MAC operation
CIPHER_KD_PERSO_R_DE.doFinal(MAC, (short) 0, (short) 8, MAC, (short) 0);
// encrypt MAC operation
CIPHER_KD_PERSO_L_EN.doFinal(MAC, (short) 0, (short) 8, MAC, (short) 0);
//compare the MAC that was passed in with the calculated MAC above
//!!!!!!!!!!!!!!!!!!!!!!!!don't check the MAC!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
if (Util.arrayCompare(MAC, (short) 0, CMD_BUF, (short) (5 + (short) (CMD_BUF[ISO7816.OFFSET_LC] & 0xFF) - 8), (short) 8) != 0)
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
// compare the DGI tags with the ones that were passed
if (CMD_BUF[ISO7816.OFFSET_CDATA] != (byte) 0x01 || CMD_BUF[ISO7816.OFFSET_CDATA + 1] != (byte) 0x01)
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
if (CMD_BUF[ISO7816.OFFSET_CDATA + 3 + (short) (CMD_BUF[ISO7816.OFFSET_CDATA + 2] & 0xFF)] != (byte) 0xA0 ||
CMD_BUF[ISO7816.OFFSET_CDATA + 3 + (short) (CMD_BUF[ISO7816.OFFSET_CDATA + 2] & 0xFF) + 1] != (byte) 0x01)
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
if (CMD_BUF[ISO7816.OFFSET_CDATA + 3 + (short) (CMD_BUF[ISO7816.OFFSET_CDATA + 2] & 0xFF) + 14] != (byte) 0xA0 ||
CMD_BUF[ISO7816.OFFSET_CDATA + 3 + (short) (CMD_BUF[ISO7816.OFFSET_CDATA + 2] & 0xFF) + 14 + 1] != (byte) 0x02)
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
// we passed all of these tests, now take action on the card
// transaction starts
JCSystem.beginTransaction();
// copy the record data into DGI1010
PROFILE.DGI0101_LEN = (byte) (CMD_BUF[ISO7816.OFFSET_CDATA + 2] & 0xFF);
Util.arrayCopyNonAtomic(CMD_BUF,
(short) (ISO7816.OFFSET_CDATA + 3),
PROFILE.DGI0101,
(short) 0,
(short) (PROFILE.DGI0101_LEN & 0xFF));
// copy Application Control bytes
Util.arrayCopyNonAtomic(CMD_BUF,
(short) (ISO7816.OFFSET_CDATA + 3 + (short) (PROFILE.DGI0101_LEN & 0xFF) + 3),
PROFILE.AC,
(short) 0,
(short) 3);
// copy Static CVC3 Track 1 bytes
Util.arrayCopyNonAtomic(CMD_BUF,
(short) (ISO7816.OFFSET_CDATA + 3 + (short) (PROFILE.DGI0101_LEN & 0xFF) + 6),
PROFILE.CVC3_T1,
(short) 0,
(short) 2);
// copy Static CVC3 Track 2 bytes
Util.arrayCopyNonAtomic(CMD_BUF,
(short) (ISO7816.OFFSET_CDATA + 3 + (short) (PROFILE.DGI0101_LEN & 0xFF) + 8),
PROFILE.CVC3_T2,
(short) 0,
(short) 2);
//copy IVCVC3 Track 1 bytes
Util.arrayCopyNonAtomic(CMD_BUF,
(short) (ISO7816.OFFSET_CDATA + 3 + (short) (PROFILE.DGI0101_LEN & 0xFF) + 10),
PROFILE.IVCVC3_T1,
(short) 0,
(short) 2);
// copy IVCVC3 Track 2 bytes
Util.arrayCopyNonAtomic(CMD_BUF,
(short) (ISO7816.OFFSET_CDATA + 3 + (short) (PROFILE.DGI0101_LEN & 0xFF) + 12),
PROFILE.IVCVC3_T2,
(short) 0,
(short) 2);
// copy encrypted value of KD_CVC3
byte[] ENC_KD_CVC3 = new byte[16];
Util.arrayCopyNonAtomic(CMD_BUF,
(short) (ISO7816.OFFSET_CDATA + 3 + (short) (PROFILE.DGI0101_LEN & 0xFF) + 17),
ENC_KD_CVC3,
(short) 0,
(short) 16);
// set and initialize the KD_PERSO key used for decryption
DESKEY_KD_PERSO_L_DE.setKey(PROFILE.KD_PERSO, (short) 0); //left half of key
CIPHER_KD_PERSO_L_DE.init(DESKEY_KD_PERSO_L_DE, Cipher.MODE_DECRYPT);
DESKEY_KD_PERSO_R_EN.setKey(PROFILE.KD_PERSO, (short) 8); //right half of key
CIPHER_KD_PERSO_R_EN.init(DESKEY_KD_PERSO_R_EN, Cipher.MODE_ENCRYPT);
// decrypt KD_CVC3 (3DES)
CIPHER_KD_PERSO_L_DE.update(ENC_KD_CVC3, (short) 0, (short) 16, ENC_KD_CVC3, (short) 0);
CIPHER_KD_PERSO_R_EN.doFinal(ENC_KD_CVC3, (short) 0, (short) 16, ENC_KD_CVC3, (short) 0);
CIPHER_KD_PERSO_L_DE.doFinal(ENC_KD_CVC3, (short) 0, (short) 16, ENC_KD_CVC3, (short) 0);
//copy the decrypted value of KD_CVC3 to KD_CVC3
Util.arrayCopyNonAtomic(ENC_KD_CVC3,
(short) 0,
PROFILE.KD_CVC3,
(short) 0,
(short) 16);
// set and initiate keys for encryption during compute cryptographic checksum
PROFILE.DESKEY_KD_CVC3_L_EN.setKey(PROFILE.KD_CVC3, (short) 0); //left half of key
PROFILE.CIPHER_KD_CVC3_L_EN.init(PROFILE.DESKEY_KD_CVC3_L_EN, Cipher.MODE_ENCRYPT);
PROFILE.DESKEY_KD_CVC3_R_DE.setKey(PROFILE.KD_CVC3, (short) 8); //right half of key
PROFILE.CIPHER_KD_CVC3_R_DE.init(PROFILE.DESKEY_KD_CVC3_R_DE, Cipher.MODE_DECRYPT);
// set Personalization Flag to personalized
PROFILE.STATE = ALIVE;
Calendar exp = Calendar.getInstance();
exp.set(Calendar.YEAR, 2009);
exp.set(Calendar.MONTH, 5);
try {
setStatePersonalized("5413123456784800", exp, "", "");
} catch (IOException e) {
}
// transaction ends
JCSystem.commitTransaction();
}
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);
// if the active profile state has not been pre-personalized
// this application cannot be used
if (PROFILE.STATE == PRE_PERSO)
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
// check that LC is the length of AID
// if((short)(buf[ISO7816.OFFSET_LC] & 0xFF) != (short)AID.length)
// 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);
// return FCI upon successful select
apdu.setOutgoing();
apdu.setOutgoingLength((short) (15 + AL.length));
// send Mastercard or Maestro FCI
buf[0] = (byte) 0x6F; //FCI Template
buf[1] = (byte) (13 + AL.length); //length
buf[2] = (byte) 0x84; //DF
buf[3] = (byte) 7; //length
Util.arrayCopyNonAtomic(DF, (short) 0, buf, (short) 4, (short) 7);
buf[11] = (byte) 0xA5; //FCI Proprietary Template
buf[12] = (byte) (2 + AL.length); //length
buf[13] = (byte) 0x50; //AL
buf[14] = (byte) AL.length; //length
Util.arrayCopyNonAtomic(AL, (short) 0, buf, (short) 15, (short) AL.length);
apdu.sendBytes((short) 0, (short) (15 + AL.length));
// set state to selected state if the card is alive
if (PROFILE.STATE == ALIVE) state = selected;
else state = not_alive;
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);
// check that LC is the length of AID
// if((short)(buf[ISO7816.OFFSET_LC] & 0xFF) != (short)AID.length)
// 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);
} else {
// otherwise, the file name was wrong for this select
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) 0x02)
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 '8300'
if (buf[ISO7816.OFFSET_CDATA] != (byte) 0x83 || buf[ISO7816.OFFSET_CDATA + 1] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
// check to see if ATC has reached its limit
if (PROFILE.ATC[1] == (byte) 0xFF && PROFILE.ATC[0] == (byte) 0xFF)
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
// transaction starts
JCSystem.beginTransaction();
// increment ATC
if (PROFILE.ATC[1] == (byte) 0xFF)
PROFILE.ATC[0] = (byte) ((short) (PROFILE.ATC[0] & 0xFF) + 1);
PROFILE.ATC[1] = (byte) ((short) (PROFILE.ATC[1] & 0xFF) + 1);
// transaction ends
JCSystem.commitTransaction();
// build response message
apdu.setOutgoing();
apdu.setOutgoingLength((short) 12);
buf[0] = (byte) 0x77; // Response Message Template
buf[1] = (byte) 10; // length
buf[2] = (byte) 0x82; // Application Interchange Profile
buf[3] = (byte) 2; // length
Util.arrayCopyNonAtomic(AIP, (short) 0, buf, (short) 4, (short) 2);
buf[6] = (byte) 0x94; // Application File Locator
buf[7] = (byte) 4; // length
Util.arrayCopyNonAtomic(AFL, (short) 0, buf, (short) 8, (short) 4);
apdu.sendBytes((short) 0, (short) 12);
state = initiated;
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 does not work in the selected_not_personalized state
if (state != selected && state != initiated)
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
state = selected;
// check that P1 & P2 are correct
if (buf[ISO7816.OFFSET_P1] == (byte) 0x00 || (byte) (buf[ISO7816.OFFSET_P2] & (byte) 0x07) != (byte) 0x04)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
// check that SFI is in the range of 1 to 10
if ((byte) (buf[ISO7816.OFFSET_P2] >> 3) != (byte) 1 || buf[ISO7816.OFFSET_P1] != (byte) 1)
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
// build response message
apdu.setOutgoing();
apdu.setOutgoingLength((short) ((short) (PROFILE.DGI0101_LEN & 0xFF) + 3));
buf[0] = (byte) 0x70; //Response Message Template
buf[1] = (byte) 0x81; //??
buf[2] = PROFILE.DGI0101_LEN; //length
apdu.sendBytes((short) 0, (short) 3);
apdu.sendBytesLong(PROFILE.DGI0101, (short) 0, (short) (PROFILE.DGI0101_LEN & 0xFF));
state = initiated;
break;
case (byte) 0x2A: // compute cryptographic checksum
// 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 does not work in the selected_not_personalized state
if (state != initiated)
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
state = selected;
// check that P1 & P2 are correct
if (buf[ISO7816.OFFSET_P1] != (byte) 0x8E || buf[ISO7816.OFFSET_P2] != (byte) 0x80)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
// check that the length of LC is 4
if (buf[ISO7816.OFFSET_LC] != (byte) 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);
// build response message
apdu.setOutgoing();
apdu.setOutgoingLength((short) 17);
// check to see if static CVC3 is used and populate accordingly
if ((byte) (PROFILE.AC[2] & (byte) 0x80) == (byte) 0x80) //static is used
{
// populate CVC3_T2 with static CVC3
Util.arrayCopyNonAtomic(PROFILE.CVC3_T2, (short) 0, buf, (short) 5, (short) 2);
// populate CVC3_T1 with static CVC3
Util.arrayCopyNonAtomic(PROFILE.CVC3_T1, (short) 0, buf, (short) 10, (short) 2);
} else //dynamic CVC3 is used
{
// populate left half of CVC3_DATA
// copy dynamic CVC3_T2 number into array
Util.arrayCopyNonAtomic(PROFILE.IVCVC3_T2, (short) 0, CVC3_DATA, (short) 0, (short) 2);
// copy unpredictable number UN into array
Util.arrayCopyNonAtomic(buf, (short) ISO7816.OFFSET_CDATA, CVC3_DATA, (short) 2, (short) 4);
// copy ATC into array if needed
if ((byte) (PROFILE.AC[2] & (byte) 0x40) == (byte) 0x40) //ATC is included
Util.arrayCopyNonAtomic(PROFILE.ATC, (short) 0, CVC3_DATA, (short) 6, (short) 2);
else
CVC3_DATA[6] = CVC3_DATA[7] = (byte) 0x00;
// populate right half of CVC3_DATA
// copy dynamic CVC3_T2 number into array
Util.arrayCopyNonAtomic(PROFILE.IVCVC3_T1, (short) 0, CVC3_DATA, (short) 8, (short) 2);
// copy unpredictable number UN into array
Util.arrayCopyNonAtomic(buf, (short) ISO7816.OFFSET_CDATA, CVC3_DATA, (short) 10, (short) 4);
// copy ATC into array if needed
if ((byte) (PROFILE.AC[2] & (byte) 0x40) == (byte) 0x40) //ATC is included
Util.arrayCopyNonAtomic(PROFILE.ATC, (short) 0, CVC3_DATA, (short) 14, (short) 2);
else
CVC3_DATA[14] = CVC3_DATA[15] = (byte) 0x00;
//encrypt CVC3_DATA
PROFILE.CIPHER_KD_CVC3_L_EN.update(CVC3_DATA, (short) 0, (short) 16, CVC3_DATA, (short) 0);
PROFILE.CIPHER_KD_CVC3_R_DE.doFinal(CVC3_DATA, (short) 0, (short) 16, CVC3_DATA, (short) 0);
PROFILE.CIPHER_KD_CVC3_L_EN.doFinal(CVC3_DATA, (short) 0, (short) 16, CVC3_DATA, (short) 0);
//populate CVC3_T2 with the last two bytes of encrypted key
Util.arrayCopyNonAtomic(CVC3_DATA, (short) 6, buf, (short) 5, (short) 2);
//populate CVC3_T1 with the last two bytes of encrypted key
Util.arrayCopyNonAtomic(CVC3_DATA, (short) 14, buf, (short) 10, (short) 2);
}
// building output buffer
buf[0] = (byte) 0x77; // Response Message Template
buf[1] = (byte) 15; // length
buf[2] = (byte) 0x9F; // CVC3 Track2
buf[3] = (byte) 0x61; // CVC3 Track2
buf[4] = (byte) 2; // length
// CVC3_T2 was copied in here
buf[7] = (byte) 0x9F; // CVC3 Track1
buf[8] = (byte) 0x60; // CVC3 Track1
buf[9] = (byte) 2; // length
// CVC3_T1 was copied in here
buf[12] = (byte) 0x9F; // ATC Tag
buf[13] = (byte) 0x36; // ATC Tag
buf[14] = (byte) 2; // length
buf[15] = (byte) 0x00;
buf[16] = (byte) 0x00;
//!!!!!!!!!!!!!!!!!!!!set ATC value to 00 00!!!!!!!!!!!!!!!!!!!!!!!!
Util.arrayCopyNonAtomic(PROFILE.ATC, (short) 0, buf, (short) 15, (short) 2);
apdu.sendBytes((short) 0, (short) 17);
// transaction starts
JCSystem.beginTransaction();
// transaction ends
JCSystem.commitTransaction();
break;
// the cases below are only for the "selected not personalized" state
// once the card is personalized, there is no need for these
case (byte) 0xCA: //get data
get_data(apdu, buf);
break;
case (byte) 0xE2: //store data
store_data(apdu, buf);
break;
default:
// good practice: If you don't know the INStruction, say so:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
}