PGDATA.java
// PUT DATA and GET DATA - Example for the ISO/IEC 7816-4 commands PUT DATA and GET DATA
// File: PGDATA.java
// This applet realize a portable data memory with standard ISO/IEC 7816-4 commands.
// In the administrative phase it is possible to write after a successful
// PIN verification data objects into the memory. For adminsitration is also a 
// delete memory command after successful PIN verification available. In the 
// operative phase it is possible to read data without restriction from the card. 
// All data objects are organised in ASN.1 BER TLV coded data objects in the 
// format: DO_1 || DO_2 || ...
//
// In an typical use case the PUT DATA and VERIFY commands will be used in the 
// administrative phase an the GET DATA command will be used for the operational phase.
//
// Package AID: 'D2 76 00 00 60 50 03'
// Applet AID:  'D2 76 00 00 60 41 03'
//
// Source code based on java card specification version 2.1.
// Compiled and tested with JDK 1.3.1 Java Card Application Studio (JCAST) V 2.2,
// JLoad 2.0, Java Card Converter 2.1.2, IFDSIM 4.0 and UniverSIM Java Card 64 kB from G&D.
//
//---------------------------------------------------------------------------------------
// Specification of ISO/IEC 7816-4 case 3 command SELECT FILE
//   command APDU               CLA = '00' || INS = 'A4' ||
//                              P1 (= '04' = select by DF Name) || P2 (='00') ||
//                              Lc (= length of DF Name) || DATA (= DF Name)
//   response APDU (all case)   SW1 || SW2
//
// Specification of ISO/IEC 7816-4 case 3 command VERIFY
//   command APDU               CLA = '00' || INS = '20' || P1 (= '00') ||
//                              P2 ('01' = PIN)
//                              Lc (= length of PIN) || DATA (= PIN)
//   response APDU (all case)   SW1 || SW2
//
// Specification of ISO/IEC 7816-4 case 3 command PUT DATA
//   command APDU               CLA = '00' || INS = 'DA' ||
//                              P1 (= '00') || P2 (= TAG ['40' ... 'FE']) ||
//                              Lc (= length of DATA) ||
//                              DATA (value field of the DO, with length Lc)
//   response APDU (all case)   SW1 || SW2
//
// Specification of ISO/IEC 7816-4 case 2 command GET DATA
//   command APDU               CLA = '00' || INS = 'CA' ||
//                              P1 (= '00') || P2 (= TAG ['40' ... 'FE'])
//                              Le (= length of expected data)
//   response APDU (good case)  DATA (value field of the DO, with length Le) ||
//                              SW1 || SW2
//   response APDU (bad case)   SW1 || SW2
//
// Specification of the proprietary case 1 command DELETE DATA
//   command APDU               CLA = '80' || INS = '06' || P1 (= '00') || P2 (= '00')
//                              Lc (= '00')
//   response APDU (all case)   SW1 || SW2
//
//---------------------------------------------------------------------------------------
// Implementation Notes
//    * based of the GET DATA and PUT DATA command from ISO/IEC 7816-4 CD from 12. July 2002
//    * DOs are simple BER-TLV coded
//    * no support of different DFs
//    * no secure messaging support (= class is alway '00')
//    * TAG:    length of the tag value is 1 byte, tag value: '40' ... 'FE'
//    * LENGTH: length of the length value is 1 byte
//    * VALUE:  length of the value is 127 byte, because of the limitations
//              of a 1 byte length byte (BER-TLV convention). For test reasons 
//              it is set to 9 byte but could be changed with SIZE_MEMORY.
//    * abbreviations: DO - data object
//
//---------------------------------------------------------------------------------------
// Test Strategy
//  				good case tests:
//     					1.1 verify correct PIN
//     					2.1 write DO, read same DO
//     					2.2 try all allowed tag values for DOs
//     					2.3 write smallest DO, read same DO
//     					2.4 write largest DO, read same DO
//     					3.1 delete DO memory
//
//  				bad case tests:
//    					1.1 check access conditions of DELETE
//    					1.2 check access conditions of PUT DATA
//    					1.3 check access conditions of GET DATA
//    					2.1 write DO, read another DO
//    					2.2 try some of the not allowed tag values for DOs (border test)
//    					2.3 write DO with length 0
//    					2.4 write DO with length greater than allowed
//    					3.1 verify wrong PIN, check PIN error counter up till max. value
//    					4.1 fill whole memory with smallest possible DOs
//
//---------------------------------------------------------------------------------------
// This source code is under GNU general public license (see www.opensource.org for details).
// Please send corrections and ideas for extensions to Wolfgang Rankl (www.wrankl.de)
// Copyright 2003-2004 by Wolfgang Rankl, Munich
//---------------------------------------------------------------------------------------
// 13. April 2004 - V 2: improved documentation, 2nd published version
//  7. April 2004 - V 1: initial runnable version, 1st published version
//---------------------------------------------------------------------------------------

