JC101-7C: Processing APDU’s (1/2)

In the last post, we have defined a specification for an APDU-based application. It is now time to implement this application, i.e., to start processing a few APDU’s. We’ll do that in the style that characterizes this tutorial, which is a mix of simple code with a few basic optimizations.

Our application is a simple one, so we will only include a minimal set of methods. In order to define a Java Card applet, two methods are required:

  • An installation method. This static method is called during the application application process, and it is supposed to instantiate and initialize the application and all required objects.
  • A command processing method. This method is supposed to handle all incoming APDU commands.

All other methods (like the ones invoked upon application selection and deselection) have a default definition that does nothing and perfectly suits the current application.

Applet installation

The installation of an applet typically consists of two operations: allocating an instance of an applet object, and registering it to the JCRE. The typical code for doing this is as follows:

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

This code just does exactly what I described. It uses as registration AID the AID that was sent in the Install [for install] command, assuming that the application is being installed in a GlobalPlatform-compliant card. The constructor for the PasswordManager class is just as simple:

  private PasswordManager()
  { }

It simply reminds us that, with our current implementation of the password repository, there is nothing to do upon installation. This closes the installation part. Of course, it is not always that simple, and we will get back to this in the following tutorial entries.

Command processing

The next step is to process the commands. This is of course a bit more complicated, as there is real work to be done. The first step is simple, and consists in defining the constants that are used in our application’s communication protocol. We can start with the commands:

  /** INS for command that adds a password entry */
  public final static byte
      INS_ADD_PASSWORD_ENTRY = (byte) 0x30 ;
  
  /** INS for command that retrieves a password entry */
  public final static byte
      INS_RETRIEVE_PASSWORD_ENTRY = (byte) 0x32 ;
  
  /** INS for command that deletes a password entry */
  public final static byte
      INS_DELETE_PASSWORD_ENTRY = (byte) 0x34 ;
  
  /** INS for command that lists all defined identifiers */
  public final static byte
      INS_LIST_IDENTIFIERS = (byte) 0x36 ;

We then have the possible status words. Note that they are not all defined, because many of them are defined in the ISO7816 class:

  /** Status word for a duplicate identifier */
  public final static short
      SW_DUPLICATE_IDENTIFIER = (short) 0x6A8A ;
  
  /** Status word for a failed allocation */
  public final static short
      SW_NOT_ENOUGH_MEMORY = (short) 0x6A84 ;

The final ste of constants are related the tags used to structure data:

  /** Tag byte for identifiers */
  public final static byte TAG_IDENTIFIER = (byte) 0xF1 ;
  
  /** Tag byte for user name records */
  public final static byte TAG_USERNAME = (byte) 0xF2 ;
  
  /** Tag byte for password records */
  public final static byte TAG_PASSWORD = (byte) 0xF3 ;

After defining the constants, we then get to the command processing method itself. The start of the method is very classical, and we first answer “OK” to the selection APDU command. Then, we get the reference to the APDU buffer and switch on the INS byte (still very classical). And the nice finishing touch is the default case, which rejects all unrecognized INS bytes.

  public void process(APDU apdu) {
    // Nothing particular to do on SELECT
    if (selectingApplet()) {
      return;
    }

    byte[] buf = apdu.getBuffer();
    switch (buf[ISO7816.OFFSET_INS]) {
    case (byte) INS_ADD_PASSWORD_ENTRY:
      processAddPasswordEntry();
      break;
    case (byte) INS_RETRIEVE_PASSWORD_ENTRY:
      processRetrievePasswordEntry();
      break;
    case (byte) INS_DELETE_PASSWORD_ENTRY:
      processDeletePasswordEntry();
      break;
    case (byte) INS_LIST_IDENTIFIERS:
      processListIdentifiers();
      break;
    default:
      // good practice: If you don't know the INS, say so:
      ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
    }
  }

Before we dive into the processing of commands, we need to look at a small utility method, which will be used to parse TLV’s. The method simply starts at a given offset, identifies a given TLV, and then computes and returns the offset at which the enxt TLV is expected to start. It also throws exceptions when it encounters unexpected things:

  short checkTLV(byte[] buffer, short inOfs,
                 byte tag, short maxLen)
  {    
    if (buffer[inOfs++] != tag)
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
    short len = buffer[inOfs++] ;
    if (len > maxLen)
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
    return (short)(inOfs + len) ;
  }

