/****************************************************************************
* Copyright (C) 2012 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.common.apdu.common;
import iso.std.iso_iec._24727.tech.schema.InputAPDUInfoType;
import iso.std.iso_iec._24727.tech.schema.Transmit;
import iso.std.iso_iec._24727.tech.schema.TransmitResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.openecard.common.WSHelper;
import org.openecard.common.WSHelper.WSException;
import org.openecard.common.apdu.exception.APDUException;
import org.openecard.common.interfaces.Dispatcher;
import org.openecard.common.util.ByteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements a command APDU.
* See ISO/IEC 7816-4 Section 5.1.
*
* @author Moritz Horsch <horsch@cdc.informatik.tu-darmstadt.de>
*/
public class CardCommandAPDU extends CardAPDU {
private static final Logger logger = LoggerFactory.getLogger(CardCommandAPDU.class);
private byte[] header = new byte[4];
private int le = -1;
private int lc = -1;
/**
* Creates a new command APDU.
*/
protected CardCommandAPDU() {
}
/**
* Creates a new command APDU.
*
* @param commandAPDU APDU
*/
public CardCommandAPDU(byte[] commandAPDU) {
System.arraycopy(commandAPDU, 0, header, 0, 4);
setBody(ByteUtils.copy(commandAPDU, 4, commandAPDU.length - 4));
}
/**
* Create a new command APDU.
*
* @param cla Class byte
* @param ins Instruction byte
* @param p1 Parameter byte P1
* @param p2 Parameter byte P2
*/
public CardCommandAPDU(byte cla, byte ins, byte p1, byte p2) {
header[0] = cla;
header[1] = ins;
header[2] = p1;
header[3] = p2;
}
/**
* Create a new command APDU.
*
* @param cla Class byte
* @param ins Instruction byte
* @param p1 Parameter byte P1
* @param p2 Parameter byte P2
* @param le Length expected field
*/
public CardCommandAPDU(byte cla, byte ins, byte p1, byte p2, byte le) {
this(cla, ins, p1, p2, le & 0xFF);
}
/**
* Create a new command APDU.
*
* @param cla Class byte
* @param ins Instruction byte
* @param p1 Parameter byte P1
* @param p2 Parameter byte P2
* @param le Length expected field
*/
public CardCommandAPDU(byte cla, byte ins, byte p1, byte p2, short le) {
this(cla, ins, p1, p2, le & 0xFFFF);
}
/**
* Create a new command APDU.
*
* @param cla Class byte
* @param ins Instruction byte
* @param p1 Parameter byte P1
* @param p2 Parameter byte P2
* @param le Length expected field
*/
public CardCommandAPDU(byte cla, byte ins, byte p1, byte p2, int le) {
this(cla, ins, p1, p2);
this.le = le;
}
/**
* Create a new Command APDU.
*
* @param cla Class byte
* @param ins Instruction byte
* @param p1 Parameter byte P1
* @param p2 Parameter byte P2
* @param data Data field
*/
public CardCommandAPDU(byte cla, byte ins, byte p1, byte p2, byte[] data) {
this(cla, ins, p1, p2);
this.data = data;
setLC(data.length);
}
/**
* Create a new Command APDU.
*
* @param cla Class byte
* @param ins Instruction byte
* @param p1 Parameter byte P1
* @param p2 Parameter byte P2
* @param data Data field
* @param le Length expected field
*/
public CardCommandAPDU(byte cla, byte ins, byte p1, byte p2, byte[] data, byte le) {
this(cla, ins, p1, p2, data, le & 0xFF);
}
/**
* Create a new Command APDU.
*
* @param cla Class byte
* @param ins Instruction byte
* @param p1 Parameter byte P1
* @param p2 Parameter byte P2
* @param data Data field
* @param le Length expected field
*/
public CardCommandAPDU(byte cla, byte ins, byte p1, byte p2, byte[] data, short le) {
this(cla, ins, p1, p2, data, le & 0xFFFF);
}
/**
* Create a new Command APDU.
*
* @param cla Class byte
* @param ins Instruction byte
* @param p1 Parameter byte P1
* @param p2 Parameter byte P2
* @param data Data field
* @param le Length expected field
*/
public CardCommandAPDU(byte cla, byte ins, byte p1, byte p2, byte[] data, int le) {
this(cla, ins, p1, p2, data);
setLE(le);
}
/**
* Sets the class byte of the APDU.
*
* @param cla Class byte
*/
protected void setCLA(byte cla) {
header[0] = cla;
}
/**
* Returns the class byte of the APDU.
*
* @return Class byte
*/
public byte getCLA() {
return (byte) (header[0] & 0xFF);
}
/**
* Sets the instruction byte of the APDU.
*
* @param ins Instruction byte
*/
protected void setINS(byte ins) {
header[1] = ins;
}
/**
* Returns the instruction byte of the APDU.
*
* @return Instruction byte
*/
public byte getINS() {
return (byte) (header[1] & 0xFF);
}
/**
* Sets the parameter byte P1 of the APDU.
*
* @param p1 Parameter byte P1
*/
protected void setP1(byte p1) {
header[2] = p1;
}
/**
* Returns the parameter byte P1 of the APDU.
*
* @return Parameter byte P1
*/
public byte getP1() {
return (byte) (header[2] & 0xFF);
}
/**
* Sets the parameter byte P2 of the APDU.
*
* @param p2 Parameter byte P2
*/
protected void setP2(byte p2) {
header[3] = p2;
}
/**
* Returns the parameter byte P2 of the APDU.
*
* @return parameter byte P2
*/
public byte getP2() {
return (byte) (header[3] & 0xFF);
}
/**
* Sets the parameter bytes P1 and P2 of the APDU.
*
* @param p1p2 Parameter bytes P1 and P2
*/
protected void setP1P2(byte[] p1p2) {
setP1(p1p2[0]);
setP2(p1p2[1]);
}
/**
* Returns the parameter bytes P1 and P2 of the APDU.
*
* @return parameter bytes P1 and P2
*/
public byte[] getP1P2() {
return new byte[]{getP1(), getP2()};
}
/**
* Returns the header of the APDU: CLA | INS | P1 | P2
*
* @return Header of the APDU
*/
public byte[] getHeader() {
return header;
}
/**
* Returns the header of the APDU.
*
* @param commandAPDU Command APDU
* @return Header of the APDU
*/
public static byte[] getHeader(byte[] commandAPDU) {
if (commandAPDU.length < 4) {
throw new IllegalArgumentException("Malformed APDU");
}
return ByteUtils.copy(commandAPDU, 0, 4);
}
/**
* Sets the length command (LC) field of the APDU.
*
* @param lc Length command (LC) field
*/
protected final void setLC(byte lc) {
setLC(lc & 0xFF);
}
/**
* Sets the length command (LC) field of the APDU.
*
* @param lc Length command (LC) field
*/
protected final void setLC(short lc) {
setLC(lc & 0xFFFF);
}
/**
* Sets the length command (LC) field of the APDU.
*
* @param lc Length command (LC) field
*/
protected final void setLC(int lc) {
if (lc < 1 || lc > 65536) {
throw new IllegalArgumentException("Length should be from '1' to '65535'.");
}
this.lc = lc;
}
/**
* Returns the length command (LC) field.
*
* @return Length command (LC) field
*/
public int getLC() {
return lc;
}
/**
* Sets the data field of the APDU. Length command (LC) field will be calculated.
*
* @param data Data field
*/
@Override
public void setData(byte[] data) {
super.setData(data);
setLC(data.length);
}
/**
* Sets the body (LE, DATA, LC) of the APDU.
*
* @param body Body of the APDU
*/
public final void setBody(byte[] body) {
/*
* Case 1. : |CLA|INS|P1|P2|
* Case 2. : |CLA|INS|P1|P2|LE|
* Case 2.1: |CLA|INS|P1|P2|EXTLE|
* Case 3. : |CLA|INS|P1|P2|LC|DATA|
* Case 3.1: |CLA|INS|P1|P2|EXTLC|DATA|
* Case 4. : |CLA|INS|P1|P2|LC|DATA|LE|
* Case 4.1: |CLA|INS|P1|P2|EXTLC|DATA|LE|
* Case 4.2: |CLA|INS|P1|P2|LC|DATA|EXTLE|
* Case 4.3: |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
*/
try {
ByteArrayInputStream bais = new ByteArrayInputStream(body);
int length = bais.available();
// Cleanup
lc = -1;
le = -1;
data = new byte[0];
if (length == 0) {
// Case 1. : |CLA|INS|P1|P2|
} else if (length == 1) {
// Case 2 |CLA|INS|P1|P2|LE|
le = (bais.read() & 0xFF);
} else if (length < 65536) {
int tmp = bais.read();
if (tmp == 0) {
// Case 2.1, 3.1, 4.1, 4.3
if (bais.available() < 3) {
// Case 2.1 |CLA|INS|P1|P2|EXTLE|
le = ((bais.read() & 0xFF) << 8) | (bais.read() & 0xFF);
} else {
// Case 3.1, 4.1, 4.3
lc = ((bais.read() & 0xFF) << 8) | (bais.read() & 0xFF);
data = new byte[lc];
bais.read(data);
if (bais.available() == 1) {
// Case 4.1 |CLA|INS|P1|P2|EXTLC|DATA|LE|
le = (bais.read() & 0xFF);
} else if (bais.available() == 2) {
// Case 4.3 |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
le = ((bais.read() & 0xFF) << 8) | (bais.read() & 0xFF);
} else if (bais.available() == 3) {
if (bais.read() == 0) {
// Case 4.3 |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
le = ((bais.read() & 0xFF) << 8) | (bais.read() & 0xFF);
} else {
throw new IllegalArgumentException("Malformed APDU.");
}
} else if (bais.available() > 3) {
throw new IllegalArgumentException("Malformed APDU.");
}
}
} else if (tmp > 0) {
// Case 3, 4, 4.2
lc = (tmp & 0xFF);
data = new byte[lc];
bais.read(data);
if (bais.available() == 1 || bais.available() == 3) {
tmp = bais.read();
if (tmp != 0) {
// Case 4 |CLA|INS|P1|P2|LC|DATA|LE|
le = (tmp & 0xFF);
} else {
// Case 4.2 |CLA|INS|P1|P2|LC|DATA|EXTLE|
le = ((bais.read() & 0xFF) << 8) | (bais.read() & 0xFF);
}
} else if (bais.available() == 2 || bais.available() > 3) {
throw new IllegalArgumentException("Malformed APDU.");
}
} else {
throw new IllegalArgumentException("Malformed APDU.");
}
} else {
throw new IllegalArgumentException("Malformed APDU.");
}
} catch (Exception e) {
logger.error("Exception", e);
}
}
/**
* Returns the body of the APDU (LC*|DATA|LE*).
*
* @param commandAPDU Command APDU
* @return Body of the APDU
*/
public static byte[] getBody(byte[] commandAPDU) {
if (commandAPDU.length < 4) {
throw new IllegalArgumentException("Malformed APDU");
}
return ByteUtils.copy(commandAPDU, 4, commandAPDU.length - 4);
}
/**
* Sets the length expected (LE) field of the APDU.
*
* @param le Length expected (LE) field
*/
public final void setLE(byte le) {
if (le == (byte) 0x00) {
setLE(256);
} else {
setLE(le & 0xFF);
}
}
/**
* Sets the length expected (LE) field of the APDU.
*
* @param le Length expected (LE) field
*/
public final void setLE(short le) {
if (le == (short) 0x0000) {
setLE(65536);
} else {
setLE(le & 0xFFFF);
}
}
/**
* Sets the length expected (LE) field of the APDU.
*
* @param le Length expected (LE) field
*/
public final void setLE(int le) {
if (le < 0 || le > 65536) {
throw new IllegalArgumentException("Length should be from '1' to '65535'.");
} else {
this.le = le;
}
}
/**
* Returns the length expected (LE) field
*
* @return Length expected (LE) field
*/
public final int getLE() {
return le;
}
/**
* Updates the class byte of the header to indicate command chaining.
* See ISO/IEC 7816-4 Section 5.1.1.1
*/
public final void setChaining() {
header[0] = (byte) (header[0] | 0x10);
}
/**
* Returns a iterator over the chaining APDUs.
* @return
*/
public final Iterable getChainingIterator() {
// TODO: Implemente the chaining iterator
throw new IllegalAccessError("Not implemented yet");
}
/**
* Updates the class byte of the header to indicate Secure Messaging.
* See ISO/IEC 7816-4 Section 6
*/
public final void setSecureMessaging() {
header[0] = (byte) (header[0] | 0x0C);
}
/**
* Return true if the header of the APDU indicates Secure Messaging.
*
* @return True if APDU is a Secure Messaging APDU.
*/
public final boolean isSecureMessaging() {
return ((header[0] & (byte) 0x0F) == (byte) 0x0C);
}
/**
* Returns the encoded APDU: CLA | INS | P1 | P2 | (EXT)LC | DATA | (EXT)LE
*
* @return Encoded APDU
*/
public final byte[] toByteArray() {
ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length + 10);
try {
// Write APDU header
baos.write(header);
// Write APDU LC field.
if (lc > 255) {
// Encoded extended LC field in three bytes.
baos.write(x00);
baos.write((byte) (lc >> 8));
baos.write((byte) lc);
} else if (lc > 0) {
// Write short LC field
baos.write((byte) lc);
}
// Write APDU data field
baos.write(data);
// Write APDU LE field.
if (le > 256) {
// Write extended LE field.
if (lc < 256) {
// Encoded extended LE field in three bytes.
baos.write(x00);
}
// Encoded extended LE field in two bytes if extended LC field is present.
if (le == 65536) {
baos.write(x00);
baos.write(x00);
}
baos.write((byte) (le >> 8));
baos.write((byte) le);
} else if (le > 0) {
if (lc > 255) {
// Write extended LE field in two bytes because extended LC field is present.
baos.write((byte) (le >> 8));
baos.write((byte) le);
} else {
// Write short LE field
baos.write((byte) le);
}
}
} catch (IOException ex) {
logger.error("Failed to create APDU in memory.", ex);
}
return baos.toByteArray();
}
/**
* Returns the bytes of the APDU as a hex encoded string.
*
* @return Hex encoded string of the APDU
*/
public String toHexString() {
return ByteUtils.toHexString(toByteArray());
}
/**
* Returns the bytes of the APDU as a hex encoded string.
*
* @return Hex encoded string of the APDU
*/
@Override
public String toString() {
return ByteUtils.toHexString(toByteArray(), true);
}
/**
* Creates a new Transmit message.
*
* @param slotHandle Slot handle
* @return Transmit
*/
public Transmit makeTransmit(byte[] slotHandle) {
return makeTransmit(slotHandle, new ArrayList<byte[]>(0));
}
/**
* Creates a new Transmit message.
*
* @param slotHandle Slot handle
* @param responses Positive responses
* @return Transmit
*/
public Transmit makeTransmit(byte[] slotHandle, List<byte[]> responses) {
InputAPDUInfoType apdu = new InputAPDUInfoType();
apdu.setInputAPDU(toByteArray());
apdu.getAcceptableStatusCode().addAll(responses);
Transmit t = new Transmit();
t.setSlotHandle(slotHandle);
t.getInputAPDUInfo().add(apdu);
return t;
}
/**
* Transmit the APDU.
* This function uses 0x9000 as expected status code and raises an error if a different result is returned by the
* card.
*
* @param dispatcher Dispatcher
* @param slotHandle Slot handle
* @return Response APDU
* @throws APDUException
*/
public CardResponseAPDU transmit(Dispatcher dispatcher, byte[] slotHandle) throws APDUException {
return transmit(dispatcher, slotHandle, CardCommandStatus.responseOk());
}
/**
* Transmit the APDU.
*
* @param dispatcher Dispatcher
* @param slotHandle Slot handle
* @param responses List of positive responses
* @return Response APDU
* @throws APDUException
*/
public CardResponseAPDU transmit(Dispatcher dispatcher, byte[] slotHandle, List<byte[]> responses)
throws APDUException {
Transmit t;
TransmitResponse tr = null;
try {
if (responses != null) {
t = makeTransmit(slotHandle, responses);
} else {
t = makeTransmit(slotHandle);
}
tr = (TransmitResponse) dispatcher.deliver(t);
WSHelper.checkResult(tr);
CardResponseAPDU responseAPDU = new CardResponseAPDU(tr);
return responseAPDU;
} catch (WSException ex) {
throw new APDUException(ex, tr);
} catch (Exception ex) {
throw new APDUException(ex);
}
}
}