aboutsummaryrefslogtreecommitdiff
path: root/src/cz/crcs/ectester/reader
diff options
context:
space:
mode:
authorpetrs2017-06-08 14:56:23 +0200
committerGitHub2017-06-08 14:56:23 +0200
commit359b4bb5be1a822e389e54b9697504f4f0b43d34 (patch)
tree771dac996ecab9e676a0b1b3c6d14692c5dad8a9 /src/cz/crcs/ectester/reader
parent7cf4a9d7272c1abd97a160c92ec1534d5ff82373 (diff)
parent3f1483635615a0c599ae2e75858be4e39eb08a64 (diff)
downloadECTester-359b4bb5be1a822e389e54b9697504f4f0b43d34.tar.gz
ECTester-359b4bb5be1a822e389e54b9697504f4f0b43d34.tar.zst
ECTester-359b4bb5be1a822e389e54b9697504f4f0b43d34.zip
Merge pull request #1 from petrs/devel
Merge current dev version
Diffstat (limited to 'src/cz/crcs/ectester/reader')
-rw-r--r--src/cz/crcs/ectester/reader/CardMngr.java313
-rw-r--r--src/cz/crcs/ectester/reader/Command.java551
-rw-r--r--src/cz/crcs/ectester/reader/DirtyLogger.java56
-rw-r--r--src/cz/crcs/ectester/reader/ECTester.java788
-rw-r--r--src/cz/crcs/ectester/reader/ISO7816_status_words.txt71
-rw-r--r--src/cz/crcs/ectester/reader/Response.java574
-rw-r--r--src/cz/crcs/ectester/reader/Test.java82
-rw-r--r--src/cz/crcs/ectester/reader/TestSuite.java314
-rw-r--r--src/cz/crcs/ectester/reader/Util.java342
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Category.java140
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Curve.java50
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Data.java196
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_KAResult.java61
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Key.java72
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Keypair.java34
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Params.java146
16 files changed, 3790 insertions, 0 deletions
diff --git a/src/cz/crcs/ectester/reader/CardMngr.java b/src/cz/crcs/ectester/reader/CardMngr.java
new file mode 100644
index 0000000..e11bcb3
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/CardMngr.java
@@ -0,0 +1,313 @@
+package cz.crcs.ectester.reader;
+
+import com.licel.jcardsim.io.CAD;
+import com.licel.jcardsim.io.JavaxSmartCardInterface;
+import javacard.framework.AID;
+
+import javax.smartcardio.*;
+import java.util.List;
+import java.util.Scanner;
+
+/**
+ * @author Petr Svenda petr@svenda.com
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardMngr {
+ private CardTerminal terminal = null;
+ private CardChannel channel = null;
+ private Card card = null;
+
+ // Simulator related attributes
+ private CAD cad = null;
+ private JavaxSmartCardInterface simulator = null;
+
+ private boolean simulate = false;
+ private boolean verbose = true;
+
+ private final byte[] selectCM = {
+ (byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x07, (byte) 0xa0, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x18, (byte) 0x43, (byte) 0x4d};
+
+ public static final byte OFFSET_CLA = 0x00;
+ public static final byte OFFSET_INS = 0x01;
+ public static final byte OFFSET_P1 = 0x02;
+ public static final byte OFFSET_P2 = 0x03;
+ public static final byte OFFSET_LC = 0x04;
+ public static final byte OFFSET_DATA = 0x05;
+ public static final byte HEADER_LENGTH = 0x05;
+
+ public static final short DATA_RECORD_LENGTH = (short) 0x80; // 128B per record
+ public static final short NUMBER_OF_RECORDS = (short) 0x0a; // 10 records
+
+ public CardMngr() {
+ }
+
+ public CardMngr(boolean verbose) {
+ this.verbose = verbose;
+ }
+
+ public CardMngr(boolean verbose, boolean simulate) {
+ this(verbose);
+ this.simulate = simulate;
+ }
+
+ public boolean connectToCard() throws CardException {
+ if (simulate)
+ return true;
+
+ // TRY ALL READERS, FIND FIRST SELECTABLE
+ List<CardTerminal> terminalList = getReaderList();
+
+ if (terminalList == null || terminalList.isEmpty()) {
+ System.err.println("No terminals found");
+ return false;
+ }
+
+ //List numbers of Card readers
+ boolean cardFound = false;
+ for (int i = 0; i < terminalList.size(); i++) {
+
+ if (verbose)
+ System.out.println(i + " : " + terminalList.get(i));
+
+ terminal = terminalList.get(i);
+ if (terminal.isCardPresent()) {
+ card = terminal.connect("*");
+ if (verbose)
+ System.out.println("card: " + card);
+ channel = card.getBasicChannel();
+
+ //reset the card
+ if (verbose)
+ System.out.println(Util.bytesToHex(card.getATR().getBytes()));
+
+ cardFound = true;
+ }
+ }
+
+ return cardFound;
+ }
+
+ public boolean connectToCardSelect() throws CardException {
+ if (simulate)
+ return true;
+
+ // Test available card - if more present, let user to select one
+ List<CardTerminal> terminalList = CardMngr.getReaderList();
+ if (terminalList == null || terminalList.isEmpty()) {
+ System.err.println("ERROR: No suitable reader with card detected. Please check your reader connection");
+ return false;
+ } else {
+ if (terminalList.size() == 1) {
+ terminal = terminalList.get(0); // return first and only reader
+ } else {
+ int terminalIndex = 1;
+ // Let user select target terminal
+ for (CardTerminal terminal : terminalList) {
+ Card card;
+ try {
+ card = terminal.connect("*");
+ ATR atr = card.getATR();
+ System.out.println(terminalIndex + " : " + terminal.getName() + " - " + Util.bytesToHex(atr.getBytes()));
+ terminalIndex++;
+ } catch (CardException ex) {
+ ex.printStackTrace(System.out);
+ }
+ }
+ System.out.print("Select index of target reader you like to use 1.." + (terminalIndex - 1) + ": ");
+ Scanner sc = new Scanner(System.in);
+ int answ = sc.nextInt();
+ System.out.println(String.format("%d", answ));
+ answ--; // is starting with 0
+ // BUGBUG; verify allowed index range
+ terminal = terminalList.get(answ);
+ }
+ }
+
+ if (terminal != null) {
+ card = terminal.connect("*");
+ if (verbose)
+ System.out.println("card: " + card);
+ channel = card.getBasicChannel();
+ }
+
+ return true;
+ }
+
+ public boolean reconnectToCard(byte[] selectAPDU) throws CardException {
+ if (simulate)
+ return true;
+
+ if (connected()) {
+ disconnectFromCard();
+ }
+
+ boolean result = connectToCard();
+ if (result) {
+ // Select our application on card
+ send(selectAPDU);
+ }
+ return result;
+ }
+
+ public boolean connected() {
+ return simulate || card != null;
+ }
+
+ public void disconnectFromCard() throws CardException {
+ if (simulate)
+ return;
+
+ if (card != null) {
+ card.disconnect(false);
+ card = null;
+ }
+ }
+
+ public byte[] getCPLCData() throws Exception {
+ byte[] data;
+
+ // TODO: Modify to obtain CPLC data
+ byte apdu[] = new byte[HEADER_LENGTH];
+ apdu[OFFSET_CLA] = (byte) 0x00;
+ apdu[OFFSET_INS] = (byte) 0x00;
+ apdu[OFFSET_P1] = (byte) 0x00;
+ apdu[OFFSET_P2] = (byte) 0x00;
+ apdu[OFFSET_LC] = (byte) 0x00;
+
+ ResponseAPDU resp = send(apdu);
+ if (resp.getSW() != 0x9000) { // 0x9000 is "OK"
+ System.err.println("Fail to obtain card's response data");
+ data = null;
+ } else {
+ byte temp[] = resp.getBytes();
+ data = new byte[temp.length - 2];
+ System.arraycopy(temp, 0, data, 0, temp.length - 2);
+ // Last two bytes are status word (also obtainable by resp.getSW())
+ // Take a look at ISO7816_status_words.txt for common codes
+ }
+
+ return data;
+ }
+
+ public void probeCardCommands() throws Exception {
+ // TODO: modify to probe for instruction
+ for (int i = 0; i <= 0; i++) {
+ byte apdu[] = new byte[HEADER_LENGTH];
+ apdu[OFFSET_CLA] = (byte) 0x00;
+ apdu[OFFSET_INS] = (byte) 0x00;
+ apdu[OFFSET_P1] = (byte) 0x00;
+ apdu[OFFSET_P2] = (byte) 0x00;
+ apdu[OFFSET_LC] = (byte) 0x00;
+
+ ResponseAPDU resp = send(apdu);
+
+ if (verbose)
+ System.out.println("Response: " + Integer.toHexString(resp.getSW()));
+
+ if (resp.getSW() != 0x6D00) { // Note: 0x6D00 is SW_INS_NOT_SUPPORTED
+ // something?
+ }
+ }
+ }
+
+ public static List<CardTerminal> getReaderList() {
+ try {
+ TerminalFactory factory = TerminalFactory.getDefault();
+ return factory.terminals().list();
+ } catch (CardException ex) {
+ System.err.println("Exception : " + ex);
+ return null;
+ }
+ }
+
+ public ResponseAPDU sendAPDU(CommandAPDU apdu) throws CardException {
+ if (verbose) {
+ System.out.println(">>>>");
+ System.out.println(apdu);
+
+ System.out.println(Util.bytesToHex(apdu.getBytes()));
+ }
+
+ long elapsed = -System.nanoTime();
+
+ ResponseAPDU responseAPDU = channel.transmit(apdu);
+
+ elapsed += System.nanoTime();
+
+ if (verbose) {
+ System.out.println(responseAPDU);
+ System.out.println(Util.bytesToHex(responseAPDU.getBytes()));
+ }
+
+ if (responseAPDU.getSW1() == (byte) 0x61) {
+ CommandAPDU apduToSend = new CommandAPDU((byte) 0x00,
+ (byte) 0xC0, (byte) 0x00, (byte) 0x00,
+ responseAPDU.getSW1());
+
+ responseAPDU = channel.transmit(apduToSend);
+ if (verbose)
+ System.out.println(Util.bytesToHex(responseAPDU.getBytes()));
+ }
+
+ if (verbose) {
+ System.out.println("<<<<");
+ System.out.println("Elapsed time (ms): " + elapsed / 1000000);
+ }
+ return responseAPDU;
+ }
+
+ public ResponseAPDU sendAPDU(byte apdu[]) throws CardException {
+ CommandAPDU commandAPDU = new CommandAPDU(apdu);
+ return sendAPDU(commandAPDU);
+ }
+
+ public boolean prepareLocalSimulatorApplet(byte[] appletAIDArray, byte[] installData, Class appletClass) {
+ System.setProperty("com.licel.jcardsim.terminal.type", "2");
+ cad = new CAD(System.getProperties());
+ simulator = (JavaxSmartCardInterface) cad.getCardInterface();
+ AID appletAID = new AID(appletAIDArray, (short) 0, (byte) appletAIDArray.length);
+
+ AID appletAIDRes = simulator.installApplet(appletAID, appletClass, installData, (short) 0, (byte) installData.length);
+ return simulator.selectApplet(appletAID);
+ }
+
+ public ResponseAPDU sendAPDUSimulator(CommandAPDU apdu) {
+ if (verbose) {
+ System.out.println(">>>>");
+ System.out.println(apdu);
+ System.out.println(Util.bytesToHex(apdu.getBytes()));
+ }
+
+ ResponseAPDU response = simulator.transmitCommand(apdu);
+ byte[] responseBytes = response.getBytes();
+
+ if (verbose) {
+ System.out.println(response);
+ System.out.println(Util.bytesToHex(responseBytes));
+ System.out.println("<<<<");
+ }
+
+ return response;
+ }
+
+ public ResponseAPDU sendAPDUSimulator(byte[] apdu) {
+ CommandAPDU commandAPDU = new CommandAPDU(apdu);
+ return sendAPDUSimulator(commandAPDU);
+ }
+
+ public ResponseAPDU send(CommandAPDU apdu) throws CardException {
+ ResponseAPDU response;
+ if (simulate) {
+ response = sendAPDUSimulator(apdu);
+ } else {
+ response = sendAPDU(apdu);
+ }
+ return response;
+ }
+
+ public ResponseAPDU send(byte[] apdu) throws CardException {
+ CommandAPDU commandAPDU = new CommandAPDU(apdu);
+ return send(commandAPDU);
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/Command.java b/src/cz/crcs/ectester/reader/Command.java
new file mode 100644
index 0000000..5b7be01
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/Command.java
@@ -0,0 +1,551 @@
+package cz.crcs.ectester.reader;
+
+import cz.crcs.ectester.applet.ECTesterApplet;
+import cz.crcs.ectester.applet.EC_Consts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.ec.EC_Curve;
+import cz.crcs.ectester.reader.ec.EC_Key;
+import cz.crcs.ectester.reader.ec.EC_Keypair;
+import cz.crcs.ectester.reader.ec.EC_Params;
+import javacard.security.KeyPair;
+
+import javax.smartcardio.CardException;
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class Command {
+ CommandAPDU cmd;
+ CardMngr cardManager;
+
+ Command(CardMngr cardManager) {
+ this.cardManager = cardManager;
+ }
+
+ public CommandAPDU getAPDU() {
+ return cmd;
+ }
+
+ public abstract Response send() throws CardException;
+
+ public static List<Response> sendAll(List<Command> commands) throws CardException {
+ List<Response> result = new ArrayList<>();
+ for (Command cmd : commands) {
+ result.add(cmd.send());
+ }
+ return result;
+ }
+
+
+ /**
+ * @param keyPair which keyPair/s (local/remote) to set curve domain parameters on
+ * @param keyLength key length to choose
+ * @param keyClass key class to choose
+ * @return a Command to send in order to prepare the curve on the keypairs.
+ * @throws IOException if curve file cannot be found/opened
+ */
+ public static Command prepareCurve(CardMngr cardManager, EC_Store dataStore, ECTester.Config cfg, byte keyPair, short keyLength, byte keyClass) throws IOException {
+
+ if (cfg.customCurve) {
+ // Set custom curve (one of the SECG curves embedded applet-side)
+ short domainParams = keyClass == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M;
+ return new Command.Set(cardManager, keyPair, EC_Consts.getCurve(keyLength, keyClass), domainParams, null);
+ } else if (cfg.namedCurve != null) {
+ // Set a named curve.
+ // parse cfg.namedCurve -> cat / id | cat | id
+ EC_Curve curve = dataStore.getObject(EC_Curve.class, cfg.namedCurve);
+ if (curve == null) {
+ throw new IOException("Curve could no be found.");
+ }
+ if (curve.getBits() != keyLength) {
+ throw new IOException("Curve bits mismatch: " + curve.getBits() + " vs " + keyLength + " entered.");
+ }
+ if (curve.getField() != keyClass) {
+ throw new IOException("Curve field mismatch.");
+ }
+
+ byte[] external = curve.flatten();
+ if (external == null) {
+ throw new IOException("Couldn't read named curve data.");
+ }
+ return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, curve.getParams(), external);
+ } else if (cfg.curveFile != null) {
+ // Set curve loaded from a file
+ EC_Curve curve = new EC_Curve(null, keyLength, keyClass);
+
+ FileInputStream in = new FileInputStream(cfg.curveFile);
+ curve.readCSV(in);
+ in.close();
+
+ byte[] external = curve.flatten();
+ if (external == null) {
+ throw new IOException("Couldn't read the curve file correctly.");
+ }
+ return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, curve.getParams(), external);
+ } else {
+ // Set default curve
+ /* This command was generally causing problems for simulating on jcardsim.
+ * Since there, .clearKey() resets all the keys values, even the domain.
+ * This might break some other stuff.. But should not.
+ */
+ //commands.add(new Command.Clear(cardManager, keyPair));
+ return null;
+ }
+ }
+
+
+ /**
+ * @param keyPair which keyPair/s to set the key params on
+ * @return a CommandAPDU setting params loaded on the keyPair/s
+ * @throws IOException if any of the key files cannot be found/opened
+ */
+ public static Command prepareKey(CardMngr cardManager, EC_Store dataStore, ECTester.Config cfg, byte keyPair) throws IOException {
+ short params = EC_Consts.PARAMETERS_NONE;
+ byte[] data = null;
+
+ if (cfg.key != null || cfg.namedKey != null) {
+ params |= EC_Consts.PARAMETERS_KEYPAIR;
+ EC_Params keypair;
+ if (cfg.key != null) {
+ keypair = new EC_Params(EC_Consts.PARAMETERS_KEYPAIR);
+
+ FileInputStream in = new FileInputStream(cfg.key);
+ keypair.readCSV(in);
+ in.close();
+ } else {
+ keypair = dataStore.getObject(EC_Keypair.class, cfg.namedKey);
+ }
+
+ data = keypair.flatten();
+ if (data == null) {
+ throw new IOException("Couldn't read the key file correctly.");
+ }
+ }
+
+ if (cfg.publicKey != null || cfg.namedPublicKey != null) {
+ params |= EC_Consts.PARAMETER_W;
+ EC_Params pub;
+ if (cfg.publicKey != null) {
+ pub = new EC_Params(EC_Consts.PARAMETER_W);
+
+ FileInputStream in = new FileInputStream(cfg.publicKey);
+ pub.readCSV(in);
+ in.close();
+ } else {
+ pub = dataStore.getObject(EC_Key.Public.class, cfg.namedPublicKey);
+ if (pub == null) {
+ pub = dataStore.getObject(EC_Keypair.class, cfg.namedPublicKey);
+ }
+ }
+
+ byte[] pubkey = pub.flatten(EC_Consts.PARAMETER_W);
+ if (pubkey == null) {
+ throw new IOException("Couldn't read the public key file correctly.");
+ }
+ data = pubkey;
+ }
+ if (cfg.privateKey != null || cfg.namedPrivateKey != null) {
+ params |= EC_Consts.PARAMETER_S;
+ EC_Params priv;
+ if (cfg.privateKey != null) {
+ priv = new EC_Params(EC_Consts.PARAMETER_S);
+
+ FileInputStream in = new FileInputStream(cfg.privateKey);
+ priv.readCSV(in);
+ in.close();
+ } else {
+ priv = dataStore.getObject(EC_Key.Public.class, cfg.namedPrivateKey);
+ if (priv == null) {
+ priv = dataStore.getObject(EC_Keypair.class, cfg.namedPrivateKey);
+ }
+ }
+
+ byte[] privkey = priv.flatten(EC_Consts.PARAMETER_S);
+ if (privkey == null) {
+ throw new IOException("Couldn't read the private key file correctly.");
+ }
+ data = Util.concatenate(data, privkey);
+ }
+ return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, params, data);
+ }
+
+
+ /**
+ *
+ */
+ public static class Allocate extends Command {
+ private byte keyPair;
+ private short keyLength;
+ private byte keyClass;
+
+ /**
+ * Creates the INS_ALLOCATE instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair which keyPair to use, local/remote (KEYPAIR_* | ...)
+ * @param keyLength key length to set
+ * @param keyClass key class to allocate
+ */
+ protected Allocate(CardMngr cardManager, byte keyPair, short keyLength, byte keyClass) {
+ super(cardManager);
+ this.keyPair = keyPair;
+ this.keyLength = keyLength;
+ this.keyClass = keyClass;
+
+ byte[] data = new byte[]{0, 0, keyClass};
+ Util.setShort(data, 0, keyLength);
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ALLOCATE, keyPair, 0x00, data);
+ }
+
+ @Override
+ public Response.Allocate send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Allocate(response, elapsed, keyPair, keyLength, keyClass);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Clear extends Command {
+ private byte keyPair;
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair which keyPair clear, local/remote (KEYPAIR_* || ...)
+ */
+ protected Clear(CardMngr cardManager, byte keyPair) {
+ super(cardManager);
+ this.keyPair = keyPair;
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_CLEAR, keyPair, 0x00);
+ }
+
+ @Override
+ public Response.Clear send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Clear(response, elapsed, keyPair);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Set extends Command {
+ private byte keyPair;
+ private byte curve;
+ private short params;
+ private byte[] external;
+
+ /**
+ * Creates the INS_SET instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair which keyPair to set params on, local/remote (KEYPAIR_* || ...)
+ * @param curve curve to set (EC_Consts.CURVE_*)
+ * @param params parameters to set (EC_Consts.PARAMETER_* | ...)
+ * @param external external curve data, can be null
+ */
+ protected Set(CardMngr cardManager, byte keyPair, byte curve, short params, byte[] external) {
+ super(cardManager);
+ this.keyPair = keyPair;
+ this.curve = curve;
+ this.params = params;
+ this.external = external;
+
+ int len = external != null ? 2 + external.length : 2;
+ byte[] data = new byte[len];
+ Util.setShort(data, 0, params);
+ if (external != null) {
+ System.arraycopy(external, 0, data, 2, external.length);
+ }
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_SET, keyPair, curve, data);
+ }
+
+ @Override
+ public Response.Set send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Set(response, elapsed, keyPair, curve, params);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Corrupt extends Command {
+ private byte keyPair;
+ private byte key;
+ private short params;
+ private byte corruption;
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair which keyPair to corrupt, local/remote (KEYPAIR_* || ...)
+ * @param key key to corrupt (EC_Consts.KEY_* | ...)
+ * @param params parameters to corrupt (EC_Consts.PARAMETER_* | ...)
+ * @param corruption corruption type (EC_Consts.CORRUPTION_*)
+ */
+ protected Corrupt(CardMngr cardManager, byte keyPair, byte key, short params, byte corruption) {
+ super(cardManager);
+ this.keyPair = keyPair;
+ this.key = key;
+ this.params = params;
+ this.corruption = corruption;
+
+ byte[] data = new byte[3];
+ Util.setShort(data, 0, params);
+ data[2] = corruption;
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_CORRUPT, keyPair, key, data);
+ }
+
+ @Override
+ public Response.Corrupt send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Corrupt(response, elapsed, keyPair, key, params, corruption);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Generate extends Command {
+ private byte keyPair;
+
+ /**
+ * Creates the INS_GENERATE instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair which keyPair to generate, local/remote (KEYPAIR_* || ...)
+ */
+ protected Generate(CardMngr cardManager, byte keyPair) {
+ super(cardManager);
+ this.keyPair = keyPair;
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_GENERATE, keyPair, 0);
+ }
+
+ @Override
+ public Response.Generate send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Generate(response, elapsed, keyPair);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Export extends Command {
+ private byte keyPair;
+ private byte key;
+ private short params;
+
+ /**
+ * Creates the INS_EXPORT instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair keyPair to export from (KEYPAIR_* | ...)
+ * @param key key to export from (EC_Consts.KEY_* | ...)
+ * @param params params to export (EC_Consts.PARAMETER_* | ...)
+ */
+ protected Export(CardMngr cardManager, byte keyPair, byte key, short params) {
+ super(cardManager);
+ this.keyPair = keyPair;
+ this.key = key;
+ this.params = params;
+
+ byte[] data = new byte[2];
+ Util.setShort(data, 0, params);
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_EXPORT, keyPair, key, data);
+ }
+
+ @Override
+ public Response.Export send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Export(response, elapsed, keyPair, key, params);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class ECDH extends Command {
+ private byte pubkey;
+ private byte privkey;
+ private byte export;
+ private short corruption;
+ private byte type;
+
+ /**
+ * Creates the INS_ECDH instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param pubkey keyPair to use for public key, (KEYPAIR_LOCAL || KEYPAIR_REMOTE)
+ * @param privkey keyPair to use for private key, (KEYPAIR_LOCAL || KEYPAIR_REMOTE)
+ * @param export whether to export ECDH secret
+ * @param corruption whether to invalidate the pubkey before ECDH (EC_Consts.CORRUPTION_* | ...)
+ * @param type ECDH algorithm type (EC_Consts.KA_* | ...)
+ */
+ protected ECDH(CardMngr cardManager, byte pubkey, byte privkey, byte export, short corruption, byte type) {
+ super(cardManager);
+ this.pubkey = pubkey;
+ this.privkey = privkey;
+ this.export = export;
+ this.corruption = corruption;
+ this.type = type;
+
+ byte[] data = new byte[]{export, 0,0, type};
+ Util.setShort(data, 1, corruption);
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ECDH, pubkey, privkey, data);
+ }
+
+ @Override
+ public Response.ECDH send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.ECDH(response, elapsed, pubkey, privkey, export, corruption, type);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class ECDH_direct extends Command {
+ private byte privkey;
+ private byte export;
+ private short corruption;
+ private byte type;
+ private byte[] pubkey;
+
+ protected ECDH_direct(CardMngr cardManager, byte privkey, byte export, short corruption, byte type, byte[] pubkey) {
+ super(cardManager);
+ this.privkey = privkey;
+ this.export = export;
+ this.corruption = corruption;
+ this.type = type;
+ this.pubkey = pubkey;
+
+ byte[] data = new byte[3 + pubkey.length];
+ Util.setShort(data, 0, corruption);
+ data[2] = type;
+ System.arraycopy(pubkey, 0, data, 3, pubkey.length);
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ECDH_DIRECT, privkey, export, data);
+ }
+
+ @Override
+ public Response.ECDH send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.ECDH(response, elapsed, ECTesterApplet.KEYPAIR_REMOTE, privkey, export, corruption, type);
+ }
+ }
+
+ public static class ECDSA extends Command {
+ private byte keyPair;
+ private byte export;
+ private byte[] raw;
+
+ /**
+ * Creates the INS_ECDSA instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair keyPair to use for signing and verification (KEYPAIR_LOCAL || KEYPAIR_REMOTE)
+ * @param export whether to export ECDSA signature
+ * @param raw data to sign, can be null, in which case random data is signed.
+ */
+ protected ECDSA(CardMngr cardManager, byte keyPair, byte export, byte[] raw) {
+ super(cardManager);
+ this.keyPair = keyPair;
+ this.export = export;
+ this.raw = raw;
+
+ int len = raw != null ? raw.length : 0;
+ byte[] data = new byte[2 + len];
+ Util.setShort(data, 0, (short) len);
+ if (raw != null) {
+ System.arraycopy(raw, 0, data, 2, len);
+ }
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ECDSA, keyPair, export, data);
+ }
+
+ @Override
+ public Response.ECDSA send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.ECDSA(response, elapsed, keyPair, export, raw);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Cleanup extends Command {
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ */
+ protected Cleanup(CardMngr cardManager) {
+ super(cardManager);
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_CLEANUP, 0, 0);
+ }
+
+ @Override
+ public Response.Cleanup send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Cleanup(response, elapsed);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Support extends Command {
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ */
+ protected Support(CardMngr cardManager) {
+ super(cardManager);
+
+ this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_SUPPORT, 0, 0);
+ }
+
+ @Override
+ public Response.Support send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Support(response, elapsed);
+ }
+ }
+}
+
diff --git a/src/cz/crcs/ectester/reader/DirtyLogger.java b/src/cz/crcs/ectester/reader/DirtyLogger.java
new file mode 100644
index 0000000..7a2c70d
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/DirtyLogger.java
@@ -0,0 +1,56 @@
+package cz.crcs.ectester.reader;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * @author Petr Svenda petr@svenda.com
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class DirtyLogger {
+ FileWriter log;
+ boolean systemOut;
+
+ public DirtyLogger(String filePath) throws IOException {
+ this(filePath, true);
+ }
+
+ public DirtyLogger(String filePath, boolean systemOut) throws IOException {
+ if (filePath != null)
+ this.log = new FileWriter(filePath);
+ this.systemOut = systemOut;
+ }
+
+ public void println() {
+ print("\n");
+ }
+
+ public void println(String logLine) {
+ logLine += "\n";
+ print(logLine);
+ }
+
+ public void print(String logLine) {
+ if (systemOut) {
+ System.out.print(logLine);
+ }
+ if (log != null) {
+ try {
+ log.write(logLine);
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ void flush() {
+ try {
+ if (log != null) log.flush();
+ } catch (IOException ignored) {
+ }
+ }
+
+ void close() throws IOException {
+ if (log != null) log.close();
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/ECTester.java b/src/cz/crcs/ectester/reader/ECTester.java
new file mode 100644
index 0000000..3b5b84f
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ECTester.java
@@ -0,0 +1,788 @@
+/*
+ * Copyright (c) 2016-2017 Petr Svenda <petr@svenda.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package cz.crcs.ectester.reader;
+
+import cz.crcs.ectester.applet.ECTesterApplet;
+import cz.crcs.ectester.applet.EC_Consts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.ec.EC_Category;
+import cz.crcs.ectester.reader.ec.EC_Data;
+import cz.crcs.ectester.reader.ec.EC_Params;
+import javacard.security.KeyPair;
+import org.apache.commons.cli.*;
+
+import javax.smartcardio.CardException;
+import java.io.*;
+import java.nio.file.Files;
+import java.util.*;
+
+/**
+ * Reader part of ECTester, a tool for testing Elliptic curve support on javacards.
+ *
+ * @author Petr Svenda petr@svenda.com
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class ECTester {
+
+ private CardMngr cardManager;
+ private DirtyLogger systemOutLogger;
+ private EC_Store dataStore;
+ private Config cfg;
+
+ private Options opts = new Options();
+ private static final String CLI_HEADER = "\nECTester, a javacard Elliptic Curve Cryptograhy support tester/utility.\n\n";
+ private static final String CLI_FOOTER = "\nMIT Licensed\nCopyright (c) 2016-2017 Petr Svenda <petr@svenda.com>";
+
+ private static final byte[] SELECT_ECTESTERAPPLET = {(byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0a,
+ (byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31};
+ private static final byte[] AID = {(byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31};
+ private static final byte[] INSTALL_DATA = new byte[10];
+
+ private void run(String[] args) {
+ try {
+ CommandLine cli = parseArgs(args);
+
+ //if help, print and quit
+ if (cli.hasOption("help")) {
+ help();
+ return;
+ }
+ cfg = new Config();
+
+ //if not, read other options first, into attributes, then do action
+ if (!cfg.readOptions(cli)) {
+ return;
+ }
+
+ dataStore = new EC_Store();
+ //if list, print and quit
+ if (cli.hasOption("list-named")) {
+ list();
+ return;
+ }
+
+ //init CardManager
+ cardManager = new CardMngr(cfg.verbose, cfg.simulate);
+
+ //connect or simulate connection
+ if (cfg.simulate) {
+ if (!cardManager.prepareLocalSimulatorApplet(AID, INSTALL_DATA, ECTesterApplet.class)) {
+ System.err.println("Failed to establish a simulator.");
+ System.exit(1);
+ }
+ } else {
+ if (!cardManager.connectToCardSelect()) {
+ System.err.println("Failed to connect to card.");
+ System.exit(1);
+ }
+ cardManager.send(SELECT_ECTESTERAPPLET);
+ }
+
+ systemOutLogger = new DirtyLogger(cfg.log, true);
+
+ //do action
+ if (cli.hasOption("export")) {
+ export();
+ } else if (cli.hasOption("generate")) {
+ generate();
+ } else if (cli.hasOption("test")) {
+ test();
+ } else if (cli.hasOption("ecdh") || cli.hasOption("ecdhc")) {
+ ecdh();
+ } else if (cli.hasOption("ecdsa")) {
+ ecdsa();
+ }
+
+ //disconnect
+ cardManager.disconnectFromCard();
+ systemOutLogger.close();
+
+ } catch (MissingOptionException moex) {
+ System.err.println("Missing required options, one of:");
+ for (Object opt : moex.getMissingOptions().toArray()) {
+ if (opt instanceof OptionGroup) {
+ for (Option o : ((OptionGroup) opt).getOptions()) {
+ System.err.print("-" + o.getOpt());
+
+ if (o.hasLongOpt()) {
+ System.err.print("\t/ --" + o.getLongOpt() + " ");
+ }
+
+ if (o.hasArg()) {
+ if (o.hasOptionalArg()) {
+ System.err.print("[" + o.getArgName() + "] ");
+ } else {
+ System.err.print("<" + o.getArgName() + "> ");
+ }
+ }
+
+ if (o.getDescription() != null) {
+ System.err.print("\t\t\t" + o.getDescription());
+ }
+ System.err.println();
+ }
+ } else if (opt instanceof String) {
+ System.err.println(opt);
+ }
+ }
+ } catch (MissingArgumentException maex) {
+ System.err.println("Option, " + maex.getOption().getOpt() + " requires an argument: " + maex.getOption().getArgName());
+ } catch (NumberFormatException nfex) {
+ System.err.println("Not a number. " + nfex.getMessage());
+ } catch (FileNotFoundException fnfe) {
+ System.err.println("File " + fnfe.getMessage() + " not found.");
+ } catch (ParseException | IOException ex) {
+ System.err.println(ex.getMessage());
+ } catch (CardException ex) {
+ if (systemOutLogger != null)
+ systemOutLogger.println(ex.getMessage());
+ } finally {
+ if (systemOutLogger != null)
+ systemOutLogger.flush();
+ }
+ }
+
+ /**
+ * Parses command-line options.
+ *
+ * @param args cli arguments
+ * @return parsed CommandLine object
+ * @throws ParseException if there are any problems encountered while parsing the command line tokens
+ */
+ private CommandLine parseArgs(String[] args) throws ParseException {
+ /*
+ * Actions:
+ * -h / --help
+ * -e / --export
+ * -g / --generate [amount]
+ * -t / --test [test_suite]
+ * -dh / --ecdh [count]
+ * -dhc / --ecdhc [count]
+ * -dsa / --ecdsa [count]
+ * -ln / --list-named [obj]
+ *
+ * Options:
+ * -b / --bit-size <b> // -a / --all
+ *
+ * -fp / --prime-field
+ * -f2m / --binary-field
+ *
+ * -u / --custom
+ * -nc / --named-curve <cat/id>
+ * -c / --curve <curve_file> field,a,b,gx,gy,r,k
+ *
+ * -pub / --public <pubkey_file> wx,wy
+ * -npub / --named-public <cat/id>
+ *
+ * -priv / --private <privkey_file> s
+ * -npriv / --named-private <cat/id>
+ *
+ * -k / --key <key_file> wx,wy,s
+ * -nk / --named-key <cat/id>
+ *
+ * -v / --verbose
+ *
+ * -i / --input <input_file>
+ * -o / --output <output_file>
+ * -l / --log [log_file]
+ *
+ * -f / --fresh
+ * -s / --simulate
+ * -y / --yes
+ */
+ OptionGroup actions = new OptionGroup();
+ actions.setRequired(true);
+ actions.addOption(Option.builder("h").longOpt("help").desc("Print help.").build());
+ actions.addOption(Option.builder("ln").longOpt("list-named").desc("Print the list of supported named curves and keys.").hasArg().argName("what").optionalArg(true).build());
+ actions.addOption(Option.builder("e").longOpt("export").desc("Export the defaut curve parameters of the card(if any).").build());
+ actions.addOption(Option.builder("g").longOpt("generate").desc("Generate [amount] of EC keys.").hasArg().argName("amount").optionalArg(true).build());
+ actions.addOption(Option.builder("t").longOpt("test").desc("Test ECC support. [test_suite]:\n- default:\n- invalid:\n- wrong:\n- nonprime:\n- test-vectors:").hasArg().argName("test_suite").optionalArg(true).build());
+ actions.addOption(Option.builder("dh").longOpt("ecdh").desc("Do ECDH, [count] times.").hasArg().argName("count").optionalArg(true).build());
+ actions.addOption(Option.builder("dhc").longOpt("ecdhc").desc("Do ECDHC, [count] times.").hasArg().argName("count").optionalArg(true).build());
+ actions.addOption(Option.builder("dsa").longOpt("ecdsa").desc("Sign data with ECDSA, [count] times.").hasArg().argName("count").optionalArg(true).build());
+ opts.addOptionGroup(actions);
+
+ OptionGroup size = new OptionGroup();
+ size.addOption(Option.builder("b").longOpt("bit-size").desc("Set curve size.").hasArg().argName("bits").build());
+ size.addOption(Option.builder("a").longOpt("all").desc("Test all curve sizes.").build());
+ opts.addOptionGroup(size);
+
+ opts.addOption(Option.builder("fp").longOpt("prime-field").desc("Use a prime field.").build());
+ opts.addOption(Option.builder("f2m").longOpt("binary-field").desc("Use a binary field.").build());
+
+ OptionGroup curve = new OptionGroup();
+ curve.addOption(Option.builder("nc").longOpt("named-curve").desc("Use a named curve, from CurveDB: <cat/id>").hasArg().argName("cat/id").build());
+ curve.addOption(Option.builder("c").longOpt("curve").desc("Use curve from file <curve_file> (field,a,b,gx,gy,r,k).").hasArg().argName("curve_file").build());
+ curve.addOption(Option.builder("u").longOpt("custom").desc("Use a custom curve (applet-side embedded, SECG curves).").build());
+ opts.addOptionGroup(curve);
+
+ OptionGroup pub = new OptionGroup();
+ pub.addOption(Option.builder("npub").longOpt("named-public").desc("Use public key from KeyDB: <cat/id>").hasArg().argName("cat/id").build());
+ pub.addOption(Option.builder("pub").longOpt("public").desc("Use public key from file <pubkey_file> (wx,wy).").hasArg().argName("pubkey_file").build());
+ opts.addOptionGroup(pub);
+
+ OptionGroup priv = new OptionGroup();
+ priv.addOption(Option.builder("npriv").longOpt("named-private").desc("Use private key from KeyDB: <cat/id>").hasArg().argName("cat/id").build());
+ priv.addOption(Option.builder("priv").longOpt("private").desc("Use private key from file <privkey_file> (s).").hasArg().argName("privkey_file").build());
+ opts.addOptionGroup(priv);
+
+ OptionGroup key = new OptionGroup();
+ key.addOption(Option.builder("nk").longOpt("named-key").desc("Use keyPair from KeyDB: <cat/id>").hasArg().argName("cat/id").build());
+ key.addOption(Option.builder("k").longOpt("key").desc("Use keyPair from file <key_file> (wx,wy,s).").hasArg().argName("key_file").build());
+ opts.addOptionGroup(key);
+
+ opts.addOption(Option.builder("i").longOpt("input").desc("Input from file <input_file>, for ECDSA signing.").hasArg().argName("input_file").build());
+ opts.addOption(Option.builder("o").longOpt("output").desc("Output into file <output_file>.").hasArg().argName("output_file").build());
+ opts.addOption(Option.builder("l").longOpt("log").desc("Log output into file [log_file].").hasArg().argName("log_file").optionalArg(true).build());
+ opts.addOption(Option.builder("v").longOpt("verbose").desc("Turn on verbose logging.").build());
+
+ opts.addOption(Option.builder("f").longOpt("fresh").desc("Generate fresh keys (set domain parameters before every generation).").build());
+ opts.addOption(Option.builder("s").longOpt("simulate").desc("Simulate a card with jcardsim instead of using a terminal.").build());
+ opts.addOption(Option.builder("y").longOpt("yes").desc("Accept all warnings and prompts.").build());
+
+ CommandLineParser parser = new DefaultParser();
+ return parser.parse(opts, args);
+ }
+
+ /**
+ * Prints help.
+ */
+ private void help() {
+ HelpFormatter help = new HelpFormatter();
+ help.setOptionComparator(null);
+ help.printHelp("ECTester.jar", CLI_HEADER, opts, CLI_FOOTER, true);
+ }
+
+ /**
+ * List categories and named curves.
+ */
+ private void list() {
+ Map<String, EC_Category> categories = dataStore.getCategories();
+ if (cfg.listNamed == null) {
+ // print all categories, briefly
+ for (EC_Category cat : categories.values()) {
+ System.out.println(cat);
+ }
+ } else if (categories.containsKey(cfg.listNamed)) {
+ // print given category
+ System.out.println(categories.get(cfg.listNamed));
+ } else {
+ // print given object
+ EC_Data object = dataStore.getObject(EC_Data.class, cfg.listNamed);
+ if (object != null) {
+ System.out.println(object);
+ } else {
+ System.err.println("Named object " + cfg.listNamed + " not found!");
+ }
+ }
+ }
+
+ /**
+ * Exports default card/simulation EC domain parameters to output file.
+ *
+ * @throws CardException if APDU transmission fails
+ * @throws IOException if an IO error occurs when writing to key file.
+ */
+ private void export() throws CardException, IOException {
+ byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M;
+
+ List<Response> sent = new LinkedList<>();
+ sent.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send());
+ sent.add(new Command.Clear(cardManager, ECTesterApplet.KEYPAIR_LOCAL).send());
+ sent.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL).send());
+
+ // Cofactor generally isn't set on the default curve parameters on cards,
+ // since its not necessary for ECDH, only ECDHC which not many cards implement
+ // TODO: check if its assumend to be == 1?
+ short domainAll = cfg.primeField ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M;
+ short domain = (short) (domainAll ^ EC_Consts.PARAMETER_K);
+ Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domainAll).send();
+ if (!export.successful()) {
+ export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domain).send();
+ }
+ sent.add(export);
+
+ systemOutLogger.println(Response.toString(sent));
+
+ EC_Params exported = new EC_Params(domain, export.getParams());
+
+ FileOutputStream out = new FileOutputStream(cfg.output);
+ exported.writeCSV(out);
+ out.close();
+ }
+
+ /**
+ * Generates EC keyPairs and outputs them to output file.
+ *
+ * @throws CardException if APDU transmission fails
+ * @throws IOException if an IO error occurs when writing to key file.
+ */
+ private void generate() throws CardException, IOException {
+ byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M;
+
+ new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send();
+ Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass);
+
+ FileWriter keysFile = new FileWriter(cfg.output);
+ keysFile.write("index;time;pubW;privS\n");
+
+ int generated = 0;
+ int retry = 0;
+ while (generated < cfg.generateAmount || cfg.generateAmount == 0) {
+ if ((cfg.fresh || generated == 0) && curve != null) {
+ Response fresh = curve.send();
+ systemOutLogger.println(fresh.toString());
+ }
+
+ Command.Generate generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL);
+ Response.Generate response = generate.send();
+ long elapsed = response.getDuration();
+
+ Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR).send();
+
+ if (!response.successful() || !export.successful()) {
+ if (retry < 10) {
+ retry++;
+ continue;
+ } else {
+ System.err.println("Keys could not be generated.");
+ break;
+ }
+ }
+ systemOutLogger.println(response.toString());
+
+ String pub = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false);
+ String priv = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false);
+ String line = String.format("%d;%d;%s;%s\n", generated, elapsed / 1000000, pub, priv);
+ keysFile.write(line);
+ keysFile.flush();
+ generated++;
+ }
+ Response cleanup = new Command.Cleanup(cardManager).send();
+ systemOutLogger.println(cleanup.toString());
+
+ keysFile.close();
+ }
+
+ /**
+ * Tests Elliptic curve support for a given curve/curves.
+ *
+ * @throws CardException if APDU transmission fails
+ * @throws IOException if an IO error occurs when writing to key file.
+ */
+ private void test() throws IOException, CardException {
+ TestSuite suite;
+
+ switch (cfg.testSuite) {
+ case "default":
+ suite = new TestSuite.Default(dataStore, cfg, systemOutLogger);
+ break;
+ case "test-vectors":
+ suite = new TestSuite.TestVectors(dataStore, cfg, systemOutLogger);
+ break;
+ default:
+ // These tests are dangerous, prompt before them.
+ System.out.println("The test you selected (" + cfg.testSuite + ") is potentially dangerous.");
+ System.out.println("Some of these tests have caused temporary DoS of some cards.");
+ if (!cfg.yes) {
+ System.out.print("Do you want to proceed? (y/n): ");
+ Scanner in = new Scanner(System.in);
+ String confirmation = in.nextLine();
+ if (!Arrays.asList("yes", "YES", "y", "Y").contains(confirmation)) {
+ return;
+ }
+ in.close();
+ }
+
+
+ switch (cfg.testSuite) {
+ case "wrong":
+ suite = new TestSuite.Wrong(dataStore, cfg, systemOutLogger);
+ break;
+ case "nonprime":
+ suite = new TestSuite.NonPrime(dataStore, cfg, systemOutLogger);
+ break;
+ case "invalid":
+ suite = new TestSuite.Invalid(dataStore, cfg, systemOutLogger);
+ break;
+ default:
+ System.err.println("Unknown test suite.");
+ return;
+ }
+ break;
+ }
+ suite.run(cardManager);
+ }
+
+ /**
+ * Performs ECDH key exchange.
+ *
+ * @throws CardException if APDU transmission fails
+ * @throws IOException if an IO error occurs when writing to key file.
+ */
+ private void ecdh() throws IOException, CardException {
+ byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M;
+ List<Response> prepare = new LinkedList<>();
+ prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass).send());
+ Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass);
+ if (curve != null)
+ prepare.add(curve.send());
+
+ systemOutLogger.println(Response.toString(prepare));
+
+ byte pubkey = (cfg.anyPublicKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL;
+ byte privkey = (cfg.anyPrivateKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL;
+
+ List<Command> generate = new LinkedList<>();
+ generate.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH));
+ if (cfg.anyPublicKey || cfg.anyPrivateKey || cfg.anyKey) {
+ generate.add(Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_REMOTE));
+ }
+
+ FileWriter out = null;
+ if (cfg.output != null) {
+ out = new FileWriter(cfg.output);
+ out.write("index;time;secret\n");
+ }
+
+ int retry = 0;
+ int done = 0;
+ while (done < cfg.ECDHCount) {
+ List<Response> ecdh = Command.sendAll(generate);
+
+ Response.ECDH perform = new Command.ECDH(cardManager, pubkey, privkey, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, cfg.ECDHKA).send();
+ ecdh.add(perform);
+ systemOutLogger.println(Response.toString(ecdh));
+
+ if (!perform.successful() || !perform.hasSecret()) {
+ if (retry < 10) {
+ ++retry;
+ continue;
+ } else {
+ System.err.println("Couldn't obtain ECDH secret from card response.");
+ break;
+ }
+ }
+
+ if (out != null) {
+ out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSecret(), false)));
+ }
+
+ ++done;
+ }
+ Response cleanup = new Command.Cleanup(cardManager).send();
+ systemOutLogger.println(cleanup.toString());
+
+ if (out != null)
+ out.close();
+ }
+
+ /**
+ * Performs ECDSA signature, on random or provided data.
+ *
+ * @throws CardException if APDU transmission fails
+ * @throws IOException if an IO error occurs when writing to key file.
+ */
+ private void ecdsa() throws CardException, IOException {
+ //read file, if asked to sign
+ byte[] data = null;
+ if (cfg.input != null) {
+ File in = new File(cfg.input);
+ long len = in.length();
+ if (len == 0) {
+ throw new FileNotFoundException(cfg.input);
+ }
+ data = Files.readAllBytes(in.toPath());
+ }
+
+ Command generate;
+ if (cfg.anyKeypart) {
+ generate = Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL);
+ } else {
+ generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL);
+ }
+
+ byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M;
+ List<Response> prepare = new LinkedList<>();
+ prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send());
+ Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass);
+ if (curve != null)
+ prepare.add(curve.send());
+
+ systemOutLogger.println(Response.toString(prepare));
+
+ FileWriter out = null;
+ if (cfg.output != null) {
+ out = new FileWriter(cfg.output);
+ out.write("index;time;signature\n");
+ }
+
+ int retry = 0;
+ int done = 0;
+ while (done < cfg.ECDSACount) {
+ List<Response> ecdsa = new LinkedList<>();
+ ecdsa.add(generate.send());
+
+ Response.ECDSA perform = new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, data).send();
+ ecdsa.add(perform);
+ systemOutLogger.println(Response.toString(ecdsa));
+
+ if (!perform.successful() || !perform.hasSignature()) {
+ if (retry < 10) {
+ ++retry;
+ continue;
+ } else {
+ System.err.println("Couldn't obtain ECDSA signature from card response.");
+ break;
+ }
+ }
+
+ if (out != null) {
+ out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSignature(), false)));
+ }
+
+ ++done;
+ }
+ Response cleanup = new Command.Cleanup(cardManager).send();
+ systemOutLogger.println(cleanup.toString());
+
+ if (out != null)
+ out.close();
+ }
+
+ public static void main(String[] args) {
+ ECTester app = new ECTester();
+ app.run(args);
+ }
+
+ public static class Config {
+
+ //Options
+ public int bits;
+ public boolean all;
+ public boolean primeField = false;
+ public boolean binaryField = false;
+
+ public String namedCurve;
+ public String curveFile;
+ public boolean customCurve = false;
+
+ public boolean anyPublicKey = false;
+ public String namedPublicKey;
+ public String publicKey;
+
+ public boolean anyPrivateKey = false;
+ public String namedPrivateKey;
+ public String privateKey;
+
+ public boolean anyKey = false;
+ public String namedKey;
+ public String key;
+
+ public boolean anyKeypart = false;
+
+ public String log;
+
+ public boolean verbose = false;
+ public String input;
+ public String output;
+ public boolean fresh = false;
+ public boolean simulate = false;
+ public boolean yes = false;
+
+ //Action-related ions
+ public String listNamed;
+ public String testSuite;
+ public int generateAmount;
+ public int ECDHCount;
+ public byte ECDHKA;
+ public int ECDSACount;
+
+ /**
+ * Reads and validates options, also sets defaults.
+ *
+ * @param cli cli object, with parsed args
+ * @return whether the options are valid.
+ */
+ boolean readOptions(CommandLine cli) {
+ bits = Integer.parseInt(cli.getOptionValue("bit-size", "0"));
+ all = cli.hasOption("all");
+ primeField = cli.hasOption("fp");
+ binaryField = cli.hasOption("f2m");
+
+ namedCurve = cli.getOptionValue("named-curve");
+ customCurve = cli.hasOption("custom");
+ curveFile = cli.getOptionValue("curve");
+
+ namedPublicKey = cli.getOptionValue("named-public");
+ publicKey = cli.getOptionValue("public");
+ anyPublicKey = (publicKey != null) || (namedPublicKey != null);
+
+ namedPrivateKey = cli.getOptionValue("named-private");
+ privateKey = cli.getOptionValue("private");
+ anyPrivateKey = (privateKey != null) || (namedPrivateKey != null);
+
+ namedKey = cli.getOptionValue("named-key");
+ key = cli.getOptionValue("key");
+ anyKey = (key != null) || (namedKey != null);
+ anyKeypart = anyKey || anyPublicKey || anyPrivateKey;
+
+ if (cli.hasOption("log")) {
+ log = cli.getOptionValue("log", String.format("ECTESTER_log_%d.log", System.currentTimeMillis() / 1000));
+ }
+
+ verbose = cli.hasOption("verbose");
+ input = cli.getOptionValue("input");
+ output = cli.getOptionValue("output");
+ fresh = cli.hasOption("fresh");
+ simulate = cli.hasOption("simulate");
+ yes = cli.hasOption("yes");
+
+ if (cli.hasOption("list-named")) {
+ listNamed = cli.getOptionValue("list-named");
+ return true;
+ }
+
+ if ((key != null || namedKey != null) && (anyPublicKey || anyPrivateKey)) {
+ System.err.print("Can only specify the whole key with --key/--named-key or pubkey and privkey with --public/--named-public and --private/--named-private.");
+ return false;
+ }
+ if (bits < 0) {
+ System.err.println("Bit-size must not be negative.");
+ return false;
+ }
+ if (bits == 0 && !all) {
+ System.err.println("You must specify either bit-size with -b or all bit-sizes with -a.");
+ return false;
+ }
+
+ if (key != null && namedKey != null || publicKey != null && namedPublicKey != null || privateKey != null && namedPrivateKey != null) {
+ System.err.println("You cannot specify both a named key and a key file.");
+ return false;
+ }
+
+ if (cli.hasOption("export")) {
+ if (primeField == binaryField) {
+ System.err.print("Need to specify field with -fp or -f2m. (not both)");
+ return false;
+ }
+ if (anyKeypart) {
+ System.err.println("Keys should not be specified when exporting curve params.");
+ return false;
+ }
+ if (namedCurve != null || customCurve || curveFile != null) {
+ System.err.println("Specifying a curve for curve export makes no sense.");
+ return false;
+ }
+ if (output == null) {
+ System.err.println("You have to specify an output file for curve parameter export.");
+ return false;
+ }
+ if (all) {
+ System.err.println("You have to specify curve bit-size with -b");
+ return false;
+ }
+
+ } else if (cli.hasOption("generate")) {
+ if (primeField == binaryField) {
+ System.err.print("Need to specify field with -fp or -f2m. (not both)");
+ return false;
+ }
+ if (anyKeypart) {
+ System.err.println("Keys should not be specified when generating keys.");
+ return false;
+ }
+ if (output == null) {
+ System.err.println("You have to specify an output file for the key generation process.");
+ return false;
+ }
+ if (all) {
+ System.err.println("You have to specify curve bit-size with -b");
+ return false;
+ }
+
+ generateAmount = Integer.parseInt(cli.getOptionValue("generate", "0"));
+ if (generateAmount < 0) {
+ System.err.println("Amount of keys generated cant be negative.");
+ return false;
+ }
+ } else if (cli.hasOption("test")) {
+ if (!(binaryField || primeField)) {
+ binaryField = true;
+ primeField = true;
+ }
+
+ testSuite = cli.getOptionValue("test", "default").toLowerCase();
+ String[] tests = new String[]{"default", "nonprime", "invalid", "test-vectors", "wrong"};
+ List<String> testsList = Arrays.asList(tests);
+ if (!testsList.contains(testSuite)) {
+ System.err.println("Unknown test case. Should be one of: " + Arrays.toString(tests));
+ return false;
+ }
+
+ } else if (cli.hasOption("ecdh") || cli.hasOption("ecdhc")) {
+ if (primeField == binaryField) {
+ System.err.print("Need to specify field with -fp or -f2m. (not both)");
+ return false;
+ }
+ if (all) {
+ System.err.println("You have to specify curve bit-size with -b");
+ return false;
+ }
+
+ if (cli.hasOption("ecdh")) {
+ ECDHCount = Integer.parseInt(cli.getOptionValue("ecdh", "1"));
+ ECDHKA = EC_Consts.KA_ECDH;
+ } else if (cli.hasOption("ecdhc")) {
+ ECDHCount = Integer.parseInt(cli.getOptionValue("ecdhc", "1"));
+ ECDHKA = EC_Consts.KA_ECDHC;
+ }
+ if (ECDHCount <= 0) {
+ System.err.println("ECDH count cannot be <= 0.");
+ return false;
+ }
+
+ } else if (cli.hasOption("ecdsa")) {
+ if (primeField == binaryField) {
+ System.err.print("Need to specify field with -fp or -f2m. (but not both)");
+ return false;
+ }
+ if (all) {
+ System.err.println("You have to specify curve bit-size with -b");
+ return false;
+ }
+
+ if ((anyPublicKey) != (anyPrivateKey) && !anyKey) {
+ System.err.println("You cannot only specify a part of a keypair.");
+ return false;
+ }
+
+ ECDSACount = Integer.parseInt(cli.getOptionValue("ecdsa", "1"));
+ if (ECDSACount <= 0) {
+ System.err.println("ECDSA count cannot be <= 0.");
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/ISO7816_status_words.txt b/src/cz/crcs/ectester/reader/ISO7816_status_words.txt
new file mode 100644
index 0000000..bf5af2b
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ISO7816_status_words.txt
@@ -0,0 +1,71 @@
+public interface ISO7816 {
+
+ // Fields
+ public static final byte INS_EXTERNAL_AUTHENTICATE = -126;
+ public static final byte INS_SELECT = -92;
+ public static final byte CLA_ISO7816 = 0;
+ public static final byte OFFSET_CDATA = 5;
+ public static final byte OFFSET_LC = 4;
+ public static final byte OFFSET_P2 = 3;
+ public static final byte OFFSET_P1 = 2;
+ public static final byte OFFSET_INS = 1;
+ public static final byte OFFSET_CLA = 0;
+ public static final short SW_FILE_FULL = 27268; 0x6A84
+ public static final short SW_UNKNOWN = 28416; 0x6F00
+ public static final short SW_CLA_NOT_SUPPORTED = 28160; 0x6E00
+ public static final short SW_INS_NOT_SUPPORTED = 27904; 0x6D00
+ public static final short SW_CORRECT_LENGTH_00 = 27648; 0x6C00
+ public static final short SW_WRONG_P1P2 = 27392; 0x6B00
+ public static final short SW_INCORRECT_P1P2 = 27270; 0x6A86
+ public static final short SW_RECORD_NOT_FOUND = 27267; 0x6A83
+ public static final short SW_FILE_NOT_FOUND = 27266; 0x6A82
+ public static final short SW_FUNC_NOT_SUPPORTED = 27265; 0x6A81
+ public static final short SW_WRONG_DATA = 27264; 0x6A80
+ public static final short SW_APPLET_SELECT_FAILED = 27033; 0x6999
+ public static final short SW_COMMAND_NOT_ALLOWED = 27014; 0x6986
+ public static final short SW_CONDITIONS_NOT_SATISFIED = 27013; 0x6985
+ public static final short SW_DATA_INVALID = 27012; 0x6984
+ public static final short SW_FILE_INVALID = 27011; 0x6983
+ public static final short SW_SECURITY_STATUS_NOT_SATISFIED = 27010; 0x6982
+ public static final short SW_WRONG_LENGTH = 26368; 0x6700
+ public static final short SW_BYTES_REMAINING_00 = 24832; 0x6100
+ public static final short SW_NO_ERROR = -28672; 0x9000
+}
+
+public interface JCStatus {
+static int ALGORITHM_NOT_SUPPORTED = 0x9484;
+static int APPLET_INVALIDATED = 0x6283;
+static int APPLET_SELECT_FAILED = 0x6999
+static int AUTHENTICATION_FAILED = 0x6300
+static int AUTHORIZATION_FAILED = 0x9482
+static int CHECKSUM_FAILED = 0x9584
+static int CLA_NOT_SUPPORTED = 0x6E00
+static int COMMAND_NOT_ALLOWED = 0x6986
+static int CONDITIONS_NOT_SATISFIED = 0x6985
+static int CORRECT_LENGTH_00 = 0x6C00
+static int DATA_INVALID = 0x6984
+static int DECRYPTION_FAILED = 0x9583
+static int FILE_FULL = 0x6A84
+static int FILE_INVALID = 0x6983
+static int FILE_NOT_FOUND = 0x6A82
+static int FUNC_NOT_SUPPORTED = 0x6A81
+static int INCORRECT_P1P2 = 0x6A86
+static int INS_NOT_SUPPORTED = 0x6D00
+static int INSTALLATION_FAILED = 0x9585
+static int INVALID_STATE = 0x9481
+static int NO_ERROR = 0x9000
+static int NO_SPECIFIC_DIAGNOSIS = 0x6400
+static int PIN_REQUIRED = 0x6982
+static int RECORD_NOT_FOUND = 0x6A83
+static int REFERENCE_DATA_NOT_FOUND = 0x6A88
+static int REGISTRATION_FAILED = 0x9586
+static int SECURITY_STATUS_NOT_SATISFIED = 0x6982
+static int SIGNATURE_CHECK_FAILED = 0x9582
+static int SM_INCORRECT = 0x6988
+static int SM_MISSING = 0x6987
+static int TRUNCATED_DATA = 0x6100
+static int UNKNOWN = 0x6F00
+static int WRONG_DATA = 0x6A80
+static int WRONG_LENGTH = 0x6700
+static int WRONG_P1P2 = 0x6B00
+} \ No newline at end of file
diff --git a/src/cz/crcs/ectester/reader/Response.java b/src/cz/crcs/ectester/reader/Response.java
new file mode 100644
index 0000000..d74724c
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/Response.java
@@ -0,0 +1,574 @@
+package cz.crcs.ectester.reader;
+
+import cz.crcs.ectester.applet.ECTesterApplet;
+import cz.crcs.ectester.applet.EC_Consts;
+import cz.crcs.ectester.reader.ec.EC_Curve;
+import javacard.framework.ISO7816;
+import javacard.security.KeyPair;
+
+import javax.smartcardio.ResponseAPDU;
+import java.util.List;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class Response {
+
+ private ResponseAPDU resp;
+ private long time;
+ private short[] sws;
+ private int numSW = 0;
+ private byte[][] params;
+ //TODO replace params with EC_Data?
+ private boolean success = true;
+
+ protected Response(ResponseAPDU response, long time) {
+ this.resp = response;
+ this.time = time;
+ }
+
+ protected void parse(int numSW, int numParams) {
+ this.numSW = numSW;
+ this.sws = new short[numSW];
+
+ byte[] data = resp.getData();
+ int offset = 0;
+
+ //parse SWs in response
+ for (int i = 0; i < numSW; ++i) {
+ if (getLength() >= (offset + 2)) {
+ short sw = Util.getShort(data, offset);
+ offset += 2;
+ sws[i] = sw;
+ if (sw != ISO7816.SW_NO_ERROR) {
+ success = false;
+ }
+ } else {
+ success = false;
+ }
+ }
+
+ if ((short) resp.getSW() != ISO7816.SW_NO_ERROR)
+ success = false;
+
+ //try to parse numParams..
+ params = new byte[numParams][];
+ for (int i = 0; i < numParams; i++) {
+ if (data.length - offset < 2) {
+ success = false;
+ break;
+ }
+ short paramLength = Util.getShort(data, offset);
+ offset += 2;
+ if (data.length < offset + paramLength) {
+ success = false;
+ break;
+ }
+ params[i] = new byte[paramLength];
+ System.arraycopy(data, offset, params[i], 0, paramLength);
+ offset += paramLength;
+ }
+ }
+
+ public ResponseAPDU getAPDU() {
+ return resp;
+ }
+
+ public long getDuration() {
+ return time;
+ }
+
+ public short getNaturalSW() {
+ return (short) resp.getSW();
+ }
+
+ public short getSW(int index) {
+ return sws[index];
+ }
+
+ public int getNumSW() {
+ return numSW;
+ }
+
+ public boolean hasParam(int index) {
+ return params.length >= index + 1 && params[index] != null;
+ }
+
+ public int getParamLength(int index) {
+ return params[index].length;
+ }
+
+ public byte[] getParam(int index) {
+ return params[index];
+ }
+
+ public byte[][] getParams() {
+ return params;
+ }
+
+ public int getLength() {
+ return resp.getNr();
+ }
+
+ public boolean successful() {
+ return this.success;
+ }
+
+ @Override
+ public abstract String toString();
+
+ public String toString(String inner) {
+ StringBuilder suffix = new StringBuilder();
+ for (int j = 0; j < getNumSW(); ++j) {
+ short sw = getSW(j);
+ if (sw != 0) {
+ suffix.append(" ").append(Util.getSWString(sw));
+ }
+ }
+ if (suffix.length() == 0) {
+ suffix.append(" [").append(Util.getSW(getNaturalSW())).append("]");
+ }
+ return String.format("%-62s:%4d ms : %s", inner, time / 1000000, suffix);
+ }
+
+ public static String toString(List<Response> responses) {
+ return toString(responses, null);
+ }
+
+ public static String toString(List<Response> responses, String prefix) {
+ if (prefix != null)
+ prefix += " | ";
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < responses.size(); ++i) {
+ Response r = responses.get(i);
+
+ if (prefix != null)
+ out.append(prefix);
+
+ String message = r.toString();
+ out.append(message);
+ if (i < responses.size() - 1) {
+ out.append("\n");
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ *
+ */
+ public static class Allocate extends Response {
+
+ private byte keyPair;
+ private short keyLength;
+ private byte keyClass;
+
+ protected Allocate(ResponseAPDU response, long time, byte keyPair, short keyLength, byte keyClass) {
+ super(response, time);
+ this.keyPair = keyPair;
+ this.keyLength = keyLength;
+ this.keyClass = keyClass;
+
+ int pairs = 0;
+ if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) pairs++;
+ if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) pairs++;
+ parse(pairs, 0);
+ }
+
+ @Override
+ public String toString() {
+ String field = keyClass == KeyPair.ALG_EC_FP ? "ALG_EC_FP" : "ALG_EC_F2M";
+ String key;
+ if (keyPair == ECTesterApplet.KEYPAIR_BOTH) {
+ key = "both keypairs";
+ } else {
+ key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return super.toString(String.format("Allocated %s %db %s", key, keyLength, field));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Clear extends Response {
+
+ private byte keyPair;
+
+ protected Clear(ResponseAPDU response, long time, byte keyPair) {
+ super(response, time);
+ this.keyPair = keyPair;
+
+ int pairs = 0;
+ if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) pairs++;
+ if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) pairs++;
+ parse(pairs, 0);
+ }
+
+ @Override
+ public String toString() {
+ String key;
+ if (keyPair == ECTesterApplet.KEYPAIR_BOTH) {
+ key = "both keypairs";
+ } else {
+ key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return super.toString(String.format("Cleared %s", key));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Set extends Response {
+
+ private byte keyPair;
+ private byte curve;
+ private short parameters;
+
+ protected Set(ResponseAPDU response, long time, byte keyPair, byte curve, short parameters) {
+ super(response, time);
+ this.keyPair = keyPair;
+ this.curve = curve;
+ this.parameters = parameters;
+
+ int pairs = 0;
+ if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) pairs++;
+ if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) pairs++;
+
+ parse(pairs, 0);
+ }
+
+ @Override
+ public String toString() {
+ String name;
+ switch (curve) {
+ case EC_Consts.CURVE_default:
+ name = "default";
+ break;
+ case EC_Consts.CURVE_external:
+ name = "external";
+ break;
+ default:
+ name = "custom";
+ break;
+ }
+ String what = "";
+ if (parameters == EC_Consts.PARAMETERS_DOMAIN_F2M || parameters == EC_Consts.PARAMETERS_DOMAIN_FP) {
+ what = "curve";
+ } else if (parameters == EC_Consts.PARAMETER_W) {
+ what = "pubkey";
+ } else if (parameters == EC_Consts.PARAMETER_S) {
+ what = "privkey";
+ } else if (parameters == EC_Consts.PARAMETERS_KEYPAIR) {
+ what = "keypair";
+ }
+
+ String pair;
+ if (keyPair == ECTesterApplet.KEYPAIR_BOTH) {
+ pair = "both keypairs";
+ } else {
+ pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return super.toString(String.format("Set %s %s parameters on %s", name, what, pair));
+ }
+
+ }
+
+ /**
+ *
+ */
+ public static class Corrupt extends Response {
+
+ private byte keyPair;
+ private byte key;
+ private short params;
+ private byte corruption;
+
+ protected Corrupt(ResponseAPDU response, long time, byte keyPair, byte key, short params, byte corruption) {
+ super(response, time);
+ this.keyPair = keyPair;
+ this.key = key;
+ this.params = params;
+ this.corruption = corruption;
+
+ int pairs = 0;
+ if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) pairs++;
+ if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) pairs++;
+
+ parse(pairs, 0);
+ }
+
+ @Override
+ public String toString() {
+ String corrupt = Util.getCorruption(corruption);
+
+ String pair;
+ if (keyPair == ECTesterApplet.KEYPAIR_BOTH) {
+ pair = "both keypairs";
+ } else {
+ pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return super.toString(String.format("Corrupted params of %s, %s", pair, corrupt));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Generate extends Response {
+
+ private byte keyPair;
+
+ protected Generate(ResponseAPDU response, long time, byte keyPair) {
+ super(response, time);
+ this.keyPair = keyPair;
+
+ int generated = 0;
+ if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) generated++;
+ if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) generated++;
+ parse(generated, 0);
+ }
+
+ @Override
+ public String toString() {
+ String key;
+ if (keyPair == ECTesterApplet.KEYPAIR_BOTH) {
+ key = "both keypairs";
+ } else {
+ key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return super.toString(String.format("Generated %s", key));
+ }
+
+ }
+
+ /**
+ *
+ */
+ public static class Export extends Response {
+
+ private byte keyPair;
+ private byte key;
+ private short parameters;
+
+ protected Export(ResponseAPDU response, long time, byte keyPair, byte key, short parameters) {
+ super(response, time);
+ this.keyPair = keyPair;
+ this.key = key;
+ this.parameters = parameters;
+
+ int exported = 0;
+ if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) exported++;
+ if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) exported++;
+ int keys = 0;
+ if ((key & EC_Consts.KEY_PUBLIC) != 0) keys++;
+ if ((key & EC_Consts.KEY_PRIVATE) != 0) keys++;
+ int paramCount = 0;
+ short mask = EC_Consts.PARAMETER_FP;
+ while (mask <= EC_Consts.PARAMETER_K) {
+ if ((mask & parameters) != 0) {
+ paramCount++;
+ }
+ mask = (short) (mask << 1);
+ }
+ int other = 0;
+ if ((key & EC_Consts.KEY_PUBLIC) != 0 && (parameters & EC_Consts.PARAMETER_W) != 0) other++;
+ if ((key & EC_Consts.KEY_PRIVATE) != 0 && (parameters & EC_Consts.PARAMETER_S) != 0) other++;
+
+ parse(exported, exported * keys * paramCount + exported * other);
+ }
+
+ private int getIndex(byte keyPair, short param) {
+ byte pair = ECTesterApplet.KEYPAIR_LOCAL;
+ int index = 0;
+ while (pair <= ECTesterApplet.KEYPAIR_REMOTE) {
+ short mask = EC_Consts.PARAMETER_FP;
+ while (mask <= EC_Consts.PARAMETER_S) {
+ if (pair == keyPair && param == mask) {
+ return index;
+ }
+ if ((parameters & mask) != 0 && (pair & keyPair) != 0) {
+ if (mask == EC_Consts.PARAMETER_W) {
+ if ((key & EC_Consts.KEY_PUBLIC) != 0)
+ index++;
+ } else if (mask == EC_Consts.PARAMETER_S) {
+ if ((key & EC_Consts.KEY_PRIVATE) != 0)
+ index++;
+ } else {
+ index++;
+ }
+ }
+ mask = (short) (mask << 1);
+ }
+
+ pair = (byte) (pair << 1);
+ }
+ return -1;
+ }
+
+ public boolean hasParameters(byte keyPair, short params) {
+ if ((keyPair & this.keyPair) == 0 || (params ^ parameters) != 0) {
+ return false;
+ }
+ short param = EC_Consts.PARAMETER_FP;
+ while (param <= EC_Consts.PARAMETER_S) {
+ short masked = (short) (param & params);
+ if (masked != 0 && !hasParameter(keyPair, masked)) {
+ return false;
+ }
+ param = (short) (param << 1);
+ }
+ return true;
+ }
+
+ public boolean hasParameter(byte keyPair, short param) {
+ if ((keyPair & this.keyPair) == 0 || (parameters & param) == 0) {
+ return false;
+ }
+ int index = getIndex(keyPair, param);
+ return index != -1 && hasParam(index);
+ }
+
+ public byte[] getParameter(byte keyPair, short param) {
+ return getParam(getIndex(keyPair, param));
+ }
+
+ @Override
+ public String toString() {
+ String source;
+ if (key == EC_Consts.KEY_BOTH) {
+ source = "both keys";
+ } else {
+ source = ((key == EC_Consts.KEY_PUBLIC) ? "public" : "private") + " key";
+ }
+ String pair;
+ if (keyPair == ECTesterApplet.KEYPAIR_BOTH) {
+ pair = "both keypairs";
+ } else {
+ pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return super.toString(String.format("Exported params from %s of %s", source, pair));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class ECDH extends Response {
+
+ private byte pubkey;
+ private byte privkey;
+ private byte export;
+ private short corruption;
+ private byte type;
+
+ protected ECDH(ResponseAPDU response, long time, byte pubkey, byte privkey, byte export, short corruption, byte type) {
+ super(response, time);
+ this.pubkey = pubkey;
+ this.privkey = privkey;
+ this.export = export;
+ this.corruption = corruption;
+ this.type = type;
+
+ parse(1, (export == ECTesterApplet.EXPORT_TRUE) ? 1 : 0);
+ }
+
+ public boolean hasSecret() {
+ return hasParam(0);
+ }
+
+ public byte[] getSecret() {
+ return getParam(0);
+ }
+
+ public int secretLength() {
+ return getParamLength(0);
+ }
+
+ @Override
+ public String toString() {
+ String algo = Util.getKA(type);
+
+ String pub = pubkey == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote";
+ String priv = privkey == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote";
+
+ String validity;
+ if (corruption == EC_Consts.CORRUPTION_NONE) {
+ validity = "unchanged";
+ } else {
+ validity = Util.getCorruption(corruption);
+ }
+ return super.toString(String.format("%s of %s pubkey and %s privkey(%s point)", algo, pub, priv, validity));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class ECDSA extends Response {
+
+ private byte keyPair;
+ private byte export;
+ private byte[] raw;
+
+ protected ECDSA(ResponseAPDU response, long time, byte keyPair, byte export, byte[] raw) {
+ super(response, time);
+ this.keyPair = keyPair;
+ this.export = export;
+ this.raw = raw;
+
+ parse(1, (export == ECTesterApplet.EXPORT_TRUE) ? 1 : 0);
+ }
+
+ public boolean hasSignature() {
+ return hasParam(0);
+ }
+
+ public byte[] getSignature() {
+ return getParam(0);
+ }
+
+ @Override
+ public String toString() {
+ String key = keyPair == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote";
+ String data = raw == null ? "random" : "provided";
+ return super.toString(String.format("ECDSA with %s keypair(%s data)", key, data));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Cleanup extends Response {
+
+ protected Cleanup(ResponseAPDU response, long time) {
+ super(response, time);
+
+ parse(1, 0);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString("Requested JCSystem object deletion");
+ }
+
+ }
+
+ /**
+ *
+ */
+ public static class Support extends Response {
+
+ protected Support(ResponseAPDU response, long time) {
+ super(response, time);
+
+ parse(3, 0);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString("Support of ECDH, ECDHC, ECDSA");
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/Test.java b/src/cz/crcs/ectester/reader/Test.java
new file mode 100644
index 0000000..157e360
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/Test.java
@@ -0,0 +1,82 @@
+package cz.crcs.ectester.reader;
+
+import javax.smartcardio.CardException;
+import java.util.function.BiFunction;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class Test {
+ private boolean hasRun = false;
+ private BiFunction<Command, Response, Result> callback;
+ private Result result;
+ private Result expected;
+ private Command command;
+ private Response response;
+
+ public Test(Command command, Result expected) {
+ this.command = command;
+ this.expected = expected;
+ }
+
+ public Test(Command command, Result expected, BiFunction<Command, Response, Result> callback) {
+ this(command, expected);
+ this.callback = callback;
+ }
+
+ public Command getCommand() {
+ return command;
+ }
+
+ public Response getResponse() {
+ return response;
+ }
+
+ public Result getResult() {
+ if (!hasRun) {
+ return null;
+ }
+ return result;
+ }
+
+ public Result getExpected() {
+ return expected;
+ }
+
+ public boolean ok() {
+ return result == expected || expected == Result.ANY;
+ }
+
+ public void run() throws CardException {
+ response = command.send();
+ if (callback != null) {
+ result = callback.apply(command, response);
+ } else {
+ if (response.successful()) {
+ result = Result.SUCCESS;
+ } else {
+ result = Result.FAILURE;
+ }
+ }
+ hasRun = true;
+ }
+
+ public boolean hasRun() {
+ return hasRun;
+ }
+
+ @Override
+ public String toString() {
+ if (hasRun) {
+ return (ok() ? "OK " : "NOK") + " " + response.toString();
+ } else {
+ return "";
+ }
+ }
+
+ public enum Result {
+ SUCCESS,
+ FAILURE,
+ ANY
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/TestSuite.java b/src/cz/crcs/ectester/reader/TestSuite.java
new file mode 100644
index 0000000..7118dd8
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/TestSuite.java
@@ -0,0 +1,314 @@
+package cz.crcs.ectester.reader;
+
+import cz.crcs.ectester.applet.ECTesterApplet;
+import cz.crcs.ectester.applet.EC_Consts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.ec.*;
+import javacard.security.KeyPair;
+
+import javax.smartcardio.CardException;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class TestSuite {
+
+ EC_Store dataStore;
+ ECTester.Config cfg;
+ DirtyLogger systemOut;
+ String name;
+ List<Test> tests = new LinkedList<>();
+
+ TestSuite(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut, String name) {
+ this.dataStore = dataStore;
+ this.cfg = cfg;
+ this.systemOut = systemOut;
+ this.name = name;
+ }
+
+ public List<Test> run(CardMngr cardManager) throws CardException, IOException {
+ for (Test t : tests) {
+ if (!t.hasRun()) {
+ t.run();
+ systemOut.println(t.toString());
+ }
+ }
+ return tests;
+ }
+
+ public List<Test> getTests() {
+ return Collections.unmodifiableList(tests);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ * @param generateExpected expected result of the Generate command
+ * @param ecdhExpected expected result of the ordinary ECDH command
+ * @param ecdsaExpected expected result of the ordinary ECDSA command
+ * @return tests to run
+ */
+ List<Test> testCurve(CardMngr cardManager, Test.Result generateExpected, Test.Result ecdhExpected, Test.Result ecdsaExpected) {
+ List<Test> tests = new LinkedList<>();
+
+ tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH), generateExpected));
+ tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH), ecdhExpected));
+ tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_COMPRESS, EC_Consts.KA_ECDH), ecdhExpected));
+ tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_ONE, EC_Consts.KA_ECDH), Test.Result.FAILURE));
+ tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_ZERO, EC_Consts.KA_ECDH), Test.Result.FAILURE));
+ tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_MAX, EC_Consts.KA_ECDH), Test.Result.FAILURE));
+ tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_FULLRANDOM, EC_Consts.KA_ECDH), Test.Result.FAILURE));
+ tests.add(new Test(new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, null), ecdsaExpected));
+
+ return tests;
+ }
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ * @param category category to test
+ * @param field field to test (KeyPair.ALG_EC_FP || KeyPair.ALG_EC_F2M)
+ * @param setExpected expected result of the Set (curve) command
+ * @param generateExpected expected result of the Generate command
+ * @param ecdhExpected expected result of the ordinary ECDH command
+ * @param ecdsaExpected expected result of the ordinary ECDSA command
+ * @return tests to run
+ */
+ List<Test> testCategory(CardMngr cardManager, String category, byte field, Test.Result setExpected, Test.Result generateExpected, Test.Result ecdhExpected, Test.Result ecdsaExpected) {
+ List<Test> tests = new LinkedList<>();
+ Map<String, EC_Curve> curves = dataStore.getObjects(EC_Curve.class, category);
+ if (curves == null)
+ return tests;
+ for (Map.Entry<String, EC_Curve> entry : curves.entrySet()) {
+ EC_Curve curve = entry.getValue();
+ if (curve.getField() == field && (curve.getBits() == cfg.bits || cfg.all)) {
+ tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), field), Test.Result.SUCCESS));
+ tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), setExpected));
+ tests.addAll(testCurve(cardManager, generateExpected, ecdhExpected, ecdsaExpected));
+ tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY));
+ }
+ }
+
+ return tests;
+ }
+
+ public static class Default extends TestSuite {
+
+ public Default(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) {
+ super(dataStore, cfg, systemOut, "default");
+ }
+
+ @Override
+ public List<Test> run(CardMngr cardManager) throws IOException, CardException {
+ tests.add(new Test(new Command.Support(cardManager), Test.Result.ANY));
+ if (cfg.namedCurve != null) {
+ if (cfg.primeField) {
+ tests.addAll(testCategory(cardManager, cfg.namedCurve, KeyPair.ALG_EC_FP, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS));
+ }
+ if (cfg.binaryField) {
+ tests.addAll(testCategory(cardManager, cfg.namedCurve, KeyPair.ALG_EC_F2M, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS));
+ }
+ } else {
+ if (cfg.all) {
+ if (cfg.primeField) {
+ //iterate over prime curve sizes used: EC_Consts.FP_SIZES
+ for (short keyLength : EC_Consts.FP_SIZES) {
+ defaultTests(cardManager, keyLength, KeyPair.ALG_EC_FP);
+ }
+ }
+ if (cfg.binaryField) {
+ //iterate over binary curve sizes used: EC_Consts.F2M_SIZES
+ for (short keyLength : EC_Consts.F2M_SIZES) {
+ defaultTests(cardManager, keyLength, KeyPair.ALG_EC_F2M);
+ }
+ }
+ } else {
+ if (cfg.primeField) {
+ defaultTests(cardManager, (short) cfg.bits, KeyPair.ALG_EC_FP);
+ }
+
+ if (cfg.binaryField) {
+ defaultTests(cardManager, (short) cfg.bits, KeyPair.ALG_EC_F2M);
+ }
+ }
+ }
+ return super.run(cardManager);
+ }
+
+ private void defaultTests(CardMngr cardManager, short keyLength, byte keyType) throws IOException {
+ tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, keyLength, keyType), Test.Result.SUCCESS));
+ Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, keyLength, keyType);
+ if (curve != null)
+ tests.add(new Test(curve, Test.Result.SUCCESS));
+ tests.addAll(testCurve(cardManager, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS));
+ tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY));
+ }
+ }
+
+ public static class TestVectors extends TestSuite {
+
+ public TestVectors(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) {
+ super(dataStore, cfg, systemOut, "test");
+ }
+
+ @Override
+ public List<Test> run(CardMngr cardManager) throws IOException, CardException {
+ /* Set original curves (secg/nist/brainpool). Set keypairs from test vectors.
+ * Do ECDH both ways, export and verify that the result is correct.
+ */
+ Map<String, EC_KAResult> results = dataStore.getObjects(EC_KAResult.class, "test");
+ for (EC_KAResult result : results.values()) {
+ EC_Curve curve = dataStore.getObject(EC_Curve.class, result.getCurve());
+ if (cfg.namedCurve != null && !(result.getCurve().startsWith(cfg.namedCurve) || result.getCurve().equals(cfg.namedCurve))) {
+ continue;
+ }
+ if (curve.getBits() != cfg.bits && !cfg.all) {
+ continue;
+ }
+ if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) {
+ continue;
+ }
+ EC_Params onekey = dataStore.getObject(EC_Keypair.class, result.getOneKey());
+ if (onekey == null) {
+ onekey = dataStore.getObject(EC_Key.Private.class, result.getOneKey());
+ }
+ EC_Params otherkey = dataStore.getObject(EC_Keypair.class, result.getOtherKey());
+ if (otherkey == null) {
+ otherkey = dataStore.getObject(EC_Key.Public.class, result.getOtherKey());
+ }
+ if (onekey == null || otherkey == null) {
+ throw new IOException("Test vector keys couldn't be located.");
+ }
+
+ tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS));
+ tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.SUCCESS));
+ //tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH), Test.Result.SUCCESS));
+ tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, onekey.flatten(EC_Consts.PARAMETER_S)), Test.Result.SUCCESS));
+ tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, otherkey.flatten(EC_Consts.PARAMETER_W)), Test.Result.SUCCESS));
+ tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, result.getKA()), Test.Result.SUCCESS, (command, response) -> {
+ Response.ECDH dh = (Response.ECDH) response;
+ if (!dh.successful() || !dh.hasSecret())
+ return Test.Result.FAILURE;
+ if (!Util.compareBytes(dh.getSecret(), 0, result.getParam(0), 0, dh.secretLength())) {
+ return Test.Result.FAILURE;
+ }
+ return Test.Result.SUCCESS;
+ }));
+ tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY));
+
+ }
+ return super.run(cardManager);
+ }
+ }
+
+ public static class NonPrime extends TestSuite {
+
+ public NonPrime(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) {
+ super(dataStore, cfg, systemOut, "nonprime");
+ }
+
+ @Override
+ public List<Test> run(CardMngr cardManager) throws IOException, CardException {
+ /* Do the default tests with the public keys set to provided smallorder keys
+ * over non-prime order curves. Essentially small subgroup attacks.
+ * These should fail, the curves aren't safe so that if the computation with
+ * a small order public key succeeds the private key modulo the public key order
+ * is revealed.
+ */
+ Map<String, EC_Key> keys = dataStore.getObjects(EC_Key.class, "nonprime");
+ for (EC_Key key : keys.values()) {
+ EC_Curve curve = dataStore.getObject(EC_Curve.class, key.getCurve());
+ if (cfg.namedCurve != null && !(key.getCurve().startsWith(cfg.namedCurve) || key.getCurve().equals(cfg.namedCurve))) {
+ continue;
+ }
+ if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) {
+ continue;
+ }
+ if ((curve.getBits() == cfg.bits || cfg.all)) {
+ tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS));
+ tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.ANY));
+ tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL), Test.Result.ANY));
+
+ //tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, key.getParams(), key.flatten()), Test.Result.ANY));
+ //tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH), Test.Result.FAILURE));
+ tests.add(new Test(new Command.ECDH_direct(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH, key.flatten()), Test.Result.FAILURE));
+
+ tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY));
+ }
+ }
+ return super.run(cardManager);
+ }
+ }
+
+ public static class Invalid extends TestSuite {
+
+ public Invalid(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) {
+ super(dataStore, cfg, systemOut, "invalid");
+ }
+
+ @Override
+ public List<Test> run(CardMngr cardManager) throws CardException, IOException {
+ /* Set original curves (secg/nist/brainpool). Generate local.
+ * Try ECDH with invalid public keys of increasing (or decreasing) order.
+ */
+ Map<String, EC_Key.Public> pubkeys = dataStore.getObjects(EC_Key.Public.class, "invalid");
+ Map<EC_Curve, List<EC_Key.Public>> curves = new HashMap<>();
+ for (EC_Key.Public key : pubkeys.values()) {
+ EC_Curve curve = dataStore.getObject(EC_Curve.class, key.getCurve());
+ if (cfg.namedCurve != null && !(key.getCurve().startsWith(cfg.namedCurve) || key.getCurve().equals(cfg.namedCurve))) {
+ continue;
+ }
+ if (curve.getBits() != cfg.bits && !cfg.all) {
+ continue;
+ }
+ if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) {
+ continue;
+ }
+ List<EC_Key.Public> keys = curves.getOrDefault(curve, new LinkedList<>());
+ keys.add(key);
+ curves.putIfAbsent(curve, keys);
+ }
+ for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curves.entrySet()) {
+ EC_Curve curve = e.getKey();
+ List<EC_Key.Public> keys = e.getValue();
+
+ tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS));
+ tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.SUCCESS));
+ tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL), Test.Result.SUCCESS));
+ for (EC_Key.Public pub : keys) {
+ // tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Test.Result.ANY));
+ // tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ANY), Test.Result.FAILURE));
+ tests.add(new Test(new Command.ECDH_direct(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ANY, pub.flatten()), Test.Result.FAILURE));
+ }
+ tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY));
+ }
+
+ return super.run(cardManager);
+ }
+ }
+
+ public static class Wrong extends TestSuite {
+
+ public Wrong(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) {
+ super(dataStore, cfg, systemOut, "wrong");
+ }
+
+ @Override
+ public List<Test> run(CardMngr cardManager) throws CardException, IOException {
+ /* Just do the default tests on the wrong curves.
+ * These should generally fail, the curves aren't curves.
+ */
+ if (cfg.primeField) {
+ tests.addAll(testCategory(cardManager, cfg.testSuite, KeyPair.ALG_EC_FP, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE));
+ }
+ if (cfg.binaryField) {
+ tests.addAll(testCategory(cardManager, cfg.testSuite, KeyPair.ALG_EC_F2M, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE));
+ }
+ return super.run(cardManager);
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/Util.java b/src/cz/crcs/ectester/reader/Util.java
new file mode 100644
index 0000000..e7b7338
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/Util.java
@@ -0,0 +1,342 @@
+package cz.crcs.ectester.reader;
+
+import cz.crcs.ectester.applet.ECTesterApplet;
+import cz.crcs.ectester.applet.EC_Consts;
+import javacard.framework.ISO7816;
+import javacard.security.CryptoException;
+
+/**
+ * Utility class, some byte/hex manipulation, convenient byte[] methods.
+ *
+ * @author Petr Svenda petr@svenda.com
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class Util {
+
+ public static short getShort(byte[] array, int offset) {
+ return (short) (((array[offset] & 0xFF) << 8) | (array[offset + 1] & 0xFF));
+ }
+
+ public static void setShort(byte[] array, int offset, short value) {
+ array[offset + 1] = (byte) (value & 0xFF);
+ array[offset] = (byte) ((value >> 8) & 0xFF);
+ }
+
+ public static boolean compareBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) {
+ for (int i = 0; i < length; ++i) {
+ byte a = one[i + oneOffset];
+ byte b = other[i + otherOffset];
+ if (a != b) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean allValue(byte[] array, byte value) {
+ for (byte a : array) {
+ if (a != value)
+ return false;
+ }
+ return true;
+ }
+
+ public static byte[] hexToBytes(String hex) {
+ return hexToBytes(hex, true);
+ }
+
+ public static byte[] hexToBytes(String hex, boolean bigEndian) {
+ hex = hex.replace(" ", "");
+ int len = hex.length();
+ StringBuilder sb = new StringBuilder();
+
+ if (len % 2 == 1) {
+ sb.append("0");
+ ++len;
+ }
+
+ if (bigEndian) {
+ sb.append(hex);
+ } else {
+ for (int i = 0; i < len / 2; ++i) {
+ if (sb.length() >= 2) {
+ sb.insert(sb.length() - 2, hex.substring(2 * i, 2 * i + 2));
+ } else {
+ sb.append(hex.substring(2 * i, 2 * i + 2));
+ }
+
+ }
+ }
+
+ String data = sb.toString();
+ byte[] result = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ result[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4)
+ + (Character.digit(data.charAt(i + 1), 16)));
+ }
+ return result;
+ }
+
+ public static String byteToHex(byte data) {
+ return String.format("%02x", data);
+ }
+
+ public static String bytesToHex(byte[] data) {
+ return bytesToHex(data, true);
+ }
+
+ public static String bytesToHex(byte[] data, boolean addSpace) {
+ return bytesToHex(data, 0, data.length, addSpace);
+ }
+
+ public static String bytesToHex(byte[] data, int offset, int len) {
+ return bytesToHex(data, offset, len, true);
+ }
+
+ public static String bytesToHex(byte[] data, int offset, int len, boolean addSpace) {
+ StringBuilder buf = new StringBuilder();
+ for (int i = offset; i < (offset + len); i++) {
+ buf.append(byteToHex(data[i]));
+ if (addSpace && i != (offset + len - 1)) {
+ buf.append(" ");
+ }
+ }
+ return (buf.toString());
+ }
+
+ public static byte[] concatenate(byte[]... arrays) {
+ int len = 0;
+ for (byte[] array : arrays) {
+ if (array == null)
+ continue;
+ len += array.length;
+ }
+ byte[] out = new byte[len];
+ int offset = 0;
+ for (byte[] array : arrays) {
+ if (array == null || array.length == 0)
+ continue;
+ System.arraycopy(array, 0, out, offset, array.length);
+ offset += array.length;
+ }
+ return out;
+ }
+
+ public static String getSWSource(short sw) {
+ switch (sw) {
+ case ISO7816.SW_NO_ERROR:
+ case ISO7816.SW_APPLET_SELECT_FAILED:
+ case ISO7816.SW_BYTES_REMAINING_00:
+ case ISO7816.SW_CLA_NOT_SUPPORTED:
+ case ISO7816.SW_COMMAND_NOT_ALLOWED:
+ case ISO7816.SW_CONDITIONS_NOT_SATISFIED:
+ case ISO7816.SW_CORRECT_LENGTH_00:
+ case ISO7816.SW_DATA_INVALID:
+ case ISO7816.SW_FILE_FULL:
+ case ISO7816.SW_FILE_INVALID:
+ case ISO7816.SW_FILE_NOT_FOUND:
+ case ISO7816.SW_FUNC_NOT_SUPPORTED:
+ case ISO7816.SW_INCORRECT_P1P2:
+ case ISO7816.SW_INS_NOT_SUPPORTED:
+ case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED:
+ case ISO7816.SW_RECORD_NOT_FOUND:
+ case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED:
+ case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED:
+ case ISO7816.SW_UNKNOWN:
+ case ISO7816.SW_WARNING_STATE_UNCHANGED:
+ case ISO7816.SW_WRONG_DATA:
+ case ISO7816.SW_WRONG_LENGTH:
+ case ISO7816.SW_WRONG_P1P2:
+ return "ISO";
+ case CryptoException.ILLEGAL_VALUE:
+ case CryptoException.UNINITIALIZED_KEY:
+ case CryptoException.NO_SUCH_ALGORITHM:
+ case CryptoException.INVALID_INIT:
+ case CryptoException.ILLEGAL_USE:
+ return "CryptoException";
+ case ECTesterApplet.SW_SIG_VERIFY_FAIL:
+ case ECTesterApplet.SW_DH_DHC_MISMATCH:
+ case ECTesterApplet.SW_KEYPAIR_NULL:
+ case ECTesterApplet.SW_KA_NULL:
+ case ECTesterApplet.SW_SIGNATURE_NULL:
+ case ECTesterApplet.SW_OBJECT_NULL:
+ return "ECTesterApplet";
+ default:
+ return "?";
+ }
+ }
+
+ public static String getSW(short sw) {
+ String str;
+ switch (sw) {
+ case ISO7816.SW_APPLET_SELECT_FAILED:
+ str = "APPLET_SELECT_FAILED";
+ break;
+ case ISO7816.SW_BYTES_REMAINING_00:
+ str = "BYTES_REMAINING";
+ break;
+ case ISO7816.SW_CLA_NOT_SUPPORTED:
+ str = "CLA_NOT_SUPPORTED";
+ break;
+ case ISO7816.SW_COMMAND_NOT_ALLOWED:
+ str = "COMMAND_NOT_ALLOWED";
+ break;
+ case ISO7816.SW_CONDITIONS_NOT_SATISFIED:
+ str = "CONDITIONS_NOT_SATISFIED";
+ break;
+ case ISO7816.SW_CORRECT_LENGTH_00:
+ str = "CORRECT_LENGTH";
+ break;
+ case ISO7816.SW_DATA_INVALID:
+ str = "DATA_INVALID";
+ break;
+ case ISO7816.SW_FILE_FULL:
+ str = "FILE_FULL";
+ break;
+ case ISO7816.SW_FILE_INVALID:
+ str = "FILE_INVALID";
+ break;
+ case ISO7816.SW_FILE_NOT_FOUND:
+ str = "FILE_NOT_FOUND";
+ break;
+ case ISO7816.SW_FUNC_NOT_SUPPORTED:
+ str = "FUNC_NOT_SUPPORTED";
+ break;
+ case ISO7816.SW_INCORRECT_P1P2:
+ str = "INCORRECT_P1P2";
+ break;
+ case ISO7816.SW_INS_NOT_SUPPORTED:
+ str = "INS_NOT_SUPPORTED";
+ break;
+ case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED:
+ str = "LOGICAL_CHANNEL_NOT_SUPPORTED";
+ break;
+ case ISO7816.SW_RECORD_NOT_FOUND:
+ str = "RECORD_NOT_FOUND";
+ break;
+ case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED:
+ str = "SECURE_MESSAGING_NOT_SUPPORTED";
+ break;
+ case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED:
+ str = "SECURITY_STATUS_NOT_SATISFIED";
+ break;
+ case ISO7816.SW_UNKNOWN:
+ str = "UNKNOWN";
+ break;
+ case ISO7816.SW_WARNING_STATE_UNCHANGED:
+ str = "WARNING_STATE_UNCHANGED";
+ break;
+ case ISO7816.SW_WRONG_DATA:
+ str = "WRONG_DATA";
+ break;
+ case ISO7816.SW_WRONG_LENGTH:
+ str = "WRONG_LENGTH";
+ break;
+ case ISO7816.SW_WRONG_P1P2:
+ str = "WRONG_P1P2";
+ break;
+ case CryptoException.ILLEGAL_VALUE:
+ str = "ILLEGAL_VALUE";
+ break;
+ case CryptoException.UNINITIALIZED_KEY:
+ str = "UNINITIALIZED_KEY";
+ break;
+ case CryptoException.NO_SUCH_ALGORITHM:
+ str = "NO_SUCH_ALG";
+ break;
+ case CryptoException.INVALID_INIT:
+ str = "INVALID_INIT";
+ break;
+ case CryptoException.ILLEGAL_USE:
+ str = "ILLEGAL_USE";
+ break;
+ case ECTesterApplet.SW_SIG_VERIFY_FAIL:
+ str = "SIG_VERIFY_FAIL";
+ break;
+ case ECTesterApplet.SW_DH_DHC_MISMATCH:
+ str = "DH_DHC_MISMATCH";
+ break;
+ case ECTesterApplet.SW_KEYPAIR_NULL:
+ str = "KEYPAIR_NULL";
+ break;
+ case ECTesterApplet.SW_KA_NULL:
+ str = "KA_NULL";
+ break;
+ case ECTesterApplet.SW_SIGNATURE_NULL:
+ str = "SIGNATURE_NULL";
+ break;
+ case ECTesterApplet.SW_OBJECT_NULL:
+ str = "OBJECT_NULL";
+ break;
+ default:
+ str = "unknown";
+ break;
+ }
+ return str;
+ }
+
+ public static String getSWString(short sw) {
+ if (sw == ISO7816.SW_NO_ERROR) {
+ return "OK\t(0x9000)";
+ } else {
+ String str = getSW(sw);
+ return String.format("fail\t(%s, 0x%04x)", str, sw);
+ }
+ }
+
+ public static String getCorruption(short corruptionType) {
+ String corrupt;
+ switch (corruptionType) {
+ case EC_Consts.CORRUPTION_NONE:
+ corrupt = "NONE";
+ break;
+ case EC_Consts.CORRUPTION_FIXED:
+ corrupt = "FIXED";
+ break;
+ case EC_Consts.CORRUPTION_ONE:
+ corrupt = "ONE";
+ break;
+ case EC_Consts.CORRUPTION_ZERO:
+ corrupt = "ZERO";
+ break;
+ case EC_Consts.CORRUPTION_ONEBYTERANDOM:
+ corrupt = "ONE_BYTE_RANDOM";
+ break;
+ case EC_Consts.CORRUPTION_FULLRANDOM:
+ corrupt = "FULL_RANDOM";
+ break;
+ case EC_Consts.CORRUPTION_INCREMENT:
+ corrupt = "INCREMENT";
+ break;
+ case EC_Consts.CORRUPTION_INFINITY:
+ corrupt = "INFINITY";
+ break;
+ case EC_Consts.CORRUPTION_COMPRESS:
+ corrupt = "COMPRESSED";
+ break;
+ case EC_Consts.CORRUPTION_MAX:
+ corrupt = "MAX";
+ break;
+ default:
+ corrupt = "unknown";
+ break;
+ }
+ return corrupt;
+ }
+
+ public static String getKA(byte ka) {
+ String algo = "";
+ if ((ka & EC_Consts.KA_ECDH) != 0 || ka == EC_Consts.KA_ANY) {
+ algo += "ECDH";
+ }
+ if (ka == EC_Consts.KA_BOTH) {
+ algo += "+";
+ } else if (ka == EC_Consts.KA_ANY) {
+ algo += "/";
+ }
+ if ((ka & EC_Consts.KA_ECDHC) != 0 || ka == EC_Consts.KA_ANY) {
+ algo += "ECDHC";
+ }
+ return algo;
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/ec/EC_Category.java b/src/cz/crcs/ectester/reader/ec/EC_Category.java
new file mode 100644
index 0000000..97dd1b4
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ec/EC_Category.java
@@ -0,0 +1,140 @@
+package cz.crcs.ectester.reader.ec;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Category {
+
+ private String name;
+ private String directory;
+ private String desc;
+
+ private Map<String, EC_Data> objects;
+
+
+ public EC_Category(String name, String directory) {
+ this.name = name;
+ this.directory = directory;
+ }
+
+ public EC_Category(String name, String directory, String desc) {
+ this(name, directory);
+ this.desc = desc;
+ }
+
+ public EC_Category(String name, String directory, String desc, Map<String, EC_Data> objects) {
+ this(name, directory, desc);
+ this.objects = objects;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDirectory() {
+ return directory;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public Map<String, EC_Data> getObjects() {
+ return Collections.unmodifiableMap(objects);
+ }
+
+ public <T extends EC_Data> Map<String, T> getObjects(Class<T> cls) {
+ Map<String, T> objs = new TreeMap<>();
+ for (Map.Entry<String, EC_Data> entry : objects.entrySet()) {
+ if (cls.isInstance(entry.getValue())) {
+ objs.put(entry.getKey(), cls.cast(entry.getValue()));
+ }
+ }
+ return Collections.unmodifiableMap(objs);
+ }
+
+ public <T extends EC_Data> T getObject(Class<T> cls, String id) {
+ EC_Data obj = objects.get(id);
+ if (cls.isInstance(obj)) {
+ return cls.cast(obj);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ out.append("\t- ").append(name).append((desc == null || desc.equals("")) ? "" : ": " + desc);
+ out.append(System.lineSeparator());
+
+ Map<String, EC_Curve> curves = getObjects(EC_Curve.class);
+ int size = curves.size();
+ if (size > 0) {
+ out.append("\t\tCurves: ");
+ for (Map.Entry<String, EC_Curve> curve : curves.entrySet()) {
+ out.append(curve.getKey());
+ size--;
+ if (size > 0)
+ out.append(", ");
+ }
+ out.append(System.lineSeparator());
+ }
+
+ Map<String, EC_Key> keys = getObjects(EC_Key.class);
+ size = keys.size();
+ if (size > 0) {
+ out.append("\t\tKeys: ");
+ for (Map.Entry<String, EC_Key> key : keys.entrySet()) {
+ out.append(key.getKey());
+ size--;
+ if (size > 0)
+ out.append(", ");
+ }
+ out.append(System.lineSeparator());
+ }
+
+ Map<String, EC_Keypair> keypairs = getObjects(EC_Keypair.class);
+ size = keypairs.size();
+ if (size > 0) {
+ out.append("\t\tKeypairs: ");
+ for (Map.Entry<String, EC_Keypair> key : keypairs.entrySet()) {
+ out.append(key.getKey());
+ size--;
+ if (size > 0)
+ out.append(", ");
+ }
+ out.append(System.lineSeparator());
+ }
+
+ Map<String, EC_KAResult> results = getObjects(EC_KAResult.class);
+ size = results.size();
+ if (size > 0) {
+ out.append("\t\tResults: ");
+ for (Map.Entry<String, EC_KAResult> result : results.entrySet()) {
+ out.append(result.getKey());
+ size--;
+ if (size > 0)
+ out.append(", ");
+ }
+ out.append(System.lineSeparator());
+ }
+ return out.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof EC_Category && Objects.equals(this.name, ((EC_Category) obj).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.name.hashCode() ^ this.directory.hashCode();
+ }
+
+}
diff --git a/src/cz/crcs/ectester/reader/ec/EC_Curve.java b/src/cz/crcs/ectester/reader/ec/EC_Curve.java
new file mode 100644
index 0000000..45080fb
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ec/EC_Curve.java
@@ -0,0 +1,50 @@
+package cz.crcs.ectester.reader.ec;
+
+import cz.crcs.ectester.applet.EC_Consts;
+import javacard.security.KeyPair;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Curve extends EC_Params {
+ private short bits;
+ private byte field;
+ private String desc;
+
+ /**
+ * @param bits
+ * @param field KeyPair.ALG_EC_FP or KeyPair.ALG_EC_F2M
+ */
+ public EC_Curve(short bits, byte field) {
+ super(field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M);
+ this.bits = bits;
+ this.field = field;
+ }
+
+ public EC_Curve(String id, short bits, byte field) {
+ this(bits, field);
+ this.id = id;
+ }
+
+ public EC_Curve(String id, short bits, byte field, String desc) {
+ this(id, bits, field);
+ this.desc = desc;
+ }
+
+ public short getBits() {
+ return bits;
+ }
+
+ public byte getField() {
+ return field;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public String toString() {
+ return "<" + getId() + "> " + (field == KeyPair.ALG_EC_FP ? "Prime" : "Binary") + " field Elliptic curve (" + String.valueOf(bits) + "b)" + (desc == null ? "" : ": " + desc);
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/ec/EC_Data.java b/src/cz/crcs/ectester/reader/ec/EC_Data.java
new file mode 100644
index 0000000..9dcbbe0
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ec/EC_Data.java
@@ -0,0 +1,196 @@
+package cz.crcs.ectester.reader.ec;
+
+import cz.crcs.ectester.reader.Util;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class EC_Data {
+ String id;
+ int count;
+ byte[][] data;
+
+ private static final Pattern HEX = Pattern.compile("(0x|0X)?[a-fA-F\\d]+");
+
+ EC_Data() {
+ }
+
+ EC_Data(int count) {
+ this.count = count;
+ this.data = new byte[count][];
+ }
+
+ EC_Data(byte[][] data) {
+ this.count = data.length;
+ this.data = data;
+ }
+
+ EC_Data(String id, int count) {
+ this(count);
+ this.id = id;
+ }
+
+ EC_Data(String id, byte[][] data) {
+ this(data);
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public byte[][] getData() {
+ return data;
+ }
+
+ public boolean hasData() {
+ return data != null;
+ }
+
+ public byte[] getParam(int index) {
+ return data[index];
+ }
+
+ public byte[] flatten() {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for (byte[] param : data) {
+ byte[] length = new byte[2];
+ Util.setShort(length, 0, (short) param.length);
+
+ out.write(length, 0, 2);
+ out.write(param, 0, param.length);
+ }
+
+ return out.toByteArray();
+ }
+
+ public String[] expand() {
+ List<String> out = new ArrayList<>(count);
+ for (byte[] param : data) {
+ out.add(Util.bytesToHex(param, false));
+ }
+
+ return out.toArray(new String[out.size()]);
+ }
+
+ private static byte[] pad(byte[] data) {
+ if (data.length == 1) {
+ return new byte[]{(byte) 0, data[0]};
+ } else if (data.length == 0 || data.length > 2) {
+ return data;
+ }
+ return null;
+ }
+
+ private static byte[] parse(String param) {
+ byte[] data;
+ if (param.startsWith("0x") || param.startsWith("0X")) {
+ data = Util.hexToBytes(param.substring(2));
+ } else {
+ data = Util.hexToBytes(param);
+ }
+ if (data == null)
+ return new byte[0];
+ if (data.length < 2)
+ return pad(data);
+ return data;
+ }
+
+ private boolean readHex(String[] hex) {
+ if (hex.length != count) {
+ return false;
+ }
+
+ for (int i = 0; i < count; ++i) {
+ this.data[i] = parse(hex[i]);
+ }
+ return true;
+ }
+
+ public boolean readCSV(InputStream in) {
+ Scanner s = new Scanner(in);
+
+ s.useDelimiter(",|;");
+ List<String> data = new LinkedList<>();
+ while (s.hasNext()) {
+ String field = s.next();
+ data.add(field.replaceAll("\\s+", ""));
+ }
+
+ if (data.isEmpty()) {
+ return false;
+ }
+ for (String param : data) {
+ if (!HEX.matcher(param).matches()) {
+ return false;
+ }
+ }
+ return readHex(data.toArray(new String[data.size()]));
+ }
+
+ public boolean readBytes(byte[] bytes) {
+ int offset = 0;
+ for (int i = 0; i < count; i++) {
+ if (bytes.length - offset < 2) {
+ return false;
+ }
+ short paramLength = Util.getShort(bytes, offset);
+ offset += 2;
+ if (bytes.length < offset + paramLength) {
+ return false;
+ }
+ data[i] = new byte[paramLength];
+ System.arraycopy(bytes, offset, data[i], 0, paramLength);
+ offset += paramLength;
+ }
+ return true;
+ }
+
+ public void writeCSV(OutputStream out) throws IOException {
+ Writer w = new OutputStreamWriter(out);
+ w.write(String.join(",", expand()));
+ w.flush();
+ }
+
+ @Override
+ public String toString() {
+ return String.join(",", expand());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof EC_Data) {
+ EC_Data other = (EC_Data) obj;
+ if (this.id != null || other.id != null) {
+ return Objects.equals(this.id, other.id);
+ }
+
+ if (this.count != other.count)
+ return false;
+ for (int i = 0; i < this.count; ++i) {
+ if (!Arrays.equals(this.data[i], other.data[i])) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ if (this.id != null) {
+ return this.id.hashCode();
+ }
+ return Arrays.deepHashCode(this.data);
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/ec/EC_KAResult.java b/src/cz/crcs/ectester/reader/ec/EC_KAResult.java
new file mode 100644
index 0000000..28115f7
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ec/EC_KAResult.java
@@ -0,0 +1,61 @@
+package cz.crcs.ectester.reader.ec;
+
+import cz.crcs.ectester.reader.Util;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_KAResult extends EC_Data {
+
+ private byte ka;
+ private String curve;
+ private String oneKey;
+ private String otherKey;
+
+ private String desc;
+
+ public EC_KAResult(byte ka, String curve, String oneKey, String otherKey) {
+ super(1);
+ this.ka = ka;
+ this.curve = curve;
+ this.oneKey = oneKey;
+ this.otherKey = otherKey;
+ }
+
+ public EC_KAResult(String id, byte ka, String curve, String oneKey, String otherKey) {
+ this(ka, curve, oneKey, otherKey);
+ this.id = id;
+ }
+
+ public EC_KAResult(String id, byte ka, String curve, String oneKey, String otherKey, String desc) {
+ this(id, ka, curve, oneKey, otherKey);
+ this.desc = desc;
+ }
+
+ public byte getKA() {
+ return ka;
+ }
+
+ public String getCurve() {
+ return curve;
+ }
+
+ public String getOneKey() {
+ return oneKey;
+ }
+
+ public String getOtherKey() {
+ return otherKey;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public String toString() {
+ String algo = Util.getKA(ka);
+ return "<" + getId() + "> " + algo + " result over " + curve + ", " + oneKey + " + " + otherKey + (desc == null ? "" : ": " + desc);
+ }
+
+}
diff --git a/src/cz/crcs/ectester/reader/ec/EC_Key.java b/src/cz/crcs/ectester/reader/ec/EC_Key.java
new file mode 100644
index 0000000..cecd228
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ec/EC_Key.java
@@ -0,0 +1,72 @@
+package cz.crcs.ectester.reader.ec;
+
+import cz.crcs.ectester.applet.EC_Consts;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Key extends EC_Params {
+
+ private String curve;
+ private String desc;
+
+ private EC_Key(short mask, String curve) {
+ super(mask);
+ this.curve = curve;
+ }
+
+ private EC_Key(short mask, String curve, String desc) {
+ this(mask, curve);
+ this.desc = desc;
+ }
+
+ private EC_Key(String id, short mask, String curve) {
+ this(mask, curve);
+ this.id = id;
+ }
+
+ private EC_Key(String id, short mask, String curve, String desc) {
+ this(mask, curve, desc);
+ this.id = id;
+ }
+
+ public String getCurve() {
+ return curve;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public static class Public extends EC_Key {
+
+ public Public(String curve) {
+ super(EC_Consts.PARAMETER_W, curve);
+ }
+
+ public Public(String curve, String desc) {
+ super(EC_Consts.PARAMETER_W, curve, desc);
+ }
+
+ @Override
+ public String toString() {
+ return "EC Public key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc());
+ }
+ }
+
+ public static class Private extends EC_Key {
+
+ public Private(String curve) {
+ super(EC_Consts.PARAMETER_S, curve);
+ }
+
+ public Private(String curve, String desc) {
+ super(EC_Consts.PARAMETER_S, curve, desc);
+ }
+
+ @Override
+ public String toString() {
+ return "<" + getId() + "> EC Private key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc());
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/ec/EC_Keypair.java b/src/cz/crcs/ectester/reader/ec/EC_Keypair.java
new file mode 100644
index 0000000..924906e
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ec/EC_Keypair.java
@@ -0,0 +1,34 @@
+package cz.crcs.ectester.reader.ec;
+
+import cz.crcs.ectester.applet.EC_Consts;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Keypair extends EC_Params {
+ private String curve;
+ private String desc;
+
+ public EC_Keypair(String curve) {
+ super(EC_Consts.PARAMETERS_KEYPAIR);
+ this.curve = curve;
+ }
+
+ public EC_Keypair(String curve, String desc) {
+ this(curve);
+ this.desc = desc;
+ }
+
+ public String getCurve() {
+ return curve;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public String toString() {
+ return "<" + getId() + "> EC Keypair, over " + curve + (desc == null ? "" : ": " + desc);
+ }
+}
diff --git a/src/cz/crcs/ectester/reader/ec/EC_Params.java b/src/cz/crcs/ectester/reader/ec/EC_Params.java
new file mode 100644
index 0000000..ea2e633
--- /dev/null
+++ b/src/cz/crcs/ectester/reader/ec/EC_Params.java
@@ -0,0 +1,146 @@
+package cz.crcs.ectester.reader.ec;
+
+import cz.crcs.ectester.applet.EC_Consts;
+import cz.crcs.ectester.reader.Util;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Params extends EC_Data {
+ private short params;
+
+ public EC_Params(short params) {
+ this.params = params;
+ this.count = numParams();
+ this.data = new byte[this.count][];
+ }
+
+ public EC_Params(short params, byte[][] data) {
+ this.params = params;
+ this.count = data.length;
+ this.data = data;
+ }
+
+ public EC_Params(String id, short params) {
+ this(params);
+ this.id = id;
+ }
+
+ public EC_Params(String id, short params, byte[][] data) {
+ this(params, data);
+ this.id = id;
+ }
+
+ public short getParams() {
+ return params;
+ }
+
+ public boolean hasParam(short param) {
+ return (params & param) != 0;
+ }
+
+ public int numParams() {
+ short paramMask = EC_Consts.PARAMETER_FP;
+ int num = 0;
+ while (paramMask <= EC_Consts.PARAMETER_S) {
+ if ((paramMask & params) != 0) {
+ if (paramMask == EC_Consts.PARAMETER_F2M) {
+ num += 3;
+ }
+ if (paramMask == EC_Consts.PARAMETER_W || paramMask == EC_Consts.PARAMETER_G) {
+ num += 1;
+ }
+ ++num;
+ }
+ paramMask = (short) (paramMask << 1);
+ }
+ return num;
+ }
+
+ @Override
+ public byte[] flatten() {
+ return flatten(params);
+ }
+
+ public byte[] flatten(short params) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ short paramMask = EC_Consts.PARAMETER_FP;
+ int i = 0;
+ while (paramMask <= EC_Consts.PARAMETER_S) {
+ short masked = (short) (this.params & params & paramMask);
+ short shallow = (short) (this.params & paramMask);
+ if (masked != 0) {
+ byte[] param = data[i];
+ if (masked == EC_Consts.PARAMETER_F2M) {
+ //add m, e_1, e_2, e_3
+ param = Util.concatenate(param, data[i + 1]);
+ if (!Util.allValue(data[i + 2], (byte) 0)) {
+ param = Util.concatenate(param, data[i + 2]);
+ }
+ if (!Util.allValue(data[i + 3], (byte) 0)) {
+ param = Util.concatenate(param, data[i + 3]);
+ }
+ if (!(param.length == 4 || param.length == 8))
+ throw new RuntimeException("PARAMETER_F2M length is not 8.(should be)");
+ }
+ if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) {
+ //read another param (the y coord) and put into X962 format.
+ byte[] y = data[i + 1];
+ param = Util.concatenate(new byte[]{4}, param, y); //<- ugly but works!
+ }
+ if (param.length == 0)
+ throw new RuntimeException("Empty parameter read?");
+
+ //write length
+ byte[] length = new byte[2];
+ Util.setShort(length, 0, (short) param.length);
+ out.write(length, 0, 2);
+ //write data
+ out.write(param, 0, param.length);
+ }
+ if (shallow == EC_Consts.PARAMETER_F2M) {
+ i += 4;
+ } else if (shallow == EC_Consts.PARAMETER_G || shallow == EC_Consts.PARAMETER_W) {
+ i += 2;
+ } else if (shallow != 0) {
+ i++;
+ }
+ paramMask = (short) (paramMask << 1);
+ }
+
+ return (out.size() == 0) ? null : out.toByteArray();
+ }
+
+ @Override
+ public String[] expand() {
+ List<String> out = new ArrayList<>();
+
+ short paramMask = EC_Consts.PARAMETER_FP;
+ int index = 0;
+ while (paramMask <= EC_Consts.PARAMETER_S) {
+ short masked = (short) (params & paramMask);
+ if (masked != 0) {
+ byte[] param = data[index];
+ if (masked == EC_Consts.PARAMETER_F2M) {
+ for (int i = 0; i < 4; ++i) {
+ out.add(Util.bytesToHex(data[index + i], false));
+ }
+ index += 4;
+ } else if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) {
+ out.add(Util.bytesToHex(param, false));
+ out.add(Util.bytesToHex(data[index + 1], false));
+ index += 2;
+ } else {
+ out.add(Util.bytesToHex(param, false));
+ index++;
+ }
+ }
+ paramMask = (short) (paramMask << 1);
+ }
+ return out.toArray(new String[out.size()]);
+ }
+}