Package card

Source Code of card.CardApp

package card;
//PACKAGE AID: 63686561706B6E6970     ("cheapknip")
//APPLET AID: 63686561706B6E697001
//Applet revision: 1.5
/**
*   @author Rafael Boix & Eduardo Novella
*/
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.AESKey;
import javacard.security.CryptoException;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;

public class CardApp extends Applet {

  //Constants in the card & byte arrays; the JavaCard VM sets them to false/null/0 by default
  private static boolean     CARD_ISSUED; //If true, the card is blocked for modifying card USER DATA and INS 0x10 is not valid
  private static boolean     CARD_BLOCKED; //If true, the card is blocked
  private static boolean[]   TRUSTED_TERMINAL;
  private static byte[]      AES_KEY_128;
  private static byte[]      TDES_KEY;
  final private static short CARD_ID     = (short)12345; //This should be retrieved from a manufacturer value hardwired into the Javacard

  //APDU INS operation codes in the apdu header
  final static private byte INS_ISSUE_NEW_CARD = (byte0x10;
  final static private byte INS_HANDSHAKE      = (byte0x20;
  final static private byte INS_GET_BALANCE    = (byte0x30;
  final static private byte INS_MODIFY_BALANCE = (byte0x40;
  final static private byte INS_GET_OWNER_INFO = (byte0x50;
  final static private byte INS_GET_TRNSCT_LOG = (byte0x60;
  final static private byte INS_GET_CARD_ID    = (byte0x69;
  final static private byte INS_BLOCK_CARD     = (byte0x99;
  final static private short MAX_BALANCE       = (short) 0x61A8; // Max balance  250.00€   short decimal=25000

  //Log operation types
  final static private byte LOG_OP_PAY        = (byte0x11;
  final static private byte LOG_OP_TOPUP      = (byte0x22;
  final static private byte LOG_OP_ISSUE      = (byte0x33;


  //Variables in RAM
  private byte[] userData;   //User details: name, address, birthdate
  private byte[] tempData;   //Scratchpad array for data I/O, 128 bytes
  private byte[] calcData;   //Scratchpad array for calculations, 128 bytes
  private byte[] state;      //Internal state of the applet's protocols
  private byte[] transLog;   //Log of 10 last transactions of card
  private short  balance;    //Actual Balance of money into the card
  private byte[] cryptoTemp; //Internal state of the applet's protocols
  private AESKey k;           //AES key object in RAM
  private DESKey sk;           //3DES key object in RAM for signature purposes
  private Signature sg;       //Signature object for MAC generation
  private Cipher c;           //Cipher object for AES encryption

  public CardApp() {
    //Arrays init
    tempData         = JCSystem.makeTransientByteArray((short)128, JCSystem.CLEAR_ON_RESET);
    calcData         = JCSystem.makeTransientByteArray((short)128, JCSystem.CLEAR_ON_RESET);
    cryptoTemp       = JCSystem.makeTransientByteArray((short)128, JCSystem.CLEAR_ON_RESET);
    TRUSTED_TERMINAL = JCSystem.makeTransientBooleanArray((short)1, JCSystem.CLEAR_ON_RESET);
    state            = JCSystem.makeTransientByteArray((short)10, JCSystem.CLEAR_ON_RESET);
    userData         = new byte[100];
    transLog         = new byte[60];//2byte transaction counter + 10*5byte logEntry + 8byte signature
    //AES & 3DES key init
    AES_KEY_128      = new byte[16];
    TDES_KEY         = new byte[24];
    try{
      k = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);
      sk = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_3KEY, false);
      sg = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2, false);
      c = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false);
    }catch(CryptoException ce){
      CryptoException.throwIt((short)0xCACA); //This will show up at applet install if card does not support all this crypto stuff
    }
  }

  public static void install(byte[] bArray, short bOffset, byte bLength) {
    // GP-compliant JavaCard applet registration
    new CardApp().register(bArray, (short) (bOffset + 1), bArray[bOffset]);   
  }

  /**
   * @author Eduardo Novella & Rafael Boix
   * @param apdu The apdu to be processed
   */
  public void process(APDU apdu) {
    if (selectingApplet()) {
      // Good practice: Return 9000 on SELECT
      TRUSTED_TERMINAL[0]=false;
      state[0]=(byte)0x00; // Reset authentication protocol
      return;
    }

    //Utils : references to fields in a CommandAPDU apdu
    byte[] buff = apdu.getBuffer();
    byte cla    = buff[ISO7816.OFFSET_CLA];
    byte ins    = buff[ISO7816.OFFSET_INS];
    if ( cla == (byte) 0x00){
      switch ( ins ) {
      case INS_ISSUE_NEW_CARD:
        if (CARD_BLOCKED) ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        issueNewCard(apdu);
        break;
      case INS_HANDSHAKE     :
        handshake(apdu);
        break;
      case INS_GET_BALANCE   :
        if (CARD_BLOCKED) ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        getBalance(apdu);
        break;
      case INS_MODIFY_BALANCE:
        if (CARD_BLOCKED) ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        modifyBalance(apdu);
        break;
      case INS_GET_OWNER_INFO:
        getOwnerInfo(apdu);
        break;
      case INS_GET_TRNSCT_LOG:
        getTransctLog(apdu);
        break;
      case INS_GET_CARD_ID   :
        getCardID(apdu);
        break;
      case INS_BLOCK_CARD    :
        CARD_BLOCKED=true//Card is blocked: no further operation possible with the card, only get last transaction(s)
        return;
      default:
        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
      }   
    }
  }

  /**
   * @author Eduardo Novella & Rafael Boix
   * @param apdu The apdu to be processed
   */
  private void getTransctLog(APDU apdu) {
    if (CARD_ISSUED) {
      short le         = apdu.setOutgoing();
      if (le < 60)
        ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
      //Send transaction log (log is already signed)
      apdu.setOutgoingLength((short) (short)60);
      apdu.sendBytesLong(transLog, (short) 0, (short)60);
    } else
      ISOException.throwIt((short) ISO7816.SW_INS_NOT_SUPPORTED);
  }

  /**
   * @author Eduardo Novella & Rafael Boix
   * @param apdu The apdu to be processed
   */
  private void getCardID(APDU apdu) {
    //This function is a mockup from the real instruction that gets the hardwired (tamper-proof) unique Card ID
    byte[] buff = apdu.getBuffer();
    short le = apdu.setOutgoing();
    if (le < 2) { //2bytes card id
      ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH));
    } else {
      Util.setShort(buff, (short) 0, CARD_ID);
      apdu.setOutgoingLength((short) 2);
      // Send len from buffer to bOff (begin offset)
      apdu.sendBytes((short) 0, (short) 2);
    }
  }

  /**
   * Steps for issuing a new card:   
   *   Store UserData & keys into EEPROM
   *   Set CARD_ISSUED= true
   *   Verify that everything works: cipher & signatures
   *     
   * @author Eduardo Novella & Rafael Boix
   * @param apdu The apdu to be processed
   */
  private void issueNewCard (APDU apdu){
    byte[] buff = apdu.getBuffer();

    if(CARD_ISSUED)
      ISOException.throwIt((short) ISO7816.SW_INS_NOT_SUPPORTED);
    else{
      JCSystem.beginTransaction();
      //We get in the buffer the card key K , card signing key SK and 100 byte array of user data
      //Write card key K in AES_KEY_128[]
      Util.arrayCopy(buff, ISO7816.OFFSET_CDATA, AES_KEY_128, (short)0, (short)16);
      //Write card signing key K in TDES_KEY[]
      Util.arrayCopy(buff, (short)21, TDES_KEY, (short)0, (short)24);
      //Write user data
      Util.arrayCopy(buff, (short)45, userData, (short)0, (short)100);
      CARD_ISSUED=true; //Card Issuance Blocked; from now on INS 0x10 is unreachable
      //Set keys to objects
      try{
        k.setKey(AES_KEY_128, (short)0);
        sk.setKey(TDES_KEY, (short)0);
      }
      catch(CryptoException ce){
        ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
      }
      JCSystem.commitTransaction();
      //Card verification routine:We send back to the terminal the cardID+userDATA
      //(first 16 bytes encrypted with K) and signed with sk back to verify everything
      short le = apdu.setOutgoing();

      if (le < 110) {
        ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH));
      }
      else{
        apdu.setOutgoingLength((short)110);
        Util.setShort(buff, (short)0, CARD_ID);
        Util.arrayCopy(userData, (short)0, buff, (short)2, (short)100);
        //Encrypt 16 first bytes of data for crypto testing
        encryptAES128(buff, (short)0, (short)16);
        //Sign encrypted data
        signMessage(buff, (short)102, buff, (short)102);
        // Send len from buffer to bOff (begin offset)
        apdu.sendBytes((short)0, (short)110);
        addLogEntry(LOG_OP_ISSUE, (short)0, (short)0);

      }
    } 
  }


  /**
   * Handshake function: verifies that the terminal is legit and also proves that the card
   * is legit to the terminal (mutual authentication). Includes two challenge/response operations.
   *
   * @author Eduardo Novella & Rafael Boix
   * @param apdu The apdu to be processed
   */
  private void handshake(APDU apdu){

    if (CARD_ISSUED) {
      byte[] buff = apdu.getBuffer();
      // APDU parameter XX --> 00 20 XX 00  -->  4 authentication steps (00,01,10,11)
      byte p1 = buff[ISO7816.OFFSET_P1];
      // Terminal sends a nonce to Card & Card generates nonceR = (nonceT ^ nonceC)
      if (p1 == (byte) 0x00) {
        JCSystem.beginTransaction();
        if (state[0] == (byte) 0x00) {
          //nonceT
          Util.arrayCopy(buff, (short) ISO7816.OFFSET_CDATA,tempData, (short) 0, (short) 8);
          //nonceC
          RandomData nonceC = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
          nonceC.generateData(calcData, (short) 0, (short) 8);

          //nonceT^nonceC=nonceR
          XOR(tempData, calcData, calcData, (short) 0, (short) 0,(short) 8, (short) 8);

          //We send id,AES(nonceR)K and update internal state of handshake protocol
          //-------------------------------------------------------------------------
          state[0] = (byte) 0x01;

          // Sets the data transfer direction to outbound and obtains the expected length of the response.
          short le = apdu.setOutgoing();
          if (le < 18// 16 bytes nonceR encrypted + 2bytes card id
            ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH));

          //nonceR is at offset 8 in calcData
          Util.arrayCopy(calcData,(short) 8,buff,(short) 0,(short) 8);
          Util.arrayFillNonAtomic(buff, (short)8,(short) 16,(byte) 0);

          // AES(nonceR)K
          encryptAES128(buff,(short)0,(short)16);

          // we put CardID in plaintext at the final of the buffer
          Util.setShort(buff,(short)16,CARD_ID);

          // Sets the actual length of the response data.
          apdu.setOutgoingLength((short) (18+8));
          signMessage(buff, (short)18, buff, (short)18);
          // Send len from buffer to bOff (begin offset)
          apdu.sendBytes((short) 0, (short) (18+8));
        } else {
          state[0] = (byte) 0x00;
          TRUSTED_TERMINAL[0] = false;
        }
        JCSystem.commitTransaction();
      }
      // Terminal sends (nonceC,nonceT2)K to card -> card trusts terminal after checking nonceC
      else if (p1 == (byte) 0x10 && state[0] == (byte) 0x01) {

        //Update internal protocol state
        state[0] = (byte) 0x10;

        //nonceC_T2 from buffer to tempData -- length 16 bytes, offset byte 16
        Util.arrayCopy(buff,(short) ISO7816.OFFSET_CDATA,tempData,(short) 16,(short)16);

        //AES(nonceC,nonceT2)K and check if it matches nonceC in card
        //Check if decrypted nonceC matches nonceC in calcData
        decryptAES128(tempData, (short)16);

        JCSystem.beginTransaction();

        byte nonceCok = Util.arrayCompare(calcData,(short)0,tempData,(short)16,(short)8);
        if (nonceCok == (byte) 0) {
          state[0] = (byte) 0x11; //Card trusts Terminal, we send the challenge response to terminal
          //nonceT^nonceT2=nonceR2; offset nonceT=0,offset nonceT2=16
          XOR(tempData,tempData,calcData,(short)0,(short)24,(short)0,(short)8);

          //We send AES(nonceR2)K and set TRUSTED_TERMINAL to true
          encryptAES128(calcData,(short)0,(short)16);
          // Sets the data transfer direction to outbound and obtains the expected length of the response.
          short le = apdu.setOutgoing();
          if (le < 16) { //8 bytes of response
            ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH));
          }
          Util.arrayCopy(calcData,(short)0,buff,(short)0,(short) 16);

          // Sets the actual length of the response data.
          apdu.setOutgoingLength((short) (16+8));
          signMessage(buff, (short)16, buff, (short)16);
          // Send len from buffer to bOff (begin offset)
          apdu.sendBytes((short) 0, (short) (16+8));
          TRUSTED_TERMINAL[0] = true;
          state[0] = (byte) 0x00;

        } else {
          state[0] = (byte) 0x00;      //NonceC not ok
          TRUSTED_TERMINAL[0] = false; //Should be already set to false
          ISOException.throwIt((short)0xDEAD);

        }
        JCSystem.commitTransaction();
      } else {
        state[0] = (byte) 0x00;
        TRUSTED_TERMINAL[0] = false;
      }
    }else
      ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
  }

