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