package de.persosim.simulator.platform;
import de.persosim.simulator.exception.ISO7816Exception;
import de.persosim.simulator.tlv.TlvValue;
import de.persosim.simulator.tlv.TlvValuePlain;
import de.persosim.simulator.utils.Utils;
/**
* @author slutters
*
*/
//TODO cleanup
//consistently throw Exceptions for unsupported inputs
public abstract class Iso7816Lib implements Iso7816 {
public static final short MASK_BYTE_TO_SHORT = (short) 0xFF;
public static final int MASK_SHORT_TO_INT = 0xFFFF;
public static final byte ISO_CASE_1 = 1;
public static final byte ISO_CASE_2_SHORT_LE = 2;
public static final byte ISO_CASE_3_SHORT_LC = 3;
public static final byte ISO_CASE_4_SHORT_LC_SHORT_LE = 4;
public static final byte ISO_CASE_2_EXTENDED_L_E = 10;
public static final byte ISO_CASE_3_EXTENDED_L_C = 15;
public static final byte ISO_CASE_4_EXTENDED_L_C_EXTENDED_L_E = 20;
/*----------------------------------------------------------------*/
/*----------------------------------------------------------------*/
/**
* Returns the ISO format according to ISO 7816-4, 5.1.1 Class byte
* @param apdu the APDU
* @return the ISO format according to ISO 7816-4, 5.1.1 Class byte
*/
public static byte getISOFormat(byte[] apdu) {
return getISOFormat(getClassByte(apdu));
}
/**
* Returns the ISO format according to ISO 7816-4, 5.1.1 Class byte
* @param cla the class byte
* @return the ISO format according to ISO 7816-4, 5.1.1 Class byte
*/
public static byte getISOFormat(byte cla) {
if (((byte) (cla & (byte) 0xE0) == (byte) 0x00)) {
return ISO_FORMAT_FIRSTINTERINDUSTRY;
}
if (((byte) (cla & (byte) 0xC0) == (byte) 0x40)) {
return ISO_FORMAT_FURTHERINTERINDUSTRY;
}
if (((byte) (cla & (byte) 0xE0) == (byte) 0x20)) {
return ISO_FORMAT_INTERINDUSTRY_RESERVED;
}
if ((cla == (byte) 0xFF)) {
return ISO_FORMAT_INVALID;
}
return ISO_FORMAT_PROPRIETARY;
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents an ISO inter-industry APDU
* @param isoFormat byte representing ISO format
* @return whether the provided byte represents an ISO inter-industry APDU
*/
public static boolean isISOInterindustryCLA(byte isoFormat) {
return isoFormat >= ISO_FORMAT_FIRSTINTERINDUSTRY;
}
/**
* Returns whether the provided APDU represents an ISO inter-industry APDU
* @param apdu the APDU
* @return whether the provided APDU represents an ISO inter-industry APDU
*/
public static boolean isISOInterindustry(byte[] apdu) {
return isISOInterindustryCLA(getISOFormat(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents a defined ISO inter-industry APDU
* @param isoFormat byte representing ISO format
* @return whether the provided byte represents a defined ISO inter-industry APDU
*/
public static boolean isISOInterindustryDefinedCLA(byte isoFormat) {
return ((isoFormat == ISO_FORMAT_FIRSTINTERINDUSTRY) || (isoFormat == ISO_FORMAT_FURTHERINTERINDUSTRY));
}
/**
* Returns whether the provided APDU represents a defined ISO inter-industry APDU
* @param apdu the APDU
* @return whether the provided APDU represents a defined ISO inter-industry APDU
*/
public static boolean isISOInterindustryDefinedCLA(byte[] apdu) {
return isISOInterindustryDefinedCLA(getISOFormat(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents a first ISO inter-industry APDU
* @param isoFormat byte representing ISO format
* @return whether the provided byte represents a first ISO inter-industry APDU
*/
public static boolean isISOFirstInterindustryCLA(byte isoFormat) {
return isoFormat == ISO_FORMAT_FIRSTINTERINDUSTRY;
}
/**
* Returns whether the provided APDU represents a first ISO inter-industry APDU
* @param apdu the APDU
* @return whether the provided APDU represents a first ISO inter-industry APDU
*/
public static boolean isISOFirstInterindustryCLA(byte[] apdu) {
return isISOFirstInterindustryCLA(getISOFormat(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents a further ISO inter-industry APDU
* @param isoFormat byte representing ISO format
* @return whether the provided byte represents a further ISO inter-industry APDU
*/
public static boolean isISOFurtherInterindustryCLA(byte isoFormat) {
return isoFormat == ISO_FORMAT_FURTHERINTERINDUSTRY;
}
/**
* Returns whether the provided APDU represents a further ISO inter-industry APDU
* @param apdu the APDU
* @return whether the provided APDU represents a further ISO inter-industry APDU
*/
public static boolean isISOFurtherInterindustryCLA(byte[] apdu) {
return isISOFurtherInterindustryCLA(getISOFormat(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents a reserved ISO inter-industry APDU
* @param isoFOrmat byte representing ISO format
* @return whether the provided byte represents a reserved ISO inter-industry APDU
*/
public static boolean isISOInterindustryReservedCLA(byte isoFormat) {
return isoFormat == ISO_FORMAT_INTERINDUSTRY_RESERVED;
}
/**
* Returns whether the provided APDU represents a reserved ISO inter-industry APDU
* @param apdu the APDU
* @return whether the provided APDU represents a reserved ISO inter-industry APDU
*/
public static boolean isISOInterindustryReservedCLA(byte[] apdu) {
return isISOInterindustryReservedCLA(getISOFormat(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents a proprietary APDU
* @param isoFormat byte representing ISO format
* @return whether the provided byte represents a proprietary APDU
*/
public static boolean isProprietaryCLA(byte isoFormat) {
return isoFormat == ISO_FORMAT_PROPRIETARY;
}
/**
* Returns whether the provided APDU represents a proprietary APDU
* @param apdu the APDU
* @return whether the provided APDU represents a proprietary APDU
*/
public static boolean isProprietaryCLA(byte[] apdu) {
return isProprietaryCLA(getISOFormat(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided ISO format is invalid
* @param isoFormat the ISO format
* @return whether the provided ISO format is invalid
*/
public static boolean isInvalidCLA(byte isoFormat) {
return isoFormat == ISO_FORMAT_INVALID;
}
/**
* Returns whether the provided APDU's ISO format is invalid
* @param apdu the APDU
* @return whether the provided APDU's ISO format is invalid
*/
public static boolean isInvalidCLA(byte[] apdu) {
return isInvalidCLA(getISOFormat(apdu));
}
/*----------------------------------------------------------------*/
/*----------------------------------------------------------------*/
/**
* Returns the extended ISO case no of the provided APDU
* If short L_C and/or L_E fields are found, the return value will represent the expected ISO case no.
* If extended L_C and/or L_E fields are found, the return value will represent the expected ISO case no multiplied by factor 5.
* Factor 5 was only chosen as it is the smallest factor for multiplication that will never yield a result indistinguishable from short L_C or L_E APDUs.
* In order to prevent confusion this method is kept private and only accessible through delegate methods.
*
* return value --> ISO case
* Byte.Min_VALUE --> Invalid ISO case --> Exception
* 1 --> ISO case 1
* 2 --> ISO case 2, short L_E
* 3 --> ISO case 3, short L_C
* 4 --> ISO case 4, short L_C, short L_E
* 10 --> ISO case 2, extended L_E
* 15 --> ISO case 3, extended L_C
* 20 --> ISO case 4, extended L_C, extended L_E
* @param apdu the APDU
* @return the extended ISO case no of the provided APDU
*/
public static byte getISOcaseExtended(byte[] apdu) {
int indicatedLength;
int offsetLc;
short lc;
offsetLc = -1;
if(apdu.length >= APDU_MINIMUM_LENGTH_ISO_CASE1) {
if(apdu.length == APDU_MINIMUM_LENGTH_ISO_CASE1) {
/* ISO case 1 */
return ISO_CASE_1;
}
if(apdu.length == APDU_MINIMUM_LENGTH_ISO_CASE2) {
/* ISO case 2 */
/* short Le */
return ISO_CASE_2_SHORT_LE;
}
if((apdu.length == APDU_MAXIMUM_LENGTH_ISO_CASE2) && (apdu[OFFSET_LE_ISO_CASE_2] == (byte) 0x00)) {
/* ISO case 2 */
/* extended Le */
return ISO_CASE_2_EXTENDED_L_E;
}
if(apdu.length >= APDU_MINIMUM_LENGTH_ISO_CASE3) {
offsetLc = OFFSET_LC;
lc = Utils.maskUnsignedByteToShort(apdu[offsetLc]);
if(lc == (short) 0x0000) {
/* extended Lc */
/* ISO case 3 or 4 if at all */
if(apdu.length >= APDU_MINIMUM_LENGTH_EXTENDED_ISO_CASE3) {
lc = Utils.concatenate(apdu[OFFSET_LC + 1], apdu[OFFSET_LC + 2]);
/* this is the total length of the APDU */
indicatedLength = Utils.maskUnsignedByteToInt(APDU_MINIMUM_LENGTH_EXTENDED_ISO_CASE3) + Utils.maskUnsignedShortToInt(lc) -1;
if(lc != (short) 0x0000) {
if(apdu.length == indicatedLength) {
// ISO case 3
return ISO_CASE_3_EXTENDED_L_C;
} else{
if(apdu.length == (indicatedLength + 2)) { /* +2 for extended Le field with Lc present*/
/* ISO case 4 */
/* extended Le */
return ISO_CASE_4_EXTENDED_L_C_EXTENDED_L_E;
} else{
/* Intentionally left blank */
ISO7816Exception.throwIt(Iso7816.SW_6700_WRONG_LENGTH, "the APDU appears to be ISO case 4 with extended L_C but indicated length does not match actual length");
}
}
}
}
} else{
/* short Lc */
indicatedLength = Utils.maskUnsignedByteToInt(APDU_MINIMUM_LENGTH_ISO_CASE3) + Utils.maskUnsignedShortToInt(lc) -1;
if(apdu.length == indicatedLength) {
/* ISO case 3 */
return ISO_CASE_3_SHORT_LC;
} else{
if(apdu.length == (indicatedLength + 1)) {
/* ISO case 4 */
/* short Le */
return ISO_CASE_4_SHORT_LC_SHORT_LE;
} else{
/* Intentionally left blank */
ISO7816Exception.throwIt(Iso7816.SW_6700_WRONG_LENGTH, "the APDU appears to be ISO case 4 with short L_C but indicated length does not match actual length");
}
}
}
}
}
// ERROR
ISO7816Exception.throwIt(Iso7816.SW_6700_WRONG_LENGTH, "the APDU can not be related to any ISO case");
return Byte.MIN_VALUE;
}
/*----------------------------------------------------------------*/
/*----------------------------------------------------------------*/
/**
* Returns the ISO case according to ISO 7816-4
* @param isoCaseExtended isoCase according to getISOcaseExtended(APDU):byte
* @return the ISO case according to ISO 7816-4
*/
public static byte getISOcase(byte isoCaseExtended) {
if(isoCaseExtended <= ISO_CASE_4_SHORT_LC_SHORT_LE) {
return isoCaseExtended;
}
return (byte) (isoCaseExtended / 5);
}
/**
* Returns the ISO case according to ISO 7816-4
* @param apdu the APDU
* @return the ISO case according to ISO 7816-4
*/
public static byte getISOcase(byte[] apdu) {
return getISOcase(getISOcaseExtended(apdu));
}
public static boolean isExtendedLengthLCLE(byte isoCaseExtended) {
return (isoCaseExtended > ISO_CASE_4);
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents an SIO case 1 APDU
* @param isoCaseExtended isoCase according to getISOcaseExtended(APDU):byte
* @return whether the provided byte represents an SIO case 1 APDU
*/
private static boolean isIsoCase1(byte isoCaseExtended) {
return (isoCaseExtended == ISO_CASE_1);
}
/**
* Returns whether the APDU is an SIO case 1 APDU
* @param apdu the APDU
* @return whether the APDU is an SIO case 1 APDU
*/
public static boolean isIsoCase1(byte[] apdu) {
return isIsoCase1(getISOcaseExtended(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents an SIO case 2 APDU
* @param isoCaseExtended isoCase according to getISOcaseExtended(APDU):byte
* @return whether the provided byte represents an SIO case 2 APDU
*/
private static boolean isIsoCase2(byte isoCaseExtended) {
return ((isoCaseExtended == ISO_CASE_2_SHORT_LE) || (isoCaseExtended == ISO_CASE_2_EXTENDED_L_E));
}
/**
* Returns whether the APDU is an SIO case 2 APDU
* @param apdu the APDU
* @return whether the APDU is an SIO case 2 APDU
*/
public static boolean isIsoCase2(byte[] apdu) {
return isIsoCase2(getISOcaseExtended(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents an SIO case 3 APDU
* @param isoCaseExtended isoCase according to getISOcaseExtended(APDU):byte
* @return whether the provided byte represents an SIO case 3 APDU
*/
private static boolean isIsoCase3(byte isoCaseExtended) {
return ((isoCaseExtended == ISO_CASE_3_SHORT_LC) || (isoCaseExtended == ISO_CASE_3_EXTENDED_L_C));
}
/**
* Returns whether the APDU is an SIO case 3 APDU
* @param apdu the APDU
* @return whether the APDU is an SIO case 3 APDU
*/
public static boolean isIsoCase3(byte[] apdu) {
return isIsoCase3(getISOcaseExtended(apdu));
}
/*----------------------------------------------------------------*/
/**
* Returns whether the provided byte represents an SIO case 4 APDU
* @param isoCaseExtended isoCase according to getISOcaseExtended(APDU):byte
* @return whether the provided byte represents an SIO case 4 APDU
*/
private static boolean isIsoCase4(byte isoCaseExtended) {
return ((isoCaseExtended == ISO_CASE_4_SHORT_LC_SHORT_LE) || (isoCaseExtended == ISO_CASE_4_EXTENDED_L_C_EXTENDED_L_E));
}
/**
* Returns whether the APDU is an SIO case 4 APDU
* @param apdu the APDU
* @return whether the APDU is an SIO case 4 APDU
*/
public static boolean isIsoCase4(byte[] apdu) {
return isIsoCase4(getISOcaseExtended(apdu));
}
/*----------------------------------------------------------------*/
/*----------------------------------------------------------------*/
/**
* Returns whether the APDU uses extended length L_C and/or L_E field(s).
* NOTE: ISO 7816-4 does not allow for mixed L_C/L_E fields
* @param apdu the APDU
* @return whether the APDU uses extended length L_C and/or L_E field(s)
*/
public static boolean isExtendedLengthLCLE(byte[] apdu) {
return (getISOcaseExtended(apdu) > ISO_CASE_4);
}
/*----------------------------------------------------------------*/
/*----------------------------------------------------------------*/
/*----------------------------------------------------------------*/
/**
* Returns whether the APDU's CLA byte indicates command chaining
* @param apdu the APDU
* @return whether the APDU's CLA byte indicates command chaining
*/
public static boolean isCommandChainingCLA(byte[] apdu) {
byte cla = getClassByte(apdu);
return ((byte) (cla & (byte) 0x10) == (byte) 0x10);
}
/*----------------------------------------------------------------*/
/**
* Returns the kind of secure messaging the provided APDU's CLA byte indicates
* according to ISO 7816-4:
* <ul>
* <li>0 0 - No SM or no indication</li>
* <li>0 1 - Proprietary SM format </li>
* <li>1 0 - SM according to 6, command header not processed according to 6.2.3.1</li>
* <li>1 1 - SM according to 6, command header authenticated according to 6.2.3.1</li>
* </ul>
* <p/>
* @param apdu the APDU
* @return the kind of secure messaging the provided APDU's CLA byte indicates one of
* {@link Iso7816#SM_OFF_OR_NO_INDICATION},<br/>
* {@link Iso7816#SM_PROPRIETARY},<br/>
* {@link Iso7816#SM_COMMAND_HEADER_NOT_PROCESSED},<br/>
* {@link Iso7816#SM_COMMAND_HEADER_AUTHENTICATED},
*/
public static byte getSecureMessagingStatus(byte[] apdu) {
byte cla = apdu[OFFSET_CLA];
byte isoFormat = getISOFormat(apdu);
if(isoFormat == ISO_FORMAT_FIRSTINTERINDUSTRY) {
cla = (byte) (cla & (byte) 0x0C);
return (byte) (cla >> 2);
} else{
if(isoFormat == ISO_FORMAT_FURTHERINTERINDUSTRY) {
cla = (byte) (cla & (byte) 0x20);
return (byte) (cla >> 4);
} else{
if(isoFormat >= ISO_FORMAT_PROPRIETARY) {
ISO7816Exception.throwIt(Iso7816.SW_6E00_CLA_NOT_SUPPORTED);
} else{
// exception is thrown implicitly by called function.
}
return Byte.MIN_VALUE;
}
}
}
/**
* Returns whether the provided APDU matches the provided secure messaging indication
* according to ISO 7816-4:
* 0 0 - No SM or no indication
* 0 1 - Proprietary SM format
* 1 0 - SM according to 6, command header not processed according to 6.2.3.1
* 1 1 - SM according to 6, command header authenticated according to 6.2.3.1
* @param apdu the APDU
* @param specification some secure messaging status
* @return whether the provided APDU matches the provided secure messaging status
*/
public static boolean isSecureMessagingCLA(byte[] apdu, byte specification) {
return specification == getSecureMessagingStatus(apdu);
}
/**
* Change the SM encoding in class byte according to its iso format.
*
* Only works for further interindustry class bytes.
*
*
* Otherwise throws a RuntimeException.
*
* Supported :
* {@link Iso7816#SM_OFF_OR_NO_INDICATION}
* {@link Iso7816#SM_PROPRIETARY}
* {@link Iso7816#SM_COMMAND_HEADER_NOT_PROCESSED}
* {@link Iso7816#SM_COMMAND_HEADER_AUTHENTICATED}
* @param cla class byte to modify
* @return the modified class byte
*/
public static byte setSecureMessagingStatus(byte cla, byte smStatus) {
switch (getISOFormat((cla))) {
case ISO_FORMAT_FIRSTINTERINDUSTRY:
switch (smStatus) {
case SM_OFF_OR_NO_INDICATION: //fall through
case SM_PROPRIETARY: //fall through
case SM_COMMAND_HEADER_NOT_PROCESSED: //fall through
case SM_COMMAND_HEADER_AUTHENTICATED:
// keep command chaining and channels, add sm status
return (byte) ((cla & 0x13) | (smStatus << 2));
default:
throw new IllegalArgumentException(
"Unsupported status for first interindustry CLA");
}
case ISO_FORMAT_FURTHERINTERINDUSTRY:
switch (smStatus) {
case SM_OFF_OR_NO_INDICATION:
return (byte) ((cla & 0x5F));
case SM_COMMAND_HEADER_NOT_PROCESSED:
return (byte) ((cla & 0x5F) | 0x20);
default:
throw new IllegalArgumentException(
"Unsupported status for further interindustry CLA");
}
default:
throw new IllegalArgumentException("Format of CLA not supported");
}
}
/*----------------------------------------------------------------*/
/*----------------------------------------------------------------*/
/**
* Returns the channel indicated by the CLA byte of the provided APDU
* @param apdu the APDU
* @param isoFormat the ISO format of the provided APDU
* @return the channel indicated by the CLA byte of the provided APDU
*/
public static byte getChannel(byte[] apdu, byte isoFormat) {
byte cla, channel;
cla = apdu[OFFSET_CLA];
if(isoFormat == ISO_FORMAT_FIRSTINTERINDUSTRY) {
cla = (byte) (cla & (byte) 0x03);
channel = cla;
} else{
if(isoFormat == ISO_FORMAT_FURTHERINTERINDUSTRY) {
cla = (byte) (cla & (byte) 0x0F);
cla = (byte) (cla << 2);
channel = (byte) (cla | (byte) 0x03);
} else{
// exception is thrown implicitly by called function.
channel = Byte.MIN_VALUE;
}
}
return channel;
}
/**
* Returns the channel indicated by the CLA byte of the provided APDU
* @param apdu the APDU
* @return the channel indicated by the CLA byte of the provided APDU
*/
public static byte getChannel(byte[] apdu) {
return getChannel(apdu, getISOFormat(apdu));
}
/**
* Returns whether the provided APDU uses the default channel '0'
* @param apdu the APDU
* @param isoFormat the ISO format of the rpovided APDU
* @return whether the provided APDU uses the default channel '0'
*/
public static boolean usesDefaultChannel(byte[] apdu, byte isoFormat) {
return getChannel(apdu, isoFormat) == CH_DEFAULT;
}
/**
* Returns whether the provided APDU uses the default channel '0'
* @param apdu the APDU
* @return whether the provided APDU uses the default channel '0'
*/
public static boolean usesDefaultChannel(byte[] apdu) {
return getChannel(apdu) == CH_DEFAULT;
}
/*----------------------------------------------------------------*/
/**
* Returns the class byte (CLA) of the provided APDU
* @param apdu the APDU
* @return the instruction byte of the provided APDU
*/
public static byte getClassByte(byte[] apdu) {
return apdu[OFFSET_CLA];
}
/*----------------------------------------------------------------*/
/**
* Returns the instruction byte (INS) of the provided APDU
* @param apdu the APDU
* @return the instruction byte of the provided APDU
*/
public static byte getInstructionByte(byte[] apdu) {
return apdu[OFFSET_INS];
}
/*----------------------------------------------------------------*/
/**
* Returns whether the instruction byte indicates an invalid ISO7816-4, 5.1.2 Instruction byte
* @param ins the instruction byte
* @return whether the instruction byte indicates an invalid ISO7816-4, 5.1.2 Instruction byte
*/
public static boolean isInvalidInstruction(byte ins) {
ins &= (byte) 0xF0;
return (ins == (byte) 0x60) || (ins == (byte) 0x90);
}
/**
* Returns whether the provided APDU indicates an invalid ISO7816-4, 5.1.2 Instruction byte
* @param apdu the APDU
* @return whether the provided APDU indicates an invalid ISO7816-4, 5.1.2 Instruction byte
*/
public static boolean isInvalidInstruction(byte[] apdu) {
return isInvalidInstruction(getInstructionByte(apdu));
}
/**
* Returns whether the provided instruction byte is known according to ISO7816-4
* @param ins the instruction byte
* @return whether the provided instruction byte is known according to ISO7816-4
*/
public static boolean isKnowInstruction(byte ins) {
for(int i=0; i<Iso7816.INSTRUCTIONSET.length; i++) {
if(Iso7816.INSTRUCTIONSET[i] == ins) {
return true;
}
}
return false;
}
/**
* Returns whether the provided APDU holds an instruction byte known according to ISO7816-4
* @param apdu the APDU
* @return whether the provided APDU holds an instruction byte known according to ISO7816-4
*/
public static boolean isKnowInstruction(byte[] apdu) {
return isKnowInstruction(getInstructionByte(apdu));
}
/*--------------------------------------------------------------------------------*/
/**
* Returns the P1 byte of the provided APDU
* @param apdu the APDU
* @return the P1 byte of the provided APDU
*/
public static byte getP1(byte[] apdu) {
return apdu[OFFSET_P1];
}
/**
* Returns the P2 byte of the provided APDU
* @param apdu the APDU
* @return the P2 byte of the provided APDU
*/
public static byte getP2(byte[] apdu) {
return apdu[OFFSET_P2];
}
/**
* Returns the P1P2 bytes of the provided APDU
* @param apdu the APDU
* @return the P1P2 bytes of the provided APDU
*/
public static short getP1P2(byte[] apdu) {
return Utils.concatenate(getP1(apdu), getP2(apdu));
}
/*--------------------------------------------------------------------------------*/
/**
* Returns value N_c as encoded in L_c field of the provided APDU
* @param apdu the APDU
* @param isoCase the ISO case of the provided APDU
* @return the N_c encoded in the provided APDU
*/
public static short getNc(byte[] apdu) {
if(getISOcase(apdu) < ISO_CASE_3) {
return 0;
}
if(isExtendedLengthLCLE(apdu)) {
return Utils.concatenate(apdu[OFFSET_LC + 1], apdu[OFFSET_LC + 2]);
} else{
return Utils.maskUnsignedByteToShort(apdu[OFFSET_LC]);
}
}
/**
* Returns value N_e as encoded in L_e field of the provided APDU
* @param apdu the APDU
* @param isoCase the ISO case of the provided APDU
* @param isExtendedLengthLCLE whether the APDU uses extended length L_C/L_E fields
* @return the N_e encoded in the provided APDU
*/
public static int getNe(byte[] apdu) {
byte isoCase = getISOcase(apdu);
if((isoCase == ISO_CASE_2) || (isoCase == ISO_CASE_4)) {
int apduLength = apdu.length;
if(isExtendedLengthLCLE(apdu)) {
short retVal = Utils.concatenate(apdu[apduLength - 2], apdu[apduLength - 1]);
if (retVal <= 0) {
return 65536 + retVal;
} else {
return retVal;
}
} else{
if (apdu[apduLength - 1] == 0) {
return 256;
} else {
return Utils.maskUnsignedByteToShort(apdu[apduLength - 1]);
}
}
} else{
//L_e absent, thus N_e is zero
return 0;
}
}
/*--------------------------------------------------------------------------------*/
/**
* Returns the offset of the L_C field of any APDU
* @param isoCase the ISO case of the APDU
* @return the offset of the L_C field of any APDU
*/
public static short getOffsetLc(byte isoCase) {
if((isoCase == Iso7816.ISO_CASE_3) || (isoCase == Iso7816.ISO_CASE_4)) {
return Iso7816.OFFSET_LC;
} else{
ISO7816Exception.throwIt(SW_6700_WRONG_LENGTH);
return Short.MIN_VALUE;
}
}
/**
* Returns the offset of the L_C field of the provided APDU
* @param apdu the APDU
* @return the offset of the L_C field of the provided APDU
*/
public static short getOffsetLc(byte[] apdu) {
return getOffsetLc(getISOcase(apdu));
}
/**
* Returns the offset of the data field of any APDU
* @param isoCase the ISO case of the APDU
* @param isExtendedLengthLCLE whether the APDU uses extended length L_C/L_E fields
* @return the offset of the data field of any APDU
*/
public static short getOffsetData(byte isoCase, boolean isExtendedLengthLCLE) {
if((isoCase < ISO_CASE_1) || (isoCase > ISO_CASE_4)) {
throw new IllegalArgumentException(isoCase + " is no valid ISO case");
}
if((isoCase == Iso7816.ISO_CASE_3) || (isoCase == Iso7816.ISO_CASE_4)) {
if(isExtendedLengthLCLE) {
return Iso7816.OFFSET_CDATA_EXTENDED_LC;
} else{
return Iso7816.OFFSET_CDATA_SHORT_LC;
}
} else{
ISO7816Exception.throwIt(SW_6700_WRONG_LENGTH);
return Short.MIN_VALUE;
}
}
/**
* Returns the offset of the data field of the provided APDU
* @param apdu the APDU
* @return the offset of the data field of the provided APDU
*/
public static short getOffsetData(byte[] apdu) {
return getOffsetData(getISOcase(apdu), isExtendedLengthLCLE(apdu));
}
/**
* Returns the offset of the L_E field of an APDU
* @param apduLength the length of the APDU in bytes
* @param isoCase the ISO case of the APDU
* @param isExtendedLengthLCLE whether the APDU uses extended length L_C/L_E fields
* @return the offset of the L_E field of an APDU
*/
public static short getOffsetLe(int apduLength, byte isoCase, boolean isExtendedLengthLCLE) {
if((isoCase == Iso7816.ISO_CASE_2) || (isoCase == Iso7816.ISO_CASE_4)) {
if(isExtendedLengthLCLE) {
return (short) (apduLength - 2);
} else{
return (short) (apduLength - 1);
}
} else{
ISO7816Exception.throwIt(SW_6700_WRONG_LENGTH);
return Short.MIN_VALUE;
}
}
/**
* Returns the offset of the L_E field of the provided APDU
* @param apdu the APDU
* @return the offset of the L_E field of the provided APDU
*/
public static int getOffsetLe(byte[] apdu) {
return getOffsetLe(apdu.length, getISOcase(apdu), isExtendedLengthLCLE(apdu));
}
/*--------------------------------------------------------------------------------*/
/**
* Returns whether the provided status word is reporting an error, i.e.
* statusWord = 0x9000 or 0x6100 <= status word <= 0x61FF
* @param statusWord
* @return
*/
public static boolean isReportingNormalProcessing(short statusWord) {
return (statusWord == SW_9000_NO_ERROR) || ((statusWord >= 24832) && (statusWord <= 25087));
}
/**
* Returns whether the provided status word is reporting an error, i.e.
* 0x6400 <= status word <= 0x6FFF
* @param statusWord the status word to be checked
* @return whether the provided status word is reporting an error
*/
public static boolean isReportingError(short statusWord) {
return (statusWord >= 25600) && (statusWord <= 458751);
}
/**
* Returns whether the provided status word is reporting a warning, i.e.
* 0x6200 <= status word <= 0x63FF
* @param statusWord the status word to be checked
* @return whether the provided status word is reporting a warning
*/
public static boolean isReportingWarning(short statusWord) {
return (statusWord >= 25088) && (statusWord <= 25599);
}
/**
* Returns whether the provided status word is invalid.
* Due to specifications in ISO/IEC 7816-3, any value different
* from '6XXX' and '9XXX' is invalid; any value '60XX' is also invalid.
* @param statusWord the status word to be checked
* @return whether the provided status word is invalid
*/
public static boolean isInvalidStatusWord(short statusWord) {
short swcopy;
swcopy = statusWord;
swcopy &= (short) 0xFF00;
if(swcopy == (short) 0x6000) {
return false;
}
swcopy &= (short) 0xF000;
return !((swcopy == (short) 0x6000) || (swcopy == (short) 0x9000));
}
/**
* Returns whether the provided status word is interindustry.
* The values '61XX', '62XX', '63XX', '64XX', '65XX', '66XX',
* '68XX', '69XX', '6AXX' and '6CXX' are interindustry.
* The values '6700', '6B00', '6D00', '6E00', '6F00' and '9000'
* are also interindustry.
* @param statusWord the status word to be checked
* @return whether the provided status word is interindustry
*/
public static boolean isInterIndustryStatusWord(short statusWord) {
if((statusWord >= 24832) && (statusWord <= 26367)) {return true;}
if((statusWord >= 26880) && (statusWord <= 27135)) {return true;}
if((statusWord >= 271362) && (statusWord <= 27391)) {return true;}
if((statusWord >= 27648) && (statusWord <= 27903)) {return true;}
if((statusWord == SW_6700_WRONG_LENGTH) || (statusWord == SW_6B00_WRONG_P1P2)
|| (statusWord == SW_6D00_INS_NOT_SUPPORTED) || (statusWord == SW_6E00_CLA_NOT_SUPPORTED)
|| (statusWord == SW_6F00_UNKNOWN) || (statusWord == SW_9000_NO_ERROR))
{
return true;
}
return false;
}
/**
* Returns whether the provided status word is proprietary.
* Due to specifications in ISO/IEC 7816-3, the values '67XX',
* '6BXX', '6DXX', '6EXX', '6FXX' and '9XXX' are proprietary,
* except the values '6700', '6B00', '6D00', '6E00', '6F00' and
* '9000' that are interindustry.
* @param statusWord the status word to be checked
* @return whether the provided status word is proprietary.
*/
public static boolean isProprietaryStatusWord(short statusWord) {
if((statusWord == SW_6700_WRONG_LENGTH) || (statusWord == SW_6B00_WRONG_P1P2)
|| (statusWord == SW_6D00_INS_NOT_SUPPORTED) || (statusWord == SW_6E00_CLA_NOT_SUPPORTED)
|| (statusWord == SW_6F00_UNKNOWN) || (statusWord == SW_9000_NO_ERROR))
{
return false;
}
short swcopy;
swcopy = statusWord;
swcopy &= (short) 0xFF00;
if((swcopy == SW_6700_WRONG_LENGTH) || (swcopy == SW_6B00_WRONG_P1P2)
|| (swcopy == SW_6D00_INS_NOT_SUPPORTED) || (swcopy == SW_6E00_CLA_NOT_SUPPORTED)
|| (swcopy == SW_6F00_UNKNOWN))
{
return true;
}
swcopy &= (short) 0xF000;
if(swcopy == (short) 9000)
{
return true;
}
return false;
}
/**
* Returns whether the provided status word allows for return data to be sent with the response APDU.
* Return data is allowed if the status word is valid and does not indicate an error.
* @param statusWord the status word to be checked
* @return whether the provided status word allows for return data to be sent with the response APDU.
*/
public static boolean statusWordAllowsReturnData(short statusWord) {
return isReportingNormalProcessing(statusWord) || isReportingWarning(statusWord);
}
/*--------------------------------------------------------------------------------*/
/**
* Returns a TLV structure of the APDU's command data field.
* @param apdu the APDU
* @return a TLV structure of the APDU's command data field.
*/
public static TlvValue getCommandData(byte[] apdu) {
short offsetData = getOffsetData(apdu);
short nc = getNc(apdu);
return new TlvValuePlain(apdu, offsetData, offsetData + nc);
}
/**
* Returns whether the APDU's header indicates the command data field to encode TLV.
* @param apdu the APDU.
* @param ins the instruction byte.
* @param p1 the P1 byte.
* @param p2 the P2 byte.
* @return whether the APDU's header indicates the command data field to encode TLV.
*/
public static boolean commandDataEncodesTLV(byte[] apdu, byte ins, byte p1, byte p2) {
/* if bit 1 of INS byte is set to one (c.f. ISO7816-4 5.1.2 Instruction byte) */
if(((byte) (ins & (byte) 0x01)) == (byte) 0x01) {
return true;
}
/* or if INS byte != 0x04, i.e. "SELECT" */
if((ins == (byte) 0xA4) && (p1 == (byte) 0x04)) {
return false;
}
/* if in doubt return "true" as default value */
return true;
}
/**
* Returns a textual representation of the Secure Messaging status.
* @param sm the SM byte.
* @return a textual representation of the Secure Messaging status.
*/
public static String getSMAsString(byte sm) {
switch (sm) {
case SM_OFF_OR_NO_INDICATION:
return SM_OFF_OR_NO_INDICATION_STRING;
case SM_PROPRIETARY:
return SM_PROPRIETARY_STRING;
case SM_COMMAND_HEADER_NOT_PROCESSED:
return SM_COMMAND_HEADER_NOT_PROCESSED_STRING;
case SM_COMMAND_HEADER_AUTHENTICATED:
return SM_COMMAND_HEADER_AUTHENTICATED_STRING;
default:
/* Intentionally left blank */
return "";
}
}
/*----------------------------------------------------------------*/
/*---------------------------- BER-TLV ---------------------------*/
/*----------------------------------------------------------------*/
public static byte[] getTLVLength(int dataLengthInBytes) {
if(dataLengthInBytes < 0) {throw new IllegalArgumentException("data length in bytes must be greater or equal 0");}
byte[] lengthField, length;
length = Utils.removeLeadingZeroBytes(Utils.toUnsignedByteArray(dataLengthInBytes));
if(length.length > 4) {ISO7816Exception.throwIt(SW_6A85_NC_INCONSISTENT_WITH_TLV_STRUCTURE, "data too long");}
if(dataLengthInBytes <= 127) {
lengthField = new byte[1];
lengthField[0] = (byte) dataLengthInBytes;
} else{
lengthField = new byte[length.length + 1];
lengthField[0] = (byte) 0x8F;
lengthField[0] = (byte) (lengthField[0] & (byte) (((byte) lengthField.length) & ((byte) 0x0F)));
System.arraycopy(length, 0, lengthField, 1, length.length);
}
return lengthField;
}
}