/**
* CardApplet.java
*
* JavaCard operations
*
* Copyright (C) Pim Vullers, March 2010.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package applet;
import com.nxp.id.jcopx.KeyAgreementX;
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.Util;
import javacard.security.CryptoException;
import javacard.security.ECPrivateKey;
import javacard.security.ECPublicKey;
import javacard.security.KeyAgreement;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
/**
* JavaCard applet class
*/
public class CardApplet extends Applet {
public static void install(byte[] bArray, short bOffset, byte bLength) {
// GP-compliant applet registration
new CardApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]);
}
private static final short _0 = 0;
private static final short KEY_LENGTH = KeyBuilder.LENGTH_EC_FP_128;
private static final short ATTRIBUTE_COUNT = 4;
private static final short KEY_SIZE = KEY_LENGTH / 8;
private static final short POINT_SIZE = KEY_SIZE * 2 + 1;
private static final short ATTRIBUTE_SIZE = KEY_SIZE + POINT_SIZE + 4;
private boolean initialised = false;
private KeyAgreement agreement;
private ECPrivateKey privKey;
private ECPublicKey pubKey;
private KeyPair keyPair;
private ECPrivateKey blindKeyValue;
private ECPublicKey blindKeyPoint;
private KeyPair blindKeyPair;
private byte[] attribute_id;
private short[] attribute_length;
private Object[] attribute_signature;
private Object[] attribute_value;
private byte[] public_key;
private byte[] point;
/**
* Allocate memory for the data on the card and initialise the fields
*/
public CardApplet() {
// Get instances of cryptographic operations
agreement = KeyAgreementX.getInstance(KeyAgreementX.ALG_EC_SVDP_DH_PLAIN, false);
// Build keys
privKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KEY_LENGTH, false);
pubKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KEY_LENGTH, false);
blindKeyValue = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KEY_LENGTH, false);
blindKeyPoint = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KEY_LENGTH, false);
public_key = new byte[POINT_SIZE];
// Construct key pairs
keyPair = new KeyPair(pubKey, privKey);
blindKeyPair = new KeyPair(blindKeyPoint, blindKeyValue);
// Construct attribute storage
attribute_id = new byte[ATTRIBUTE_COUNT];
Util.arrayFillNonAtomic(attribute_id, _0, ATTRIBUTE_COUNT, (byte) 0x00);
attribute_length = new short[ATTRIBUTE_COUNT];
attribute_signature = new Object[ATTRIBUTE_COUNT];
attribute_value = new Object[ATTRIBUTE_COUNT];
for (short i = 0; i < ATTRIBUTE_COUNT; i++) {
attribute_signature[i] = new byte[POINT_SIZE];
attribute_value[i] = new byte[ATTRIBUTE_SIZE];
}
// Some temporary space
point = JCSystem.makeTransientByteArray((short) (POINT_SIZE*2), JCSystem.CLEAR_ON_RESET);
}
/**
* Process an incoming APDU command, i.e. select the appropriate method
*/
public void process(APDU apdu) {
// Good practice: Return 9000 on SELECT
if (selectingApplet()) return;
// Select the appropriate method
apdu.setIncomingAndReceive();
byte[] buffer = apdu.getBuffer();
switch (buffer[ISO7816.OFFSET_INS]) {
case (byte) 0x01:
initialise(apdu);
break;
case (byte) 0x02:
personalise(apdu);
break;
case (byte) 0x03:
getAttribute(apdu);
break;
case (byte) 0x04:
getKey(apdu);
break;
default:
// Good practice: If you don't know the INStruction, say so:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
/**
* Initialise the cryptographic parameters on the card
*
* @param apdu APDU containing the prime number for the finite field F_P,
* the order of the elliptic curve, A and B parameters defining
* the curve: y^2 = x^3 + Ax + B (mod P) and a generator point on
* the curve
*/
private void initialise(APDU apdu) {
short length, offset = ISO7816.OFFSET_CDATA;
byte[] buffer = apdu.getBuffer();
try {
if(initialised) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
// Set the field prime P
length = Util.getShort(buffer, offset);
offset += 2;
privKey.setFieldFP(buffer, offset, length);
pubKey.setFieldFP(buffer, offset, length);
blindKeyValue.setFieldFP(buffer, offset, length);
blindKeyPoint.setFieldFP(buffer, offset, length);
offset += length;
// Set the curve order R
length = Util.getShort(buffer, offset);
offset += 2;
privKey.setR(buffer, offset, length);
pubKey.setR(buffer, offset, length);
blindKeyValue.setR(buffer, offset, length);
blindKeyPoint.setR(buffer, offset, length);
offset += length;
// Set the curve parameter A
length = Util.getShort(buffer, offset);
offset += 2;
privKey.setA(buffer, offset, length);
pubKey.setA(buffer, offset, length);
blindKeyValue.setA(buffer, offset, length);
blindKeyPoint.setA(buffer, offset, length);
offset += length;
// Set the curve parameter B
length = Util.getShort(buffer, offset);
offset += 2;
privKey.setB(buffer, offset, length);
pubKey.setB(buffer, offset, length);
blindKeyValue.setB(buffer, offset, length);
blindKeyPoint.setB(buffer, offset, length);
offset += length;
// Set the generator point G
length = Util.getShort(buffer, offset);
offset += 2;
privKey.setG(buffer, offset, length);
pubKey.setG(buffer, offset, length);
blindKeyValue.setG(buffer, offset, length);
blindKeyPoint.setG(buffer, offset, length);
offset += length;
// Switch APDU mode
apdu.setOutgoing();
apdu.setOutgoingLength((short) (length + 2));
offset = 0;
// Generate a key pair
boolean privIsNat = false;
while (!privIsNat) {
keyPair.genKeyPair();
((ECPrivateKey)keyPair.getPrivate()).getS(buffer, offset);
privIsNat = (buffer[0] & 0x80) == 0x00;
}
// Store the public key in an array to avoid copying later on
pubKey.getW(public_key, _0);
// Return the cards public key
length = pubKey.getW(buffer, (short) (offset + 2));
Util.setShort(buffer, offset, length);
offset += length + 2;
// Send response
apdu.sendBytes(_0, offset);
initialised = true;
} catch (CryptoException e) {
ISOException.throwIt((short) ((short)0x5000 + offset | (e.getReason() << 8)));
} catch (APDUException e) {
ISOException.throwIt((short) ((short)0x7000 + offset | (e.getReason() << 8)));
} catch (Exception e) {
ISOException.throwIt((short) ((short)0x8000 + offset));
}
}
/**
* Initialise the cryptographic parameters on the card
*
* @param apdu APDU containing the prime number for the finite field F_P,
* the order of the elliptic curve, A and B parameters defining
* the curve: y^2 = x^3 + Ax + B (mod P) and a generator point on
* the curve
*/
private void getKey(APDU apdu) {
short length, offset = 0;
try {
// Switch APDU mode
apdu.setOutgoing();
byte[] buffer = apdu.getBuffer();
length = pubKey.getW(buffer, (short) (offset + 2));
apdu.setOutgoingLength((short) (length + 2));
// Return the cards public key
Util.setShort(buffer, offset, length);
offset += length + 2;
// Send response
apdu.sendBytes(_0, offset);
} catch (CryptoException e) {
ISOException.throwIt((short) ((short)0x5000 + offset | (e.getReason() << 8)));
} catch (APDUException e) {
ISOException.throwIt((short) ((short)0x7000 + offset | (e.getReason() << 8)));
}
}
/**
* Store a number of attributes (with corresponding signatures) on the card
*
* @param apdu APDU containing attribute/signature pairs
*/
private void personalise(APDU apdu) {
short offset = ISO7816.OFFSET_CDATA;
byte[] buffer = apdu.getBuffer();
// Set attributeCount
short attribute_count = Util.getShort(buffer, offset);
offset += 2;
for (short k = 0; k < attribute_count; k++) {
// Store the attribute ID (1 byte)
short index = (short)(buffer[offset]-1);
attribute_id[index] = buffer[offset];
offset += 1;
// Store the attribute signature (POINT_SIZE bytes)
Util.arrayCopyNonAtomic(buffer, offset, (byte[]) attribute_signature[index], _0, POINT_SIZE);
offset += POINT_SIZE;
// Store the length of the attribute (1 short == 2 bytes)
attribute_length[index] = Util.getShort(buffer, offset);
offset += 2;
// Store the attribute value and its length (length bytes)
Util.arrayCopyNonAtomic(buffer, offset, (byte[]) attribute_value[index], _0, attribute_length[index]);
offset += attribute_length[index];
}
}
/**
* Get an attribute from the card
*
* @param apdu APDU containing the index of the attribute
* @return Blinded public key, blinded attribute signature and the attribute
*/
private void getAttribute(APDU apdu) {
short length = 0, index = 0, offset = ISO7816.OFFSET_CDATA;
byte[] buffer = apdu.getBuffer();
try {
// Get the index, i.e. look-up the id, throw exception if not found
byte id = buffer[offset++];
while (index < ATTRIBUTE_COUNT && attribute_id[index] != id) {
index++;
}
if (index >= ATTRIBUTE_COUNT || attribute_id[index] != id) {
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
}
// Get the nonce send by the terminal
length = Util.getShort(buffer, offset);
offset += 2;
blindKeyPoint.setG(buffer, offset, length);
blindKeyValue.setG(buffer, offset, length);
// Switch APDU mode
apdu.setOutgoing();
apdu.setOutgoingLength((short) (KEY_SIZE * 3 + attribute_length[index] + 8));
offset = 0;
// Generate a blinding factor b, store it in blinder and blindKey
blindKeyPair.genKeyPair();
// Sign the nonce using the private key
agreement.init(privKey);
blindKeyPoint.getW(point, _0);
length = agreement.generateSecret(point, _0, POINT_SIZE, buffer, (short) (offset + 2));
Util.setShort(buffer, offset, length);
offset += length + 2;
// Blind the public key using the blinding factor
agreement.init(blindKeyValue);
length = agreement.generateSecret(public_key, _0, POINT_SIZE, buffer, (short) (offset + 2));
Util.setShort(buffer, offset, length);
offset += length + 2;
// Blind attribute signature, which is at attr_index + 2*lengthvalues.length + attribute_value.length
length = agreement.generateSecret((byte[]) attribute_signature[index], _0, POINT_SIZE, buffer, (short) (offset + 2));
Util.setShort(buffer, offset, length);
offset += length + 2;
// Append attribute
Util.arrayCopyNonAtomic((byte[]) attribute_value[index], _0, buffer, (short) (offset + 2), attribute_length[index]);
Util.setShort(buffer, offset, attribute_length[index]);
offset += attribute_length[index] + 2;
// Send response
apdu.sendBytes(_0, offset);
} catch (CryptoException e) {
ISOException.throwIt((short) ((short)0x5000 + offset | (e.getReason() << 8)));
} catch (APDUException e) {
ISOException.throwIt((short) ((short)0x7000 + offset | (e.getReason() << 8)));
} catch (NullPointerException e) {
ISOException.throwIt((short) ((short)0x8000 + offset));
} catch (ArrayIndexOutOfBoundsException e) {
ISOException.throwIt((short) ((short)0x8100 + offset));
}
}
}