/*
* Quick-Key Toolset Project.
* Copyright (C) 2010 FedICT.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version
* 3.0 as published by the Free Software Foundation.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, see
* http://www.gnu.org/licenses/.
*/
package be.fedict.eidapplet;
import javacard.framework.APDU;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import javacard.security.KeyPair;
import javacard.security.RSAPrivateKey;
import javacard.security.RSAPrivateCrtKey;
import javacard.security.RSAPublicKey;
import javacard.security.RandomData;
import javacardx.crypto.Cipher;
import org.globalplatform.GPSystem;
public class EidCard extends javacard.framework.Applet {
/* APDU header related constants */
// codes of CLA byte in the command APDUs
private final static byte EIDCARD_CLA_2 = (byte) 0x80;
private final static byte EIDCARD_CLA_1 = (byte) 0x00;
// codes of INS byte in the command APDUs
private final static byte INS_GET_RESPONSE = (byte) 0xC0;
private final static byte INS_SELECT_FILE = (byte) 0xA4;
private final static byte INS_ACTIVATE_FILE = (byte) 0x44;
private final static byte INS_DEACTIVATE_FILE = (byte) 0x04;
private final static byte INS_READ_BINARY = (byte) 0xB0;
private final static byte INS_UPDATE_BINARY = (byte) 0xD6;
private final static byte INS_ERASE_BINARY = (byte) 0x0E;
private final static byte INS_VERIFY_PIN = (byte) 0x20;
private final static byte INS_CHANGE_PIN = (byte) 0x24;
private final static byte INS_UNBLOCK = (byte) 0x2C;
private final static byte INS_GET_CHALLENGE = (byte) 0x84;
private final static byte INS_INTERNAL_AUTHENTICATE = (byte) 0x88;
private final static byte INS_EXTERNAL_AUTHENTICATE = (byte) 0x82;
private final static byte INS_ENVELOPE = (byte) 0xC2;
private final static byte INS_PREPARE_SIGNATURE = (byte) 0x22;
private final static byte INS_GENERATE_SIGNATURE = (byte) 0x2A;
private final static byte INS_GENERATE_KEYPAIR = (byte) 0x46;
private final static byte INS_GET_KEY = (byte) 0xE2;
private final static byte INS_PUT_KEY = (byte) 0xF2;
private final static byte INS_ERASE_KEY = (byte) 0xF4;
private final static byte INS_ACTIVATE_KEY = (byte) 0xF6;
private final static byte INS_DEACTIVATE_KEY = (byte) 0xF8;
private final static byte INS_GET_CARD_DATA = (byte) 0xE4;
private final static byte INS_LOG_OFF = (byte) 0xE6;
private final static byte INS_BLOCK = (byte) 0xE8;
private byte[] previousApduType; // transient byte array with 1 element
// "generate signature" needs to know whether the previous APDU checked the
// cardholder PIN
private final static byte VERIFY_CARDHOLDER_PIN = (byte) 0x01;
// PIN Change needs to know whether the previous APDU checked the reset PIN
private final static byte VERIFY_RESET_PIN = (byte) 0x02;
private final static byte GENERATE_KEY_PAIR = (byte) 0x03;
private final static byte OTHER = (byte) 0x00;
/* applet specific status words */
// some are defined in ISO7816, but not by JavaCard
private final static short SW_CANCELLED = (short) 0xFFFF;
private final static short SW_ALGORITHM_NOT_SUPPORTED = (short) 0x9484;
// last nibble of SW2 needs to be overwritten by the counter value/number of
// PIN tries left
private final static short SW_WRONG_PIN_0_TRIES_LEFT = (short) 0x63C0;
private final static short SW_INCONSISTENT_P1P2 = (short) 0x6A87;
private final static short SW_REFERENCE_DATA_NOT_FOUND = (short) 0x6A88;
// wrong Le field; SW2 encodes the exact number of available data bytes
private final static short SW_WRONG_LENGTH_00 = (short) 0x6C00;
/* PIN related variables */
// offsets within PIN related APDUs
private final static byte OFFSET_PIN_HEADER = ISO7816.OFFSET_CDATA;
private final static byte OFFSET_PIN_DATA = ISO7816.OFFSET_CDATA + 1;
private final static byte OFFSET_SECOND_PIN_HEADER = ISO7816.OFFSET_CDATA + 8;
private final static byte OFFSET_SECOND_PIN_DATA = ISO7816.OFFSET_CDATA + 9;
private final static byte OFFSET_SECOND_PIN_DATA_END = ISO7816.OFFSET_CDATA + 15;
// 4 different PIN codes
protected final static byte PIN_SIZE = 8;
protected final static byte CARDHOLDER_PIN = (byte) 0x01;
protected final static byte CARDHOLDER_PIN_TRY_LIMIT = 3;
protected final static byte RESET_PIN = (byte) 0x02;
protected final static byte RESET_PIN_TRY_LIMIT = 10;
protected final static byte UNBLOCK_PIN = (byte) 0x03;
protected final static byte UNBLOCK_PIN_TRY_LIMIT = 12;
protected final static byte ACTIVATE_PIN = (byte) 0x84;
protected final static byte ACTIVATE_PIN_TRY_LIMIT = 15;
protected OwnerPIN cardholderPin, resetPin, unblockPin, activationPin;
/* signature related variables */
private byte signatureAlgorithm;
private final static byte ALG_PKCS1 = (byte) 0x01;
private final static byte ALG_SHA1_PKCS1 = (byte) 0x02;
private final static byte ALG_MD5_PKCS1 = (byte) 0x04;
private final static byte[] PKCS1_HEADER = { (byte) 0x00 };
private final static byte[] PKCS1_SHA1_HEADER = { (byte) 0x00, (byte) 0x30, (byte) 0x21, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x05, (byte) 0x2b, (byte) 0x0e, (byte) 0x03, (byte) 0x02, (byte) 0x1a, (byte) 0x05, (byte) 0x00, (byte) 0x04,
(byte) 0x14 };
private final static byte[] PKCS1_MD5_HEADER = { (byte) 0x00, (byte) 0x30, (byte) 0x20, (byte) 0x30, (byte) 0x0c, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x02, (byte) 0x05,
(byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x10 };
private byte[] signatureType; // transient byte array with 1 element
private final static byte NO_SIGNATURE = (byte) 0x00;
private final static byte BASIC = (byte) 0x81;
private final static byte AUTHENTICATION = (byte) 0x82;
private final static byte NON_REPUDIATION = (byte) 0x83;
private final static byte CA_ROLE = (byte) 0x87;
// make this static to save some memory
protected static KeyPair basicKeyPair;
protected static KeyPair authKeyPair;
protected static KeyPair nonRepKeyPair;
// reuse these objects in all subclasses, otherwise we will use up all
// memory
private static Cipher cipher;
private static RandomData randomData;
// this buffer is used to correct PKCS#1 clear text message
private static byte[] messageBuffer;
/*
* "file system" related variables see Belgian Electronic Identity Card
* content
*/
protected final static short MF = (short) 0x3F00;
protected final static short EF_DIR = (short) 0x2F00;
protected final static short DF_BELPIC = (short) 0xDF00;
protected final static short DF_ID = (short) 0xDF01;
protected MasterFile masterFile;
protected DedicatedFile belpicDirectory, idDirectory;
protected ElementaryFile dirFile;
// data under BELPIC directory
protected final static short ODF = (short) 0x5031;
protected final static short TOKENINFO = (short) 0x5032;
protected final static short AODF = (short) 0x5034;
protected final static short PRKDF = (short) 0x5035;
protected final static short CDF = (short) 0x5037;
protected final static short AUTH_CERTIFICATE = (short) 0x5038;
protected final static short NONREP_CERTIFICATE = (short) 0x5039;
protected final static short CA_CERTIFICATE = (short) 0x503A;
protected final static short ROOT_CA_CERTIFICATE = (short) 0x503B;
protected final static short RRN_CERTIFICATE = (short) 0x503C;
protected ElementaryFile objectDirectoryFile, tokenInfo, authenticationObjectDirectoryFile, privateKeyDirectoryFile, certificateDirectoryFile, authenticationCertificate, nonRepudiationCertificate, caCertificate, rootCaCertificate, rrnCertificate;
// data under ID directory
protected final static short IDENTITY = (short) 0x4031;
protected final static short SGN_IDENTITY = (short) 0x4032;
protected final static short ADDRESS = (short) 0x4033;
protected final static short SGN_ADDRESS = (short) 0x4034;
protected final static short PHOTO = (short) 0x4035;
protected final static short CA_ROLE_ID = (short) 0x4038;
protected final static short PREFERENCES = (short) 0x4039;
protected ElementaryFile identityFile, identityFileSignature, addressFile, addressFileSignature, photoFile, caRoleIDFile, preferencesFile;
/*
* different file operations see ISO 7816-4 table 17+18
*/
// access mode byte for EFs
private final static byte READ_BINARY = (byte) 0x01;
private final static byte SEARCH_BINARY = (byte) 0x01;
private final static byte UPDATE_BINARY = (byte) 0x02;
private final static byte ERASE_BINARY = (byte) 0x02;
private final static byte WRITE_BINARY = (byte) 0x04;
// access mode byte for DFs
private final static byte DELETE_CHILD_FILE = (byte) 0x01;
private final static byte CREATE_EF = (byte) 0x02;
private final static byte CREATE_DF = (byte) 0x04;
// access mode byte common to DFs and EFs
private final static byte DEACTIVATE_FILE = (byte) 0x08;
private final static byte ACTIVATE_FILE = (byte) 0x10;
private final static byte TERMINATE_FILE = (byte) 0x20;
private final static byte DELETE_FILE = (byte) 0x40;
/* variables to pass information between different APDU commands */
// last generated random challenge will be stored in this buffer
private byte[] randomBuffer;
// last generated response (e.g. signature) will be stored in this buffer
private byte[] responseBuffer;
// file selected by SELECT FILE; defaults to the MF
private File selectedFile;
// only 5000 internal authenticates can be done and then the activation
// PIN needs to be checked again
private short internalAuthenticateCounter = 5000;
/**
* called by the JCRE to create an applet instance
*/
public static void install(byte[] bArray, short bOffset, byte bLength) {
// create a eID card applet instance
new EidCard();
}
/**
* private constructor - called by the install method to instantiate a
* EidCard instance
*
* needs to be protected so that it can be invoked by subclasses
*/
protected EidCard() {
randomBuffer = new byte[256];
responseBuffer = new byte[128];
// initialize these objects once for the superclass
// otherwise we have RAM problems when running multiple EidCard applets
if (EidCard.randomData == null)
EidCard.randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
if (EidCard.cipher == null)
EidCard.cipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false);
if (EidCard.messageBuffer == null)
messageBuffer = JCSystem.makeTransientByteArray((short) 128, JCSystem.CLEAR_ON_DESELECT);
// make these transient objects so that they are stored in RAM
previousApduType = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_DESELECT);
signatureType = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_DESELECT);
// register the applet instance with the JCRE
register();
}
/**
* initialize the applet when it is selected
*
* select always has to happen after a reset
*/
public boolean select() {
// Clear data and set default selectedFile to masterFile
clear();
return true;
}
/**
* perform any cleanup and bookkeeping tasks before the applet is deselected
*/
public void deselect() {
clear();
return;
}
/**
* perform any cleanup tasks and set default selectedFile
*/
private void clear() {
// clear signature and random data buffer
Util.arrayFillNonAtomic(randomBuffer, (short) 0, (short) 256, (byte) 0);
Util.arrayFillNonAtomic(responseBuffer, (short) 0, (short) 128, (byte) 0);
// no EF and DF selected yet; select MF by default
selectedFile = masterFile;
// invalidate cardholder PIN
cardholderPin.reset();
/*
* clear text message buffer, signature and previous ADPU type are
* transient so no need to reset these manually
*/
}
/**
* process APDUs
*/
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
/*
* - non repudiation signatures can only be generated if the previous
* APDU verified the cardholder PIN - administrator PIN change is only
* possible if the previous APDU verified the reset PIN
*
* so only the "generate signature" and PIN Change APDU needs to check
* the previous APDU type; in all other cases overwrite the previous
* APDU type, because this information is not needed; we do this as
* early as possible to cope with exceptions being thrown during
* processing of APDU
*
* IMPORTANT : we have to set the previous APDU type in the processing
* of a PIN Verify APDU (because the type gets overwritten to a wrong
* value) and at the end of a "generate signature" and PIN Change APDU
*/
if ((buffer[ISO7816.OFFSET_INS] != INS_GENERATE_SIGNATURE) && (buffer[ISO7816.OFFSET_INS] != INS_CHANGE_PIN) && (buffer[ISO7816.OFFSET_INS] != INS_GET_KEY))
setPreviousApduType(OTHER);
// return if the APDU is the applet SELECT command
if (selectingApplet()) {
return;
}
if (buffer[ISO7816.OFFSET_CLA] == EIDCARD_CLA_1)
// check the INS byte to decide which service method to call
switch (buffer[ISO7816.OFFSET_INS]) {
// case INS_CHANGE_ATR :
// changeATR(apdu);
// break;
case INS_VERIFY_PIN:
verifyPin(apdu, buffer);
break;
case INS_CHANGE_PIN:
changePin(apdu, buffer);
break;
case INS_UNBLOCK:
unblock(apdu, buffer);
break;
case INS_GET_CHALLENGE:
getChallenge(apdu, buffer);
break;
case INS_PREPARE_SIGNATURE:
prepareForSignature(apdu, buffer);
break;
case INS_GENERATE_SIGNATURE:
generateSignature(apdu, buffer);
break;
case INS_GENERATE_KEYPAIR:
generateKeyPair(apdu);
break;
case INS_INTERNAL_AUTHENTICATE:
internalAuthenticate(apdu, buffer);
break;
case INS_GET_RESPONSE:
// if only T=0 supported: remove
// not possible in case of T=0 protocol
if (APDU.getProtocol() == APDU.PROTOCOL_T1)
getResponse(apdu, buffer);
else
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
break;
case INS_SELECT_FILE:
selectFile(apdu, buffer);
break;
case INS_ACTIVATE_FILE:
activateFile(apdu, buffer);
break;
case INS_DEACTIVATE_FILE:
deactivateFile(apdu, buffer);
break;
case INS_READ_BINARY:
readBinary(apdu, buffer);
break;
case INS_UPDATE_BINARY:
updateBinary(apdu, buffer);
break;
case INS_ERASE_BINARY:
eraseBinary(apdu, buffer);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
break;
}
else if (buffer[ISO7816.OFFSET_CLA] == EIDCARD_CLA_2)
switch (buffer[ISO7816.OFFSET_INS]) {
case INS_GET_KEY:
getPublicKey(apdu);
break;
case INS_PUT_KEY:
putPublicKey(apdu, buffer);
break;
case INS_ERASE_KEY:
eraseKey(apdu, buffer);
break;
case INS_ACTIVATE_KEY:
activateKey(apdu, buffer);
break;
case INS_DEACTIVATE_KEY:
deactivateKey(apdu, buffer);
break;
case INS_GET_CARD_DATA:
getCardData(apdu, buffer);
break;
case INS_LOG_OFF:
logOff(apdu, buffer);
break;
// case INS_BLOCK :
// blockCard(apdu, buffer);
// break;
}
else
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
/**
* Gives back information on this eID
*
* @param apdu
* @param buffer
*/
private void getCardData(APDU apdu, byte[] buffer) {
// check P1 and P2
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00 || buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
// inform the JCRE that the applet has data to return
apdu.setOutgoing();
byte[] data = identityFile.getData();
// Only the chip number is of importance: get this at tag position 2
short pos = 1;
short dataLen = (short) data[pos];
pos = (short) (pos + 1 + dataLen + 1);
dataLen = (short) data[pos];
pos = (short) (pos + 1);
// check Le
// if (le != dataLen)
// ISOException.throwIt((short)(ISO7816.SW_WRONG_LENGTH));
byte version[] = { (byte) 0xA5, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x11, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x0F };
byte chipNumber[] = new byte[(short) (dataLen + 12)];
Util.arrayCopy(data, pos, chipNumber, (short) 0, dataLen);
Util.arrayCopy(version, (short) 0, chipNumber, dataLen, (short) 12);
// //Set serial number
// Util.arrayCopy(tokenInfo.getData(), (short) 7, tempBuffer, (short) 0,
// (short) 16);
//
// //Set component code: TODO
//
//
// //Set OS number: TODO
//
//
// //Set OS version: TODO
// JCSystem.getVersion();
//
// //Set softmask number: TODO
//
// //Set softmask version: TODO
//
// //Set applet version: TODO : 4 bytes in file system
//
//
// //Set Interface version: TODO
//
// //Set PKCS#15 version: TODO
//
// //Set applet life cycle
// tempBuffer[(short)(le-1)] = GPSystem.getCardState();
// set the actual number of outgoing data bytes
apdu.setOutgoingLength((short) chipNumber.length);
// send content of buffer in apdu
apdu.sendBytesLong(chipNumber, (short) 0, (short) chipNumber.length);
}
/**
* verify the PIN
*/
private void verifyPin(APDU apdu, byte[] buffer) {
// check P1
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
// receive the PIN data for validation
apdu.setIncomingAndReceive();
// check PIN depending on value of P2
switch (buffer[ISO7816.OFFSET_P2]) {
case CARDHOLDER_PIN:
// overwrite previous APDU type
setPreviousApduType(VERIFY_CARDHOLDER_PIN);
// check the cardholder PIN
checkPin(cardholderPin, buffer);
break;
case ACTIVATE_PIN:
// check the activation PIN
checkPin(activationPin, buffer);
// if the activation PIN was entered correctly
if (GPSystem.getCardContentState() == GPSystem.APPLICATION_SELECTABLE)
// set the applet status to personalized
GPSystem.setCardContentState(GPSystem.CARD_SECURED);
// reset internal authenticate counter
internalAuthenticateCounter = 5000;
break;
case RESET_PIN:
// overwrite previous APDU type
setPreviousApduType(VERIFY_RESET_PIN);
// check the reset PIN
checkPin(resetPin, buffer);
break;
case UNBLOCK_PIN:
// check the unblock PIN: after this, the pin will be 'activated'
checkPin(unblockPin, buffer);
break;
default:
ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
}
}
/**
* check the PIN
*/
private void checkPin(OwnerPIN pin, byte[] buffer) {
if (pin.check(buffer, OFFSET_PIN_HEADER, PIN_SIZE) == true)
return;
short tries = pin.getTriesRemaining();
// the eID card throws this exception, SW=0x63C0 would make more sense
if (tries == 0) {
// if the cardholder PIN is no longer valid (too many tries)
if (pin == cardholderPin)
// set the applet status to blocked
GPSystem.setCardContentState(GPSystem.CARD_LOCKED);
ISOException.throwIt(ISO7816.SW_FILE_INVALID);
}
/*
* create the correct exception the status word is of the form 0x63Cx
* with x the number of tries left
*/
short sw = (short) (SW_WRONG_PIN_0_TRIES_LEFT | tries);
ISOException.throwIt(sw);
}
/**
* change the PIN
*/
private void changePin(APDU apdu, byte[] buffer) {
/*
* IMPORTANT: in all other APDUs the previous APDU type gets overwritten
* in process() function; this is not the case here because the
* information is needed when processing to verify the security
* condition for administrator PIN change
*
* the previous APDU type has to be overwritten in every possible exit
* path out of this function
*/
// check P2
if (buffer[ISO7816.OFFSET_P2] != (byte) 0x01) {
setPreviousApduType(OTHER);
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// P1 determines whether it is user or administrator PIN change
switch (buffer[ISO7816.OFFSET_P1]) {
case (byte) 0x00:
setPreviousApduType(OTHER);
userChangePin(apdu, buffer);
break;
case (byte) 0x01:
administratorChangePin(apdu, buffer);
break;
default:
setPreviousApduType(OTHER);
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
break;
}
}
/**
* user changes the PIN
*/
private void userChangePin(APDU apdu, byte[] buffer) {
// receive the PIN data
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((lc != 16) || (byteRead != 16))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// first check old cardholder PIN
checkPin(cardholderPin, buffer);
// do some checks on the new PIN header and data
if (!isNewPinFormattedCorrectly(buffer, OFFSET_SECOND_PIN_HEADER))
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
// include header as well in PIN object
cardholderPin.update(buffer, OFFSET_SECOND_PIN_HEADER, PIN_SIZE);
// validate cardholder PIN immediately after change PIN
// so that cardholder access rights are immediately granted
cardholderPin.check(buffer, OFFSET_SECOND_PIN_HEADER, PIN_SIZE);
}
/**
* administrator changes the PIN
*/
private void administratorChangePin(APDU apdu, byte[] buffer) {
// The previous getChallenge() should ask for at least the length of the
// new administrator pin. Otherwise exception is thrown
/*
* IMPORTANT: the previous APDU type has to be overwritten in every
* possible exit path out of this function; therefore we check the
* security conditions as early as possible
*/
// previous APDU must have checked the reset PIN
if ((!resetPin.isValidated()) || (getPreviousApduType() != VERIFY_RESET_PIN)) {
setPreviousApduType(OTHER);
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
// overwrite previous ADPU type as soon as possible
setPreviousApduType(OTHER);
// receive the PIN data
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((lc != 8) || (byteRead != 8))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// do some checks on the new PIN header and data
if (!isNewPinFormattedCorrectly(buffer, OFFSET_PIN_HEADER))
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
// compare the new PIN with the last generated random challenge
if (!isNewPinCorrectValue(buffer))
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
// include header as well in PIN object
cardholderPin.update(buffer, OFFSET_PIN_HEADER, PIN_SIZE);
}
/**
* check if new PIN conforms to internal format
*
* returns false if new PIN is not formatted correctly
*/
private boolean isNewPinFormattedCorrectly(byte[] buffer, byte offset) {
// 1st nibble of new PIN header should be 2
if ((buffer[offset] >> 4) != 2)
return false;
// 2nd nibble of new PIN header is the length (in digits)
byte pinLength = (byte) (buffer[offset] & 0x0F);
// the new PIN should be between 4 and 12 digits
if (pinLength < 4 || pinLength > 12)
return false;
// divide PIN length by 2 to get the length in bytes
byte pinLengthInBytes = (byte) (pinLength >> 1);
// check if PIN length is odd
if ((pinLength & (byte) 0x01) == (byte) 0x01)
pinLengthInBytes++;
// check if PIN data is padded with 0xFF
byte i = (byte) (offset + PIN_SIZE - 1);
for (; i > (byte)(offset + pinLengthInBytes); i--) {
if (buffer[i] != (byte) 0xFF)
return false;
}
// if PIN length is odd, check if last PIN data nibble is F
if ((pinLength & (byte) 0x01) == (byte) 0x01) {
if ((byte) (buffer[i] << 4) != (byte) 0xF0)
return false;
}
return true;
}
/**
* check if new PIN is based on the last generated random challenge
*/
private boolean isNewPinCorrectValue(byte[] buffer) {
// 2nd nibble of the PIN header is the length (in digits)
byte pinLength = (byte) (buffer[OFFSET_PIN_HEADER] & 0x0F);
// check if PIN length is odd
byte oldLength = (byte) (pinLength & 0x01);
// divide PIN length by 2 to get the length in bytes
byte pinLengthInBytes = (byte) (pinLength >> 1);
byte i;
for (i = 0; i < pinLengthInBytes; i++) {
if (buffer[(short)(OFFSET_PIN_DATA + i)] != (randomBuffer[i] & 0x77))
return false;
}
if (oldLength == (byte) 0x01) {
if ((buffer[(short)(OFFSET_PIN_DATA + pinLengthInBytes)] >> 4) != ((randomBuffer[i] & 0x7F) >> 4))
return false;
}
return true;
}
/**
* Discard current fulfilled access conditions
*/
private void logOff(APDU apdu, byte[] buffer) {
// check P1 and P2
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00 || buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
// remove previous access conditions:
setPreviousApduType(OTHER);
setSignatureType(NO_SIGNATURE);
cardholderPin.reset();
resetPin.reset();
unblockPin.reset();
activationPin.reset();
}
/**
* unblock card
*/
private void unblock(APDU apdu, byte[] buffer) {
// check P1 and P2
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00 || buffer[ISO7816.OFFSET_P2] != (byte) 0x01)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
// receive the PUK data for validation
apdu.setIncomingAndReceive();
// check PUK
checkPin(unblockPin, buffer);
// if PUK is correct, then unblock cardholder PINs
cardholderPin.resetAndUnblock();
// set the applet status back to personalized
GPSystem.setCardContentState(GPSystem.CARD_SECURED);
}
/**
* prepare for authentication or non repudiation signature
*/
private void prepareForSignature(APDU apdu, byte[] buffer) {
// check P1 and P2
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x41 || buffer[ISO7816.OFFSET_P2] != (byte) 0xB6)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// receive the data to see which kind of signature
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((lc != 5) || (byteRead != 5))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// the first 2 bytes of the data part should be 0x04 0x80
// the fourth byte should be 0x84
if ((buffer[ISO7816.OFFSET_CDATA] != (byte) 0x04) || (buffer[ISO7816.OFFSET_CDATA + 1] != (byte) 0x80) || (buffer[ISO7816.OFFSET_CDATA + 3] != (byte) 0x84))
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
// initialize signature object depending on hash function type
switch (buffer[ISO7816.OFFSET_CDATA + 2]) {
case ALG_SHA1_PKCS1:
signatureAlgorithm = ALG_SHA1_PKCS1;
break;
case ALG_MD5_PKCS1:
signatureAlgorithm = ALG_MD5_PKCS1;
break;
case ALG_PKCS1:
signatureAlgorithm = ALG_PKCS1;
break;
default: // algorithm not supported (SW=9484)
ISOException.throwIt(SW_ALGORITHM_NOT_SUPPORTED);
break;
}
// signature type is determined by the the last byte
switch (buffer[ISO7816.OFFSET_CDATA + 4]) {
case BASIC:
setSignatureType(BASIC);
break;
case AUTHENTICATION: // use authentication private key
setSignatureType(AUTHENTICATION);
break;
case NON_REPUDIATION: // use non repudiation private key
setSignatureType(NON_REPUDIATION);
break;
case CA_ROLE:
setSignatureType(NO_SIGNATURE);
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
break;
default:
setSignatureType(NO_SIGNATURE);
ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
break;
}
}
/**
* generate (authentication or non repudiation) signature
*/
private void generateSignature(APDU apdu, byte[] buffer) {
/*
* IMPORTANT: in all other APDUs the previous APDU type gets overwritten
* in process() function; this is not the case here because the
* information is needed when processing to verify the security
* condition for non repudiation signature
*
* the previous APDU type has to be overwritten in every possible exit
* path out of this function; therefore we check the security conditions
* of the non repudiation signature as early as possible, but we have to
* overwrite the previous APDU type in the 2 possible exceptions before
*/
// check P1 and P2
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x9E || buffer[ISO7816.OFFSET_P2] != (byte) 0x9A) {
setPreviousApduType(OTHER);
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
}
// generate signature without prepare signature results:
// "conditions of use not satisfied"
if (getSignatureType() == NO_SIGNATURE) {
setPreviousApduType(OTHER);
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
/*
* verify authentication information throw
* "security condition not satisfied" if something is wrong
*/
// check if previous APDU did a cardholder PIN verification
if ((getSignatureType() == NON_REPUDIATION) && (getPreviousApduType() != VERIFY_CARDHOLDER_PIN)) {
setPreviousApduType(OTHER);
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
// overwrite previous ADPU type as soon as possible
setPreviousApduType(OTHER);
// it is impossible to generate basic signatures with this command
if (getSignatureType() == BASIC)
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
// check if cardholder PIN was entered correctly
if (!cardholderPin.isValidated())
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
switch (signatureAlgorithm) {
case ALG_MD5_PKCS1:
generatePkcs1Md5Signature(apdu, buffer);
break;
case ALG_SHA1_PKCS1:
generatePkcs1Sha1Signature(apdu, buffer);
break;
case ALG_PKCS1:
generatePkcs1Signature(apdu, buffer);
break;
}
// if T=1, store signature in sigBuffer so that it can latter be sent
if (APDU.getProtocol() == APDU.PROTOCOL_T1) {
Util.arrayCopy(buffer, (short) 0, responseBuffer, (short) 0, (short) 128);
// in case T=0 protocol, send the signature immediately in a
// response APDU
} else {
// send first 128 bytes (= 1024 bit) of buffer
apdu.setOutgoingAndSend((short) 0, (short) 128);
}
}
/**
* generate PKCS#1 MD5 signature
*/
private void generatePkcs1Md5Signature(APDU apdu, byte[] buffer) {
// receive the data that needs to be signed
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((lc != 16) || (byteRead != 16))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// use the correct key
if (getSignatureType() == NON_REPUDIATION)
cipher.init((RSAPrivateCrtKey)nonRepKeyPair.getPrivate(), Cipher.MODE_ENCRYPT);
if (getSignatureType() == AUTHENTICATION)
cipher.init((RSAPrivateCrtKey)authKeyPair.getPrivate(), Cipher.MODE_ENCRYPT);
// prepare the message buffer to the PKCS#1 (v1.5) structure
preparePkcs1ClearText(messageBuffer, ALG_MD5_PKCS1, lc);
// copy the MD5 hash from the APDU to the message buffer
Util.arrayCopy(buffer, (short) (ISO7816.OFFSET_CDATA), messageBuffer, (short) (128 - lc), lc);
// generate signature
cipher.doFinal(messageBuffer, (short) 0, (short) 128, buffer, (short) 0);
}
/**
* generate PKCS#1 SHA1 signature
*/
private void generatePkcs1Sha1Signature(APDU apdu, byte[] buffer) {
// receive the data that needs to be signed
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((lc != 20) || (byteRead != 20))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// use the correct key
if (getSignatureType() == NON_REPUDIATION)
//cipher.init(nonRepPrivateKey, Cipher.MODE_ENCRYPT);
cipher.init((RSAPrivateCrtKey)nonRepKeyPair.getPrivate(), Cipher.MODE_ENCRYPT);
if (getSignatureType() == AUTHENTICATION)
cipher.init((RSAPrivateCrtKey)authKeyPair.getPrivate(), Cipher.MODE_ENCRYPT);
// prepare the message buffer to the PKCS#1 (v1.5) structure
preparePkcs1ClearText(messageBuffer, ALG_SHA1_PKCS1, lc);
// copy the SHA1 hash from the APDU to the message buffer
Util.arrayCopy(buffer, (short) (ISO7816.OFFSET_CDATA), messageBuffer, (short) (128 - lc), lc);
// generate signature
cipher.doFinal(messageBuffer, (short) 0, (short) 128, buffer, (short) 0);
}
/**
* generate PKCS#1 signature
*/
private void generatePkcs1Signature(APDU apdu, byte[] buffer) {
// receive the data that needs to be signed
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((lc > 117) || (byteRead > 117))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// use the correct key
if (getSignatureType() == NON_REPUDIATION)
cipher.init((RSAPrivateCrtKey)nonRepKeyPair.getPrivate(), Cipher.MODE_ENCRYPT);
if (getSignatureType() == AUTHENTICATION)
cipher.init((RSAPrivateCrtKey)authKeyPair.getPrivate(), Cipher.MODE_ENCRYPT);
// prepare the message buffer to the PKCS#1 (v1.5) structure
preparePkcs1ClearText(messageBuffer, ALG_PKCS1, lc);
// copy the clear text from the APDU to the message buffer
Util.arrayCopy(buffer, (short) (ISO7816.OFFSET_CDATA), messageBuffer, (short) (128 - lc), lc);
// generate signature
cipher.doFinal(messageBuffer, (short) 0, (short) 128, buffer, (short) 0);
}
/**
* prepare the clear text buffer with correct PKCS#1 encoding
*/
private void preparePkcs1ClearText(byte[] clearText, short type, short messageLength) {
// first pad the whole clear text with 0xFF
Util.arrayFillNonAtomic(clearText, (short) 0, (short) 128, (byte) 0xff);
// first 2 bytes should be 0x00 and 0x01
Util.arrayFillNonAtomic(clearText, (short) 0, (short) 1, (byte) 0x00);
Util.arrayFillNonAtomic(clearText, (short) 1, (short) 1, (byte) 0x01);
// add the PKCS#1 header at the correct location
byte[] header = PKCS1_HEADER;
if (type == ALG_SHA1_PKCS1)
header = PKCS1_SHA1_HEADER;
if (type == ALG_MD5_PKCS1)
header = PKCS1_MD5_HEADER;
Util.arrayCopy(header, (short) 0, messageBuffer, (short) (128 - messageLength - header.length), (short) header.length);
}
/**
* generate a key pair
*
* only the private key will be stored in the eid. the get public key method
* should be called directly after this method, otherwise the public key
* will be discarded security conditions depend on the key to generate the
* role R03 (see belgian eid card content) shall be verified for changing
* authentication or non repudiation keys.
*/
private void generateKeyPair(APDU apdu) {
apdu.setIncomingAndReceive();// If this was removed, function will not
// work: no data except for command will be read
byte[] buffer = apdu.getBuffer();
// check if access to this method is allowed
if (GPSystem.getCardContentState() != GPSystem.APPLICATION_SELECTABLE)
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
// check P1 and P2
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if (lc != (short) 11)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
byte offset = (ISO7816.OFFSET_CDATA + 0x01);
// create keypair using parameters given:
// short keyLength = Util.makeShort(buffer[ISO7816.OFFSET_CDATA],
// buffer[offset]);
if (buffer[offset] != (byte) 0x80)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// This is commented out as changing exponent makes getting modulus
// impossible on some java cards
// ((RSAPublicKey)tempkp.getPublic()).setExponent(buffer, (short)(13),
// (short)3);
setPreviousApduType(GENERATE_KEY_PAIR);
switch (buffer[ISO7816.OFFSET_P2]) {
case BASIC:
basicKeyPair = new KeyPair(KeyPair.ALG_RSA_CRT, (short) (1024));
basicKeyPair.genKeyPair();
break;
case AUTHENTICATION: // use authentication private key
authKeyPair = new KeyPair(KeyPair.ALG_RSA_CRT, (short) (1024));
authKeyPair.genKeyPair();
break;
case NON_REPUDIATION: // use non repudiation private key
nonRepKeyPair = new KeyPair(KeyPair.ALG_RSA_CRT, (short) (1024));
nonRepKeyPair.genKeyPair();
break;
default:
ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
break;
}
}
/**
* get a public key. for the authentication and non-repudiation key, this
* method can only be called after the generateKeyPair method was called
*
*/
private void getPublicKey(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// if this is thrown: problem accesses getPreviousapdu
// check P1
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// inform the JCRE that the applet has data to return
short le = apdu.setOutgoing();
// Le = 0 is not allowed
if (le != (short) (5 + 8 + 128))
ISOException.throwIt((short) (SW_WRONG_LENGTH_00 + (5 + 8 + 128)));
byte[] tempBuffer = new byte[le];
tempBuffer[(short) 0] = (byte) 0x02;
tempBuffer[(short) 1] = (byte) 0x08;
tempBuffer[(short) 10] = (byte) 0x03;
tempBuffer[(short) 11] = (byte) 0x81;
tempBuffer[(short) 12] = (byte) 0x80;
if (buffer[ISO7816.OFFSET_P2] == AUTHENTICATION){
if (getPreviousApduType() != GENERATE_KEY_PAIR) {
authKeyPair.getPublic().clearKey();
setPreviousApduType(OTHER);
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
((RSAPublicKey) authKeyPair.getPublic()).getExponent(tempBuffer, (short) 7);
((RSAPublicKey) authKeyPair.getPublic()).getModulus(tempBuffer, (short) 13);
}else if (buffer[ISO7816.OFFSET_P2] == NON_REPUDIATION) {
if (getPreviousApduType() != GENERATE_KEY_PAIR) {
nonRepKeyPair.getPublic().clearKey();
setPreviousApduType(OTHER);
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
((RSAPublicKey) nonRepKeyPair.getPublic()).getExponent(tempBuffer, (short) 7);
((RSAPublicKey) nonRepKeyPair.getPublic()).getModulus(tempBuffer, (short) 13);
}else if (buffer[ISO7816.OFFSET_P2] == BASIC) {
((RSAPublicKey) basicKeyPair.getPublic()).getExponent(tempBuffer, (short) 7);
((RSAPublicKey) basicKeyPair.getPublic()).getModulus(tempBuffer, (short) 13);
} else {
ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
}
setPreviousApduType(OTHER);
authKeyPair.getPublic().clearKey();
nonRepKeyPair.getPublic().clearKey();
// set the actual number of outgoing data bytes
apdu.setOutgoingLength(le);
// send content of buffer in apdu
apdu.sendBytesLong(tempBuffer, (short) 0, le);
}
/**
* put a public key as commune or role key this is not supported anymore
*/
private void putPublicKey(APDU apdu, byte[] buffer) {
ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
}
/**
* erase a public key (basic, commune or role key) only basic supported
*/
private void eraseKey(APDU apdu, byte[] buffer) {
// check P1
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
switch (buffer[ISO7816.OFFSET_P2]) {
case BASIC:
basicKeyPair = null;
break;
default:
ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
break;
}
}
/**
* activate a public authentication or non repudiation key if deactivated
* keys in this applet are always active
*/
private void activateKey(APDU apdu, byte[] buffer) {
// check P1
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
switch (buffer[ISO7816.OFFSET_P2]) {
case AUTHENTICATION:
// activate key: key always active, do nothing
break;
case NON_REPUDIATION:
// activate key: key always active, do nothing
break;
default:
ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
break;
}
}
/**
* deactivate a public authentication or non repudiation key if activated as
* keys are always active, throw exception
*/
private void deactivateKey(APDU apdu, byte[] buffer) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
/**
* internal authenticate generates a signature with the basic private key no
* security conditions needed if used for internal authentication only
* (Mutual authentication not supported)
*/
private void internalAuthenticate(APDU apdu, byte[] buffer) {
// check P1 and P2
if ((buffer[ISO7816.OFFSET_P1] != ALG_SHA1_PKCS1) || buffer[ISO7816.OFFSET_P2] != BASIC)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// receive the data that needs to be signed
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
// we do not support Lc=0x97, only Lc=0x16
if ((lc == 0x97) || (byteRead == 0x97))
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
if ((lc != 0x16) || (byteRead != 0x16))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// the first data byte must be "94" and the second byte is the length
// (20 bytes)
if ((buffer[ISO7816.OFFSET_CDATA] != (byte) 0x94) || (buffer[ISO7816.OFFSET_CDATA + 1] != (byte) 0x14))
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
// use the basic private key
cipher.init(basicKeyPair.getPrivate(), Cipher.MODE_ENCRYPT);
// prepare the message buffer to the PKCS#1 (v1.5) structure
preparePkcs1ClearText(messageBuffer, ALG_SHA1_PKCS1, lc);
// copy the challenge (SHA1 hash) from the APDU to the message buffer
Util.arrayCopy(buffer, (short) (ISO7816.OFFSET_CDATA + 2), messageBuffer, (short) 108, (short) 20);
// generate signature
cipher.doFinal(messageBuffer, (short) 0, (short) 128, buffer, (short) 0);
// if T=0, store signature in sigBuffer so that it can latter be sent
if (APDU.getProtocol() == APDU.PROTOCOL_T1) {
Util.arrayCopy(buffer, (short) 0, responseBuffer, (short) 0, (short) 128);
// in case T=1 protocol, send the signature immediately in a
// response APDU
} else {
// send first 128 bytes (= 1024 bit) of buffer
apdu.setOutgoingAndSend((short) 0, (short) 128);
}
// decrement internal authenticate counter
internalAuthenticateCounter--;
}
/**
* return the generated signature in a response APDU Used in T=0 protocol
*/
private void getResponse(APDU apdu, byte[] buffer) {
// use P1 and P2 as offset
short offset = Util.makeShort(buffer[ISO7816.OFFSET_P1], buffer[ISO7816.OFFSET_P2]);
if (offset > responseBuffer.length)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// inform the JCRE that the applet has data to return
short le = apdu.setOutgoing();
// if Le = 0, then return the complete signature (128 bytes = 1024 bits)
// Le = 256 possible on real card
if ((le == 0) || (le == 256))
le = 128;
// set the actual number of outgoing data bytes
apdu.setOutgoingLength(le);
// send content of sigBuffer in apdu
apdu.sendBytesLong(responseBuffer, offset, le);
}
/**
* generate a random challenge
*/
private void getChallenge(APDU apdu, byte[] buffer) {
// check P1 and P2
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00 || buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// inform the JCRE that the applet has data to return
short le = apdu.setOutgoing();
// Le = 0 is not allowed
if (le == 0)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
RandomData random = EidCard.randomData;
// generate random data and put it into buffer
random.generateData(randomBuffer, (short) 0, le);
// set the actual number of outgoing data bytes
apdu.setOutgoingLength(le);
// send content of buffer in apdu
apdu.sendBytesLong(randomBuffer, (short) 0, le);
}
/**
* select a file on the eID card
*
* this file can latter be read by a READ BINARY
*/
private void selectFile(APDU apdu, byte[] buffer) {
// check P2
if (buffer[ISO7816.OFFSET_P2] != (byte) 0x0C)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// P1 determines the select method
switch (buffer[ISO7816.OFFSET_P1]) {
case (byte) 0x02:
selectByFileIdentifier(apdu, buffer);
break;
case (byte) 0x08:
selectByPath(apdu, buffer);
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
break;
}
}
/**
* select file under the current DF
*/
private void selectByFileIdentifier(APDU apdu, byte[] buffer) {
// receive the data to see which file needs to be selected
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((lc != 2) || (byteRead != 2))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// get the file identifier out of the APDU
short fid = Util.makeShort(buffer[ISO7816.OFFSET_CDATA], buffer[ISO7816.OFFSET_CDATA + 1]);
// if file identifier is the master file, select it immediately
if (fid == MF)
selectedFile = masterFile;
else {
// check if the requested file exists under the current DF
File s = ((DedicatedFile) masterFile).getSibling(fid);
if (s != null)
selectedFile = s;
//the fid is an elementary file:
else {
s = belpicDirectory.getSibling(fid);
if (s != null)
selectedFile = s;
else {
s = idDirectory.getSibling(fid);
if (s != null)
selectedFile = s;
else ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
}
}
}
/**
* select file by path from the MF
*/
private void selectByPath(APDU apdu, byte[] buffer) {
// receive the path name
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
// it must be a multiple of 2
if (((lc & 1) == 1) || ((byteRead & 1) == 1))
ISOException.throwIt(SW_INCONSISTENT_P1P2);
// use the path name in the APDU data to select a file
File f = masterFile;
for (byte i = 0; i < lc; i += 2) {
short fid = Util.makeShort(buffer[(short) (ISO7816.OFFSET_CDATA + i)], buffer[(short) (ISO7816.OFFSET_CDATA + i + 1)]);
// MF can be explicitely or implicitely in the path name
if ((i == 0) && (fid == MF))
f = masterFile;
else {
if ((f instanceof ElementaryFile) || f == null)
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
f = ((DedicatedFile) f).getSibling(fid);
}
}
if (f == null)
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
selectedFile = f;
}
/**
* activate a file on the eID card security conditions depend on file to
* activate: see belgian eID content file
*/
private void activateFile(APDU apdu, byte[] buffer) {
// check P2
if (buffer[ISO7816.OFFSET_P2] != (byte) 0x0C)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// P1 determines the select method
switch (buffer[ISO7816.OFFSET_P1]) {
case (byte) 0x02:
selectByFileIdentifier(apdu, buffer);
break;
case (byte) 0x08:
selectByPath(apdu, buffer);
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
break;
}
// check if activating this file is allowed
if (!fileAccessAllowed(UPDATE_BINARY))
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
selectedFile.setActive(true);
}
/**
* deactivate a file on the eID card security conditions depend on file to
* activate: see belgian eID content file
*/
private void deactivateFile(APDU apdu, byte[] buffer) {
// check P2
if (buffer[ISO7816.OFFSET_P2] != (byte) 0x0C)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// P1 determines the select method
switch (buffer[ISO7816.OFFSET_P1]) {
case (byte) 0x02:
selectByFileIdentifier(apdu, buffer);
break;
case (byte) 0x08:
selectByPath(apdu, buffer);
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
break;
}
// check if deactivating this file is allowed
if (!fileAccessAllowed(UPDATE_BINARY))
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
selectedFile.setActive(false);
}
/**
* put file that was selected with SELECT FILE in a response APDU
*/
private void readBinary(APDU apdu, byte[] buffer) {
// check if access to this file is allowed
if (!fileAccessAllowed(READ_BINARY))
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
// use P1 and P2 as offset
short offset = Util.makeShort(buffer[ISO7816.OFFSET_P1], buffer[ISO7816.OFFSET_P2]);
// inform the JCRE that the applet has data to return
short le = apdu.setOutgoing();
// impossible to start reading from offset large than size of file
short size = ((ElementaryFile) selectedFile).getCurrentSize();
if (offset > size)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// number of bytes in file starting from offset
short remaining = (short) (size - offset);
if (le == 0) {
if (remaining < 256) {
// wrong Le field
// SW2 encodes the exact number of available data bytes
short sw = (short) (ISO7816.SW_CORRECT_LENGTH_00 | remaining);
ISOException.throwIt(sw);
} else
// Le = 0 is interpreted as 256 bytes
le = 256;
}
// only read out the remaining bytes
if (le > remaining) {
le = remaining;
}
// set the actual number of outgoing data bytes
apdu.setOutgoingLength(le);
// write selected file in APDU
apdu.sendBytesLong(((ElementaryFile) selectedFile).getData(), offset, le);
}
/**
* erase data in file that was selected with SELECT FILE
*/
private void eraseBinary(APDU apdu, byte[] buffer) {
// check if access to this file is allowed
if (!fileAccessAllowed(ERASE_BINARY))
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
// use P1 and P2 as offset
short offset = Util.makeShort(buffer[ISO7816.OFFSET_P1], buffer[ISO7816.OFFSET_P2]);
// impossible to start erasing from offset large than size of file
short size = ((ElementaryFile) selectedFile).getCurrentSize();
if (offset > size)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
((ElementaryFile) selectedFile).eraseData(offset);
}
/**
* change data in a file that was selected with SELECT FILE
*/
private void updateBinary(APDU apdu, byte[] buffer) {
// check if access to this file is allowed
if (!fileAccessAllowed(UPDATE_BINARY))
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
// use P1 and P2 as offset
short offset = Util.makeShort(buffer[ISO7816.OFFSET_P1], buffer[ISO7816.OFFSET_P2]);
// impossible to start updating from offset larger than max size of file
// this however does not imply that the file length can not change
short size = ((ElementaryFile) selectedFile).getMaxSize();
if (offset > size)
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
// number of bytes in file starting from offset
// short remaining = (short) (size - offset);
// get the new data
short byteRead = apdu.setIncomingAndReceive();
// check Lc
short lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((lc == 0) || (byteRead == 0))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// update file
((ElementaryFile) selectedFile).updateData(offset, buffer, ISO7816.OFFSET_CDATA, lc);
}
/**
* checks if a certain file operation is allowed on the currently selected
* file
*
* remark 1: a very dedicated (so not generic) implementation! a more
* elegant solution would be to put (parts of) access control in File
* objects
*
* remark 2: there is a huge hack to allow some write updates. this hack is
* harmless, as these write operations happen during the copying of a card,
* not during its use
*/
private boolean fileAccessAllowed(byte mode) {
// if selected file is not an EF, throw "no current EF" exception
if (!(selectedFile instanceof ElementaryFile))
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
// always allow READ BINARY
if (mode == READ_BINARY)
return true;
// allow write access to the preference file if the cardholder pin was
// entered correctly
if ((selectedFile == preferencesFile) && cardholderPin.isValidated())
return true;
// we abuse the activation pin to update some of the large files (photo
// + certificates)
if (GPSystem.getCardContentState() == GPSystem.APPLICATION_SELECTABLE)
return true;
// default to false
return false;
}
/**
* set the previous APDU type to a certain value
*/
private void setPreviousApduType(byte type) {
previousApduType[0] = type;
}
/**
* return the previous APDU type
*/
private byte getPreviousApduType() {
return previousApduType[0];
}
/**
* set the signature type to a certain value
*/
private void setSignatureType(byte type) {
signatureType[0] = type;
}
/**
* return the signature type
*/
private byte getSignatureType() {
return signatureType[0];
}
}