In the first article of this series, I introduced you to Java Card technology. I started by examining the inner workings of an example e-bank application in which a Java Card serves a Java 2 Platform, Micro Edition (J2ME) client to enable dual-factor security. I then showed you how to load and install the application onto a Java Card and explained how the J2ME client would use the services provided by the application. I concluded the article with an overview of the sequence of internal events that would enable a J2ME mobile phone user to check an account balance using the Java Card e-bank application, and a look at the major classes of the e-bank application.
In this article, I expand on the e-bank application example from last time. I also explain the pros and cons of the current cryptographic support for the Java Card API, then demonstrate a roll-your-own cryptography solution. The article also discusses the SATSA API, shows you how to implement the J2ME-based client-side logic of the e-bank application, and leaves you with some important tips for debugging Java Card applications.
I'll pick up right where I left off in the last article, with a closer look at the e-bank application's UserPIN and EBankPIN classes. I'll take you through the steps of authenticating the user, employing cryptography for decryption, and extracting the session key from encrypted data to establish a secure session.
For the remainder of the article, I will focus on the major topic of the Security and Trust Services API (SATSA), which I discussed only briefly last time. I'll explain the role of SATSA in making Java Card applications usable by J2ME devices and demonstrate the use of SATSA classes. I will also implement the J2ME-based client-side functions of the KerberosEBank application, further demonstrating the use of the SATSA API. I'll conclude the article with an overview of techniques for debugging Java Card applications.
Please note that the discussion in this article follows from the discussion in the first article. If you haven't read that article, you should do so before continuing. You may also wish to download the article source now.
In the first article in this series you got a glimpse of the simple authentication process that underlies Java Card security. When I installed the e-bank Java Card application onto a Java Card (see Part 1, Listing 4) the JavaCardKerberosKey applet instantiated the UserPIN class. While instantiating, it passed the user's key to the UserPIN constructor. The
KerberosSecurityService class then used the UserPIN class to authenticate the user.
When a J2ME user accesses the JavaCardKerberosKey applet, it provides the user's key
to the UserPIN class. The UserPIN class compares the key provided by the
user with the user's key stored during the installation process. If the
two keys match, the authentication succeeds; if not, it fails.
Listing 1 shows the UserPIN class.
Listing 1. The UserPIN class
public class UserPIN implements PIN {
private byte userPIN[] = new byte[4];
private byte availableTries = 0;
public UserPIN (byte[] userKey) throws PINException {
if(pin.length < 4)
PINException.throwIt((short)1);
Util.arrayCopy(userKey,
(short)0,
userPIN,
(short)0,
(short)4);
}//UserPIN()
public boolean check (byte[] data,
short offset,
byte length) {
if(getTriesRemaining() > 10)
// We assume the Java Card is stolen.
return false;
//decrypts the encrypted user pin
if(Util.arrayCompare(data,
offset,
userPIN,
(short)0,
(short)(length)) == 0) {
triesRemaining = 0;
return true;
}
triesRemaining++;
return false;
}//check()
public byte getTriesRemaining(){
return availableTries;
}//getTriesRemaining
//Other methods of the PIN interface not relevant to the e-bank
//application.
}//UserPIN
|
The UserPIN constructor in Listing 1 takes just one parameter, which is a byte
array named userKey. The constructor stores
the userKey value in a class-level variable
named userPIN. This value will be used later
on for user authentication.
Recall the processCommand() method of the KerberosSecurityService class (Part 1, Listing 5), where I called the check() method of the UserPIN class. This check() method actually performs user authentication.
The check() method takes three parameters -- namely data, offset, and length:
datais a byte array that contains an encrypted Kerberos structure and the user's key. (I explained this while discussing theprocessCommand()method of Part 1, Listing 5.)offsetis the index number where the user's key starts in thedatabyte array.lengthspecifies the length of the user's key (that is, how many bytes need to be read from thedatabyte array).
The check() method calls another method named getTriesRemaining() to check the number of unsuccessful tries to login. If the unsuccessful tries are more than 10, the Java Card will be blocked. In this case, the JavaCardKerberosKey applet can only be accessed following a new installation by the e-bank.
If the number of tries is less than 10, the check() method extracts the user's key from the data byte array and compares it with the value of the user's key stored during installation by the JavaCardKerberosKey applet. If the two values match, the check() method returns true to the calling method (that is, the processCommand() method of the KerberosSecurityService class) and initializes the number of tries remaining.
You'll notice that the constructor and check() method of the UserPIN class are called at two different stages of the application life cycle. The UserPIN constructor is called when the e-bank application installs the JavaCardKerberosKey applet in the Java Card of an account holder. The KerberosSecurityService.processCommand() method calls the UserPIN.check() method every time the account holder wants to make a transaction using a J2ME device.
Also note that the user's key has traveled from the KerberosEBank MIDlet to the JavaCardKerberosKey applet in plain-text form. That's because I am assuming the Java Card resides inside the account holder's J2ME cell phone: in that case there's no need to secure the communication between the MIDlet and the Java Card applet.
If you wanted to enable the account holder's Java Card to be used outside of a cell phone, you would need to add cryptographic support to the current implementation, as I'll soon discuss.
Going back to the event sequence diagram of Part 1, Figure 1, you'll recall that the KerberosEBank MIDlet fetches a TGT (ticket-granting ticket) that contains a session key wrapped inside an encrypted portion. The KerberosEBank MIDlet needs this session key to securely communicate with the e-bank application. The main purpose of the EBankPIN class is to extract the session key from the encrypted portion.
In Part 1, Listing 4, you saw that the JavaCardKerberosKey constructor instantiated the EBankPIN class while the e-bank was installing the JavaCardKerberosKey applet in the user's card. The JavaCardKerberosKey constructor passed two parameters to the EBankPIN constructor: the user's key and the e-bank's key. The EBankPIN class uses both of these keys to decrypt the TGT.
The implementation of the EBankPIN class is shown in Listing 2, note the steps in the getDecryptedSessionKey() method. (I'll discuss the second set of steps later in the article.)
Listing 2. The EBankPIN class
public class EBankPIN {
private byte[] userPIN = new byte [4];
private byte[] eBankPIN = new byte [4];
private static int CONTEXT_SPECIFIC = 160;
private static int APPLICATION_TYPE = 96;
private byte[] encKerberosData;
public EBankPIN (byte[] ownerPIN, byte[] bankPIN )
throws PINException {
Util.arrayCopy(ownerPIN,
(short)0,
userPIN,
(short)0,
(short)4);
Util.arrayCopy(bankPIN,
(short)0,
eBankPIN,
(short)0,
(short)4);
}//EBankPIN
public byte[] getDecryptedSessionKey(byte [] encKerberosData) {
/*** Step 1 ***/
byte[]decryptionKey = new byte[8];
Util.arrayCopy(userPIN,
(short)0,
decryptionKey,
(short)0,
(short)4);
Util.arrayCopy(eBankPIN,
(short)0,
decryptionKey,
(short)4,
(short)4);
/*** Step 2 ***/
byte[] plainText = new byte[encKerberosData.length];
byte algoId;
ExtendedCipher exCipher = new ExtendedCipher();
plainText=exCipher.decrypt(encKerberosData,
decryptionKey,
algoId);
/*** Step 3 ***/
byte [] sessionKey = parse(plainText);
/*** Step 4 ***/
if (sessionKey != null)
return sessionKey;
return null;
}// getDecryptedSessionKey
private byte[] parse(byte[] keyWrapperStruct) {
/***** Step 1 *****/
byte dataOffset = 24;
byte[] keyWrapperStructAfterRemoving24Bytes = new byte[(short)(
keyWrapperStruct.length- dataOffset)];
for (short i=0; i < keyWrapperStruct.length; i++)
keyWrapperStructAfterRemoving24Bytes [i] =
keyWrapperStruct [(short)(i+dataOffset)];
if (keyWrapperStructAfterRemoving24Bytes != null) {
/***** Step 2 *****/
if((isASN1Structure(
keyWrapperStructAfterRemoving24Bytes [(short)0],
APPLICATION_TYPE,25)) ||
(isASN1Structure(
keyWrapperStructAfterRemoving24Bytes [(short)0],
APPLICATION_TYPE,26)))
{
/***** Step 3 *****/
byte[] enc_rep_part_content =
getContents(getContents(
keyWrapperStructAfterRemoving24Bytes));
/***** Step 4 *****/
byte[] enc_key_structure =
getASN1Structure(enc_rep_part_content,
CONTEXT_SPECIFIC, 0);
/***** Step 5 *****/
byte[] key_content =
getContents(getContents(key_content));
/***** Step 6 *****/
byte[] enc_key_val = getASN1Structure(enc_key_sequence,
CONTEXT_SPECIFIC, 1);
/***** Step 7 *****/
byte[] enc_key = getContents(getContents(enc_key_val));
/***** Step 8 *****/
return enc_key;
}
}
}//parse()
private byte[] getASN1Structure (byte[] inputByteArray,
int tagType,
int tagNumber){
}//getASN1Structure
private short getNumberOfLengthBytes (byte firstLengthByte) {
}//getNumberOfLengthBytes
private short getIntegerValue(byte[] intValueAsBytes) {
}//getIntegerValue()
private short getLength (byte[] ASN1Structure, short offset) {
}//getLength()
private boolean isASN1Structure (byte tagByte, int tagType,
int tagNumber){
}//isASN1Structure
private byte[] getContents (byte[] ASN1Structure){
}//getContents
}//EBankPIN
|
The EBankPIN constructor takes two bytes arrays named ownerPIN and bankPIN and simply assigns them to class-level variables named userPIN and eBankPIN, respectively. This much work is done during the applet's installation procedure. Later in the getDecryptedSessionKey() method the EBankPIN class will use these keys to decrypt the encrypted portion of the TGT.
The getDecryptedSessionKey() method takes just one parameter named encKerberosData, which is a byte array. This byte array contains the encrypted ticket data (which contains the session key). In Part 1, Listing 7, the KerberosKeyManager.getKey() method calls the getDecryptedSessionKey() method passing it the encrypted structure.
The getDecryptedSessionKey() method
You can map the four steps in the getDecryptedSessionKey() portion of Listing 2 (above) to the following explanation:
- I concatenate the
userPINandeBankPINbyte arrays to form a single decryption key. I also store the key in a variable nameddecryptionKey. I'll use this key to decrypt the encrypted data. - I instantiate a class named
ExtendedCipher. Later in the article I'll discuss theExtendedCipherclass in detail. Here, I call a method nameddecrypt()of theExtendedCipherclass. Thedecrypt()method takes three parameters:- The first parameter contains the encrypted data that needs to be decrypted.
- The second parameter contains the key needed for decryption.
- The third parameter is an identifier for the cryptographic algorithm that will be used for decryption.
decrypt()method decrypts the encrypted data using the decryption key and returns the decrypteddata. I store the decrypted data in a uvariable namedplainText. - I call the
parse()method passing it theplainTextfrom Step 3. Theparse()method processes the plain-text data to extract the session key. The processing involves a number of steps that I will shortly explain. Theparse()method returns the session key. - The
getDecryptedSessionKey()method returns the session key to theKerberosKeyManagerclass.
I'll return to Listing 2 for a look at the parsing procedure to extract the session key later in the article.
In this section I'll discuss the support for cryptography in the Java Card API. I will also discuss the limitations of the cryptograpy features available in the current version of the Java Card Development Kit (JCDK) and show you how to overcome those limitations to enable dual-factor security.
I'll start by discussing the cryptographic support in the Java Card API.
Cryptography support in the Java Card API
The Java Card API has a package named javacardx.crypto that provides cryptographic support. This package contains a class named Cipher, which you can use for encryption and decryption according to different encryption algorithms.
You need to perform following four steps to use the Cipher class for encryption or decryption:
- Instantiate the
Cipherclass. - Instantiate a
DESKeyobject. - Set the
Cipherobject for encryption or decryption. - Get the encrypted or decrypted output.
I'll discuss each of them in detail.
Step 1: Instantiate the Cipher class
The first step is to instantiate the Cipher class for some specific encryption algorithm, as shown in the following code snippet:
Cipher cipher =
Cipher.getInstance(
Cipher.ALG_DES_CBC_NOPAD, true);
|
Note that I've created an instance of the Cipher class by calling the getInstance() method of the Cipher class. The getInstance() method is static and takes two parameters. The first parameter is a byte-code identifier that specifies the encryption algorithm that you wish to use. The Java Card API provides a list of identifiers for popular encryption algorithms, such as the ones shown in Listing 3.
Listing 3. Identifiers for algorithms supported in the Java Card API
1. ALG_DES_CBC_NOPAD for the DES CBC algorithm without padding
2. ALG_DES_CBC_ISO9797_M1 for DES CBC algorithm with ISO9797 padding
of method 1 scheme
3. ALG_DES_CBC_ISO9797_M2 for DES CBC algorithm with ISO9797 padding
of method 2 scheme
4. ALG_DES_CBC_PKCS5 for DES CBC algorithm with PKCS5 scheme padding
5. ALG_AES_BLOCK_128_CBC_NOPAD for AES block 128 CBC algorithm without
padding
6. ALG_AES_BLOCK_128_ECB_NOPAD for AES block 128 ECB algorithm without
padding
7. ALG_DES_ECB_NOPAD for DES EBC algorithm without padding
8. ALG_DES_EBC_ISO9797_M1 for DES EBC algorithm with ISO9797 padding
of method 1 scheme
9. ALG_DES_EBC_ISO9797_M2 for DES EBC algorithm with ISO9797 padding
of method 2 scheme
10. ALG_RSA_ISO14888 for RSA algorithm with ISO14888 padding
11. ALG_RSA_NOPAD for RSA algorithm without padding
12. ALG_RSA_PKCS1 for RSA algorithm with PKCS1 padding
|
The second parameter specifies whether you wish to use the Cipher class outside the applet or not. The true value of this parameter means the instance of the Cipher class will be shared among different applets.
Notice that the getInstance() method call shown above returns a Cipher object capable of encrypting or decrypting according to the DES CBC algorithm (see Resources) without padding bytes.
Step 2: Instantiate a DESKey object
The next step is to instantiate a DESKey object and set the key bytes into that object. The DESKey object is capable of handling encryption keys according to the DES CBC algorithm with or without padding bytes.
DESKey desKey = (DESKey)
KeyBuilder.buildKey(
KeyBuilder.TYPE_DES,
KeyBuilder.LENGTH_DES,
false);
byte [] keyBytes =
{(byte)0x01,(byte)0x02,(byte)0x03,(byte)0x04};}
desKey.setKey(keyBytes, (short)0);
|
Step 3: Set the Cipher object for encryption or decryption
The next task is to call the init() method of the Cipher object of Step 1 to initialize it with the DES key and set the cipher for either encryption or decryption.
cipher.init(desKey, Cipher.MODE_DECRYPT); |
As you can see from the above code snippet, the init() method takes two parameters. The first parameter is the DESKey object of Step 2. The second parameter is a Boolean type value that specifies whether you want to initialize the cipher for encryption or decryption.
Step 4: Get encrypted or decrypted output
The last step is to call the doFinal() method to get encrypted or decrypted output, as shown here:
byte []inputData = {(byte)0x01,(byte)0x02,(byte)0x03,(byte)0x04};}
byte []outputData = new byte[8];
cipher.doFinal(
inputData,
(short)0,
(short)inputData.length,
outputData,
(short)0);
|
The doFinal() method takes five parameters. The first three parameters specify the input data that you want to encrypt or decrypt. The last two parameters specify a byte array (or a place holder) for the output data after encryption or decryption. The parameters are as follows:
- The first parameter (
inputData) is an array that holds input data. - The second parameter is an offset into the
inputDataarray from where the cipher will start reading the input data. - The third parameter is the number of bytes of the
inputDatabyte array to be encrypted or decrypted. - The fourth parameter is an empty byte array that will contain the results after encryption or decryption.
- The last parameter is an offset value that specifies where to start writing the output data in the
outputDatabyte array.
The doFinal() method will encrypt or decrypt the input data and populate the output byte array with the resulting data.
Limits of Java Card cryptography
In the previous section I described the code that is supposed to provide cryptographic support for Java Card applications. Unfortunately, if you were to to use this code in your Java Card applet it would throw a null pointer exception in Step 1. This is because I instantiated the Cipher class for the ALG_DES_CBC_NOPAD algorithm. The current version of JCDK does not support this algorithm, and therefore, instead of returning a Cipher object, it would throw an exception.
In fact, instead of the long list of algorithms in Listing 3, only the following three algorithms are currently implemented in JCDK:
- ALG_DES_CBC_ISO9797_M2
- ALG_AES_BLOCK_128_CBC_NOPAD
- ALG_RSA_PKCS1
None of these three algorithms is supported by the KDC server that I used in my series of articles on Kerberos authentication ("Lock down J2ME applications with Kerberos;" see Resources). I decided to use the ALG_DES_CBC_NOPAD algorithm in this article because most KDC servers support the DES algorithm, and it is one of the most popular encryption algorithms used in Kerberos-based systems (see Resources).
However, this doesn't mean that my sample e-bank application will only work with a few KDC servers: you should be able to use the e-bank application with any KDC server compliant with Kerberos version 5. In fact, I will ensure this by demonstrating, in the next section, a very flexible technique for overcoming the limitations of cryptography support in Java Card.
Extending Java Card cryptography support
In order to extend the JCDK to support the ALG_DES_CBC_NOPAD algorithm, I will wrap my extended decryption logic in a class named ExtendedCipher. The ExtendedCipher class will contain just one method named decrypt(), that will take three parameters:
- The first parameter named
inputDatais a byte array that contains encrypted data that needs to be decrypted. - The second parameter named
keyBytesis also a byte array that contains the cryptographic key needed for decryption. - The third parameter is the identifier for the cryptographic algorithm that the user wants to use for decryption.
I've included two implementations of the ExtendedCipher class in the article source. The first implementation, shown in Listing 4, uses the same four-step logic that I explained in the previous section. You can use this implementation of the ExtendedCipher class if your KDC server uses one of the three encryption algorithms supported by the Java Card implementation.
The other implementation of the ExtendedCipher class (included in the article source), contains the decryption logic according to the ALG_DES_CBC_NOPAD algorithm. I've borrowed the implementation logic for this ExtendedCipher class from the Java-based, open-source implementation of cryptographic algorithms called Bouncy Castle (see Resources). I've modified the Bouncy Castle code to make it work in a Java Card application, as you will see below.
You can use either of my two implementations of the ExtendedCipher class to run the article code, depending on what KDC server you're using. You'll also find two versions of the JavaCardKerberosKey applet included with the article source. The first version includes Listing 4 as part of the JavaCardKerberosKey applet, while the second version includes the ExtendedCipher class that I built using Bouncy Castle. Each version includes both the source code and its compiled form. The readme file in the source contains simple instructions for running both versions.
Listing 4. The ExtendedCipher class using the Java Card API
public class ExtendedCipher {
protected byte[] decrypt ( byte[] inputData ,
byte[] keyBytes,
byte algoId) {
byte []outputData = new byte[8];
Cipher cipher = Cipher.getInstance(algoId,
true);
DESKey desKey = (DESKey)
KeyBuilder.buildKey(
KeyBuilder.TYPE_DES,
KeyBuilder.LENGTH_DES,
false);
desKey.setKey(keyBytes,
(short)0);
cipher.init(desKey,
Cipher.MODE_DECRYPT);
cipher.doFinal(inputData,
(short)0,
(short)inputData.length,
outputData,
(short)0);
return outputData;
}//decrypt
}//ExtendedCipher
|
The strategy outlined in the next section will allow you to borrow from Bouncy Castle the support of algorithms not supported by the Java Card implementation.
See Resources to download Bouncy Castle's J2ME-based cryptographic implementation in zipped form. The zip file contains two files that you will need to use in your Java Card application:
CBCBlockCipherDESEngine
I copied the code of these files into the ExtendedCipher class and modified the code so as to make it work as a Java Card application. The only major consideration while modifying the code is to cater for limited data types supported in the Java Card specification. Because the Java Card environment is built keeping in mind the limited resources available in smart cards, the Java Card environment does not support int, float, long, String, and other bigger primitive data types. Only boolean, byte, short, and int data types are available in Java Card applications.
Another limitation is that you cannot use an int as an index value while accessing a particular entry in an array.
Therefore, I had to use only the available data types while implementing the ExtendedCipher class. Now, look at the following line of code from the Bouncy Castle's J2ME-based implementation:
pc1m[j] = ((key[l >>> 3] & bytebit[l & 07]) != 0);
|
If you keep in mind the data type limitations of Java Card, then the modified equivalent Java Card code will need to look as follows:
pc1m[j] = ((short) (key[(short)(l >>> 3)]
& bytebit[(short)(l & 07)]) != 0);
|
Compare the ExtendedCipher class included in the article source with the CBCBlockCipher and DESEngine classes from Bouncy Castle and you will see that I've used the same idea throughout the ExtendedCipher class.
With the JCDK extended to support the ALG_DES_CBC_NOPAD, I can now return to the cryptography procedure outlined in Listing 2. You will recall that in Step 3 of the four-step getDecryptedSessionKey() method shown in Listing 2, I called the parse() method. The parse() method takes just one parameter, named keyWrapperStruct, which is a byte array. The keyWrapperStruct byte array follows the ASN.1 syntax, which I explained in-depth in my Kerberos series (see Resources).
The name of the Kerberos structure contained in the keyWrapperStruct byte array is EncKDCRepPart. Figure 1 shows the structure of the data contained in the keyWrapperStruct byte array.
Figure 1. The structure of the data contained in the keyWrapperStruct byte array
Note that I've used several ASN.1 processing methods that I developed for the Kerberos series as helper methods for parsing the structure shown in Figure 1. The methods are as follows:
getASN1Structure()isASN1Structure()getContents()getNumberOfLengthBytes()getIntegerValue()getLength()
Refer to that article to learn how the methods work.
Looking back at the parse() method in Listing 2, you see that I've marked eight steps in the code. I'll explain each step in detail.
- Before parsing the structure I eliminate the initial 24 bytes from the structure. These bytes are not required to extract the session key.
- I check whether the
keyWrapperStructis an ASN.1 structure. To check this, I call a helper method namedisASN1Structure(). (Note that I first used this helper method in the Kerberos article.) If theisASN1Structure()method returnsfalse, a null pointer will be returned to theKerberosEBankMIDlet. - I call the
getContents()helper method passing it thekeyWrapperStructbyte array. ThegetContents()method takes a byte array containing an ASN.1 structure, parses the structure, and then extracts its contents. ThegetContents()method will return the content part of theEncKDCRepPartstructure. I store the returned byte array in a variable namedenc_rep_part_content. The contents of theenc_rep_part_contentbyte array are shown in Figure 2. You can see from Figure 2 that it contains aSEQUENCE of ASN.1structures:
Figure 2. Contents of the EncKDCRepPart structure
- Next, I call a method named
getASN1Structure(). ThegetASN1Structure()method finds and extracts a particularASN.1 SEQUENCEfrom a byte array consisting of aSEQUENCE of ASN.1structures. TheKeystructure is the topmost ASN.1 structure in Figure 2. So when I call thegetASN1Structure()method, I will get a byte array representing theKeystructure. TheKeystructure is shown in Figure 3.
Figure 3. The Key structure
- As you've likely guessed, I again need to call the
getContents()method. ThegetContents()method will return a byte array that contains the contents of theKeystructure. I also store the byte array in a variable namedkey_content. The contents of thekey_contentbyte array are shown in Figure 4. You can see from Figure 4 that it contains aSEQUENCE of ASN.1structures.
Figure 4. Contents of the Key structure
- From the
SEQUENCE of ASN.1structures shown in Figure 4, I need to fetch the key structure. Therefore, I again call thegetASN1Structure()method and store the returned byte array in a variable namedenc_key_val. Thisenc_key_valis an ASN.1 structure and wraps the actual session key. - Next, I call the
getContents()method to get contents of theenc_key_valASN.1 structure. This time thegetContents()method returns the desired 8-byte session key after parsing theenc_key_valstructure. - Finally, the
parse()method returns the session key to thegetDecryptedSessionKey()method.
Up until now I've discussed and implemented the features that are required for the JavaCardKerberosKey applet. For the remainder of this article I'll focus on how a banking MIDlet, KerberosEBank, uses the SATSA API to communicate with the JavaCardKerberosKey applet.
The KerberosEBank MIDlet will be familiar to you if you've read my earlier series on Kerberos. It's taken from the Kerberos-based J2ME application that I developed in the third article of that series (see Resources). I've made a few changes to the MIDlet class to
enable it to interact with the Java Card environment, so I'll start by discussing those changes in detail.
You'll find the complete KerberosEBank MIDlet in the source code that comes with this article. When you run the KerberosEBank MIDlet, you will get the screen shown in Figure 5.
Figure 5. Screen shot of the KerberosEBank MIDlet
Figure 5 shows four data-entry fields:
- The "Username" field takes the username of the person who wants to use the financial services of the e-bank application.
- The "User's key" field takes the user's key that the e-bank provided to the user. Note that when the e-bank generated the user's key, it was originally a sequence of four bytes. I've generated a human-readable value from the hex-value of the original four bytes. For example, if the original hex-value was
0xa8, 0xae, 0x34, 0xda, the user will entera8ae34dain the user's key field. TheKerberosEBankMIDlet will transform the readable format back to the original hex-value. - The "Amount" field allows the entry of the amount of money that the user wants to pay.
- The "Pay to" field contains the user name of the beneficiary.
After entering the data, the user will press the Pay button. The event handler for the Pay button (that is, the sendMoney() method in Listing 5) will do the following:
- Author a TGT request, send the request to the server, and receive the TGT response.
- Extract the encrypted portion from the TGT.
- Convert the user's key to its original hex value.
- Extract a remote reference of the
KeyManagerclass of theJavaCardKerberosKeyapplet. - Pass on the encrypted data and the user's key to the
JavaCardKerberosKeyapplet. - Get the session key from the
JavaCardKerberosKeyapplet. - Author a service ticket request, send the request to the KDC, and receive the service ticket response.
- Extract the service ticket and the sub-session key from the service ticket response.
- Author and send a context establishment request to the e-bank's business logic server, receive the response, and parse it to make sure that the server agrees to establish a new secure context.
- Author a secure message, send the message to the server, and receive the response from the server.
- Decode the message from the server.
Steps 2 to 6 are specific to the Java Card implementation of the KerberosEBank MIDlet. The rest were originally used for the Kerberos-based J2ME application. The additional steps are to enable Java Card to verify the user.
In Listing 5 you can see the entire KerberosEBank MIDlet with the additional steps annotated (don't worry if you're confused -- I'll explain each step in just a second!).
Listing 5. The KerberosEBank MIDlet
public class KerberosEBank extends MIDlet implements
CommandListener, Runnable {
private JavaCardRMIConnection mConnection;
public KerberosEBank (){
}
public void startApp() {
}//startApp()
public void pauseApp() {
}//pauseApp()
public void destroyApp(boolean unconditional) {
}//destroyApp
public void commandAction(Command c, Displayable s){
}//commandaction
public void sendMoney() {
System.out.println("MIDlet... SendMoney() Starts");
String userName = txt_userName.getString();
String password = txt_password.getString();
kc.setParameters(userName,
password,
realmName);
//Author request to fetch TGT
response = kc.getTicketResponse (userName,
kdcServerName,
realmName,
null,
null);
tk = new TicketAndKey();
byte[] sessionkey = null;
// Step 1: Extracting the encrypted portion from TGT
byte[] ticketCipher = kc.getTicketCipher(response, tk);
// Step 2: Converting the user's key
byte[] userKey = getUserKey(password);
try {
// Step 3B: Getting a remote reference
Object reference = mConnection.getInitialReference();
// Step 3C: Getting a remote reference
KeyManager = (KeyManager)reference;
// Step 4: Passing the data to Java Card applet
short length = (short)ticketCipher.length;
byte[] firstBlock = new byte[(length/2)];
byte[] secondBlock = new byte[(length/2) + userKey.length];
for ( int i = 0; i < length/2; i++ )
firstBlock [i] = ticketCipher[i];
for (int x = 0; x < userKey.length; x++ )
secondBlock [x] = userKey[x];
for ( int i = 4, j = length/2; j < length; i++ )
secondBlock [i] = ticketCipher[j++];
// Step 5: Extracting the session key
km.getKey ( firstBlock, length );
sessionkey = km.getKey ( secondBlock );
}
catch (Exception e) {
System.out.println("Exception... " + e.toString());
}
tk.setKey(sessionkey);
//MIDLet...Getting Service Ticket (TGS)
}//sendMoney()
public synchronized void run() {
try {
dc = (DatagramConnection)Connector.open
("datagram://"+kdcAddress+":"+kdcPort);
kc = new KerberosClient(dc);
sc = (SocketConnection)Connector.open
("socket://"+e_bankAddress+":"+e_bankPort);
sc.setSocketOption(SocketConnection.KEEPALIVE, 1);
// Step 3A: Getting a remote reference
String kRMIURL = "jcrmi:0;AID=a0.0.0.0.62.3.1.c.9.1";
mConnection =
(JavaCardRMIConnection)Connector.open(kRMIURL);
is = sc.openDataInputStream();
os = sc.openDataOutputStream();
}
catch (ConnectionNotFoundException ce) {
System.out.println(
"Socket connection to server not found....");
}
catch (IOException ie) {
ie.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
}// run
public void transactionForm(){
}// transactionForm
public void showTransResult(String info, String message) {
}// showTransResult
public byte[] getUserKey(String pwd){
}// getUserKey
}//J2MEClientMIDlet
|
I'll discuss the steps for the sendMoney() method one by one.
Step 1. Extract the encrypted portion from the TGT
You will recall from my discussion of the architecture of an e-bank application in Part 1 that the KerberosEBank MIDlet uses the JavaCardKerberosKey applet to decrypt the encrypted portion of the TGT and extract the needed session key. Before that, the KerberosEBank MIDlet requests a TGT from the e-bank KDC server. On receipt of the TGT, the MIDlet extracts the encrypted portion of the TGT and hands it over to the Java Card applet.
In this step, I call a method named getTicketCipher() of a class named JavaCardKerberosClient. I've extended the JavaCardKerberosClient class from an existing KerberosClient class (from my Kerberos series; see Resources). Whereas in the earlier application all the decryption was done by the KerberosClient class, this time decryption is performed in the JavaCardKerberosKey applet. You can see the JavaCardKerberosClient class in Listing 6.
Listing 6. The JavaCardKerberosClient class
public class JavaCardKerberosClient extends KerberosClient {
public byte[] getTicketCipher(byte[] ticketResponse,
TicketAndKey ticketAndKey) {
int offset = 0;
/***** Step 1:*****/
if ((isASN1Structure(ticketResponse[0], APPLICATION_TYPE, 11))
||
(isASN1Structure(ticketResponse[0], APPLICATION_TYPE, 13)))
{
try {
/***** Step 2:*****/
byte[] kdc_rep_sequence = getContents(ticketResponse);
/***** Step 3:*****/
if (isSequence(kdc_rep_sequence[0])) {
/***** Step 4:*****/
byte[] kdc_rep_sequenceContent =
getContents(kdc_rep_sequence);
/***** Step 5:*****/
byte[] ticket = getContents(
getASN1Structure(kdc_rep_sequenceContent,
CONTEXT_SPECIFIC, 5));
ticketAndKey.setTicket(ticket);
/***** Step 6:*****/
byte[] enc_part =
getASN1Structure(kdc_rep_sequenceContent,
CONTEXT_SPECIFIC, 6);
if (enc_part!=null) {
/***** Step 7:*****/
byte[] enc_data_sequence =
getContents(enc_part);
if (isSequence(enc_data_sequence[0])) {
byte[] eType = getASN1Structure(
getContents(
enc_data_sequence),
CONTEXT_SPECIFIC, 0);
if (eType != null) {
int eTypeValue = getIntegerValue(
getContents(
getContents(eType)));
if ( eTypeValue == 3) {
byte[] cipher =
getASN1Structure(
getContents(
enc_data_sequence),
CONTEXT_SPECIFIC, 2);
byte[] cipherText =
getContents(
getContents(cipher));
if (cipherText != null)
return cipherText;
else
return null;
}else
return null;
}else
return null;
}else
return null;
}else
return null;
}else
return null;
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
else
return null;
}//getTicketCipher()
public byte[] getUserKey(String userKey) {
byte [] byteRep = new byte[8];
for (int i = 0; i < 8 ; i++){
char charAt = userKey.charAt(i);
switch(charAt) {
case 'a':
byteRep [i] = 0xa;
break;
case 'b':
byteRep [i] = 0xb;
break;
case 'c':
byteRep [i] = 0xc;
break;
case 'd':
byteRep [i] = 0xd;
break;
case 'e':
byteRep [i] = 0xe;
break;
case 'f':
byteRep [i] = 0xf;
break;
case '1':
byteRep [i] = 0x1;
break;
case '2':
byteRep [i] = 0x2;
break;
case '3':
byteRep [i] = 0x3;
break;
case '4':
byteRep [i] = 0x4;
break;
case '5':
byteRep [i] = 0x5;
break;
case '6':
byteRep [i] = 0x6;
break;
case '7':
byteRep [i] = 0x7;
break;
case '8':
byteRep [i] = 0x8;
break;
case '9':
byteRep [i] = 0x9;
break;
case '0':
byteRep [i] = 0x0;
break;
}
}
byte [] key = new byte[4];
byte temp;
for (int i=0,j=0; i < byteRep.length && j < 4; j++){
temp = (byte) ( byteRep[i++] << 4 );
key[j] = (byte) ( temp | byteRep[i++] );
}
return key;
}//getUserKey()
}//JavaCardKerberosClient
|
The JavaCardKerberosClient class has two methods, namely getTicketCipher() and getUserKey(). I'll talk about the second method in just a moment.
The first method, getTicketCipher(), takes two parameters: a ticketResponse byte array and a TicketAndKey object. The ticketResponse contains the TGT response from the KDC server. The TicketAndKey is a structure that wraps the key and the ticket. The getTicketCipher() method sets the ticket in the TicketAndKey object. The KerberosEBank MIDlet later extracts the session key from the TGT and also sets the key in the same TicketAndKey structure.
The getTicketCipher() method then simply extracts the encrypted portion from the TGT and returns it to the KerberosEBank MIDlet.
Step 2. Convert the user's key
Here I call the getUserKey() method of the JavaCardKerberosClient class. The getUserKey() method just takes one parameter, named key, which is a string. This is the key that the user entered from his or her J2ME-enabled device.
I discussed the dual-factor security issue in Part 1 and here I will return to it. The e-bank generated an 8-byte key and divided it into two equal parts. The first part is named as the user's key and the second part is named as the e-bank's key. The e-bank application provides the user's key to the user as a string.
The getUserKey() method converts the string into a hex bytes array. After successful conversion it returns the key to the KerberosEBank MIDlet. The MIDlet passes on this key to verify the user of the Java Card.
Step 3. Get a remote reference
The KerberosEBank MIDlet needs a reference of a Java Card class to communicate with the JavaCardKerberosKey applet. The KerberosEBank MIDlet performs three steps to get a remote reference from the JavaCardKerberosKey applet. The steps are marked in Listing 5.
- Step 3A: This step is found in the
run()method of theKerberosEBankMIDlet shown in Listing 5. Therun()method is the first method to be called when you run the MIDlet. In this step, I create a connection with theJavaCardKerberosKeyapplet using a class namedJavaCardRMIConnection. TheJavaCardRMIConnectionclass is part of the SATSA API.
I use the static methodopen()of theConnectorclass to create a connection. The call to theConnector.open()method returns an instance of aConnectionclass that I've typecast into aJavaCardRMIConnectionobject. TheConnector.open()method just takes one parameter, which is the AID (application ID) of theJavaCardKerberosKeyapplet in the Java Card. You'll recall from Part 1 that each applet in the Java Card is recognized with the help of an AID. SATSA uses the same AID to identify a particular Java Card applet. - Step 3B: After I've established a connection with the
JavaCardKerberosKeyapplet, I can get a remote reference from the Java Card applet using this connection. A MIDlet application can communicate with Java Card applets in one of two ways: synchronously or asynchronously, as I explained in Part 1. I've used RMI-based synchronous communications for the Java Card application.
Next, I call thegetInitialReference()method of theJavaCardRMIConnectionclass. This method will return the reference of the class that has implemented theRemoteinterface. I store that reference in an instance ofObject. - Step 3C: In the
JavaCardKerberosKeyapplet, theKeyManagerclass implements theRemoteinterface. Therefore, the reference I received in Step 3B is of theKeyManagerclass. But to compile the application I also need this class on the client side. The RMI mechanism works on the concept of a "stub" that can be used to compile an application using a remote class. Through this stub class, I have theKeyManagerclass on the client side so I can compile theKerberosEBankclass. (See the readme file in the article source for the steps to generate the stub for theKeyManagerclass.)
In this step, I typecast the reference object returned by thegetInitialReference()method into aKeyManagerobject. Now theKerberosEBankMIDlet has aKeyManagerreference on the client side. TheKerberosEBankcan use this reference to call a method namedgetKey(), that belongs to theKeyManagerclass.
Step 4. Pass the data to Java Card applet
Now the KerberosEBank MIDlet is all set to pass on the data to the JavaCardKerberosKey applet. I have to pass on the encrypted data and the user's key to the JavaCardKerberosKey applet. But here I face a problem: the Java Card can only receive a limited amount of data.
In fact, the Java Card can only receive 133 bytes, according to the Java Card API specification. Out of 133 bytes, the first five contain the header and the remaining 128 bytes contain the content. On the other hand, the length of encrypted data is 224 bytes and the length of user's key is 4 bytes. So, I have 228 bytes to pass on to the JavaCardKerberosKey applet.
In order to solve this problem, I divide this encrypted data byte array into two parts, passing the data in two method calls to the JavaCardKerberosKey applet. I store the first half of the encrypted data in a firstBlock byte array, and the second half in a byte array named secondBlock.
Step 5. Extract the session key from the Java Card applet
My next step is to call the getKey() method, passing to it all the encrypted data. The call to the getKey() method returns the session key in case of successful extraction of the key.
The JavaCardKerberosKey applet has successfully completed its job by acquiring the session key from the encrypted portion of the TGT.
After getting the session key, the KerberosEBank will continue with the remaining 11 steps of Listing 5.
For most kinds of Java applications, you can use the System.out.print command to display data on the output console at run time and view the behavior of the application. You can also write the data to a file so that you can later study the application's run time behavior at different points. Unfortunately, you can't use either of these techniques to debug your Java Card applications. The JCDK doesn't have a console, so you can't use the System.out.print command to view the data at run time. The Java Card API also does not support creating a file on the Java Card. At present, the only way to debug a Java Card application is to rely on data returned to a client application like a J2ME MIDlet.
I used this technique to debug the e-bank application as I developed it. Whenever I felt the need to check the application data, I sent the data to the KerberosEBank MIDlet. The MIDlet has a console that can be used to show data coming from a Java Card application. For example, when I was parsing the ticket to extract the session key, I needed to check the values of different variables at run time. There I checked the values of different variables by returning them to the MIDlet one by one.
Let me show you how I returned these values. As you know, the KeyManager is the only interface exposed to the KerberosEBank MIDlet. The KerberosEBank MIDlet calls the getKey() method of the KeyManager instance, which returns a byte array. So any data that I needed to check at run time had to be returned through the getKey() method.
The KerberosEBank MIDlet console
In Listing 7 you can see that I've returned the enc_rep_part_content byte array in Step 3. Here I wanted to check the value of the enc_rep_part_content byte array; that is why I've commented the rest of the code.
Listing 7. The debugging code
private byte[] parse(byte[] keyWrapperStruct) {
/***** Step 1 *****/
byte dataOffset = 24;
byte[] keyWrapperStructAfterRemoving24Bytes = new byte[(short)(
keyWrapperStruct.length- dataOffset)];
for (short i=0; i < keyWrapperStruct.length; i++)
keyWrapperStructAfterRemoving24Bytes [i] =
keyWrapperStruct [(short)(i+dataOffset)];
if (keyWrapperStructAfterRemoving24Bytes != null) {
/***** Step 2 *****/
if((isASN1Structure(
keyWrapperStructAfterRemoving24Bytes [(short)0],
APPLICATION_TYPE,25)) ||
(isASN1Structure(
keyWrapperStructAfterRemoving24Bytes [(short)0],
APPLICATION_TYPE,26))) {
/***** Step 3 *****/
byte[] enc_rep_part_content =
getContents(getContents(
keyWrapperStructAfterRemoving24Bytes));
return enc_rep_part_content;
/***** Step 4 *****/
//byte[] enc_key_structure =
// getASN1Structure(enc_rep_part_content,
// CONTEXT_SPECIFIC, 0);
/***** Step 5 *****/
//byte[] key_content =
// getContents(getContents(key_content));
/***** Step 6 *****/
//byte[] enc_key_val =
// getASN1Structure(enc_key_sequence,
// CONTEXT_SPECIFIC, 1);
/***** Step 7 *****/
//byte[] enc_key =
// getContents(getContents(enc_key_val));
/***** Step 8 *****/
//return enc_key;
}
}
}//parse()
|
You can use a similar strategy to check other application data as well. I've included a debugging version of the applet in the article source. The debugging version contains commented lines of code that will show you how I returned various different types of data.
In this two-part series I have explained and demonstrated the use of dual-factor security in J2ME applications. In Part 1, I provided an overview of smart cards and Java Card technology, then introduced a sample e-bank application. I used the e-bank application to demonstrate how to load a Java Card application onto a Java Card and how to use a Java Card applet with a J2ME client application. I also discussed the major classes of the e-bank application: KerberosSecurityService and KerberosKeyManager.
In Part 2, I picked up right where I left off, with a look at the UserPin and EBankPIN classes. These classes enable the simple authentication procedure of the e-bank Java Card application. I then discussed the JCDK's support for cryptography and showed you a simple
workaround for one of its major shortcomings. For the remainder of Part 2, I focused on the Security and Trust Services API (SATSA), using a J2ME MIDlet originally developed for my three-part series on Kerberos (see Resources) to demonstrate the role of SATSA in making Java Card applications usable by J2ME devices.
See Resources to learn more about the security topics discussed here. See the Download section for the article source, where you will find many extra details not included in the article discussion.
| Description | Name | Size | Download method |
|---|---|---|---|
| Code sample | wi-satsa2source.zip | 143KB | HTTP |
Information about download methods
Learn
- Get the lowdown from the Kerberos Security Guide.
- Lock down J2ME applications with Kerberos, Part 1 (developerWorks, October 2003): Build a J2ME MIDlet that uses Kerberos to protect financial data (three parts).
-
Build smart J2ME mobile applications (developerWorks, April 2005): A step-by-step guide to developing J2ME applications.
- Simplify enterprise Java authentication with single sign-on (developerWorks, September 2003): Learn to implement SSO on the Java platform.
-
Introduction to Smart Cards (Dhar's blog, November 2004) is an introduction to smart card technology.
- The official Java Card Platform Specification is a set of enhancements to ease the alignment with smart card industry standards.
-
Using WebSphere Studio Device Developer to Build Embedded Java Applications (IBM RedBooks) is a guide to developing Java-based mobile applications using IBM's WSDD.
-
Smart card and secure identity from IBM provides an overview of IBM's smart card solutions, based on Java Card technology.
Get products and technologies
-
MIT Kerberos: The Kerberos implementation from MIT.
-
Shishi: A free Kerberos implementation.
- Heimdal: Another free Kerberos implementation.
-
The JCDK:
The Java Card Development Kit includes the complete Java Card development
environment.
-
The Legion of the Bouncy Castle: Includes the cryptotography library for MIDP used in this article.
-
ASN.1 zip: Download the complete set of ASN.1 documentation and encoding rules.
Discuss
- developerWorks
blogs: Get involved in the developerWorks community.
Faheem Khan is an independent software consultant specializing in Enterprise Application Integration (EAI) and B2B solutions. He can be reached at fkhan872@yahoo.com.





