JC101-10C: Adding a password and state management

In the last tutorial entry, we have seen in theory how it is possible to add a password and to manage an application’s state. In the present entry, we will actually add support for a password in the application. This support will be provided using the OwnerPIN class, which goes beyond simple PIN codes, and is actually able to check any secret, including passwords. We will see how to do this step-by-step.

Adding a password

In order to add a password, we need to define at least two commands: one that will verify that a password provided by the user is equal to the reference password, and one that will change the value of the reference password. We therefore need to define two instruction bytes:

  /** INS byte for the command that verifies the PIN
   * (from ISO7816-4) */
  public final static byte INS_VERIFY = (byte) 0x20 ;
  
  /** INS byte for the command that changes the PIN
   * (from ISO7816-4) */
  public final static byte
      INS_CHANGE_REFERENCE_DATA = (byte) 0x24 ;

As you can see in the comments, these instruction bytes are actually not invented, but taken from the ISO7816-4 standard. The VERIFY command is a rather old one, and it is easy to figure out how to implement it (for instance, here). The CHANGE REFERENCE DATA command, on the other hand, has been introduced in the latest release of the standard, and its definition is a bit more fuzzy. A Google Code Search provided me with a GnuPG implementation that shows that they are actually encoding directly the values in the APDU, without any kind of encapsulation. This can work out for systems that use fixed length passwords, but we don’t want to do that, so we will need to encode the length in that command (and forget about ISO compliance).

OK. Enough of that. Let’s get back to code. We of course will need a field that holds our OwnerPIN object:

  private OwnerPIN pin ;

A PIN also needs to be instantiated. In order to do so, we need to know the PIN try limit and the PIN maximum size. The number that we have put there are not very realistic, and I would actually recommend to go beyond 3 tries, in particular if we allow 16-character passwords:

  /** PIN try limit */
  public final static byte PIN_TRY_LIMIT = (byte) 3 ;
  
  /** PIN Maximum size */
  public final static byte PIN_MAX_SIZE = (byte) 16 ;

The instantiation simply consists in invoking the appropriate constructor:

  /**
   * The default constructor. Because of the structure of the list, there
   * is no need for any initialization for the main structure. The PIN object
   * needs to be initialized, though.
   */
  private PasswordManager()
  {
    pin = new OwnerPIN(PIN_TRY_LIMIT, PIN_MAX_SIZE) ;
  }

Finally, we have stated as a requirement that the password be checked at every session. If we consider that a session is an application session (between selection and deselection), we will then have a small issue: the flag that indicates that an OwnerPIN has been correctly presented is stored in a transient boolean array of CLEAR_ON_RESET duration. This means that we will need to reset it explicitly at selection or deselection time (it does not matter whether we do it at selection or deselection, because we just want to do it once between two selections). Let’s assume that we will do it at deselection time. We will then need to define a deselect method:

  /**
   * Resets the PIN's validated flag upon deselection of the applet.
   */
  public void deselect()
  {
    pin.reset();
  }

The effect of the reset method simply is to reset the validated flag, without affecting the number of remaining tries

Verifying a password

The next step is to implement the VERIFY command. This command is very simple. The ISO specification mandates a value for P1 (0), and allows some values for P2 (we have selected 0x80). If there is no incoming data, then the command should simply provide some information about the number of remaining tries. Finally, the proposed password value is stored directly in the data field. The implementation is therefore rather simple. The method starts with the usual APDU recovery:

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

The next step consists in checking the command parameters:

    // INITIAL CHECKS
    // Expect P1=00 and P2=80
    if (Util.getShort(buf,ISO7816.OFFSET_P1)!=0x80)
      ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);    

Next, we need to check whether or not the PIN is blocked, in order to ensure that no verification can occur on an already blocked PIN:

    // First verifies that the PIN is not blocked
    if (pin.getTriesRemaining()==0)
      ISOException.throwIt(ISO7816. SW_DATA_INVALID);

The next case covers the case in which information needs to be returned. It is not that simple, because we need to distinguish between three cases: (1) the PIN has been correctly presented, (2) the PIN is blocked (no more tries remaining), and (3) the PIN has not been correctly presented in the session (but there are remaining tries).

    // If no input data, simply return the number of remaining tries
    if (buf[ISO7816.OFFSET_LC]==0)
    {
      if (pin.isValidated())
        return ;
      else
        ISOException.throwIt((short)(SW_WRONG_PIN+pin.getTriesRemaining())) ;
    }

