/**
* PAYMENT Terminal - HW Security Course
* @author Rafael Boix & Eduardo Novella
*/
package Payment;
//Java SDK imports
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Scanner;
//JCardSim imports -- based on bouncy castle -- for signature generation/verification a-la-JCOP
import javacard.framework.security.DESKey;
import javacard.framework.security.KeyBuilder;
import javacard.framework.security.Signature;
//Javacard IO imports
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CardTerminals;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;
//JCE imports
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class PaymentTerminal {
Scanner keyboard = new Scanner(System.in);
final private static double VERSION = 1.5;
final private static String TERMINAL_ID="CK_TERM_RU0001PT";
final static short MAX_BALANCE = (short) 0x61A8; // Max balance 250.00€ short decimal=25000
//Logfile in /PATH_TO_cheapknip/logs/cheaplogPayment.txt
final private static String home = System.getProperty("user.dir")+"/../logs/cheaplogPayment.txt";
private boolean CARD_LEGIT;
private CardChannel ch;
private CardTerminal terminal;
private DESKey sk;
private Signature sg;
private SecretKeySpec AES_KEY_CARD;
private Cipher cp;
private String cID;
private Hashtable<Integer,Object> blacklist;
// APPLET_ID --> 10 Bytes
final private static byte[] CHEAPKNIP_AID = { (byte) 0x63,(byte) 0x68,(byte) 0x65,(byte) 0x61,(byte) 0x70,(byte) 0x6B,
(byte) 0x6E,(byte) 0x69,(byte) 0x70,(byte) 0x01 };
// SELECT APDU
final private static CommandAPDU SELECT_APDU = new CommandAPDU((byte) 0x00, (byte) 0xA4,(byte) 0x04, (byte) 0x00,CHEAPKNIP_AID);
//APDU INS operation codes in the apdu header
final private static byte INS_HANDSHAKE = (byte) 0x20;
final private static byte INS_GET_BALANCE = (byte) 0x30;
final private static byte INS_MODIFY_BALANCE = (byte) 0x40;
final private static byte INS_GET_OWNER_INFO = (byte) 0x50;
final static private byte INS_GET_TRNSCT_LOG = (byte) 0x60;
final static private byte INS_BLOCK_CARD = (byte) 0x99;
//Log operation types
final static private byte LOG_OP_PAY = (byte) 0x11;
final static private byte LOG_OP_TOPUP = (byte) 0x22;
final static private byte LOG_OP_ISSUE = (byte) 0x33;
// CONSTRUCTOR
public PaymentTerminal(){
TerminalFactory tf;
CardTerminals ct;
List<CardTerminal> cs;
Card card;
loadBlacklist();
CARD_LEGIT=false;
cID="0";
sg = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2, false);
try {
cp = Cipher.getInstance("AES/ECB/NoPadding");
} catch (Exception e) {
Utilities.writeToLogs(home,"[E] "+e.toString());
}
try {
tf = TerminalFactory.getDefault();
ct = tf.terminals();
cs = ct.list(CardTerminals.State.CARD_PRESENT);
if (cs.isEmpty()){
Utilities.writeToLogs(home,"[E] No terminal with a card found!");
System.out.println("[-] No terminal with a card found!");
return;
}
for(CardTerminal c : cs){
if (c.isCardPresent()){
try{
card = c.connect("*");
try{
ch =card.getBasicChannel();
ResponseAPDU resp = ch.transmit(SELECT_APDU);
terminal=c;
if (resp.getSW() != 0x9000){
Utilities.writeToLogs(home,"[E] Problems: Card Device not selectable != 0x9000");
throw new Exception("[!] Problems: Card Device not selectable");
}
}catch(Exception e){
Utilities.writeToLogs(home,"[E] "+e.toString());
System.out.println("[-] Card isn't ok!");
}
//card.disconnect(false);
}catch (CardException ce){
Utilities.writeToLogs(home,"[E] "+ce.toString());
System.err.println("[-] Couldn't connect to card!");
}
return;
} else{
Utilities.writeToLogs(home,"[W] No card present!");
System.out.println("[-] No card present!");
}
}
} catch (CardException ce){
Utilities.writeToLogs(home,"[E] "+ce.toString());
System.out.println("[-] Card status problem!");
}
}
public static void printMainMenuOptions() {
System.out.println("##################################################################################################");
System.out.println("# Welcome to CHEAPKNIP terminal! #");
System.out.println("# ------------------------------ #");
System.out.println("# 1.- Check the balance #");
System.out.println("# 2.- Pay with Cheapknip #");
System.out.println("# 3.- Check card transaction log #");
System.out.println("# 0.- Exit #");
System.out.println("# #");
System.out.println("# #");
System.out.printf ("# 2012 Cheapknip Payment %.2f #\n",VERSION);
System.out.println("##################################################################################################");
}
public static void main(String[] arg) {
int opt = -1;
System.out.printf("Terminal:%s\t\t\t\t\t\t\t%s\n",TERMINAL_ID,Utilities.getSystemDate());
PaymentTerminal pt=new PaymentTerminal();
do{
printMainMenuOptions();
opt = Utilities.enterOption(0,3);
try{
switch (opt){
case 1:
System.out.println("[1] Check the balance ");
double balance =-999999.99;
try{
balance= pt.checkBalance();
}
catch (CardException e) {
Utilities.writeToLogs(home,"[E] Error reading the card balance. Aborting operation. "+pt.cID);
System.out.println("[!] Error reading the card balance. Aborting operation. \n");
}
if(balance!=-999999.99){
Utilities.writeToLogs(home, "[+] The current balance in the CheapKnip card "+pt.cID+" is "+ balance);
System.out.printf("[+] The current balance in the CheapKnip card is %3.2f €\n", balance);
}
break;
case 2:
System.out.println("[2] Pay with Cheapknip");
double payment=0.0;
do{
System.out.print("[+] How much do you want to pay with your Cheapknip?: (€) ");
payment=Utilities.enterDouble();
}while(250< payment|| 0>=payment);
boolean payOK=false;
try{
payOK=pt.pay(payment);
}catch(CardException e){
Utilities.writeToLogs(home,"[E] Error reading the card balance. Aborting operation. "+pt.cID);
System.out.println("[!] Error reading the card balance. Aborting operation. \n");
}
if (payOK){
double money=0;
try{
money=pt.checkBalance();
}catch(CardException e){
Utilities.writeToLogs(home,"[E] Error reading the card balance. Aborting operation. "+pt.cID);
throw new CardException("[!] Error reading the card balance. Aborting operation. \n");
}
System.out.printf("[+] Your payment was successful! \nThe current balance in the CheapKnip card is %3.2f €\n", money);
}else{
Utilities.writeToLogs(home,"[E] Not enough money . Aborting operation. "+pt.cID);
System.out.println("[!] Not enough money . Aborting operation. \n");
}
break;
case 3:
System.out.println("[3] Check transactions");
pt.getTransactionLog();
break;
}
} catch(Exception e){
e.printStackTrace();
}
}while(opt != 0);
System.out.println("Thank you for using our CheapKnip Terminal!");System.exit(0);
}
//Mockup for a real implementation of getting the Master key
private byte[] getMasterKey(){
return "1234567890123456".getBytes();
}
/**
* Handshake function: verifies that the card is legit and also proves that the terminal
* is legit to the card (mutual authentication). Includes two challenge/response operations.
*
* @author Eduardo Novella & Rafael Boix
*
*/
private void handshakeProtocol() throws CardException {
SecureRandom sr = new SecureRandom();
//STEP 1: generate nonceT, send it to the Card as challenge in plaintext
//----------------------------------------------------------
byte[] nonceT = new byte[8];
byte[] nonceT2 = new byte[8];
sr.nextBytes(nonceT);
sr.nextBytes(nonceT2);
//Card handshake INS=0x20 ; response length=2bytes cardID+16bytes nonceR+8bytes signature
CommandAPDU challenge1 = new CommandAPDU((byte) 0x00, (byte) 0x20,(byte) 0x00, (byte) 0x00,nonceT,18+8);
if(ch==null) throw new CardException("Card not present!");
ResponseAPDU res= ch.transmit(challenge1);
byte[] buff= res.getData();
if (res.getSW() != 0x9000){
Utilities.writeToLogs(home,"[E] Error reading the card. Aborting operation. Error while handshake - step 1 !=0x9000 "+cID);
System.out.println("[!] Error reading the card. Aborting operation. ");
System.exit(-1);
}
else{
//Step 2: recover data from response APDU = cardID, nonceR
//----------------------------------------------------------
// CardID goes in plaintext
byte[] cardID=Arrays.copyOfRange(buff, 16, 18);
cID=Integer.toString((int)Utilities.getShort(cardID, 0)); //We put the read CardID in the cID variable for logging purposes
//We check if the card is blacklisted; if it is, block it && break handshake
if(isBlacklisted(cardID)){
Utilities.writeToLogs(home,"[E] Error: Blacklisted card handshake attempt "+cID);
System.out.println("[!] Error: your card is blocked. Go to the closest CheapKnip Customer Service Point");
CARD_LEGIT=false;
CommandAPDU blockCard = new CommandAPDU((byte) 0x00, INS_BLOCK_CARD,(byte) 0x00, (byte) 0x00);
res= ch.transmit(blockCard);
System.exit(-1);
}
// Compute & init card keys
if (AES_KEY_CARD==null)
AES_KEY_CARD=getKeyFromCardID();
if (sk==null)
initSignatureKey(cardID);
if(!verifySignature(buff, 18)){
Utilities.writeToLogs(home,"[E] Error: check that your card is a CheapKnip card(SIGNATURE CHECK FAILED) "+cID);
System.out.println("[!] Error: check that your card is a CheapKnip card");
CARD_LEGIT=false;
return;
}
// Decrypt nonceR --> AES(nonceR)K
byte[] cryptoBuff = decryptAES128(buff,0,16);
byte[] nonceR = Arrays.copyOf(cryptoBuff,8);
byte[] nonceC_T2=new byte[16];
//Recover nonceC from Card: nonceR XOR nonceT
//We put nonceC in the first 8 bytes of the array
for(int i=0;i<8;i++){
nonceC_T2[i]=(byte)((nonceR[i])^(nonceT[i]));
}
for(int i=8;i<16;i++){
nonceC_T2[i]=nonceT2[i-8];
}
//encrypt properly nonceC_T2
//Step3: we send AES(nonceC,nonceT2)K back to the card and wait for response AES(nonceR2)K
//----------------------------------------------------------
byte[] enc_nonceC_T2=encryptAES128(nonceC_T2, 0, 16);
//Note in step3: state is a parameter; P1 is 0x10!!
CommandAPDU challenge2 = new CommandAPDU((byte) 0x00, INS_HANDSHAKE,(byte) 0x10, (byte) 0x00,enc_nonceC_T2,16+8);
res= ch.transmit(challenge2);
buff= res.getData();
if(!verifySignature(buff, 16)){
Utilities.writeToLogs(home,"[E] Card error. Remove your card & try again(SIGNATURE FAILED) "+cID);
System.out.println("[!] Card error. Remove your card & try again");
CARD_LEGIT=false;
return;
}
if (res.getSW() != 0x9000){
Utilities.writeToLogs(home,"[E] Error reading the card. Aborting operation. Error while handshake - step 3 "+cID);
System.out.println("[!] Card error. Remove your card & try again");
System.exit(-1);
}
else{
//Step 4: check if card is legit: nonceT2 should be the same than the one in memory
//----------------------------------------------------------
cryptoBuff=Arrays.copyOf(buff, 16);
buff=decryptAES128(cryptoBuff, 0, 16);
byte[] nonceR2=Arrays.copyOf(buff,8);
//We rebuild nonceT2 from nonceR2 by doing XOR(nonceR2,nonceT)
Utilities.XOR(nonceR2, nonceT, nonceR2, 0, 0, 0, 8);
if(Arrays.equals(Arrays.copyOf(nonceR2, 8), nonceT2)){
CARD_LEGIT=true;
}
else{
CARD_LEGIT=false;
Utilities.writeToLogs(home,"[E] Bogus answer from card, failed challenge2." +cID);
System.out.println("[!] Error reading the card. Aborting operation. ");
System.exit(-1);
}
}
}
}
/**
* The real balance is actualBalanceInCard/100 (to avoid decimals); retrieve the short
* @return Balance
* @throws CardException
* @author Eduardo Novella
*/
private double checkBalance() throws CardException {
if(!CARD_LEGIT)
handshakeProtocol();
if(CARD_LEGIT){
if(terminal==null ||(terminal!=null && terminal.isCardPresent()==false)){
Utilities.writeToLogs(home, "[E] Card was teared! Card ID:"+cID);
System.out.println("Card was teared. Terminal will now reboot.");
System.exit(-1);
}
CommandAPDU apduBalance = new CommandAPDU((byte) 0x00, INS_GET_BALANCE,(byte) 0x00, (byte) 0x00,16+8);
ResponseAPDU res = ch.transmit(apduBalance);
double balance = 0;
if (res.getSW() != 0x9000){
Utilities.writeToLogs(home,"[E] Error during operation. Remove card from terminal != 0x9000 "+cID);
throw new CardException("Error while reading Card Balance. Aborting operation.");
}
else{
if(verifySignature(res.getData(), 16)){
byte[] plainData = decryptAES128(res.getData(),0,16);
short b = Utilities.getShort(plainData, 0);
balance = (double) (b / 100.0);
return balance;
}
else{
System.out.println("Error during operation. Remove card from terminal");
CARD_LEGIT=false;
Utilities.writeToLogs(home,"[E] Signature failure ---- Error during operation. Remove card from terminal "+cID);
throw new CardException("Signature failure");
}
}
}
else{
Utilities.writeToLogs(home,"[E] Handshake protocol failure "+cID);
System.out.println("Error during operation. Remove card from terminal");
System.exit(-1);
throw new CardException("Handshake protocol failure");
}
}
/** Steps: ATOMIC
* Enter amount for paying ( Menu )
* checkbalance (handshake included if not previously done)
* Subtract balance - amount
* If ok -----> modifybalance(newBalance)
* else -----> no credit enough
*/
private boolean pay(double payment) throws CardException {
if(!CARD_LEGIT)
handshakeProtocol();
if(CARD_LEGIT){
if(terminal==null ||(terminal!=null && terminal.isCardPresent()==false)){
Utilities.writeToLogs(home, "[E] Card was teared! Card ID:"+cID);
System.out.println("Card was teared. Terminal will now reboot.");
System.exit(-1);
}
getUserData();
double balance=checkBalance();
if ((balance - payment)>=0){
byte[] data =operationAndPayment(payment,0);
// Expansion of array from 3 bytes to 16 bytes(for AES128 encryption)
data=Arrays.copyOf(data, 16);
byte[] encdata=encryptAES128(data, 0, 16);
// Expansion of array from 16 bytes to 16+8 bytes(for MAC signature to fit)
encdata=Arrays.copyOf(encdata, 24);
signMessage(encdata, 16, encdata, 16);
CommandAPDU apduModBalance = new CommandAPDU((byte) 0x00,INS_MODIFY_BALANCE,(byte) 0x00, (byte) 0x00,encdata,16+8);
ResponseAPDU res = ch.transmit(apduModBalance);
if (res.getSW() != 0x9000){
Utilities.writeToLogs(home,"[E] Error while reading Card Balance "+cID);
throw new CardException("Error while reading Card Balance");
}
else{
notifyPaymentMockupFunction(payment);
Utilities.writeToLogs(home,"[+] Paid successfully from card "+cID+" a total of "+payment+" €.");
return true;
}
}else
return false;
}
else
return false;
}
/*
* Mockup function to notify somewhere that a payment has been done
*/
private void notifyPaymentMockupFunction(double payment){
//Do something interesting in here, like sending the amount
}
/**
* Function that create a byte[] for payment (Always subtracting money)
* We have 3 bytes, the first one is for saying "Substract Money" 00
* The others 2 bytes are the balance to subtract
* 00 XX XX
* op $$ $$
*
* op==> can be 00(-) or 01(+)
*/
private byte[] operationAndPayment(double payment,int operacion){
byte[] op = new byte[]{(byte) operacion};
byte[] pay = new byte[2];
pay = Utilities.getArrayBytes((short)(payment*100));
byte[] oppay= new byte[ op.length +pay.length];
System.arraycopy(op, 0, oppay, 0, op.length);
System.arraycopy(pay,0, oppay, op.length, pay.length);
return oppay;
}
/**
* Prints user data
* User details: name, address ,birthday 100 Bytes
* Name == 40 bytes
* Address == 54 bytes
* Birthday == 6 bytes
* @throws CardException
*/
@SuppressWarnings("unused")
private void getUserData() throws CardException{
if(!CARD_LEGIT)
handshakeProtocol();
if(CARD_LEGIT){
if(terminal==null ||(terminal!=null && terminal.isCardPresent()==false)){
Utilities.writeToLogs(home, "[E] Card was teared! Card ID:"+cID);
System.out.println("Card was teared. Terminal will now reboot.");
System.exit(-1);
}
CommandAPDU dataApdu = new CommandAPDU((byte) 0x00, INS_GET_OWNER_INFO,(byte) 0x00, (byte) 0x00,100);
ResponseAPDU res = ch.transmit(dataApdu);
if (res.getSW() != 0x9000){
Utilities.writeToLogs(home,"[E] Error while reading Card Balance. Aborting operation. != 0x9000 "+cID);
throw new CardException("Error while reading Card Balance. Aborting operation.");
}
else{
String userdata= Utilities.byteArrayToHexString(res.getData());
String name = userdata.substring(0, 40);
String address = userdata.substring(40, 94);
String date = userdata.substring(94,100);
System.out.printf("[+] Mr/Ms. %s \n",Utilities.hex2ascii(name));
}
}
else{
Utilities.writeToLogs(home,"[E] Failure with getUserData");
throw new CardException("Failure");
}
}
/**
*
* CRYPTO FUNCTIONS
*
*/
/**
* Sign message with MAC using same algorithm as the JavaCard does
* @author Eduardo Novella & Rafael Boix
*/
private void signMessage(byte[] msg, int msgLength, byte[] signature, int signOffset){
if(sk!=null)
{sg.init(sk, Signature.MODE_SIGN);
sg.sign(msg, (short)0, (short)msgLength, signature, (short)signOffset);
}
else
Utilities.writeToLogs(home,"[E] Signature key not initialized");
}
/**
* Verify 8byte signature byte array
* @author Eduardo Novella & Rafael Boix
*/
private boolean verifySignature(byte[] msgPlusSignature, int msgLength){
if(sk!=null)
{sg.init(sk, Signature.MODE_VERIFY);
return sg.verify(msgPlusSignature, (short)0, (short)msgLength, msgPlusSignature,(short)msgLength,(short)8);}
else{
Utilities.writeToLogs(home,"[E] Signature key not initialized"); return false;
}
}
/**
* Encrypt byte array
* @author Eduardo Novella & Rafael Boix
*/
public byte[] encryptAES128 (byte[] plain,int offset,int len) {
byte[] encrypted=null;
byte[] plainTrimmed = new byte[len];
System.arraycopy(plain, offset, plainTrimmed, 0, len);
try {
if(AES_KEY_CARD==null)
cp.init(Cipher.ENCRYPT_MODE,getKeyFromCardID());
else
cp.init(Cipher.ENCRYPT_MODE,AES_KEY_CARD);
encrypted = cp.doFinal(plainTrimmed);
} catch (Exception e){
Utilities.writeToLogs(home,"[E] "+e.toString());
}
return encrypted;
}
/**
* Decrypt byte array
* @author Eduardo Novella & Rafael Boix
*/
public byte[] decryptAES128 (byte[] encrypted,int offset,int len){
byte[] plaintext=null;
byte[] encryptedTrimmed = new byte[len];
System.arraycopy(encrypted, offset, encryptedTrimmed, 0, len);
try {
if(AES_KEY_CARD==null)
cp.init(Cipher.DECRYPT_MODE,getKeyFromCardID());
else
cp.init(Cipher.DECRYPT_MODE,AES_KEY_CARD);
plaintext = cp.doFinal(encryptedTrimmed);
} catch (Exception e){
Utilities.writeToLogs(home,"[E] "+e.toString());
}
return plaintext;
}
/**
* Get card key from card directly
* @author Eduardo Novella & Rafael Boix
*/
private SecretKeySpec getKeyFromCardID() throws CardException{
SecretKeySpec k = null;
CommandAPDU cardIDrequest = new CommandAPDU((byte) 0x00, (byte) 0x69,(byte) 0x00, (byte) 0x00,2);
ResponseAPDU res= ch.transmit(cardIDrequest);
byte[] cardID= res.getData();
if (res.getSW() != 0x9000){
Utilities.writeToLogs(home,"[E] Error reading card ID. Check card connection & reader. ");
throw new CardException("Error while reading Card ID");
}
else{
//We set K as the CardID encrypted with the master key
SecretKeySpec sks = new SecretKeySpec(getMasterKey(), "AES");
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks);
k= new SecretKeySpec(cipher.doFinal(cardID),"AES");
} catch (Exception e) {
Utilities.writeToLogs(home,"[E] "+e.toString());
}
}
return k;
}
/**
* Get card key from byte array
* @author Eduardo Novella & Rafael Boix
*/
@SuppressWarnings("unused")
private SecretKeySpec getKeyFromCardID(byte[] cardID) throws CardException{
SecretKeySpec k = null;
SecretKeySpec sks = new SecretKeySpec(getMasterKey(), "AES");
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks);
k= new SecretKeySpec(cipher.doFinal(cardID),"AES");
} catch (Exception e) {
Utilities.writeToLogs(home,"[E] "+e.toString());
}
return k;
}
/**
* @author Eduardo Novella & Rafael Boix
*/
private boolean initSignatureKey(byte[] cardID){
//Note that this works *in the terminal* due to the JCardSim wrapper of BouncyCastle :)
SecretKeySpec sks = new SecretKeySpec(getMasterKey(), "AES");
try {
Cipher cipher = Cipher.getInstance("AES");
int len=cardID.length;
byte[] sigCardID = Arrays.copyOf(cardID, 8*len);
for(int i=len;i<8*len;i++)
sigCardID[i]=(byte)(sigCardID[i-1]^sigCardID[i-2]*i);
cipher.init(Cipher.ENCRYPT_MODE, sks);
byte[] sgn = Arrays.copyOf(cipher.doFinal(sigCardID),24); //3DES key for signature purposes= 192 bit
sk = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_3KEY, false);
sk.setKey(sgn, (short)0);
} catch (Exception e) {
Utilities.writeToLogs(home,"[E] "+e.toString());
return false;
}
return true;
}
/**
* @author Eduardo Novella & Rafael Boix
*/
private String logOperationToString(byte logOp){
switch(logOp){
case LOG_OP_ISSUE:
return new String("CARD_ISSUE");
case LOG_OP_PAY:
return new String("PAYMENT");
case LOG_OP_TOPUP:
return new String("TOPUP");
}
return new String("UNDEFINED");
}
private void loadBlacklist() {
//Mockup for function which loads a blacklist in the terminal; we will just update the blacklist with one card value
blacklist=new Hashtable<Integer, Object>();
blacklist.put(12346, true);
blacklist.put(12345, true);
}
private boolean isBlacklisted(byte[] cardid){
int ID=Utilities.getShort(cardid, 0);
if(blacklist.containsKey(ID)){
return true;
}
return false;
}
private void getTransactionLog() throws CardException{
if(!CARD_LEGIT)
handshakeProtocol();
if(CARD_LEGIT){
if(terminal==null ||(terminal!=null && terminal.isCardPresent()==false)){
Utilities.writeToLogs(home, "[E] Card was teared! Card ID:"+cID);
System.out.println("Card was teared. Terminal will now reboot.");
System.exit(-1);
}
CommandAPDU cardIDrequest = new CommandAPDU((byte) 0x00, INS_GET_TRNSCT_LOG,(byte) 0x00, (byte) 0x00,60);
ResponseAPDU res= ch.transmit(cardIDrequest);
byte[] trLog= res.getData();
if (res.getSW() != 0x9000){
Utilities.writeToLogs(home,"[E] Error while reading transaction log. "+cID);
throw new CardException("Error while reading transaction log.");
}
else{
if(!verifySignature(trLog, 52))
Utilities.writeToLogs(home,"[E] LOG SIGNATURE FAILED - CHECK FOR TAMPERED DATA. "+cID);
int count=Utilities.getShort(trLog, 0);
byte op;
double oldbalance=0;
double newbalance=0;
int index;
if(count<=10){
System.out.println("Log of last operations ** "+Utilities.getSystemDate());
System.out.println("#OP");
for(int i=0;i<count;i++){
op=trLog[(i*5)+2];
oldbalance=(double)(Utilities.getShort(trLog, ((i*5)+3))/100.0);
newbalance=(double)(Utilities.getShort(trLog, ((i*5)+5))/100.0);
System.out.println((i+1)+".- Operation: "+logOperationToString(op)+" ** Old Balance: "+oldbalance+" ** New Balance: "+newbalance);
}
}
else if (count>10){
System.out.println("Log of last operations ** "+Utilities.getSystemDate());
int correctIndexes=count%10; //Up to correctIndexes the indexes are in the same decade as count
for(int i=0;i<10;i++){
if(i<correctIndexes)
index=count-correctIndexes+i;
else
index=count-correctIndexes-10+i;
op=trLog[(i*5)+2];
oldbalance=(double)(Utilities.getShort(trLog, ((i*5)+3))/100.0);
newbalance=(double)(Utilities.getShort(trLog, ((i*5)+5))/100.0);
System.out.println((index+1)+".- Operation: "+logOperationToString(op)+" ** Old Balance: "+oldbalance+" ** New Balance: "+newbalance);
}
}
}
}
}
}