  /**
   * Returns the card balance
   *
   * @author Eduardo Novella & Rafael Boix
   * @param apdu The apdu to be processed
   */
  private void getBalance (APDU apdu){
    if(CARD_BLOCKED){
      ISOException.throwIt((short) ISO7816.SW_INS_NOT_SUPPORTED);
      return;
    }
    if (CARD_ISSUED) {
      if (TRUSTED_TERMINAL[0]) {
        byte[] buff      = apdu.getBuffer();
        short le         = apdu.setOutgoing();

        if (le < 16+8)
          ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

        // Copy balance to buffer
        Util.setShort(buff, (short) 0, balance);
        encryptAES128(buff,(short)0,(short)16);

        //Sign&send encrypted data
        apdu.setOutgoingLength((short) (short)(16+8));
        signMessage(buff, (short)16, buff, (short)16);
        apdu.sendBytes((short) 0, (short) (short)(16+8));
      } else
        ISOException.throwIt((short) ISO7816.SW_INS_NOT_SUPPORTED);
    }else
      ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
  }


  /**
   * Modifies the card balance
   *
   * We receive a buffer like that:  YY XX XX
   *                                  op b1 b2
   *  Where op=00 for -
   *        op=01 for +
   * 
   *  XX XX is the balance (short number)
   * 
   * @author Eduardo Novella & Rafael Boix
   * @param apdu The apdu to be processed
   */
  private void modifyBalance (APDU apdu){
    if(CARD_BLOCKED){
      ISOException.throwIt((short) ISO7816.SW_INS_NOT_SUPPORTED);
      return;
    }
    if (CARD_ISSUED) {
      if (TRUSTED_TERMINAL[0]) {
        byte[] buff = apdu.getBuffer();
        //Failed signature = untrusted terminal + throw exception
        if(!verifySignature(buff, ISO7816.OFFSET_CDATA, (short)16, (short)(ISO7816.OFFSET_CDATA+16))){
          TRUSTED_TERMINAL[0]=false;
          ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        }

        decryptAES128(buff,ISO7816.OFFSET_CDATA);
        byte op = buff[ISO7816.OFFSET_CDATA];
        short newAmount = Util.makeShort(buff[ISO7816.OFFSET_CDATA + 1],buff[ISO7816.OFFSET_CDATA + 2]);
        short finalBalance = (short) 0;

        JCSystem.beginTransaction();
        if (newAmount == 0)
          return;
        if (newAmount < 0) {
          JCSystem.abortTransaction();
          ISOException.throwIt((short) ISO7816.SW_COMMAND_NOT_ALLOWED);
        }

        if (op == (byte) 0x00) { // We subtract amount of money
          finalBalance = (short) (balance - newAmount);
          if (finalBalance < 0) {
            JCSystem.abortTransaction();
            ISOException.throwIt((short) ISO7816.SW_DATA_INVALID);
          } else{
            addLogEntry(LOG_OP_PAY, balance, finalBalance);
            balance = finalBalance;
          }


        } else if (op == (byte) 0x01) { // We add amount of money
          finalBalance = (short) (balance + newAmount);
          if (finalBalance > MAX_BALANCE) {
            JCSystem.abortTransaction();
            ISOException.throwIt(ISO7816.SW_DATA_INVALID);
          }
          else{
            addLogEntry(LOG_OP_TOPUP, balance, finalBalance);
            balance = finalBalance;
          }
        } else {
          JCSystem.abortTransaction();
          ISOException.throwIt((short) ISO7816.SW_INS_NOT_SUPPORTED);
        }

        // Prepare buffer to reply
        short le = apdu.setOutgoing();
        if (le < 16+8)
          ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH));

        // Copy balance to buffer
        Util.setShort(buff, (short) 0, balance);

        //Encrypt, sign & send back
        encryptAES128(buff,(short)0,(short)16);
        apdu.setOutgoingLength((short) (short)(16+8));
        signMessage(buff, (short)16, buff, (short)16);
        apdu.sendBytes((short) 0, (short) (short)(16+8));

        JCSystem.commitTransaction();
      } else
        ISOException.throwIt((short) ISO7816.SW_INS_NOT_SUPPORTED);
    }else
      ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
  }

  /**
   * Sends the user data in the card
   *  User details: name, address ,birthday  100 Bytes
   *  Name       == 40 bytes
   *  Address    == 54 bytes
   *  Birth date ==  6 bytes
   * @author Eduardo Novella & Rafael Boix
   * @param apdu
   */
  private void getOwnerInfo (APDU apdu){
    if (CARD_ISSUED) {
      if (TRUSTED_TERMINAL[0]) {
        byte[] buff = apdu.getBuffer();
        // Prepare buffer to reply
        short le = apdu.setOutgoing();
        if (le < 100) {
          ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH));
        }
        // Copy UserData to buffer
        Util.arrayCopy(userData, (short) 0, buff, (short) 0,
            (short) 100);
        // Sets the actual length of the response data.
        apdu.setOutgoingLength((short) 100);
        // Send len from buffer to bOff (begin offset)
        apdu.sendBytes((short) 0, (short) 100);
      } else
        ISOException.throwIt((short) ISO7816.SW_INS_NOT_SUPPORTED);
    }else
      ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
  }

  //Utility functions:xor, crypto, signatures, logging
  /**
   * This function performs a XOR of length bytes of buffer1, buffer2 and writes output to bufferDest (all in their respective offsets)
   *
   * @author Eduardo Novella & Rafael Boix
   */
  private void XOR(byte[] buffer1, byte[] buffer2,byte[] bufferDest, short offset1, short offset2, short offsetDest, short length){
    short n=(short) 0;
    while(n<length){
      bufferDest[offsetDest+n] = (byte) (buffer1[offset1+n] ^ buffer2[offset2+n]);
      n++;
    }
  }
  /**
   * Decrypt 16 bytes using AES128
   * @author Eduardo Novella & Rafael Boix
   * @param buff The buffer to decrypt 16 bytes
   * @param offset The offset in buffer buff
   */
  private void decryptAES128(byte[] buff,short offset){
    Util.arrayCopy(buff, offset, cryptoTemp, (short)0, (short)16);
    try{     
      c.init(k, Cipher.MODE_DECRYPT);
      c.doFinal(cryptoTemp,(short)0,(short)16,buff,(short)offset);
    }catch(CryptoException e){
      ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
    }
  }

  /**
   * Encrypt a buffer using AES128
   * @author Eduardo Novella & Rafael Boix
   * @param buff The buffer to encrypt
   * @param offset The offset in buffer buff
   * @param len The length of the message to be encrypted
   */
  private void encryptAES128(byte[] buff,short offset,short len){
    Util.arrayCopy(buff, offset, cryptoTemp, (short)0, len);
    try{     
      c.init(k, Cipher.MODE_ENCRYPT);
      c.doFinal(cryptoTemp,(short)0,len,buff,(short)offset);
    }catch(CryptoException e){
      ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
    }
  }
  /**
   * Sign a message with 8-byte MAC using 3DES
   * @author Eduardo Novella & Rafael Boix
   * @param msg The buffer to sign
   * @param msgLength The length of data to be signed
   * @param signature The byte[] where the 8byte signature will be written
   * @param signOffset The offset in the signature byte[]
   */
  private void signMessage(byte[] msg, short msgLength, byte[] signature, short signOffset){
    sg.init(sk, Signature.MODE_SIGN);
    sg.sign(msg, (short)0, msgLength, signature, signOffset);
  }

  /**
   * Verify a 8-byte MAC signature generated using 3DES
   * @author Eduardo Novella & Rafael Boix
   * @param buff The buffer to encrypt
   * @param offset The offset in buffer buff
   * @param len The length of the message to be encrypted
   */
  private boolean verifySignature(byte[] msgPlusSignature,short messageOffset, short msgLength, short signatureOffset){
    sg.init(sk, Signature.MODE_VERIFY);
    return sg.verify(msgPlusSignature, messageOffset, msgLength, msgPlusSignature,signatureOffset,(short)8);
  }
  /**
   * Add an entry to the log in the card; the log is signed, and behaves like a circular buffer when adding entries (10 entries max)
   * @author Eduardo Novella & Rafael Boix
   * @param op The operation that has been performed
   * @param prevBalance The previous balance in the card
   * @param newBalance The new balance in the card
   */
  private void addLogEntry(byte op,short prevBalance,short newBalance){
    short count=Util.getShort(transLog, (short)0);
    //Prevent transaction counter overflow: block card (should happen in ~4 years with 20 transactions/day)
    if(count==(short)32766)
      CARD_BLOCKED=true;
    short index=(short)(((count%10)*5)+2);
    transLog[index]=op;
    Util.setShort(transLog, (short)(index+1), prevBalance);
    Util.setShort(transLog, (short)(index+3), newBalance);
    Util.setShort(transLog, (short)0,(short)(count+1));
    signMessage(transLog, (short)52, transLog, (short)52);
  }
}
TOP

Related Classes of card.CardApp

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.