The last case is the main one, in which we simply receive the incoming data and verify that it matches the reference password. The code is made very simple by the use of a helper method:

    // Receives data and checks it
    short len = apdu.setIncomingAndReceive() ;
    verifyPIN(buf,ISO7816.OFFSET_CDATA,(byte)len) ;

The helper method is used to factor out code that is actually used in the processing of several commands (we will reuse it in the processing of CHANGE REFERENCE DATA). It is itself quite simple:

  void verifyPIN(byte[] buffer, short index, byte len)
  {
    if (len > PIN_MAX_SIZE)
      ISOException.throwIt(ISO7816.SW_WRONG_DATA);
    if (!pin.check(buffer,index,len))
    {
      if (pin.getTriesRemaining()==0)
        ISOException.throwIt(ISO7816. SW_DATA_INVALID);
      else
      ISOException.throwIt((short)(SW_WRONG_PIN+pin.getTriesRemaining())) ;
    }
  }

!!! You may have noticed that I have been using void methods to perform checks. The idea is here that the checks are entirely contained in the method, and that exceptions will be used to signal issues. This is particularly useful when the checks performed lead to command failure, because the ISOException that is thrown does not even have to be caught.

Initializing a password

The second command that we need to implement is the one that initializes a password. There are actually two ways to use it:

  • If the password has not yet been initialized, then the password change command only needs to include the new password value.
  • If the password has already been initialized, then the password change command needs to include both the current password value (for verification) and the new password value.

These two cases are actually defined in the ISO7816-4(2005) standard, so we simply follow it. Our main deviation from the standard (I think) will come from the fact that we will use LV records for the password values instead of straight values. Another deviation from the standard is that, in the case where the password is first initialized, the state of the application will be set to PERSONALIZED, indicating that the application has now been personalized.

The method starts with traditional things, including a check that P2’s value is 0x80, a valid value:

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

    // INITIAL CHECKS
    
    // Checks the value of P2
    if (buf[ISO7816.OFFSET_P2] != 0x80)
      ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);    

Checking the value of P1 is more complex, because there are two possible uses of the command, and each one is associated to an application state (SELECTABLE for P1=0, and PERSONALIZED for P1=1):

    byte p1 = buf[ISO7816.OFFSET_P1] ;
    switch(p1)
    {
    case 0:
      if (GPSystem.getCardContentState() !=
              GPSystem.APPLICATION_SELECTABLE)
        ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);    
      break ;
    case 1:
      if (GPSystem.getCardContentState() !=
              APPLICATION_PERSONALIZED)
        ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);    
      break ;
    default:
      ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);    
    }

The next step is to receive the incoming data, and check the length of received data:

    // Receives data and checks it
    short len = apdu.setIncomingAndReceive() ;
    if (len<2)
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);    

    short index=ISO7816.OFFSET_CDATA ;

Next, we cover the case in which there is a password to verify before to update the value. Note that we here use the same verifyPIN method that we have use in the handling of the VERIFY command:

    // If there is a PIN presentation, checks it
    if (p1==0)
    {
      byte oldPinLen = buf[index++] ;
      if (len<(short)(oldPinLen+3))
        ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);    
      verifyPIN(buf,index,oldPinLen) ;
      index += oldPinLen ;
    }

Next, we handle the new value. We verify that its length corresponds to the declared length, and that it is not too large to fit in the OwnerPIN object that we have allocated. If all conditions are met, the password value is updated:

    // In all cases, checks the new PIN value
    byte newPinLen = buf[index++] ;
    if (len!=(short)(index+(short)(newPinLen-ISO7816.OFFSET_CDATA)))
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    if (newPinLen > PIN_MAX_SIZE)
      ISOException.throwIt(ISO7816.SW_WRONG_DATA);

    // If everything is OK, update the PIN value
    pin.update(buf,index,newPinLen) ;

The final step in the method is to update the state if everything went fine and it is the first time that we set a password value:

    // If everything went fine, update the state
    if (p1==0)
      GPSystem.setCardContentState(APPLICATION_PERSONALIZED);
  }

We have not used a Java Card transaction in this method, because there is no need for it. The last update (of the application state) is guaranteed to be atomic, and there is no need to link it to the actual update of the password. If power is lost during the state change, the application will revert to its previous state, which means that the password will need to be initialized a second time.

