/*
* @(#)LoadCyberFlexAccess.java 1.0 09/06/1999
*
* Copyright (c) 1994-1999 Sun Microsystems, Inc.
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
*/
package sunlabs.oc.javacard.loaders.schlumberger;
import sunlabs.oc.javacard.loaders.JavaCardLoaderProxy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Hashtable;
import opencard.core.service.CardService;
import opencard.core.service.CardServiceRuntimeException;
import opencard.core.terminal.CardTerminalException;
import sun.oc.terminal.ISOCommandAPDU;
import opencard.core.terminal.CommandAPDU;
import opencard.core.terminal.ResponseAPDU;
import sunlabs.sts.ByteArray;
import sunlabs.oc.ErrorMsg;
/**
* Load a CyberFlex Access card.
*
* This class loads a CyberFlex Access card given a ByteInputStream
* and a hashtable with load properties. The
* properties currently supported are:
*
* <br>fileIdContainerSize -- size in bytes to make container
* <br>instanceDataSize -- size in bytes to make instance
* <br>fileId -- the ID for the file
* <br>instanceFileId -- the id for the instance
* <br>aid -- an aid to indentify this application
* <br>appletDESKey -- the DES signature of the cardlets bytes for verification.
*
*
* @author Rinaldo Di Giorgio
* @author Mike Montgomery
* @author Jennifer Yonemitsu
* @version %I%, %G%
*/
public class LoadCyberFlexAccess extends CardService implements JavaCardLoaderProxy {
private int tNumber = 0;
private final byte APPLET = 0x01;
private short fileIdContainerSize = 7000;
private short instanceDataSize = 6000;
private short fileId = 0x2222;
private short instanceFileId = 0x2223;
private byte aid[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
};
private byte appletDESKey[] = null;
private byte BZ = (byte) 0;
private byte B1 = (byte) 1;
private byte B8 = (byte) 8;
private byte FF = (byte) 0xFF;
private byte SEL = (byte) 0xA4;
private byte DEL = (byte) 0xE4;
private byte SID = (byte) 0x02; // Use file ID
private int appletSize = 0;
private short offset = 0;
private String status = "Ready To Load";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final byte selectRoot[] = {
BZ, SEL, BZ, BZ, SID, (byte) 0x3F, BZ
};
private final byte selectFileId[] = {
BZ, SEL, BZ, BZ, SID, (byte) 0x22, (byte) 0x22
};
private final byte selectInstance[] = {
BZ, SEL, BZ, BZ, SID, (byte) 0x22, (byte) 0x23
};
private final byte deleteFileId[] = {
BZ, DEL, BZ, BZ, SID, (byte) 0x22, (byte) 0x22
};
private final byte deletecDir[] = {
BZ, DEL, (byte) 0x80, BZ, BZ
};
private final byte verifyKey[] = {
BZ, (byte) 0x2A, BZ, BZ, (byte) 0x08, (byte) 0xAD, (byte) 0x9F,
(byte) 0x61, (byte) 0xFE, (byte) 0xFA, (byte) 0x20, (byte) 0xCE,
(byte) 0x63
};
private final byte nonCryptoKey[] = {
(byte) 0xCE, (byte) 0xA4, (byte) 0x5D, (byte) 0xC2, (byte) 0xAE,
(byte) 89, (byte) 0xFE, (byte) 0x4F
};
private final byte createFileId[] = {
BZ, (byte) 0xE0, BZ, BZ, (byte) 0x10, (byte) 0x10, (byte) 0x00,
(byte) 0x22, (byte) 0x22, (byte) 0x03, (byte) 0x01, BZ, BZ, FF, FF,
FF, FF, FF, FF, FF, FF
};
private final byte selectDefaultLoader[] = {
BZ, SEL, (byte) 0x04, BZ
};
private final byte resetProgram[] = {
BZ, (byte) 0x0a, (byte) 0x02, BZ
};
private final byte validateAppletDES[] = {
BZ, (byte) 0x0a, B1, BZ, B8
};
private final byte validateAppletRSA[] = {
BZ, (byte) 0x0a, B1, BZ, (byte) 128
};
private final byte createInstance[] = {
BZ, (byte) 0x0C, (byte) 0x13, BZ, 0x1B
};
private final byte resetInstance[] = {
BZ, (byte) 0x08, (byte) 0x03, BZ
};
private final byte deleteInstance[] = {
BZ, (byte) 0x08, (byte) 0x02, BZ
};
private String containerID;
private String prefix = "";
/**
* Loads the cardlet with the given properties.
*
*
* @param loadProps Properties to be used in laoding this cardlet.
* @param bis the bytes comprising the cardlet.
*
* @return a hashtabnle with information about the load attempt.
*
*/
public Hashtable downloadApplet(Hashtable loadProps,
ByteArrayInputStream bis) {
appletSize = bis.available();
offset = 0;
status = "Ready To Load";
String s = (String) loadProps.get("LOADERPREFIX");
if (s != null) {
prefix = s;
}
System.out.println("Starting download with Properties:" + loadProps);
System.out.println("Starting download looking for prefixes:"
+ prefix);
ResponseAPDU resp;
Hashtable results = new Hashtable();
//
// Get all the parameters from the user to overried current
// Parameters
//
try {
fileIdContainerSize = getParamShort(loadProps,
"fileIdContainerSize");
} catch (Exception e) {}
;
try {
instanceDataSize = getParamShort(loadProps, "instanceDataSize");
} catch (Exception e) {}
;
try {
fileId = getParamShort(loadProps, "fileId");
} catch (Exception e) {}
;
try {
instanceFileId = getParamShort(loadProps, "instanceFileId");
} catch (Exception e) {}
;
try {
if (getParamByte(loadProps, "aid") != null) {
aid = getParamByte(loadProps, "aid");
}
} catch (Exception e) {
System.err
.println("Error Loading AID Using 01020304050607089090A0B0C0D0E0F");
}
try {
appletDESKey = getParamByte(loadProps, "appletDESKey");
} catch (Exception e) {
System.err.println("Error Loading DES Key");
}
System.out.println("fileIdContainerSize:" + fileIdContainerSize);
System.out.println("instanceDataSize:" + instanceDataSize);
System.out.println("fileId:" + fileId);
System.out.println("instanceFileId:" + instanceFileId);
System.out.println("Aid:" + ByteArray.toHexString(aid));
if (appletDESKey != null) {
System.out.println("appletDESKey:"
+ ByteArray.toHexString(appletDESKey));
}
try {
status = "Preparing Directory Structure";
allocateCardChannel();
resp = sendAPDU(selectDefaultLoader);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Select Default Loader");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
// APDUs for downloading and verifying a program.
// Step 1:
// Select the card root 3F00
resp = sendAPDU(selectRoot);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Select Root");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
// Step 2:
// Verify yourself to the card (Key 0)
resp = sendAPDU(verifyKey);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Authenticate");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
// Select the File ID and delete it
resp = sendAPDU(selectFileId);
if (resp.sw() == 0x9000) {
// reset the program in case previous load failed
// and program was left in an unknwon state.
resp = sendAPDU(resetProgram);
resp = sendAPDU(deleteFileId);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Delete existing file");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
}
// Select the File ID and delete it
resp = sendAPDU(selectInstance);
if (resp.sw() == 0x9000) {
// reset the program in case previous load failed
// and program was left in an unknwon state.
resp = sendAPDU(resetInstance);
resp = sendAPDU(deleteInstance);
if (resp.sw() != 0x9000) {
results.put("error",
"Unable to Delete existing Instance");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
}
resp = sendAPDU(selectRoot);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Select Root");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
resp = sendAPDU(createFileId);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Create File");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
// Select file
resp = sendAPDU(selectFileId);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Select File");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
resp = sendAPDU(selectDefaultLoader);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Select Default Loader");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
resp = sendAPDU(resetProgram);
// Step 6
// Download the processed class to file 2222 as follows:
// P1 and P2 form a 16-bit offset in the file; P1 is the most significant
// byte, P2 is the least. Le is the length of the block you are sending. I
// use a block size of 120 bytes of data. Just use something convenient, such
// as 100, 120, or 128. Remember that the larger the size, the fewer APDUs
// are needed, and the faster the download will be. I would not go above 128,
// though, since this might cause buffering problems.
// A0 D6 P1 P2 Le XX XX ...
// Keep sending APDUs as necessary to get the entire file downloaded.
int blockSize = 120;
ByteArrayOutputStream xmitBuffer = new ByteArrayOutputStream();
long fullBlocks = appletSize / blockSize;
long lastBlock = appletSize % blockSize;
System.out.println("The size of the applet: " + appletSize);
System.out.println("The number of full blocks is: " + fullBlocks);
System.out.println("The bytes in the last block: " + lastBlock);
offset = 0;
int blockNumber = 0;
byte dataBlock[] = new byte[blockSize];
status = "Transmitting Program";
while (fullBlocks-- > 0) {
xmitBuffer.reset();
xmitBuffer.write(BZ);
xmitBuffer.write(0xD6);
xmitBuffer.write(left(offset));
xmitBuffer.write(right(offset));
xmitBuffer.write(blockSize);
int amtread = bis.read(dataBlock, 0, blockSize);
xmitBuffer.write(dataBlock, 0, blockSize);
System.out.println("Sending block: " + blockNumber++);
ResponseAPDU rapdu = sendAPDU(xmitBuffer.toByteArray());
if (rapdu.sw() != 0x9000) {
results.put("error", rapdu.toString());
status = "Load Error:" + resp;
return (results);
}
offset += blockSize;
}
xmitBuffer.reset();
xmitBuffer.write(BZ);
xmitBuffer.write(0xD6);
xmitBuffer.write(left(offset));
xmitBuffer.write(right(offset));
xmitBuffer.write((int) lastBlock);
int amtread = bis.read(dataBlock, 0, blockSize);
System.out.println("Sending last Block:");
xmitBuffer.write(dataBlock, 0, (int) lastBlock);
sendAPDU(xmitBuffer.toByteArray());
offset += lastBlock;
System.out.println("Validate Applet");
status = "Validate Applet";
xmitBuffer.reset();
xmitBuffer.write(validateAppletDES, 0, 5);
if (appletDESKey == null) {
xmitBuffer.write(nonCryptoKey, 0, 8);
} else {
xmitBuffer.write(appletDESKey, 0, 8);
}
resp = sendAPDU(xmitBuffer.toByteArray());
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Authenticate Applet");
results.put("errorData", resp);
status = "Load Error:" + resp;
}
try {
Thread.sleep(5 * 1000);
} catch (Exception e) {}
System.out.println("Select Root");
resp = sendAPDU(selectRoot);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Select Root");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
System.out.println("Select File");
resp = sendAPDU(selectFileId);
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Select File");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
System.out.println("Create Instance");
xmitBuffer.reset();
xmitBuffer.write(createInstance, 0, 5);
xmitBuffer.write(APPLET);
xmitBuffer.write(left(fileId));
xmitBuffer.write(right(fileId));
xmitBuffer.write(left(fileIdContainerSize));
xmitBuffer.write(right(fileIdContainerSize));
xmitBuffer.write(left(instanceFileId));
xmitBuffer.write(right(instanceFileId));
xmitBuffer.write(left(instanceDataSize));
xmitBuffer.write(right(instanceDataSize));
xmitBuffer.write(BZ);
xmitBuffer.write(0x10);
xmitBuffer.write(aid, 0, 16);
resp = sendAPDU(xmitBuffer.toByteArray());
if (resp.sw() != 0x9000) {
results.put("error", "Unable to Create Instance");
results.put("errorData", resp);
status = "Load Error:" + resp;
return (results);
}
status = "Load Complete Loaded, Validated and Instance Created";
} catch (CardTerminalException e) {
results.put("error", ErrorMsg.format(e, null, "downloading"));
}
finally {
try {
bis.close();
} catch (Exception e) {}
try {
releaseCardChannel();
} catch (CardServiceRuntimeException e) {
results.put("error", "card removed");
return results;
}
}
results.put("OK", getBytesSent() + " transferred.");
return (results);
}
/**
* Method to send an APDU and get the response
*
*
* @param apdu to send.
*
* @return the response APDU
*
* @exception CardTerminalException is raised if the APDU could not be
* send or received.
*
*/
private ResponseAPDU sendAPDU(byte apdu[]) throws CardTerminalException {
CommandAPDU commandAPDU = new CommandAPDU(apdu);
System.out.println(">>>>>>>>>>>>>>>> Start Transaction Number "
+ ++tNumber);
System.out.println(commandAPDU);
ResponseAPDU responseAPDU =
getCardChannel().sendCommandAPDU(commandAPDU);
System.out.println(responseAPDU);
if (responseAPDU.sw1() == (byte) 0x61) {
ISOCommandAPDU apduToSend = new ISOCommandAPDU((byte) 0x00,
(byte) 0xC0, (byte) 0x00, (byte) 0x00,
(int) responseAPDU.sw2());
responseAPDU = getCardChannel().sendCommandAPDU(apduToSend);
}
System.out.println("<<<<<<<<<<<<<<<< End Transaction Number "
+ tNumber);
return (responseAPDU);
}
/**
* Constructor method which can have additional initializations
*
*
*/
public LoadCyberFlexAccess() {}
/**
* Get a short from the properties object
*
*
* @param lp the hash table with the values
* @param key the parameter we want
*
* @return the short which is the parameter value
*
*/
private short getParamShort(Hashtable lp, String key) {
String s = (String) lp.get(prefix + key);
return (Short.parseShort(s));
}
/**
* Get a byte array from the properties object
*
*
*
* @param lp the hash table with the values
* @param key the parameter we want
*
* @return the byte array which is the parameter value
*
*/
private byte[] getParamByte(Hashtable lp, String key) {
String s = (String) lp.get(prefix + key);
byte[] data = ByteArray.parseHexString(s);
return (data);
}
/**
* Print debugging information for given object
*
*
* @param o the object to use.
*
*/
void debugObject(Object o) {
System.out.println(o.toString());
}
/**
* Shift a short left 8
*
*
* @param s the short to shift left 8
*
* @return the byte result of the shift
*
*/
byte left(short s) {
return ((byte) ((s & (short) 0xFF00) >> 8));
}
/**
* Lower Byte of a short
*
*
* @param s the short
*
* @return byte with lower byte of short
*
*/
byte right(short s) {
return ((byte) ((s & (short) 0x00FF)));
}
/**
* Write a short to a byte output stream
*
*
* @param s the short
*
*/
void stuffShort(short s) {
baos.write(left(s));
baos.write(right(s));
}
/**
* Fill byte to a an output stream
*
*
* @param b the fill byte
* @param number the number of bytes to fill
*
*/
void stuffBytes(byte b, int number) {
int count = number;
while (count-- > 0) {
baos.write(b);
}
}
/**
* Number of bytes to send for this cardlet
*
*
* @return the size of the cardlet
*
*/
public int getBytesToSend() {
return (appletSize);
}
/**
* Number of bytes sent
*
*
* @return the current number of bytes transmitted to the card.
*
*/
public int getBytesSent() {
return (offset);
}
/**
* String representation of the load process.
*
*
* @return the string representation of the current load state.
*
*/
public String getStatus() {
return (status);
}
/**
* Reset this object for a new load to begin.
*
*
*/
public void reset() {
status = "Ready to Load";
appletSize = 0;
offset = 0;
}
}