package packpgdata;             // this is the package name
import javacard.framework.*;    // import all neccessary packages for java card

public class PGDATA extends Applet {
  final static byte    PROP_CLASS  = (byte) 0x80;     // Class of the proprietary APDU commands

  final static byte    INS_SELECT  = (byte) 0xA4;     // instruction for the ISO/IEC 7816-4 SELECT FILE command
  final static byte    INS_VERIFY  = (byte) 0x20;     // instruction for the ISO/IEC 7816-4 VERIFY command
  final static byte    INS_PUTDATA = (byte) 0xDA;     // instruction for the ISO/IEC 7816-4 PUT DATA command
  final static byte    INS_GETDATA = (byte) 0xCA;     // instruction for the ISO/IEC 7816-4 GET DATA command
  final static byte    INS_DELETE  = (byte) 0x06;     // instruction for the proprietary DELETE DATA command

  final static short   SW_PIN_FAILED =     (short) 0x63C0;  // returncode for PIN verification failed
                                                            // the last nibble shows the number of remaining tries
  final static short   SW_DATA_NOT_FOUND = (short) 0x6A88;  // referenced data not found

  // definitions for the data storage area
  final static short   SIZE_MEMORY =     (short) (9);      // size of the data storage area in byte
  static byte[]        memory;                             // this is the data storage area for the application
  static short         memory_NoOfDOs;                     // number of DO stored in the memory

  // definitions for the storage of the DOs
  final static short LEN_TAG = (short) 1;   // length of the DO tag information in bytes
  final static short LEN_LEN = (short) 1;   // length of the DO length information in bytes

  // constants and variables for the PIN management
  final static byte[]  DEFAULT_PIN = {(byte) 0x12, (byte) 0x34}; // default PIN value
  final static byte    PIN_SIZE =          (byte) 2;             // size of the PIN in byte
  final static byte    DEFAULT_PIN_MAXEC = (byte) 3;             // default value of the PIN error counter
  final static byte    PIN_ID =            (byte) 1;             // PIN identifier, PIN 1 = card owner PIN
  static OwnerPIN      pin;                                      // the PIN object

  // error codes of some methods
  // the value of this error codes is outside the memory size and thus
  // they can easily identified, this simplifies the return value of the appropriate methods
  private static final short DO_PROPPER_SET =       (short) (SIZE_MEMORY + 1);  // DO is propper set
  private static final short NOT_ENOUGH_SPACE =     (short) (SIZE_MEMORY + 2);  // not enough space for a new DO
  private static final short DO_HAVE_WRONG_LENGTH = (short) (SIZE_MEMORY + 3);  // presented DO have a different length than stored DO
  private static final short DO_NOT_FOUND =         (short) (SIZE_MEMORY + 4);  // presented DO not found in the memory

//---------------------------------------------------------------------------------------
  //----- installation and registration of the applet
  public static void install(byte[] buffer, short offset, byte length) {
    memory = new byte[SIZE_MEMORY];  // create data storage object for the application

    // create and set the PIN object for the card owner
    pin = new OwnerPIN(DEFAULT_PIN_MAXEC, PIN_SIZE);
    pin.update(DEFAULT_PIN, (short)(0), PIN_SIZE);

    new PGDATA().register();         // register the application within the JCRE
  }  // install

