FileDocCategorySizeDatePackage
TransitApplet.javaAPI DocJava Card28256Wed Mar 22 21:07:24 GMT 2006com.sun.javacard.samples.transit

TransitApplet

public class TransitApplet extends javacard.framework.Applet
This applet implements the on-card part of a transit system solution. The on-card applet and the off-card applications (transit terminal and POS terminal) use a mutual authentication scheme based on a dynamically generated DES session key to ensure data integrity and origin authentication during a session. When interacting with a POS terminal, the account maintained on the card can be credited or queried for the current balance. When interacting with a transit terminal, the transit system entry and the exit events are checked for consistency and processed - the account maintained on the card is debited upon proper exit from the transit system. Design notes: - This sample transit applet does not account for any admin or self-admin use cases such as resetting the card of a transit system user when it is in an inconsistent transit state. Such an inconsistent state can, for example, result from the user jumping the gates when the turnstile is out of order... - This sample transit applet does not account for any system-wide transactional operations. For example, during a credit operation, if the user removes his card just after the balance has been updated but before the APDU response gets to the terminal, the account on the card will remain credited but the terminal will only be able to detect an IO error b/w the card and the card reader. - The constants defined for this class should have been shared through an additional class or interface with the terminal code (see com.sun.javacard.clientsamples.transit.Constants). - This applet could be refactored so that the mutual authentication code be moved in a base abstract class and the transit system specific behavior be implemented in a subclass of this base class. This refactoring would facilitate the reuse of the mutual authentication scheme in other application domain.

