JC101-20C: A secure channel API

Continuing our secure channel example, we will next define a secure channel API, and provide a small example based on this API.

Beware! As mentioned before, this is only an example, not intended for real use. In addition, the code has not been actually tested so far …

If our secure channel is to be used by several applications, it should be defined in a shared library.

Definition of the API

The first step is here to define the API. The objective of such an API is to bring as little disruption as possible to the applications that use it. We will base our API on a pattern that is inspired from the one used in the GlobalPlatform API.

The main API for applications here contains four methods, as indicated below:

  public short unwrap(APDU apdu);

  public void wrapAndSend(
      APDU apdu,
      byte[] buffer,
      short outOffset,
      short outLen,
      short sw );

  public void processSecurity(APDU apdu);

  public boolean isCommandProtected();

The behavior of these methods is as follows:

  • The unwrap() method removes the protection of a method, after verifying its authenticity. It also works for unprotected commands, in which case it does nothing.
  • The wrapAndSend() method adds the protection around the response and sends it back. It also work for unprotected commands, in which case it simply sends the response. Note that the response is complete and also includes the status word.
  • The processSecurity() method is used to process the commands related to the secure channel protocol. The idea is here to send to it all the commands that the application does not know, and this method will throw the appropriate exception for unknown instructions.
  • The isSessionOpen() is used to determine whether or not a session is currently open.
  • Finally, the is CommandProtected() method is used to determine whether or not the current command is protected. This method must be called during the handling of commands that require some protection.

This API is quite simple to use, and it does not change significantly the way in which programs should be written. We also have defined a constructor and a few management methods, which are shown below:

  public SecureChannel();
  public void setSharedSecret(byte[] buffer, short ofs);
  public void closeSession();
  public void clear()

The explanations for these methods are:

  • The SecureChannel() constructor has no arguments and simply allocates the data required.
  • The setSharedSecret() method is used to initialize the shared secret and make the instance ready to be used.
  • The closeSession() method is used to abruptly close a session, for instance as part of a security countermeasure.
  • Finally, the clear() method clears all information, and in particular the shared secret. It can be used as cleanup or as a countermeasure.

This API is as simple as it gets, and it does not include any optional feature, such as the management of a state for the secure channel instance. The idea is here to keep it as simple as possible.

Example client program

Our example will be some kind of “Hello World” program, with two commands defined: one without security, and one with security. Both commands will do the same thing: return two concatenated copies of the input data.
Let’s start our program with the simple declarations:

package com.vetilles.securehello;

import com.vetilles.security.SecureChannel;
import javacard.framework.*;

public class Hello extends Applet {

It is quite simple and classical, except of course that it imports my library class SecureChannel. We can continue by the definition of constants:

  // The instruction bytes
  private static final byte INS_HELLO        = 0x02 ;
  private static final byte INS_SECURE_HELLO = 0x04 ;

  // The static secret
  private static final byte[] secret =
  { 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 
    0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F } ;

We here have the two instruction bytes that we expect, as well as a static secret. We have done this in order to keep the program simple, but it definitely is not wise in terms of security, for at least two reasons:

  • First, all instances of that class will use the same static shared secret, which makes the application’s security brittle. If the key for a single instance is leaked, then all keys are compromised.
  • Then, in a less obvious way, the value of the key is defined as a constant, and as such, it will appear in clear in the binary file for the application (the CAP file, and more specifically here, its static field component). The distribution of this file may be difficult to control, and it is unlikely to be encrypted when it is transferred to the card. In other words, the CAP file is a very bad place to put secrets in.

There is a single field, which is used to hold a reference to the secure channel instance:

  private SecureChannel sc;

Then, the installation sequence (installer and constructor) is very simple:

  public static void install(
    byte[] bArray, short bOffset, byte bLength)
  {
    new Hello().register();
  }

  private Hello()
  {
    sc = new SecureChannel();
    sc.setSharedSecret(secret, (short)0);
  }

The constructor simply allocates a secure channel instance, and initializes its shared secret with the value in the CAP file. Once again, this is not the standard way to go; in a normal application, the initialization of the secret would be part of the application’s personalization process.
We then get to the main command processing method. It starts in a very classical way:

  public void process(APDU apdu) {
    byte[] buffer = apdu.getBuffer();
    short len ;

    if (selectingApplet())
      return;

    switch (buffer[ISO7816.OFFSET_INS]) {

The first instruction is the standard implementation of a command without security:

    case INS_HELLO:
      len = apdu.setIncomingAndReceive();
      len = sayHello(buffer,len);
      apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
      break;

Note that the processing is performed in an external method, which is quite classical. Now, we can look at the secure version of that command:

    case INS_SECURE_HELLO:
      // Receives and unwraps the data
      apdu.setIncomingAndReceive();
      len = sc.unwrap(apdu);

      // Verifies that the command was protected
      if (!sc.isCommandProtected())
        ISOException.throwIt(
            ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);

      //Processes the command itself
      len = sayHello(buffer,len);
      
      // Wraps the response data
      sc.wrapAndSend(
          apdu, buffer,
          ISO7816.OFFSET_CDATA, len,
          (short)0x9000);
      break;

There are here four steps to be included:

  • First, the data is received, and the command is unwrapped. If an attempt is performed to unwrap a command without first opening a secure session, or if the received command is not correct, an exception will b thrown. Otherwise, the command will be simply unwrapped, and its actual length will be returned.
  • Then, the application verifies that the command is actually protected. This is very important, since the unwrap method works perfectly on standard commands. It is therefore the application’s responsibility to verify that the command was actually protected.
  • The third part is the application processing itself, which has not been modified.
  • The fourth and final part is to wrap the response data and then send it out, to finish the processing.

The last item in the switch will handle all other incoming commands:

    default:
      sc.processSecurity(apdu);

The processSecurity() method handles all the secure session management commands, and rejects all the other ones.

This example is very small, and it shows the difference between a protected command and a non-protected command. Note that there is no real need to distinguish between the two, as the “unwrap” and “wrap” methods are designed to work on standard commands. All we have to do is here to modify the test that verifies that the command is protected to only require encryption if a session is open:

      // Verifies that the command was protected
      if (!sc.isCommandProtected() && sc.isSessionOpen())
        ISOException.throwIt(
            ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);

This small change shows how important that test about the status of the command is. Note that the very common GlobalPlatform API uses a similar architecture, in which the same kind of tests is required.

No Comments

Leave a Reply

Your email is never shared.Required fields are marked *