import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.math.BigInteger;
import java.util.List;
import javax.swing.*;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.smartcardio.*;
/**
* Sample terminal for the Crypto applet.
*
* @author Martijn Oostdijk (martijno@cs.kun.nl)
* @author Joeri de Ruiter (joeri@cs.ru.nl)
*
* @version $Revision: 2.0 $
*/
public class CryptoTerminal extends JPanel implements ActionListener {
static final int BLOCKSIZE = 128;
static final String TITLE = "Crypto Terminal";
static final int DISPLAY_WIDTH = 30;
static final int DISPLAY_HEIGHT = 20;
static final String MSG_ERROR = "Error";
static final String MSG_INVALID = "Invalid";
static final byte[] APPLET_AID = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x01 };
static final byte CLA_ISO = (byte) 0x00;
static final byte INS_SELECT = (byte) 0xA4;
static final short SW_NO_ERROR = (short) 0x9000;
static final short SW_APPLET_SELECT_FAILED = (short) 0x6999;
static final short SW_FILE_NOT_FOUND = (short) 0x6A82;
static final CommandAPDU SELECT_APDU = new CommandAPDU((byte) 0x00,
(byte) 0xA4, (byte) 0x04, (byte) 0x00, APPLET_AID);
private static final byte CLA_CRYPTO = (byte) 0xCC;
private static final byte INS_SET_PUB_MODULUS = (byte) 0x02;
private static final byte INS_SET_PRIV_MODULUS = (byte) 0x12;
private static final byte INS_SET_PRIV_EXP = (byte) 0x22;
private static final byte INS_SET_PUB_EXP = (byte) 0x32;
private static final byte INS_ISSUE = (byte) 0x40;
private static final byte INS_ENCRYPT = (byte) 0xE0;
private static final byte INS_DECRYPT = (byte) 0xD0;
private static final int STATE_INIT = 0;
private static final int STATE_ISSUED = 1;
/** GUI stuff. */
JTextArea display;
/** GUI stuff. */
JButton setPubKeyButton, setPrivKeyButton, issueButton, encryptButton,
decryptButton;
/** File browser needs to remember last dir it accessed. */
File currentDir = new File(".");
/** The card applet. */
CardChannel applet;
/**
* Constructs the terminal application.
*/
public CryptoTerminal() {
buildGUI();
setEnabled(false);
addActionListener(this);
(new CardThread()).start();
}
/**
* Builds the GUI.
*/
void buildGUI() {
setLayout(new BorderLayout());
display = new JTextArea(DISPLAY_HEIGHT, DISPLAY_WIDTH);
display.setEditable(false);
add(new JScrollPane(display), BorderLayout.CENTER);
JPanel buttons = new JPanel(new FlowLayout());
setPubKeyButton = new JButton("Set Pub");
setPrivKeyButton = new JButton("Set Priv");
issueButton = new JButton("Issue");
encryptButton = new JButton("Encrypt");
decryptButton = new JButton("Decrypt");
buttons.add(setPubKeyButton);
buttons.add(setPrivKeyButton);
buttons.add(issueButton);
buttons.add(encryptButton);
buttons.add(decryptButton);
add(buttons, BorderLayout.SOUTH);
}
/**
* Enables/disables the buttons.
*
* @param b
* boolean indicating whether to enable or disable the buttons.
*/
public void setEnabled(boolean b) {
super.setEnabled(b);
setPubKeyButton.setEnabled(b);
setPrivKeyButton.setEnabled(b);
issueButton.setEnabled(b);
encryptButton.setEnabled(b);
decryptButton.setEnabled(b);
}
/**
* Adds the action listener l
to all buttons.
*
* @param l
* the action listener to add.
*/
public void addActionListener(ActionListener l) {
setPubKeyButton.addActionListener(l);
setPrivKeyButton.addActionListener(l);
issueButton.addActionListener(l);
encryptButton.addActionListener(l);
decryptButton.addActionListener(l);
}
class CardThread extends Thread {
public void run() {
try {
TerminalFactory tf = TerminalFactory.getDefault();
CardTerminals ct = tf.terminals();
List cs = ct
.list(CardTerminals.State.CARD_PRESENT);
if (cs.isEmpty()) {
log("No terminals with a card found.");
return;
}
while (true) {
try {
for (CardTerminal c : cs) {
if (c.isCardPresent()) {
try {
Card card = c.connect("*");
try {
applet = card.getBasicChannel();
ResponseAPDU resp = applet
.transmit(SELECT_APDU);
if (resp.getSW() != 0x9000) {
throw new Exception("Select failed");
}
setEnabled(true);
// Wait for the card to be removed
while (c.isCardPresent())
;
setEnabled(false);
break;
} catch (Exception e) {
log("Card does not contain CryptoApplet?!");
sleep(2000);
continue;
}
} catch (CardException e) {
log("Couldn't connect to card!");
sleep(2000);
continue;
}
} else {
log("No card present!");
sleep(2000);
continue;
}
}
} catch (CardException e) {
log("Card status problem!");
}
}
} catch (Exception e) {
setEnabled(false);
log("ERROR: " + e.getMessage());
e.printStackTrace();
}
}
}
/**
* Handles button events.
*
* @param ae
* event indicating a button was pressed.
*/
public void actionPerformed(ActionEvent ae) {
try {
Object src = ae.getSource();
if (src instanceof JButton) {
JButton button = (JButton) src;
if (button.equals(setPubKeyButton)) {
setPubKey();
} else if (button.equals(setPrivKeyButton)) {
setPrivKey();
} else if (button.equals(issueButton)) {
issue();
} else if (button.equals(encryptButton)) {
encrypt();
} else if (button.equals(decryptButton)) {
decrypt();
}
}
} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage());
}
}
/**
* Handles 'set pub key' button event.
*
* @throws CardException
* if something goes wrong.
*/
void setPubKey() throws CardException {
try {
byte[] data = readFile();
X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
KeyFactory factory = KeyFactory.getInstance("RSA");
RSAPublicKey key = (RSAPublicKey) factory.generatePublic(spec);
byte[] modulus = getBytes(key.getModulus());
CommandAPDU capdu;
capdu = new CommandAPDU(CLA_CRYPTO, INS_SET_PUB_MODULUS, (byte) 0,
(byte) 0, modulus);
sendCommandAPDU(capdu);
byte[] exponent = getBytes(key.getPublicExponent());
capdu = new CommandAPDU(CLA_CRYPTO, INS_SET_PUB_EXP, (byte) 0,
(byte) 0, exponent);
sendCommandAPDU(capdu);
} catch (Exception e) {
throw new CardException(e.getMessage());
}
}
/**
* Handles 'set priv key' button event.
*
* @throws CardException
* if something goes wrong.
*/
void setPrivKey() throws CardException {
try {
byte[] data = readFile();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(data);
KeyFactory factory = KeyFactory.getInstance("RSA");
RSAPrivateKey key = (RSAPrivateKey) factory.generatePrivate(spec);
byte[] modulus = getBytes(key.getModulus());
CommandAPDU capdu;
capdu = new CommandAPDU(CLA_CRYPTO, INS_SET_PRIV_MODULUS, (byte) 0,
(byte) 0, modulus);
sendCommandAPDU(capdu);
byte[] exponent = getBytes(key.getPrivateExponent());
capdu = new CommandAPDU(CLA_CRYPTO, INS_SET_PRIV_EXP, (byte) 0,
(byte) 0, exponent);
sendCommandAPDU(capdu);
} catch (Exception e) {
throw new CardException(e.getMessage());
}
}
/**
* Handles 'issue' button event.
*
* @throws CardException
* if something goes wrong.
*/
void issue() throws CardException {
try {
CommandAPDU capdu = new CommandAPDU(CLA_CRYPTO, INS_ISSUE,
(byte) 0, (byte) 0);
sendCommandAPDU(capdu);
} catch (Exception e) {
throw new CardException(e.getMessage());
}
}
/**
* Handles 'encrypt' button event.
*
* @throws CardException
* if something goes wrong.
*/
void encrypt() throws CardException {
try {
byte[] data = readFile();
if (data.length > BLOCKSIZE) {
throw new CardException("File too large.");
}
CommandAPDU capdu = new CommandAPDU(CLA_CRYPTO, INS_ENCRYPT,
(byte) 0, (byte) 0, data, BLOCKSIZE);
sendCommandAPDU(capdu);
} catch (IOException e) {
throw new CardException(e.getMessage());
}
}
/**
* Handles 'decrypt' button event.
*
* @throws CardException
* if something goes wrong.
*/
void decrypt() throws Exception {
try {
byte[] data = readFile();
if (data.length > BLOCKSIZE) {
throw new Exception("File too large.");
}
CommandAPDU capdu = new CommandAPDU(CLA_CRYPTO, INS_DECRYPT,
(byte) 0, (byte) 0, data, BLOCKSIZE);
applet.transmit(capdu);
} catch (IOException e) {
throw new Exception(e.getMessage());
}
}
/**
* Sends a command to the card.
*
* @param capdu
* the command to send.
*
* @return the response from the card.
*
* @throws CardTerminalException
* if something goes wrong.
*/
ResponseAPDU sendCommandAPDU(CommandAPDU capdu)
throws CardException {
log(capdu);
ResponseAPDU rapdu = applet.transmit(capdu);
log(rapdu);
return rapdu;
}
String toHexString(byte[] in) {
StringBuilder out = new StringBuilder(2*in.length);
for(int i = 0; i < in.length; i++) {
out.append(String.format("%02x ", (in[i] & 0xFF)));
}
return out.toString().toUpperCase();
}
/**
* Writes obj
to the log.
*
* @param obj
* the message to write to the log.
*/
void log(ResponseAPDU obj) {
display.append(obj.toString() + ", Data=" + toHexString(obj.getData()) + "\n");
System.out.println(obj.toString());
}
/**
* Writes obj
to the log.
*
* @param obj
* the message to write to the log.
*/
void log(CommandAPDU obj) {
display.append(obj.toString() + ", Data=" + toHexString(obj.getData()) + "\n");
System.out.println(obj.toString());
}
/**
* Writes obj
to the log.
*
* @param obj
* the message to write to the log.
*/
void log(Object obj) {
display.append(obj.toString() + "\n");
System.out.println(obj.toString());
}
/**
* Pops up dialog to ask user to select file and reads the file.
*
* @return byte array with contents of the file.
*
* @throws IOException
* if file could not be read.
*/
byte[] readFile() throws IOException {
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(currentDir);
int n = chooser.showOpenDialog(this);
if (n != JFileChooser.APPROVE_OPTION) {
throw new IOException("No file selected");
}
File file = chooser.getSelectedFile();
FileInputStream in = new FileInputStream(file);
int length = in.available();
byte[] data = new byte[length];
in.read(data);
in.close();
currentDir = file.getParentFile();
return data;
}
/**
* Gets an unsigned byte array representation of big
. A leading
* zero (present only to hold sign bit) is stripped.
*
* @param big
* a big integer.
*
* @return a byte array containing a representation of big
.
*/
byte[] getBytes(BigInteger big) {
byte[] data = big.toByteArray();
if (data[0] == 0) {
byte[] tmp = data;
data = new byte[tmp.length - 1];
System.arraycopy(tmp, 1, data, 0, tmp.length - 1);
}
return data;
}
/**
* Creates an instance of this class and puts it inside a frame.
*
* @param arg
* command line arguments.
*/
public static void main(String[] arg) {
JFrame frame = new JFrame(TITLE);
Container c = frame.getContentPane();
CryptoTerminal panel = new CryptoTerminal();
c.add(panel);
frame.addWindowListener(new CloseEventListener());
frame.pack();
frame.setVisible(true);
}
}
/**
* Class to close window.
*/
class CloseEventListener extends WindowAdapter {
/**
* What to do when user closes window.
*
* @param we
* window event.
*/
public void windowClosing(WindowEvent we) {
System.exit(0);
}
}