Let’s now look at the code for the first method, which adds a new password entry to the list. I have chosen in the process method to use a method without arguments, which means that the first thing that the method needs to do is to retrieve its environment:

  void processAddPasswordEntry()
  {
    // Retrieves references to the APDU and its buffer
    APDU apdu = APDU.getCurrentAPDU() ;
    byte[] buf = APDU.getCurrentAPDUBuffer() ;

I don’t necessarily recommend this practice, but I really like it, because I am convinced that these two small method calls cost almost nothing in time, only 3 bytes of code each, and that they greatly improve the readability of programs. But once again, this is a personal preference.

The next step is far more important, and it goes beyond personal preference. Before we accept a command, we must verify that it satisfies the specification, which is done below:

    // Checks the value of P1&P2
    if (Util.getShort(buf, ISO7816.OFFSET_P1)!=0)
      ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
    // Checks the minimum length
    if ((short)(buf[ISO7816.OFFSET_LC]&0xFF) < 3)
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
    // Receives data and checks its length
    if (apdu.setIncomingAndReceive() !=
              (short)(buf[ISO7816.OFFSET_LC]&0xFF))
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

This all looks very basic, but forgetting these simple checks is a good way to allow attackers in your application. Also, don't forget that, if your application should be able to accept commands with more than 128 incoming bytes of data, you need to remove the "sign" from the LC byte by using the formula (short)(buf[ISO7816.OFFSET_LC]&0xFF). Not good for readability, but essential for reliability.

One may ask the question: Why check that the length of the data is at least 3? The reason is here to ensure that we will receive at least one complete TLV with a non-null value. We can therefore start parsing while being sure that we are actually parsing data that we have actually received.

The next step is to continue the checking work by parsing the arguments, which are repsented as TLV records. We therefore use the method that we have defined previously. Here is the code that checks the first argument (the identifier):

    // Checks the identifier
    short ofsId = ISO7816.OFFSET_CDATA ;
    short ofsUserName = checkTLV(
        buf, ofsId, TAG_IDENTIFIER, PasswordEntry.SIZE_ID) ;
    if (buf[ISO7816.OFFSET_LC] < (short)(ofsUserName - 3))
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);

The first check verifies that we have a correct identifier TLV, and it returns the offset to the next entry, the user name. The second check verifies that there is enough room for the user name in the incoming data.

The parsing of the incoming data continues with the rest of the arguments:

    // Checks the user name
    short ofsPassword = checkTLV(buf, ofsUserName,
        TAG_USERNAME, PasswordEntry.SIZE_USERNAME) ;
    if (buf[ISO7816.OFFSET_LC] < (short)(ofsPassword - 3))
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
      
    // Checks the password
    if (checkTLV(buf, ofsPassword, TAG_PASSWORD,
                  PasswordEntry.SIZE_PASSWORD) !=
        (short)(ISO7816.OFFSET_CDATA +
            (short)(buf[ISO7816.OFFSET_LC]&0xFF)))
      ISOException.throwIt(ISO7816.SW_DATA_INVALID) ;

The last check is slightly different, since it verifies here that we have parsed exactly the amount of bytes that we have received. Once again, this check is important, because it is input validation for Java Card, and input validation is good for security.

Once the entry has been parsed, the next step is to verify that there exists no other entry with the same identifier in the database:

    // Search the identifier in the current base
    if (PasswordEntry.search(
            buf, (short)(ofsId+2), buf[ofsId+1]) != null)
      ISOException.throwIt(SW_DUPLICATE_IDENTIFIER) ;

And finally, we need to allocate and initialize a password entry:

    // Allocates and initializes a password entry
    JCSystem.beginTransaction();
    PasswordEntry pe = PasswordEntry.getInstance();
    pe.setId(buf, (short)(ofsId+2), buf[ofsId+1]);
    pe.setUserName(buf, (short)(ofsUserName+2),
                   buf[ofsUserName+1]);
    pe.setPassword(buf, (short)(ofsPassword+2),
                   buf[ofsPassword+1]);
    JCSystem.commitTransaction();

This operation is done in a transaction in order to ensure that, if a password entry is not compltely initialized, it is "garbage collected". This actually means that the library code for the PasswordEntry initialization needs to be modified, in order to remove the transaction that was initially included in the getInstance method.

The change is here required because of a Java Card 2 issue. It is not possible in Java Card 2 to have nested transactions, or to notify that a method needs to be performed in a transaction. Because of that, one has to be particularly careful about where transactions start and end. As a side note, this issue has been resolved in Java Card 3, and I will cover it in the near future.

This remark concludes the method that creates a new entry, and also this entry of the tutorial. The other methods will be covered in the next tutorial post. If you can't wait and want to see the code, you can find it on the jc2tutorial SVN repository. And to conclude this long post, let's discuss a stupid optimization.

Stupid optimization

In the length check, I have mentioned that you need to do some special handling "if you want to accept commands with more than 128 bytes of incoming data". Well, it's not the case of the commands here, so we can optimize the length checks as follows:

    // Checks the value of P1&P2
    if (Util.getShort(buf, ISO7816.OFFSET_P1)!=0)
      ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
    // Checks the minimum length
    if (buf[ISO7816.OFFSET_LC] < 3)
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
    // Receives data and checks its length
    if (apdu.setIncomingAndReceive() !=
        buf[ISO7816.OFFSET_LC])
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

Be careful, though! This stupid trick only works because there is a minimum length check, which also happens to refuse all negative lengths (since 0x80, whose signed value is -128, happens to be smaller than 3). If you ever do this on a real application, don't forget to include a nice comment, or it will look like a big bad bug.

No Comments

Leave a Reply

Your email is never shared.Required fields are marked *