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

In the previous tutorial entry, we have looked at APDU processing, with an initial focus on processing a first command with incoming data. We will here look at the next commands, focusing on the ones that return data.

A first command with outgoing data

The first such method is the one that returns the password entry associated to a given identifier. It starts like the previous method, by retrieving some references and by performing initial checks on the incoming command:

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

    // INITIAL CHECKS
    
    // 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);

The next step is to interpret the incoming data, after checking that it is correct. The checks are the same ones as for the previous command:

    // INTERPRETS AND CHECKS THE DATA
    
    // Checks the identifier
    if (checkTLV(buf, ISO7816.OFFSET_CDATA,
        TAG_IDENTIFIER, PasswordEntry.SIZE_ID) !=
        (short)(ISO7816.OFFSET_CDATA +
          (short)(buf[ISO7816.OFFSET_LC]&0xFF)))
      ISOException.throwIt(ISO7816.SW_DATA_INVALID) ;

Of course, this time, if we can find an entry that corresponds to the provided identifier, this is considered as good news, and an exception is returned if the identifier cannot be found:

    // Search the identifier in the current base
    PasswordEntry pe = PasswordEntry.search(buf,
         (short)(ISO7816.OFFSET_CDATA+2),
         buf[ISO7816.OFFSET_CDATA+1]) ;
    if (pe==null)
      ISOException.throwIt(SW_IDENTIFIER_NOT_FOUND) ;

At this point we can start preparing the outgoing data, i.e., the result of the command. The data is prepared in the APDU buffer; it starts at offset 0, since there is no particular need to keep the APDU command header and data at this point. The first part is the TLV for the username:

    // Builds the result, starting with the user name
    short outOfs = 0 ;
    buf[outOfs++] = TAG_USERNAME ;
    byte len = pe.getUserName(buf,(short)(outOfs+1)) ;
    buf[outOfs++] = len ;
    outOfs += len ;

Next comes the password, which is added in its own TLV. When writing this code, it is important to keep track of the length of the data, which we will need to know in order to send the outgoing data. Traditionally, this is done by tracking the offset at which the data is written:

    // Builds the result, continuing with the password
    buf[outOfs++] = TAG_PASSWORD ;
    len = pe.getPassword(buf,(short)(outOfs+1)) ;
    buf[outOfs++] = len ;
    outOfs += len ;

The final part of the command handling is to send the outgoing data:

    // Sends the result
    apdu.setOutgoingAndSend((short)0, outOfs) ;

Outputting more than 256 bytes

The first command was rather simple, as it can only output a single record of limited length. The next one that we will see here is more complex, as it should be able to list all the identifiers registered in the application. Represented in a TLV format, it is only possible to fit 25 8-byte identifiers in a response APDU. The command must therefore take this into consideration, and include a way to provide a response over several APDU's.

The idea that is used in our application is fairly classical. A first command will be used to start the sequence of responses, and the subsequent commands will get the rest of the response. The control parameter used for this is P1, which will take value 0 to start the process, and 1 for continuing.

In order to implement this command, we need to loop through the password entries, and to keep track of the current one. This requires a specific variable:

  private PasswordEntry current ;

The method that output identifiers starts like the others, by retrieving the APDU:

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

The first check consists in verifying the value of P1, and of performing the appropriate initialization:

    // Checks P1 and initializes the "current" value
    if (buf[ISO7816.OFFSET_P1]==0)
      current = PasswordEntry.getFirst() ;
    else if ( (buf[ISO7816.OFFSET_P1]!=1) ||
               (current==null) )
      ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);

If P1 is 0, the current variable is initialized to the first password entry, and if its value is 1, the current variable is not modified, but it is expected not to be null. This means that we only expect a command with P1=1 after a command with P1=0. In all other cases, an exception is thrown.

There is no incoming data, so the processing starts immediately with the loop that builds the response. The idea is here to loop until either there is no more identifier to return (current != null), or until the APDU buffer cannot hold the next identifier (the check that leads to the break statement):

    // Builds the response
    short offset = 0 ;
    while(current != null)
    {
      // Checks that the identifier record fits in the APDU
      // WARNING: assumes a 256-byte APDU buffer
      byte len = current.getIdLength() ;
      if ((short)((short)(offset+len)+2) > 255)
        break ;

The rest of the loop simply copies the current identifier in the buffer, in a TLV record. The last part of the loop gets to the next record:

      // Copies the identifier in the buffer
      buf[offset++] = TAG_IDENTIFIER ;
      buf[offset++] = len ;
      current.getId(buf, offset) ;
      
      // Gest to the next record
      offset += len ;
      current = current.getNext() ;
    }

