/*
* passportapplet - A reference implementation of the MRTD standards.
*
* Copyright (C) 2006 SoS group, Radboud University
*
* 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
*
* $Id: PassportApplet.java 945 2009-05-12 08:31:57Z woj76 $
*/
package sos.passportapplet;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.CardRuntimeException;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;
// API for setATRHistBytes - requires Global Platform API gp211.jar
// Comment out the following line if API not available.
// import org.globalplatform.GPSystem;
/**
* PassportApplet
*
* @author ceesb (ceeesb@gmail.com)
* @author woj (woj@cs.ru.nl)
* @author martijno (martijn.oostdijk@gmail.com)
*
* @version $Revision: 945 $
*/
public class PassportApplet extends Applet implements ISO7816 {
static byte volatileState[];
static byte persistentState;
/* values for volatile state */
static final byte CHALLENGED = 1;
static final byte MUTUAL_AUTHENTICATED = 2; // ie BAC
static final byte FILE_SELECTED = 4;
static final byte CHIP_AUTHENTICATED = 0x10;
static final byte TERMINAL_AUTHENTICATED = 0x20;
/* values for persistent state */
static final byte HAS_MUTUALAUTHENTICATION_KEYS = 1;
static final byte HAS_EXPONENT = 2;
static final byte LOCKED = 4;
static final byte HAS_MODULUS = 8;
static final byte HAS_EC_KEY = 0x10;
static final byte HAS_CVCERTIFICATE = 0x20;
static final byte CHAIN_CLA = 0x10;
/* for authentication */
static final byte INS_EXTERNAL_AUTHENTICATE = (byte) 0x82;
static final byte INS_GET_CHALLENGE = (byte) 0x84;
static final byte CLA_PROTECTED_APDU = 0x0c;
static final byte INS_INTERNAL_AUTHENTICATE = (byte) 0x88;
/* for EAC */
static final byte INS_PSO = (byte) 0x2A;
static final byte INS_MSE = (byte) 0x22;
static final byte P2_VERIFYCERT = (byte) 0xBE;
static final byte P1_SETFORCOMPUTATION = (byte) 0x41;
static final byte P1_SETFORVERIFICATION = (byte) 0x81;
static final byte P2_KAT = (byte) 0xA6;
static final byte P2_DST = (byte) 0xB6;
static final byte P2_AT = (byte) 0xA4;
/* for reading */
static final byte INS_SELECT_FILE = (byte) 0xA4;
static final byte INS_READ_BINARY = (byte) 0xB0;
/* for writing */
static final byte INS_UPDATE_BINARY = (byte) 0xd6;
static final byte INS_CREATE_FILE = (byte) 0xe0;
static final byte INS_PUT_DATA = (byte) 0xda;
static final short KEY_LENGTH = 16;
static final short KEYMATERIAL_LENGTH = 16;
static final short RND_LENGTH = 8;
static final short MAC_LENGTH = 8;
private static final byte PRIVMODULUS_TAG = 0x60;
private static final byte PRIVEXPONENT_TAG = 0x61;
private static final byte MRZ_TAG = 0x62;
private static final byte ECPRIVATEKEY_TAG = 0x63;
private static final byte CVCERTIFICATE_TAG = 0x64;
/* status words */
private static final short SW_OK = (short) 0x9000;
private static final short SW_REFERENCE_DATA_NOT_FOUND = (short) 0x6A88;
static final short SW_INTERNAL_ERROR = (short) 0x6d66;
private byte[] rnd;
private byte[] ssc;
private byte[] documentNumber;
private FileSystem fileSystem;
private RandomData randomData;
private short selectedFile;
private PassportCrypto crypto;
private PassportInit init;
static CVCertificate certificate;
KeyStore keyStore;
private byte[] lastINS;
private short[] chainingOffset;
private byte[] chainingTmp;
// This is as long we suspect a card verifiable certifcate could be
private static final short CHAINING_BUFFER_LENGTH = 400;
// public ATRGlobal atrGlobal;
/**
* Creates a new passport applet.
*/
public PassportApplet(byte mode) {
fileSystem = new FileSystem();
randomData = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM);
certificate = new CVCertificate();
keyStore = new KeyStore(mode);
switch (mode) {
case PassportCrypto.CREF_MODE:
crypto = new CREFPassportCrypto(keyStore);
break;
case PassportCrypto.PERFECTWORLD_MODE:
crypto = new PassportCrypto(keyStore);
break;
case PassportCrypto.JCOP41_MODE:
crypto = new JCOP41PassportCrypto(keyStore);
break;
}
init = new PassportInit(crypto);
rnd = JCSystem.makeTransientByteArray(RND_LENGTH,
JCSystem.CLEAR_ON_RESET);
ssc = JCSystem
.makeTransientByteArray((byte) 8, JCSystem.CLEAR_ON_RESET);
volatileState = JCSystem.makeTransientByteArray((byte) 1,
JCSystem.CLEAR_ON_RESET);
lastINS = JCSystem.makeTransientByteArray((short) 1,
JCSystem.CLEAR_ON_DESELECT);
chainingOffset = JCSystem.makeTransientShortArray((short) 1,
JCSystem.CLEAR_ON_DESELECT);
chainingTmp = JCSystem.makeTransientByteArray(CHAINING_BUFFER_LENGTH,
JCSystem.CLEAR_ON_DESELECT);
}
/**
* Installs an instance of the applet. The default crypto mode is now
* PERFECTWORLD_MODE as the new JCOP41 cards support all required crypto.
*
* @param buffer
* @param offset
* @param length
* @see javacard.framework.Applet#install(byte[], byte, byte)
*/
public static void install(byte[] buffer, short offset, byte length) {
(new PassportApplet(PassportCrypto.JCOP41_MODE)).register();
}
/**
* Processes incoming APDUs.
*
* @param apdu
* @see javacard.framework.Applet#process(javacard.framework.APDU)
*/
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
byte cla = buffer[OFFSET_CLA];
byte ins = buffer[OFFSET_INS];
short sw1sw2 = SW_OK;
boolean protectedApdu = (byte)(cla & CLA_PROTECTED_APDU) == CLA_PROTECTED_APDU;
short responseLength = 0;
short le = 0;
if (lastINS[0] != ins) {
chainingOffset[0] = 0;
}
lastINS[0] = ins;
/* Ignore APDU that selects this applet... */
if (selectingApplet()) {
// Set ATR Historical Bytes (ATS).
// Requires gp211 API. Comment out the following line if API not
// available.
// org.globalplatform.GPSystem.setATRHistBytes(ATRGlobal.ATR_HIST,
// (short) 0x00, ATRGlobal.ATR_HIST_LEN);
return;
}
if (protectedApdu & hasMutuallyAuthenticated()) {
try {
le = crypto.unwrapCommandAPDU(ssc, apdu);
} catch (CardRuntimeException e) {
sw1sw2 = e.getReason();
}
} else if (protectedApdu) {
ISOException.throwIt(ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED);
}
if (sw1sw2 == SW_OK) {
try {
responseLength = processAPDU(apdu, cla, ins, protectedApdu, le);
} catch (CardRuntimeException e) {
sw1sw2 = e.getReason();
}
}
if (protectedApdu && hasMutuallyAuthenticated()) {
responseLength = crypto.wrapResponseAPDU(ssc, apdu, crypto
.getApduBufferOffset(responseLength), responseLength,
sw1sw2);
}
if (responseLength > 0) {
if (apdu.getCurrentState() != APDU.STATE_OUTGOING)
apdu.setOutgoing();
if (apdu.getCurrentState() != APDU.STATE_OUTGOING_LENGTH_KNOWN)
apdu.setOutgoingLength(responseLength);
apdu.sendBytes((short) 0, responseLength);
}
if (sw1sw2 != SW_OK) {
ISOException.throwIt(sw1sw2);
}
}
/**
* Processes incoming APDUs, excluding Secure Messaging.
*
* This method assumes SM protection has been removed from the
* incoming APDU, and does not add SM protection to the response.
* Handling of Secure Messaging is done in process(APDU apdu)
*
* @param protectedApdu true if Secure Messaging is used
* @return length of the response APDU
*/
public short processAPDU(APDU apdu, byte cla, byte ins,
boolean protectedApdu, short le) {
short responseLength = 0;
switch (ins) {
case INS_GET_CHALLENGE:
responseLength = processGetChallenge(apdu, protectedApdu, le);
break;
case INS_EXTERNAL_AUTHENTICATE:
responseLength = processMutualAuthenticate(apdu, protectedApdu);
break;
case INS_PSO:
if (!protectedApdu) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
responseLength = processPSO(apdu);
break;
case INS_MSE:
if (!protectedApdu) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
responseLength = processMSE(apdu);
break;
case INS_INTERNAL_AUTHENTICATE:
responseLength = processInternalAuthenticate(apdu, protectedApdu);
break;
case INS_SELECT_FILE:
processSelectFile(apdu);
break;
case INS_READ_BINARY:
responseLength = processReadBinary(apdu, le, protectedApdu);
break;
case INS_UPDATE_BINARY:
processUpdateBinary(apdu);
break;
case INS_CREATE_FILE:
processCreateFile(apdu);
break;
case INS_PUT_DATA:
processPutData(apdu);
break;
default:
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
break;
}
return responseLength;
}
private short processPSO(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if (!hasChipAuthenticated() || hasTerminalAuthenticated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
if (buffer[OFFSET_P1] != (byte) 0x00
&& buffer[OFFSET_P2] != P2_VERIFYCERT) {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
if (certificate.currentCertSubjectId[0] == 0) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
short lc = (short) (buffer[OFFSET_LC] & 0xFF);
if (chainingOffset[0] > (short) (CHAINING_BUFFER_LENGTH - lc)) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
chainingOffset[0] = Util.arrayCopyNonAtomic(buffer, OFFSET_CDATA,
chainingTmp, chainingOffset[0], lc);
if (((byte) (buffer[OFFSET_CLA] & CHAIN_CLA) == CHAIN_CLA)) {
return (short) 0;
}
short chainingTmpLength = chainingOffset[0];
chainingOffset[0] = (short) 0;
certificate.parseCertificate(chainingTmp, (short) 0, chainingTmpLength,
false);
if (!certificate.verify()) {
ISOException.throwIt((short) 0x6300);
}
return (short) 0;
}
private short processMSE(APDU apdu) {
byte[] buffer = apdu.getBuffer();
byte p1 = (byte) (buffer[OFFSET_P1] & 0xff);
byte p2 = (byte) (buffer[OFFSET_P2] & 0xff);
short lc = (short) (buffer[OFFSET_LC] & 0xff);
short buffer_p = OFFSET_CDATA;
if (!hasEACKey() || !hasEACCertificate()) {
ISOException.throwIt(SW_INS_NOT_SUPPORTED);
}
if (!hasMutuallyAuthenticated() || hasTerminalAuthenticated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
if (p1 == P1_SETFORCOMPUTATION && p2 == P2_KAT) {
short lastOffset = (short) (lc + OFFSET_CDATA);
if (buffer_p > (short) (lastOffset - 2)) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if (buffer[buffer_p++] != (byte) 0x91) {
ISOException.throwIt(SW_WRONG_DATA);
}
short pubKeyLen = (short) (buffer[buffer_p++] & 0xFF);
short pubKeyOffset = buffer_p;
if (pubKeyOffset > (short) (lastOffset - pubKeyLen)) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
buffer_p += pubKeyLen;
short keyIdOffset = 0;
short keyIdLength = 0;
if (buffer_p != lastOffset) {
if (buffer_p > (short) (lastOffset - 2)) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if (buffer[buffer_p++] != (byte) 0x84) {
ISOException.throwIt(SW_WRONG_DATA);
}
keyIdLength = (short) (buffer[buffer_p++] & 0xFF);
keyIdOffset = buffer_p;
if (keyIdOffset != (short) (lastOffset - keyIdLength)) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
// ignore the key id, we don't use it for now
}
if (!crypto.authenticateChip(buffer, pubKeyOffset, pubKeyLen)) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
volatileState[0] |= CHIP_AUTHENTICATED;
return 0;
} else if (p1 == P1_SETFORVERIFICATION && (p2 == P2_DST || p2 == P2_AT)) {
if (!hasChipAuthenticated() || hasTerminalAuthenticated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
short lastOffset = (short) (lc + OFFSET_CDATA);
if (buffer_p > (short) (lastOffset - 2)) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if (buffer[buffer_p++] != (byte) 0x83) {
ISOException.throwIt(SW_WRONG_DATA);
}
short subIdLen = (short) (buffer[buffer_p++] & 0xFF);
if (buffer_p != (short) (lastOffset - subIdLen)) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
if (!certificate.selectSubjectId(buffer, buffer_p, subIdLen)) {
ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND);
}
} else {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
return 0;
}
private void processPutData(APDU apdu) {
if (isLocked()) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short buffer_p = (short) (OFFSET_CDATA & 0xff);
short lc = (short) (buffer[OFFSET_LC] & 0xff);
short p1 = (short) (buffer[OFFSET_P1] & 0xff);
short p2 = (short) (buffer[OFFSET_P2] & 0xff);
// sanity check
if (buffer.length < (short) (buffer_p + lc)) {
ISOException.throwIt(SW_INTERNAL_ERROR);
}
if (p1 == 0xde && p2 == 0xad) {
persistentState |= LOCKED;
} else if (p1 == 0 && p2 == PRIVMODULUS_TAG) {
buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag ==
// PRIVMODULUS_TAG
buffer_p = BERTLVScanner.readLength(buffer, buffer_p); // length ==
// 00
buffer_p = BERTLVScanner.skipValue();
buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == 04
short modOffset = BERTLVScanner.readLength(buffer, buffer_p);
short modLength = BERTLVScanner.valueLength;
if (buffer[modOffset] == 0) {
modLength--;
modOffset++;
}
keyStore.rsaPrivateKey.setModulus(buffer, modOffset, modLength);
persistentState |= HAS_MODULUS;
} else if (p1 == 0 && p2 == PRIVEXPONENT_TAG) {
buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag ==
// PRIVEXP_TAG
buffer_p = BERTLVScanner.readLength(buffer, buffer_p); // length ==
// 00
buffer_p = BERTLVScanner.skipValue();
buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == 04
short expOffset = BERTLVScanner.readLength(buffer, buffer_p);
short expLength = BERTLVScanner.valueLength;
// leading zero
if (buffer[expOffset] == 0) {
expLength--;
expOffset++;
}
keyStore.rsaPrivateKey.setExponent(buffer, expOffset, expLength);
persistentState |= HAS_EXPONENT;
} else if (p1 == 0 && p2 == MRZ_TAG) {
// data is BERTLV object with three objects; docNr, dataOfBirth,
// dateOfExpiry
buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
buffer_p = BERTLVScanner.readLength(buffer, buffer_p);
buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
short docNrOffset = BERTLVScanner.readLength(buffer, buffer_p);
short docNrLength = BERTLVScanner.valueLength;
buffer_p = BERTLVScanner.skipValue();
buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
short dobOffset = BERTLVScanner.readLength(buffer, buffer_p);
short dobLength = BERTLVScanner.valueLength;
buffer_p = BERTLVScanner.skipValue();
buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
short doeOffset = BERTLVScanner.readLength(buffer, buffer_p);
short doeLength = BERTLVScanner.valueLength;
buffer_p = BERTLVScanner.skipValue();
documentNumber = new byte[(short)(docNrLength+1)];
Util.arrayCopyNonAtomic(buffer, docNrOffset, documentNumber,
(short) 0, docNrLength);
documentNumber[docNrLength] = PassportInit.checkDigit(documentNumber,(short)0, docNrLength);
short keySeed_offset = init.computeKeySeed(buffer, docNrOffset,
docNrLength, dobOffset, dobLength, doeOffset, doeLength);
short macKey_p = (short) (keySeed_offset + KEYMATERIAL_LENGTH);
short encKey_p = (short) (keySeed_offset + KEYMATERIAL_LENGTH + KEY_LENGTH);
crypto.deriveKey(buffer, keySeed_offset, KEYMATERIAL_LENGTH, PassportCrypto.MAC_MODE,
macKey_p);
crypto.deriveKey(buffer, keySeed_offset, KEYMATERIAL_LENGTH, PassportCrypto.ENC_MODE,
encKey_p);
keyStore.setMutualAuthenticationKeys(buffer, macKey_p, buffer,
encKey_p);
persistentState |= HAS_MUTUALAUTHENTICATION_KEYS;
} else if (p1 == 0 && p2 == ECPRIVATEKEY_TAG) {
short finish = (short) (buffer_p + lc);
while (buffer_p < finish) {
buffer_p = BERTLVScanner.readTag(buffer, buffer_p);
buffer_p = BERTLVScanner.readLength(buffer, buffer_p);
short len = BERTLVScanner.valueLength;
switch (BERTLVScanner.tag) {
case (short) 0x81:
if (len == (short) 6) {
short e1 = Util.getShort(buffer, buffer_p);
short e2 = Util
.getShort(buffer, (short) (buffer_p + 2));
short e3 = Util
.getShort(buffer, (short) (buffer_p + 4));
keyStore.ecPrivateKey.setFieldF2M(e1, e2, e3);
keyStore.ecPublicKey.setFieldF2M(e1, e2, e3);
} else {
keyStore.ecPrivateKey.setFieldF2M(Util.getShort(buffer,
buffer_p));
keyStore.ecPublicKey.setFieldF2M(Util.getShort(buffer,
buffer_p));
}
break;
case (short) 0x82:
keyStore.ecPrivateKey.setA(buffer, buffer_p, len);
keyStore.ecPublicKey.setA(buffer, buffer_p, len);
break;
case (short) 0x83:
keyStore.ecPrivateKey.setB(buffer, buffer_p, len);
keyStore.ecPublicKey.setB(buffer, buffer_p, len);
break;
case (short) 0x84:
keyStore.ecPrivateKey.setG(buffer, buffer_p, len);
keyStore.ecPublicKey.setG(buffer, buffer_p, len);
break;
case (short) 0x85:
keyStore.ecPrivateKey.setR(buffer, buffer_p, len);
keyStore.ecPublicKey.setR(buffer, buffer_p, len);
break;
case (short) 0x86:
if (len == (short) 20) {
buffer_p--;
len++;
buffer[buffer_p] = 0x00;
}
keyStore.ecPrivateKey.setS(buffer, buffer_p, len);
break;
case (short) 0x87:
// This is the k, ignore it
// short k = Util.getShort(buffer, buffer_p);
break;
default:
ISOException.throwIt(SW_WRONG_DATA);
break;
}
buffer_p = BERTLVScanner.skipValue();
}
if (keyStore.ecPrivateKey.isInitialized()) {
persistentState |= HAS_EC_KEY;
} else {
ISOException.throwIt(SW_WRONG_DATA);
}
} else if (p2 == CVCERTIFICATE_TAG) {
if ((byte) (persistentState & HAS_CVCERTIFICATE) == HAS_CVCERTIFICATE) {
// We already have the certificate initialized
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
certificate.parseCertificate(buffer, buffer_p, lc, true);
certificate.setRootCertificate(buffer, p1);
persistentState |= HAS_CVCERTIFICATE;
} else {
ISOException.throwIt(SW_INCORRECT_P1P2);
}
}
/**
* Processes INTERNAL_AUTHENTICATE apdus, ie Active Authentication (AA).
* Receives a random and signs it.
*
* @param apdu
* @param protectedApdu true if Secure Messaging was used
* @return
*/
private short processInternalAuthenticate(APDU apdu, boolean protectedApdu) {
if (!hasInternalAuthenticationKeys() || !hasMutuallyAuthenticated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
short buffer_p = (short) (OFFSET_CDATA & 0xff);
short hdr_offset = 0;
if (protectedApdu) {
hdr_offset = crypto.getApduBufferOffset((short) 128);
}
short hdr_len = 1;
short m1_len = 106; // whatever
short m1_offset = (short) (hdr_offset + hdr_len);
short m2_len = 8;
short m2_offset = (short) (m1_offset + m1_len);
// we will write the hash over m2
short m1m2hash_offset = (short) (m1_offset + m1_len);
short m1m2hash_len = 20;
short trailer_offset = (short) (m1m2hash_offset + m1m2hash_len);
short trailer_len = 1;
byte[] buffer = apdu.getBuffer();
short bytesLeft = (short) (buffer[OFFSET_LC] & 0x00FF);
if (bytesLeft != m2_len)
ISOException.throwIt(SW_WRONG_LENGTH);
// put m2 in place
Util.arrayCopyNonAtomic(buffer, buffer_p, buffer, m2_offset, m2_len);
// write some random data of m1_len
// randomData.generateData(buffer, m1_offset, m1_length);
for (short i = m1_offset; i < (short) (m1_offset + m1_len); i++) {
buffer[i] = 0;
}
// calculate SHA1 hash over m1 and m2
crypto.shaDigest.doFinal(buffer, m1_offset, (short) (m1_len + m2_len), buffer,
m1m2hash_offset);
crypto.shaDigest.reset();
// write trailer
buffer[trailer_offset] = (byte) 0xbc;
// write header
buffer[hdr_offset] = (byte) 0x6a;
// encrypt the whole buffer with our AA private key
short plaintext_len = (short) (hdr_len + m1_len + m1m2hash_len + trailer_len);
// sanity check
if (plaintext_len != 128) {
ISOException.throwIt(SW_INTERNAL_ERROR);
}
crypto.rsaCiph.init(keyStore.rsaPrivateKey, Cipher.MODE_ENCRYPT);
short ciphertext_len = crypto.rsaCiph.doFinal(buffer, hdr_offset,
plaintext_len, buffer, hdr_offset);
// sanity check
if (ciphertext_len != 128) {
ISOException.throwIt(SW_INTERNAL_ERROR);
}
return ciphertext_len;
}
/**
* Processes incoming GET_CHALLENGE APDUs, as part of BAC or EAC.
*
* Generates random 8 bytes, sends back result and stores result in rnd.
* A GET_CHALLENGE APDU can be sent as part of BAC, or as part of
* EAC (more specifically, Terminal Authentication (TA).
*
* @param apdu
* is used for sending (8 bytes) only
* @param protectedApdu true if Secure Messaging was used
*/
private short processGetChallenge(APDU apdu, boolean protectedApdu, short le) {
if (protectedApdu) {
// we're doing TA
if (!hasChipAuthenticated()
|| certificate.cert1HolderReference[0] == (byte)0
|| hasTerminalAuthenticated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
} else {
// we're doing BAC
if (!hasMutualAuthenticationKeys() || hasMutuallyAuthenticated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
}
byte[] buffer = apdu.getBuffer();
if (!protectedApdu) {
le = apdu.setOutgoing();
}
// For the BAP challenge the length should be 8, for the EAP challenge
// we guess other lenghts are fine too?
if (le != 8) {
ISOException.throwIt(SW_WRONG_LENGTH);
}
randomData.generateData(rnd, (short) 0, le);
short bufferOffset = protectedApdu ? crypto.getApduBufferOffset(le)
: (short) 0;
Util.arrayCopyNonAtomic(rnd, (short) 0, buffer, bufferOffset, le);
volatileState[0] |= CHALLENGED;
return le;
}
/**
* Processes incoming EXTERNAL_AUTHENTICATE APDUs, as part of BAC or EAC.
*
* An EXTERNAL_AUTHENTICATE can be the last step of BAC, or the last
* step of Terminal Authentication (TA) when doing EAC.
*
* @param apdu
* the APDU
* @param protectedApdu true if Secure Messaging was used
* @return length of response APDU
*/
private short processMutualAuthenticate(APDU apdu, boolean protectedApdu) {
if (protectedApdu) {
// we're doing EAC
if (!hasChipAuthenticated() || !isChallenged()
|| certificate.currentCertSubjectId[0] == (byte)0
|| hasTerminalAuthenticated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short buffer_p = OFFSET_CDATA;
short lc = (short) (buffer[OFFSET_LC] & 0xFF);
setNoChallenged();
if (!crypto.eacVerifySignature(certificate.currentCertPublicKey, rnd,
documentNumber, buffer, buffer_p, lc)) {
certificate.clear();
ISOException.throwIt((short) 0x6300);
}
certificate.clear();
volatileState[0] |= TERMINAL_AUTHENTICATED;
return 0;
} else {
// we're doing BAC
if (!isChallenged() || hasMutuallyAuthenticated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short bytesLeft = (short) (buffer[OFFSET_LC] & 0x00FF);
short e_ifd_length = RND_LENGTH + RND_LENGTH + KEYMATERIAL_LENGTH;
// incoming message is e_ifd || m_ifd
// where e_ifd == E_KENC(rnd_ifd || rnd_icc || k_ifd)
if (bytesLeft != (short) (e_ifd_length + MAC_LENGTH))
ISOException.throwIt(SW_WRONG_LENGTH);
short e_ifd_p = OFFSET_CDATA;
short m_ifd_p = (short) (e_ifd_p + e_ifd_length);
if (apdu.getCurrentState() == APDU.STATE_INITIAL) {
apdu.setIncomingAndReceive();
}
if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) {
// need all data in one APDU.
ISOException.throwIt(SW_INTERNAL_ERROR);
}
// buffer[OFFSET_CDATA ... +40] consists of e_ifd || m_ifd
// verify checksum m_ifd of cryptogram e_ifd
crypto.initMac(Signature.MODE_VERIFY);
if (!crypto.verifyMacFinal(buffer, e_ifd_p, e_ifd_length, buffer,
m_ifd_p))
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
// decrypt e_ifd into buffer[0] where buffer = rnd.ifd || rnd.icc ||
// k.ifd
crypto.decryptInit();
short plaintext_len = crypto.decryptFinal(buffer, e_ifd_p,
e_ifd_length, buffer, (short) 0);
if (plaintext_len != e_ifd_length) // sanity check
ISOException.throwIt(SW_INTERNAL_ERROR);
short rnd_ifd_p = 0;
short rnd_icc_p = RND_LENGTH;
short k_ifd_p = (short) (rnd_icc_p + RND_LENGTH);
/*
* we use apdu buffer for writing intermediate data in buffer with
* following pointers
*/
short k_icc_p = (short) (k_ifd_p + KEYMATERIAL_LENGTH);
short keySeed_p = (short) (k_icc_p + KEYMATERIAL_LENGTH);
short keys_p = (short) (keySeed_p + KEYMATERIAL_LENGTH);
// verify that rnd.icc equals value generated in getChallenge
if (Util.arrayCompare(buffer, rnd_icc_p, rnd, (short) 0, RND_LENGTH) != 0)
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
// generate keying material k.icc
randomData.generateData(buffer, k_icc_p, KEYMATERIAL_LENGTH);
// calculate keySeed for session keys by xorring k_ifd and k_icc
PassportUtil.xor(buffer, k_ifd_p, buffer, k_icc_p, buffer, keySeed_p,
KEYMATERIAL_LENGTH);
// calculate session keys
crypto.deriveKey(buffer, keySeed_p, KEYMATERIAL_LENGTH, PassportCrypto.MAC_MODE, keys_p);
short macKey_p = keys_p;
keys_p += KEY_LENGTH;
crypto.deriveKey(buffer, keySeed_p, KEYMATERIAL_LENGTH, PassportCrypto.ENC_MODE, keys_p);
short encKey_p = keys_p;
keys_p += KEY_LENGTH;
keyStore.setSecureMessagingKeys(buffer, macKey_p, buffer, encKey_p);
// compute ssc
PassportCrypto.computeSSC(buffer, rnd_icc_p, buffer, rnd_ifd_p, ssc);
// create response in buffer where response = rnd.icc || rnd.ifd ||
// k.icc
PassportUtil.swap(buffer, rnd_icc_p, rnd_ifd_p, RND_LENGTH);
Util.arrayCopyNonAtomic(buffer, k_icc_p, buffer, (short) (2 * RND_LENGTH),
KEYMATERIAL_LENGTH);
// make buffer encrypted using k_enc
crypto.encryptInit();
short ciphertext_len = crypto.encryptFinal(buffer, (short) 0,
(short) (2 * RND_LENGTH + KEYMATERIAL_LENGTH), buffer,
(short) 0);
// create m_icc which is a checksum of response
crypto.initMac(Signature.MODE_SIGN);
crypto.createMacFinal(buffer, (short) 0, ciphertext_len, buffer,
ciphertext_len);
setNoChallenged();
volatileState[0] |= MUTUAL_AUTHENTICATED;
return (short) (ciphertext_len + MAC_LENGTH);
}
}
/**
* Processes incoming SELECT_FILE APDUs.
*
* @param apdu
* where the first 2 data bytes encode the file to select.
*/
private void processSelectFile(APDU apdu) {
if (isLocked() & !hasMutuallyAuthenticated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short lc = (short) (buffer[OFFSET_LC] & 0x00FF);
if (lc != 2)
ISOException.throwIt(SW_WRONG_LENGTH);
if (apdu.getCurrentState() == APDU.STATE_INITIAL) {
apdu.setIncomingAndReceive();
}
if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) {
// need all data in one APDU.
ISOException.throwIt(SW_INTERNAL_ERROR);
}
short fid = Util.getShort(buffer, OFFSET_CDATA);
if (fileSystem.getFile(fid) != null) {
selectedFile = fid;
volatileState[0] |= FILE_SELECTED;
return;
}
setNoFileSelected();
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
/**
* Processes incoming READ_BINARY APDUs. Returns data of the currently
* selected file.
*
* @param apdu
* where the offset is carried in header bytes p1 and p2.
* @param le
* expected length by terminal
* @return length of the response APDU
*/
private short processReadBinary(APDU apdu, short le, boolean protectedApdu) {
if (!hasMutuallyAuthenticated()) {
ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED);
}
if (!hasFileSelected()) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
byte p1 = buffer[OFFSET_P1];
byte p2 = buffer[OFFSET_P2];
short offset = Util.makeShort(p1, p2);
byte[] file = fileSystem.getFile(selectedFile);
if (file == null) {
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
short len;
short fileSize = fileSystem.getFileSize(selectedFile);
len = PassportUtil.min((short) (buffer.length - 37),
(short) (fileSize - offset));
// FIXME: 37 magic
len = PassportUtil.min(len, (short) buffer.length);
len = PassportUtil.min(le, len);
short bufferOffset = 0;
if (protectedApdu) {
bufferOffset = crypto.getApduBufferOffset(len);
}
Util.arrayCopyNonAtomic(file, offset, buffer, bufferOffset, len);
return len;
}
/**
* Processes and UPDATE_BINARY apdu. Writes data in the currently selected
* file.
*
* @param apdu
* carries the offset where to write date in header bytes p1 and
* p2.
*/
private void processUpdateBinary(APDU apdu) {
if (!hasFileSelected() || isLocked()) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
byte p1 = buffer[OFFSET_P1];
byte p2 = buffer[OFFSET_P2];
short offset = Util.makeShort(p1, p2);
short readCount = (short) (buffer[ISO7816.OFFSET_LC] & 0xff);
readCount = apdu.setIncomingAndReceive();
while (readCount > 0) {
fileSystem.writeData(selectedFile, offset, buffer, OFFSET_CDATA,
readCount);
offset += readCount;
readCount = apdu.receiveBytes(ISO7816.OFFSET_CDATA);
}
}
/**
* Processes and CREATE_FILE apdu.
*
* This functionality is only partly implemented. Only non-directories
* (files) can be created, all options for CREATE_FILE are ignored.
*
* @param apdu
* containing 6 bytes: 0x64 || (1 byte) || size (2) || fid (2)
*/
private void processCreateFile(APDU apdu) {
if (isLocked()) {
ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short lc = (short) (buffer[OFFSET_LC] & 0xff);
if (apdu.getCurrentState() == APDU.STATE_INITIAL) {
apdu.setIncomingAndReceive();
}
if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) {
// need all data in one APDU.
ISOException.throwIt(SW_INTERNAL_ERROR);
}
if (lc < (short) 6 || (buffer[OFFSET_CDATA + 1] & 0xff) < 4)
ISOException.throwIt(SW_WRONG_LENGTH);
if (buffer[OFFSET_CDATA] != 0x63)
ISOException.throwIt(SW_DATA_INVALID);
short size = Util.makeShort(buffer[(short) (OFFSET_CDATA + 2)],
buffer[(short) (OFFSET_CDATA + 3)]);
short fid = Util.makeShort(buffer[(short) (OFFSET_CDATA + 4)],
buffer[(short) (OFFSET_CDATA + 5)]);
if(fid == FileSystem.EF_CVCA_FID) {
fileSystem.createFile(fid, size, certificate);
}else{
fileSystem.createFile(fid, size);
}
}
public static boolean hasInternalAuthenticationKeys() {
return (persistentState & (HAS_EXPONENT | HAS_MODULUS)) == (HAS_EXPONENT | HAS_MODULUS);
}
public static boolean hasMutualAuthenticationKeys() {
return (persistentState & HAS_MUTUALAUTHENTICATION_KEYS) == HAS_MUTUALAUTHENTICATION_KEYS;
}
public static boolean hasEACKey() {
return (persistentState & HAS_EC_KEY) == HAS_EC_KEY;
}
public static boolean hasEACCertificate() {
return (persistentState & HAS_CVCERTIFICATE) == HAS_CVCERTIFICATE;
}
public static void setNoFileSelected() {
if (hasFileSelected()) {
volatileState[0] ^= FILE_SELECTED;
}
}
public static void setNoChallenged() {
if ((volatileState[0] & CHALLENGED) == CHALLENGED) {
volatileState[0] ^= CHALLENGED;
}
}
public static boolean hasFileSelected() {
return (volatileState[0] & FILE_SELECTED) == FILE_SELECTED;
}
public static boolean isLocked() {
return (persistentState & LOCKED) == LOCKED;
}
public static boolean isChallenged() {
return (volatileState[0] & CHALLENGED) == CHALLENGED;
}
/** Has BAC been completed? */
public static boolean hasMutuallyAuthenticated() {
return (volatileState[0] & MUTUAL_AUTHENTICATED) == MUTUAL_AUTHENTICATED;
}
public static boolean hasChipAuthenticated() {
return (volatileState[0] & CHIP_AUTHENTICATED) == CHIP_AUTHENTICATED;
}
public static boolean hasTerminalAuthenticated() {
return (volatileState[0] & TERMINAL_AUTHENTICATED) == TERMINAL_AUTHENTICATED;
}
}