Skip to main content

Securing Java Card applications, Part 2: Integrating Java Card into J2ME applications

Dual-factor security with Java Card and J2ME

Faheem Khan (fkhan872@yahoo.com)Freelance Consultant
Faheem Khan is an independent software consultant specializing in Enterprise Application Integration (EAI) and B2B solutions. He can be reached at fkhan872@yahoo.com.

Summary:  Get up to date using the Java™ Card API to bring Kerberos-based security to mobile applications. Learn the pros and cons of the current cryptographic support for the Java Card API, then get a demonstration of a roll-your-own cryptography solution.

View more content in this series

Date:  13 Dec 2005
Level:  Intermediate
Activity:  1201 views

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.

Remember this!

Dual-factor security is an authentication mechanism in which authentication is based on both something that a user knows (such as a PIN code) and something that a user carries or possesses (such as a Java Card). Much of the discussion in this series involves dual-factor security.

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.

The UserPIN class

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
      

How UserPIN works

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:

  • data is a byte array that contains an encrypted Kerberos structure and the user's key. (I explained this while discussing the processCommand() method of Part 1, Listing 5.)
  • offset is the index number where the user's key starts in the data byte array.
  • length specifies the length of the user's key (that is, how many bytes need to be read from the data byte 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.

Security details

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.


The EBankPIN class

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
      

How EBankPIN works

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:

  1. I concatenate the userPIN and eBankPIN byte arrays to form a single decryption key. I also store the key in a variable named decryptionKey. I'll use this key to decrypt the encrypted data.
  2. I instantiate a class named ExtendedCipher. Later in the article I'll discuss the ExtendedCipher class in detail. Here, I call a method named decrypt() of the ExtendedCipher class. The decrypt() 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.
    The decrypt() method decrypts the encrypted data using the decryption key and returns the decrypted data. I store the decrypted data in a uvariable named plainText.
  3. I call the parse() method passing it the plainText from Step 3. The parse() method processes the plain-text data to extract the session key. The processing involves a number of steps that I will shortly explain. The parse() method returns the session key.
  4. The getDecryptedSessionKey() method returns the session key to the KerberosKeyManager class.

I'll return to Listing 2 for a look at the parsing procedure to extract the session key later in the article.


Cryptography in Java Card

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:

  1. Instantiate the Cipher class.
  2. Instantiate a DESKey object.
  3. Set the Cipher object for encryption or decryption.
  4. 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 inputData array from where the cipher will start reading the input data.
  • The third parameter is the number of bytes of the inputData byte 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 outputData byte 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:

  1. ALG_DES_CBC_ISO9797_M2
  2. ALG_AES_BLOCK_128_CBC_NOPAD
  3. 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:

  1. The first parameter named inputData is a byte array that contains encrypted data that needs to be decrypted.
  2. The second parameter named keyBytes is also a byte array that contains the cryptographic key needed for decryption.
  3. 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.

Two kinds of ExtendedCipher

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.

The Bouncy Castle solution

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:

  1. CBCBlockCipher
  2. DESEngine

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.


Extracting the session key

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
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.