Like usual, the last part of the command is to send the outgoing data:

    apdu.setOutgoingAndSend((short)0, offset) ;

Important optimization

For the sake of readability, we have used a "normal" field to hold the current value in the previous example. However, such fields are persistent and stored in EEPROM. This causes two potential problems when a variable is used very often (like the current field, using as a loop index):

  • First, there is a performance issue, since persistent updates can be slow, and accumulating them can lead to significant performance issues
  • Then, there is a reliability issue. The issue is not as important today as it once was, but persistent memory has a limited life, and it is not possible to endlessly write at the same memory location.

Even though these problems are not all that likely, it still remains recommended to use a non-persistent variable to hold commonly updated variables like loop indexes. The problem in our case is that we cannot use a local variable, because it must be used across several APDUs. We must therefore use a transient variable, which cannot be declared in a standard way. Instead, we need to allocate a transient array:

  private Object[] current =
    JCSystem.makeTransientObjectArray((short)1,
        JCSystem.CLEAR_ON_DESELECT); 

A transient array is an array that exists persistently, but whose content is not persistent (in practical terms, the array's descriptor is stored in EEPROM, but its content is stored in RAM). The array that is allocated here is declared CLEAR_ON_DESELECT, which means that its content is cleared every time that an applet is deselected. It is also possible to declare a transient array CLEAR_ON_RESET, in which case its content is only cleared when the card is reset.

In our case, the CLEAR_ON_DESELECT choice is obvious, as the current variable has a very short lifespan (a few commands). There is no need for this variable to be declared CLEAR_ON_RESET. This distinction may seem useless, but there is a significant difference between the two kinds of transient data: if the transient data's lifecycle is associated to the selection of an application, it then means that, when application A is deselected and application B is selected, A's transient data can be removed from memory and replaced by B's transient data. Transient data of CLEAR_ON_DESELECT duration can therefore be overlaid. It is not the case of data that must remain available throughout a session, regardless of applet selection; such data must be permanently allocated, whether or not the application it owns is selected. Transient data of CLEAR_ON_RESET duration can therefore not be overlaid.

In terms of usage, transient data is fairly easy to use. The main problem is here that transient data are always arrays, and that all transient reference arrays are Object arrays. This means that some casting will be required. As an example, let's consider the modified loop for our method:

    while(current[0] != null)
    {
      // Checks that the identifier record fits in the APDU
      // WARNING: assumes a 256-byte APDU buffer
      byte len = ((PasswordEntry)current[0]).getIdLength() ;
      if ((short)((short)(offset+len)+2) > 255)
        break ;
      
      // Copies the identifier in the buffer
      buf[offset++] = TAG_IDENTIFIER ;
      buf[offset++] = len ;
      ((PasswordEntry)current[0]).getId(buf, offset) ;
      
      // Gets to the next record
      offset += len ;
      current[0] = ((PasswordEntry)current[0]).getNext() ;
    }

Once again, the heavy casting made mandatory by the use of a transient array is not good for the code's readability. We will therefore introduce this updated code only in the next version of the application, together with the first security measures.

Other modification

Another possible modification would be to reset the current variable when the sequence of commands to list identifiers is not correct. the idea is here to ensure that no command can be inserted between two commands that list identifiers. This sounds trivial, but it isn't, as it may lead to functional and security issues. In our case, if the current password entry is deleted, we may end up listing deleted entries, which may not be such a good idea.

The changes to be performed are really small. The first one is to insert at the beginning of the process method a test that resets the current variable if the current command is not a LIST IDENTIFIERS command:

      //Resets the id list current reference if required
      if (buf[ISO7816.OFFSET_INS] != INS_LIST_IDENTIFIERS)
        current[0] = null ;

The second change is to insert a reset of the same variable when a LIST IDENTIFIERS command is rejected:

    // Checks P1 and initializes the "current" value
    if (buf[ISO7816.OFFSET_P1]==0)
      current[0] = PasswordEntry.getFirst() ;
    else if ( (buf[ISO7816.OFFSET_P1]!=1) ||
               (current[0]==null))
    {
      current[0] = null ;
      ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
    }

Just like the previous remark, the changes in the program will only be included on the next version of the application in the repository.

No Comments

Leave a Reply

Your email is never shared.Required fields are marked *