OpenCard Framework Application Development

Dr. Dobb's Journal February 2000

Using Java to build platform-independent smartcards

By Vesna Hassler and Oliver Fodor

Vesna is a professor in the Distributed Computing Group at the Technical University of Vienna. She can be contacted at hassler@ infosys.tuwien.ac.at. Oliver is a graduate student at the Technical University of Vienna. He can be reached at ofodor@ infosys.tuwien .ac.at.

The Personal Computer/Smart Card Interface (PC/SC) and OpenCard Framework are two industry initiatives to define a standard way to integrate smartcards into computer systems. With PC/SC, emphasis has been placed on the interoperability of smartcards and card terminals, and on the integration of those card terminals into Microsoft Windows. (Interoperability for smartcards means that one manufacturer's card or card reader can be used with another manufacturer's.) Members of the OpenCard (http://www .opencard.org/) and PC/SC (http://www .smartcardsys.com/) consortiums realized the need for a common framework to support smartcards on various platforms (ranging from network computers to smart phones and set-top boxes), to securely authenticate users, and to personalize these otherwise anonymous devices (similar to GSM cell phones that are activated and personalized via inserting a smartcard). OCF took advantage of some features already available within PC/SC and other smartcard standards, then focused on two new areas -- independence from host operating systems, and transparent support of different multiapplication cards and management schemes.

In this article, we'll discuss OCF, then present an OCF-based terminal (reader) application. The complete source code for this Java applet is available electronically (see "Resource Center," page 7). Although the examples and techniques are based on Schlumberger's Cyberflex Open16K card family supporting JavaCard 2.0 API, OCF is platform independent. For more information on JavaCard development, see "JavaCard Application Development," DDJ, February 1999. Likewise, Version 1.0 of the PC/SC standard is available at http:// www.smartcardsys.com/.

What Is OCF?

OCF is a high-level interface that provides a framework for developing terminal (reader) applications for smartcards in Java. It is independent of the underlying operating system because it is implemented in Java. OCF supports multiapplication cards, such as JavaCard. Its target platforms are network computers, web browsers, and any other platform that runs Java and needs to interact with smartcards. Java applications running on a desktop computer can use OCF to access smartcards. Figure 1 illustrates the general OCF architecture:

Development Environment

To develop an application using JavaCard and OCF, you need:

Most card vendors provide an SDK that supports features for managing applets and communication with the card. For example, the applet loader provided in the Cyberflex Open16K development kit needs PC/SC, but you can use it with a pure Java applet loader. However, if you use a Java loader based on the Java Communications API, you need to remove PC/SC because it captures the necessary communication port.

Configuring the Framework

In OCF 1.1, the code making up the framework is organized into components (base-core.jar, base-opt.jar, and the like). Each component is made up of one or more Java packages that provide a well-defined functionality for you.

Depending on the card terminal you use, you should install the CardTerminal classes. We use the Litronic 210 1.0b OCF CardTerminal driver that uses the Java Communication API (javax.comm 2.0) to access the device connected to a serial port. The Java Communications API can be used to write platform-independent communications applications for technologies such as voice mail, fax, and smartcards.

OCF obtains some configuration information via the Java system properties. The Java system properties are platform-independent mechanisms to make operating system and run-time environment variables available to programs. OCF provides a utility class to load properties from a file called "opencard.properties."

You can configure the card terminal registry via the OpenCard.terminals property, and the card service registry via the OpenCard.services property. Listing One is the opencard.property file for the SimpleString card service.

Card Applet

The SimpleString applet lets you write a string to the card (the setString() method), and read it back from the card (getString() method). The card applet class must store strings as byte arrays, but the terminal application can easily handle type translation to store strings.

The setString() method stores a string on the card; see Listing Two. The setIncomingAndReceive() method in the APDU class is used to both set the JavaCard Runtime Environment (JCRE) to receive mode and to receive any available data into the APDU buffer. The command data will not be in the APDU buffer until it is read by the applet calling setIncomingAndReceive() or receiveBytes(). The second method can be called only after setIncomingAndReceive() when there is more data than can fit into the APDU buffer. The APDU buffer has a minimum size of 37 bytes, and the maximum size is determined by you (255 bytes on Cyberflex Open16K).

The getString() method retrieves the string from the card (see Listing Three). To access the data in the APDU buffer, the applet must retrieve a reference to the APDU data buffer by calling the APDU getBuffer() method.

When a client application asks for the string, it has no way of knowing how long the string really is. We handle this in the following way:

1. The client sends a getString APDU with the length field set at 0.

2. The card responds with a status word set at 0x50yy, where yy is the string length (hex).

3. The client sends the getString APDU again, but this time with the length field set at yy (string length).

The response APDU may or may not contain data. If the response doesn't contain data, the applet need not do anything but return.

To set the JCRE mode to send, the APDU method setOutgoing() is called (see Listing Three). This method returns the number of response bytes expected by the client application. This number corresponds to the number of bytes expected by the command APDU (in most cases the Le byte) to which the applet is responding.

The setOutgoingLength() method informs the JCRE about the number of bytes the applet will be sending. The sendBytes() method sends a specified number of response bytes from the APDU buffer.

The APDU class contains the setOutgoingAndSend() method combining the three methods described in this section. Using this method, the data are actually not sent until the applet returns from the process() method, at which time the data are combined with the status bytes. So, once setOutgoingAndSend() is called, the applet cannot alter the APDU buffer until process() returns.

To compile the applet, you can use a standard Java compiler (javac, for example). For example, the Cyberflex post-processor mksolo requires using javac with the debugging option "-g." Before you can load the applet to the card, it must be converted (that is, previously compiled with mksolo) to a form that the JavaCard can understand. This is done by the off-card part of the JavaCard Virtual Machine. Now you can load, install, and register the applet using a proprietary card loader provided by your card SDK.

Card Service

Once the applet is properly loaded, installed, and registered on the card, you need to provide a corresponding terminal application for accessing the card. In other words, a special card service that supports the interfaces of the card applet is necessary.

At this point, we'll introduce several OCF features by a demo card service for the SimpleString applet. The card service is not suitable for the multiapplication/multiterminal scenario; it only supports one terminal application accessing one card applet at a time.

A card service is instantiated by the corresponding card-service factory registered with OCF. The instantiation is performed in two steps. First, the default constructor is called, then the initialize() method is called, which we override. In this case, we must call the super.initialize() method.

The card service instance provides the methods for application use. We'll provide two methods corresponding to the two functions of the card: the writeString() and readString() methods. To communicate with the card, the Framework's CardChannel class is used. It must be allocated (allocateCardChannel()) before use, and deallocated (releaseCardChannel()) after use. The allocateCardChannel() method makes sure that a card channel is available for communication with the card. This method blocks other threads until the active thread releases the channel. The card applet must be selected before it can accept APDU commands (see Listing Four). If the AppMgmntCardService class (see Figure 1) was provided by Cyberflex, methods for selecting applets would already be provided (Schlumberger has announced planned support for the service).

We also have to define a method for sending APDUs to the card (see Listing Five). The getCardChannel() method returns a reference to a CardChannel object that can be used for communication with the card. The CardChannel offers methods for sending commands and receiving responses.

Card-Service Factory

To create a card-service instance, OCF needs a card-service factory. Each smartcard has a unique identifier that is returned by the card in the Answer To Reset (ATR) message when it is powered up (in our case it is cyberFlexATR[]). CardID is an OCF class for handling unique smartcard identifiers that are returned in an ATR. Our SimpleStringCardServiceFactory class can only create instances of SimpleStringCardService (see Listing Six).

Now we are ready to register our service with OCF (see Listing Seven). Additionally, all card-service factories must override two abstract methods that are used by OCF to instantiate services -- knows() and cardServiceClasses(). The knows() method indicates whether this factory knows the smartcard operating system (cardID). If it does, the factory is able to instantiate card services for the card.

The card-service factory of each service supported by OCF has to be registered with the card-service registry. To register the factory, add OpenCard.services = samples.simplestring.SimpleStringCardServiceFactory to the opencard.properties file.

Terminal Application

The SimpleStringDemo terminal application (available electronically; see "Resource Center," page 7) writes a string to the card and reads it back. Importing the SimpleStringCardService lets you use the high-level interface previously defined. At the beginning we have to initialize OCF. OCF offers two approaches to find out whether a card supporting a particular service is inserted in the card terminal. The first one works with event notification, an event being card insertion into or card removal from the terminal. The second approach uses the SmartCard.waitForCard() method, which delays the program execution until a card supporting specified service is inserted into the card terminal. In our terminal application, OCF waits for a card supporting the SimpleString card service (see Listing Nine). Now we can instantiate the SimpleStringCardService and start using the card-service methods (writeString() and readString()).

Conclusion

JavaCard and OCF provide a development environment that makes it possible for you to create platform-independent smartcard-based applications. Because the concept is relatively new (some parts are still under development) and rapidly changing (for example, the latest version of the JavaCard API is 2.1, and our examples are written for 2.0), it is not yet quite suitable for developing industry- or commercial-strength applications. However, these two concepts -- together with the Sun Microsystem's JavaCommerce platform -- are promising to develop into a pure Java development environment for e-commerce applications.

DDJ

Listing One

OpenCard.services = samples.simplestring.SimpleStringCardServiceFactory
OpenCard.terminals = dk.itplus.smartcard.terminal.litronic210.
                     LitronicCardTerminalFactory|Litronic210|Litronic210|COM1 

Back to Article

Listing Two

private void setString(APDU apdu)
{
        buffer = apdu.getBuffer();
        // receive data from terminal
        byte size = (byte)(apdu.setIncomingAndReceive());
        byte index;
        TheBuffer[0] = size;
        // store string and its length
        Util.arrayCopy(buffer, ISO.OFFSET_CDATA, TheBuffer, 
                                                   (short)1, (short)size);
        return;
}

Back to Article

Listing Three

private void getString(APDU apdu) {
        buffer = apdu.getBuffer();
        byte numBytes = buffer[ISO.OFFSET_LC];
        if (numBytes == 0)
        ISOException.throwIt((short)(SW_WRONG_LENGTH + TheBuffer[0]));
        apdu.setOutgoing();
        apdu.setOutgoingLength(numBytes);
        Util.arrayCopy(TheBuffer,(short)1,buffer,(short)0,(short)numBytes);
        apdu.sendBytes((short)0,(short)numBytes);
        return;
}

Back to Article

Listing Four

public void selectApplet() throws CardServiceException
{
    try
    {
        allocateCardChannel();
        sendAPDU(selectRoot);
        sendAPDU(selectApp);
    } catch(Exception e)
    {
        e.printStackTrace();
        throw new CardServiceException();
    } finally
    {
        releaseCardChannel();
    }
    appletSelected = true;
    return;

Back to Article

Listing Five

private ResponseAPDU sendAPDU(byte[] apdu) throws CardTerminalException
{
     // Set up the command APDU
     CommandAPDU commandAPDU = new CommandAPDU(apdu);
     ResponseAPDU responseAPDU=getCardChannel().sendCommandAPDU(commandAPDU);
     return (responseAPDU);
}

Back to Article

Listing Six

public SimpleStringCardServiceFactory()
     {
     try {
          cyberFlexCID = new CardID (cyberFlexATR);
     } catch ( Exception e ) {
     }
}

Back to Article

Listing Seven

// register card service with OCF
static {
        services_.addElement(SimpleStringCardService.class);
}

Back to Article

Listing Eight

protected Enumeration cardServiceClasses(CardID cid)
{
    return services_.elements();
}
public boolean knows(CardID cardID)
{
    // check whether the factory knows the smartcard OS
    if (cardID.equals(cyberFlexCID))
    {
         return true;
    } else
    {
         return false;
    }
}

Back to Article

Listing Nine

// initialize OCF
SmartCard.start();
CardRequest cr = new CardRequest(SimpleStringCardService.class);
// wait for card supporting SimpleStringCardService
SmartCard sc = SmartCard.waitForCard(cr);
// instantiate card service
SimpleStringCardService ssp = (SimpleStringCardService)sc.
                        getCardService(SimpleStringCardService.class, true);


Back to Article