Checking authentication

The last part is to update the handling of other commands in order to take into account the authentication. There are in fact two things to do:

  • First, modify the command dispatch in order to allow only the CHANGE REFERENCE DATA command in the SELECTABLE state, and to allow all commands in the PERSONALIZED state.
  • Then, modify the command dispatch in order to verify that all commands except VERIFY and CHANGE REFERENCE DATA can only run after a successful authentication in the session.

Both changes can be included in the process method. I like to do it by first doing a "switch" on the current state, and then another "switch" on the instruction byte. The advantage of this approach is that it makes it easier to enforce the restrictions that are specific to a given application state, and it makes the list of instructions allowed in a given state really obvious.

The beginning of the process method does not change until the switch statement, which is now performed on the current application state, as managed by GlobalPlatform:

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

    byte[] buf = apdu.getBuffer();
    // First, switches on the application's state,
    // as managed by GlobalPlatform
    switch(GPSystem.getCardContentState())
    {

The first case corresponds to the SELECTABLE state, which is fairly easy, since there is only one command supported in that state. There is nothing really specific to do there, except to process that command, and to reject all the other ones:

    // In the SELECTABLE state,
    // the application is not personalized yet
    case GPSystem.APPLICATION_SELECTABLE:
      switch (buf[ISO7816.OFFSET_INS]) {
      case (byte) INS_CHANGE_REFERENCE_DATA:
        processChangeReferenceData();
        break;
      default:
        // If you don't know the INStruction, say so:
        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
      }
      break ;

The second case is more complex. In particular, it covers the specific case of resetting the current variable used by the LIST IDENTIFIERS instruction:

    case APPLICATION_PERSONALIZED:
      //Resets the identifier list's current reference if required
      if (buf[ISO7816.OFFSET_INS] != INS_LIST_IDENTIFIERS)
        current[0] = null ;

The next switch statement includes all instructions, since they are all valid in PERSONALIZED mode. However, the handling of each instruction has been modified in order to include an authentication check:

      switch (buf[ISO7816.OFFSET_INS]) {
      case (byte) INS_ADD_PASSWORD_ENTRY:
        checkAuthentication();
        processAddPasswordEntry();
        break;
      case (byte) INS_RETRIEVE_PASSWORD_ENTRY:
        checkAuthentication();
        processRetrievePasswordEntry();
        break;
      case (byte) INS_DELETE_PASSWORD_ENTRY:
        checkAuthentication();
        processDeletePasswordEntry();
        break;
      case (byte) INS_LIST_IDENTIFIERS:
        checkAuthentication();
        processListIdentifiers();
        break;

Of course, we have also added the handling of the newly defined instructions, which are used to manage the password. The switch ends with the usual "instruction unknown" check, and we also need to have a default case for the state switch, which of course rejects all commands in an unknown state:

      case (byte) INS_VERIFY:
        processVerify();
        break;
      case (byte) INS_CHANGE_REFERENCE_DATA:
        processChangeReferenceData();
        break;
      default:
        // good practice: If you don't know the INStruction, say so:
        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
      }
    default:
      ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
    }

Finally, we can take a look at the checkAuthentication method. It is built in our usual model, which throws an ISO exception when something goes wrong. Here, the check simply consists in verifying the PIN's validated flag:

  void checkAuthentication()
  {
    if (!pin.isValidated())
      ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
  }

This piece of code closes this part of the tutorial. Our application now includes some security countermeasures against illegal accesses by unauthenticated users. If we combine that with the fact that we have been very careful to cover all possible cases, there is hope that this application is well protected against logical attacks that use legal software (basically, attacks based on sending APDUs to the application). Next, we will have a look at the other kinds of attacks, which use different means to get access to the application.

One Comment

  • Wolfgang wrote:

    Hey, first of all: awesome tutorial!

    I think I found a failure, in the processChangeReferenceData() method you check the value of P1 instead of P2 at the beginning of the method:

    // Checks the value of P2
    if (buf[ISO7816.OFFSET_P1] != 0x80)
    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);

    it probably should be:

    // Checks the value of P2
    if (buf[ISO7816.OFFSET_P2] != 0x80)
    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);

    thank you for your excellent tutorial and all the work you have put in this!

Leave a Reply

Your email is never shared.Required fields are marked *