/** * This file is part of CardApplet-MMPP which is card applet implementation * of M Remote-SE Mobile PayP for SimplyTapp cloud platform. * Copyright 2014 SimplyTapp, Inc. * * CardApplet-MMPP 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-MMPP 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-MMPP. If not, see <http://www.gnu.org/licenses/>. */ package com.st.mmpp; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.util.Calendar; 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.MessageDigest; import javacard.security.RSAPrivateCrtKey; import javacard.security.RandomData; import javacardx.apdu.ExtendedLength; import org.globalplatform.GPSystem; import org.globalplatform.SecureChannel; import com.st.mmpp.crypto.DataEncryption; import com.st.mmpp.crypto.DataGeneration; import com.st.mmpp.data.CardProfile; import com.st.mmpp.data.PaymentTokenPayloadSingleUseKey; /** * Implementation of PayP based on Remote-SE Mobile PayP * * @author SimplyTapp, Inc. * @version 1.2.1 GPL */ public final class STPayP extends Applet implements ExtendedLength { private static final long serialVersionUID = 1L; // 1.2.1 private static final byte[] VERSION = { 0x31, 0x2E, 0x32, 0x2E, 0x31 }; // Remote Management Information definitions. private static final byte RMI_VERSION = (byte) 0x60; private static final byte RMI_FUNCTION_PTP_CP = (byte) 0x01; private static final byte RMI_FUNCTION_PTP_SUK = (byte) 0x02; private static final byte RMI_FUNCTION_MOBILE_CHECK = (byte) 0x1C; private static final byte RMI_FUNCTION_MOBILE_PIN_CHANGE = (byte) 0x1D; private static final byte RMI_FUNCTION_DEACTIVATE = (byte) 0x1E; // Proprietary private static final byte RMI_FUNCTION_REMOTE_WIPE = (byte) 0x1F; private static final byte RMI_FORMAT_DISPLAY = (byte) 0x01; private Records records; // Persistent data objects. // Stores persistent data objects. private byte[] persistentByteBuffer; // Stores personalized persistent data objects. private byte[] personalizedPersistentByteBuffer; // Stores File Control Information. private byte[] selectResponse; // Stores Card Layout Description Part 1. private byte[] cardLayoutDescriptionPart1; // Stores Card Layout Description Part 2. private byte[] cardLayoutDescriptionPart2; // Stores Card Layout Description Part 3. private byte[] cardLayoutDescriptionPart3; // Transient data objects. // Stores transient data objects. private byte[] transientByteBuffer; // Key objects. private DESKey mkAC; // Card Master Key private DESKey mkIDN; // ICC Dynamic Number Master Key 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; // For MPP Remote-SE Lite. private CardProfile cardProfile; private PaymentTokenPayloadSingleUseKey ptpSuk; private byte[] cardProfileHash; private byte[] mobilePin; private MessageDigest sha256; private RandomData random; private DataEncryption dataEncryption; /** * 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 STPayP(byte[] array, short offset, byte length) { /*** Start allocate memory when applet is instantiated. ***/ this.records = new Records(Constants.MAX_SFI_RECORDS); this.persistentByteBuffer = new byte[Constants.SIZE_PBB]; this.personalizedPersistentByteBuffer = new byte[Constants.SIZE_PPBB]; this.transientByteBuffer = JCSystem.makeTransientByteArray(Constants.SIZE_TBB, JCSystem.CLEAR_ON_DESELECT); // NOTE: 'keyEncryption' parameter not used. this.mkAC = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false); this.mkIDN = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false); /*** End allocate memory when applet is instantiated. ***/ /*** Allocate memory when personalized. ***/ this.selectResponse = null; this.cardLayoutDescriptionPart1 = null; this.cardLayoutDescriptionPart2 = null; this.cardLayoutDescriptionPart3 = null; this.gpState = GPSystem.APPLICATION_SELECTABLE; /*** Start initialize variables specific to MPP Remote-SE Lite. ***/ this.cardProfile = new CardProfile(); // Build Card Profile. // NOTE: This is a kludge to retrieve AID. This would not work with real Java Card. byte aidLength = JCSystem.getAID().getBytes(this.transientByteBuffer, (short) 0); this.cardProfile.setAid(this.transientByteBuffer, (short) 0, aidLength); this.cardProfileHash = new byte[32]; // Initialize and seed random. this.random = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM); byte[] seed = DataUtil.stringToCompressedByteArray(String.valueOf(Calendar.getInstance().getTimeInMillis())); this.random.setSeed(seed, (short) 0, (short) seed.length); // Initialize Mobile Key. this.dataEncryption = new DataEncryption(); if (!this.dataEncryption.initMobileKey()) { System.out.println("Error: M_Key not initialized."); } this.sha256 = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); /*** End initialize variables specific to MPP Remote-SE Lite. ***/ // 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 STPayP(array, offset, length); } /** * Processes incoming APDU command. * <p> * Supported commands (<b>CLA INS</b>): * <ul> * <li><b>00 A4</b>: Select * <li><b>80 50</b>: Initialize Update [from Issuer] * <li><b>80 80</b>: Get Card Profile [from card agent] * <li><b>80 82</b>: Get PTP_SUK [from card agent] * <li><b>80 84</b>: Get Mobile Key [from card agent] * <li><b>80 90</b>: Send Agent Notification [from Issuer] * <li><b>80 A0</b>: Initialize Mobile PIN [from card agent] * <li><b>80 E2</b>: Store Data [from Issuer] * <li><b>80 F0</b>: Set Status [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); } // Create/reset global transient data. // NOTE: Buffer contents should already be reset. This forces reset. Util.arrayFillNonAtomic(this.transientByteBuffer, (short) 0, Constants.SIZE_TBB, (byte) 0x00); // 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; } // Check if applet is not personalized. else if (this.gpState != GPSystem.SECURITY_DOMAIN_PERSONALIZED) { // Set FCI Template tag. apduBuffer[(byte) 0] = Constants.TAG_FCI_TEMPLATE; // Set DF Name tag. apduBuffer[(byte) 2] = Constants.TAG_DF_NAME; // Set DF Name length and DF Name value. apduBuffer[(byte) 3] = JCSystem.getAID().getBytes(apduBuffer, (short) 4); // Set FCI Template length. apduBuffer[(byte) 1] = (byte) (apduBuffer[(byte) 3] + (byte) 2); // Send Select response. apdu.setOutgoingAndSend((short) 0, (short) (apduBuffer[(byte) 1] + (byte) 2)); // Go to Personalized state. this.transientByteBuffer[Constants.TBB_OFFSET_STATE] = Constants.APP_STATE_PERSO; return; } // Retrieve personalized FCI response and send Select response. apdu.setOutgoingAndSend((short) 0, Util.arrayCopyNonAtomic(this.selectResponse, (short) 0, apduBuffer, (short) 0, (short) this.selectResponse.length)); // Go to Selected state. this.transientByteBuffer[Constants.TBB_OFFSET_STATE] = Constants.APP_STATE_SELECTED; // IF 'Application Blocked' in Previous Transaction History is set if ((this.personalizedPersistentByteBuffer[Constants.PPBB_OFFSET_PREVIOUS_TRANSACTION_HISTORY] & Constants.PREVIOUS_TRANSACTION_HISTORY_BIT_APP_BLOCKED) != (byte) 0x00) { // Return FCI with SW=0x6283. ISOException.throwIt(Constants.SW_WARNING_SELECTED_FILE_INVALIDATED); } return; } // Retrieve current application state. byte appState = this.transientByteBuffer[Constants.TBB_OFFSET_STATE]; // Handle commands starting from ones that have higher timing dependence. // Get CLA (ignore logical channel bits) and INS. short capduClaIns = (short) (Util.getShort(apduBuffer, ISO7816.OFFSET_CLA) & (short) 0xFCFF); switch (capduClaIns) { case Constants.CLA_INS_GET_CARD_PROFILE: { // Process Get Card Profile 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); } try { getCardProfile(apdu); if (this.mobilePin == null) { // Send message to card agent to trigger Mobile PIN initialization. try { sendRemoteNotificationMessage(RMI_FUNCTION_MOBILE_PIN_CHANGE, true); } catch (Exception e) { System.out.println("sendToAgentSecured exception: " + e.getMessage()); } } } catch (ISOException isoe) { ISOException.throwIt(isoe.getReason()); } return; } case Constants.CLA_INS_GET_PTP_SUK: { // Process Get PTP_SUK 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); } getPtpSuk(apdu); return; } case Constants.CLA_INS_GET_MOBILE_KEY: { // Process Get Mobile Key command (from card agent). if (protocolMedia != APDU.PROTOCOL_MEDIA_SOFT) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } getMobileKey(apdu); return; } case Constants.CLA_INS_INITIALIZE_MOBILE_PIN: { // Process Initialize Mobile PIN 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); } // Check if P1=0x00 and P2=0x00. if (Util.getShort(apduBuffer, ISO7816.OFFSET_P1) != (short) 0x0000) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } short cdataLength = apdu.setIncomingAndReceive(); // Check if Lc=[number of data bytes read]. // Check if Lc>=4 and Lc<=8. if ((cdataLength != (short) (apduBuffer[ISO7816.OFFSET_LC] & (short) 0x00FF)) || (cdataLength < (short) 4) || (cdataLength > (short) 8)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } this.mobilePin = new byte[cdataLength]; Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, this.mobilePin, (short) 0, cdataLength); // Send message to card agent to trigger Get PTP_SUK command. try { sendRemoteNotificationMessage(RMI_FUNCTION_PTP_SUK, false); } catch (Exception e) { } return; } case Constants.CLA_INS_SEND_AGENT_NOTIFICATON: { // Process Send Agent Notification command (from Issuer). // Check if P1=0x00 and P2=0x00. if (Util.getShort(apduBuffer, ISO7816.OFFSET_P1) != (short) 0x0000) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } // Send message to card agent to trigger Get PTP_SUK command. try { sendRemoteNotificationMessage(RMI_FUNCTION_PTP_SUK, true); } catch (Exception e) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } return; } case Constants.CLA_INS_INITIALIZE_UPDATE: { // Process Initialize for Update command. // NOTE: Allowed post-personalization. /* // Check if applet is already personalized. if (appState != Constants.APP_STATE_PERSO) { 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 Constants.CLA_INS_EXTERNAL_AUTHENTICATE: { // Process External Authenticate command. // NOTE: Allowed post-personalization. /* // Check if applet is already personalized. if (appState != Constants.APP_STATE_PERSO) { 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 Constants.CLA_INS_SET_STATUS: case Constants.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); } byte rmiFunction = (byte) 0; if (stateControl == GPSystem.SECURITY_DOMAIN_PERSONALIZED) { if (this.gpState != GPSystem.SECURITY_DOMAIN_PERSONALIZED) { // Send message to card agent to indicate card activated. rmiFunction = RMI_FUNCTION_PTP_CP; } } else if (stateControl == GPSystem.CARD_LOCKED) { if (this.gpState != GPSystem.CARD_LOCKED) { // Send message to card agent to indicate card deactivated. rmiFunction = RMI_FUNCTION_DEACTIVATE; } } else { try { setStateTerminated(); } catch (IOException e) { } // Send message to card agent to indicate card terminated. rmiFunction = RMI_FUNCTION_REMOTE_WIPE; } // Update GP state. this.gpState = stateControl; if (rmiFunction != (byte) 0) { // Send message to card agent. try { sendRemoteNotificationMessage(rmiFunction, true); } catch (Exception e) { ISOException.throwIt(ISO7816.SW_WARNING_STATE_UNCHANGED); } } // Note: There is no response data. return; } case Constants.CLA_INS_STORE_DATA: case Constants.CLA_INS_STORE_DATA_SECURED: { // Process Store Data command. // NOTE: Allow post-issuance personalization update. /* // Check if applet is already personalized. if (appState != Constants.APP_STATE_PERSO) { 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]; storeData(apdu); // Check if last Store Data command. if ((p1 & (byte) 0x80) == (byte) 0x80) { // Check perso state. if (appState == Constants.APP_STATE_PERSO) { // Build Card Profile. this.cardProfile.setSfi1Record1(this.records.getRecord((byte) 1, (short) 1)); this.cardProfile.setSfi2Record1(this.records.getRecord((byte) 2, (short) 1)); this.cardProfile.setSfi2Record2(this.records.getRecord((byte) 2, (short) 2)); this.cardProfile.setSfi2Record3(this.records.getRecord((byte) 2, (short) 3)); // Check if all mandatory data objects are personalized. // Return DGI or tag of missing data elements separated by 'FF'. short offset = (short) 0; if (this.cardProfile.getTagA5Data() == null) { offset = Util.setShort(apduBuffer, offset, (short) 0x9102); } if ((this.cardProfile.getAip() == null) || (this.cardProfile.getAfl() == null)) { if (offset > (short) 0) { apduBuffer[offset++] = (byte) 0xFF; } offset = Util.setShort(apduBuffer, offset, (short) 0xB005); } if ((this.cardProfile.getSfi1Record1() == null) && (this.cardProfile.getSfi2Record1() == null)) { if (offset > (short) 0) { apduBuffer[offset++] = (byte) 0xFF; } offset = Util.setShort(apduBuffer, offset, (short) 0x0101); apduBuffer[offset++] = (byte) 0xFF; offset = Util.setShort(apduBuffer, offset, (short) 0x0201); } if (!this.mkAC.isInitialized()) { if (offset > (short) 0) { apduBuffer[offset++] = (byte) 0xFF; } offset = Util.setShort(apduBuffer, offset, (short) 0x8000); } if (!this.mkIDN.isInitialized()) { if (offset > (short) 0) { apduBuffer[offset++] = (byte) 0xFF; } offset = Util.setShort(apduBuffer, offset, (short) 0xA006); } if (offset > (short) 0) { apdu.setOutgoingAndSend((short) 0, offset); // NOTE: Throwing exception returns empty response data. //ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); return; } String recordData; int track2DataOffset = 0; if (this.cardProfile.getSfi2Record1() != null) { recordData = DataUtil.byteArrayToHexString(this.cardProfile.getSfi2Record1()); track2DataOffset = recordData.indexOf("571") + 4; } else { recordData = DataUtil.byteArrayToHexString(this.cardProfile.getSfi1Record1()); track2DataOffset = recordData.indexOf("9F6B1") + 6; } int separatorOffset = recordData.indexOf("D", track2DataOffset); String pan = recordData.substring(track2DataOffset, separatorOffset); String tempExpDate = "20" + recordData.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 (Exception e) { } // Update application life cycle state to personalized. gpState = GPSystem.SECURITY_DOMAIN_PERSONALIZED; } else if (gpState == GPSystem.SECURITY_DOMAIN_PERSONALIZED) { // Send message to card agent to trigger card update. try { sendRemoteNotificationMessage(RMI_FUNCTION_PTP_CP, true); } catch (Exception e) { ISOException.throwIt(ISO7816.SW_WARNING_STATE_UNCHANGED); } } } return; } default: } // Get CLA byte, ignore logical channels bits. byte claByte = (byte) (capduClaIns >> (byte) 8); if ((claByte == ISO7816.CLA_ISO7816) || (claByte == Constants.CLA_PROPRIETARY) || (claByte == Constants.CLA_PROPRIETARY_SECURE)) { ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } else { ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); } } // NOTE: This method contains non-standard Java Card methods. private void sendRemoteNotificationMessage(byte remoteManagementFunction, boolean displayNotification) throws Exception { byte[] msgData = new byte[32]; // Set Remote Management Information. msgData[0] = (byte) (RMI_VERSION | remoteManagementFunction); // Set Session ID Version and Format. msgData[1] = RMI_VERSION; if (displayNotification) { msgData[1] |= RMI_FORMAT_DISPLAY; } // Set Session ID random value. this.random.generateData(msgData, (short) 2, (short) 13); // Encrypt notification message using the Mobile Key. short encMsgDataLength = this.dataEncryption.encryptRemoteMessage(msgData, (short) 0, (short) 15); // Send notification message to card agent. if (encMsgDataLength > (short) 0) { sendToAgent(DataUtil.byteArrayToHexString(msgData, 0, encMsgDataLength)); } } // NOTE: This method contains non-standard Java Card methods. private void getCardProfile(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); } short dataLength = apdu.setOutgoing(); // Check if Le=0x00 or 0x0000. if ((dataLength != (short) 256) && (dataLength != (short) 32767)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } /* // DEBUG if (this.cardProfile != null) { System.out.println("cardProfile Aid: " + DataUtil.byteArrayToHexString(this.cardProfile.getAid())); System.out.println("cardProfile AidPpse: " + DataUtil.byteArrayToHexString(this.cardProfile.getAidPpse())); System.out.println("cardProfile PpseResponse: " + DataUtil.byteArrayToHexString(this.cardProfile.getPpseResponse())); System.out.println("cardProfile TagA5Data: " + DataUtil.byteArrayToHexString(this.cardProfile.getTagA5Data())); System.out.println("cardProfile Aip: " + DataUtil.byteArrayToHexString(this.cardProfile.getAip())); System.out.println("cardProfile Afl: " + DataUtil.byteArrayToHexString(this.cardProfile.getAfl())); System.out.println("cardProfile Sfi1Record1: " + DataUtil.byteArrayToHexString(this.cardProfile.getSfi1Record1())); System.out.println("cardProfile Sfi2Record1: " + DataUtil.byteArrayToHexString(this.cardProfile.getSfi2Record1())); System.out.println("cardProfile Sfi2Record2: " + DataUtil.byteArrayToHexString(this.cardProfile.getSfi2Record2())); System.out.println("cardProfile Sfi2Record3: " + DataUtil.byteArrayToHexString(this.cardProfile.getSfi2Record3())); System.out.println("cardProfile Cdol1RelatedDataLength: " + String.format("%02X", this.cardProfile.getCdol1RelatedDataLength())); System.out.println("cardProfile MchipCvmIssuerOptions: " + String.format("%02X", this.cardProfile.getMchipCvmIssuerOptions())); System.out.println("cardProfile CrmCountryCode: " + String.format("%04X", this.cardProfile.getCrmCountryCode())); System.out.println("cardProfile CiacDeclineOnlineCapable: " + DataUtil.byteArrayToHexString(this.cardProfile.getCiacDeclineOnlineCapable())); System.out.println("cardProfile KeyDerivationIndex: " + String.format("%02X", this.cardProfile.getKeyDerivationIndex())); System.out.println("cardProfile ApplicationControl: " + DataUtil.byteArrayToHexString(this.cardProfile.getApplicationControl())); System.out.println("cardProfile AdditionalCheckTable: " + DataUtil.byteArrayToHexString(this.cardProfile.getAdditionalCheckTable())); System.out.println("cardProfile DualTapResetTimeout: " + String.format("%04X", this.cardProfile.getDualTapResetTimeout())); //System.out.println("cardProfile SecurityWord: " + DataUtil.byteArrayToHexString(this.cardProfile.getSecurityWord())); System.out.println("cardProfile CvmResetTimeout: " + String.format("%04X", this.cardProfile.getCvmResetTimeout())); System.out.println("cardProfile MagstripeCvmIssuerOptions: " + String.format("%02X", this.cardProfile.getMagstripeCvmIssuerOptions())); System.out.println("cardProfile CiacDeclinePpms: " + DataUtil.byteArrayToHexString(this.cardProfile.getCiacDeclinePpms())); //System.out.println("cardProfile PinIvCvc3Track1: " + DataUtil.byteArrayToHexString(this.cardProfile.getPinIvCvc3Track1())); //System.out.println("cardProfile PinIvCvc3Track2: " + DataUtil.byteArrayToHexString(this.cardProfile.getPinIvCvc3Track2())); System.out.println("cardProfile IccPubKeyModulusLength: " + String.format("%02X", this.cardProfile.getIccPubKeyModulusLength())); //System.out.println("cardProfile IccPrivKeyPrimeP: " + DataUtil.byteArrayToHexString(this.cardProfile.getIccPrivKeyPrimeP())); //System.out.println("cardProfile IccPrivKeyPrimeQ: " + DataUtil.byteArrayToHexString(this.cardProfile.getIccPrivKeyPrimeQ())); //System.out.println("cardProfile IccPrivKeyPrimeExponentP: " + DataUtil.byteArrayToHexString(this.cardProfile.getIccPrivKeyPrimeExponentP())); //System.out.println("cardProfile IccPrivKeyPrimeExponentQ: " + DataUtil.byteArrayToHexString(this.cardProfile.getIccPrivKeyPrimeExponentQ())); //System.out.println("cardProfile IccPrivKeyCrtCoefficient: " + DataUtil.byteArrayToHexString(this.cardProfile.getIccPrivKeyCrtCoefficient())); System.out.println("cardProfile MaxNumberPtpSuk: " + this.cardProfile.getMaxNumberPtpSuk()); System.out.println("cardProfile MinThresholdNumberPtpSuk: " + this.cardProfile.getMinThresholdNumberPtpSuk()); System.out.println(); } */ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = null; byte[] cardProfileBytes = null; try { out = new ObjectOutputStream(bos); out.writeObject(this.cardProfile); cardProfileBytes = bos.toByteArray(); // Calculate Card Profile hash. this.sha256.reset(); this.sha256.doFinal(cardProfileBytes, (short) 0, (short) cardProfileBytes.length, this.cardProfileHash, (short) 0); } catch (IOException e) { } finally { try { if (out != null) { out.close(); } } catch (IOException ex) { } try { bos.close(); } catch (IOException ex) { } } if (cardProfileBytes != null) { short responseOffset = (short) 0; // Check if Mobile PIN is initialized. if (this.mobilePin == null) { // NOTE: Kludge to prepend 'FF FF FF FF' to response to indicate Mobile PIN not initialized. responseOffset = Util.arrayFillNonAtomic(apduBuffer, (short) 0, (short) 4, (byte) 0xFF); dataLength -= (byte) 4; } try { if (dataLength < (short) cardProfileBytes.length) { dataLength = Util.arrayCopyNonAtomic(cardProfileBytes, (short) 0, apduBuffer, responseOffset, dataLength); } else { dataLength = Util.arrayCopyNonAtomic(cardProfileBytes, (short) 0, apduBuffer, responseOffset, (short) cardProfileBytes.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: This method contains non-standard Java Card methods. private void getPtpSuk(APDU apdu) throws ISOException { byte[] apduBuffer = apdu.getBuffer(); // Check if P1=0x00/0x01 and P2=0x00. // NOTE: P1=0x01 indicates Mobile PIN not used. byte p1 = apduBuffer[ISO7816.OFFSET_P1]; if (((p1 != (byte) 0x00) && (p1 != (byte) 0x01)) || (apduBuffer[ISO7816.OFFSET_P2] != (byte) 0x00)) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } short dataLength = apdu.setOutgoing(); // Check if Le=0x00 or 0x0000. if ((dataLength != (short) 256) && (dataLength != (short) 32767)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } if (p1 != (byte) 0x01) { // Check if Mobile PIN is initialized. if (this.mobilePin == null) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } } // Increment ATC. short atc = Util.getShort(this.persistentByteBuffer, Constants.PBB_OFFSET_APPLICATION_TRANSACTION_COUNTER); atc++; Util.setShort(this.persistentByteBuffer, Constants.PBB_OFFSET_APPLICATION_TRANSACTION_COUNTER, atc); short keyLength = 0; final short keyOffset = (short) 128; if (p1 != (byte) 0x01) { keyLength = DataGeneration.generateSuk(this.mkAC, atc, this.mobilePin, apduBuffer, keyOffset); } else { keyLength = DataGeneration.generateSuk(this.mkAC, atc, null, apduBuffer, keyOffset); } if (keyLength != DataGeneration.BYTE_LENGTH_DES3_2KEY) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } final short idnOffset = (short) (keyOffset + DataGeneration.BYTE_LENGTH_DES3_2KEY); short idnLength = DataGeneration.generateIdn(this.mkIDN, atc, apduBuffer, idnOffset); if (idnLength != (short) 8) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } // Generate Payment Token Payload (Single Use Key) for MPP Remote-SE Lite. this.ptpSuk = new PaymentTokenPayloadSingleUseKey(this.cardProfileHash, (short) 0, atc, apduBuffer, keyOffset, apduBuffer, idnOffset); /* // DEBUG if (this.ptpSuk != null) { //System.out.println("PTP_SUK PtpCpTruncatedHash: " + DataUtil.byteArrayToHexString(this.ptpSuk.getPtpCpTruncatedHash())); System.out.println("PTP_SUK Atc: " + String.format("%04X", this.ptpSuk.getAtc())); //System.out.println("PTP_SUK Suk: " + DataUtil.byteArrayToHexString(this.ptpSuk.getSuk())); //System.out.println("PTP_SUK Idn: " + DataUtil.byteArrayToHexString(this.ptpSuk.getIdn())); System.out.println(); } */ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = null; byte[] ptpSukBytes = null; try { out = new ObjectOutputStream(bos); out.writeObject(this.ptpSuk); ptpSukBytes = bos.toByteArray(); } catch (IOException e) { } finally { try { if (out != null) { out.close(); } } catch (IOException ex) { } try { bos.close(); } catch (IOException ex) { } } if (ptpSukBytes != null) { try { if (dataLength < (short) ptpSukBytes.length) { Util.arrayCopyNonAtomic(ptpSukBytes, (short) 0, apduBuffer, (short) 0, dataLength); } else { dataLength = Util.arrayCopyNonAtomic(ptpSukBytes, (short) 0, apduBuffer, (short) 0, (short) ptpSukBytes.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: This method contains non-standard Java Card methods. private void getMobileKey(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); } short dataLength = apdu.setOutgoing(); // Check if Le=0x00. if (dataLength != (short) 256) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } // Check if Mobile Key is initialized. if (!this.dataEncryption.isMobileKeyInit()) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } dataLength = this.dataEncryption.getMobileKey(apduBuffer, (short) 0); apdu.setOutgoingLength(dataLength); apdu.sendBytes((short) 0, dataLength); } /** * Handle Store Data command. * * @param apdu * the incoming <code>APDU</code> object */ private void storeData(APDU apdu) { byte[] apduBuffer = apdu.getBuffer(); byte p1 = apduBuffer[ISO7816.OFFSET_P1]; // Validate sequence counter in P2. if (apduBuffer[ISO7816.OFFSET_P2] != this.transientByteBuffer[Constants.TBB_OFFSET_SEQUENCE_NUMBER]) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } this.transientByteBuffer[Constants.TBB_OFFSET_SEQUENCE_NUMBER]++; short cdataLength = apdu.setIncomingAndReceive(); // 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) (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; } byte dgiHighByte, dgiLowByte; short dgi, dgiLength; short dgiOffset = ISO7816.OFFSET_CDATA; cdataLength += ISO7816.OFFSET_CDATA; while (dgiOffset < cdataLength) { // Get DGI. dgiHighByte = apduBuffer[dgiOffset++]; dgiLowByte = apduBuffer[dgiOffset++]; // Get DGI length, which is defined to be 1 byte. dgiLength = (short) (apduBuffer[dgiOffset++] & (short) 0x00FF); // 'dgiDataLength' is the actual length of DGI data. short dgiDataLength = (short) 0; boolean dgiDecrypted = false; if ((p1 & (byte) 0x60) == (byte) 0x60) { dgiDataLength = this.secureChannel.decryptData(apduBuffer, dgiOffset, dgiLength); dgiDecrypted = true; } else { dgiDataLength = dgiLength; } // Check if DGI is SFI record. if ((dgiHighByte >= (byte) 0x01) && (dgiHighByte <= (byte) 0x1E) && (dgiLowByte != (byte) 0x00)) { // Save SFI record. this.records.addSFIRecord(dgiHighByte, dgiLowByte, apduBuffer, dgiOffset, dgiDataLength); dgiOffset += dgiLength; continue; } /* Supported DGIs: * 0x8000 * 0x8201, 0x8202, 0x8203, 0x8204, 0x8205 * 0x9102 * 0xA002 * 0xA003 * 0xA004 * 0xB005 * 0xA006 * 0xA007 * 0xA009 * 0xA026 * 0xA027 * 0xA028 * 0xB003 * 0xB007 */ dgi = Util.makeShort(dgiHighByte, dgiLowByte); // Check if DGI is supported. switch (dgi) { case Constants.DGI_DES_KEYS: { // SKUdek encryption required. if (!dgiDecrypted) { if ((p1 & (byte) 0x60) != (byte) 0x00) { dgiDataLength = this.secureChannel.decryptData(apduBuffer, dgiOffset, dgiLength); } else { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } } if (dgiDataLength != (short) 16) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } this.mkAC.setKey(apduBuffer, dgiOffset); break; } case Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_PQ: case Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_DQ1: case Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_DP1: case Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_Q: case Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_P: { // SKUdek encryption required. if (!dgiDecrypted) { if ((p1 & (byte) 0x60) != (byte) 0x00) { dgiDataLength = this.secureChannel.decryptData(apduBuffer, dgiOffset, dgiLength); } else { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } } short keySize = (short) (dgiDataLength * (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 == Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_PQ) { // Build Card Profile. this.cardProfile.setIccPrivKeyCrtCoefficient(apduBuffer, dgiOffset, dgiDataLength); this.iccPrivKey.setPQ(apduBuffer, dgiOffset, dgiDataLength); } else if (dgi == Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_DQ1) { // Build Card Profile. this.cardProfile.setIccPrivKeyPrimeExponentQ(apduBuffer, dgiOffset, dgiDataLength); this.iccPrivKey.setDQ1(apduBuffer, dgiOffset, dgiDataLength); } else if (dgi == Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_DP1) { // Build Card Profile. this.cardProfile.setIccPrivKeyPrimeExponentP(apduBuffer, dgiOffset, dgiDataLength); this.iccPrivKey.setDP1(apduBuffer, dgiOffset, dgiDataLength); } else if (dgi == Constants.DGI_ICC_PRIV_KEY_CRT_CONSTANT_Q) { // Build Card Profile. this.cardProfile.setIccPrivKeyPrimeQ(apduBuffer, dgiOffset, dgiDataLength); this.iccPrivKey.setQ(apduBuffer, dgiOffset, dgiDataLength); } else { // Build Card Profile. this.cardProfile.setIccPrivKeyPrimeP(apduBuffer, dgiOffset, dgiDataLength); this.iccPrivKey.setP(apduBuffer, dgiOffset, dgiDataLength); } break; } case Constants.DGI_SELECT_RESPONSE_DATA: { // Build Card Profile. this.cardProfile.setTagA5Data(apduBuffer, dgiOffset, dgiDataLength); byte aidLength = JCSystem.getAID().getBytes(this.transientByteBuffer, Constants.TBB_OFFSET_PREV_PARTIAL_DGI_DATA); // Determine entire Select response length. short selectResponseLength = (short) (dgiDataLength + aidLength + (byte) 4); if (selectResponseLength > (short) 129) { selectResponseLength++; } // Allocate memory for entire Select response. this.selectResponse = new byte[selectResponseLength]; short selectResponseOffset = (short) 0; this.selectResponse[selectResponseOffset++] = Constants.TAG_FCI_TEMPLATE; if (selectResponseLength > (short) 129) { this.selectResponse[selectResponseOffset++] = (byte) 0x81; this.selectResponse[selectResponseOffset++] = (byte) (selectResponseLength - (byte) 3); } else { this.selectResponse[selectResponseOffset++] = (byte) (selectResponseLength - (byte) 2); } // Save DF Name in select response. this.selectResponse[selectResponseOffset++] = Constants.TAG_DF_NAME; this.selectResponse[selectResponseOffset++] = aidLength; selectResponseOffset = Util.arrayCopyNonAtomic(this.transientByteBuffer, Constants.TBB_OFFSET_PREV_PARTIAL_DGI_DATA, this.selectResponse, selectResponseOffset, aidLength); // Save FCI Proprietary Template in select response. Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.selectResponse, selectResponseOffset, dgiDataLength); break; } case Constants.DGI_DATA: { // Build Card Profile. this.cardProfile.setData(apduBuffer, dgiOffset); break; } case Constants.DGI_MAGSTRIPE_CVM_DATA: { // Build Card Profile. this.cardProfile.setMagstripeData(apduBuffer, dgiOffset); // For MPP Remote-SE Lite. // Magstripe CVM Issuer Options 1 1 // Card Issuer Action Code - Decline On PPMS 2 2 Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.personalizedPersistentByteBuffer, Constants.PPBB_OFFSET_MAGSTRIPE_CVM_ISSUER_OPTIONS, dgiDataLength); break; } case Constants.DGI_PUBLIC_KEY_MODULUS_LENGTH: { // Build Card Profile. this.cardProfile.setIccPubKeyModulusLength((short) (apduBuffer[dgiOffset] & (short) 0x00FF)); // For MPP Remote-SE Lite. // Length Of ICC Public Key Modulus 1 1 Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.personalizedPersistentByteBuffer, Constants.PPBB_OFFSET_ICC_PUB_KEY_MODULUS_LENGTH, dgiDataLength); break; } case Constants.DGI_GPO_RESPONSE_DATA_PAYMENT: { // Note that for 'A005' and 'B005', only values of the AIP and the AFL // are personalized, without TLV-coding. short aflLength = (short) (dgiDataLength - (byte) 2); // Build Card Profile. this.cardProfile.setAip(apduBuffer, dgiOffset); this.cardProfile.setAfl(apduBuffer, (short) (dgiOffset + (byte) 2), aflLength); break; } case Constants.DGI_ICC_DYNAMIC_NUMBER_MASTER_KEY: { // SKUdek encryption required. if (!dgiDecrypted) { if ((p1 & (byte) 0x60) != (byte) 0x00) { dgiDataLength = this.secureChannel.decryptData(apduBuffer, dgiOffset, dgiLength); } else { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } } if (dgiDataLength != (short) 16) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } this.mkIDN.setKey(apduBuffer, dgiOffset); break; } case Constants.DGI_LIMITS: { /* Previous Transaction History 1 1 ATC Counter Limit 2 2 AC Session Key Counter Limit 2 3 SMI Session Key Counter Limit 2 4 Bad Cryptogram Counter Limit 2 5 */ Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.personalizedPersistentByteBuffer, Constants.PPBB_OFFSET_PREVIOUS_TRANSACTION_HISTORY, (short) (1 + 2 + 2 + 2 + 2)); break; } case Constants.DGI_APPLICATION_LIFE_CYCLE_DATA: { Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.personalizedPersistentByteBuffer, Constants.PPBB_OFFSET_APPLICATION_LIFE_CYCLE_DATA, dgiDataLength); break; } case Constants.DGI_CARD_LAYOUT_DESCRIPTION_PART_1: { if (dgiDataLength > (short) 255) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } // Allocate memory. this.cardLayoutDescriptionPart1 = new byte[dgiDataLength]; // Save data. Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.cardLayoutDescriptionPart1, (short) 0, dgiDataLength); break; } case Constants.DGI_CARD_LAYOUT_DESCRIPTION_PART_2: { if (dgiDataLength > (short) 255) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } // Allocate memory. this.cardLayoutDescriptionPart2 = new byte[dgiDataLength]; // Save data. Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.cardLayoutDescriptionPart2, (short) 0, dgiDataLength); break; } case Constants.DGI_CARD_LAYOUT_DESCRIPTION_PART_3: { if (dgiDataLength > (short) 255) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } // Allocate memory. this.cardLayoutDescriptionPart3 = new byte[dgiDataLength]; // Save data. Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.cardLayoutDescriptionPart3, (short) 0, dgiDataLength); break; } case Constants.DGI_IVCVC3: { Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.personalizedPersistentByteBuffer, Constants.PPBB_OFFSET_IVCVC3_TRACK1, (short) (2 + 2)); break; } case Constants.DGI_PIN_IVCVC3: { // Build Card Profile. this.cardProfile.setPinIvCvc3(apduBuffer, dgiOffset); Util.arrayCopyNonAtomic(apduBuffer, dgiOffset, this.personalizedPersistentByteBuffer, Constants.PPBB_OFFSET_PIN_IVCVC3_TRACK1, (short) (2 + 2)); break; } case (short) 0x4000: { dgiDataLength += dgiOffset; while (dgiOffset < dgiDataLength) { short tag = Util.getShort(apduBuffer, dgiOffset); dgiOffset += (byte) 2; if (apduBuffer[dgiOffset++] != (byte) 1) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } // Build Card Profile. byte value = apduBuffer[dgiOffset++]; if (tag == (short) 0xDF30) { // Maximum Number of Live PTP_SUK this.cardProfile.setMaxNumberPtpSuk(value); } else if (tag == (short) 0xDF31) { // Minimum Threshold Number of Live PTP_SUK this.cardProfile.setMinThresholdNumberPtpSuk(value); } else { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } } break; } default: break; } dgiOffset += dgiLength; } } }