Draft APDU API

From W3C Wiki

Secure Elements API

This provides access to contact and contactless smart cards via the APDU protocol conforming to ISO/IEC 7816-4.

APDU is a command response protocol for invoking functions executed on a smart card or similar device. In essence, the command consists of a 4 byte header followed by up to 255 bytes of data. The response contains a 2 byte header followed by up to 256 bytes of data. The headers and data are specified in a suite of standards from ISO and others. It is possible to develop custom secure elements using Java cards from companies such as G&D and Gemalto.

The aim is to give system applications written in JavaScript, and executing on a web run-time, a lightweight means to establish a channel with an application on a secure element, to send it commands, and handle the responses using byte arrays. Applications would have access to the crypto APIs being developed in the W3C Web Crypto WG. This work would feed into the W3C System Applications and NFC Working Groups.

A brief introduction to APDU can be found at:

Examples

This gets the list of readers, picks the first, and attempts to connect to a named applet, and then invokes a given function on it and process the response:

  Navigator.seService.getReaders(readercb, errorcb)

  function readercb (readers)
  {
    // if a secure element is present open a session
    // otherwise prompt user and register a listener

    if (readers[0].isPresent)
      readers[0].openSession(sessioncb, errorcb);
    else
    {
      alert("please present card to reader");

      Navigator.seService.registerSeListener = function (reader)
      {
        reader.openSession(sessioncb, errorcb);
      };
    }
  }

  function sessioncb(session)
  {
    var aid = ...;  // applet id

    // open a channel to an applet on the card
    session.openBasicChallen(aid, channelcb, errorcb);
  }

  function channelcb(channel)
  {
    var comand = ...;  // byte array with APDU command

    channel.transmit(command, transmitcb, errorcb);
  }

  function transmitcb(response)
  {
    // do something with byte array containing response
  }

  // something went wrong
  function errorcb(error)
  {
    console.log(error);
  }

We may want to provide help for formatting commands, e.g.

  var command = navigator.seService.createCommand();

  // initialize header bytes
  command.setHeaders(cla, ins, p1, p2);

  // set payload from byte array
  command.setPayload(payload);

  // and send it to the card
  channel.transmit(command, transmitcb, errorcb);

  // handle the response
  function transmitcb (response)
  {
    console.log("response status: " + to_hex(response.status) +
                " with " + response.payload.length + " bytes of data");

    // when done, break off connection to applet
    channel.close();
  }

  // called if transmit operation failed
  function errorcb (why)
  {
    console.log("couldn't transmit command to card: " + why);
  }

The above would involve replacing the command and response by interfaces instead of byte arrays, as in:

interface ApduCommand {
  void setHeaders(byte cla, byte ins, byte p1, byte p2);
  void setPayload(byte[] payload);
  void setResponseLength(unsigned short length);
};

interface ApduResponse {
  attribute unsigned short status;  // the 2 status bytes
  attribute byte[] payload;
};

Secure Elements API - Complete WebIDL

Navigator implements SecureElement;

partial interface SecureElement {
  readonly attribute SEService seService;
};
   
[NoInterfaceObject] interface SEService
{

  PendingSEReaderOp getReaders(ReaderArrayCallback successCallback,
                               optional ErrorCallback errorCallback);

  unsigned long registerSEListener(SEListener listener);

  void unregisterSEListener(unsigned long id);

  void shutdown();
};

[NoInterfaceObject] interface Reader
{

  readonly attribute boolean isPresent;

  DOMString getName();

  PendingSESessionOp openSession(SessionCallback successCallback,
                                 optional ErrorCallback errorCallback);

  void closeSession();
};

[NoInterfaceObject] interface Session
{

  readonly attribute boolean isClosed;

  PendingSEChannelOp openBasicChannel(ByteArray? aid, 
                    ChannelCallback successCallback,
                    optional ErrorCallback errorCallback);

  PendingSEChannelOp openLogicalChannel(ByteArray aid,
                    ChannelCallback successCallback,
                    optional ErrorCallback errorCallback);

  Byte[] getATR();

  void close();

  void closeChannels();
};

[NoInterfaceObject] interface Channel
{

  void close();

  readonly attribute boolean isBasicChannel;

  PendingSEtransmitOp transmit(Byte[] command,
                    TransmitCallback successCallback,
                    optional ErrorCallback errorCallback);
};

callback interface SEListener
{

  void onSEReady(Reader reader);

  void onSENotReady(Reader reader);
};

callback ErrorCallback = void (DOMError error);
       
callback ReaderArrayCallback = void (Reader[] readers);

callback SessionCallback = void (Session session);
      
callback ChannelCallback = void (Channel channel);
      
callback TransmitCallback = void (Byte[] response);
      
interface PendingSEReaderOp
{
  void cancel ();
};
       
interface PendingSESessionOp
{
  void cancel ();
};