JavaPurseCryptopublic class JavaPurseCrypto extends Applet This class is intended to demonstrate how an electronic cash application
might be developed using Java Card
See Java Card(TM) Reference Implementation User's Guide
for details. |
Fields Summary |
---|
static final byte | VERIFY | static final byte | READ | static final byte | INITIALIZE_TRANSACTION | static final byte | COMPLETE_TRANSACTION | static final byte | INITIALIZE_UPDATE | static final byte | COMPLETE_UPDATE | static final byte | CREDIT | static final byte | DEBIT | static final byte | MASTER_PIN | static final byte | USER_PIN | static final short | MAX_MAC_DATA | static final short | SW_CREDIT_TOO_HIGH | static final short | SW_NOT_ENOUGH_FUNDS | static final short | SW_AMOUNT_TOO_HIGH | static final short | SW_COMMAND_OUT_OF_SEQUENCE | static final short | SW_WRONG_SIGNATURE | static final short | SW_PIN_FAILED | static final short | SW_SUCCESS | static final byte | LC_IT | static final byte | LC_CT | static final byte | LC_CU_MIN | static final byte | CAD_ID_OFFSET | static final short | DATE_LENGTH | static final short | DATETIME_LENGTH | static final short | ID_LENGTH | static final short | SHORT_LENGTH | static final short | START | static final short | SIGNATURE_LENGTH | static final short | MAX_LOYALTY | static final byte | TN_IX | static final byte | NEW_BALANCE_IX | static final byte | CURRENT_BALANCE_IX | static final byte | AMOUNT_IX | static final byte | TRANSACTION_TYPE_IX | static final byte | SELECTED_FILE_IX | static final byte | NUM_TRANSIENT_SHORTS | static final byte | TRANSACTION_INITIALIZED | static final byte | UPDATE_INITIALIZED | static final byte | NUM_TRANSIENT_BOOLS | private static final byte | FCI_TEMPLATE_TAG | private static final byte | FCI_AID_TAG | private static byte[] | FCI_PROPERIETARY | private ParametersFile | parametersFile | private CyclicFile | transactionLogFile | private short | TN | private short | PUN | private boolean | isPersonalized | static final short | PARAMETERS_FID | static final short | TRANSACTION_LOG_FID | static final short | BALANCES_FID | static final byte | FID_BYTE | static final byte | TRANSACTION_RECORD_LENGTH | static final byte | TRANSACTION_RECORD_NUMBER | static final byte | BALANCES_RECORD_LENGTH | static final byte | BALANCES_RECORD_NUMBER | static final byte | PARAMETERS_RECORD_NUMBER | static final byte | OFFSET_BAL_CURRENT | static final byte | OFFSET_BAL_MAX | static final byte | OFFSET_AMOUNT_MAX | static final byte | NUMBER_OF_FILES | private OwnerPIN | masterPIN | private OwnerPIN | userPIN | static final byte | MASTER_PIN_UPDATE | static final byte | USER_PIN_UPDATE | static final byte | EXP_DATE_UPDATE | static final byte | PURSE_ID_UPDATE | static final byte | MAX_BAL_UPDATE | static final byte | MAX_M_UPDATE | static final byte | VERSION_UPDATE | static final byte | LOYALTY1_UPDATE | static final byte | LOYALTY2_UPDATE | static final byte | LOYALTY3_UPDATE | static final byte | LOYALTY4_UPDATE | static final short | TLV_OFFSET | byte[] | keyData | private byte[] | CAD_ID_array | private byte[] | byteArray8 | private short[] | transientShorts | private boolean[] | transientBools | private byte[] | ID_Purse | private byte[] | ExpDate | private byte[] | balancesRecord | private byte[] | MAC_buffer | private short[] | loyaltyCAD | private com.sun.javacard.samples.SampleLibrary.JavaLoyaltyInterface[] | loyaltySIO | private DESKey | deskey | private Signature | sig |
Constructors Summary |
---|
protected JavaPurseCrypto(byte[] bArray, short bOffset, byte bLength)Performs memory allocations, initialization, and applet registration.
ID_Purse = new byte[ID_LENGTH];
ExpDate = new byte[DATE_LENGTH];
ExpDate[0] = (byte)12; ExpDate[1] = (byte)31; ExpDate[2] = (byte)99;
balancesRecord = new byte[BALANCES_RECORD_LENGTH];
loyaltyCAD = new short[MAX_LOYALTY];
loyaltySIO = new JavaLoyaltyInterface[MAX_LOYALTY];
TN = 0;
PUN = 0;
isPersonalized = false;
//Create transient objects.
transientShorts = JCSystem.makeTransientShortArray( NUM_TRANSIENT_SHORTS,
JCSystem.CLEAR_ON_DESELECT);
transientBools = JCSystem.makeTransientBooleanArray( NUM_TRANSIENT_BOOLS,
JCSystem.CLEAR_ON_DESELECT);
CAD_ID_array = JCSystem.makeTransientByteArray( (short)4,
JCSystem.CLEAR_ON_DESELECT);
// initialize crypto key/signature objects and work array.
byteArray8 = JCSystem.makeTransientByteArray( (short)8,
JCSystem.CLEAR_ON_DESELECT);
deskey = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES_TRANSIENT_DESELECT,
KeyBuilder.LENGTH_DES, false);
MAC_buffer = JCSystem.makeTransientByteArray( MAX_MAC_DATA,
JCSystem.CLEAR_ON_DESELECT);
deskey.setKey(keyData, (short)0);
sig = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2, false);
masterPIN = new OwnerPIN ((byte)1, (byte)8);
//There is only one try - it's not supposed to be done by human operator
userPIN = new OwnerPIN ((byte)5, (byte)8);
parametersFile = new ParametersFile(PARAMETERS_RECORD_NUMBER);
transactionLogFile = new CyclicFile(TRANSACTION_RECORD_NUMBER,
TRANSACTION_RECORD_LENGTH);
Util.setShort(balancesRecord, OFFSET_BAL_CURRENT, (short)0);
/*
* if AID length is not zero register Java Loyalty
* applet with specified AID
*
* NOTE: all the memory allocations should be performed before register()
*/
byte aidLen = bArray[bOffset];
if (aidLen== (byte)0){
register();
} else {
register(bArray, (short)(bOffset+1), aidLen);
}
|
Methods Summary |
---|
private short | checkTransactionValues(byte transactionType, short amount)Verifies numerical limitations on Transaction Amount.
NOTE: With some values of maxBalance and maxAmount the logic
in this method might become somewhat unrealistic. It's the result of using
short arithmetic on values which might be too big to fit in short variables.
short newBalance;
short currentBalance = Util.getShort(balancesRecord, OFFSET_BAL_CURRENT);
short maxBalance = Util.getShort(balancesRecord, OFFSET_BAL_MAX);
short maxAmount = Util.getShort(balancesRecord, OFFSET_AMOUNT_MAX);
switch (transactionType) {
case CREDIT : {
newBalance = (short)(currentBalance + amount);
transientShorts[NEW_BALANCE_IX] = newBalance;
if (newBalance > maxBalance || newBalance < 0) //to prevent rollover
ISOException.throwIt(SW_CREDIT_TOO_HIGH);
break;
}
case DEBIT : {
if (amount > maxAmount) ISOException.throwIt(SW_AMOUNT_TOO_HIGH);
newBalance = (short)(currentBalance - amount);
transientShorts[NEW_BALANCE_IX] = newBalance;
if (newBalance < 0)ISOException.throwIt(SW_NOT_ENOUGH_FUNDS);
break;
}
default :
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
transientShorts[CURRENT_BALANCE_IX] = currentBalance;
return currentBalance;
| public void | deselect()Performs the session finalization.
userPIN.reset();
masterPIN.reset();
| public static void | install(byte[] bArray, short bOffset, byte bLength)Installs Java Purse applet.
new JavaPurseCrypto(bArray, bOffset, bLength);
| public void | process(APDU apdu)Dispatches APDU commands.
byte[] buffer = apdu.getBuffer();
if (!apdu.isISOInterindustryCLA()) {
switch (buffer[ISO7816.OFFSET_INS]) {
case INITIALIZE_TRANSACTION:
processInitializeTransaction(apdu); break;
case COMPLETE_TRANSACTION:
processCompleteTransaction(apdu); break;
case INITIALIZE_UPDATE:
processInitializeUpdate(apdu); break;
case COMPLETE_UPDATE:
processCompleteUpdate(apdu); break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
} else {
if (buffer[ISO7816.OFFSET_INS] == VERIFY){
processVerifyPIN(apdu);
} else if (buffer[ISO7816.OFFSET_INS] == ISO7816.INS_SELECT) {
if (selectingApplet())
processSelectPurse(apdu);
else
processSelectFile(apdu);
} else if (buffer[ISO7816.OFFSET_INS] == READ) {
processReadRecord(apdu);
} else
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
| private void | processCompleteTransaction(APDU apdu)Handles Complete Transaction APDU.
if (!transientBools[TRANSACTION_INITIALIZED])
ISOException.throwIt(SW_COMMAND_OUT_OF_SEQUENCE);
byte[] buffer = apdu.getBuffer();
if (buffer[ISO7816.OFFSET_LC] != LC_CT)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
if ((buffer[ISO7816.OFFSET_P1] != 0) || (buffer[ISO7816.OFFSET_P2] != 0))
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
apdu.setIncomingAndReceive(); // get expected data
//restore transaction data from transient
short newTN = transientShorts[TN_IX];
short amount = transientShorts[AMOUNT_IX];
short newBalance = transientShorts[NEW_BALANCE_IX];
//The signature verification
//get CLA, INS, P1, P2, LC
short offset = Util.arrayCopyNonAtomic(buffer, (short)0, MAC_buffer,(byte)0, (short)5);
Util.arrayCopyNonAtomic(buffer, offset, byteArray8,(byte)0, SIGNATURE_LENGTH);
Util.arrayCopyNonAtomic(buffer, (short)(offset + SIGNATURE_LENGTH), MAC_buffer,(byte)5, (short)DATETIME_LENGTH);
deskey.setKey(keyData, (short)0);
sig.init(deskey, Signature.MODE_VERIFY);
boolean signatureOK = sig.verify(MAC_buffer, (short)0, (short)10,
byteArray8, START, SIGNATURE_LENGTH);
//prepare transaction record in APDU buffer
offset = Util.setShort(buffer, START, newTN);
buffer[offset] = (byte)transientShorts[TRANSACTION_TYPE_IX];
offset++;
offset = Util.setShort(buffer, offset, amount);
//CAD ID was left in this array from Initialize Transaction
offset = Util.arrayCopyNonAtomic(CAD_ID_array, START, buffer, offset, ID_LENGTH);
//Date and time are copied in APDU buffer to where they should go
//in the transaction record.
short balanceOffset = offset =
Util.arrayCopyNonAtomic(buffer, (short)(ISO7816.OFFSET_CDATA + 8),
buffer, offset, DATETIME_LENGTH);
//Balance and SW will be added to transactionRecord later
if (!signatureOK) {
//Branch for unsuccessful transaction. Balance is not updated,
//otherwise transactionLog is recorded the same way as in successful transaction
offset = Util.setShort(buffer, offset, transientShorts[CURRENT_BALANCE_IX]); // old balance
Util.setShort(buffer, offset, SW_WRONG_SIGNATURE);
//done with preparing transaction record
byte[] theRecord = transactionLogFile.getNewLogRecord();
//The following few steps have to be performed atomically!
JCSystem.beginTransaction();
TN = newTN;
Util.arrayCopy(buffer, START,
theRecord, START, TRANSACTION_RECORD_LENGTH);
transactionLogFile.updateNewLogRecord();
JCSystem.commitTransaction();
//Now we can throw exception
transientBools[TRANSACTION_INITIALIZED] = false;
ISOException.throwIt(SW_WRONG_SIGNATURE);
} else {
//Branch for successful transaction.
offset = Util.setShort(buffer, offset, transientShorts[NEW_BALANCE_IX]);
Util.setShort(buffer, offset, ISO7816.SW_NO_ERROR);
// done with preparing transaction record
byte[] theRecord = transactionLogFile.getNewLogRecord();
//The following few steps have to be performed atomically!
JCSystem.beginTransaction();
TN = transientShorts[TN_IX];
//Update balance
Util.setShort(balancesRecord, START, newBalance);
Util.arrayCopy(buffer, START,
theRecord, START, TRANSACTION_RECORD_LENGTH);
transactionLogFile.updateNewLogRecord();
JCSystem.commitTransaction();
}
// Do loyalty work
// We have all the information in the buffer, the loyalty applets shouldn't
// know transaction number and the balance of purse - so we zero out these
// fields first
Util.setShort(buffer, START, (short)0);
Util.setShort(buffer, balanceOffset, (short)0);
short loyaltyCADValue = Util.getShort(CAD_ID_array, START);
for (byte loyaltyIndex = 0; loyaltyIndex < MAX_LOYALTY; loyaltyIndex++) {
if (loyaltyCAD[loyaltyIndex] == loyaltyCADValue) {
loyaltySIO[loyaltyIndex].grantPoints (buffer);
break;
}
}
offset = Util.setShort(MAC_buffer, START, newBalance);
Util.setShort(MAC_buffer, offset, SW_SUCCESS);
deskey.setKey(keyData, (short)0);
sig.init(deskey, Signature.MODE_SIGN);
short sigLength = sig.sign(MAC_buffer, (short)0, (short)4,
byteArray8, (short)0);
//send R-APDU
offset = Util.setShort(buffer, START, newBalance);
offset = Util.arrayCopyNonAtomic(byteArray8, START, buffer, offset, SIGNATURE_LENGTH);
apdu.setOutgoingAndSend(START, (short)(offset - START));
transientBools[TRANSACTION_INITIALIZED] = false;
| private void | processCompleteUpdate(APDU apdu)Handles Complete Parameter Update APDU.
if (!transientBools[UPDATE_INITIALIZED])
ISOException.throwIt(SW_COMMAND_OUT_OF_SEQUENCE);
byte[] buffer = apdu.getBuffer();
if ((buffer[ISO7816.OFFSET_P1] != 0) || (buffer[ISO7816.OFFSET_P2] != 0))
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
short count = apdu.setIncomingAndReceive(); // get expected data
byte lc = buffer[ISO7816.OFFSET_LC];
//The signature verification
//get CLA, INS, P1, P2, LC
//message to sign length
short messageLength = (short)(lc - SIGNATURE_LENGTH + 5);
short offset = Util.arrayCopyNonAtomic(buffer, (short)0, MAC_buffer,(byte)0, (short)messageLength);
offset = Util.arrayCopyNonAtomic(buffer, offset, byteArray8,(byte)0, SIGNATURE_LENGTH);
// verify signature if in coming apdu
deskey.setKey(keyData, (short)0);
sig.init(deskey, Signature.MODE_VERIFY);
boolean signatureOK = sig.verify(MAC_buffer, (short)0, messageLength,
byteArray8, START, SIGNATURE_LENGTH);
if (!signatureOK)
ISOException.throwIt(SW_WRONG_SIGNATURE);
switch (buffer[TLV_OFFSET]) {
case MASTER_PIN_UPDATE:
updatePIN(apdu, masterPIN);
setIsPersonalized();
break;
case USER_PIN_UPDATE:
updatePIN(apdu, userPIN);
break;
case EXP_DATE_UPDATE:
updateParameterValue(apdu, ExpDate);
break;
case PURSE_ID_UPDATE:
updateParameterValue(apdu, ID_Purse);
break;
case MAX_BAL_UPDATE:
updateBalanceValue(apdu, OFFSET_BAL_MAX);
break;
case MAX_M_UPDATE:
updateBalanceValue(apdu, OFFSET_AMOUNT_MAX);
break;
case VERSION_UPDATE:
updateParametersFile(apdu);
break;
case LOYALTY1_UPDATE:
updateLoyaltyProgram(apdu, (byte)0);
break;
case LOYALTY2_UPDATE:
updateLoyaltyProgram(apdu, (byte)1);
break;
case LOYALTY3_UPDATE:
updateLoyaltyProgram(apdu, (byte)2);
break;
case LOYALTY4_UPDATE:
updateLoyaltyProgram(apdu, (byte)3);
break;
default:
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
//sign the return message
//Admittedly, this is a poor MAC, but this message only returns the status
//(what is MACed here) and the MAC itself. A real application should return a
//little more information.
offset = Util.setShort(MAC_buffer, START, SW_SUCCESS);
deskey.setKey(keyData, (short)0);
sig.init(deskey, Signature.MODE_SIGN);
short sigLength = sig.sign(MAC_buffer, (short)0, (short)2,
byteArray8, (short)0);
//send R-APDU
//offset = Util.arrayCopyNonAtomic(byteArray8, START, buffer, offset, SIGNATURE_LENGTH);
Util.arrayCopyNonAtomic(byteArray8, START, buffer, START, SIGNATURE_LENGTH);
apdu.setOutgoingAndSend(START, SIGNATURE_LENGTH);
transientBools[UPDATE_INITIALIZED] = false;
| private void | processInitializeTransaction(APDU apdu)Handles Initialize Transaction APDU.
See Java Card 2.1 Reference Implementation User's Guide for details.
if (transientBools[TRANSACTION_INITIALIZED])
ISOException.throwIt(SW_COMMAND_OUT_OF_SEQUENCE);
if (!userPIN.isValidated())
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
byte[] buffer = apdu.getBuffer();
if (buffer[ISO7816.OFFSET_LC] != LC_IT)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
if (buffer[ISO7816.OFFSET_P2] != 0)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
apdu.setIncomingAndReceive(); // get expected data
byte transactionType = buffer[ISO7816.OFFSET_P1];
transientShorts[TRANSACTION_TYPE_IX] = transactionType;
short amount = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
transientShorts[AMOUNT_IX] = amount;
short balance = checkTransactionValues(transactionType, amount);
// Increment TN in Transient Memory & compute signature
short newTN = (short)(TN + 1);
transientShorts[TN_IX] = newTN;
Util.arrayCopyNonAtomic(buffer, CAD_ID_OFFSET, CAD_ID_array, START, ID_LENGTH);
// Send R-APDU
short offset = Util.arrayCopyNonAtomic(ID_Purse, START, buffer, START, ID_LENGTH);
offset = Util.arrayCopyNonAtomic(ExpDate, START, buffer, offset, DATE_LENGTH);
offset = Util.setShort(buffer, offset, balance);
offset = Util.setShort(buffer, offset, newTN);
// The crypto processing could be done here
deskey.setKey(keyData, (short)0);
sig.init(deskey, Signature.MODE_SIGN);
short sigLength = sig.sign(buffer, START, (short)(offset - START),
byteArray8, (short)0);
offset = Util.arrayCopyNonAtomic(byteArray8, START, buffer, offset, SIGNATURE_LENGTH);
apdu.setOutgoingAndSend(START, (short)(offset - START));
transientBools[TRANSACTION_INITIALIZED] = true;
| private void | processInitializeUpdate(APDU apdu)Handles Initialize Parameter Update APDU.
NOTE: In this sample implementation we assume that all the
Parameter Updates are performed in somewhat secured facility and therefore
the tearing of the card is not an issue. That's why we don't do any
transactional protection while processing Initialize and Complete
Parameter Update APDU commands.
if (transientBools[UPDATE_INITIALIZED])
ISOException.throwIt(SW_COMMAND_OUT_OF_SEQUENCE);
if (!masterPIN.isValidated() && isPersonalized)
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
byte[] buffer = apdu.getBuffer();
if ((buffer[ISO7816.OFFSET_P1] != 0) || (buffer[ISO7816.OFFSET_P2] != 0))
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
// Because this is a case 2 command (outgoing data only), the contents of P3
// are undefined. In T=0, P3 is Le. In T=1, P3 is Lc. Therefore, we don't
// bother to test the contents of buffer[ISO7816.OFFSET_LC].
PUN++; //Increment parameter Update Number
// Send R-APDU
short offset = Util.arrayCopyNonAtomic(ID_Purse, START, buffer, START, ID_LENGTH);
offset = Util.arrayCopyNonAtomic(ExpDate, START, buffer, offset, DATE_LENGTH);
offset = Util.setShort(buffer, offset, PUN);
apdu.setOutgoingAndSend(START, (short)(offset - START));
transientBools[UPDATE_INITIALIZED] = true;
| private void | processReadRecord(APDU apdu)Reads a record by the record number or reads the first occurrence
of the record by the record identifier.
This implementation doesn't support all variations of READ RECORD command
in the ISO 7816-4 standard, but provides reasonable subset of it. It is
here to demonstrate that even without "built-in" support for ISO 7816
File System an Applet can have the behavior prescribed by the standard.
// used to hold the record read
byte record[] = null;
short fid = 0;
// get the APDU buffer and fields in the APDU header
byte buffer[] = apdu.getBuffer();
byte P1 = buffer[ISO7816.OFFSET_P1];
byte P2 = buffer[ISO7816.OFFSET_P2];
// process file selection here according to ISO 7816-4, 6.5.3
// P2 = xxxxxyyy
// if xxxxx = 0, use the current selected file
// xxxxx(not all equal) = other value, select EF by SFI as
// specified in xxxxx
if ( (P2 >> 3) == 0 ) {
if (transientShorts[SELECTED_FILE_IX] == (short)0)
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
else
fid = transientShorts[SELECTED_FILE_IX];
} else {
// Short file identifier
byte sfi = (byte) ( ( P2 >> 3 ) & 0x1F );
fid = Util.makeShort(FID_BYTE, sfi);
switch (fid) {
case PARAMETERS_FID:
case TRANSACTION_LOG_FID:
case BALANCES_FID:
transientShorts[SELECTED_FILE_IX] = fid;
break;
default:
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
}
// check for security status (validated PIN)
switch (fid) {
case TRANSACTION_LOG_FID:
case BALANCES_FID:
if (!userPIN.isValidated())
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
break;
case PARAMETERS_FID:
if (!masterPIN.isValidated())
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
// get the last three bits of P2
P2 = (byte) (P2 & 0x07);
// select the record by record number
if ( ( P2 & 0x04 ) != 0 ){
if ( P2 == 0x04 ) {
// P1 = 00 indicates in ISO 7816-4 the current record: we don't
// support it in this implementation
if (P1 == 0)
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
switch (fid) {
case BALANCES_FID:
// There is only one record in balancesRecord
if (P1 == 1)
record = balancesRecord;
else
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
break;
case TRANSACTION_LOG_FID:
record = transactionLogFile.getRecord(P1);
break;
case PARAMETERS_FID:
record = parametersFile.getRecord(P1);
}
if (record == null)
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
} else
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
} else {
// select record by record identifier (first byte of the record)
// read the first occurrence
if ( P2 == 0) {
switch (fid) {
case BALANCES_FID:
// There is only one record in balancesRecords
if (balancesRecord[0] == P1)
record = balancesRecord;
else
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
break;
case TRANSACTION_LOG_FID:
P1 = transactionLogFile.findRecord(P1);
if (P1 == 0 )
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
else
record = transactionLogFile.getRecord(P1);
break;
case PARAMETERS_FID:
P1 = parametersFile.findRecord(P1);
if (P1 == 0 )
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
else
record = parametersFile.getRecord(P1);
}
} else {
// function not supported, when P2 = 1, 2, 3
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
}
// set the data transfer direction to outbound
short Le = apdu.setOutgoing();
// if Lr < Le, set Le == Lr
// otherwise send exactly Le bytes back
if (record.length < Le) {
Le = (short)record.length;
}
apdu.setOutgoingLength(Le);
apdu.sendBytesLong(record, (short)0, Le);
| private void | processSelectFile(APDU apdu)Selects file by FID according to ISO7816-4.
This implementation doesn't support all variations of SELECT command
in the standard, but provides reasonable subset for selection by FID
(P1==2).
byte[] buffer = apdu.getBuffer();
// get the apdu data
apdu.setIncomingAndReceive();
if (buffer[ISO7816.OFFSET_P1] == (byte)2) {
// select file by FID
if ( buffer[ISO7816.OFFSET_LC] != (byte)2)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
short fid = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
switch (fid) {
case PARAMETERS_FID:
case TRANSACTION_LOG_FID:
case BALANCES_FID:
transientShorts[SELECTED_FILE_IX] = fid;
break;
default:
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
} else
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
| private void | processSelectPurse(APDU apdu)Handles Select Purse APDU.
//
// There might be multiple instances of Java Purse on the card with
// different AID values. This method returns the FCI here identifying its
// particular instance.
//
// Format of response data
// FCI tag, FCI length,
// AID tag, length, value,
// PROPRIETARY tag, value.
//
// TEMPLATE
byte[] buffer = apdu.getBuffer();
buffer[0] = FCI_TEMPLATE_TAG;
// buffer[1] set later
// AID
buffer[2] = FCI_AID_TAG;
buffer[3] = JCSystem.getAID().getBytes(buffer, (short)4);
short offset=(short)(3+buffer[3]);
// PROPRIETARY DATA
buffer[offset++] = (byte)FCI_PROPERIETARY.length;
offset = Util.arrayCopyNonAtomic(FCI_PROPERIETARY, (short)0, buffer,
offset, (short)FCI_PROPERIETARY.length);
// FCI template length
buffer[1] = (byte)(offset-(short)2);
apdu.setOutgoingAndSend((short)0, offset);
| private void | processVerifyPIN(APDU apdu)Handles Verify Pin APDU.
byte[] buffer = apdu.getBuffer();
byte pinLength = buffer[ISO7816.OFFSET_LC];
byte triesRemaining = (byte)0;
short count = apdu.setIncomingAndReceive(); // get expected data
if (count < pinLength)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
byte pinType = buffer[ISO7816.OFFSET_P2];
switch (pinType) {
case MASTER_PIN:
if (!masterPIN.check(buffer, ISO7816.OFFSET_CDATA, pinLength)) {
triesRemaining = masterPIN.getTriesRemaining();
//The last nibble of return code is number of remaining tries
ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining));
}
break;
case USER_PIN:
if (!userPIN.check(buffer, ISO7816.OFFSET_CDATA, pinLength)) {
triesRemaining = userPIN.getTriesRemaining();
//The last nibble of return code is number of remaining tries
ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining));
}
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
| private void | setIsPersonalized()Set JavaPurse in personalized state.
It happens only once - after the first update of masterPIN
if (!isPersonalized) isPersonalized = true;//happens only once
| private void | updateBalanceValue(APDU apdu, short offset)Updates values of maximum balance or maximum amount for transaction in the
balancesRecord.
byte[] buffer = apdu.getBuffer();
Util.arrayCopyNonAtomic(buffer, (short)(TLV_OFFSET + 2),
balancesRecord, offset, SHORT_LENGTH);
| private void | updateLoyaltyProgram(APDU apdu, byte loyaltyIndex)Updates loyalty program.
It takes standard TLV record for Parameter Update, interprets first
two bytes as loyaltyCAD (to be compared with first two bytes of CAD ID
in a transaction), the rest of record as AID for loyalty applet.
In case of successful retrieval of Shareable Interface Object with this AID
it is stored as an element of array, and method grantPoints of this loyalty
applet will be called in transaction to grant loyalty points. If SIO is not
returned, or loyaltyCAD in parameter is 0 the corresponding elements in
loyaltyCAD and loyaltySIO arrays are cleared. The Parameters File is updated.
byte[] buffer = apdu.getBuffer();
loyaltyCAD[loyaltyIndex] = Util.getShort(buffer,(short) (TLV_OFFSET+2));
if (loyaltyCAD[loyaltyIndex] != (short)0) {
AID loyaltyAID = JCSystem.lookupAID(buffer, (short) (TLV_OFFSET+4),
(byte)(buffer[TLV_OFFSET+1]-2));
if (loyaltyAID != null) {
loyaltySIO[loyaltyIndex] = (JavaLoyaltyInterface)
JCSystem.getAppletShareableInterfaceObject(loyaltyAID, (byte)0);
if (loyaltySIO[loyaltyIndex] == null)
loyaltyCAD[loyaltyIndex] = (short)0;
} else
loyaltyCAD[loyaltyIndex] = (short)0;
}
if (loyaltyCAD[loyaltyIndex] == (short)0) {
// clean-up
buffer[TLV_OFFSET+1] = (byte)2;
Util.arrayFillNonAtomic (buffer,(short) (TLV_OFFSET+2),
(short) (buffer.length - TLV_OFFSET - 2), (byte)0);
}
updateParametersFile(apdu);
| private void | updatePIN(APDU apdu, OwnerPIN PIN)Updates PIN.
byte[] buffer = apdu.getBuffer();
PIN.update(buffer, (short)(TLV_OFFSET + 2), buffer[TLV_OFFSET + 1]);
| private void | updateParameterValue(APDU apdu, byte[] value)Update value of a Expiration Date or ID_Purse. Also updates corresponding
records in Parameters File
byte[] buffer = apdu.getBuffer();
Util.arrayCopyNonAtomic(buffer, (short)(TLV_OFFSET + 2), value,
START, buffer[TLV_OFFSET + 1]);
updateParametersFile(apdu);
| private void | updateParametersFile(APDU apdu)Updates record in Parameters File.
byte[] buffer = apdu.getBuffer();
byte recordNumber = parametersFile.findRecord(buffer[TLV_OFFSET]);//match tag
if (recordNumber == (byte)0) {
/*
* The record is not found. We have to create a new record.
* NOTE: This is an example that a requirement to perform all memory
* allocations (all "new") in class constructor is not an absolute one.
*/
byte[] newRecord = new byte[buffer[TLV_OFFSET + 1] + 2];
Util.arrayCopyNonAtomic(buffer, TLV_OFFSET, newRecord, START,
(short)(buffer[TLV_OFFSET + 1] + 2));
parametersFile.addRecord(newRecord);
} else {
byte[] theRecord = parametersFile.getRecord(recordNumber);
Util.arrayCopyNonAtomic(buffer, TLV_OFFSET, theRecord, START,
(short)(buffer[TLV_OFFSET + 1] + 2));
}
|
|