Fields Summary
static final byte
VERIFY
INS value for ISO 7816-4 VERIFY command
static final byte
INITIALIZE_SESSION
INS value for INITIALIZE_SESSION command
static final byte
PROCESS_REQUEST
INS value for PROCESS_REQUEST command
static final byte
PROCESS_ENTRY
TLV Tag for PROCESS_ENTRY request
static final byte
PROCESS_EXIT
TLV Tag for PROCESS_EXIT request
static final byte
CREDIT
TLV Tag for CREDIT request
static final byte
GET_BALANCE
TLV Tag for GET_BALANCE request
static final short
TLV_TAG_OFFSET
TLV tag offset
static final short
TLV_LENGTH_OFFSET
TLV length offset
static final short
TLV_VALUE_OFFSET
TLV value offset
static final short
MAX_BALANCE
Maximum allowed balance
static final short
MIN_TRANSIT_BALANCE
Minimum balance to start transit
static final short
MAX_CREDIT_AMOUNT
Maximum amount to be credited
static final byte
MAX_PIN_TRIES
Maximum number of incorrect tries before the PIN is blocked
static final byte
MAX_PIN_SIZE
Maximum PIN size
static final short
SW_VERIFICATION_FAILED
SW bytes for PIN verification failure
static final short
SW_PIN_VERIFICATION_REQUIRED
SW bytes for PIN validation required
static final short
SW_INVALID_TRANSACTION_AMOUNT
SW bytes for invalid credit amount (amount > MAX_CREDIT_AMOUNT or amount < 0)
static final short
SW_EXCEED_MAXIMUM_BALANCE
SW bytes for maximum balance exceeded
static final short
SW_NEGATIVE_BALANCE
SW bytes for negative balance reached
static final short
SW_WRONG_SIGNATURE
SW bytes for wrong signature condition
static final short
SW_MIN_TRANSIT_BALANCE
SW bytes for minimum transit balance not met
static final short
SW_INVALID_TRANSIT_STATE
SW bytes for invalid transit state
static final short
SW_SUCCESS
SW bytes for success, used in MAC
static final short
UID_LENGTH
Unique ID length
static final short
LENGTH_DES_BYTE
DES key length in bytes
static final short
CHALLENGE_LENGTH
Host and card challenge length (note: (2 * CHALLENGE_LENGTH) * 8 == KeyBuilder.LENGTH_DES
static final short
MAC_LENGTH
MAC length as generated by Signature.ALG_DES_MAC8_ISO9797_M2
private byte[]
uid
Unique ID
private javacardx.crypto.Cipher
cipher
Cipher used to encrypt - using the static DES key - the derivation data to form the session key
private javacard.security.DESKey
staticKey
DES static key, shared b/w host and card
private byte[]
cardChallenge
4-bytes Card challenge
private byte[]
keyDerivationData
8-bytes key derivation data, generated from the host challenge and the card challenge
private byte[]
sessionKeyData
8-bytes session key data, generated from the derivation data
private javacard.security.DESKey
sessionKey
DES session key, generated from the derivation data
private boolean
useTransientKey
Indicates whether or not to use transient session key - for performance measurement only
private javacard.security.Signature
signature
Signature initialized with the DES key and used to verify incoming messages and to sign outgoing messages
private javacard.security.RandomData
random
Random data generator, used to generate the card challenge
private javacard.framework.OwnerPIN
pin
The user PIN
private short
balance
The balance
private short
entryStationId
The entry ststion id, set to (-1) when not in transit
private byte
correlationId
A correlation id that may be used by the backend system to correlate entry and exit events
Constructors Summary
protected TransitApplet(byte[] bArray, short bOffset, byte bLength)
Creates a new Transit applet instance.

param
bArray The array containing installation parameters
param
bOffset The starting offset in bArray
param
bLength The length in bytes of the parameter data in bArray


                                                                           
           

        // Create static DES key
        staticKey = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
                KeyBuilder.LENGTH_DES, false);

        // Create cipher
        cipher = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2, false);

        // Create card challenge transient buffer
        cardChallenge = JCSystem.makeTransientByteArray(CHALLENGE_LENGTH,
                JCSystem.CLEAR_ON_DESELECT);

        // Create key derivation data transient buffer
        keyDerivationData = JCSystem.makeTransientByteArray(
                (short) (2 * CHALLENGE_LENGTH), JCSystem.CLEAR_ON_DESELECT);

        // Create session key data transient buffer
        sessionKeyData = JCSystem.makeTransientByteArray(
	    (short) (2 * keyDerivationData.length),
	    JCSystem.CLEAR_ON_DESELECT);
        // XXX: Allocates more than actual key to contain the complete
        // encrypted key derivation data

        // Create signature
        signature = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2,
                false);

        byte aidLen = bArray[bOffset]; // aid length
        if (aidLen == (byte) 0) {
            register();
        } else {
            register(bArray, (short) (bOffset + 1), aidLen);
        }

        // Ignore control info
        bOffset = (short) (bOffset + aidLen + 1);
        byte infoLen = bArray[bOffset]; // control info length
        bOffset = (short) (bOffset + infoLen + 1);

        byte paramLen = bArray[bOffset++]; // applet parameters length

        // Retrieve UID, static key data and the PIN initialization values from
        // installation parameters

        if (paramLen <= (LENGTH_DES_BYTE + UID_LENGTH)
                || paramLen > (LENGTH_DES_BYTE + UID_LENGTH + MAX_PIN_SIZE)) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Retrieve the UID
        uid = new byte[UID_LENGTH];
        Util.arrayCopy(bArray, bOffset, uid, (short) 0, UID_LENGTH);
        bOffset += UID_LENGTH;

        // Retrieve the static key data
        staticKey.setKey(fixParity(bArray, bOffset, LENGTH_DES_BYTE), bOffset);
        bOffset += LENGTH_DES_BYTE;

	// Retrieve the flag indicating whether or not to use a transient key
	useTransientKey = (bArray[bOffset] != (byte) 0);
	bOffset++;
	    
        // Retrieve the PIN
        pin = new OwnerPIN(MAX_PIN_TRIES, MAX_PIN_SIZE);
        pin.update(bArray, bOffset,
                (byte) (paramLen - UID_LENGTH - LENGTH_DES_BYTE - 1));

        // Create transient DES session key
	if (useTransientKey) {
	    sessionKey = (DESKey) KeyBuilder.buildKey(
                KeyBuilder.TYPE_DES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_DES,
                false);
	} else {
	    sessionKey = (DESKey) KeyBuilder.buildKey(
                KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES,
                false);
	}

        // Create and initialize the ramdom data generator with the UID (seed)
        random = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM);
        random.setSeed(uid, (short) 0, UID_LENGTH);

        // Initialize the cipher with the static key
        cipher.init(staticKey, Cipher.MODE_ENCRYPT);

    
Methods Summary
private booleancheckMAC(byte[] buffer)
Checks the request message signature.

param
buffer The APDU buffer
return
true if the message signature is correct; false otherwise

        byte numBytes = buffer[ISO7816.OFFSET_LC];

        if (numBytes <= MAC_LENGTH) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Initialize signature with current session key for verification
        signature.init(sessionKey, Signature.MODE_VERIFY);
        // Verify request message signature
        return signature.verify(buffer, ISO7816.OFFSET_CDATA,
                (short) (numBytes - MAC_LENGTH), buffer,
                (short) (ISO7816.OFFSET_CDATA + numBytes - MAC_LENGTH),
	        MAC_LENGTH);
    
