By Ed Ort, Release 2.1.1, January 2001
Source: Wallet.java
This article introduces smart cards, gives a brief overview of Java Card technology, and by stepping you through the code of a sample applet distributed with a Java Card toolkit, shows you how to code a Java Card applet. This is the first in a series of articles on Java Card applets. After you write a Java Card applet, you're ready to test and deploy it. Future articles will describe how to perform those tasks.
Here are the topics covered in this article:
Wallet
AppletWallet
AppletWallet
AppletIt's hard to imagine making major purchases--and many types of minor ones--without credit cards. They've become almost ubiquitous in modern economies. But as familiar as these plastic cards have become, they're being joined by something that has much more power and flexibility: the "smart card." A smart card is a plastic card that has the look and feel of a credit card--it's about the same size and shape-- but with one significant difference: a smart card has an embedded microprocessor. The microprocessor is capable of doing computer-like things such as running programs, processing input and output, and storing data. But unlike most computers, a smart card does not incorporate a display or a keyboard--in fact, it has no power supply. Because it needs a way to get input and display results, a smart card works in tandem with a card acceptance device (CAD), that is, a card reader or terminal. Both of these device types have slots into which a card is placed for reading. Card readers are connected to computers; terminals are themselves computers.
A smart card and smart card reader
A smart card microprocessor can be programmed and because of that, you can use smart cards to do things that you can't do with credit cards. For example, you can use a smart card to "pay-as-you-go." One application of that is using a smart card to pay bridge tolls. Here you purchase a card that has an initial monetary value. Each time you go through a toll booth, a scanner reads the current value of the card (the card is probably mounted on the dashboard of your car) and deducts the toll from the card's current value. When the current value of the card drops to a low or zero value, you buy another one or go to the appropriate people and have them add more value to the card.
A smart card can also be used to maintain personal records, for example, your medical records. Each time you visit a medical office, say your doctor's office, a clinic, or a hospital, the administrative staff updates your card to keep your records current. This can be particularly valuable in emergencies, when you might not be physically capable of communicating with medical personnel.
These are just two of an almost limitless number of possible applications for smart cards. Not only can a card be programmed for many different types of applications, but more than one application can be loaded onto a card. If these applications are coded using Java Card technology, the code and data for one application is protected from access (and possible damage) by the others. In Java terminology these applications are said to run in their own "sandbox."
In addition, smart cards can be reprogrammed. This is an important convenience to smart card issuers. For example, imagine that a bank has issued smart cards to over 500,000 of its customers. If the bank needs to update a program on these cards, it simply makes the update available for download on a computer attached to the card reader. The next time a bank customer swipes the card through the reader, the update is downloaded onto the card; the bank does not have to reissue any smart cards.
Initially, smart card application development was essentially proprietary. Although all smart cards generally looked the same, each smart card's software was specific to the design of its embedded microprocessor. This usually meant that if company A manufactured smart cards, and company B manufactured smart cards, there was no generic way of coding applications that could run on cards produced by both manufacturers. As a result, smart card application development was limited to a relatively small group of developers who either worked for the smart card manufacturers or the smart card issuers.
Java Card technology provides an architecture for open application development for smart cards, using the Java programming language.
However in recent years, smart card application development has evolved so that it is no longer proprietary. Now smart card developers can produce applications that can run on cards from different manufacturers. Applications from different developers can run on the same smart card. This has opened up smart card development to independent application providers.
Java Card technology provides an architecture for open application development for smart cards, using the Java programming language. The technology can also be used to develop applications for other devices that have extremely small memory, such as subscriber identity module (SIM) cards for wireless phones. A SIM card is a smart card with a much smaller plastic substrate than credit-card sized smart cards. Typically, a SIM card has more memory than other types of smart cards such as bank cards.
Java Card technology comprises a set of specifications for the following:
Java Card 1.0 was initially proposed by engineers at Schlumberger. It consisted of a specification for APIs only. Later, other companies, such as Bull and Gemplus, joined Schlumberger to form the Java Card Forum. This is an industry consortium that works for the adoption of Java Card technology in the smart card industry. Later Sun Microsystems worked with the Java Card Forum and other representatives of the smart card industry to develop Java Card 2.0. This level of the technology included a JCRE specification.
Java Card 2.1.1, the most recent release of the technology, includes the API, virtual machine, and JCRE specifications. It is available for download.
Also available for download is the Java Card 2.1.1 Development Kit. The development kit provides an environment to test Java card applets, and a converter tool that prepares Java Card applets for downloading onto a smart card (or onto another device that implements Java Card 2.1 or higher). It also includes sample Java Card applets.
Now that you've been introduced to smart cards and Java Card technology, it's time to look at a Java Card applet. By examining the details of the code in an applet, you'll should be able to get started coding your own applets.
The Java Card applet illustrated in this article is one of the sample Java Card applets in the Java Card 2.1.1 development kit.
Wallet
is a sample Java card applet that is packaged in the Java Card 2.1.1 development kit. You can find the source code file, Wallet.java
, in the samples
directory. Or click here to see the source code for the Wallet
applet. The Wallet
applet turns a smart card into an electronic version of a wallet. Like a wallet, a smart card with the Wallet
applet can hold money, although here it's a digital version of money. The applet can add money to or subtract money from the wallet. It can also indicate the current amount of money in the wallet. Of course a wallet should be protected so that only its owner (or someone else who is similarly authorized) can get to the money. Because of this, the Wallet
applet also includes a security mechanism that requires each user to enter a Personal Identification Number (PIN). Only users who enter authorized PINs can direct money-related requests to the applet.
The following sections step you through the Wallet
source code in detail.
In the same way that you can bundle related Java classes and interfaces into a package, you can bundle related Java Card applets into a package. You do this by specifying a package
statement at the beginning of the source file for the applet. The format of the package
statement and the name you use for the package follow Java language conventions. In fact, Java Card technology follows Java language conventions for all identifiers. The following statement specifies Wallet
as a member of the com.sun.javacard.samples.wallet
package:
package com.sun.javacard.samples.wallet;
Importing the Java Card Framework
Java Card technology defines a set of classes and interfaces for Java Card application programming. These classes and interfaces are provided in various packages. One of the packages, javacard.framework
, defines classes and interfaces that are essential for developing Java Card applets, such as the base Applet
class. The following statement imports the javacard.framework
package:
import javacard.framework.*;
See Java Card Classes Used in the Wallet
Applet for a description of the classes in the javacard.framework
package that are used in the Wallet
applet.
Extending the Base Applet
Class
The javacard.framework
package defines the class javacard.framework.Applet
, which is the base class for all Java Card applets. javacard.framework.Applet
defines the methods that a Java Card applet uses to communicate with the JCRE. All Java Card applets extend from the base class, as is the case for the Wallet
applet:
public class Wallet extends Applet
The Reference section of this article contains a list of all the javacard.framework.Applet
Methods Used in the Wallet
Applet.
The Wallet
applet declares various constants. Some constants are one-byte values in the header of command APDUs (Application Protocol Data Units). APDUs are packets of data that are exchanged between the CAD and a smart card. APDUs are the standard means of communication for smart cards. There are two types: command APDUs, which specify an operation to be performed by a smart card; and response APDUs, which contain the smart card's response (status and optionally, data) to an operational request.
Java Card technology is modeled after a smart card specification standard, ISO7816. The standard specifies that communication between a host application and a smart card is done through Application Protocol Data Units (APDUs). An APDU is a packet of data that conforms to a specific format. There are two types of APDUs: command APDUs and response APDUs.
A command APDU starts with a header and is optionally followed by a body. The header contains fields that specify an operation to be performed by a smart card. The body includes any data that accompanies the request; it also indicates the maximum number of data bytes expected in response to the command.
A response APDU optionally begins with a body that contains any data returned in th response. The response APDU ends with two mandatory bytes that specify the processing state of the card.
See APDU formats for an illustration of the command and response APDU formats.
In Java Card technology, the host application sends a command APDU, and a Java Card applet responds with a response APDU. (In fact, a Java Card applet sits idle until it receives a command APDU.) However the communication is not directly host application-to-Java Card applet. Instead the JCRE acts as an intermediary. The command APDU is transmitted to the JCRE, which sends it to the appropriate Java Card applet for processing. After processing the APDU, the Java Card applet transmits a response APDU to the JCRE, which sends it to the host application.
If you refer to APDU Formats, you'll notice that the first byte of a command APDU is the CLA byte. CLA stands for class of instruction. The value of the CLA byte identifies an APDU category. ISO7816 demands a specific value for the CLA byte for only one category of APDU commands -- the SELECT
APDU command. The value of the CLA byte for a SELECT
APDU command must be 0. Although ISO7816 does not demand a specific value for other APDU command categories, it does identify a set of requirements that these values must meet. So for APDU command categories other than the SELECT
APDU command, you're free to select a value for the CLA byte as long as the value is compliant with the ISO specification. For the Wallet
applet, the hexadecimal value 0xB0 is used to identify the PROCESS category of APDU commands. This means that for processing operations such as credit and debit, the CLA byte in the applicable APDU commands have a hexadecimal value 0xB0.
// code of CLA byte in the command APDU header
final static byte Wallet_CLA =(byte)0xB0;
The second byte of a command APDU is the INS byte. This byte identifies a specific instruction, for example, a specific type of processing request. You're free to select the values for the INS byte. For the Wallet
applet, the hexadecimal values 0x20, 0x30, 0x40, and 0x50 specify instructions for PIN verification, credit, debit, and get the current balance, respectively.
// codes of INS byte in the command APDU header
final static byte VERIFY = (byte) 0x20;
final static byte CREDIT = (byte) 0x30;
final static byte DEBIT = (byte) 0x40;
final static byte GET_BALANCE = (byte) 0x50;
Other constants set limits on the credit and debit processing done by the applet. The constant MAX_BALANCE
sets a maximum balance for the electronic wallet of $32,767 (that is, hexadecimal 0x7FFF), and the constant MAX_TRANSACTION_AMOUNT
sets a maximum value for credit and debit transactions of $127. There are also constants that control the maximum number of times an invalid PIN can be entered before PIN entry is blocked (3), and the maximum size of the PIN (8).
// maximum balance
final static short MAX_BALANCE = 0x7FFF;
// maximum transaction amount
final static byte MAX_TRANSACTION_AMOUNT = 127;
// maximum number of incorrect tries before the
// PIN is blocked
final static byte PIN_TRY_LIMIT =(byte)0x03;
// maximum size PIN
final static byte MAX_PIN_SIZE =(byte)0x08;
The final set of constants declared in the Wallet
applet are status word values that are returned by the applet in certain circumstances, such as when PIN verification fails. As part of the APDU protocol, these constants are passed from the applet to the JCRE, and on to the host application.
// signal that the PIN verification failed
final static short SW_VERIFICATION_FAILED = 0x6300;
// signal the the PIN validation is required
// for a credit or a debit transaction
final static short SW_PIN_VERIFICATION_REQUIRED = 0x6301;
// signal invalid transaction amount
// amount > MAX_TRANSACTION_AMOUNT or amount < 0
final static short sw_invalid_transaction_amount = 0x6a83;
// signal that the balance exceed the maximum
final static short sw_exceed_maximum_balance = 0x6a84;
// signal the the balance becomes negative
final static short sw_negative_balance = 0x6a85;
The Wallet
applet declares two member variables that are used to contain values for specific objects. The variable pin
is an OwnerPIN
object that holds the user's PIN value. OwnerPIN
is a class defined in the javacard.framework
package. The class provides methods for performing PIN operations, such as updating or verifying a PIN. The variable balance
indicates the current amount of money in the electronic wallet.
/* instance variables declaration */
OwnerPIN pin;
short balance;
An instance of a Java Card applet is created through the applet's install
method (see Installing the Applet for details). When the install
method runs, it invokes a constructor. The constructor does three things: it creates an OwnerPIN
object, initializes the object, and registers the applet instance. The constructor is declared private
. This means that no other class can instantiate the Wallet
applet.
private Wallet (byte[] bArray,short bOffset,byte
bLength){
// It is good programming practice to allocate
// all the memory that an applet needs during
// its lifetime inside the constructor
pin = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
// The installation parameters contain the PIN
// initialization value
pin.update(bArray, bOffset, bLength);
register();
} // end of the constructor
Notice the comment about memory allocation. Although not a requirement, it's recommended that all objects created in a Java card applet should be instantiated in the applet's constructor. This is to avoid running out of memory at run time.
Also notice that the OwnerPIN
instantiation is specified with two parameters: the maximum number of incorrect tries before the the PIN is blocked ( PIN_TRY_LIMIT
) and the maximum PIN size ( MAX_PIN_SIZE
). Recall that the PIN_TRY_LIMIT
constant is 3 and the MAX_PIN_SIZE
constant is 8. The update
method is defined in the OwnerPIN
class. It sets a new value for the PIN, and sets the maximum number of PIN tries to the value of PIN_TRY_LIMIT
.
Before an instance of a Java Card applet can run, it needs to be registered with the JCRE. The Wallet
constructor does this by calling register
, a method defined in javacard.framework.Applet
.
The ISO7816 standard specifies that each smart card application must be uniquely identified by an application identifier (AID). The AID is an array of bytes. The first five bytes is a resource identifier (RID); the remaining bytes (which can range from zero to eleven bytes) is a proprietary identifier extension (PIX). ISO assigns AIDs to requesters such as smart card manufacturers; the requesters are free to choose their own RIDs.
In Java Card technology, AIDs are used to identify Java Card applets as well as packages of Java Card applets. When someone inserts a smart card that implements Java Card technology into a card acceptance device, the application running on the device sends a command to the card. The command identifies an operation to be performed as well as the AID of the applet to perform the operation.
The javacard.framework.Applet
class defines a static method, install
. The install
method is like the main
method in a Java program. It's the main entry point into the applet. The JCRE calls this method to create an instance of a Java Card applet and give it control. All Java Card applets must implement the install
method. What the implementation does is up to the applet designer. However at the very least, the implementation should call the applet's constructor to create and initialize an instance of the applet. It's also typical for the constructor to register the instance. And that's the case in the Wallet
applet (see Specifying a Constructor). Because there is a call to the register
method in the applet's constructor, each instance of Wallet
registers itself with the JCRE when the instance is initialized. How does the JCRE know which applet to install and register? See Identifying Applets for the answer.
public static void install(byte[] bArray,
short bOffset, byte bLength){
// create a Wallet applet instance
new Wallet(bArray, bOffset, bLength);
} // end of install method
Notice that the install
method accepts three parameters.
bArray
is an array of type byte
that contains installation parameters.bOffset
is a variable of type short
that contains the starting offset into the array.bLength
is a variable of type byte
that contains the length, in bytes, of the parameter data in the array.The installation parameters usually include applet configuration values such as the size of internal files, and applet initialization values such as an initial identification. But where do these installation parameters come from? The answer is that usually the installation parameters are loaded onto the smart card when the applet is installed. Of course, for an applet to correctly process the installation parameters, it needs to know the parameter content and format. Or more to the point, the designer of the applet needs to understand the content and format of the installation parameters and factor that into the applet's processing logic.
Selecting and Deselecting the Applet
After it's initialized, an applet waits in a suspended state until the JCRE specifically selects it. The selection process is triggered when the JCRE receives a SELECT
APDU command from the host application. In general, a host application communicates with a Java Card applet through APDUs (see the box What's an APDU?). When the JCRE receives an APDU, it checks to see if the APDU header specifies a SELECT
APDU command.
As indicated in APDU Formats, the header of a SELECT
APDU command has a standard value. If the header value indicates that this is a SELECT
APDU command, the JCRE compares the AID valuein the data field to a list of AIDs that are registered for the smart card. If the JCRE finds a match, it calls the select
method for that applet. This is a method defined in the javacard.framework.Applet
class. The select
method tells the JCRE if the applet is ready to process requests, by returning a true
value. If the applet is not ready to accept processing requests, the select
method returns a false
value. The JCRE then sends a Response APDU to the host application indicating whether the selection was successful or unsuccessful. See APDU Formats, for the format of the Response APDU sent in response to a SELECT
APDU command.
The criteria an applet uses to determine whether it should return true
or false
depends on the applet's design. For the Wallet
applet, the select
method checks to see if the PIN is blocked (because the pin try limit has been reached). If the PIN is blocked, the select
method returns false
, otherwise it returns true
.
public boolean select() {
// The applet declines to be selected
// if the pin is blocked.
if ( pin.getTriesRemaining() == 0 )
return false;
return true;
}// end of select method
When another applet is selected, that is, an applet with another AID, the JCRE calls the current applet's deselect
method. Like the select
method, the deselect
method is defined in the javacard.framework.Applet
class. The deselect
method does any necessary cleanup before the JCRE gives the newly selected applet control. In the Wallet
applet, the deselect
method resets a flag to indicate that the PIN is no longer validated.
public void deselect() {
// reset the pin value
pin.reset();
}
After an applet is selected and returns true
to the JCRE, all incoming requests from the host application are sent to the applet's process
method for processing. The process
method is another method defined in javacard.framework.Applet
.
public void process(APDU apdu) {
Notice that the process
method accepts one parameter, an APDU object. The APDU object is an instance of the javacard.framework.APDU
class. The JCRE creates an APDU object as a way to communicate a command APDU to a Java Card applet and to receive a response APDU from the Java Card applet. It's important to note that references to an APDU object are allowed only within a method, that is, as a parameter to the method or stored in a local variable. This is to protect against the possibility of one Java Card applet accessing APDU data that belongs to another Java Card applet.
In the Wallet
applet, the process
method does the following:
Gets a reference to the APDU buffer
As mentioned in Selecting and Deselecting the Applet, a host application communicates with a Java Card applet through APDUs. When the JCRE receives a command APDU, it writes the APDU header into an internal byte array called the APDU buffer. The APDU header comprises the first five bytes of the APDU, that is, the CLA and INS bytes, plus three other bytes, P1, P2, and Lc that are described in APDU Formats. The APDU buffer is encapsulated in the APDU object that the JCRE passes to the process
method. To process an APDU, an applet must first get a pointer to the APDU buffer. It does this by using getBuffer
, a method defined in the APDU class:
byte buffer[] = apdu.getBuffer();
The process
method examines the first two bytes of the APDU header, the CLA byte and INS byte. If the value of the CLA byte is 0 and the value of the INS byte is 0xA4, it indicates that this is the header of a SELECT
APDU command. In this case, the process
method returns control to the JCRE:
// check SELECT APDU command
if ((buffer[ISO7816.OFFSET_CLA] == 0) &&
(buffer[ISO7816.OFFSET_INS] == (byte)
(0xA4)) )
return;
Notice the use of the constants ISO7816.OFFSET_CLA
and ISO7816.OFFSET_INS
. The ISO7816 interface in the Java Card API defines various constants for use by Java Card applets. A number of these constants are intended for use as indexes into the APDU header; these constants begin with OFFSET
. The constants ISO7816.OFFSET_CLA
and ISO7816.OFFSET_INS
are the indexes for the CLA and INS bytes, respectively.
The process
method then checks the CLA byte to see if it has the value Wallet_CLA
. Recall that in the Wallet
applet, the hexadecimal value 0xB0 is used to identify the PROCESS category of APDU commands, and that the constant Wallet_CLA
is the value 0xB0. If the CLA byte does not have the value Wallet_CLA
, the request can't be processed by the Wallet
applet, and so the process
method throws an exception with a constant that is used in the status word of the response APDU:
// verify the reset of commands have the
// correct CLA byte, which specifies the
// command structure
if (buffer[ISO7816.OFFSET_CLA] != Wallet_CLA)
ISOException.throwIt
(ISO7816.SW_CLA_NOT_SUPPORTED);
The constant ISO7816.SW_CLA_NOT_SUPPORTED
is defined in the IS07816 interface of the Java Card API. ISO7816 interface constants that begin with SW are used in the status word of a response APDU.
Calls the appropriate method to process the request
If the CLA byte of the APDU header indicates a PROCESS category of APDU command, the process
method checks the INS byte to determine the type of PROCESS category request. It then calls the appropriate method in Wallet
, as follows, to handle the request:
Method | Purpose |
---|---|
credit (apdu) |
Credit an amount to the current balance |
debit (apdu) |
Debit an amount from the current balance |
getBalance (apdu) |
Get the current balance |
verify (apdu) |
Validate the PIN |
If the INS byte does not have a value that is recognized by the Wallet
applet, the process
method throws an exception with a constant that is returned in the status word of the response APDU.
switch (buffer[ISO7816.OFFSET_INS]) {
case GET_BALANCE: getBalance(apdu);
return;
case DEBIT: debit(apdu);
return;
case CREDIT: credit(apdu);
return;
case VERIFY: verify(apdu);
return;
default: ISOException.throwIt
(ISO7816.SW_INS_NOT_SUPPORTED);
}
} // end of process method
Credit an Amount to the Current Balance
The credit
method in the Wallet
applet credits an amount to the current balance. The method is called with one parameter: an APDU object. The APDU object encapsulates an APDU buffer that contains the CREDIT
command APDU. The amount to be credited to the current balance is in the data field of the APDU.
private void credit(APDU apdu) {
The method begins by checking the PIN for validation. Recall that the variable pin
is an OwnerPIN
object. The method isValidated
is defined in OwnerPIN
; the method returns a true
value if the PIN is currently validated, that is, a valid PIN was presented since the last card reset, or since the last call to the reset
method. If the PIN is not validated, that is, ! pin.isValidated()
returns true
, the credit
method throws an exception with a constant that is returned in the status word of the response APDU:
// access authentication
if ( ! pin.isValidated() )
ISOException.throwIt(
SW_PIN_VERIFICATION_REQUIRED);
The credit
method then gets a reference to the APDU buffer (for more details, see Get a Reference to the APDU buffer):
byte buffer[] = apdu.getBuffer();
The APDU buffer initially contains the header of the CREDIT
APDU command. As illustrated in APDU Formats, the Lc byte of the APDU header indicates the number of bytes in the data field. The credit
method uses the IS07816 interface constant OFFSET_LC
to access the Lc byte in the APDU buffer:
// Lc byte denotes the number of bytes in the
// data field of the command APDU
byte numBytes = buffer[ISO7816.OFFSET_LC];
// indicate that this APDU has incoming data
// and receive data starting from the offset
// ISO7816.OFFSET_CDATA following the 5 header
// bytes.
At this point, the credit
method is ready to get the incoming data, that is, the amount to be credited to the current balance, and put it in the APDU buffer. It does this by calling setIncomingAndReceive
, a method defined in the APDU object. setIncomingAndReceive
gets as many bytes of data as will fit into the APDU buffer, and returns the number of bytes it reads. For the Wallet
applet, setIncomingAndReceive
should read one byte of data. That's because the Lc byte, which indicates the number of bytes in the data field, has the value 1. The credit
method checks this. If the number of bytes read is not 1, the method throws an exception with a constant that is returned in the status word of the response APDU:
byte byteRead =
(byte)(apdu.setIncomingAndReceive());
// it is an error if the number of data bytes
// read does not match the number in Lc byte
if ( ( numBytes != 1 ) || (byteRead != 1) )
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
With the credit amount available in the APDU buffer, credit
performs the core part of its processing. It:
MAX_TRANSACTION_AMOUNT
constant, or is less than 0MAX_BALANCE
// get the credit amount
byte creditAmount =
buffer[ISO7816.OFFSET_CDATA];
// check the credit amount
if ( ( creditAmount > MAX_TRANSACTION_AMOUNT)
|| ( creditAmount < 0 ) )
isoexception.throwit
(sw_invalid_transaction_amount);
// check the new balance
if ( (short)( balance + creditamount)
> MAX_BALANCE )
ISOException.throwIt
(SW_EXCEED_MAXIMUM_BALANCE);
// credit the amount
balance = (short)(balance + creditAmount);
} // end of deposit method
Notice that credit
uses another ISO7816 interface constant, OFFSET_CDATA
as an index to the data field of the APDU buffer. Notice too that the method throws an exception with a constant if the transaction limit or the balance limit is exceeded. The constant is returned in the status word of the response APDU.
After the credit
method successfully completes its processing, it returns control to the JCRE. The JCRE then sends a response APDU to the host application that contains the status word value 0x9000; this indicates that the CREDIT
APDU command was successfully processed.
1 As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.