  //---------------------------------------------------------------------------------------
  //----- this is the command dispatcher
  public void process(APDU apdu) {
    byte[] cmd_apdu = apdu.getBuffer();

    if (cmd_apdu[ISO7816.OFFSET_CLA] == ISO7816.CLA_ISO7816) {
      //----- it is the ISO/IEC 7816 class
      switch(cmd_apdu[ISO7816.OFFSET_INS]) {  // check the instruction byte
        case INS_SELECT:                      // it is a SELECT FILE instruction
          cmdSELECT(apdu);
          break;
        case INS_VERIFY:                      // it is a VERIFY instruction
          cmdVERIFY(apdu);
          break;
        case INS_PUTDATA:                     // it is a PUT DATA instruction
          cmdPUTDATA(apdu);
          break;
        case INS_GETDATA:                     // it is a GET DATA instruction
          cmdGETDATA(apdu);
          break;
        default :                             // the instruction in the command apdu is not supported
          ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
      }  // switch
    }  // if
    else if (cmd_apdu[ISO7816.OFFSET_CLA] == PROP_CLASS) {
      //----- it is the proprietary class
      switch(cmd_apdu[ISO7816.OFFSET_INS]) {  // check the instruction byte
        case INS_DELETE:                      // it is a DELETE DATA instruction
          cmdDELETE(apdu);
          break;
        default :                             // the instruction in the command apdu is not supported
          ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
      }  // switch
    }  // else if
    else {                                    // the class in the command apdu is not supported
      ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
    }  // else
  }  // process

  //---------------------------------------------------------------------------------------
  //----- program code for the APDU command SELECT FILE
  private void cmdSELECT(APDU apdu) {
    byte[] cmd_apdu = apdu.getBuffer();

    //----- check preconditions in the APDU header
    // check if P1='04'
    if (cmd_apdu[ISO7816.OFFSET_P1] != 0x04) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    // check if P2='00'
    if (cmd_apdu[ISO7816.OFFSET_P2] != 0x00) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    short lc = (short)(cmd_apdu[ISO7816.OFFSET_LC] & 0x00FF);  // get Lc (command length)
    receiveAPDUBody(apdu);                                     // get the command body

    //----- command functionality
    if (JCSystem.getAID().equals(cmd_apdu, ISO7816.OFFSET_CDATA, (byte) lc) == false) {
      ISOException.throwIt(ISO7816.SW_APPLET_SELECT_FAILED);
    } // if

    //----- prepare response APDU
    ISOException.throwIt(ISO7816.SW_NO_ERROR);   // command proper executed
  }  // cmdSELECT

