![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
![]() |
![]() |
Java Developer
SummaryBy Mike Wendler and Stephan Breideneich, with Rinaldo Di Giorgio
In October, the 1.1 release of the OpenCard Framework was announced. There were a number of changes to the previous release, version 1.0, in the area of card terminals. These changes greatly simplify programming of card terminals and provide significant performance enhancements. OpenCard Framework 1.1 differs from 1.0 in the card terminal area in that exceptions are more concise; the CardID, which served a dual purpose in 1.0, now serves the single purpose of identifying the card using the ATR; event handling has been simplified; and the APDU classes have been made reusable to improve performance. Also, many of the convenience methods ofCommandAPDU
have been removed in order to contribute to slimlining the framework.In order to provide developers with a good library for developing card terminals, this month's Java Developer contributors offer two card terminal implementations: one that uses RS232 signals to control the reader and one that uses a packet protocol to send data back and forth to the reader, which decodes it and forwards it on to the card. (4,500 words)
o, you've been reading previous Java Developer columns on smart cards and the OpenCard Framework (OCF). You have the OpenCard Framework software and your card terminal device. Now, what exactly do you need to do to make the two work together? In this edition of Java Developer, we'll explain, step-by-step, exactly how to get your software and hardware to cooperate. We hope this article will convince you that this is a fairly easy process!
The OpenCard Framework offers general functionality for dealing with card terminals. (Although they're often called card readers, the OCF uses the more general, all-encompassing term card terminal for these devices.) Unfortunately, no concrete card terminal class was provided in the core
and opt
packages (although there are a few example implementations in the OCF Reference Implementation). Including a concrete card terminal class simply isn't possible due to the sheer variety of existing devices. This means some work remains to be done by the "terminal integrator" -- yes, that's you!
The missing terminal-dependent functionality is comprised of the following facilities:
Smart card-related functionality:
In the following section, we'll show you what to implement in order to realize this functionality.
Writing an OCF card terminal step by step
Step 1. Obtain the required information & make the basic decisions
To ensure an efficient development process, it is recommended that you collect the following information and make a few decisions right from the beginning:
Objects of subclasses of CardTerminal
are brought into being by means of a factory object. If you already have such a factory class, reuse it and change it such that it can handle the new CardTerminal
class too. If you don't have a factory class yet, well, write one from scratch! (We'll explain how to do this later on in the article.)
B. Determine the name of the terminal family
The card terminal device should come with a name. So take this name and make a name string from it that complies with the Java syntax.
C. Determine the number of slots
The number of slots will default to one in most cases, but as the smart card market expands we'll see devices with more than one slot.
D. Enable communication over javax.comm or native methods
Your Java terminal class must somehow communicate with the physical device. This can be accomplished in one of several ways. If the terminal comes with prebuilt libraries, you can use those by means of Java's native method interface (JNI). The advantage to this is that a lot of the required functionality is already implemented. The downside is, of course, that this approach renders your card terminal implementation platform-dependent. The second possibility is to use Java's mechanism of accessing the serial port of your machine using the package javax.comm
. This package isn't included in the current JDK release but it is part of Java's extension classes in the just-released major upgrade to JDK 1.2 (now known as the Java 2 platform). A beta release can be obtained separately from the Java Developer Connection on Sun's java.sun.com site. Currently javax.comm
supports only serial and parallel devices.
E. Provide your own polling or use the polling of the card terminal registry
Polling of the slot(s) (to be informed when a smart card is being inserted or removed) can be done by the card terminal registry (which deals with all registered terminals in the framework) or by the card terminal object itself. The first approach is the easiest and therefore is the recommended way. In some cases, however, you may want to exercise more control over the polling process, in which case the terminal object should take care of the polling. Future devices may even support interrupts, which, of course, would be preferable to polling.
F. Determine which protocols the supported smart cards use
Most smart cards either use the "T=0" or the "T=1" protocol. The relevant difference for OCF lies in the number of commands required to send an APDU (Application Protocol Data Unit) to the card and get a response APDU (technically we're talking about APDUs with both additional data fields, also called case 4 commands by the ISO 7816-4 standard).
View this enumeration as an introduction only. We'll go into greater detail for each of the above mentioned issues when we explain the actual implementation.
Step 2. Creation of the terminal object
Objects of the card terminal class you're going to implement aren't created directly (via new
) but only indirectly by a factory class. That means that writing a card terminal usually means not only implementing the CardTerminal
class but a factory class as well. Figure 1 shows you a UML class diagram of the classes and interfaces involved.
Figure 1: A UML class diagram of the classes and interfaces involved
|
Okay. As the factory class depends on the terminal class, let's start with this one. Create a subclass of the abstract CardTerminal
class and name it something like <yourTerminalsName>CardTerminal
(OCF convention).
Here's a list of the inherited abstract methods you'll have to implement:
public abstract CardID getCardID(int slotID) throws CardTerminalException
public abstract CardID getCardID(int slotID, int ms) throws CardTerminalException
public abstract boolean isCardPresent(int slotID) throws CardTerminalException
public abstract void open() throws CardTerminalException
public abstract void close() throws CardTerminalException
protected abstract Properties internalFeatures(Properties features)
protected abstract CardID internalReset(int slot, int ms)
protected abstract ResponseAPDU internalSendAPDU(int slot, CommandAPDU capdu, int ms)
If you're like us, you'll want to see some code running soon. So simply write empty bodies for the methods mentioned above, returning null
from the getCardID
, internalFeatures
, internalReset
, and internalSendAPDU
methods, and false
from isCardPresent
. The constructor is easy. Invoke the super class's constructor, passing through all arguments, and then call the addSlots
method with the number of slots as an argument.
Here's an example:
protected MyCardTerminal (String name, String type, String address)
throws CardTerminalException {
super (name, type, address);
addSlots(1);
}
This is sufficient at this stage to compile this class so we can use it in the factory. We'll talk about the actual implementation of the other methods as soon as we need them. For your terminal factory class you must implement the CardTerminalFactory
interface. Give that a name something like <yourTerminalFamiliesName>CardTerminalFactory
(OCF convention).
The interface CardTerminalFactory
provides the following methods:
public void createCardTerminals(CardTerminalRegistry ctr, String[] terminalInfo) throws CardTerminalException, TerminalInitException
public void open() throws CardTerminalException
public void close() throws CardTerminalException
You can safely ignore the open
/close
methods for now -- that is, write an empty method body. Okay, now things are starting to get interesting! createCardTerminals
is responsible for parsing the configuration information passed and for filtering out the information it knows something about. The passed String
array's first element contains the name of a terminal type. The Factory
interface must determine whether this name belongs to a card terminal class it knows about. If it does know about it, the Factory
interface instantiates the class and registers the resulting object with the CardTerminalRegistry
.
Here's an example implementation:
public void createCardTerminals(CardTerminalRegistry ctr, String [] terminalInfo)
throws CardTerminalException, TerminalInitException {
// is it my terminal?
if (terminalInfo[1].equals ("MyTerminalTypeName")) {
// if the terminal requires address information an additional parameter is needed
if (terminalInfo.length != 3)
throw new TerminalInitException("createCardTerminals: "
+ "Factory needs 3 parameters for My terminal");
// create the actual terminal object and register it with the CardTerminalRegistry
ctr.add (new MyCardTerminal (terminalInfo [TERMINAL_NAME_ENTRY],
terminalInfo [TERMINAL_TYPE_ENTRY],
terminalInfo [TERMINAL_ADDRESS_ENTRY]));
}
else
throw new TerminalInitException("Type unknown: "
+ terminalInfo[TERMINAL_NAME_ENTRY]);
}
Now we're ready to see some code in action! For this we'll use one of the short demo programs provided with the OCF software, which will print out information about the terminals registered in the card terminal registry. You only need to change the opencard.properties
file such that it includes information about your special new terminal. For a more thorough explanation about how OCF can be configured, have a look at the "README-configuration.html" which should be in the top directory of your OCF installation.
Basically, your opencard.properties
file should contain a line like this:
OpenCard.terminals = YourCardTerminalFactory|MyTerminal1|MyTerminalTypeName|<yourPortNr>
The OpenCard.terminals
entry may contain several terminal entries, which should comprise this information:
createCardTerminals
method above)
Now, run the demo program
java demos.samples.GetTerminalInfos
and you will (hopefully) see some information being printed about the terminals that are now registered.
Note: Our reference to port/device info may sound pretty vague to you, and rightly so. But alas, this is platform- and terminal-specific, so we can hardly give you more specific hints here. Simply put, the terminal class must be able to use this information to connect to the physical device. For example, for the IBM 5948 test terminal, we only had to put a 1
or 2
here in order to indicate the port on which to connect to the physical device. With a javax.comm
implementation, you may have to use something like Com1
on a WinTel Platform or SerialA
on a Solaris box to denote the first serial port.
Step 3. Initialization/shutdown of the terminal object
Quite often, specific operations have to be carried out in order to start up the terminal device or shut it down when it is no longer in use. These operations belong in the open
and close
methods you've left empty until now.
The framework invokes these methods during its start or shutdown process. The OCF has a requirement here only if your class implements the Pollable
interface (see Step 5, "Detect the insertion or removal of a smart card" below). In this case, you must make sure your terminal object is added to the CardTerminalRegistry
's list of terminals it has to poll. You do this by calling its addPollable
method in open
, and removePollable
in close
, respectively. Either open
or close
should throw a CardTerminalException
if something goes wrong in the process of initializing or shutting down the terminal device.
Step 4. Provide some information about the terminal type
The most basic information about your terminal (which you provided in the opencard.properties
file) is returned by the features
method of the CardTerminal
class in a Properties
object. This method is final
so there is no way to change it. But it calls the non-final
method internalFeatures
, which you can use to add specific properties of your terminal to the Properties
object passed. (No need to create a new object!)
Step 5. Detect the insertion or removal of a smart card
Your terminal should somehow make it possible to query its status and find out whether a card is inserted in one of its slots. Implement the isCardPresent
method and provide that functionality there. Just query the terminal status and return true
if a card is currently inserted in the slot with the ID passed. If no card is present, return false
.
The OCF contains a CardTerminalListener
interface that must be implemented by client classes that want to receive CardTerminalEvent
s. By means of these events, the client is able to detect the insertion or removal of a card into or from a slot. You guessed it, it's your terminal's task to fire these events. (And it's your task to make it work.) Basically, you must detect and record the status changes within the class. So add a boolean flag to the class that is to record the card insertion status. Now, where you do the actual work depends on the polling mechanism you choose.
There are two possibilities here: use the CardTerminalRegistry
's polling thread or provide your own. The tradeoff is between ease of implementation and the level of control you have over the polling behavior. Using the CardTerminalRegistry
s polling thread only requires you to let your terminal class implement the Pollable
interface. The lone method poll
allows you to check the terminal device's status on the given slot and compare it with the status of the flag in the class. Whenever you detect a status change on the slot, you toggle the flags value and invoke either the method cardInserted
or the method cardRemoved
, depending on the change you detected.
Here's an example implementation of a poll
method (assuming the terminal has just one slot):
public void poll() throws CardTerminalException {
boolean newStatus = isCardPresent (0); // do we have a card in slot '0'?
// _isCardInserted is a data field for recording the status
if (_isCardInserted != newStatus) { // if status changed
_isCardInserted = ! _isCardInserted; // the boolean flag holding the status
if (_isCardInserted) // make sure the according events
cardInserted (0); // are delivered to the listeners
else {
cardRemoved (0);
// invalidateCachedCardID (); // if you cache the ATR, clear it somewhere
}
}
}
Note: Make sure the poll
method completes quickly. Otherwise, you may cause bad overall performance of the system. This is because the CardTerminalRegistry
object employs just one polling thread, which sequentially calls the poll
method of all terminals that implement the Pollable
interface.
By the way, the interval in which the card terminal registry polls the registered Pollable
card terminals can be changed by calling its setPollInterval
method. (The card terminal registry is a Singleton, which can be obtained by calling CardTerminalRegistry.getRegistry ()
.)
Providing your own polling mechanism may sometimes be necessary, in order to have more control over the timing. This requires you to implement your own polling thread. We won't explain that in detail here, rather, we'll leave it to you as an exercise.
Step 6. Return information about the smart card (ATR)
Responsible for this task are the two getCardID
methods. Implementing the version with just one int
argument really is a piece of cake, so we're starting with this one -- we assume your boss wants to see results quickly! Simply call the other getCardID
method that requires two arguments. Pass through the value for the slot's ID as the first argument. Choose a suitable timeout value as the second argument. And that's it.
The code snippet below exemplifies this text.
public CardID getCardID (int slotID) throws CardTerminalException {
return getCardID (slotID, TERMINAL_TIMEOUT);
}
This is obviously the default version of the getCardID
method. Its semantic is: return the ATR of the card but wait only a predefined period of time. So the only real work to be done here is to decide just how long this default period is to be and assign this value to a constant (named TERMINAL_TIMEOUT
in our sample above) in your terminal class.
Now let's turn to the overloaded getCardID
method, which does the real work. To obtain the ATR of a card, it must be powered up explicitly. But first you should be sure a card is actually inserted in the given slot (and throw a CardTerminalException
if a card isn't in the slot). Then you retrieve the byte sequence that makes up the ATR. This will be device-specific, so we encapsulated this operation in a separate private method powerUpCard
. The ATR returned by this method can then be used to construct an object of type CardID
, which is OCF's wrapper class for an ATR.
If the ATR cannot be obtained within the given time period, a CardTerminalException
should be thrown.
Here's an example of a getCardID
method (see the note on implementation below for an explanation of the caching issue):
public CardID getCardID(int slotID, int ms) throws CardTerminalException {
CardID cardID = null;
byte[] cardStatus;
// check whether card is inserted
if ((getCardStatus(slotID) & STATE_CARD_INSERTED) == STATE_CARD_INSERTED) {
// check whether card is powered (==> cachedATR != null)
if (cachedATR == null)
cardID = new CardID (getSlot(slotID), powerUpCard (slotID) );
else
cardID = new CardID (getSlot(slotID), cachedATR);
}
else
cachedATR = null;
return cardID;
} // getCardID
Note: Powering up a smart card does take some time -- as does powering up any computer. Though smart cards seem to power up instantaneously compared to a Unix or Windows box, nonetheless you should strive to minimize these requests. With this in mind, we recommend you cache the ATR when retrieving it the first time (maybe in cardInserted
). Within getCardID
you then need to return the cached cardID
object only (if it is set). Of course, you must make sure to clear the cache when the card is removed (for example, in cardRemoved
).
Step 7. Communication with a smart card
The actual communication with a smart card is realized by sending APDUs to it via the sendAPDU
method of the CardTerminal
class. The default implementation must be changed in concrete subclasses by implementing the internalSendAPDU
method.
As indicated above, there is a difference in the way APDUs are handled by the T=0 or T=1 protocol, respectively. In the first case, you need to carry out two commands with the card to send an APDU and receive a response APDU. In the latter case, one command is sufficient.
Here's an example:
protected ResponseAPDU internalSendAPDU (int slotID, CommandAPDU capdu, int timeout)
throws CardTerminalException {
// precondition: terminal command consists of one byte
CommandAPDU sendAPDU = new CommandAPDU(capdu.getBytes().length + 1);
// terminal command used to exchange data with the smartcard
sendAPDU.append(0x15);
// append the apdu to exchange with the smart card
sendAPDU.append(capdu.getBytes());
return new ResponseAPDU(ProtocolLayer.transmit(slotID, timeout, sendAPDU));
}
This code snippet is quite lengthy but the functionality boils down to sending a byte array (which comes wrapped into a CommandAPDU
object) to the card and receiving a byte array from the card (which must be wrapped in a ResponseAPDU
object before it's returned).
The example above uses a pure-Java implementation. If you want or need to use existing libraries to communicate with the terminal, you must use Java's mechanism to integrate native code, preferably JNI. We won't go deeper into this, but it basically means you need to create a wrapper class in Java, which you then use in the terminal class to access the physical device. See the Resources below for a good start link to JNI.
Step 8. Reset a card
The actual method being called by the framework to reset a card is reset
. Again, this one isn't accessible, but there is an internalReset
method for you where you must repower the card and return a fresh CardID
object. Make sure to clear a cached card ID if you employ the caching solution proposed above.
Card terminal source code for various readers
The jar files, linked in the Resources section below, contain source code for card terminal implementations of the SmartMouse and the Litronic 210, and code for a generic packet protocol approach to card terminals called TLP224. In addition to the card terminal source code, we've also included some non-OCF Java code for developing a device driver to speak to a SmartMouse reader from GIS systems. The original code for the Litronics driver came from Solmer Hilger. It was modified slightly to work with the Java Card loader to be described in next month's column on loading Java Cards. Other changes were made to it to get it to function as a driver for the SmartMouse as well. The SmartMouse code that can be used without OpenCard comes to us from Jon Barber and was based upon original code by Sean Kelly at Smart Card Solutions. Note that a SmartMouse open card driver (similar to the Litronic driver) will be available by early January; check back soon.
Complex and simple readers
Readers are grouped into two categories: complex and simple. These terms aren't to be taken as commentary on the implementation of the readers, but as a quick way to understand what each reader can do for you and what you will be required to do in order to support it. What are the differences between complex and simple readers?
A simple reader doesn't perform any interpretation of the data being sent back and forth to the card; effectively, it is a transparent pass-through device. Smart cards implementing the ISO7816 standard can communicate with a host via a reader using the ISO7816 transmission protocol. The code to process readers of this type has been included in the drivers in the Resources below. This protocol has been implemented in the source code provided.
Simple readers use RS232 signaling to communicate with smart cards and have the following generic interface that varies slightly in the area of getting the ATR, reset, and card presence.
Complex readers have additional logic to perform processing on behalf of the requestor. So, instead of communicating with the card directly, complex readers interpret and act upong the bytes being sent to the card and perform additional operations in some cases.
Two simple readers
The two simple readers described in this section are the Litronic 210 and SmartMouse. Each of these readers uses electrical signaling for card detection and subsequent transmission of the ATR. Sending and receiving APDUs to the reader is accomplished by simply reading and writing data to the serial port and following the protocol.
Complex readers, which can also be expensive simple readers, perform processing directly on the reader with the small embedded microcontroller located in the reader. Yes, there is yet another computer in the card terminal in complex readers. In order to speak to this controller, the card terminal for the complex reader sometimes requires less code in the card terminal implementation than the simple reader, since the microcontroller performs many of the tasks normally carried out by the software on the host computer.
Some of the card terminal manufacturers tried to standardize in this area and came up with a protocol called TLP224. This protocol implements standard telecommunications practice; however, things like card insertion and removal detection weren't included in the specification.
Interfacing to a reader of this type can be very quickly done with OpenCard since you need only code a few methods. The most difficult part is coding the ISO7816 transmission protocol. This is fairly well documented in the source code.
Two complex readers
The GCR400 and others from GemPlus and the PE132 from DeLaRue Card Systems are examples of complex readers, in that these readers support the TLP224 packet protocol and other proprietary protocols with features that are sometimes documented and sometimes undocumented.
With these types of readers simple things like card insertion and removal can be difficult, since often you must send a communications packet to the device to determine reader presence as well as card presence. It can be done, but it makes for more complex code in comparison to RS232 signaling. We have provided an example of a TLP224 driver for the two readers mentioned.
The GCR400 reader provides card inserted indication via the Ring Indicator(RI) RS232 signal line. Ring Indicator isn't commonly supported on RS232, so what do you do? Make a special cable for it, where you would, say, take the RI pin and connect it to the CD pin so that your OpenCard software could read the CD pin. In other words, we route the RI signal to a signal we can read. If we did this for the GCR400, then we might have to do something different for the PE132, since it doesn't use RI to tell you a card has been inserted. The point here is that even with some standard like TLP224, there is much room for interpretation. One of the strengths of OpenCard is that it allows you to support each of these readers with specific implementations -- so feel free to take the source code and make it specific to a particular reader.
Conclusion
In this article we've explained the interfaces and their relationship to OpenCard as well as the questions a developer should ask in order to write a card terminal. We've followed our explanations with several implementations of card terminals for simple and complex readers. As we explained, relatively few methods are required for the implementation of a card terminal and writing OpenCard card terminals isn't hard. And, thanks to input from your peers in the developer community, we hope we've empowered you to create a card terminal library for OpenCard that makes using smart cards with different readers an easy project.
We would like to acknowledge special thanks to the Moshe Levy of Sun Microsystems for providing information on the PE132 reader and to Soren Hilmer for doing the hard work on the Litronic driver as well as Mike Montomgery of Schlumberger for optimizing the Litronic Card Terminal and to William King of Sun Microsystems for describing the TLP224 Error messages in great detail. You can see that OpenCard has a lot of individual contributors.
All of the source code provided requires further development and/or testing before being used in mission-critical systems. The purpose of providing code of this type is to offer starting points for you, the developer, to develop OpenCard card terminal implementations; it is our hope that this effort will relieve the widespread concern with the unavailability of different types of readers.
About the author
Stephan studied Business Informatics in Mannheim and has been a freelance software developer since he was 16. His current interests include distributed systems, system programming, and databases, all in connection with Java.
Mike holds a diploma in Business Informatics from the Martin-Luther-University in Halle and a M.Sc. in Information Technology from Leicester University. He is an aficionado of object-oriented analysis and design with patterns, components, and frameworks and switched from C++ to Java as his favorite programming language with Version 1.0.
Both Stephan and Mike are responsible for the card terminal layer in the OCF team at the IBM Development/Research Lab in Boeblingen right now. Besides that they have joined forces recently to offer consulting and software development services with their startup company "interface solutions."
![]() |
![]() |
|
![]() |
Copyright © 2001 JavaWorld.com, an IDG Communications company |
![]() |
![]() |
![]() |