// TAN Generator - Example for a TAN (transaction number) generator // File: TANGen.java // This applet realize a DES based generator for transaction numbers or one-time-passworts // using standard ISO/IEC 7816-4 commands for administrative and operational data exchange. // In the administrative phase it is possible to write after a successful // PIN verification administrative data objects into the memory. // In the operative phase it is possible to get TANs from the card. // // In an typical use case the VERIFY command and PUT DATA will be used in the // administrative phase and the GET DATA command will be used in the operational phase. // // Package AID: 'D2 76 00 00 60 50 04' // Applet AID: 'D2 76 00 00 60 41 04' // // 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.3, // 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' = user PIN, '02' = admin 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 // //--------------------------------------------------------------------------------------- // Implementation Notes // * no support of different DFs // * no secure messaging support (= class is alway '00') // * the adminstration phase (e.g. the personalisation of the smart card) is done with the // PUT DATA command. All external seeable data elements of the smart card can be written // with this ISO/IEC 7816-4 compatible command. // * The access condition for the administration phase is a VERIFY command with the // administration PIN. // * abbreviations: admin - administration // //--------------------------------------------------------------------------------------- // Algorithm for the TAN generation // key: smart card individual key // C: TAN counter // S: smart card individual seed value // // TANhex := DES (key) [C || S || padding according to ISO/IEC 9797 Method 1 (= 00 00 ...)] // TAN := ASCII-conversion of TANhex // //--------------------------------------------------------------------------------------- // Usage of this application in a System // // In the administrative phase all neccessary data are written into the smart card and parallel // also stored in a second system. Typical this could be a background system or a second smart card. // // In the operative phase the user sends his user PIN to the smart card and fetches a TAN with the // GET DATA command. A user PIN is neccessary because it prevents the usage of the smart card when lost. // In the second step the user uses the generated TAN for authentication in a second // system. For the authentication the following data are needed: Smart Card ID, TAN, TAN Number // // Main System // generate and store for each smart card in database: smart card ID, admin PIN, user PIN, seed value, TAN key, TAN counter // store above data in smart cards // // Smart Card // generate TAN, TAN number // send TAN, TAN number, smart card ID to Main System // // Main System // fetch seed value, TAN key and last TAN number from database with smart card ID // IF TAN Number is too much different to last TAN number THEN not authenticated and stop processing // calculate TAN' with smart card ID, seed value, TAN Number // compare TAN' with TAN // IF TAN'=TAN THEN authenticated ELSE not authenticated // store TAN number in DB //--------------------------------------------------------------------------------------- // Use Cases // note: all shown use cases are good cases // format: --command to smart card--> // <--response from smart card-- // // -----Administrative Phase // Personalisation // --VERIFY (with default Admin PIN)--> // <--ok-- // --PUT DATA (smart card ID, admin PIN, user PIN, seed value, TAN key, TAN counter)--> // <--ok-- // the following reset is important because of the reset of the validation status of the admin PIN // --Reset--> // <--ATR-- // // change user PIN (e.g. new user PIN, forgotten user PIN, error counter of the user PIN reached maximum value) // the following command is only neccessary if a card individual admin PIN is used // --GET DATA (smart card ID)--> // <--Smart Card ID-- // --VERIFY (with admin PIN)--> // <--ok-- // --PUT DATA (new user PIN)--> // <--ok-- // the following reset is important because of the reset of the validation status of the admin PIN // --Reset--> // <--ATR-- // // forgotten admin PIN, error counter of the admin PIN reached maximum value // new smart card neccessary // // -----Operative Phase // get smart card ID // --GET DATA (smart card ID)--> // <--smart card ID-- // // get a TAN // --VERIFY (with user PIN)--> // <--ok-- // --GET DATA (TAN)--> // <--TAN, sequence number-- // //--------------------------------------------------------------------------------------- // Test Strategy // good case tests: // 1.1 test all use cases in good case // 2.1 write all data objects and verify if they are stored correct // 2.2 do the error counters count in a correct way? // 3.1 Test all marked "Test!" testpoints // 4.1 correct counting of the TAN counter? // // bad case tests: // 1.1 test correct and wrong admin PIN incl. error counter behaviour // 1.2 is PUT DATA only possible after successful admin PIN verification? // 2.1 test case 1.1 with user PIN // 2.2 TAN generation blocked if user PIN error counter reaches its max value? // //--------------------------------------------------------------------------------------- // 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 2004 by Wolfgang Rankl, Munich //--------------------------------------------------------------------------------------- // 9. May 2004 - V 1: initial runnable version // 12. May 2004 - V 2: improved documentation, 1st published version //--------------------------------------------------------------------------------------- package packtangen; // this is the package name import javacard.framework.*; // import all neccessary packages for the java card framework import javacard.security.*; // import all neccessary packages for the java card security public class TANGen extends Applet { // definitions for the classes and 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 // definitions for specific returncodes 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 some data elements static short tancntr; // TAN counter of the generator final static short LEN_TANCNTR = (short) 2; // size of the TAN counter in byte static byte[] scid; // the smart card ID final static short LEN_SCID = (short) 4; // size of the unique smart card identifier in byte static byte[] workarray; // workarray for various operations final static short LEN_WORKARRAY = (short) 20; // size of the workarray static byte[] seed; // seed value for the TAN generator final static short LEN_SEED = (short) 8; // size of the seed value static DESKey tangenkey; // TAN generation key final static short LEN_TANGENKEY = (short) 8; // size of the generation key, it is a 8 byte long DES key static Signature mac; // MAC (message authentication code) // definitions for the storage of the data elements final static short TAG_SCID = (short) 0x50; // tag for the smart card ID final static short TAG_USERPIN = (short) 0x51; // tag for the user PIN final static short TAG_ADMINPIN = (short) 0x52; // tag for the administrative PIN final static short TAG_SEED = (short) 0x54; // tag for the TAN generation seed value final static short TAG_TANGENKEY = (short) 0x55; // tag for the TAN generation key final static short TAG_TANCNTR = (short) 0x56; // tag for the TAN counter final static short TAG_TAN = (short) 0x57; // tag for the TAN // definitions for the TAN generation final static short INDEX_TANCNTR = (short) 0; // start position of the TAN counter for TAN generation final static short INDEX_SEED = (short) 2; // start position of the seed value for TAN generation final static short INDEX_MAC = (short) 10; // position of the MAC (= TAN in hex notation) for TAN generation final static short LEN_TAN = (short) 4; // length of the TAN in byte // constants and variables for the admin PIN management final static byte[] DEFAULT_ADMINPIN // default admin PIN value = {(byte) 0x22, (byte) 0x22, (byte) 0x22}; final static byte LEN_ADMINPIN = (byte) 3; // size of the PIN in byte final static byte DEFAULT_ADMINPIN_MAXEC = (byte) 2; // default value of the PIN error counter final static byte ADMINPIN_ID = (byte) 2; // PIN identifier, PIN 2 = admin PIN static OwnerPIN adminpin; // the PIN object // constants and variables for the user PIN management final static byte[] DEFAULT_USERPIN // default user PIN value = {(byte) 0x00, (byte) 0x00}; final static byte LEN_USERPIN = (byte) 2; // size of the PIN in byte final static byte DEFAULT_USERPIN_MAXEC = (byte) 3; // default value of the PIN error counter final static byte USERPIN_ID = (byte) 1; // PIN identifier, PIN 1 = user PIN static OwnerPIN userpin; // the PIN object //--------------------------------------------------------------------------------------- //----- installation and registration of the applet public static void install(byte[] buffer, short offset, byte length) { // create the object for the smart card ID scid = new byte[LEN_SCID]; // create the object for the seed value seed = new byte[LEN_SEED]; // create a multiple useable object in RAM with an array of bytes workarray = JCSystem.makeTransientByteArray(LEN_WORKARRAY, JCSystem.CLEAR_ON_DESELECT); // Test! // create the object for admin PIN and set it to the default value adminpin = new OwnerPIN(DEFAULT_ADMINPIN_MAXEC, LEN_ADMINPIN); adminpin.update(DEFAULT_ADMINPIN, (short)(0), LEN_ADMINPIN); // create the object for user PIN and set it to the default value userpin = new OwnerPIN(DEFAULT_USERPIN_MAXEC, LEN_USERPIN); userpin.update(DEFAULT_USERPIN, (short)(0), LEN_USERPIN); // create the key object for TAN generation tangenkey = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false); // create a signature object for MAC calculating // padding in line with ISO/IEC 9797 Method 1 (= 000 ...) mac = Signature.getInstance(Signature.ALG_DES_MAC4_ISO9797_M1, false); // register the application within the JCRE new TANGen().register(); } // install //--------------------------------------------------------------------------------------- //----- this is the command dispatcher public void process(APDU apdu) { byte[] cmd_apdu = apdu.getBuffer(); // delete global used variables, for better robustness Util.arrayFillNonAtomic(workarray, (short) 0, LEN_WORKARRAY, (byte) 0); if (cmd_apdu[ISO7816.OFFSET_CLA] == ISO7816.CLA_ISO7816) { //----- it is the ISO/IEC 7816 class switch(cmd_apdu[ISO7816.OFFSET_INS]) { 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 { // 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] == USERPIN_ID) { // it is the user PIN if (lc != LEN_USERPIN) { // Lc = length of PIN ? ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } //if if (userpin.check(cmd_apdu, ISO7816.OFFSET_CDATA, LEN_USERPIN) == false) { // PIN verification not successful short tries = userpin.getTriesRemaining(); ISOException.throwIt( (short) (SW_PIN_FAILED + tries)); // send error counter in APDU back } // if } // if else if (cmd_apdu[ISO7816.OFFSET_P2] == ADMINPIN_ID) { // it is the administrative PIN if (lc != LEN_ADMINPIN) { // Lc = length of PIN ? ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } //if if (adminpin.check(cmd_apdu, ISO7816.OFFSET_CDATA, LEN_ADMINPIN) == false) { // PIN verification not successful short tries = adminpin.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 (command length) receiveAPDUBody(apdu); // get the command body //----- check precoditions of security status if (adminpin.isValidated() == false) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } // if //----- command functionality switch(tag) { case TAG_USERPIN: // the user PIN must be changed if (lc != LEN_USERPIN) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); Util.arrayCopy(cmd_apdu, (short)((ISO7816.OFFSET_CDATA) & 0x00FF), workarray, (short) 0, lc); userpin.update(workarray, (short) 0, LEN_USERPIN); break; case TAG_ADMINPIN: // the admin PIN must be changed if (lc != LEN_ADMINPIN) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); Util.arrayCopy(cmd_apdu, (short)((ISO7816.OFFSET_CDATA) & 0x00FF), workarray, (short) 0, lc); adminpin.update(workarray, (short) 0, LEN_ADMINPIN); break; case TAG_TANGENKEY: // the key for the authentication generator must be changed if (lc != LEN_TANGENKEY) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); Util.arrayCopy(cmd_apdu, (short)((ISO7816.OFFSET_CDATA) & 0x00FF), workarray, (short) 0, lc); tangenkey.setKey(workarray, (short)0); break; case TAG_TANCNTR: // the TAN counter must be changed if (lc != LEN_TANCNTR) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); Util.arrayCopy(cmd_apdu, (short)((ISO7816.OFFSET_CDATA) & 0x00FF), workarray, (short) 0, lc); if (Util.getShort(workarray, (short)0) > 0x7FFF) ISOException.throwIt(ISO7816.SW_WRONG_DATA); tancntr = Util.getShort(workarray, (short)0); break; case TAG_SEED: // the seed value must be changed if (lc != LEN_SEED) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); Util.arrayCopy(cmd_apdu, (short)((ISO7816.OFFSET_CDATA) & 0x00FF), seed, (short) 0, lc); break; case TAG_SCID: // the smart card ID must be changed if (lc != LEN_SCID) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); Util.arrayCopy(cmd_apdu, (short)((ISO7816.OFFSET_CDATA) & 0x00FF), scid, (short) 0, lc); break; default : // the tag in the command apdu is not supported ISOException.throwIt(SW_DATA_NOT_FOUND); } // switch //----- prepare response APDU ISOException.throwIt(ISO7816.SW_NO_ERROR); // command proper executed } // 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) //----- command functionality switch(tag) { // a switch construct is used because of the easy extensibility for additional tags case TAG_SCID: // get the smart card ID if (le != LEN_SCID) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); Util.arrayCopy(scid, (short) (0), workarray, (short) 0, le); break; case TAG_TANCNTR: // get the TAN counter if (le != LEN_TANCNTR) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); Util.setShort(workarray, (short) (0), tancntr); break; case TAG_TAN: // get a new TAN if (le != LEN_TAN) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); //----- check security preconditions if (userpin.isValidated() == false) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } // if //----- check functional preconditions if (tancntr >= 0x7FFF) ISOException.throwIt(ISO7816.SW_DATA_INVALID); generateTAN(); //----- rearrange the data for the response Util.arrayCopy(workarray, (short) (INDEX_MAC), workarray, (short) (0), LEN_TAN); break; default : // the tag in the command apdu could not found ISOException.throwIt(SW_DATA_NOT_FOUND); } // switch //----- 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(workarray, (short) 0, (short)le); // send the requested the number of bytes to the IFD, data field of the DO } // cmdGETDATA //--------------------------------------------------------------------------------------- //----- 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 (command 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 //----- generates a TAN public void generateTAN() { //----- collect all data for the TAN Util.setShort(workarray, INDEX_TANCNTR, tancntr); // TAN counter Util.arrayCopy(seed, (short) (0), workarray, INDEX_SEED, LEN_SEED); // seed value //----- calculate the MAC mac.init(tangenkey, Signature.MODE_SIGN); short len = mac.sign(workarray, (short) 0, LEN_TAN, workarray, INDEX_MAC); // update the TAN counter tancntr = (short) (tancntr + (short) 1); } // generateTAN } // class