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

TransitApplet.java

/*
 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.javacard.samples.transit;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;

/**
 * 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.
 */
public class TransitApplet extends Applet {

    // Codes of INS byte in the command APDU header

    /**
     * INS value for ISO 7816-4 VERIFY command
     */
    final static byte VERIFY = (byte) 0x20;

    /**
     * INS value for INITIALIZE_SESSION command
     */
    final static byte INITIALIZE_SESSION = (byte) 0x30;

    /**
     * INS value for PROCESS_REQUEST command
     */
    final static byte PROCESS_REQUEST = (byte) 0x40;

    // Tags for TLV records in PROCESS_REQUEST C-APDU

    /**
     * TLV Tag for PROCESS_ENTRY request
     */
    final static byte PROCESS_ENTRY = (byte) 0xC1;

    /**
     * TLV Tag for PROCESS_EXIT request
     */
    final static byte PROCESS_EXIT = (byte) 0xC2;

    /**
     * TLV Tag for CREDIT request
     */
    final static byte CREDIT = (byte) 0xC3;

    /**
     * TLV Tag for GET_BALANCE request
     */
    final static byte GET_BALANCE = (byte) 0xC4;

    // Offsets of TLV components in PROCESS_REQUEST C-APDU [CLA, INS, P1, P2, LC
    // T L V...]

    /**
     * TLV tag offset
     */
    final static short TLV_TAG_OFFSET = ISO7816.OFFSET_CDATA;

    /**
     * TLV length offset
     */
    final static short TLV_LENGTH_OFFSET = TLV_TAG_OFFSET + 1;

    /**
     * TLV value offset
     */
    final static short TLV_VALUE_OFFSET = TLV_LENGTH_OFFSET + 1;

    /**
     * Maximum allowed balance
     */
    final static short MAX_BALANCE = (short) 500;

    /**
     * Minimum balance to start transit
     */
    final static short MIN_TRANSIT_BALANCE = (short) 10;

    /**
     * Maximum amount to be credited
     */
    final static short MAX_CREDIT_AMOUNT = (short) 100;

    /**
     * Maximum number of incorrect tries before the PIN is blocked
     */
    final static byte MAX_PIN_TRIES = (byte) 0x03;

    /**
     * Maximum PIN size
     */
    final static byte MAX_PIN_SIZE = (byte) 0x08;

    /**
     * SW bytes for PIN verification failure
     */
    final static short SW_VERIFICATION_FAILED = 0x6300;

    /**
     * SW bytes for PIN validation required
     */
    final static short SW_PIN_VERIFICATION_REQUIRED = 0x6301;

    /**
     * SW bytes for invalid credit amount (amount > MAX_CREDIT_AMOUNT or amount <
     * 0)
     */
    final static short SW_INVALID_TRANSACTION_AMOUNT = 0x6A83;

    /**
     * SW bytes for maximum balance exceeded
     */
    final static short SW_EXCEED_MAXIMUM_BALANCE = 0x6A84;

    /**
     * SW bytes for negative balance reached
     */
    final static short SW_NEGATIVE_BALANCE = 0x6A85;

    /**
     * SW bytes for wrong signature condition
     */
    final static short SW_WRONG_SIGNATURE = (short) 0x9105;

    /**
     * SW bytes for minimum transit balance not met
     */
    final static short SW_MIN_TRANSIT_BALANCE = (short) 0x9106;

    /**
     * SW bytes for invalid transit state
     */
    final static short SW_INVALID_TRANSIT_STATE = (short) 0x9107;

    /**
     * SW bytes for success, used in MAC
     */
    final static short SW_SUCCESS = (short) 0x9000;

    /**
     * Unique ID length
     */
    final static short UID_LENGTH = (short) 8;

    /**
     * DES key length in bytes
     */
    final static short LENGTH_DES_BYTE = (short) (KeyBuilder.LENGTH_DES / 8);

    /**
     * Host and card challenge length (note: (2 * CHALLENGE_LENGTH) * 8 ==
     * KeyBuilder.LENGTH_DES
     */
    final static short CHALLENGE_LENGTH = (short) 4;

