/**
* This file is part of CardApplet-VCBP which is card applet implementation
* of V Cloud-Based Payments for SimplyTapp cloud platform.
* Copyright 2014 SimplyTapp, Inc.
*
* CardApplet-VCBP is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CardApplet-VCBP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CardApplet-VCBP. If not, see <http://www.gnu.org/licenses/>.
*/
package com.st.vcbp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
//import java.util.Iterator;
import java.util.TimeZone;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacard.security.RSAPrivateCrtKey;
import javacardx.apdu.ExtendedLength;
import org.globalplatform.GPSystem;
import org.globalplatform.SecureChannel;
import com.st.vcbp.crypto.KeyDerivation;
import com.st.vcbp.data.AccountParamsDynamic;
import com.st.vcbp.data.AccountParamsStatic;
import com.st.vcbp.data.LinkedHashMapFixedSize;
import com.st.vcbp.data.TransactionVerificationLog;
/**
* Implementation based on V Cloud-Based Payments Contactless
* Specifications Version 1.3 July 2014.
*
* @author SimplyTapp, Inc.
* @version 1.3.2 GPL
*/
public final class STPayW extends Applet implements ExtendedLength {
private static final long serialVersionUID = 1L;
// 1.3.2
private static final byte[] VERSION = { 0x31, 0x2E, 0x33, 0x2E, 0x32 };
private static final String GCM_MSG_ACCOUNT_PARAMETERS_UPDATE = "apupdate";
private static final String GCM_MSG_DEACTIVATE = "deactivate";
private static final String GCM_MSG_TERMINATE = "terminate";
// CLA for supported commands.
private static final byte CLA_PROPRIETARY = (byte) 0x80;
private static final byte CLA_PROPRIETARY_SECURE = (byte) 0x84;
// CLA/INS for supported commands.
// GP Commands
private static final short CLA_INS_INITIALIZE_UPDATE = (short) 0x8050;
private static final short CLA_INS_SET_STATUS = (short) 0x80F0;
private static final short CLA_INS_STORE_DATA = (short) 0x80E2;
private static final short CLA_INS_EXTERNAL_AUTHENTICATE = (short) 0x8482;
private static final short CLA_INS_SET_STATUS_SECURED = (short) 0x84F0;
private static final short CLA_INS_STORE_DATA_SECURED = (short) 0x84E2;
// Card Agent Commands
private static final short CLA_INS_GET_STATIC_ACCOUNT_PARAMETERS = (short) 0x8030;
private static final short CLA_INS_GET_DYNAMIC_ACCOUNT_PARAMETERS = (short) 0x8032;
private static final short CLA_INS_PUT_TRANSACTION_VERIFICATION_LOG = (short) 0x8034;
// Issuer Commands
private static final short CLA_INS_GET_TRANSACTION_VERIFICATION_LOG = (short) 0x8040;
private static final short CLA_INS_GET_TRANSACTION_VERIFICATION_LOG_SECURED = (short) 0x8440;
// Application-specific SW.
private static final short SW_UNKNOWN_DGI = (short) 0x6A88;
// Proprietary Personalization Tags
private static final short TAG_MAX_NUM_LIVE_DYNAMIC_ACCT_PARAMS = (short) 0xDF30;
private static final short TAG_MIN_THRESHOLD_NUM_LIVE_DYNAMIC_ACCT_PARAMS = (short) 0xDF31;
private static final short TAG_TIME_TO_LIVE_CHECK_INTERVAL = (short) 0xDF39;
private static final short TAG_TIME_TO_LIVE_DYNAMIC_ACCT_PARAMS = (short) 0xDF3A;
private static final short TAG_MAX_NUM_TRANSACTION_VERIFICATION_LOGS = (short) 0xDF3B;
// Records in AFL list.
private HashMap<Short, byte[]> records = new HashMap<Short, byte[]>();
// Variables for cryptogram calculation.
private DESKey udk;
private DESKey udkMsd;
private DESKey tempKey;
private RSAPrivateCrtKey iccPrivKey;
private transient SecureChannel secureChannel;
// NOTE: Use 'gpState' instead of using GPSystem.getCardContentState() and GPSystem.setCardContentState().
// Supported States:
// - GPSystem.APPLICATION_SELECTABLE (7)
// - GPSystem.SECURITY_DOMAIN_PERSONALIZED (15)
// - GPSystem.CARD_LOCKED (127)
// - GPSystem.CARD_TERMINATED (-1)
private byte gpState;
private AccountParamsStatic accountParamsStatic;
private AccountParamsDynamic accountParamsDynamic;
private int prevHourOfDay;
private short lukGenerationCounter;
private short sequenceCounter;
// Time to Live in Hours
// Supports:
// - 0 = never expire
// - 1-255 = expires in 1-255 hours
private byte timeToLiveHours;
private LinkedHashMapFixedSize<String, TransactionVerificationLog> transactionVerificationLogs;
/**
* Creates Java Card applet object.
*
* @param array
* the byte array containing the AID bytes
* @param offset
* the start of AID bytes in array
* @param length
* the length of the AID bytes in array
*/
private STPayW(byte[] array, short offset, byte length) {
this.udk = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false);
this.udkMsd = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false);
this.tempKey = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_DES3_2KEY, false);
this.gpState = GPSystem.APPLICATION_SELECTABLE;
this.accountParamsStatic = new AccountParamsStatic();
// Build Static Account Parameters.
// NOTE: This is a kludge to retrieve AID. This would not work with real Java Card.
byte[] aidBuffer = new byte[16];
byte aidLength = JCSystem.getAID().getBytes(aidBuffer, (short) 0);
this.accountParamsStatic.setAid(aidBuffer, (short) 0, aidLength);
this.sequenceCounter = (short) 0;
try {
setStatePerso();
}
catch (IOException e) {
}
// Register instance AID.
register(array, (short) (offset + (byte) 1), array[offset]);
}
/**
* Registers applet instance AID by calling constructor.
*
* @param array
* the byte array containing the AID bytes
* @param offset
* the start of AID bytes in array
* @param length
* the length of the AID bytes in array
* @see javacard.framework.Applet.install
*/
public static void install(byte[] array, short offset, byte length) {
new STPayW(array, offset, length);
}
/**
* Processes incoming APDU command.
* <p>
* Supported commands (<b>CLA INS</b>):
* <ul>
* <li><b>00 A4</b>: Select
* <li><b>80 30</b>: Get Static Account Parameters [from card agent]
* <li><b>80 32</b>: Get Dynamic Account Parameters [from card agent]
* <li><b>80 34</b>: Put Transaction Verification Log [from card agent]
* <li><b>80 40</b>: Get Transaction Verification Log [from Issuer]
* <li><b>80 50</b>: Initialize Update [from Issuer]
* <li><b>80 E2</b>: Store Data [from Issuer]
* <li><b>80 F0</b>: Set Status [from Issuer]
* <li><b>84 40</b>: Get Transaction Verification Log, Secured [from Issuer]
* <li><b>84 82</b>: External Authenticate [from Issuer]
* <li><b>84 E2</b>: Store Data, Secured [from Issuer]
* <li><b>84 F0</b>: Set Status, Secured [from Issuer]
* </ul>
*
* @param apdu
* the incoming <code>APDU</code> object
* @see javacard.framework.Applet.process
*/
public void process(APDU apdu) throws ISOException {
byte[] apduBuffer = apdu.getBuffer();
byte protocolMedia = (byte) (APDU.getProtocol() & APDU.PROTOCOL_MEDIA_MASK);
if (selectingApplet()) {
// Process Select command.
// Check if card is terminated.
if (this.gpState == GPSystem.CARD_TERMINATED) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
// Only support direct selection by DF name of first record and return FCI.
// Check if P1=0x04 and P2=0x00.
if (Util.getShort(apduBuffer, ISO7816.OFFSET_P1) != (short) 0x0400) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Check AID, partial select not supported.
short aidLength = apdu.setIncomingAndReceive();
if (!JCSystem.getAID().equals(apduBuffer, ISO7816.OFFSET_CDATA, (byte) aidLength)) {
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
// Construct Select response data.
if (protocolMedia == APDU.PROTOCOL_MEDIA_SOFT) {
// Return VERSION if Select command is from card agent.
apdu.setOutgoingAndSend((short) 0,
Util.arrayCopyNonAtomic(VERSION, (short) 0,
apduBuffer, (short) 0,
(short) VERSION.length));
return;
}
apduBuffer[0] = (byte) 0x6F;
apduBuffer[2] = (byte) 0x84;
apduBuffer[3] = JCSystem.getAID().getBytes(apduBuffer, (short) 4);
if ((this.gpState == GPSystem.SECURITY_DOMAIN_PERSONALIZED) &&
(protocolMedia == APDU.PROTOCOL_MEDIA_NFC)) {
// FCI Template must be present if personalized.
apduBuffer[1] = (byte) (Util.arrayCopyNonAtomic(this.accountParamsStatic.getTagA5Data(), (short) 0,
apduBuffer, (short) (apduBuffer[3] + 4),
(short) this.accountParamsStatic.getTagA5Data().length) - 2);
}
else {
apduBuffer[1] = (byte) (apduBuffer[3] + 2);
}
apdu.setOutgoingAndSend((short) 0, (short) (apduBuffer[1] + 2));
return;
}
// Handle commands starting from ones that have higher timing dependence.
// Get CLA (ignore logical channel bits) and INS.
byte claByte = (byte) (apduBuffer[ISO7816.OFFSET_CLA] & 0xFC);
short capduClaIns = (short) (Util.getShort(apduBuffer, ISO7816.OFFSET_CLA) & (short) 0xFCFF);
switch (capduClaIns) {
case CLA_INS_GET_STATIC_ACCOUNT_PARAMETERS: {
// Process Get Static Account Parameters command (from card agent).
if (this.gpState != GPSystem.SECURITY_DOMAIN_PERSONALIZED) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
if (protocolMedia != APDU.PROTOCOL_MEDIA_SOFT) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
getAccountParamsStatic(apdu);
return;
}
case CLA_INS_GET_DYNAMIC_ACCOUNT_PARAMETERS: {
// Process Get Dynamic Account Parameters command (from card agent).
if (this.gpState != GPSystem.SECURITY_DOMAIN_PERSONALIZED) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
if (protocolMedia != APDU.PROTOCOL_MEDIA_SOFT) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
getAccountParamsDynamic(apdu);
return;
}
case CLA_INS_PUT_TRANSACTION_VERIFICATION_LOG: {
// Process Put Transaction Verification Log command (from card agent).
if ((this.gpState != GPSystem.SECURITY_DOMAIN_PERSONALIZED) ||
(protocolMedia != APDU.PROTOCOL_MEDIA_SOFT)) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
putTransactionVerificationLog(apdu);
return;
}
case CLA_INS_GET_TRANSACTION_VERIFICATION_LOG:
case CLA_INS_GET_TRANSACTION_VERIFICATION_LOG_SECURED: {
// Process Get Transaction Verification Log command (from Issuer).
if (this.gpState == GPSystem.APPLICATION_SELECTABLE) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
getTransactionVerificationLog(apdu);
return;
}
case CLA_INS_INITIALIZE_UPDATE: {
// Process Initialize for Update command.
// NOTE: Allowed post-personalization.
/*
if (this.gpState == GPSystem.SECURITY_DOMAIN_PERSONALIZED) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
*/
// Obtain handle to SecureChannel interface.
this.secureChannel = GPSystem.getSecureChannel();
this.secureChannel.resetSecurity();
// Use GP API to process the APDU.
// Note: Returns SW=0x6700 if Lc is not 0x08.
short respLen = this.secureChannel.processSecurity(apdu);
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, respLen);
return;
}
case CLA_INS_EXTERNAL_AUTHENTICATE: {
// Process External Authenticate command.
// NOTE: Allowed post-personalization.
/*
if (this.gpState == GPSystem.SECURITY_DOMAIN_PERSONALIZED) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
*/
if (this.secureChannel == null) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
// Use GP API to process the APDU.
// Note: If Initialize Update has not been processed, processSecurity automatically returns SW=0x6985.
// Note: If External Authenticate has already been processed, processSecurity automatically returns SW=0x6985.
this.secureChannel.processSecurity(apdu);
// Note: There is no response data.
return;
}
case CLA_INS_SET_STATUS:
case CLA_INS_SET_STATUS_SECURED: {
// Process Set Status command.
// NOTE: Only allowed post-personalization.
if ((this.gpState == GPSystem.APPLICATION_SELECTABLE) ||
(this.gpState == GPSystem.CARD_TERMINATED)) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
// Check if External Authenticate has been performed successfully.
if ((byte) (this.secureChannel.getSecurityLevel() & SecureChannel.AUTHENTICATED) != SecureChannel.AUTHENTICATED) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
// Check if P1 indicates status type is "Application or Supplementary Security Domain"
// and if P2 indicates supported state control.
byte stateControl = apduBuffer[ISO7816.OFFSET_P2];
if ((apduBuffer[ISO7816.OFFSET_P1] != (byte) 0x40) ||
((stateControl != GPSystem.SECURITY_DOMAIN_PERSONALIZED) &&
(stateControl != GPSystem.CARD_LOCKED) &&
(stateControl != GPSystem.CARD_TERMINATED))) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
short cdataLength = apdu.setIncomingAndReceive();
// Check GP security level is C_MAC or C_MAC+C_DECRYPTION.
if ((this.secureChannel.getSecurityLevel() & (byte) 0x03) >= SecureChannel.C_MAC) {
// Use GP API to unwrap data.
try {
cdataLength = this.secureChannel.unwrap(apduBuffer, (short) 0, (short) (ISO7816.OFFSET_CDATA + cdataLength));
}
catch (ISOException isoe) {
// Throw security exception to be consistent with SE.
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
cdataLength -= ISO7816.OFFSET_CDATA;
}
// Validate AID.
apduBuffer[(byte) 64] = JCSystem.getAID().getBytes(apduBuffer, (short) 65);
if ((cdataLength != apduBuffer[(byte) 64]) ||
(Util.arrayCompare(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) 65, cdataLength) != (byte) 0)) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
String gcmMsg = null;
if (stateControl == GPSystem.SECURITY_DOMAIN_PERSONALIZED) {
// Resume account.
if (this.gpState != GPSystem.SECURITY_DOMAIN_PERSONALIZED) {
// Send message to card agent to indicate card activated.
gcmMsg = GCM_MSG_ACCOUNT_PARAMETERS_UPDATE;
}
}
else if (stateControl == GPSystem.CARD_LOCKED) {
// Suspend account.
if (this.gpState != GPSystem.CARD_LOCKED) {
// Send message to card agent to indicate card deactivated.
gcmMsg = GCM_MSG_DEACTIVATE;
}
}
else {
// Delete account.
try {
setStateTerminated();
}
catch (IOException e) {
}
// Send message to card agent to indicate card terminated.
gcmMsg = GCM_MSG_TERMINATE;
}
// Update GP state.
this.gpState = stateControl;
if (gcmMsg != null) {
// Send message to card agent.
try {
sendToAgent(gcmMsg);
}
catch (IOException e) {
ISOException.throwIt(ISO7816.SW_WARNING_STATE_UNCHANGED);
}
}
// Note: There is no response data.
return;
}
case CLA_INS_STORE_DATA:
case CLA_INS_STORE_DATA_SECURED: {
// Process Store Data command.
// NOTE: Allow post-issuance personalization update.
/*
// Check if applet is already personalized.
if (this.gpState == GPSystem.SECURITY_DOMAIN_PERSONALIZED) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
*/
// Check if External Authenticate has been performed successfully.
if ((byte) (this.secureChannel.getSecurityLevel() & SecureChannel.AUTHENTICATED) != SecureChannel.AUTHENTICATED) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte p1 = apduBuffer[ISO7816.OFFSET_P1];
short cdataLength = apdu.setIncomingAndReceive();
// Check GP security level is C_MAC or C_MAC+C_DECRYPTION.
if ((this.secureChannel.getSecurityLevel() & (byte) 0x03) >= SecureChannel.C_MAC) {
// Use GP API to unwrap data.
try {
cdataLength = this.secureChannel.unwrap(apduBuffer, (short) 0, (short) (ISO7816.OFFSET_CDATA + cdataLength));
}
catch (ISOException isoe) {
// Throw security exception to be consistent with SE.
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
cdataLength -= ISO7816.OFFSET_CDATA;
}
// NOTE: Restrict 1 DGI per Store Data command.
short dgi = Util.getShort(apduBuffer, ISO7816.OFFSET_CDATA);
short dgiOffset = 7;
// DGI length is defined to be 1 byte.
short dgiLength = (short) (apduBuffer[dgiOffset++] & 0xFF);
if ((short) (cdataLength - (byte) 3) != dgiLength) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (((dgi & (short) 0xF000) == (short) 0x8000) ||
((p1 & (byte) 0x60) == (byte) 0x60)) {
dgiLength = this.secureChannel.decryptData(apduBuffer, dgiOffset, dgiLength);
}
storeData(apduBuffer, dgi, dgiOffset, dgiLength);
// Check if last Store Data command.
if ((p1 & (byte) 0x80) == (byte) 0x80) {
// Check perso state.
if (this.gpState == GPSystem.APPLICATION_SELECTABLE) {
// Check if all mandatory data objects are personalized.
// Return DGI or tag of missing data elements separated by 'FF'.
dgiOffset = (short) 0;
if (this.accountParamsStatic.getTagA5Data() == null) {
dgiOffset = Util.setShort(apduBuffer, dgiOffset, (short) 0x9102);
}
if (this.accountParamsStatic.getGpoResponseQvsdc() == null) {
if (dgiOffset > (short) 0) {
apduBuffer[dgiOffset++] = (byte) 0xFF;
}
dgiOffset = Util.setShort(apduBuffer, dgiOffset, (short) 0x9207);
}
if (this.accountParamsStatic.getIssuerApplicationData() == null) {
if (dgiOffset > (short) 0) {
apduBuffer[dgiOffset++] = (byte) 0xFF;
}
dgiOffset = Util.setShort(apduBuffer, dgiOffset, (short) 0x9200);
}
if (this.accountParamsStatic.getTrack2EquivalentData() == null) {
if (dgiOffset > (short) 0) {
apduBuffer[dgiOffset++] = (byte) 0xFF;
}
apduBuffer[dgiOffset++] = (byte) 0x57;
}
if (this.accountParamsStatic.getPanSequenceNumber() == null) {
if (dgiOffset > (short) 0) {
apduBuffer[dgiOffset++] = (byte) 0xFF;
}
dgiOffset = Util.setShort(apduBuffer, dgiOffset, (short) 0x5F34);
}
if (this.accountParamsStatic.getCardTransactionQualifier() == null) {
if (dgiOffset > (short) 0) {
apduBuffer[dgiOffset++] = (byte) 0xFF;
}
dgiOffset = Util.setShort(apduBuffer, dgiOffset, (short) 0x9F6C);
}
if (!this.udk.isInitialized()) {
if (dgiOffset > (short) 0) {
apduBuffer[dgiOffset++] = (byte) 0xFF;
}
dgiOffset = Util.setShort(apduBuffer, dgiOffset, (short) 0x8000);
}
if (dgiOffset > (short) 0) {
apdu.setOutgoingAndSend((short) 0, dgiOffset);
// NOTE: Throwing exception returns empty response data.
//ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
return;
}
String track2EquivalentData = DataUtil.byteArrayToHexString(this.accountParamsStatic.getTrack2EquivalentData());
int separatorOffset = track2EquivalentData.indexOf("D");
String pan = track2EquivalentData.substring(4, separatorOffset);
String tempExpDate = "20" + track2EquivalentData.substring(separatorOffset + 1, separatorOffset + 5);
Calendar expDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
expDate.set(Integer.parseInt(tempExpDate.substring(0, 4)),
// Next month 0th day which reverts to this month last day.
Integer.parseInt(tempExpDate.substring(4)), 0,
// Last hour, minute, second of the day.
23, 59, 59);
try {
setStatePersonalized(pan, expDate, "", "");
}
catch (IOException e) {
}
// Update application life cycle state to personalized.
this.gpState = GPSystem.SECURITY_DOMAIN_PERSONALIZED;
}
else if (this.gpState == GPSystem.SECURITY_DOMAIN_PERSONALIZED) {
// Send message to card agent to trigger account parameters update.
try {
sendToAgent(GCM_MSG_ACCOUNT_PARAMETERS_UPDATE);
}
catch (IOException e) {
ISOException.throwIt(ISO7816.SW_WARNING_STATE_UNCHANGED);
}
}
}
return;
}
default:
}
if ((claByte == ISO7816.CLA_ISO7816) ||
(claByte == CLA_PROPRIETARY) ||
(claByte == CLA_PROPRIETARY_SECURE)) {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
else {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
}
// NOTE: Processing this APDU does not use Java Card methods.
private void getAccountParamsStatic(APDU apdu) throws ISOException {
byte[] apduBuffer = apdu.getBuffer();
// Check if P1=0x00 and P2=0x00.
if (Util.getShort(apduBuffer, ISO7816.OFFSET_P1) != (short) 0x0000) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// No Lc.
// Retrieve Le.
short dataLength = apdu.setOutgoing();
// Check if Le=0x00 or 0x0000.
if ((dataLength != (short) 256) &&
(dataLength != (short) 32767)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Set secret data in serializable class.
if ((this.iccPrivKey != null) && this.iccPrivKey.isInitialized()) {
try {
this.accountParamsStatic.setIccPrivKeyCrtCoefficient(apduBuffer, (short) 0,
this.iccPrivKey.getPQ(apduBuffer, (short) 0));
this.accountParamsStatic.setIccPrivKeyPrimeExponentQ(apduBuffer, (short) 0,
this.iccPrivKey.getDQ1(apduBuffer, (short) 0));
this.accountParamsStatic.setIccPrivKeyPrimeExponentP(apduBuffer, (short) 0,
this.iccPrivKey.getDP1(apduBuffer, (short) 0));
this.accountParamsStatic.setIccPrivKeyPrimeQ(apduBuffer, (short) 0,
this.iccPrivKey.getQ(apduBuffer, (short) 0));
this.accountParamsStatic.setIccPrivKeyPrimeP(apduBuffer, (short) 0,
this.iccPrivKey.getP(apduBuffer, (short) 0));
this.accountParamsStatic.setIccKeyModulusLength(this.iccPrivKey.getSize() / 8);
}
catch (Exception e) {
}
Util.arrayFillNonAtomic(apduBuffer, (short) 0, (short) (this.iccPrivKey.getSize() / (byte) 16), (byte) 0x00);
}
// DEBUG
/*
if (this.accountParamsStatic != null) {
System.out.println("accountParamsStatic Aid: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getAid()));
System.out.println("accountParamsStatic AidPpse: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getAidPpse()));
System.out.println("accountParamsStatic PpseResponse: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getPpseResponse()));
System.out.println("accountParamsStatic TagA5Data: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getTagA5Data()));
System.out.println("accountParamsStatic GpoResponseMsd: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getGpoResponseMsd()));
System.out.println("accountParamsStatic GpoResponseQvsdc: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getGpoResponseQvsdc()));
System.out.println("accountParamsStatic IssuerApplicationData: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getIssuerApplicationData()));
System.out.println("accountParamsStatic PanSequenceNumber: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getPanSequenceNumber()));
System.out.println("accountParamsStatic CardTransactionQualifier: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getCardTransactionQualifier()));
System.out.println("accountParamsStatic Track2EquivalentData: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getTrack2EquivalentData()));
System.out.println("accountParamsStatic CardholderName: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getCardholderName()));
System.out.println("accountParamsStatic CvmList: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getCvmList()));
//System.out.println("accountParamsStatic IccPrivKeyCrtCoefficient: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getIccPrivKeyCrtCoefficient()));
//System.out.println("accountParamsStatic IccPrivKeyPrimeExponentQ: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getIccPrivKeyPrimeExponentQ()));
//System.out.println("accountParamsStatic IccPrivKeyPrimeExponentP: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getIccPrivKeyPrimeExponentP()));
//System.out.println("accountParamsStatic IccPrivKeyPrimeQ: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getIccPrivKeyPrimeQ()));
//System.out.println("accountParamsStatic IccPrivKeyPrimeP: " + DataUtil.byteArrayToHexString(this.accountParamsStatic.getIccPrivKeyPrimeP()));
System.out.println("accountParamsStatic IccKeyModulusLength: " + this.accountParamsStatic.getIccKeyModulusLength());
System.out.println("accountParamsStatic MaxNumberAccountParamsDynamic: " + this.accountParamsStatic.getMaxNumberAccountParamsDynamic());
System.out.println("accountParamsStatic MinThresholdNumberAccountParamsDynamic: " + this.accountParamsStatic.getMinThresholdNumberAccountParamsDynamic());
System.out.println("accountParamsStatic CheckIntervalTimeToExpire: " + this.accountParamsStatic.getCheckIntervalTimeToExpire());
System.out.println("accountParamsStatic MaxTransactionVerificationLogs: " + this.accountParamsStatic.getMaxTransactionVerificationLogs());
System.out.println();
}
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
byte[] accountParamsStaticBytes = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(this.accountParamsStatic);
accountParamsStaticBytes = bos.toByteArray();
}
catch (Exception e) {
}
finally {
try {
if (out != null) {
out.close();
}
}
catch (IOException ioe) {
}
try {
bos.close();
}
catch (IOException ioe) {
}
}
// Clear secret data in serializable class.
if ((this.iccPrivKey != null) && this.iccPrivKey.isInitialized()) {
this.accountParamsStatic.setIccPrivKeyCrtCoefficient(null, (short) 0, (short) 0);
this.accountParamsStatic.setIccPrivKeyPrimeExponentQ(null, (short) 0, (short) 0);
this.accountParamsStatic.setIccPrivKeyPrimeExponentP(null, (short) 0, (short) 0);
this.accountParamsStatic.setIccPrivKeyPrimeQ(null, (short) 0, (short) 0);
this.accountParamsStatic.setIccPrivKeyPrimeP(null, (short) 0, (short) 0);
}
if (accountParamsStaticBytes != null) {
try {
if (dataLength < (short) accountParamsStaticBytes.length) {
Util.arrayCopyNonAtomic(accountParamsStaticBytes, (short) 0, apduBuffer, (short) 0, dataLength);
}
else {
dataLength = Util.arrayCopyNonAtomic(accountParamsStaticBytes, (short) 0, apduBuffer, (short) 0, (short) accountParamsStaticBytes.length);
}
}
catch (Exception e) {
// In case of buffer overflow.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
apdu.setOutgoingLength(dataLength);
apdu.sendBytes((short) 0, dataLength);
}
else {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
}
// NOTE: Processing this APDU does not use Java Card methods.
private void getAccountParamsDynamic(APDU apdu) throws ISOException {
byte[] apduBuffer = apdu.getBuffer();
// Check if P1=0x00 and P2=0x00.
if (Util.getShort(apduBuffer, ISO7816.OFFSET_P1) != (short) 0x0000) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// No Lc.
// Retrieve Le.
short dataLength = apdu.setOutgoing();
// Check if Le=0x00 or 0x0000.
if ((dataLength != (short) 256) &&
(dataLength != (short) 32767)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Increment Sequence Counter.
this.sequenceCounter++;
// Initialize Account Parameters Index (YHHHHCC).
// --------------------
Calendar calendar = Calendar.getInstance();
String year = String.valueOf(calendar.get(Calendar.YEAR));
// Least significant digit of the current year. (0-9)
year = year.substring(year.length() - 1);
int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR) - 1;
int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY) + 1;
if (hourOfDay != this.prevHourOfDay) {
this.prevHourOfDay = hourOfDay;
this.lukGenerationCounter = (short) 0;
}
// Number of hours since start of January 1 of the current year. (0001-8784)
String hours = String.format("%04d", (dayOfYear * 24) + hourOfDay);
// Counter that starts at 00 at the beginning of each hour and incremented by 1 each time Limited Use Key is generated. (00-99)
if (this.lukGenerationCounter >= (short) 100) {
this.lukGenerationCounter = (short) (this.lukGenerationCounter % 100);
// Generate warning when LUK Generation Counter overflows.
//System.out.println("lukGenerationCounter overflowed");
}
String cc = String.format("%02d", this.lukGenerationCounter++);
// --------------------
short keyLength = (short) 0;
final short lukOffset = (short) 0;
final short lukMsdOffset = (short) (lukOffset + KeyDerivation.BYTE_LENGTH_DES3_2KEY);
if (Util.getShort(this.accountParamsStatic.getIssuerApplicationData(), AccountParamsStatic.IAD_VALUE_OFFSET) == (short) 0x1F43) {
// Derive LUK for CVN 43.
keyLength = KeyDerivation.deriveCvn43Luk(this.udk, this.udkMsd,
year, hours, cc,
apduBuffer, lukOffset);
}
else {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Set expiration timestamp based on current timestamp.
long expirationTimestamp = (long) ((this.timeToLiveHours & 0xFF) * 3600000);
// TEST: Use minutes instead of hours for testing.
//long expirationTimestamp = (long) ((this.timeToLiveHours & 0xFF) * 60000);
if (expirationTimestamp > 0) {
expirationTimestamp += calendar.getTimeInMillis();
}
else {
expirationTimestamp = 0;
}
// Generate Dynamic Account Parameters.
if (keyLength == KeyDerivation.BYTE_LENGTH_DES3_2KEY) {
this.accountParamsDynamic = new AccountParamsDynamic(year + hours + cc,
apduBuffer, lukOffset,
expirationTimestamp,
this.sequenceCounter);
}
else if (keyLength == (short) (2 * KeyDerivation.BYTE_LENGTH_DES3_2KEY)) {
this.accountParamsDynamic = new AccountParamsDynamic(year + hours + cc,
apduBuffer, lukOffset,
expirationTimestamp,
this.sequenceCounter,
apduBuffer, lukMsdOffset);
}
else {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// DEBUG
/*
if (this.accountParamsDynamic != null) {
System.out.println("accountParamsDynamic AccountParamtersIndex: " + this.accountParamsDynamic.getAccountParamtersIndex());
System.out.println("accountParamsDynamic Luk: " + DataUtil.byteArrayToHexString(this.accountParamsDynamic.getLuk()));
System.out.println("accountParamsDynamic ExpirationTimestamp: " + this.accountParamsDynamic.getExpirationTimestamp());
System.out.println("accountParamsDynamic Atc: " + String.format("%04X", this.accountParamsDynamic.getAtc()));
System.out.println("accountParamsDynamic LukMsd: " + DataUtil.byteArrayToHexString(this.accountParamsDynamic.getLukMsd()));
System.out.println();
}
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
byte[] accountParamsDynamicBytes = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(this.accountParamsDynamic);
accountParamsDynamicBytes = bos.toByteArray();
}
catch (Exception e) {
}
finally {
try {
if (out != null) {
out.close();
}
}
catch (IOException ioe) {
}
try {
bos.close();
}
catch (IOException ioe) {
}
}
if (accountParamsDynamicBytes != null) {
try {
if (dataLength < (short) accountParamsDynamicBytes.length) {
Util.arrayCopyNonAtomic(accountParamsDynamicBytes, (short) 0, apduBuffer, (short) 0, dataLength);
}
else {
dataLength = Util.arrayCopyNonAtomic(accountParamsDynamicBytes, (short) 0, apduBuffer, (short) 0, (short) accountParamsDynamicBytes.length);
}
}
catch (Exception e) {
// In case of buffer overflow.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
apdu.setOutgoingLength(dataLength);
apdu.sendBytes((short) 0, dataLength);
}
else {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
}
// NOTE: Processing this APDU does not use Java Card methods.
private void putTransactionVerificationLog(APDU apdu) throws ISOException {
byte[] apduBuffer = apdu.getBuffer();
// Check if P1=0x00 and P2=0x00.
if (Util.getShort(apduBuffer, ISO7816.OFFSET_P1) != (short) 0x0000) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Retrieve Lc.
short cdataLength = apdu.setIncomingAndReceive();
short offsetCdata = apdu.getOffsetCdata();
// Retrieve Le.
short dataLength = apdu.setOutgoing();
if (this.transactionVerificationLogs == null) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
ByteArrayInputStream bis = new ByteArrayInputStream(apduBuffer,
offsetCdata,
cdataLength);
ObjectInput in = null;
TransactionVerificationLog transactionVerificationLog = null;
String exceptionMsg = null;
try {
in = new ObjectInputStream(bis);
transactionVerificationLog = (TransactionVerificationLog) in.readObject();
if (transactionVerificationLog != null) {
// DEBUG
/*
System.out.println("transactionVerificationLog UtcTimestamp: " + transactionVerificationLog.getUtcTimestamp());
System.out.println("transactionVerificationLog AccountParametersIndex: " + transactionVerificationLog.getAccountParametersIndex());
System.out.println("transactionVerificationLog TransactionType: " + transactionVerificationLog.getTransactionType());
System.out.println("transactionVerificationLog UnpredictableNumber: " + transactionVerificationLog.getUnpredictableNumber());
System.out.println();
*/
this.transactionVerificationLogs.put(transactionVerificationLog.getAccountParametersIndex(),
transactionVerificationLog);
// DEBUG
/*
System.out.println("transactionVerificationLogs size: " + this.transactionVerificationLogs.size());
Iterator<Map.Entry<String, TransactionVerificationLog>> iterator = this.transactionVerificationLogs.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, TransactionVerificationLog> entry = iterator.next();
System.out.println(" key=" + entry.getKey());
}
System.out.println();
*/
}
}
catch (Exception e) {
exceptionMsg = e.getMessage();
if (exceptionMsg == null) {
exceptionMsg = "null";
}
}
finally {
try {
bis.close();
}
catch (IOException ioe) {
}
try {
if (in != null) {
in.close();
}
}
catch (IOException ioe) {
}
}
if (exceptionMsg != null) {
// Return exception message.
try {
byte[] exceptionMsgBytes = exceptionMsg.getBytes("UTF-8");
apdu.setOutgoingLength((short) exceptionMsgBytes.length);
apdu.sendBytes((short) 0,
Util.arrayCopyNonAtomic(exceptionMsgBytes, (short) 0,
apduBuffer, (short) 0, (short) exceptionMsgBytes.length));
return;
}
catch (UnsupportedEncodingException e) {
ISOException.throwIt(ISO7816.SW_FILE_INVALID);
}
}
if (transactionVerificationLog == null) {
/*
// DEBUG
// Return received data.
apdu.setOutgoingLength(cdataLength);
apdu.sendBytes(offsetCdata, cdataLength);
return;
*/
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
// Return transaction timestamp in response.
byte[] transactionTimestamp = DataUtil.stringToCompressedByteArray(String.valueOf(transactionVerificationLog.getUtcTimestamp()));
dataLength = Util.arrayCopyNonAtomic(transactionTimestamp, (short) 0, apduBuffer, (short) 0, (short) transactionTimestamp.length);
apdu.setOutgoingLength(dataLength);
apdu.sendBytes((short) 0, dataLength);
}
// NOTE: Processing this APDU does not use Java Card methods.
private void getTransactionVerificationLog(APDU apdu) throws ISOException {
byte[] apduBuffer = apdu.getBuffer();
// Check if P1=0x00 and P2=0x00.
if (Util.getShort(apduBuffer, ISO7816.OFFSET_P1) != (short) 0x0000) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Check if External Authenticate has been performed successfully.
if ((byte) (this.secureChannel.getSecurityLevel() & SecureChannel.AUTHENTICATED) != SecureChannel.AUTHENTICATED) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
short cdataLength = apdu.setIncomingAndReceive();
short offsetCdata = apdu.getOffsetCdata();
// Check GP security level.
if ((this.secureChannel.getSecurityLevel() & (byte) 0x03) >= SecureChannel.C_MAC) {
// Use GP API to unwrap data.
try {
cdataLength = this.secureChannel.unwrap(apduBuffer, (short) 0, (short) (offsetCdata + cdataLength));
}
catch (ISOException isoe) {
// Throw security exception to be consistent with SE.
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
cdataLength -= offsetCdata;
}
// Retrieve Le.
short dataLength = apdu.setOutgoing();
// Check if Lc=0x07.
// Check if Le=0x00.
if ((cdataLength != (short) 7) ||
(dataLength != (short) 256)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (this.transactionVerificationLogs == null) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
String accountParameterIndex = null;
try {
accountParameterIndex = new String(apduBuffer, offsetCdata, cdataLength, "UTF-8");
}
catch (Exception e) {
}
if (accountParameterIndex == null) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
TransactionVerificationLog transactionVerificationLog = this.transactionVerificationLogs.get(accountParameterIndex);
if (transactionVerificationLog == null) {
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
byte[] transactionVerificationLogBytes = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(transactionVerificationLog);
transactionVerificationLogBytes = bos.toByteArray();
}
catch (Exception e) {
}
finally {
try {
if (out != null) {
out.close();
}
}
catch (IOException ioe) {
}
try {
bos.close();
}
catch (IOException ioe) {
}
}
if (transactionVerificationLogBytes != null) {
try {
if (dataLength < (short) transactionVerificationLogBytes.length) {
Util.arrayCopyNonAtomic(transactionVerificationLogBytes, (short) 0, apduBuffer, (short) 0, dataLength);
}
else {
dataLength = Util.arrayCopyNonAtomic(transactionVerificationLogBytes, (short) 0, apduBuffer, (short) 0, (short) transactionVerificationLogBytes.length);
}
}
catch (Exception e) {
// In case of buffer overflow.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
apdu.setOutgoingLength(dataLength);
apdu.sendBytes((short) 0, dataLength);
}
else {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
}
private void storeData(byte[] data, short dgi, short dgiOffset, short dgiLength) {
// Check if DGI contains record data.
if (((short) (dgi & (short) 0xFF00) >= (short) 0x0100) &&
((short) (dgi & (short) 0xFF00) <= (short) 0x0A00)) {
// NOTE: SFIs outside of EVM range, '0BXX' to '1EXX', will not be stored in records.
//((short) (dgi & (short) 0xFF00) <= (short) 0x1E00)) {
// Store SFI record.
this.records.put(dgi, Arrays.copyOfRange(data, dgiOffset, dgiOffset + dgiLength));
// Build Static Account Parameters.
this.accountParamsStatic.setSfiRecords(this.records);
return;
}
switch (dgi) {
case (short) 0x4000: {
dgiLength += dgiOffset;
while (dgiOffset < dgiLength) {
short tag = Util.getShort(data, dgiOffset);
dgiOffset += (byte) 2;
if (data[dgiOffset++] != (byte) 1) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Build Static Account Parameters.
byte value = data[dgiOffset++];
if (tag == TAG_MAX_NUM_LIVE_DYNAMIC_ACCT_PARAMS) {
// Maximum Number of Live Dynamic Account Parameters
this.accountParamsStatic.setMaxNumberAccountParamsDynamic(value);
}
else if (tag == TAG_MIN_THRESHOLD_NUM_LIVE_DYNAMIC_ACCT_PARAMS) {
// Minimum Threshold Number of Live Dynamic Account Parameters
this.accountParamsStatic.setMinThresholdNumberAccountParamsDynamic(value);
}
else if (tag == TAG_TIME_TO_LIVE_CHECK_INTERVAL) {
// Time to Live Check Interval in Minutes
this.accountParamsStatic.setCheckIntervalTimeToExpire(value);
}
else if (tag == TAG_TIME_TO_LIVE_DYNAMIC_ACCT_PARAMS) {
// Time to Live in Hours
this.timeToLiveHours = value;
}
else if (tag == TAG_MAX_NUM_TRANSACTION_VERIFICATION_LOGS) {
// Maximum Number of Transaction Verification Logs
this.accountParamsStatic.setMaxTransactionVerificationLogs(value);
// Initialize Transaction Verification Log storage.
final int sizeTransactionVerificationLogs = this.accountParamsStatic.getMaxTransactionVerificationLogs();
if (sizeTransactionVerificationLogs <= 0) {
this.transactionVerificationLogs = null;
}
else {
if (this.transactionVerificationLogs == null) {
this.transactionVerificationLogs = new LinkedHashMapFixedSize<String, TransactionVerificationLog>(sizeTransactionVerificationLogs);
}
else {
this.transactionVerificationLogs.updateSize(sizeTransactionVerificationLogs);
}
// DEBUG
/*
System.out.println("transactionVerificationLogs size: " + this.transactionVerificationLogs.size());
Iterator<Map.Entry<String, TransactionVerificationLog>> iterator = this.transactionVerificationLogs.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, TransactionVerificationLog> entry = iterator.next();
System.out.println(" key=" + entry.getKey());
}
System.out.println();
*/
}
}
else {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
}
return;
}
case (short) 0x8000: // DES Key
case (short) 0x8001: { // Alternate DES Key for MSD
// UDK (Unique Derivation Key).
// Save temporarily in transient key object until KCV is verified.
this.tempKey.setKey(data, dgiOffset);
return;
}
case (short) 0x8201:
case (short) 0x8202:
case (short) 0x8203:
case (short) 0x8204:
case (short) 0x8205: {
short keySize = (short) (dgiLength * (byte) 16);
if (this.iccPrivKey == null) {
// NOTE: 'keyEncryption' parameter not used.
this.iccPrivKey = (RSAPrivateCrtKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_CRT_PRIVATE, keySize, false);
}
else if (this.iccPrivKey.getSize() != keySize) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (dgi == (short) 0x8201) {
// CRT constant q^-1 mod p
this.iccPrivKey.setPQ(data, dgiOffset, dgiLength);
}
else if (dgi == (short) 0x8202) {
// CRT constant d mod (q - 1)
this.iccPrivKey.setDQ1(data, dgiOffset, dgiLength);
}
else if (dgi == (short) 0x8203) {
// CRT constant d mod (p - 1)
this.iccPrivKey.setDP1(data, dgiOffset, dgiLength);
}
else if (dgi == (short) 0x8204) {
// CRT constant prime factor q
this.iccPrivKey.setQ(data, dgiOffset, dgiLength);
}
else {
// CRT constant prime factor p
this.iccPrivKey.setP(data, dgiOffset, dgiLength);
}
return;
}
case (short) 0x9000: // DES Key Check Value
case (short) 0x9001: { // Alternate DES Key Check Value
// UDK KCV.
final short kcvOffset = (short) 64;
short kcvLength = KeyDerivation.generateKcv(this.tempKey, data, kcvOffset);
if (kcvLength == (short) -1) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
// Compare computed KCV with received KCV.
if (Util.arrayCompare(data, dgiOffset, data, kcvOffset, dgiLength) != (byte) 0) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
this.tempKey.getKey(data, (short) 0);
this.tempKey.clearKey();
if (dgi == (short) 0x9000) {
// Save UDK.
this.udk.setKey(data, (short) 0);
}
else {
// Save alternate UDK for MSD.
this.udkMsd.setKey(data, (short) 0);
}
Util.arrayFillNonAtomic(data, (short) 0, (short) 16, (byte) 0x00);
return;
}
case (short) 0x9102: { // Required
// Build Static Account Parameters.
// Select response data for contactless.
this.accountParamsStatic.setTagA5Data(data, dgiOffset, dgiLength);
return;
}
case (short) 0x9200: {
// Build Static Account Parameters.
// Issuer Application Data
this.accountParamsStatic.setIssuerApplicationData(data, dgiOffset, dgiLength);
return;
}
case (short) 0x9206: { // Required
// Build Static Account Parameters.
// MSD GPO response data.
this.accountParamsStatic.setGpoResponseMsd(data, dgiOffset, dgiLength);
return;
}
case (short) 0x9207: { // Required
// Build Static Account Parameters.
// qVSDC GPO response data.
this.accountParamsStatic.setGpoResponseQvsdc(data, dgiOffset, dgiLength);
return;
}
case (short) 0x0E01: {
// Internal Data
dgiLength += dgiOffset;
while (dgiOffset < dgiLength) {
// Find the tag (1 or 2 bytes).
// If the low order 5 bits of high order byte are set, then we have a 2 byte tag.
short tag = Util.makeShort(((byte) (data[dgiOffset] & 0x1F) == 0x1F) ? data[dgiOffset++] : (byte) 0,
data[dgiOffset++]);
short length = (short) (data[dgiOffset++] & 0xFF);
switch (tag) {
case (short) 0x0057: {
// Build Static Account Parameters.
// Include tag and length.
this.accountParamsStatic.setTrack2EquivalentData(data,
(short) (dgiOffset - 2),
(short) (length + 2));
break;
}
case (short) 0x008E: {
// Build Static Account Parameters.
this.accountParamsStatic.setCvmList(data, dgiOffset, length);
break;
}
case (short) 0x5F20: {
// Build Static Account Parameters.
// Include tag and length.
this.accountParamsStatic.setCardholderName(data,
(short) (dgiOffset - 3),
(short) (length + 3));
break;
}
case (short) 0x5F34: {
if (length != (short) 1) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Build Static Account Parameters.
// Include tag and length.
this.accountParamsStatic.setPanSequenceNumber(data,
(short) (dgiOffset - 3),
(short) (length + 3));
break;
}
case (short) 0x9F6C: {
if (length != (short) 2) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Build Static Account Parameters.
// Include tag and length.
this.accountParamsStatic.setCardTransactionQualifier(data,
(short) (dgiOffset - 3),
(short) (length + 3));
break;
}
default:
}
dgiOffset += length;
}
return;
}
default:
ISOException.throwIt(SW_UNKNOWN_DGI);
}
}
}