private shortcredit(byte[] buffer, short messageOffset, short messageLength)
Credits the account of the passed-in amount. Request Message: [1-byte Credit Amount] Response Message: []

param
buffer The APDU buffer
param
messageOffset The offset of the request message content in the APDU buffer
param
messageLength The length of the request message content.
return
The offset at which content can be appended to the response message


        // Check access authorization
        if (!pin.isValidated()) {
            ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
        }

        // Request Message: [1-byte Credit Amount]

        if (messageLength != 1) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Get credit amount from request message
        byte creditAmount = buffer[messageOffset];

        // Check credit amount
        if ((creditAmount > MAX_CREDIT_AMOUNT) || (creditAmount < 0)) {
            ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);
        }

        // Check the new balance
        if ((short) (balance + creditAmount) > MAX_BALANCE) {
            ISOException.throwIt(SW_EXCEED_MAXIMUM_BALANCE);
        }

        // Credit the amount
        balance += creditAmount;

        // Response Message: []

        return 0;
    
public voiddeselect()

        // Reset the PIN value
        pin.reset();
	if (!useTransientKey) {
	    sessionKey.clearKey();
	}
    
private byte[]fixParity(byte[] buffer, short offset, short length)
Fixes the parity on DES key data.

param
buffer The buffer containing the DES key data
param
offset The offset of the DES key data in the buffer
param
messageLength The length of the DES key data
return
The passed-in buffer with the DES key data parity fixed

	for (byte i = 0; i < length; i++) {
	    short parity = 0;
	    buffer[(short) (offset + i)] &= 0xFE;
	    for (byte j = 1; j < 8; j++) {
		if ((buffer[(short) (offset + i)] & (byte) (1 << j)) != 0) {
		    parity++;
		}
	    }
	    if ((parity % 2) == 0) {
		buffer[(short) (offset + i)] |= 1;
	    }
	}
	return buffer;
    
private voidgenerateCardChallenge()
Generates a new random card challenge.

        // Generate random card challenge
        random.generateData(cardChallenge, (short) 0, CHALLENGE_LENGTH);
    
private voidgenerateKeyDerivationData(byte[] buffer)
Generates the session key derivation data from the passed-in host challenge and the card challenge.

param
buffer The APDU buffer

        byte numBytes = buffer[ISO7816.OFFSET_LC];

        if (numBytes < CHALLENGE_LENGTH) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Derivation data: [[8-bytes host challenge], [8-bytes card challenge]]

        // Append host challenge (from buffer) to derivation data
        Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, keyDerivationData,
                (short) 0, CHALLENGE_LENGTH);
        // Append card challenge to derivation data
        Util.arrayCopy(cardChallenge, (short) 0, keyDerivationData,
                CHALLENGE_LENGTH, CHALLENGE_LENGTH);
    
private shortgenerateMAC(byte[] buffer, short offset)
Generates the response message MAC: generates the MAC and appends the MAC to the response message.

param
buffer The APDU buffer
param
offset The offset of the MAC in the buffer
return
The resulting length of the response message

        // Initialize signature with current session key for signing
        signature.init(sessionKey, Signature.MODE_SIGN);
        // Sign response message and append the MAC to the response message
        short sigLength = signature.sign(buffer, (short) 0, offset, buffer,
                offset);
        return (short) (offset + sigLength);
    
private voidgenerateSessionKey()
Generates a new DES session key from the derivation data.

        cipher.doFinal(keyDerivationData, (short) 0, (short) keyDerivationData.length,
                sessionKeyData, (short) 0);
        // Generate new session key from encrypted derivation data
        sessionKey.setKey(fixParity(sessionKeyData, (short) 0, (short) sessionKeyData.length /*LENGTH_DES_BYTE*/), (short) 0);
    
private shortgetBalance(byte[] buffer, short messageOffset, short messageLength)
Gets/returns the balance. Request Message: [] Response Message: [2-bytes Balance]

param
buffer The APDU buffer
param
messageOffset The offset of the request message content in the APDU buffer
param
messageLength The length of the request message content.
return
The offset at which content can be appended to the response message


        // Check access authorization
        if (!pin.isValidated()) {
            ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
        }

        // Request Message: []

        if (messageLength != 0) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Response Message: [2-bytes Balance]

        short offset = 0;

        // Append balance to response message
        offset = Util.setShort(buffer, offset, balance);

        return offset;
    