    /**
     * MAC length as generated by Signature.ALG_DES_MAC8_ISO9797_M2
     */
    final static short MAC_LENGTH = (short) 8;

    /**
     * Unique ID
     */
    private byte[] uid;

    // Signature/key objects

    /**
     * Cipher used to encrypt - using the static DES key - the derivation data
     * to form the session key
     */
    private Cipher cipher;

    /**
     * DES static key, shared b/w host and card
     */
    private DESKey staticKey;

    /**
     * 4-bytes Card challenge
     */
    private byte[] cardChallenge; // Transient

    /**
     * 8-bytes key derivation data, generated from the host challenge and the
     * card challenge
     */
    private byte[] keyDerivationData; // Transient

    /**
     * 8-bytes session key data, generated from the derivation data
     */
    private byte[] sessionKeyData; // Transient

    /**
     * DES session key, generated from the derivation data
     */
    private DESKey sessionKey; // Transient key

    /**
     * Indicates whether or not to use transient session key - for performance
     * measurement only
     */
    private boolean useTransientKey = true;

    /**
     * Signature initialized with the DES key and used to verify incoming
     * messages and to sign outgoing messages
     */
    private Signature signature;

    /**
     * Random data generator, used to generate the card challenge
     */
    private RandomData random;

    /**
     * The user PIN
     */
    private OwnerPIN pin;

    /**
     * The balance
     */
    private short balance = (short) 0;

    /**
     * The entry ststion id, set to (-1) when not in transit
     */
    private short entryStationId = (short) -1;

    /**
     * A correlation id that may be used by the backend system to correlate
     * entry and exit events
     */
    private byte correlationId = (byte) 0;

    /**
     * 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
     */
    protected TransitApplet(byte[] bArray, short bOffset, byte bLength) {

        // 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);

    }

    public static void install(byte[] bArray, short bOffset, byte bLength) {
        // Create a Transit applet instance
        new TransitApplet(bArray, bOffset, bLength);
    }

    public boolean select() {
        // The applet declines to be selected
        // if the PIN is blocked.
        if (pin.getTriesRemaining() == 0) {
            return false;
        }
        return true;
    }

    public void deselect() {
        // Reset the PIN value
        pin.reset();
	if (!useTransientKey) {
	    sessionKey.clearKey();
	}
    }

    public void process(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);
            }
        }
    }

    /**
     * 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
     */
    private void initializeSession(APDU 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);
    }

    /**
     * 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
     */
    private void processRequest(APDU 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);
    }

    /**
     * Verifies the PIN.
     * 
     * @param apdu
     *            The APDU
     */
    private void verify(APDU 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);
        }
    }

    /**
     * Generates a new random card challenge.
     *  
     */
    private void generateCardChallenge() {
        // Generate random card challenge
        random.generateData(cardChallenge, (short) 0, CHALLENGE_LENGTH);
    }

    /**
     * Generates the session key derivation data from the passed-in host
     * challenge and the card challenge.
     * 
     * @param buffer
     *            The APDU buffer
     */
    private void generateKeyDerivationData(byte[] 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);
    }

    /**
     * Generates a new DES session key from the derivation data.
     *  
     */
    private void generateSessionKey() {
        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);
    }

    /**
     * Checks the request message signature.
     * 
     * @param buffer
     *            The APDU buffer
     * @return true if the message signature is correct; false otherwise
     */
    private boolean checkMAC(byte[] buffer) {
        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);
    }

    /**
     * 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
     */
    private short generateMAC(byte[] buffer, short offset) {
        // 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);
    }

    /**
     * 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
     */
    private short processEntry(byte[] buffer, short messageOffset,
            short messageLength) {

        // 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;
    }

    /**
     * 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
     */
    private short processExit(byte[] buffer, short messageOffset,
            short messageLength) {

        // 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;
    }

    /**
     * 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
     */
    private short credit(byte[] buffer, short messageOffset, short messageLength) {

        // 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;
    }

    /**
     * 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
     */
    private short getBalance(byte[] buffer, short messageOffset,
            short messageLength) {

        // 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;
    }

    /**
     * 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
     */
    private byte[] fixParity(byte[] buffer, short offset, short length) {
	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;
    }
}