By C. Enrique Ortiz, September 2003
Part 1 of this article covered the high-level aspects of Java Card technology - what smart cards are, the elements of a Java Card application, communication aspects, and a summary of the various Java Card technology specifications. Part 2 covered the development aspects of Java Card applets: the typical steps when developing a Java Card application, Sun's Java Card Development Kit, and the Java Card and Java Card RMI APIs. The final part of this article will cover the development aspects of host applications for smart cards.
Elements of a Java Card Application
Keep in mind that Java Card applications are not standalone, but rather part of an end-to-end application:
Figure 1. Typical Components of a Java Card Application
A Java Card application typically comprises:
Writing a Host Application - Accessing Your Applet
The host application, on the client side, handles communication among the user, the Java Card applet, and the provider's back-end application. The host program accesses the services provided by your applet. It resides on a terminal or card acceptance device such as a workstation, a point of sale (POS) terminal, a cell phone, or a set-top box. Recall that a host and an applet interact by way of a card reader or terminal, using ISO-7816 APDU commands.
Traditionally, reader-side applications have been written in C, but the host program can be written in the Java programming language, or any other, as long as it's prepared to exchange valid ISO-7816 APDU commands with the applet.
Most cell phones deployed today integrate a smart card reader to access the SIM card bundled with it. With the coming introduction of JSR 177, the Security and Trust Services API (SATSA) for J2ME, and the widespread adoption of J2ME devices, we can expect that a variety of host applications will be written using Java technology on mobile devices. The intent of SATSA is to enable a Java Card host application to run on a J2ME-based device. JSR 177 is currently in JCP community review.
Three major APIs are available when you're writing client-side applications: the OpenCard Framework, the Java Card RMI Client API, and the Security and Trust Services API (SATSA). We'll look at each of these in turn.
Introduction to the OpenCard Framework
Smart card vendors typically provide, not only a development kit, but also APIs to support reader-side applications as well as Java Card applets. Many of these vendors support the OpenCard Framework (OCF), a Java-based set of APIs that hide some of the details of interacting with card readers from different vendors.
The OpenCard Consortium is a group of companies driving the definition and adoption of the OpenCard Framework, currently in its 1.2 version. OCF's objective is to provide developers of host-side applications with an API that works across different card-reader vendors.
To accomplish vendor-independence, the OCF defines two software layers:
CardTerminal
layer provides the card reader abstractions, such as CardTerminal
(representing a physical card reader), APDU
, CommandADPU
, and ResponseAPDU
.The OCF defines a number of standard card services for you. Two of these are FileAccessCardService
and SignatureCardService
. A special type is ApplicationManagerCardService
, which provides life-cycle management methods to install, register, and remove applets on the card.
When writing a host-side OCF-based application, you basically separate it into two parts:
getBalance()
.Figure 2. Structure of an OCF Application
In summary, a typical OCF application has one or more main objects, which are created on the host, probably on their own threads of execution. These main application objects expose the high-level application-specific calls, which are eventually delegated to the applet proxy. They make use of the SmartCard
object, which is the application's entry point to the OCF, enabling the application to initialize and shut down the OCF, and wait for cards to be inserted. The main object can implement a CTListener
, which as you'll see soon, provides asynchronous notification of events such as card insertion and removal.
You can write your application using a synchronous or asynchronous model.
In the synchronous model your host application initializes the OCF, then waits for a card to be inserted. Then it executes your main application logic, and when done shuts down the OCF:
...
try {
// Initialize OCF
SmartCard.start();
// Wait for a smart card
CardRequest cr = new CardRequest(CardRequest.NEWCARD, null,
OCFCardAccessor.class);
SmartCard myCard = SmartCard.waitForCard(cr);
// Main client work is done here...
...
} catch (Exception e){
// Handle exception
} finally {
try {
// Shut down OCF
SmartCard.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
...
Listing 1. Typical Structure of a Synchronous OCF Application
If you prefer to use the asynchronous approach, your class must implement the CTListener interface, and, at initialization, register itself for notification of card-terminal events such as insertion and removal. The following application skeleton starts by initializing the OCF and registering the listener, then defines callback methods for significant events.
public class MyHostSideApp implements CTListener
...
public MyHostSideApp() {
try {
// Initialize the framework
SmartCard.start ();
// Register this as a Card Terminal Event Listener
CardTerminalRegistry.getRegistry().addCTListener(this);
} catch (Exception e) {
// handle error...
}
}
public void cardInserted(CardTerminalEvent ctEvent) {
...
}
public void cardRemoved(CardTerminalEvent ctEvent) {
...
}
...
}
Listing 2. Typical Structure of an Asynchronous OCF Application
When a card is inserted, the runtime calls the cardInserted()
method, and when the card is removed, the runtime calls the cardRemoved()
method. In the more fleshed-out listing that follows, inserting a card initiates creation of an applet proxy, and removing the card triggers cleanup. The listing also shows delegation of requests for credit-card balances to the proxy.
import opencard.core.event.CTListener;
import opencard.core.event.CardTerminalEvent;
import opencard.core.service.SmartCard;
import opencard.core.service.CardService;
...
public class MyHostSideApp implements CTListener
{
public void MyHostSideApp() {
try {
// Initialize the framework
SmartCard.start ();
// Register this as a Card Terminal Event Listener
CardTerminalRegistry.getRegistry().addCTListener(this);
} catch (Exception e) {
// Handle error.
...
}
}
/**
* Card insertion event. Get new card and card service
* @param ctEvent The card insertion event.
*/
public void cardInserted(CardTerminalEvent ctEvent) {
try {
// Get a SmartCard object
card = SmartCard.getSmartCard(ctEvent);
// Get the card proxy instance.
myCardProxy = (MyCardProxy)
card.getCardService(MyCardProxy.class, true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Card removal event. Invalidate card and card service.
* @param ctEvent The card removal event.
*/
public synchronized void cardRemoved(CardTerminalEvent ctEvent) {
card = null;
myCardProxy = null;
// Initialize the framework
SmartCard.shutdown();
}
/**
* Get balance from the smart card.
*/
public int getBalance() {
try {
// Get mutex to prevent other Card Services from modifying
// data. Delegate the call to the applet proxy.
card.beginMutex();
return Integer.parseInt(myCardProxy.getBalance());
} catch (Throwable e) {
return 0;
} finally {
// End mutual exclusion
card.endMutex();
}
}
...
}
Listing 3. An Amplified Listener-Based OCF Application
An excerpt from the applet proxy is next. The OCF application delegates service invocation to the applet proxy, which implements the (complex) APDU management piece:
public class MyCardProxy extends AppletProxy {
// My APDU definitions.
final static byte MyAPPLET_CLA = (byte)0x80;
final static byte VERIFY_INS = (byte)0x20;
final static byte GET_BALANCE_INS = (byte) 0x30;
final static short GET_BALANCE_RESPONSE_SZ = 2;
protected final static int OK = 0x9000;
final static short SW_PINVERIFY_FAILED = (short)0x6900;
/**
* Reusable command APDU for getting an information
* entry field.
*/
private CommandAPDU getBalanceAPDU = new CommandAPDU(14);
...
/** Application identifier of the BusinessCard applet */
private static final ApplicationID MY_CARD_AID =
new ApplicationID(new byte[] { (byte)0xD4,
(byte)0x55,
(byte)0x00,
(byte)0x00,
(byte)0x22,
(byte)0x00,
(byte)0x00,
(byte)0x00,
(byte)0xFF});
/**
* Create a MyCardProxy instance.
*
* @param scheduler The Scheduler from which channels
* have to be obtained.
* @param card The SmartCard object to which this
* service belongs.
* @param blocking Currently not used.
*
* @throws opencard.core.service.CardServiceException
* Thrown when instantiation fails.
*/
protected void initialize(CardServiceScheduler scheduler,
SmartCard card, boolean blocking)
throws CardServiceException {
super.initialize(MY_CARD_AID, scheduler, card, blocking);
try {
// Allocate the card channel. This gives us
// exclusive access to the card until we release the
// channel.
allocateCardChannel();
// Get the Card State.
...
} finally {
releaseCardChannel();
}
}
/**
* Gets the balance.
* @return The balance.
*/
public String getBalance()
throws CardServiceInvalidCredentialException,
CardServiceOperationFailedException,
CardServiceInvalidParameterException,
CardServiceUnexpectedResponseException,
CardServiceException,
CardTerminalException {
try {
allocateCardChannel();
// Set up the command APDU and send it to the card.
getBalanceAPDU.setLength(0);
getBalanceAPDU.append(MyAPPLET_CLA); // Class
getBalanceAPDU.append(GET_BALANCE_INS); // Instr'n
getBalanceAPDU.append((byte) 0x00); // P1
getBalanceAPDU.append((byte) 0x00); // P2
getBalanceAPDU.append((byte) 0x00); // Lc
getBalanceAPDU.append((byte) 0x00); // Le
// Send command APDU and check the response.
ResponseAPDU response =
sendCommandAPDU(getCardChannel(), MY_CARD_AID,
getBalanceAPDU);
switch (response.sw() & 0xFFFF) {
case OK :
return new String(response.data());
default :
throw new
CardServiceUnexpectedResponseException
("RC=" + response.sw());
}
} finally {
releaseCardChannel();
}
}
...
}
Listing 4. An Applet Proxy Example
Not covered in this article but worth mentioning is the host-side API called the Global Platform. The Global Platform Card Committee provides a set of card APIs that complement the Java Card APIs, providing among other things card-management features. These specifications are in version 2.1.1.
The Java Card RMI Client API
Earlier you learned how to write a JCRMI-based applet. If your Java Card applet is JCRMI-based, you can use the Java Card RMI Client API to write a host application that accesses the applet's objects that are stored on the smart card.
For smart card management and access, the JCRMI Client API requires a card-terminal and services API such as the OpenCard Framework just described.
When we put these two APIs together we get a much-simplified, more fully object-oriented programming model, with several benefits:
The JCRMI Client API is defined in the following packages:
com.sun.javacard.javax.smartcard.rmiclient
contains the core JCRMI Client API. It defines:
CardAccessor
interface that JCRMI stubs use to access the smart card.CardObjectFactory
class that is the base class for JCRMI-stub generation implementations. An instance of this class is associated with one Java Card applet selection session.JavaCardRMIConnect
class that is used by the client application to initialize a JCRMI session, and obtain an initial remote reference.APDUExceptionSubclass
, CardExceptionSubclass
, CardRuntimeExceptionSubclass
, CryptoExceptionSubclass
, ISOExceptionSubclass
, PINExceptionSubclass
, PINException
, ServiceExceptionSubclass
, SystemExceptionSubclass
, TransactionExceptionSubclass
, and UserExceptionSubclass
.javacard.framework
defines a number of Java Card exceptions that can be re-thrown on the client: APDUException
, CardException
, CardRuntimeException
, ISOException
, PINException
, SystemException
, TransactionException
, and UserException
.javacard.framework.service
defines the ServiceException
, which represents an exception related to the service framework.javacard.security
defines the CryptoException
that represents a cryptography-related exception.Generating the RMI Client Stubs
You have the option of generating client stubs using the standard Java RMI compiler (rmic). You must run rmic
for each remote class in your applet using a command in the following form:
rmic -v1.2 -classpath path -d output_dir class_name
...where:
-v1.2
is a flag required by the Java Card RMI client framework.-classpath path
identifies the path to the remote class.output_dir
is the directory in which to place the resulting stubs.class_name
is the name of the remote class.The preferred approach to generating RMI client stubs, though, is to use the dynamic proxy generation mechanism introduced with J2SE SDK 1.3. Version 2.2 of the Java Card RMI Client API will automatically generate stubs for you if you use the CardObjectFactory
subtype JCCardProxyFactory
when selecting the JCRMI applet - you don't have to generate any more stubs! This approach is illustrated in Listing 5.
Usage Restrictions
Because Java Card is a restricted runtime environment, we can expect to find limitations on what JCRMI actually supports. Java Card doesn't support serialization, and JCRMI arguments and return values are restricted:
char
, double
, float
, long
, or multidimensional arrays. Support for int is optional.void
, or a remote interface type.The JCRMI Client Application
A JCRMI client application has some similarities to the OCF host application you've already seen, because the JCRMI Client API depends on the OCF for card management and communications.
The next code snippet first initializes the OCF and waits for a smart card to be inserted. It then creates an OCFCardAccessor
implementation for our JCRMI connection to the card, client stubs are generated dynamically if needed, the applet is selected, we get the remote reference, and finally we make our remote call to getBalance()
:
...
try {
// Initialize OCF
SmartCard.start();
// Wait for a smart card
CardRequest cr = new CardRequest(CardRequest.NEWCARD, null,
OCFCardAccessor.class);
SmartCard myCard = SmartCard.waitForCard(cr);
// Get an OCFCardAccessor for Java Card RMI
CardAccessor ca = (CardAccessor)
myCard.getCardService(OCFCardAccessor.class, true);
// Create a Java Card RMI instance
JavaCardRMIConnect jcRMI = new JavaCardRMIConnect(ca);
// Create a Java Card Proxy Factory that is used for dynamic
// proxy generation.
CardObjectFactory factory = new JCCardProxyFactory(ca);
// select the Java Card applet
jcRMI.selectApplet(MY_APPLET_AID, factory);
// Get the initial reference
MyRemoteInterface myRemoteInterface =
(MyRemoteInterface) jcRMI.getInitialReference();
if(myRemoteInterface == null) {
throw new
Exception("Received null instead of the initial ref");
}
// Invoke the remote getBalance() method
short balance = myRemoteInterface.getBalance();
}
catch(UserException e) {
// Handle exception
...
}
catch (Exception e){
// Handle exception
...
} finally {
// Clean up
try{
SmartCard.shutdown();
}catch (Exception e){
System.out.println(e);
}
}
Listing 5. Sample JCRMI Client
As you can see, the coding you have to do is reduced and greatly simplified.
Using the Security and Trust Services API for J2ME
SATSA is a new set of optional packages for J2ME that defines a client-side API to access what are referred to as security elements: devices such as smart cards. In this section I'm going to introduce only the communication aspects of SATSA; SATSA PKI and crypto APIs are not covered.
The SATSA communication API is broken down as follows:
javax.microedition.io.JavaCardRMIConnection
javax.microedition.jcrmi.RemoteRef
javax.microedition.jcrmi.RemoteStub
java.rmi.Remote
java.rmi.RemoteException
javacard.framework.service.ServiceException
javacard.framework.CardRuntimeException
javacard.framework.ISOException
javacard.framework.APDUException
javacard.framework.CardException
javacard.framework.PINException
javacard.framework.SystemException
javacard.framework.TransactionException
javacard.framework.UserException
javacard.security.CryptoException
SATSA brings the J2ME and Java Card platforms closer together. SATSA uses the CLDC 1.0 Generic Connection Framework (GCF) for communications between a J2ME-based device and a smart card as illustrated here:
Figure 3. The Generic Connection Framework and SATSA Connections
Because SATSA is based on the GCF, developing MIDlets that make use of the smart card on the cellphone is not only relatively easy, but also familiar to J2ME developers.
SATSA caters to both of the overall programming models for Java Card applications: the APDU-message model and the Java Card RMI object-oriented distributed model. For each of these models SATSA defines a new GCF connection type:
APDUConnection
allows a J2ME application to exchange APDUs with a smart-card application using the ISO-7816 APDU protocol.JavaCardRMIConnection
allows a J2ME application to use Java Card RMI to invoke remote methods on a smart card.Specifying SATSA Connection Types
All GCF connections are created using the Connector.open()
method. One of the arguments to Connector.open()
is a URL that indicates the type of connection to create. The CLDC GCF defines this URL as a string in the following format:
scheme:[target][params]
...where:
For SATSA, the format of the URL is:
protocol:[slotID]; AID
...where:
A0.0.0.67.4.7.1F.3.2C.3
".Using an APDUConnection
An APDUConnection
defines the methods that allow us to talk to ISO-7816-compliant cards using GCF. It defines three methods:
enterPIN()
prompts the user for a PIN.exchangeAPDU()
exchanges an APDU with a smart card application. This call blocks until a response has been received from the smart card (or processing is interrupted).getATR()
returns the Answer To Reset (ATR) message sent by the smart card in response to the reset operation.The following snippet shows how to open an APDUConnection, how to close it, and how to exchange a command APDU and receive a response APDU:
...
try {
// Create an APDUConnection
String url = "apdu:0;AID=A1.0.0.67.4.7.1F.3.2C.5";
APDUConnection ac = (APDUConnection) Connector.open(url);
// Send a command APDU and receive a response APDU
byte[] responseAPDU = ac.exchangeAPDU(commandAPDU);
...
// Close connection.
ac.close();
} catch(IOException e){
...
}
...
Listing 6. Using SATSA-APDU
SATSA makes APDU communications simple. Note that the formats of the command and response APDUs are the same as you saw earlier in the "Writing a Card-Side Java Card Applet" section of Part 2, which tells you how to write a Java Card applet based on APDU messaging.
Using a JavaCardRMIConnection
A JavaCardRMIConnection
defines the methods that allow us to use the Java Card RMI programming model with GCF. The JavaCardRMIConnection
defines the method getInitialReference()
, which returns the stub object for an initial remote reference.
...
try {
// Create a JavaCardRMIConnection
String url = "jcrmi:0;AID=A0.0.0.67.4.7.1F.3.2C.3";
JavaCardRMIConnection jc = (JavaCardRMIConnection)
Connector.open(url);
MyRemoteObject robj = (MyRemoteObject)
jc.getInitialReference();
...
short balance = robj.getBalance();
...
// Close connection
jc.close();
} catch (Exception e) {
...
}
...
Listing 7. Using SATSA-JCRMI
Using the SATSA-JCRMI API as shown above allows us to invoke the getBalance()
method that we defined in Part 2 of this article, which shows you how to write an RMI-based Java Card applet.
Summary
The first installment of this article covered the use of smart cards to store sensitive information and process transactions securely, and the various aspects of Java Card technology: the Java Card VM, the runtime environment, the relevant APIs, and the behavior of Java Card applets.
Part 2 examined the development of a Java Card applet. It covered the structure of a Java Card applet, the Sun Java Card Development kit, and use of the APIs and programming models that are available when writing card-based applets: the Java Card API, and the Java Card RMI API.
This final part of the article covered the development of host applications, and some of the Java APIs that are available when writing them: the OpenCard Framework, the Java Card RMI Client API, and the Security and Trust Services API (SATSA) for J2ME.
Smart cards with Java Card technology are the most portable and secure way of carrying digital personal information and computational capabilities - a very powerful and needed technology in today's digital world.
Links of Interest
Acknowledgements
Many thanks to the Java Card team, Florian Tournier, Oscar A. Montemayor, Eduard de Jong, Victor Olkhovets, and Nachi Periakaruppan, and to Richard Marejka and Brian Christeson for their great contributions and feedback to this article.