  //---------------------------------------------------------------------------------------
  //----- program code for the APDU command VERIFY
  private void cmdVERIFY(APDU apdu) {
    byte[] cmd_apdu = apdu.getBuffer();

    //----- check preconditions in the APDU header
    // check if P1='00'
    if (cmd_apdu[ISO7816.OFFSET_P1] != 0) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    short lc = (short)(cmd_apdu[ISO7816.OFFSET_LC] & 0x00FF);  // get Lc (command length)
    receiveAPDUBody(apdu);                                     // get the command body

    if (cmd_apdu[ISO7816.OFFSET_P2] == PIN_ID) {    
      if (lc != PIN_SIZE) {                    // Lc = length of PIN ?
        ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
      } //if
      if (pin.check(cmd_apdu, ISO7816.OFFSET_CDATA, PIN_SIZE) == false) {
        // PIN verification not successful
        short tries = pin.getTriesRemaining();
        ISOException.throwIt( (short) (SW_PIN_FAILED + tries));  // send error counter in APDU back
      } // if
    } // if
    else {          // it is not a valid PIN identifier
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // else
    // PIN verification successful
    ISOException.throwIt(ISO7816.SW_NO_ERROR);               // command proper executed
  }  // cmdVERIFY

  //---------------------------------------------------------------------------------------
  //----- program code for the APDU command PUT DATA
  private void cmdPUTDATA(APDU apdu) {
    byte[] cmd_apdu = apdu.getBuffer();

    //----- check preconditions in the APDU header
    // check if P1=0
    if (cmd_apdu[ISO7816.OFFSET_P1] != 0) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    // check if P2 contents an allowed tag value
    short tag = (short) (cmd_apdu[ISO7816.OFFSET_P2] & (short) 0x00FF);
    if ((tag < (short) 0x0040) || (tag > (short) 0x00FE)) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    short lc = (short)(cmd_apdu[ISO7816.OFFSET_LC] & (short) 0x00FF);  // calculate Lc (expected length)
    receiveAPDUBody(apdu);     // get the command body

    //----- check precoditions of security status
    if (pin.isValidated() == false) {
      ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
    } // if

    //----- command functionality
    short result = SetDO(lc, tag, apdu);

    //----- prepare response APDU
    if (result == DO_PROPPER_SET) {
      ISOException.throwIt(ISO7816.SW_NO_ERROR);   // command proper executed
    } // if
    else if (result == NOT_ENOUGH_SPACE) {
      ISOException.throwIt(ISO7816.SW_FILE_FULL);   // not enough free memory
    } // else if
    else if (result == DO_HAVE_WRONG_LENGTH) {
      short index = GetIdxToDO((short) tag);
      short len = memory[(short) (index + LEN_TAG)];  // get the correct length of the DO
      ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH + len));   // send correct length back
    }  // else if
    else { // this case should never happen, there must be an error in code above
      ISOException.throwIt(ISO7816.SW_UNKNOWN);   // unknown error
    }  // else
  }  // cmdPUTDATA

  //---------------------------------------------------------------------------------------
  //----- program code for the APDU command GET DATA
  private void cmdGETDATA(APDU apdu) {
    byte[] cmd_apdu = apdu.getBuffer();

    //----- check preconditions in the APDU header
    // check if P1=0
    if (cmd_apdu[ISO7816.OFFSET_P1] != 0) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    // check if P2 contents an allowed tag value
    short tag = (short) (cmd_apdu[ISO7816.OFFSET_P2] & (short) 0x00FF);
    if ((tag < 0x40) || (tag > 0xFE)) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    short le = (short)(cmd_apdu[ISO7816.OFFSET_LC] & 0x00FF);  // calculate Le (expected length)
    if (le > SIZE_MEMORY) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    } // if

    //----- command functionality
    short index = (short) GetIdxToDO(tag);
    if (index == DO_NOT_FOUND) {             // DO not found
      ISOException.throwIt(SW_DATA_NOT_FOUND);
    } // if
    short len = (short) memory[(short) (index+LEN_TAG)];  // length of the DO value
    len = (short) (len);                          // calculate length of the whole DO
    if (le != len) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    } // if
    index = (short) (index + LEN_TAG + LEN_LEN);     // index to the value of the DO

    //----- now all preparations are done, the data object can be send to the IFD -----
    apdu.setOutgoing();                           // set transmission to outgoing data
    apdu.setOutgoingLength((short)le);            // set the number of bytes to send to the IFD
    apdu.sendBytesLong(memory, (short)index, (short)le); // send the requested the number of bytes to the IFD, data field of the DO
  }  // cmdGETDATA

  //---------------------------------------------------------------------------------------
  //----- program code for the APDU command DELETE DATA
  private void cmdDELETE(APDU apdu) {
    byte[] cmd_apdu = apdu.getBuffer();

    //----- check preconditions in the APDU header
    // check if P1=0
    if (cmd_apdu[ISO7816.OFFSET_P1] != 0) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    // check if P2=0
    if (cmd_apdu[ISO7816.OFFSET_P2] != 0) {
      ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
    } // if
    short lc = (short)(cmd_apdu[ISO7816.OFFSET_LC] & 0x00FF);  // get Lc (command length)
    if (lc != 0) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    } // if

    //----- check precoditions of security status
    if (pin.isValidated() == false) {
      ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
    } // if

    //----- now all preparations are done, the data storage area can be deleted
    Util.arrayFillNonAtomic(memory, (short) 0, SIZE_MEMORY, (byte) 0x00);   // overwrite the data storage area with '00'
    memory_NoOfDOs = 0;    // no stored DOs anymore
    ISOException.throwIt(ISO7816.SW_NO_ERROR);               // command proper executed
  }  // cmdDELETEDATA

  //---------------------------------------------------------------------------------------
  //----- receive the body of the command APDU
  public void receiveAPDUBody(APDU apdu) {
    byte[] buffer = apdu.getBuffer();
    short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0x00FF);  // calculate Lc (expected length)
    // check if Lc != number of received bytes of the command APDU body
    if (lc != apdu.setIncomingAndReceive()) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    } // if
  }  // receiveAPDUBody

  //---------------------------------------------------------------------------------------
  //----- calculate the index to the first free byte in the memory
  public short GetIdxToFreeSpace () {
    short tag, len, index, docounter;

    index = 0;    //set index to first byte of the memory
    for (docounter = 0; docounter < memory_NoOfDOs; docounter ++) {
      tag = memory[index];                          // get a valid tag, for debugging reasons
      len = memory[(short) (index + LEN_TAG)];      // get length of the DO
      index = (short) (index + len + LEN_TAG + 1);  // calculate the new index to the next DO
    } // for
    return index;       // give the index of the first free byte back
  } // GetIdxToFreeSpace

  //---------------------------------------------------------------------------------------
  //----- get the index to a specific DO in the memory
  public short GetIdxToDO (short tag) {
    short xtag, len, index, docounter;

    index = 0;    //set index to first byte of the memory
    for (docounter = 0; docounter < memory_NoOfDOs; docounter ++) {
      xtag = memory[index];               // get a valid tag
      if (xtag == tag) return index;      // tag found
      else  {    // tag not found
        len = memory[(short) (index + LEN_TAG)];      // get length of the DO
        index = (short) (index + len + LEN_TAG + 1);  // calculate the new index to the next DO
      } // else
    } // for
    return (short) DO_NOT_FOUND;          // tag not found
  } // GetIdxToDO

  //---------------------------------------------------------------------------------------
  //----- set a DO to a new value or create a new DO if it is not existing
  public short SetDO (short lc, short tag, APDU apdu) {
    byte[] cmd_apdu = apdu.getBuffer();

    short index = GetIdxToDO(tag);
    if (index == (short) DO_NOT_FOUND) {
      //----- DO not found -> create a new one
      short index2free = GetIdxToFreeSpace();
      short freesize = (short) (SIZE_MEMORY - index2free);   // calculate size of free space
      short DOsize = (short) (lc+LEN_TAG+LEN_LEN);           // calculate size of the new DO 
      if (DOsize <= freesize) {
        // it is enough space for a new DO
        memory[index2free] = (byte) tag;              // set DO tag
        memory[(short) (index2free+LEN_TAG)] = (byte) lc;              // set DO length
        // copy the DO atomic into the memory
        Util.arrayCopy(cmd_apdu, (short)((ISO7816.OFFSET_CDATA) & 0x00FF), memory, (short) (index2free+LEN_TAG+LEN_LEN), lc);
        memory_NoOfDOs++;      //increase the number of stored DOs
        return DO_PROPPER_SET;
      }  // if
      else  // it is not enough space for a new DO
        return NOT_ENOUGH_SPACE;
    }  // if
    else {
      //----- DO found -> update it
      if (lc != memory[(short) (index+LEN_TAG)]) { // DO have wrong length
        return DO_HAVE_WRONG_LENGTH;
      }  // if
      else {                    // DO have right length -> update it
        // copy the DO atomic into the memory
        Util.arrayCopy(cmd_apdu, (short)((ISO7816.OFFSET_CDATA) & 0x00FF), memory, (short) (index+LEN_TAG+LEN_LEN), lc);
        return DO_PROPPER_SET;
      }  // else
    }  // else
  }  // SetDO

}  // class





PGDATA.java