private voidinitializeSession(javacard.framework.APDU apdu)
Initializes a CAD/card interaction session. This is the first step of mutual authentication. A new card challenge is generated and used along with the passed-in host challenge to generate the derivation data from which a new session key is derived. The card challenge is appended to the response message. The response message is signed using the newly generated session key then sent back. Note that mutual authentication is subsequently completed upon succesful verification of the signature of the first request received.

param
apdu The APDU


        // C-APDU: [CLA, INS, P1, P2, LC, [4-bytes Host Challenge]]

        byte[] buffer = apdu.getBuffer();

        if ((buffer[ISO7816.OFFSET_P1] != 0)
                || (buffer[ISO7816.OFFSET_P2] != 0)) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        byte numBytes = buffer[ISO7816.OFFSET_LC];

        byte count = (byte) apdu.setIncomingAndReceive();

        if (numBytes != CHALLENGE_LENGTH || count != CHALLENGE_LENGTH) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Generate card challenge
        generateCardChallenge();

        // Generate key derivation data from host challenge and card challenge
        generateKeyDerivationData(buffer);

        // Generate session key from derivation data
        generateSessionKey();

        // R-APDU: [[4-bytes Card Challenge], [2-bytes Status Word], [8-bytes
        // MAC]]

        short offset = 0;

        // Append card challenge to response message
        offset = Util.arrayCopyNonAtomic(cardChallenge, (short) 0, buffer,
                offset, CHALLENGE_LENGTH);

        // Append status word to response message
        offset = Util.setShort(buffer, offset, SW_SUCCESS);

        // Sign response message and append MAC to response message
        offset = generateMAC(buffer, offset);

        // Send R-APDU
        apdu.setOutgoingAndSend((short) 0, offset);
    
public static voidinstall(byte[] bArray, short bOffset, byte bLength)

        // Create a Transit applet instance
        new TransitApplet(bArray, bOffset, bLength);
    
public voidprocess(javacard.framework.APDU apdu)


        // C-APDU: [CLA, INS, P1, P2, LC, ...]

        byte[] buffer = apdu.getBuffer();

        // Dispatch C-APDU for processing
        if (!apdu.isISOInterindustryCLA()) {
            switch (buffer[ISO7816.OFFSET_INS]) {
            case INITIALIZE_SESSION:
                initializeSession(apdu);
                return;
            case PROCESS_REQUEST:
                processRequest(apdu);
                return;
            default:
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
            }
        } else {
            if (buffer[ISO7816.OFFSET_INS] == (byte)(0xA4)) {
                return;
            } else if (buffer[ISO7816.OFFSET_INS] == VERIFY) {
                verify(apdu);
            } else {
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
            }
        }
    
private shortprocessEntry(byte[] buffer, short messageOffset, short messageLength)
Processes a transit entry event. The passed-in entry station ID is recorded and the correlation ID is incremented. The UID and the correlation ID are returned in the response message. Request Message: [2-bytes Entry Station ID] Response Message: [[2-bytes UID], [2-bytes Correlation ID]]

param
buffer The APDU buffer
param
messageOffset The offset of the request message content in the APDU buffer
param
messageLength The length of the request message content.
return
The offset at which content can be appended to the response message


        // Request Message: [2-bytes Entry Station ID]

        if (messageLength != 2) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Check minimum balance
        if (balance < MIN_TRANSIT_BALANCE) {
            ISOException.throwIt(SW_MIN_TRANSIT_BALANCE);
        }

        // Check consistent transit state: should not currently be in transit
        if (entryStationId >= 0) {
            ISOException.throwIt(SW_INVALID_TRANSIT_STATE);
        }

        JCSystem.beginTransaction();

        // Get/assign entry station ID from request message
        entryStationId = Util.getShort(buffer, messageOffset);

        // Increment correlation ID
        correlationId++;

        JCSystem.commitTransaction();

        // Response Message: [[8-bytes UID], [2-bytes Correlation ID]]

        short offset = 0;

        // Append UID to response message
        offset = Util.arrayCopy(uid, (short) 0, buffer, offset, UID_LENGTH);

        // Append correlation ID to response message
        offset = Util.setShort(buffer, offset, correlationId);

        return offset;
    
private shortprocessExit(byte[] buffer, short messageOffset, short messageLength)
Processes a transit exit event. The passed-in transit fee is debited from the account. The UID and the correlation ID are returned in the response message. Request Message: [1-byte Transit Fee] Response Message: [[2-bytes UID], [2-bytes Correlation ID]]

param
buffer The APDU buffer
param
messageOffset The offset of the request message content in the APDU buffer
param
messageLength The length of the request message content.
return
The offset at which content can be appended to the response message


        // Request Message: [1-byte Transit Fee]

        if (messageLength != 1) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Check minimum balance
        if (balance < MIN_TRANSIT_BALANCE) {
            ISOException.throwIt(SW_MIN_TRANSIT_BALANCE);
        }

        // Check consistent transit state: should be currently in transit
        if (entryStationId < 0) {
            ISOException.throwIt(SW_INVALID_TRANSIT_STATE);
        }

        // Get transit fee from request message
        byte transitFee = buffer[messageOffset];

        // Check potential negative balance
        if (balance < transitFee) {
            ISOException.throwIt(SW_NEGATIVE_BALANCE);
        }

        JCSystem.beginTransaction();

        // Debit transit fee
        balance -= transitFee;

        // Reset entry station ID
        entryStationId = -1;

        JCSystem.commitTransaction();

        // Response Message: [[8-bytes UID], [2-bytes Correlation ID]]

        short offset = 0;

        // Append UID to response message
        offset = Util.arrayCopy(uid, (short) 0, buffer, offset, UID_LENGTH);

        // Append correlation ID to response message
        offset = Util.setShort(buffer, offset, correlationId);

        return offset;
    
private voidprocessRequest(javacard.framework.APDU apdu)
Processes an incoming request. The request message signature is verified, then it is dispatched to the relevant handling method. The response message is then signed and sent back.

param
apdu The APDU


        // C-APDU: [CLA, INS, P1, P2, LC, [Request Message], [8-bytes MAC]]
        // Request Message: [T, L, [V...]]

        byte[] buffer = apdu.getBuffer();

        if ((buffer[ISO7816.OFFSET_P1] != 0)
                || (buffer[ISO7816.OFFSET_P2] != 0)) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        byte numBytes = buffer[ISO7816.OFFSET_LC];

        byte count = (byte) apdu.setIncomingAndReceive();

        if (numBytes != count) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Check request message signature
        if (!checkMAC(buffer)) {
            ISOException.throwIt(SW_WRONG_SIGNATURE);
        }

        if ((numBytes - MAC_LENGTH) != (buffer[TLV_LENGTH_OFFSET] + 2)) {
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }

        // R-APDU: [[Response Message], [2-bytes Status Word], [8-bytes MAC]]

        short offset = 0;

        // Dispatch request message for processing
        switch (buffer[TLV_TAG_OFFSET]) {
        case PROCESS_ENTRY:
            offset = processEntry(buffer, TLV_VALUE_OFFSET,
                    buffer[TLV_LENGTH_OFFSET]);
            break;
        case PROCESS_EXIT:
            offset = processExit(buffer, TLV_VALUE_OFFSET,
                    buffer[TLV_LENGTH_OFFSET]);
            break;
        case CREDIT:
            offset = credit(buffer, TLV_VALUE_OFFSET, buffer[TLV_LENGTH_OFFSET]);
            break;
        case GET_BALANCE:
            offset = getBalance(buffer, TLV_VALUE_OFFSET,
                    buffer[TLV_LENGTH_OFFSET]);
            break;
        default:
            ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
        }

        // Append status word to response message
        offset = Util.setShort(buffer, offset, SW_SUCCESS);

        // Sign response message and append MAC to response message
        offset = generateMAC(buffer, offset);

        // Send R-APDU
        apdu.setOutgoingAndSend((short) 0, offset);
    
public booleanselect()

        // The applet declines to be selected
        // if the PIN is blocked.
        if (pin.getTriesRemaining() == 0) {
            return false;
        }
        return true;
    
private voidverify(javacard.framework.APDU apdu)
Verifies the PIN.

param
apdu The APDU


        byte[] buffer = apdu.getBuffer();

        byte numBytes = buffer[ISO7816.OFFSET_LC];

        byte count = (byte) apdu.setIncomingAndReceive();

        if (numBytes != count) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }

        // Verify PIN
        if (pin.check(buffer, ISO7816.OFFSET_CDATA, numBytes) == false) {
            ISOException.throwIt(SW_VERIFICATION_FAILED);
        }