The parse() method

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.

  1. Before parsing the structure I eliminate the initial 24 bytes from the structure. These bytes are not required to extract the session key.
  2. I check whether the keyWrapperStruct is an ASN.1 structure. To check this, I call a helper method named isASN1Structure(). (Note that I first used this helper method in the Kerberos article.) If the isASN1Structure() method returns false, a null pointer will be returned to the KerberosEBank MIDlet.
  3. I call the getContents() helper method passing it the keyWrapperStruct byte array. The getContents() method takes a byte array containing an ASN.1 structure, parses the structure, and then extracts its contents. The getContents() method will return the content part of the EncKDCRepPart structure. I store the returned byte array in a variable named enc_rep_part_content. The contents of the enc_rep_part_content byte array are shown in Figure 2. You can see from Figure 2 that it contains a SEQUENCE of ASN.1 structures:


    Figure 2. Contents of the EncKDCRepPart structure
    Contents of the EncKDCRepPart structure


  4. Next, I call a method named getASN1Structure(). The getASN1Structure() method finds and extracts a particular ASN.1 SEQUENCE from a byte array consisting of a SEQUENCE of ASN.1 structures. The Key structure is the topmost ASN.1 structure in Figure 2. So when I call the getASN1Structure() method, I will get a byte array representing the Key structure. The Key structure is shown in Figure 3.


    Figure 3. The Key structure
    The Key structure The Key structure


  5. As you've likely guessed, I again need to call the getContents() method. The getContents() method will return a byte array that contains the contents of the Key structure. I also store the byte array in a variable named key_content. The contents of the key_content byte array are shown in Figure 4. You can see from Figure 4 that it contains a SEQUENCE of ASN.1 structures.


    Figure 4. Contents of the Key structure
    Contents of the Key structure


  6. From the SEQUENCE of ASN.1 structures shown in Figure 4, I need to fetch the key structure. Therefore, I again call the getASN1Structure() method and store the returned byte array in a variable named enc_key_val. This enc_key_val is an ASN.1 structure and wraps the actual session key.
  7. Next, I call the getContents() method to get contents of the enc_key_val ASN.1 structure. This time the getContents() method returns the desired 8-byte session key after parsing the enc_key_val structure.
  8. Finally, the parse() method returns the session key to the getDecryptedSessionKey() method.

The SATSA API

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.

The KerberosEBank MIDlet

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
MIDlet screen


Figure 5 shows four data-entry fields:

  1. The "Username" field takes the username of the person who wants to use the financial services of the e-bank application.
  2. 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 enter a8ae34da in the user's key field. The KerberosEBank MIDlet will transform the readable format back to the original hex-value.
  3. The "Amount" field allows the entry of the amount of money that the user wants to pay.
  4. 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:

  1. Author a TGT request, send the request to the server, and receive the TGT response.
  2. Extract the encrypted portion from the TGT.
  3. Convert the user's key to its original hex value.
  4. Extract a remote reference of the KeyManager class of the JavaCardKerberosKey applet.
  5. Pass on the encrypted data and the user's key to the JavaCardKerberosKey applet.
  6. Get the session key from the JavaCardKerberosKey applet.
  7. Author a service ticket request, send the request to the KDC, and receive the service ticket response.
  8. Extract the service ticket and the sub-session key from the service ticket response.
  9. 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.
  10. Author a secure message, send the message to the server, and receive the response from the server.
  11. 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.

Six steps to sendMoney()

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 the KerberosEBank MIDlet shown in Listing 5. The run() method is the first method to be called when you run the MIDlet. In this step, I create a connection with the JavaCardKerberosKey applet using a class named JavaCardRMIConnection. The JavaCardRMIConnection class is part of the SATSA API.

    I use the static method open() of the Connector class to create a connection. The call to the Connector.open() method returns an instance of a Connection class that I've typecast into a JavaCardRMIConnection object. The Connector.open() method just takes one parameter, which is the AID (application ID) of the JavaCardKerberosKey applet 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 JavaCardKerberosKey applet, 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 the getInitialReference() method of the JavaCardRMIConnection class. This method will return the reference of the class that has implemented the Remote interface. I store that reference in an instance of Object.

  • Step 3C: In the JavaCardKerberosKey applet, the KeyManager class implements the Remote interface. Therefore, the reference I received in Step 3B is of the KeyManager class. 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 the KeyManager class on the client side so I can compile the KerberosEBank class. (See the readme file in the article source for the steps to generate the stub for the KeyManager class.)

    In this step, I typecast the reference object returned by the getInitialReference() method into a KeyManager object. Now the KerberosEBank MIDlet has a KeyManager reference on the client side. The KerberosEBank can use this reference to call a method named getKey(), that belongs to the KeyManager class.

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.


Debugging Java Card apps

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.


Wrap up

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.



Download

DescriptionNameSizeDownload method
Code samplewi-satsa2source.zip143KB HTTP

Information about download methods


Resources

Learn

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

About the author

Faheem Khan is an independent software consultant specializing in Enterprise Application Integration (EAI) and B2B solutions. He can be reached at fkhan872@yahoo.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=100294
ArticleTitle=Securing Java Card applications, Part 2: Integrating Java Card into J2ME applications
publish-date=12132005
author1-email=fkhan872@yahoo.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers