Skip to Main Content

Java Card

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Some issues with Applet (Sending RSAKey to Card + Getting data from Card)

861148Sep 13 2011 — edited Sep 20 2011
Hello,

i have 2 issues with my applet. I searched a lot but i was not able to find the mistakes.

1.) I want to send more than 255 bytes from the card applet to the host application. But if i use my code, i noticed a strange behavior. If the fileBuffer contains more than 255 bytes and the getData - APDU is send, i get 510 bytes back. But the 510 exists of 2*255 bytes. So the response is only repeated.
If i send the getData - APDU again without deselecting the applet i get only 255 bytes back.

2.) I want to send a RSA - 2048 Public Key from the host application to the smart card. Therefore i send the exponent || modulus to the card. I use the putData - APDU for this. If i want to set the Key, i get a CryptoException with reason ILLEGAL_VALUE. I know, that the setModulus - method cause this Exception.
package smartcard;

import javacard.framework.APDU;
import javacard.framework.APDUException;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.SystemException;
import javacard.framework.Util;
import javacard.security.CryptoException;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.MessageDigest;
import javacard.security.RSAPublicKey;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;

public class SmartCardApplet extends Applet {

    private final static byte SmartCard_CLA = (byte) 0xB0;
    private final static byte INS_GET_RESPONSE = (byte) 0xC0;
    private final static byte INS_PUT_DATA = (byte) 0x00;
    private final static byte INS_GET_DATA = (byte) 0x01;
    private final static byte INS_GET_SMARTCARD_KEY = (byte) 0x02;
    private final static byte INS_PUT_PUBLICKEY = (byte) 0x03;
    private final static byte INS_ENCRYPT_DATA = (byte) 0x04;
    private final static byte INS_SIGN_DATA = (byte) 0x05;
    private final static byte INS_DECRYPT_DATA = (byte) 0x06;
    private final static byte INS_VERIFY_DATA = (byte) 0x07;
    private final static byte P1_ELECTRICITY_SUPPLIER = (byte) 0x10;
    private final static byte P1_TRUSTED_THIRD_PARTY = (byte) 0x20;
    private final static byte OFFSET_SENT = 0x00;
    private final static byte OFFSET_RECV = 0x01;
    private final static short SW_CRYPTO_EXCEPTION = 0x62A0;
    private final static short SW_APDU_EXCEPTION = 0x63B0;
    private final static short SW_NULLPOINTER_EXCEPTION = 0x63C0;
    private final static short SW_ARRAY_EXCEPTION = 0x63D0;
    private final static short SW_SYSTEM_EXCEPTION = 0x63E0;
    private final static short RSA_LENGTH = 256;
    private final static short FILE_SIZE = 2048;
    private final static short MAX_APDU = 255;
    private static short fileSize = 0;
    private static short[] offset;
    private static byte[] fileBuffer;
    private KeyPair smartCardKeys;
    private RSAPublicKey publicKeyTTP;
    private RSAPublicKey publicKeyES;
    private DESKey sessionKey;
    private Signature signature;
    private MessageDigest messageDigest;
    private Cipher cipherRSA;
    private Cipher cipherDES;
    private RandomData randomData;
    private byte[] rsaBuffer = new byte[256];
    private byte[] sessionKeyBytes = new byte[16];
    private byte[] sigBuffer = new byte[256];
    private byte[] desBuffer = new byte[4096];
    private byte[] hashBuffer = new byte[20];
    private byte[] lowLevelID = new byte[12];
    private byte[] highLevelID = new byte[12];
    private byte[] esID = new byte[4];
    private byte[] areaID = new byte[4];

    /**
     * 
     */
    public SmartCardApplet() {
        try {
            signature = Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1,
                    false);

            messageDigest = MessageDigest.getInstance(MessageDigest.ALG_SHA,
                    false);
            cipherRSA = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
            cipherDES = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD,
                    false);
            randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
            smartCardKeys = new KeyPair(KeyPair.ALG_RSA_CRT,
                    KeyBuilder.LENGTH_RSA_2048);
            smartCardKeys.genKeyPair();
            buildKeys();

            offset = JCSystem.makeTransientShortArray((short) 2,
                    JCSystem.CLEAR_ON_RESET);
            fileBuffer = new byte[FILE_SIZE];
        } catch (CryptoException ex) {
            ISOException.throwIt((short) (SW_CRYPTO_EXCEPTION
                    + ex.getReason()));
        } catch (SystemException ex) {
            ISOException.throwIt((short) (SW_SYSTEM_EXCEPTION
                    + ex.getReason()));
        }
    }

    /**
     * @param bArray
     * @param bOffset
     * @param bLength
     */
    public static void install(byte[] bArray, short bOffset, byte bLength) {
        // GP-compliant JavaCard applet registration
        new SmartCardApplet().register(bArray, (short) (bOffset + 1),
                bArray[bOffset]);


    }

    public void process(APDU apdu) {
        // Good practice: Return 9000 on SELECT
        if (selectingApplet()) {
            return;
        }

        byte[] buf = apdu.getBuffer();
        byte ins = buf[ISO7816.OFFSET_INS];

        // Good practice: if CLA-Byte is unknown say so!
        if (buf[ISO7816.OFFSET_CLA] != SmartCard_CLA) {
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        }

        // Check whitch method has to be applied
        switch (buf[ISO7816.OFFSET_INS]) {
            case INS_GET_RESPONSE:
                sendData(apdu);
                break;
            case INS_PUT_DATA:
                try {
                    // this is the amount of data read from the OS
                    short len = apdu.setIncomingAndReceive();
                    short lc = (short) (buf[ISO7816.OFFSET_LC] & 0x00ff);

                    // get all bytes from the buffer
                    while (len < lc) {
                        len += apdu.receiveBytes(len);
                    }
                    saveData(buf, ISO7816.OFFSET_CDATA, offset[OFFSET_RECV],
                            len);
                    // Check if putting data to the card is finished
                    if (buf[ISO7816.OFFSET_P1] == (byte) 0x00) {
                        offset[OFFSET_RECV] += len;
                    } else {
                        fileSize = (short) (offset[OFFSET_RECV] + len);
                        offset[OFFSET_RECV] = 0;
                    }
                } catch (APDUException ex) {
                    ISOException.throwIt((short) (SW_APDU_EXCEPTION
                            + ex.getReason()));
                }
                break;
            case INS_GET_DATA:
                // encrypt data here for transport xD
                sendData(apdu);
                break;
            case INS_GET_SMARTCARD_KEY:
                sendSmartCardKey();
                break;
            case INS_PUT_PUBLICKEY:
                switch (buf[ISO7816.OFFSET_P1]) {
                    case P1_ELECTRICITY_SUPPLIER:
                        setPublicKey(publicKeyES);
                        break;
                    case P1_TRUSTED_THIRD_PARTY:
                        setPublicKey(publicKeyTTP);
                        break;
                    default:
                        // Good practice: if parameter is unknown say so!
                        ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                }
                break;
            case INS_ENCRYPT_DATA:
                switch (buf[ISO7816.OFFSET_P1]) {
                    case P1_ELECTRICITY_SUPPLIER:
                        encryptData(publicKeyES);
                        break;
                    case P1_TRUSTED_THIRD_PARTY:
                        encryptData(publicKeyTTP);
                        break;
                    default:
                        // Good practice: if parameter is unknown say so!
                        ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                }
                break;
            case INS_SIGN_DATA:
                switch (buf[ISO7816.OFFSET_P1]) {
                    case P1_ELECTRICITY_SUPPLIER:
                        extendMessage(P1_ELECTRICITY_SUPPLIER);
                        break;
                    case P1_TRUSTED_THIRD_PARTY:
                        extendMessage(P1_TRUSTED_THIRD_PARTY);
                        break;
                    default:
                        // Good practice: if parameter is unknown say so!
                        ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                }
                signData();
                break;
            default:
                // good practice: if INS-byte is unknown say so!
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }
    }

    /**
     *
     */
    private void buildKeys() {
        // build publicKey for the trusted third party (TTP)
        publicKeyTTP = (RSAPublicKey) KeyBuilder.buildKey(
                KeyBuilder.TYPE_RSA_PUBLIC,
                KeyBuilder.LENGTH_RSA_2048, false);

        // build publicKey for the electricity supplier (ES)
        publicKeyES = (RSAPublicKey) KeyBuilder.buildKey(
                KeyBuilder.TYPE_RSA_PUBLIC,
                KeyBuilder.LENGTH_RSA_2048, false);

        // build DesKey for the hybride encryption used in this applet
        sessionKey = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
                KeyBuilder.LENGTH_DES3_2KEY, false);
    }

    /**
     *
     * @param source
     * @param sourceOff
     * @param destOff
     * @param length
     */
    private void saveData(byte[] source, short sourceOff, short destOff,
            short length) {
        if ((short) (destOff + length) > FILE_SIZE) {
            ISOException.throwIt(ISO7816.SW_FILE_FULL);
        }
        Util.arrayCopy(source, sourceOff, fileBuffer, destOff, length);
    }

    /**
     *
     * @param apdu
     */
    private void sendData(APDU apdu) {
        try {
            short remain = (short) (fileSize - offset[OFFSET_SENT]);
            boolean chain = remain > MAX_APDU;
            short sendLen = chain ? MAX_APDU : remain;

            apdu.setOutgoing();
            apdu.setOutgoingLength(sendLen);
            apdu.sendBytesLong(fileBuffer, offset[OFFSET_SENT], sendLen);

            // Check to see if there are more APDU's to send
            if (chain) {
                // count the bytes sent
                offset[OFFSET_SENT] += sendLen;
                // indicate there are more bytes to come
                ISOException.throwIt(ISO7816.SW_BYTES_REMAINING_00);
            } else {
                // no more bytes to send
                offset[OFFSET_SENT] = 0;
            }
        } catch (APDUException ex) {
            ISOException.throwIt((short) (SW_APDU_EXCEPTION + ex.getReason()));
        }
    }

    /**
     * Method to copy the smartcard public key into the filebuffer in order
     * to send it to the host application     *
     */
    private void sendSmartCardKey() {
        try {
            // copy exponent into fileBuffer
            short exponent_length =
                    ((RSAPublicKey) smartCardKeys.getPublic()).getExponent(
                    fileBuffer, (short) 0);

            // copy modulus into fileBuffer
            short modulus_length =
                    ((RSAPublicKey) smartCardKeys.getPublic()).getModulus(
                    fileBuffer, exponent_length);

            // set length fileBuffer
            fileSize = (short) (modulus_length + exponent_length);
        } catch (CryptoException ex) {
            ISOException.throwIt((short) (SW_CRYPTO_EXCEPTION
                    + ex.getReason()));
        } catch (ArrayIndexOutOfBoundsException ex) {
            ISOException.throwIt(SW_ARRAY_EXCEPTION);
        } catch (NullPointerException ex) {
            ISOException.throwIt(SW_NULLPOINTER_EXCEPTION);
        }
    }

    /**
     * Method to set the exponent and modulus of a rsa public key
     *
     * @param key rsa public key which component should be set
     */
    private void setPublicKey(RSAPublicKey key) {
        try {
            // set the exponent of the key
            key.setExponent(fileBuffer, (short) 0, (short) 3);

            // strip of any integer padding
            short off = 3;
            short length = fileSize;
            if (fileBuffer[off] == 0x00) {
                off++;
                length--;
            }

            // set the modulus of the key
            key.setModulus(fileBuffer, off, length);
        } catch (CryptoException ex) {
            ISOException.throwIt((short) (SW_CRYPTO_EXCEPTION
                    + ex.getReason()));
        }
    }

    /**
     * Method to realize a hybrid encryption.
     * First the fileBuffer is encrypted with 3DES and afterwards the
     * 3DES key is encrypted with the public keey of the receiver
     *
     * @param key RSA-PublicKey of the Receiver
     *
     */
    private void encryptData(RSAPublicKey key) {
        try {
            // generate a random session key
            randomData.generateData(sessionKeyBytes, (short) 0, (short) 15);
            sessionKey.setKey(sessionKeyBytes, (short) 0);

            // encrypt the complete fileBuffer with the session key
            cipherDES.init(sessionKey, Cipher.MODE_ENCRYPT);
            short des_length = cipherDES.doFinal(fileBuffer, (short) 0,
                    (short) (fileSize), desBuffer, (short) 0);
            // reset the session key
            sessionKey.clearKey();

            // encrypt the session key with the public key of the receiver
            cipherRSA.init(key, Cipher.MODE_ENCRYPT);
            short rsa_length = cipherRSA.doFinal(sessionKeyBytes, (short) 0,
                    (short) sessionKeyBytes.length, rsaBuffer, (short) 0);

            // copy the encrypted session key in the fileBuffer
            Util.arrayCopy(rsaBuffer, (short) 0, fileBuffer, (short) 0,
                    (short) rsa_length);

            // copy the encrypted fileBuffer back in the fileBuffer
            Util.arrayCopy(desBuffer, (short) 0, fileBuffer, rsa_length,
                    des_length);

            // set the length of the fileBuffer
            fileSize = (short) (des_length + rsa_length);
        } catch (CryptoException ex) {
            ISOException.throwIt((short) (SW_CRYPTO_EXCEPTION
                    + ex.getReason()));
        } catch (ArrayIndexOutOfBoundsException ex) {
            ISOException.throwIt(SW_ARRAY_EXCEPTION);
        } catch (NullPointerException ex) {
            ISOException.throwIt(SW_NULLPOINTER_EXCEPTION);
        }
    }

    /**
     * Method to extend the received message from the host application with
     * the specific IDs.
     * @param parameter
     */
    private void extendMessage(byte parameter) {
        if (parameter == P1_ELECTRICITY_SUPPLIER) {
            // copy high level ID to the fileBuffer
            Util.arrayCopy(fileBuffer, (short) 0, fileBuffer,
                    (short) highLevelID.length, fileSize);
            Util.arrayCopy(highLevelID, (short) 0, fileBuffer, (short) 0,
                    (short) highLevelID.length);
            fileSize+=highLevelID.length;
        }
        if (parameter == P1_TRUSTED_THIRD_PARTY) {
            // copy low level ID and electric supplier ID to the fileBuffer
            Util.arrayCopy(fileBuffer, (short) 0, fileBuffer,
                    (short) (lowLevelID.length + esID.length+areaID.length), fileSize);
            Util.arrayCopy(lowLevelID, (short) 0, fileBuffer, (short) 0,
                    (short) lowLevelID.length);
            Util.arrayCopy(esID, (short) 0, fileBuffer,
                    (short) lowLevelID.length, (short) esID.length);
            Util.arrayCopy(areaID, (short) 0, fileBuffer,
                    (short) lowLevelID.length, (short) esID.length);
            fileSize+=lowLevelID.length + esID.length+areaID.length;
        }
    }

    /**
     * Method to sign a message.
     */
    private void signData() {
        try {
            // hash the fileBuffer, because the sign / verify implementation of
            // the used smart card needs exact 20 bytes as input size
            // normally this is not needed
            messageDigest.doFinal(fileBuffer, (short) 0, fileSize, hashBuffer,
                    (short) 0);

            // sign the fileBuffer
            signature.init(smartCardKeys.getPrivate(), Signature.MODE_SIGN);
            short sig_length = signature.sign(hashBuffer, (short) 0,
                    (short) hashBuffer.length, sigBuffer, (short) 0);

            // copy the signature at the beginning of the fileBuffer
            Util.arrayCopy(fileBuffer, (short) 0, fileBuffer, (short) 0,
                    (short) sig_length);
            Util.arrayCopy(sigBuffer, (short) 0, fileBuffer, (short) 0,
                    sig_length);

            // set the length of the fileBuffer
            fileSize += sig_length;
        } catch (CryptoException ex) {
            ISOException.throwIt((short) (SW_CRYPTO_EXCEPTION
                    + ex.getReason()));
        } catch (ArrayIndexOutOfBoundsException ex) {
            ISOException.throwIt(SW_ARRAY_EXCEPTION);
        } catch (NullPointerException ex) {
            ISOException.throwIt(SW_NULLPOINTER_EXCEPTION);
        }
    }
}
If you need additional information, let me know.
Thanks
Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Oct 18 2011
Added on Sep 13 2011
3 comments
392 views