/*
* Java Card PKI applet - ISO7816 compliant Java Card applet.
*
* Copyright (C) 2009 Wojciech Mostowski, woj@cs.ru.nl
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package net.sourceforge.javacardsign.applet;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.SystemException;
import javacard.framework.APDU;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.framework.OwnerPIN;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.RSAPrivateCrtKey;
import javacard.security.MessageDigest;
import javacard.security.RSAPublicKey;
import javacard.security.RandomData;
import javacard.security.CryptoException;
import javacardx.crypto.Cipher;
import org.globalplatform.GPSystem;
/**
* @author Wojciech Mostowski <woj@cs.ru.nl>
*
*/
public class PKIApplet extends Applet implements ISO7816 {
/** CLAss byte masks */
private static final byte CLA_CHAIN = 0x10;
private static final byte CLA_SM = 0x0C;
/** INStructions */
private static final byte INS_READBINARY = (byte)0xB0;
private static final byte INS_VERIFY = (byte)0x20;
private static final byte INS_CHANGEREFERENCEDATA = (byte)0x24;
private static final byte INS_GETCHALLENGE = (byte)0x84;
private static final byte INS_MSE = (byte)0x22;
private static final byte INS_PSO = (byte)0x2A;
private static final byte INS_INTERNALAUTHENTICATE = (byte)0x88;
private static final byte INS_WRITEBINARY = (byte)0xD0;
private static final byte INS_CREATEFILE = (byte)0xE0;
private static final byte INS_PUTDATA = (byte)0xDA;
private static final byte INS_GENERATE_KEY_PAIR = (byte)0x46;
/** Other constants */
private static final byte MASK_SFI = (byte)0x80;
private static final byte MAX_PIN_SIZE = 20;
private static final byte MIN_PIN_SIZE = 4;
private static final byte PIN_TRIES = 3;
private static final byte PUC_SIZE = 16;
private static final byte PUC_TRIES = 3;
private static final byte ALG_AUTH_DEC_RSA = (byte)0x01;
private static final byte ALG_SIGN_RSA_PKCS1_SHA1 = (byte)0x02;
private static final byte ALG_SIGN_RSA_PKCS1_SHA256 = (byte)0x03;
private static final byte ALG_SIGN_RSA_PSS = (byte)0x04;
private static final byte ALG_SIGN_RSA_PKCS1_SHA1MD5 = (byte)0x05;
private static final short MAX_BLOCK_LEN = 256;
private static final short C_LEN = 4;
private static final short SHA1_LEN = 20;
private static final short SHA256_LEN = 32;
private static final short SHA1MD5_LEN = 36;
/** SW-s not defined in the ISO7816 interface */
private static final short SW_END_OF_FILE = (short)0x6282;
private static final short SW_PIN_INCORRECT_TRIES_LEFT = (short)0x63C0;
private static final short SW_KEY_NOT_FOUND = (short)0x6A88;
private static final short SW_LAST_COMMAND_EXPECTED = (short)0x6883;
private static final short SW_SECURE_MESSAGING_NOT_SUPPORTED = (short)0x6882;
/** For calculation of the PSS scheme */
private static final short TMP_OFFSET = 0;
private static final short TMP_HASH_OFFSET = TMP_OFFSET + MAX_BLOCK_LEN;
private static final short TMP_C_OFFSET = TMP_HASH_OFFSET + SHA1_LEN;
private static final short TMP_SIGNALG_OFFSET = TMP_C_OFFSET + C_LEN;
private static final short TMP_DECSEQ_OFFSET = TMP_SIGNALG_OFFSET + 1;
private static final short TMP_SIZE = TMP_DECSEQ_OFFSET + 1;
/** Life states of the applet */
private static final byte STATE_INITIAL = 1; // Before files are written, hist bytes are set, the puc is set
private static final byte STATE_PREPERSONALISED = 2; // Before the pin is set
private static final byte STATE_PERSONALISED = 3; // pin is set, ready to use
private FileSystem fileSystem = null;
private OwnerPIN pin = null;
private OwnerPIN puc = null;
private RandomData rd = null;
private Cipher pkcs1Cipher = null;
private Cipher nopadCipher = null;
private MessageDigest md = null;
private byte state = 0;
private RSAPrivateCrtKey authKeyPrivate = null;
private RSAPrivateCrtKey signKeyPrivate = null;
private byte signKeyFirstModulusByte = 0;
private RSAPrivateCrtKey decKeyPrivate = null;
private RSAPublicKey tempKeyPublic = null;
private Object[] currentPrivateKey = null;
private byte[] tmp = null;
private short[] expectedDecipherDataLength = null;
private byte[] authKeyId = null;
private byte[] signKeyId = null;
private byte[] decKeyId = null;
private static final short KEY_ID_SIZE = 17; // Len + 16 bytes of data
private final static byte[] myAID = new byte[] { (byte) 0xA0, 0x00, 0x00, 0x00,
0x63, 0x50, 0x4B, 0x43, 0x053, 0x2D, 0x31, 0x35 };
public static void install(byte[] bArray, short bOffset, byte bLength) throws SystemException {
new PKIApplet().register(bArray, (short) (bOffset + 1),
bArray[bOffset]);
}
public void deselect() {
pin.reset();
puc.reset();
}
private PKIApplet() {
pin = new OwnerPIN(PIN_TRIES, MAX_PIN_SIZE);
puc = new OwnerPIN(PUC_TRIES, PUC_SIZE);
rd = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
pkcs1Cipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
nopadCipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false);
md = MessageDigest.getInstance(MessageDigest.ALG_SHA, false);
tmp = JCSystem.makeTransientByteArray(TMP_SIZE, JCSystem.CLEAR_ON_DESELECT);
state = STATE_INITIAL;
authKeyId = new byte[KEY_ID_SIZE];
signKeyId = new byte[KEY_ID_SIZE];
decKeyId = new byte[KEY_ID_SIZE];
authKeyPrivate = (RSAPrivateCrtKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_CRT_PRIVATE, KeyBuilder.LENGTH_RSA_1024, false);
signKeyPrivate = (RSAPrivateCrtKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_CRT_PRIVATE, KeyBuilder.LENGTH_RSA_1024, false);
decKeyPrivate = (RSAPrivateCrtKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_CRT_PRIVATE, KeyBuilder.LENGTH_RSA_1024, false);
tempKeyPublic = (RSAPublicKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_1024, false);
currentPrivateKey = JCSystem.makeTransientObjectArray((short)1, JCSystem.CLEAR_ON_DESELECT);
expectedDecipherDataLength = JCSystem.makeTransientShortArray((short)1, JCSystem.CLEAR_ON_DESELECT);
fileSystem = new FileSystem((short)16);
}
public void process(APDU apdu) {
byte protocol = (byte)(APDU.getProtocol() & APDU.PROTOCOL_MEDIA_MASK);
//if(protocol == APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A
// || protocol == APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_B) {
// // We are in contactless mode, refuse operation:
// ISOException.throwIt(SW_FUNC_NOT_SUPPORTED);
//}
byte[] buf = apdu.getBuffer();
byte cla = buf[OFFSET_CLA];
byte ins = buf[OFFSET_INS];
// No secure messaging for the PKI applet
if((byte)(cla & CLA_SM) == CLA_SM) {
ISOException.throwIt(SW_SECURE_MESSAGING_NOT_SUPPORTED);
}
// Only PSO can be chained
if (!(cla == CLA_ISO7816
|| (cla == CLA_CHAIN && ins == INS_PSO))) {
ISOException.throwIt(SW_CLA_NOT_SUPPORTED);
}
switch (ins) {
case INS_SELECT:
processSelectFile(apdu);
break;
case INS_READBINARY:
processReadBinary(apdu);
break;
case INS_WRITEBINARY:
processWriteBinary(apdu);
break;
case INS_VERIFY:
processVerify(apdu);
break;
case INS_CHANGEREFERENCEDATA:
processChangeReferenceData(apdu);
break;
case INS_PUTDATA:
processPutData(apdu);
break;
case INS_GENERATE_KEY_PAIR:
processGenerateAssymetricKeyPair(apdu);
break;
case INS_CREATEFILE:
processCreateFile(apdu);
break;
case INS_GETCHALLENGE:
processGetChallenge(apdu);
break;
case INS_MSE:
processManageSecurityEnvironment(apdu);
break;
case INS_PSO:
processPerformSecurityOperation(apdu);
break;
case INS_INTERNALAUTHENTICATE:
processInternalAuthenticate(apdu);
break;
default:
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
}
/**
* Process the SELECT (file) instruction (0xA4)
* ISO7816-4 Section 7.1.1
*
*/
private void processSelectFile(APDU apdu) {
byte[] buf = apdu.getBuffer();
byte p1 = buf[OFFSET_P1];
//byte p2 = buf[OFFSET_P2];
short lc = unsigned(buf[OFFSET_LC]);
if(p1 == 0x04) {
// Select the AID of the applet
// do heavy verification, just for the fun of it ;)
if (lc != (short) 0x0C) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
if (Util.arrayCompare(buf, OFFSET_CDATA, myAID, (short) 0, lc) != 0) {
ISOException.throwIt(SW_WRONG_DATA);
}
return ;
}
short id = 0;
switch (p1) {
case (byte) 0x00:
// Direct selection of MF, DF, or EF:
if (lc != 0 && lc != 2) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if (lc > 0) {
apdu.setIncomingAndReceive();
id = Util.makeShort(buf[OFFSET_CDATA],
buf[(short) (OFFSET_CDATA + 1)]);
} else {
id = FileSystem.MASTER_FILE_ID;
}
if (!fileSystem.selectEntryAbsolute(id)) {
ISOException.throwIt(SW_FILE_NOT_FOUND);
}
break;
case (byte) 0x01:
case (byte) 0x02:
// Select the child under the current DF,
// p1 0x01 DF identifier in data field
// p1 0x02 EF identifier in data field
if (lc != 2) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
id = Util.makeShort(buf[OFFSET_CDATA],
buf[(short) (OFFSET_CDATA + 1)]);
if (!fileSystem.selectEntryUnderCurrent(id, p1 == (byte) 0x02)) {
ISOException.throwIt(SW_FILE_NOT_FOUND);
}
break;
case (byte) 0x03:
// Select the parent of the current DF
// no command data
if (lc != 0) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if (!fileSystem.selectEntryParent()) {
ISOException.throwIt(SW_FILE_NOT_FOUND);
}
break;
case (byte) 0x08:
case (byte) 0x09:
// Select by path
// p1 0x08 from MF
// p1 0x09 from current DF
// data field: the path without the head
if (lc == 0 || (short) (lc % 2) != 0) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
if (!fileSystem.selectEntryByPath(buf, OFFSET_CDATA, lc,
p1 == (byte) 0x08)) {
ISOException.throwIt(SW_FILE_NOT_FOUND);
}
break;
default:
ISOException.throwIt(SW_INCORRECT_P1P2);
}
}
/**
* Process the READ BINARY instruction (0xB0)
* ISO7816-4 Section 7.2.3
*
* We handle only the INS == 0xB0 case.
*
*/
private void processReadBinary(APDU apdu) {
if(state != STATE_PERSONALISED) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte[] buf = apdu.getBuffer();
byte p1 = buf[OFFSET_P1];
byte p2 = buf[OFFSET_P2];
short offset = 0;
short ef = -1;
if((byte)(p1 & MASK_SFI) == MASK_SFI) {
byte sfi = (byte)(p1 & ~MASK_SFI);
if(sfi >= 0x1F) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
ef = fileSystem.findCurrentSFI(sfi);
if(ef == -1) {
ISOException.throwIt(SW_FILE_NOT_FOUND);
}
ef = fileSystem.fileStructure[ef];
offset = unsigned(p2);
}else{
ef = fileSystem.getCurrentIndex();
if(fileSystem.getFile(ef) == null) {
ISOException.throwIt(SW_COMMAND_NOT_ALLOWED);
}
offset = Util.makeShort(p1, p2);
}
byte[] file = fileSystem.getFile(ef);
if(offset > file.length) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
if(fileSystem.getPerm(ef) == FileSystem.PERM_PIN && !pin.isValidated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
short le = apdu.setOutgoing();
if(le == 0 || le == 256) {
le = (short)(file.length - offset);
if(le > 256) le = 256;
}
boolean eof = false;
if((short)(file.length - offset) < le) {
le = (short)(file.length - offset);
eof = true;
}
apdu.setOutgoingLength(le);
apdu.sendBytesLong(file, offset, le);
if(eof) {
ISOException.throwIt(SW_END_OF_FILE);
}
}
/**
* Process the VERIFY instruction (0x20)
* ISO7816-4 Section 7.5.6
*
*/
private void processVerify(APDU apdu) {
if(state != STATE_PERSONALISED) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
byte[] buf = apdu.getBuffer();
if(buf[OFFSET_P1] != 0x00 || buf[OFFSET_P2] != 0x00) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
short lc = unsigned(buf[OFFSET_LC]);
if(lc < MIN_PIN_SIZE || lc > MAX_PIN_SIZE) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
// Pad the PIN to overwrite any possible garbage in the APDU (e.g. Le)
Util.arrayFillNonAtomic(buf, (short)(OFFSET_CDATA+lc),
(short)(MAX_PIN_SIZE - lc), (byte)0x00);
if(!pin.check(buf, OFFSET_CDATA, MAX_PIN_SIZE)) {
ISOException.throwIt((short)(SW_PIN_INCORRECT_TRIES_LEFT | pin.getTriesRemaining()));
}
}
/**
* Process the CHANGE REFERENCE DATA instruction (0x24)
* ISO7816-4 Section 7.5.7
*
* We have two options here: (a) in a procudction state we can
* set the PUC with this, (b) in the distribution state and operational
* state we change the PIN
*/
private void processChangeReferenceData(APDU apdu) {
byte[] buf = apdu.getBuffer();
short lc = unsigned(buf[OFFSET_LC]);
byte p1 = buf[OFFSET_P1];
byte p2 = buf[OFFSET_P2];
if(state > STATE_INITIAL) {
// We are changing the PIN, PUC has to be provided
// check that P1 is 0x00: verification data (puc) followed by new reference data (pin)
if(p1 != 0x00 || p2 != (byte)0x00) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
short pinSize = (short)(lc - PUC_SIZE);
if(pinSize < MIN_PIN_SIZE || pinSize > MAX_PIN_SIZE) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
short offset = (short)(OFFSET_CDATA+PUC_SIZE);
for(short i=0;i<pinSize;i++) {
byte b = buf[(short)(offset+i)];
if(b < (byte)0x30 || b > (byte)0x39) {
ISOException.throwIt(SW_WRONG_DATA);
}
}
// Pad the pin with 0x00 to overwrite any garbage, e.g. le
Util.arrayFillNonAtomic(buf, (short)(offset+pinSize), (short)(MAX_PIN_SIZE - pinSize), (byte)0x00);
if(!puc.check(buf, OFFSET_CDATA, PUC_SIZE)) {
ISOException.throwIt((short)(SW_PIN_INCORRECT_TRIES_LEFT | puc.getTriesRemaining()));
}
pin.update(buf, offset, MAX_PIN_SIZE);
pin.resetAndUnblock();
if(state == STATE_PREPERSONALISED) {
state = STATE_PERSONALISED;
}
}else{
// State is production, we set the puc
if(p1 != 0x01 || p2 != 0x00) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
if(lc != PUC_SIZE) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
puc.update(buf, OFFSET_CDATA, (byte)lc);
puc.resetAndUnblock();
}
}
/**
* Process the PUT DATA instruction (0xDA)
* P1 and P2 are custom
*
*/
private void processPutData(APDU apdu) {
byte p1 = apdu.getBuffer()[OFFSET_P1];
if(p1 >= (byte)0x61 && p1 <= (byte)0x66) {
processSetupKey(apdu);
}else if(p1 == (byte)0x67) {
processSetHistoricalBytes(apdu);
}else if(p1 == (byte)0x68) {
processSetState(apdu);
}else if(p1 == (byte)0x69) {
processCreateFileSystemStructure(apdu);
}else{
ISOException.throwIt(SW_INCORRECT_P1P2);
}
}
/**
* Process the GET CHALLENGE instruction (0x84)
* ISO 7816-4, Section 7.5.3
*
*/
private void processGetChallenge(APDU apdu) {
if(state != STATE_PERSONALISED) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
byte[] buf = apdu.getBuffer();
if(buf[OFFSET_P1] != 0x00 || buf[OFFSET_P2] != 0x00) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
short le = apdu.setOutgoing();
if(le == 0) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setOutgoingLength(le);
rd.generateData(buf, (short)0, le);
apdu.sendBytes((short)0, le);
}
/** Process the MANAGE SECURITY ENVIRONMENT instruction (0x22).
* ISO7816-4, Section 7.5.11
*
* This command can be also used to prepare key generation.
* In this case the algorithm indication is not required, in
* fact, should not be present. Note that the
* key identifiers should be already set up with put data before that.
*/
private void processManageSecurityEnvironment(APDU apdu) {
boolean forKeyGeneration = false;
if(state == STATE_INITIAL) {
forKeyGeneration = true;
}else if(state == STATE_PREPERSONALISED) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
pin.reset();
byte[] buf = apdu.getBuffer();
byte p1 = buf[OFFSET_P1];
byte p2 = buf[OFFSET_P2];
// P1 should be:
// (a) 0x40: computation, decipherment, internal authentication, ...
// (b) 0x01: set
if(p1 != (byte)0x41) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
byte[] expectedKeyId = null;
// P2 should be one of the following, see ISO7816-4 Table 79
if(p2 == (byte)0xa4) {
expectedKeyId = authKeyId;
}else if (p2 == (byte)0xb6) {
expectedKeyId = signKeyId;
}else if (p2 == (byte)0xB8) {
expectedKeyId = decKeyId;
}else{
ISOException.throwIt(SW_INCORRECT_P1P2);
}
short lc = unsigned(buf[OFFSET_LC]);
if(lc == 0) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
short offset = OFFSET_CDATA;
lc += OFFSET_CDATA;
// Tag for the private key:
short len = checkDataObject(buf, offset, lc, (byte)0x84);
offset += 2;
if(len != expectedKeyId[0]) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if(Util.arrayCompare(buf, offset, expectedKeyId, (short)1, len) != 0) {
ISOException.throwIt(SW_KEY_NOT_FOUND);
}
offset += len;
// Algorithm identfier tag
if(!forKeyGeneration) {
if(offset >= lc) {
ISOException.throwIt(SW_WRONG_DATA);
}
len = checkDataObject(buf, offset, lc, (byte)0x80);
offset += 2;
if(len != 1) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
byte sAlg = buf[offset++];
if(offset != lc) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if(sAlg < ALG_AUTH_DEC_RSA || sAlg > ALG_SIGN_RSA_PKCS1_SHA1MD5) {
ISOException.throwIt(SW_WRONG_DATA);
}
tmp[TMP_SIGNALG_OFFSET] = sAlg;
}else{
if(offset != lc) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
}
if(expectedKeyId == authKeyId) {
currentPrivateKey[0] = authKeyPrivate;
}else if(expectedKeyId == signKeyId) {
currentPrivateKey[0] = signKeyPrivate;
}else if(expectedKeyId == decKeyId) {
currentPrivateKey[0] = decKeyPrivate;
}
}
// Checks the DO in the buffer, report any inconsitencies
// Return the length of the data
private short checkDataObject(byte[] buffer, short offset, short lastOffset, byte expectedTag) {
if(offset >= lastOffset || lastOffset > buffer.length) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if(buffer[offset++] != expectedTag) {
ISOException.throwIt(SW_WRONG_DATA);
}
short len = unsigned(buffer[offset++]);
if(offset > (short)(lastOffset - len)) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
return len;
}
/**
* Generate an assymetric RSA key pair according to ISO7816-8,
* Section 5.1. We only support RSA 1024 bit at the moment, and
* return data in simple TLV data objects, tags 81 and 82.
*
* Successful MSE command has to be performed prior to this one.
*/
private void processGenerateAssymetricKeyPair(APDU apdu) {
// This is only valid in state initial (at the moment)
if(state != STATE_INITIAL) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
byte[] buf = apdu.getBuffer();
byte p1 = buf[OFFSET_P1];
byte p2 = buf[OFFSET_P2];
if(p1 != (byte)0x80 || p2 != (byte)0x00) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
if(currentPrivateKey[0] == null) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
KeyPair pair = new KeyPair(tempKeyPublic, (RSAPrivateCrtKey)currentPrivateKey[0]);
pair.genKeyPair();
// Sanity check, the KeyPair class should regenerate the keys "in place".
if(pair.getPrivate() != currentPrivateKey[0] || pair.getPublic() != tempKeyPublic) {
ISOException.throwIt(SW_DATA_INVALID);
}
apdu.setOutgoing();
short len = (short)0;
short offset = 0;
buf[offset++] = (byte)0x81;
len = tempKeyPublic.getModulus(buf, (short)(offset+2));
buf[offset++] = (byte)0x81;
buf[offset++] = (byte)len;
offset += len;
buf[offset++] = (byte)0x82;
len = tempKeyPublic.getExponent(buf, (short)(offset+1));
buf[offset++] = (byte)len;
offset += len;
apdu.setOutgoingLength(offset);
apdu.sendBytes((short)0, offset);
}
/**
* Process the PERFORM SECURITY OPERATION instruction (0x2A).
* ISO 7816-8 Section 5.2
*/
private void processPerformSecurityOperation(APDU apdu) {
if(state != STATE_PERSONALISED) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
if(!pin.isValidated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buf = apdu.getBuffer();
byte p1 = buf[OFFSET_P1];
byte p2 = buf[OFFSET_P2];
if(p1 == (byte)0x80 && (p2 == (byte)0x82 || p2 == (byte)0x84 || p2 == (byte)0x86)) {
processDecipher(apdu);
}else if(p1 == (byte)0x9E && p2 == (byte)0x9A) {
processComputeDigitalSignature(apdu);
}else{
ISOException.throwIt(SW_INCORRECT_P1P2);
}
}
/**
* Process the PSO DECIPHER instruction.
* ISO 7816-8 Section 5.10
*
*/
private void processDecipher(APDU apdu) {
byte[] buf = apdu.getBuffer();
byte cla = buf[OFFSET_CLA];
boolean chain = ((byte)(cla & CLA_CHAIN) == CLA_CHAIN);
short lc = unsigned(buf[OFFSET_LC]);
// We need at least 1 byte of data to feed into the cipher,
// so that a progression is made
if(lc == 0) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
short offset = OFFSET_CDATA;
// The first in chain, intialized and check:
if(tmp[TMP_DECSEQ_OFFSET] == (byte)0x00) {
RSAPrivateCrtKey key = (RSAPrivateCrtKey)currentPrivateKey[0];
if(key == null) {
ISOException.throwIt(SW_KEY_NOT_FOUND);
}
byte alg = tmp[TMP_SIGNALG_OFFSET];
if(key != decKeyPrivate || alg != ALG_AUTH_DEC_RSA) {
ISOException.throwIt(SW_WRONG_DATA);
}
pkcs1Cipher.init(key, Cipher.MODE_DECRYPT);
tmp[TMP_DECSEQ_OFFSET]++;
expectedDecipherDataLength[0] = (short)(key.getSize()/8);
}
short decipheredLen = 0;
try {
decipheredLen = pkcs1Cipher.update(buf, offset, lc, tmp, (short)(TMP_OFFSET + decipheredLen));
}catch(CryptoException ce) {
ISOException.throwIt(SW_WRONG_DATA);
}
expectedDecipherDataLength[0] -= lc;
offset += lc;
// Data still to come:
if(expectedDecipherDataLength[0] != 0 && !chain) {
ISOException.throwIt(SW_WRONG_DATA);
}
// No more data:
if(expectedDecipherDataLength[0] == 0 && chain) {
ISOException.throwIt(SW_LAST_COMMAND_EXPECTED);
}
if(chain) {
// It should also be the case the deciphereLen == 0, check?
return;
}
pin.reset();
tmp[TMP_DECSEQ_OFFSET] = 0x00;
try {
decipheredLen = pkcs1Cipher.doFinal(buf, offset, (short)0, tmp, (short)(TMP_OFFSET + decipheredLen));
}catch(CryptoException ce) {
ISOException.throwIt(SW_WRONG_DATA);
}
Util.arrayCopyNonAtomic(tmp, TMP_OFFSET, buf, (short)0, decipheredLen);
apdu.setOutgoingAndSend((short)0, decipheredLen);
}
/**
* Process the PSO COMPUTE DIGITAL SIGNATURE instruction (0x2A)
* ISO 7816-8 Section 5.4
*
*/
private void processComputeDigitalSignature(APDU apdu) {
pin.reset();
byte[] buf = apdu.getBuffer();
short lc = unsigned(buf[OFFSET_LC]);
if(lc == 0) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey)currentPrivateKey[0];
byte alg = tmp[TMP_SIGNALG_OFFSET];
if(privateKey != signKeyPrivate || (alg != ALG_SIGN_RSA_PKCS1_SHA1 && alg != ALG_SIGN_RSA_PKCS1_SHA256 && alg != ALG_SIGN_RSA_PSS && alg != ALG_SIGN_RSA_PKCS1_SHA1MD5)) {
ISOException.throwIt(SW_WRONG_DATA);
}
short offset = OFFSET_CDATA;
short expectedLength = 0;
if(alg == ALG_SIGN_RSA_PKCS1_SHA256) {
expectedLength = (short)(SHA256_LEN + 17);
}else if(alg == ALG_SIGN_RSA_PKCS1_SHA1) {
expectedLength = (short)(SHA1_LEN + 13);
}else if(alg == ALG_SIGN_RSA_PSS) {
expectedLength = SHA1_LEN;
}else if(alg == ALG_SIGN_RSA_PKCS1_SHA1MD5) {
expectedLength = SHA1MD5_LEN;
}
if(lc != expectedLength) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
short sigLen = 0;
if(alg == ALG_SIGN_RSA_PKCS1_SHA1 || alg == ALG_SIGN_RSA_PKCS1_SHA256 || alg == ALG_SIGN_RSA_PKCS1_SHA1MD5) {
pkcs1Cipher.init(privateKey, Cipher.MODE_ENCRYPT);
sigLen = pkcs1Cipher.doFinal(buf, offset, lc, tmp, TMP_OFFSET);
Util.arrayCopyNonAtomic(tmp, TMP_OFFSET, buf, (short)0, sigLen);
}else{
short emLen = (short)(privateKey.getSize() / 8);
pssPad(buf, offset, lc, tmp, TMP_OFFSET, emLen, signKeyFirstModulusByte);
nopadCipher.init(privateKey, Cipher.MODE_ENCRYPT);
sigLen = nopadCipher.doFinal(tmp, (short)0, emLen, buf, (short)0);
}
apdu.setOutgoingAndSend((short)0, sigLen);
}
/**
* Process the INTERNAL AUTHENTICATE instruction (0x88)
* ISO 7816-4 Section 7.5.2
*
*/
private void processInternalAuthenticate(APDU apdu) {
if(state != STATE_PERSONALISED) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
if(!pin.isValidated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buf = apdu.getBuffer();
short lc = unsigned(buf[OFFSET_LC]);
if(lc == 0) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey)currentPrivateKey[0];
byte alg = tmp[TMP_SIGNALG_OFFSET];
if(privateKey != authKeyPrivate || alg != ALG_AUTH_DEC_RSA) {
ISOException.throwIt(SW_WRONG_DATA);
}
short offset = OFFSET_CDATA;
short maxLength = (short)((short)(privateKey.getSize() / 8) - 11);
if(lc > maxLength) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
pkcs1Cipher.init(privateKey, Cipher.MODE_ENCRYPT);
short len = pkcs1Cipher.doFinal(buf, offset, lc, tmp, TMP_OFFSET);
Util.arrayCopyNonAtomic(tmp, TMP_OFFSET, buf, (short)0, len);
apdu.setOutgoingAndSend((short)0, len);
}
private void processSetHistoricalBytes(APDU apdu) {
if(state != STATE_INITIAL) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte[] buf = apdu.getBuffer();
byte lc = buf[OFFSET_LC];
if(lc <= 0) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
GPSystem.setATRHistBytes(buf, OFFSET_CDATA, lc);
}
private void processCreateFileSystemStructure(APDU apdu) {
if(state != STATE_INITIAL) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte[] buf = apdu.getBuffer();
short lc = unsigned(buf[OFFSET_LC]);
apdu.setIncomingAndReceive();
// Hack:
// Search for a non-existing file,
// if the structure is correct, then only the FileNotFoundException would be
// thrown.
try {
fileSystem.searchId(buf, OFFSET_CDATA, OFFSET_CDATA, (short)(OFFSET_CDATA + lc), (short)0x0000);
ISOException.throwIt(SW_WRONG_DATA);
}catch(FileNotFoundException e) {
}catch(ArrayIndexOutOfBoundsException aioobe) {
ISOException.throwIt(SW_WRONG_DATA);
}
fileSystem.fileStructure = new byte[lc];
Util.arrayCopy(buf, OFFSET_CDATA, fileSystem.fileStructure, (short)0, lc);
}
private void processCreateFile(APDU apdu) {
if(state != STATE_INITIAL) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
byte[] buf = apdu.getBuffer();
short lc = unsigned(buf[OFFSET_LC]);
apdu.setIncomingAndReceive();
if(lc != 5) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
short offset = OFFSET_CDATA;
short id = Util.getShort(buf, offset);
offset += 2;
short len = Util.getShort(buf, offset);
offset += 2;
byte perm = buf[offset];
if(!fileSystem.createFile(id, len, perm)) {
ISOException.throwIt(SW_WRONG_DATA);
}
}
private void processSetState(APDU apdu) throws ISOException {
if(state == STATE_PERSONALISED) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte p2 = apdu.getBuffer()[OFFSET_P2];
if(p2 != STATE_INITIAL && p2 != STATE_PREPERSONALISED) {
ISOException.throwIt(SW_WRONG_DATA);
}
state = p2;
}
/**
* Process the WRITE BINARY Instruction (0xD0).
* ISO7816-4 Section 7.2.4
*
*/
private void processWriteBinary(APDU apdu) throws ISOException {
if(state != STATE_INITIAL) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
byte[] buf = apdu.getBuffer();
byte p1 = buf[OFFSET_P1];
byte p2 = buf[OFFSET_P2];
short offset = 0;
short ef = -1;
if((byte)(p1 & MASK_SFI) == MASK_SFI) {
byte sfi = (byte)(p1 | ~MASK_SFI);
if(sfi >= 0x1F) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
ef = fileSystem.findCurrentSFI(sfi);
if(ef == -1) {
ISOException.throwIt(SW_FILE_NOT_FOUND);
}
ef = fileSystem.fileStructure[ef];
offset = unsigned(p2);
}else{
ef = fileSystem.getCurrentIndex();
if(fileSystem.getFile(ef) == null) {
ISOException.throwIt(SW_COMMAND_NOT_ALLOWED);
}
offset = Util.makeShort(p1, p2);
}
byte[] file = fileSystem.getFile(ef);
short lc = unsigned(buf[OFFSET_LC]);
if((short)(offset + lc) > file.length) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
apdu.setIncomingAndReceive();
Util.arrayCopyNonAtomic(buf, OFFSET_CDATA, file, offset, lc);
}
private void processSetupKey(APDU apdu) throws ISOException {
if(state != STATE_INITIAL) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte[] buf = apdu.getBuffer();
byte p1 = buf[OFFSET_P1];
byte p2 = buf[OFFSET_P2];
short lc = unsigned(buf[OFFSET_LC]);
apdu.setIncomingAndReceive();
if(p1 == (byte)0x61 || p1 == (byte)0x62 || p1 == (byte)0x63) {
if(lc > 16) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
byte[] keyId = null;
if(p1 == (byte)0x61) {
keyId = authKeyId;
}else if(p1 == (byte)0x62) {
keyId = signKeyId;
}else if(p1 == (byte)0x63) {
keyId = decKeyId;
}
Util.arrayCopy(buf, OFFSET_CDATA, keyId, (short)1, lc);
keyId[0] = (byte)lc;
return;
}
RSAPrivateCrtKey privKey = null;
if(p1 == (byte)0x64) {
privKey = authKeyPrivate;
}else if(p1 == (byte)0x65){
privKey = signKeyPrivate;
}else if(p1 == (byte)0x66) {
privKey = decKeyPrivate;
}else{
ISOException.throwIt(SW_INCORRECT_P1P2);
}
try {
switch(p2) {
case (byte)0x81: // Modulus, ignore, but record the first byte if key is sign key
if(privKey == signKeyPrivate) {
signKeyFirstModulusByte = buf[OFFSET_CDATA];
}
break;
case (byte)0x82: // Exponent, ignore
break;
case (byte)0x83:
privKey.setP(buf, OFFSET_CDATA, lc);
break;
case (byte)0x84:
privKey.setQ(buf, OFFSET_CDATA, lc);
break;
case (byte)0x85:
privKey.setDP1(buf, OFFSET_CDATA, lc);
break;
case (byte)0x86:
privKey.setDQ1(buf, OFFSET_CDATA, lc);
break;
case (byte)0x87:
privKey.setPQ(buf, OFFSET_CDATA, lc);
break;
default:
}
}catch(Exception e){
ISOException.throwIt(SW_WRONG_DATA);
}
}
private short unsigned(byte b) {
return (short) (b & 0x00FF);
}
/** Pads the input according to the RSASSA-PSS algorithm, the result is placed in
* output. The input should be 20-byte SHA1 hash of the message to be signed.
* This method *does not* do signing (encrypting) itself. Due to the randomness
* of this algorithm the subsequent signing may fail (when the result of this method
* is larger than the key modulus) in which case the padding should be attempted again.
*/
private void pssPad(byte[] input, short inOffset, short hashLen,
byte[] output, short outputOffset, short emLen, byte firstKeyByte) throws CryptoException {
do {
short hLen = hashLen;
short outOffset = outputOffset;
if(hLen != SHA1_LEN || (short)(inOffset + hLen) > input.length || (short)(outOffset + emLen) > output.length) {
CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
}
short sLen = SHA1_LEN;
short psLen = (short)(emLen - sLen - hLen - 2);
Util.arrayFillNonAtomic(output, outOffset, emLen, (byte)0x00);
md.update(output, outOffset, (short)8);
md.update(input, inOffset, hLen);
rd.generateData(output, (short)(outOffset + psLen + 1), sLen);
md.doFinal(output, (short)(outOffset+ psLen + 1), sLen, tmp, TMP_HASH_OFFSET);
output[(short)(outOffset + psLen)] = (byte)0x01;
Util.arrayFillNonAtomic(output, outOffset, psLen, (byte)0x00);
short hOffset = (short)(outOffset + emLen - hLen - 1);
Util.arrayCopyNonAtomic(tmp, TMP_HASH_OFFSET, output, hOffset, hLen);
output[(short)(outOffset + emLen - 1)] = (byte)0xbc;
tmp[(short)(TMP_C_OFFSET+C_LEN-1)] = 0;
while(outOffset < hOffset) {
md.update(output, hOffset, hLen);
md.doFinal(tmp, TMP_C_OFFSET, C_LEN, tmp, TMP_HASH_OFFSET);
if((short)(outOffset + hLen) > hOffset) {
hLen = (short)(hOffset - outOffset);
}
for(short i = 0; i<hLen; i++) {
output[outOffset++] ^= tmp[(short)(TMP_HASH_OFFSET + i)];
}
tmp[(short)(TMP_C_OFFSET+C_LEN-1)]++;
}
}while(firstKeyByte <= tmp[TMP_OFFSET]);
}
}