aboutsummaryrefslogtreecommitdiff
path: root/reader
diff options
context:
space:
mode:
authorJ08nY2024-03-22 23:58:55 +0100
committerJ08nY2024-03-25 14:52:43 +0100
commit73af477a8774e1ede5dd8de6491eb353dc0b12bd (patch)
tree2d4e3b19bc5fb55308b886032312be76341736d4 /reader
parent64b95fa059295e1dc23371c849f2302c1c18f5b4 (diff)
downloadECTester-73af477a8774e1ede5dd8de6491eb353dc0b12bd.tar.gz
ECTester-73af477a8774e1ede5dd8de6491eb353dc0b12bd.tar.zst
ECTester-73af477a8774e1ede5dd8de6491eb353dc0b12bd.zip
Basic Gradle setup.
Diffstat (limited to 'reader')
-rw-r--r--reader/build.gradle.kts20
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/CardMngr.java478
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/ECTesterReader.java1100
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/ISO7816_status_words.txt71
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/command/Command.java920
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/output/FileTestWriter.java53
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/output/ResponseWriter.java43
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/output/TextTestWriter.java71
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/output/XMLTestWriter.java159
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/output/YAMLTestWriter.java119
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/response/Response.java526
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardCofactorSuite.java65
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardCompositeSuite.java122
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardCompressionSuite.java155
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardDefaultSuite.java141
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardDegenerateSuite.java69
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardEdgeCasesSuite.java332
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardInvalidSuite.java72
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardMiscSuite.java77
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardSignatureSuite.java68
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardTestSuite.java94
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardTestVectorSuite.java234
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardTwistSuite.java67
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CardWrongSuite.java232
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CommandTest.java85
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/CommandTestable.java44
-rw-r--r--reader/src/main/java/cz/crcs/ectester/reader/test/PerformanceTest.java141
27 files changed, 5558 insertions, 0 deletions
diff --git a/reader/build.gradle.kts b/reader/build.gradle.kts
new file mode 100644
index 0000000..7f011c8
--- /dev/null
+++ b/reader/build.gradle.kts
@@ -0,0 +1,20 @@
+plugins {
+ application
+}
+
+repositories {
+ mavenCentral()
+
+ // Repository with JCardSim, Globalplatform, etc, ...
+ maven("https://javacard.pro/maven")
+ maven("https://deadcode.me/mvn")
+}
+
+dependencies {
+ implementation(project(":common"))
+ implementation(project(":applet"))
+}
+
+application {
+ mainClass = "cz.crcs.ectester.reader.ECTesterReader"
+} \ No newline at end of file
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/CardMngr.java b/reader/src/main/java/cz/crcs/ectester/reader/CardMngr.java
new file mode 100644
index 0000000..0a01d9e
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/CardMngr.java
@@ -0,0 +1,478 @@
+package cz.crcs.ectester.reader;
+
+import com.licel.jcardsim.io.JavaxSmartCardInterface;
+import cz.crcs.ectester.common.util.ByteUtil;
+import javacard.framework.AID;
+import javacard.framework.Applet;
+import javacard.framework.ISO7816;
+
+import javax.smartcardio.*;
+import java.util.*;
+
+/**
+ * @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 JavaxSmartCardInterface simulator = null;
+
+ private boolean simulate = false;
+ private boolean verbose = true;
+ private boolean chunking = false;
+
+ 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;
+ }
+
+ private void connectWithHighest() throws CardException {
+ try {
+ card = terminal.connect("T=1");
+ } catch (CardException ex) {
+ if (verbose)
+ System.out.println("T=1 failed, trying protocol '*'");
+ card = terminal.connect("*");
+ if (card.getProtocol().equals("T=0")) {
+ chunking = true;
+ }
+ }
+ }
+
+ 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()) {
+ connectWithHighest();
+
+ if (verbose)
+ System.out.println("card: " + card);
+ channel = card.getBasicChannel();
+
+ //reset the card
+ if (verbose)
+ System.out.println(ByteUtil.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() + " - " + ByteUtil.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) {
+ connectWithHighest();
+ 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 void setChunking(boolean state) {
+ chunking = state;
+ }
+
+ public String getProtocol() {
+ if (simulate) {
+ return simulator.getProtocol();
+ } else {
+ if (card != null) {
+ return card.getProtocol();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ // Functions for CPLC taken and modified from https://github.com/martinpaljak/GlobalPlatformPro
+ private static final byte CLA_GP = (byte) 0x80;
+ private static final byte ISO7816_INS_GET_DATA = (byte) 0xCA;
+ private static final byte[] FETCH_GP_CPLC_APDU = {CLA_GP, ISO7816_INS_GET_DATA, (byte) 0x9F, (byte) 0x7F, (byte) 0x00};
+ private static final byte[] FETCH_ISO_CPLC_APDU = {ISO7816.CLA_ISO7816, ISO7816_INS_GET_DATA, (byte) 0x9F, (byte) 0x7F, (byte) 0x00};
+ private static final byte[] FETCH_GP_CARDDATA_APDU = {CLA_GP, ISO7816_INS_GET_DATA, (byte) 0x00, (byte) 0x66, (byte) 0x00};
+
+ public byte[] fetchCPLC() throws CardException {
+ // Try CPLC via GP
+ ResponseAPDU resp = send(FETCH_GP_CPLC_APDU);
+ // If GP CLA fails, try with ISO
+ if (resp.getSW() == (ISO7816.SW_CLA_NOT_SUPPORTED & 0xffff)) {
+ resp = send(FETCH_ISO_CPLC_APDU);
+ }
+ if (resp.getSW() == (ISO7816.SW_NO_ERROR & 0xffff)) {
+ return resp.getData();
+ }
+ return null;
+ }
+
+ public static final class CPLC {
+ public enum Field {
+ ICFabricator,
+ ICType,
+ OperatingSystemID,
+ OperatingSystemReleaseDate,
+ OperatingSystemReleaseLevel,
+ ICFabricationDate,
+ ICSerialNumber,
+ ICBatchIdentifier,
+ ICModuleFabricator,
+ ICModulePackagingDate,
+ ICCManufacturer,
+ ICEmbeddingDate,
+ ICPrePersonalizer,
+ ICPrePersonalizationEquipmentDate,
+ ICPrePersonalizationEquipmentID,
+ ICPersonalizer,
+ ICPersonalizationDate,
+ ICPersonalizationEquipmentID
+ }
+
+ private Map<Field, byte[]> values = new TreeMap<>();
+
+ public CPLC(byte[] data) {
+ if (data == null) {
+ return;
+ }
+ if (data.length < 3 || data[2] != 0x2A) {
+ throw new IllegalArgumentException("CPLC must be 0x2A bytes long");
+ }
+ //offset = TLVUtils.skipTag(data, offset, (short)0x9F7F);
+ short offset = 3;
+ values.put(Field.ICFabricator, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICType, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.OperatingSystemID, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.OperatingSystemReleaseDate, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.OperatingSystemReleaseLevel, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICFabricationDate, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICSerialNumber, Arrays.copyOfRange(data, offset, offset + 4));
+ offset += 4;
+ values.put(Field.ICBatchIdentifier, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICModuleFabricator, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICModulePackagingDate, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICCManufacturer, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICEmbeddingDate, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICPrePersonalizer, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICPrePersonalizationEquipmentDate, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICPrePersonalizationEquipmentID, Arrays.copyOfRange(data, offset, offset + 4));
+ offset += 4;
+ values.put(Field.ICPersonalizer, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICPersonalizationDate, Arrays.copyOfRange(data, offset, offset + 2));
+ offset += 2;
+ values.put(Field.ICPersonalizationEquipmentID, Arrays.copyOfRange(data, offset, offset + 4));
+ offset += 4;
+ }
+
+ public Map<Field, byte[]> values() {
+ return values;
+ }
+ }
+
+ public ATR getATR() {
+ if (simulate) {
+ return new ATR(simulator.getATR());
+ } else {
+ if (card != null) {
+ return card.getATR();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public CPLC getCPLC() throws CardException {
+ byte[] data = fetchCPLC();
+ return new CPLC(data);
+ }
+
+ public static String mapCPLCField(CPLC.Field field, byte[] value) {
+ switch (field) {
+ case ICFabricator:
+ String id = ByteUtil.bytesToHex(value, false);
+ String fabricatorName = "unknown";
+ if (id.equals("3060")) {
+ fabricatorName = "Renesas";
+ }
+ if (id.equals("4090")) {
+ fabricatorName = "Infineon";
+ }
+ if (id.equals("4180")) {
+ fabricatorName = "Atmel";
+ }
+ if (id.equals("4250")) {
+ fabricatorName = "Samsung";
+ }
+ if (id.equals("4790")) {
+ fabricatorName = "NXP";
+ }
+ return id + " (" + fabricatorName + ")";
+ default:
+ return ByteUtil.bytesToHex(value, false);
+ }
+ }
+
+
+ public static List<CardTerminal> getReaderList() {
+ try {
+ TerminalFactory factory = TerminalFactory.getDefault();
+ return factory.terminals().list();
+ } catch (CardException ex) {
+ System.err.println("Exception : " + ex);
+ return null;
+ }
+ }
+
+ private CommandAPDU chunk(CommandAPDU apdu) throws CardException {
+ if (verbose) {
+ System.out.print("Chunking:");
+ }
+ byte[] data = apdu.getBytes();
+ int numChunks = (data.length + 254) / 255;
+ for (int i = 0; i < numChunks; ++i) {
+ int chunkStart = i * 255;
+ int chunkLength = 255;
+ if (chunkStart + chunkLength > data.length) {
+ chunkLength = data.length - chunkStart;
+ }
+ if (verbose) {
+ System.out.print(" " + chunkLength);
+ }
+ byte[] chunk = new byte[chunkLength];
+ System.arraycopy(data, chunkStart, chunk, 0, chunkLength);
+ CommandAPDU cmd = new CommandAPDU(apdu.getCLA(), 0x7a, 0, 0, chunk);
+ ResponseAPDU resp;
+ if (simulate) {
+ resp = simulator.transmitCommand(cmd);
+ } else {
+ resp = channel.transmit(cmd);
+ }
+ if ((short) resp.getSW() != ISO7816.SW_NO_ERROR) {
+ throw new CardException("Chunking failed!");
+ }
+ }
+ if (verbose)
+ System.out.println();
+ return new CommandAPDU(apdu.getCLA(), 0x7b, 0, 0, 0xff);
+ }
+
+ public ResponseAPDU sendAPDU(CommandAPDU apdu) throws CardException {
+ if (verbose) {
+ System.out.println(">>>>");
+ System.out.println(apdu);
+
+ System.out.println(ByteUtil.bytesToHex(apdu.getBytes()));
+ }
+
+ long elapsed;
+ if (chunking && apdu.getNc() >= 0xff) {
+ apdu = chunk(apdu);
+ }
+
+ elapsed = -System.nanoTime();
+
+ ResponseAPDU responseAPDU = channel.transmit(apdu);
+
+ elapsed += System.nanoTime();
+
+ if (verbose) {
+ System.out.println(responseAPDU);
+ System.out.println(ByteUtil.bytesToHex(responseAPDU.getBytes()));
+ }
+
+ if (responseAPDU.getSW1() == (byte) 0x61) {
+ CommandAPDU apduToSend = new CommandAPDU((byte) 0x00,
+ (byte) 0xC0, (byte) 0x00, (byte) 0x00,
+ responseAPDU.getSW2());
+
+ responseAPDU = channel.transmit(apduToSend);
+ if (verbose)
+ System.out.println(ByteUtil.bytesToHex(responseAPDU.getBytes()));
+ }
+
+ if (verbose) {
+ System.out.println("<<<<");
+ System.out.println("Elapsed time (ms): " + elapsed / 1000000);
+ System.out.println("---------------------------------------------------------");
+ }
+ 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<? extends Applet> appletClass) {
+ simulator = new JavaxSmartCardInterface();
+ AID appletAID = new AID(appletAIDArray, (short) 0, (byte) appletAIDArray.length);
+
+ simulator.installApplet(appletAID, appletClass, installData, (short) 0, (byte) installData.length);
+ return simulator.selectApplet(appletAID);
+ }
+
+ public ResponseAPDU sendAPDUSimulator(CommandAPDU apdu) throws CardException {
+ if (verbose) {
+ System.out.println(">>>>");
+ System.out.println(apdu);
+ System.out.println(ByteUtil.bytesToHex(apdu.getBytes()));
+ }
+
+ if (chunking && apdu.getNc() >= 0xff) {
+ apdu = chunk(apdu);
+ }
+
+ ResponseAPDU response = simulator.transmitCommand(apdu);
+ byte[] responseBytes = response.getBytes();
+
+ if (verbose) {
+ System.out.println(response);
+ System.out.println(ByteUtil.bytesToHex(responseBytes));
+ System.out.println("<<<<");
+ }
+
+ return response;
+ }
+
+ public ResponseAPDU sendAPDUSimulator(byte[] apdu) throws CardException {
+ 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/reader/src/main/java/cz/crcs/ectester/reader/ECTesterReader.java b/reader/src/main/java/cz/crcs/ectester/reader/ECTesterReader.java
new file mode 100644
index 0000000..33111d0
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/ECTesterReader.java
@@ -0,0 +1,1100 @@
+/*
+ * ECTester, tool for testing Elliptic curve cryptography implementations.
+ * Copyright (c) 2016-2019 Petr Svenda <petr@svenda.com>
+ * Copyright (c) 2016-2019 Jan Jancar <johny@neuromancer.sk>
+ *
+ * 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.common.cli.CLITools;
+import cz.crcs.ectester.common.cli.Colors;
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.output.OutputLogger;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.util.*;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.output.FileTestWriter;
+import cz.crcs.ectester.reader.output.ResponseWriter;
+import cz.crcs.ectester.reader.response.Response;
+import cz.crcs.ectester.reader.test.*;
+import javacard.framework.ISO7816;
+import javacard.security.KeyPair;
+import org.apache.commons.cli.*;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.smartcardio.CardException;
+import javax.smartcardio.ResponseAPDU;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.*;
+import java.math.BigInteger;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.Security;
+import java.security.spec.ECParameterSpec;
+import java.util.*;
+import java.util.jar.Manifest;
+
+/**
+ * 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
+ * @version v0.3.3
+ */
+public class ECTesterReader {
+ private CardMngr cardManager;
+ private OutputLogger logger;
+ private ResponseWriter respWriter;
+ private Config cfg;
+
+ private Options opts = new Options();
+ public static final String VERSION = "v0.3.3";
+ public static String GIT_COMMIT = "";
+ private static String DESCRIPTION;
+ private static String LICENSE = "MIT Licensed\nCopyright © 2016-2019 Petr Svenda <petr@svenda.com>\nCopyright © 2016-2019 Jan Jancar <johny@neuromancer.sk>";
+ private static String CLI_HEADER;
+ private static String CLI_FOOTER = "\n" + LICENSE;
+
+ private static final byte[] SELECT_PREFIX = {(byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0c};
+ private static final byte[] AID_PREFIX = {(byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72};
+ private static final byte[] AID_CURRENT_VERSION = {(byte) 0x30, (byte) 0x33, (byte) 0x33}; // VERSION v0.3.3
+ private static final byte[] AID_SUFFIX_221 = {(byte) 0x62};
+ private static final byte[] AID_SUFFIX_222 = {(byte) 0x78};
+ private static final byte[] INSTALL_DATA = new byte[10];
+ private static final int TRY_VERSIONS = 10;
+
+ static {
+ ClassLoader cl = ECTesterReader.class.getClassLoader();
+ try {
+ URL url = cl.getResource("META-INF/MANIFEST.MF");
+ Manifest manifest = new Manifest(url.openStream());
+ String commit = manifest.getMainAttributes().getValue("Git-Commit");
+ GIT_COMMIT = (commit == null) ? "" : "(git " + commit + ")";
+ } catch (Exception ignored) {
+ }
+
+ DESCRIPTION = "ECTesterReader " + VERSION + GIT_COMMIT + ", a javacard Elliptic Curve Cryptography support tester/utility.";
+ CLI_HEADER = "\n" + DESCRIPTION + "\n\n";
+ }
+
+ private void run(String[] args) {
+ try {
+ CommandLine cli = parseArgs(args);
+
+ cfg = new Config();
+ boolean optsOk = cfg.readOptions(cli);
+
+ //if help, print and quit
+ if (cli.hasOption("help")) {
+ CLITools.help("ECTesterReader.jar", CLI_HEADER, opts, CLI_FOOTER, true);
+ return;
+ } else if (cli.hasOption("version")) {
+ CLITools.version(DESCRIPTION, LICENSE);
+ return;
+ }
+
+ //if opts failed, quit
+ if (!optsOk) {
+ return;
+ }
+
+ //if list, print and quit
+ if (cli.hasOption("list-named")) {
+ CLITools.listNamed(EC_Store.getInstance(), cli.getOptionValue("list-named"));
+ return;
+ }
+
+ if (cli.hasOption("list-suites")) {
+ listSuites();
+ return;
+ }
+
+ //init CardManager
+ cardManager = new CardMngr(cfg.verbose, cfg.simulate);
+
+ //connect or simulate connection
+ if (cfg.simulate) {
+ if (!cardManager.prepareLocalSimulatorApplet(ByteUtil.concatenate(AID_PREFIX, AID_CURRENT_VERSION, AID_SUFFIX_221), INSTALL_DATA, ECTesterApplet.class)) {
+ System.err.println(Colors.error("Failed to establish a simulator."));
+ System.exit(1);
+ } else {
+ cardManager.setChunking(true);
+ }
+ } else {
+ if (!cardManager.connectToCardSelect()) {
+ System.err.println(Colors.error("Failed to connect to card."));
+ System.exit(1);
+ }
+ //Try the highest known version first
+ byte[] versionByte = AID_CURRENT_VERSION.clone();
+ boolean selected = false;
+ for (int i = 0; i < TRY_VERSIONS; ++i) {
+ byte[] select222 = ByteUtil.concatenate(SELECT_PREFIX, AID_PREFIX, versionByte, AID_SUFFIX_222);
+ ResponseAPDU selectResp = cardManager.send(select222);
+ if ((short) selectResp.getSW() != ISO7816.SW_NO_ERROR) {
+ byte[] select221 = ByteUtil.concatenate(SELECT_PREFIX, AID_PREFIX, versionByte, AID_SUFFIX_221);
+ selectResp = cardManager.send(select221);
+ if ((short) selectResp.getSW() == ISO7816.SW_NO_ERROR) {
+ cardManager.setChunking(true);
+ selected = true;
+ break;
+ }
+ } else {
+ selected = true;
+ break;
+ }
+ // Count down by versions
+ if (versionByte[2] == 0x30) {
+ if (versionByte[1] == 0x30) {
+ if (versionByte[0] == 0x30) {
+ break;
+ } else {
+ versionByte[0]--;
+ versionByte[1] = 0x39;
+ versionByte[2] = 0x39;
+ }
+ } else {
+ versionByte[1]--;
+ versionByte[2] = 0x39;
+ }
+ } else {
+ versionByte[2]--;
+ }
+ }
+ if (!selected) {
+ System.err.println(Colors.error("Failed to select ECTester applet, is it installed?"));
+ cardManager.disconnectFromCard();
+ System.exit(1);
+ }
+ }
+
+ // Setup logger and respWriter
+ logger = new OutputLogger(true, cfg.log);
+ respWriter = new ResponseWriter(logger.getPrintStream());
+
+ // Try adding the BouncyCastleProvider, which might be used in some parts of ECTester.
+ try {
+ Security.addProvider(new BouncyCastleProvider());
+ } catch (SecurityException | NoClassDefFoundError ignored) {
+ }
+ // Make BouncyCastle more lenient when we work with signatures in ASN.1 DER format,
+ // cards sometimes are not fully compliant.
+ System.setProperty("org.bouncycastle.asn1.allow_unsafe_integer", "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();
+ } else if (cli.hasOption("info")) {
+ info();
+ }
+
+ //disconnect
+ cardManager.disconnectFromCard();
+ logger.close();
+
+ } catch (MissingOptionException moex) {
+ System.err.println(Colors.error("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(Colors.error("Option, " + maex.getOption().getOpt() + " requires an argument: " + maex.getOption().getArgName()));
+ } catch (NumberFormatException nfex) {
+ System.err.println(Colors.error("Not a number. " + nfex.getMessage()));
+ } catch (FileNotFoundException fnfe) {
+ System.err.println(Colors.error("File " + fnfe.getMessage() + " not found."));
+ } catch (ParseException | IOException ex) {
+ System.err.println(Colors.error(ex.getMessage()));
+ } catch (CardException ex) {
+ if (logger != null)
+ logger.println(ex.getMessage());
+ ex.printStackTrace();
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ } finally {
+ if (logger != null)
+ logger.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 {
+ OptionGroup actions = new OptionGroup();
+ actions.setRequired(true);
+ actions.addOption(Option.builder("V").longOpt("version").desc("Print version info.").build());
+ 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("ls").longOpt("list-suites").desc("List supported test suites.").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. Optionally specify a test number to run only a part of a test suite. <test_suite>:\n- default\n- compression\n- invalid\n- twist\n- degenerate\n- cofactor\n- wrong\n- signature\n- composite\n- test-vectors\n- edge-cases\n- miscellaneous").hasArg().argName("test_suite[:from[:to]]").optionalArg(true).build());
+ actions.addOption(Option.builder("dh").longOpt("ecdh").desc("Do EC KeyAgreement (ECDH...), [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());
+ actions.addOption(Option.builder("nf").longOpt("info").desc("Get applet info.").build());
+
+ opts.addOptionGroup(actions);
+
+ opts.addOption(Option.builder("b").longOpt("bit-size").desc("Set curve size.").hasArg().argName("bits").build());
+ 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>. The file can be prefixed by the format (one of text,yml,xml), such as: xml:<output_file>.").hasArgs().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().longOpt("format").desc("Output format to use. One of: text,yml,xml.").hasArg().argName("format").build());
+
+ opts.addOption(Option.builder("kb").longOpt("key-builder").desc("Allocate KeyPair using KeyBuilder.").build());
+ opts.addOption(Option.builder().longOpt("fixed").desc("Generate key(s) only once, keep them for later operations.").build());
+ opts.addOption(Option.builder().longOpt("fixed-private").desc("Generate private key only once, keep it for later ECDH.").build());
+ opts.addOption(Option.builder().longOpt("fixed-public").desc("Generate public key only once, keep it for later ECDH.").build());
+ opts.addOption(Option.builder("f").longOpt("fresh").desc("Generate fresh keys (set domain parameters before every generation).").build());
+ opts.addOption(Option.builder().longOpt("time").desc("Output better timing values, by running command in dry run mode and normal mode, and subtracting the two.").build());
+ opts.addOption(Option.builder().longOpt("time-unit").desc("Use given time unit in measurement, one of: milli, micro, nano.").hasArg().argName("unit").build());
+ opts.addOption(Option.builder().longOpt("cleanup").desc("Send the cleanup command trigerring JCSystem.requestObjectDeletion() after some operations.").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());
+ opts.addOption(Option.builder("to").longOpt("test-options").desc("Test options to use:\n- preset: Use preset semi-random private keys (derived from curve) instead of generating keypairs on the cards when the test needs one.\n- random: Use fully random private keys instead of generating keypairs.").hasArg().argName("options").build());
+
+ opts.addOption(Option.builder("ka").longOpt("ka-type").desc("Set KeyAgreement object [type], corresponds to JC.KeyAgreement constants.").hasArg().argName("type").optionalArg(true).build());
+ opts.addOption(Option.builder("sig").longOpt("sig-type").desc("Set Signature object [type], corresponds to JC.Signature constants.").hasArg().argName("type").optionalArg(true).build());
+ opts.addOption(Option.builder("C").longOpt("color").desc("Print stuff with color, requires ANSI terminal.").build());
+
+ CommandLineParser parser = new DefaultParser();
+ return parser.parse(opts, args);
+ }
+
+ private void listSuites() {
+ CardTestSuite[] suites = new CardTestSuite[]{
+ new CardDefaultSuite(null, null, null),
+ new CardTestVectorSuite(null, null, null),
+ new CardCompressionSuite(null, null, null),
+ new CardWrongSuite(null, null, null),
+ new CardDegenerateSuite(null, null, null),
+ new CardCofactorSuite(null, null, null),
+ new CardCompositeSuite(null, null, null),
+ new CardInvalidSuite(null, null, null),
+ new CardEdgeCasesSuite(null, null, null),
+ new CardSignatureSuite(null, null, null),
+ new CardTwistSuite(null, null, null),
+ new CardMiscSuite(null, null, null)};
+ for (CardTestSuite suite : suites) {
+ System.out.println(" - " + Colors.bold(suite.getName()));
+ for (String line : suite.getDescription()) {
+ System.out.println("\t" + line);
+ }
+ if (suite.getOptions() != null) {
+ System.out.println("\t" + Colors.underline("Options:") + " " + Arrays.toString(suite.getOptions()));
+ }
+ }
+ System.out.println();
+ System.out.println("For more information, look at the documentation at https://github.com/crocs-muni/ECTester.");
+ }
+
+ private void info() throws CardException {
+ Response.GetInfo info = new Command.GetInfo(cardManager).send();
+ System.out.println(String.format("Card ATR:\t\t\t\t%s", ByteUtil.bytesToHex(cardManager.getATR().getBytes(), false)));
+ System.out.println(String.format("Card protocol:\t\t\t\t%s", cardManager.getProtocol()));
+ System.out.println(String.format("ECTester applet version:\t\t%s", info.getVersion()));
+ System.out.println(String.format("ECTester applet APDU support:\t\t%s", (info.getBase() == CardConsts.BASE_221) ? "basic" : "extended length"));
+ System.out.println(String.format("JavaCard API version:\t\t\t%.1f", info.getJavaCardVersion()));
+ System.out.println(String.format("JavaCard supports system cleanup:\t%s", info.getCleanupSupport()));
+ System.out.println(String.format("Array sizes (apduBuf,ram,ram2,apduArr):\t%d %d %d %d", info.getApduBufferLength(), info.getRamArrayLength(), info.getRamArray2Length(), info.getApduArrayLength()));
+ }
+
+ /**
+ * 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, CardConsts.KEYPAIR_LOCAL, cfg.keyBuilder, cfg.bits, keyClass).send());
+ //sent.add(new Command.Clear(cardManager, CardConsts.KEYPAIR_LOCAL).send());
+ sent.add(new Command.Generate(cardManager, CardConsts.KEYPAIR_LOCAL).send());
+
+ // Also support exporting set parameters, to verify they are set correctly.
+ Command curve = Command.prepareCurve(cardManager, cfg, CardConsts.KEYPAIR_LOCAL, cfg.bits, keyClass);
+ if (curve != null) {
+ sent.add(curve.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 domain = cfg.primeField ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M;
+ Response.Export export = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domain).send();
+ if (!export.successful()) {
+ domain = (short) (domain ^ EC_Consts.PARAMETER_K);
+ export = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domain).send();
+ }
+ sent.add(export);
+
+ for (Response r : sent) {
+ respWriter.outputResponse(r);
+ }
+ if (cfg.cleanup) {
+ Response cleanup = new Command.Cleanup(cardManager).send();
+ respWriter.outputResponse(cleanup);
+ }
+
+ PrintStream out = new PrintStream(FileUtil.openStream(cfg.outputs));
+ byte[][] params = export.getParams();
+ for (int i = 0; i < params.length; ++i) {
+ out.print(ByteUtil.bytesToHex(params[i], false));
+ if (i != params.length - 1) {
+ out.print(",");
+ }
+ }
+ 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;
+ Command curve = Command.prepareCurve(cardManager, cfg, CardConsts.KEYPAIR_LOCAL, cfg.bits, keyClass);
+
+ Response allocate = new Command.Allocate(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.keyBuilder, cfg.bits, keyClass).send();
+ respWriter.outputResponse(allocate);
+
+ OutputStreamWriter keysFile = FileUtil.openFiles(cfg.outputs);
+ keysFile.write(String.format("index;genTime[%s];exportTime[%s];pubW;privS\n", cfg.timeUnit, cfg.timeUnit));
+
+ int generated = 0;
+ int retry = 0;
+ while (generated < cfg.generateAmount || cfg.generateAmount == 0) {
+ if ((cfg.fresh || generated == 0) && curve != null) {
+ Response fresh = curve.send();
+ respWriter.outputResponse(fresh);
+ }
+
+ Command.Generate generate = new Command.Generate(cardManager, CardConsts.KEYPAIR_LOCAL);
+ long time = 0;
+ if (cfg.time) {
+ time = -Command.dryRunTime(cardManager, generate, 2, respWriter);
+ }
+ Response.Generate response = generate.send();
+ time += response.getDuration();
+ respWriter.outputResponse(response);
+
+ Response.Export export = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR).send();
+ respWriter.outputResponse(export);
+
+ if (!response.successful() || !export.successful()) {
+ if (retry < 10) {
+ retry++;
+ continue;
+ } else {
+ System.err.println(Colors.error("Keys could not be generated/exported."));
+ break;
+ }
+ }
+
+ String pub = ByteUtil.bytesToHex(export.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false);
+ String priv = ByteUtil.bytesToHex(export.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false);
+ String line = String.format("%d;%d;%d;%s;%s\n", generated, Util.convertTime(time, cfg.timeUnit), Util.convertTime(export.getDuration(), cfg.timeUnit), pub, priv);
+ keysFile.write(line);
+ keysFile.flush();
+ generated++;
+ }
+ if (cfg.cleanup) {
+ Response cleanup = new Command.Cleanup(cardManager).send();
+ respWriter.outputResponse(cleanup);
+ }
+
+ keysFile.close();
+ }
+
+ /**
+ * Tests Elliptic curve support for a given curve/curves.
+ *
+ * @throws IOException if an IO error occurs
+ */
+ private void test() throws ParserConfigurationException, IOException {
+ TestWriter writer = new FileTestWriter(cfg.format, true, cfg.outputs);
+
+ CardTestSuite suite;
+
+ switch (cfg.testSuite) {
+ case "default":
+ suite = new CardDefaultSuite(writer, cfg, cardManager);
+ break;
+ case "test-vectors":
+ suite = new CardTestVectorSuite(writer, cfg, cardManager);
+ break;
+ case "compression":
+ suite = new CardCompressionSuite(writer, cfg, cardManager);
+ break;
+ case "miscellaneous":
+ suite = new CardMiscSuite(writer, cfg, cardManager);
+ break;
+ case "signature":
+ suite = new CardSignatureSuite(writer, cfg, cardManager);
+ break;
+ default:
+ // These run are dangerous, prompt before them.
+ System.out.println("The test you selected (" + cfg.testSuite + ") is potentially dangerous.");
+ System.out.println("Some of these run have caused temporary(or even permanent) 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().toLowerCase();
+ if (!Arrays.asList("yes", "y").contains(confirmation)) {
+ return;
+ }
+ in.close();
+ }
+ switch (cfg.testSuite) {
+ case "wrong":
+ suite = new CardWrongSuite(writer, cfg, cardManager);
+ break;
+ case "composite":
+ suite = new CardCompositeSuite(writer, cfg, cardManager);
+ break;
+ case "invalid":
+ suite = new CardInvalidSuite(writer, cfg, cardManager);
+ break;
+ case "degenerate":
+ suite = new CardDegenerateSuite(writer, cfg, cardManager);
+ break;
+ case "twist":
+ suite = new CardTwistSuite(writer, cfg, cardManager);
+ break;
+ case "cofactor":
+ suite = new CardCofactorSuite(writer, cfg, cardManager);
+ break;
+ case "edge-cases":
+ suite = new CardEdgeCasesSuite(writer, cfg, cardManager);
+ break;
+ default:
+ System.err.println(Colors.error("Unknown test suite."));
+ return;
+ }
+ break;
+ }
+
+ suite.run(cfg.testFrom, cfg.testTo);
+ }
+
+ /**
+ * 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;
+ Command curve = Command.prepareCurve(cardManager, cfg, CardConsts.KEYPAIR_BOTH, cfg.bits, keyClass);
+ List<Response> prepare = new LinkedList<>();
+ prepare.add(new Command.AllocateKeyAgreement(cardManager, cfg.ECKAType).send()); // Prepare KeyAgreement or required type
+ prepare.add(new Command.Allocate(cardManager, CardConsts.KEYPAIR_BOTH, cfg.keyBuilder, cfg.bits, keyClass).send());
+ if (curve != null)
+ prepare.add(curve.send());
+
+ for (Response r : prepare) {
+ respWriter.outputResponse(r);
+ }
+
+ OutputStreamWriter out = null;
+ if (cfg.outputs != null) {
+ out = FileUtil.openFiles(cfg.outputs);
+ out.write(String.format("index;time[%s];pubW;privS;secret[%s]\n", cfg.timeUnit, CardUtil.getKexHashName(cfg.ECKAType)));
+ }
+
+ Response gen = new Command.Generate(cardManager, CardConsts.KEYPAIR_BOTH).send();
+ respWriter.outputResponse(gen);
+ if (cfg.anyPublicKey || cfg.anyKey) {
+ Response prep = Command.prepareKey(cardManager, EC_Store.getInstance(), cfg, CardConsts.KEYPAIR_REMOTE, EC_Consts.PARAMETER_W).send();
+ respWriter.outputResponse(prep);
+ }
+ if (cfg.anyPrivateKey || cfg.anyKey) {
+ Response prep = Command.prepareKey(cardManager, EC_Store.getInstance(), cfg, CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S).send();
+ respWriter.outputResponse(prep);
+ }
+
+ byte kp = CardConsts.KEYPAIR_BOTH;
+ if (cfg.fixedPrivate || cfg.anyPrivateKey) {
+ kp ^= CardConsts.KEYPAIR_LOCAL;
+ }
+ if (cfg.fixedPublic || cfg.anyPublicKey) {
+ kp ^= CardConsts.KEYPAIR_REMOTE;
+ }
+ if (cfg.fixedKey || cfg.anyKey) {
+ kp = 0;
+ }
+
+ Command generate = null;
+ if (kp != 0) {
+ generate = new Command.Generate(cardManager, kp);
+ }
+
+ int retry = 0;
+ int done = 0;
+ while (done < cfg.ECKACount || cfg.ECKACount == 0) {
+ if (generate != null) {
+ Response regen = generate.send();
+ respWriter.outputResponse(regen);
+ }
+
+ Response.Export exportRemote = new Command.Export(cardManager, CardConsts.KEYPAIR_REMOTE, EC_Consts.KEY_PUBLIC, EC_Consts.PARAMETER_W).send();
+ respWriter.outputResponse(exportRemote);
+ Response.Export exportLocal = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PRIVATE, EC_Consts.PARAMETER_S).send();
+ respWriter.outputResponse(exportLocal);
+ byte[] pubkey_bytes = exportRemote.getParameter(CardConsts.KEYPAIR_REMOTE, EC_Consts.PARAMETER_W);
+ byte[] privkey_bytes = exportLocal.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S);
+
+ Command.ECDH perform = new Command.ECDH(cardManager, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, cfg.ECKAType);
+
+ long time = 0;
+ if (cfg.time) {
+ time = -Command.dryRunTime(cardManager, perform, 2, respWriter);
+ }
+
+ Response.ECDH result = perform.send();
+ respWriter.outputResponse(result);
+
+ if (!result.successful() || !result.hasSecret()) {
+ if (retry < 10) {
+ ++retry;
+ continue;
+ } else {
+ System.err.println(Colors.error("Couldn't obtain ECDH secret from card response."));
+ break;
+ }
+ }
+
+ if (out != null) {
+ time += result.getDuration();
+
+ out.write(String.format("%d;%d;%s;%s;%s\n", done, Util.convertTime(time, cfg.timeUnit), ByteUtil.bytesToHex(pubkey_bytes, false), ByteUtil.bytesToHex(privkey_bytes, false), ByteUtil.bytesToHex(result.getSecret(), false)));
+ out.flush();
+ }
+
+ ++done;
+ }
+ if (cfg.cleanup) {
+ Response cleanup = new Command.Cleanup(cardManager).send();
+ respWriter.outputResponse(cleanup);
+ }
+
+ 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;
+ 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());
+ } else {
+ Random rand = new Random();
+ data = new byte[32];
+ rand.nextBytes(data);
+ }
+
+ Command generate;
+ if (cfg.anyKeypart) {
+ generate = Command.prepareKey(cardManager, EC_Store.getInstance(), cfg, CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETERS_KEYPAIR);
+ } else {
+ generate = new Command.Generate(cardManager, CardConsts.KEYPAIR_LOCAL);
+ }
+
+ byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M;
+ List<Response> prepare = new LinkedList<>();
+ prepare.add(new Command.AllocateSignature(cardManager, cfg.ECDSAType).send());
+ prepare.add(new Command.Allocate(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.keyBuilder, cfg.bits, keyClass).send());
+ Command curve = Command.prepareCurve(cardManager, cfg, CardConsts.KEYPAIR_LOCAL, cfg.bits, keyClass);
+ if (curve != null)
+ prepare.add(curve.send());
+
+ for (Response r : prepare) {
+ respWriter.outputResponse(r);
+ }
+
+ OutputStreamWriter out = FileUtil.openFiles(cfg.outputs);
+ if (out != null) {
+ out.write(String.format("index;signTime[%s];verifyTime[%s];data;pubW;privS;signature[%s];nonce;valid\n", cfg.timeUnit, cfg.timeUnit, CardUtil.getSigHashAlgo(cfg.ECDSAType)));
+ }
+
+ Command.Export export = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR);
+ Response.Export exported = null;
+ if (cfg.fixedKey) {
+ respWriter.outputResponse(generate.send());
+ exported = export.send();
+ respWriter.outputResponse(exported);
+ }
+
+ int retry = 0;
+ int done = 0;
+ while (done < cfg.ECDSACount || cfg.ECDSACount == 0) {
+ if (!cfg.fixedKey) {
+ respWriter.outputResponse(generate.send());
+ exported = export.send();
+ respWriter.outputResponse(exported);
+ }
+
+ Command.ECDSA_sign sign = new Command.ECDSA_sign(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.ECDSAType, CardConsts.EXPORT_TRUE, data);
+
+ long signTime = 0;
+ if (cfg.time) {
+ signTime = -Command.dryRunTime(cardManager, sign, 2, respWriter);
+ }
+
+ Response.ECDSA signResp = sign.send();
+ signTime += signResp.getDuration();
+ respWriter.outputResponse(signResp);
+ if (!signResp.successful() || !signResp.hasSignature()) {
+ if (retry < 10) {
+ ++retry;
+ continue;
+ } else {
+ System.err.println(Colors.error("Couldn't obtain ECDSA signature from card response."));
+ break;
+ }
+ }
+ byte[] signature = signResp.getSignature();
+ Command.ECDSA_verify verify = new Command.ECDSA_verify(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.ECDSAType, data, signature);
+ long verifyTime = 0;
+ if (cfg.time) {
+ verifyTime = -Command.dryRunTime(cardManager, verify, 2, respWriter);
+ }
+ Response.ECDSA verifyResp = verify.send();
+ verifyTime += verifyResp.getDuration();
+ respWriter.outputResponse(verifyResp);
+
+ if (verifyResp.error()) {
+ if (retry < 10) {
+ ++retry;
+ continue;
+ } else {
+ System.err.println(Colors.error("Couldn't obtain ECDSA signature from card response."));
+ break;
+ }
+ }
+
+ if (out != null) {
+ String pub = ByteUtil.bytesToHex(exported.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false);
+ String priv = ByteUtil.bytesToHex(exported.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false);
+ String dataString = (cfg.input != null) ? "" : ByteUtil.bytesToHex(data, false);
+ BigInteger privkey = new BigInteger(1, exported.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S));
+ EC_Curve actualCurve = Command.findCurve(cfg, cfg.bits, keyClass);
+ String k = "";
+ if (actualCurve != null) {
+ ECParameterSpec params = actualCurve.toSpec();
+ BigInteger kValue = ECUtil.recoverSignatureNonce(signature, data, privkey, params, CardUtil.getSigHashName(cfg.ECDSAType), "ECDSA");
+ if (kValue != null) {
+ k = ByteUtil.bytesToHex(kValue.toByteArray(), false);
+ }
+ }
+ out.write(String.format("%d;%d;%d;%s;%s;%s;%s;%s;%d\n", done, Util.convertTime(signTime, cfg.timeUnit), Util.convertTime(verifyTime, cfg.timeUnit), dataString, pub, priv, ByteUtil.bytesToHex(signature, false), k, verifyResp.successful() ? 1 : 0));
+ out.flush();
+ }
+
+ ++done;
+ }
+ if (cfg.cleanup) {
+ Response cleanup = new Command.Cleanup(cardManager).send();
+ respWriter.outputResponse(cleanup);
+ }
+ if (out != null)
+ out.close();
+ }
+
+ public static void main(String[] args) {
+ ECTesterReader app = new ECTesterReader();
+ app.run(args);
+ }
+
+ public static class Config {
+
+ //Options
+ public short 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 boolean fixedKey = false;
+ public boolean fixedPrivate = false;
+ public boolean fixedPublic = false;
+ public byte keyBuilder;
+
+ public String log;
+
+ public boolean verbose = false;
+ public String input;
+ public String[] outputs;
+ public boolean fresh = false;
+ public boolean time = false;
+ public String timeUnit;
+ public boolean cleanup = false;
+ public boolean simulate = false;
+ public boolean yes = false;
+ public String format;
+ public boolean color;
+
+ //Action-related options
+ public String listNamed;
+ public String testSuite;
+ public int testFrom;
+ public int testTo;
+ public int generateAmount;
+ public int ECKACount;
+ public byte ECKAType = EC_Consts.KeyAgreement_ALG_EC_SVDP_DH;
+ public int ECDSACount;
+ public byte ECDSAType = EC_Consts.Signature_ALG_ECDSA_SHA;
+ public Set<String> testOptions;
+
+ /**
+ * 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 = Short.parseShort(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;
+ fixedKey = cli.hasOption("fixed");
+ fixedPrivate = cli.hasOption("fixed-private");
+ fixedPublic = cli.hasOption("fixed-public");
+ keyBuilder = cli.hasOption("key-builder") ? CardConsts.BUILD_KEYBUILDER : CardConsts.BUILD_KEYPAIR;
+
+ 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");
+ outputs = cli.getOptionValues("output");
+ fresh = cli.hasOption("fresh");
+ time = cli.hasOption("time");
+ cleanup = cli.hasOption("cleanup");
+ simulate = cli.hasOption("simulate");
+ yes = cli.hasOption("yes");
+ color = cli.hasOption("color");
+ Colors.enabled = color;
+
+ timeUnit = cli.getOptionValue("time-unit", "micro");
+ String[] times = new String[]{"milli", "micro", "nano"};
+ if (!Arrays.asList(times).contains(timeUnit)) {
+ System.err.println(Colors.error("Wrong time unit " + timeUnit + ". Should be one of " + Arrays.toString(times)));
+ return false;
+ }
+
+ if (cli.hasOption("list-named")) {
+ listNamed = cli.getOptionValue("list-named");
+ return true;
+ }
+
+ format = cli.getOptionValue("format");
+ String[] formats = new String[]{"text", "xml", "yaml", "yml"};
+ if (format != null && !Arrays.asList(formats).contains(format)) {
+ System.err.println(Colors.error("Wrong output format " + format + ". Should be one of " + Arrays.toString(formats)));
+ return false;
+ }
+
+ if ((key != null || namedKey != null) && (anyPublicKey || anyPrivateKey)) {
+ System.err.print(Colors.error("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(Colors.error("Bit-size must not be negative."));
+ return false;
+ }
+
+ if (key != null && namedKey != null || publicKey != null && namedPublicKey != null || privateKey != null && namedPrivateKey != null) {
+ System.err.println(Colors.error("You cannot specify both a named key and a key file."));
+ return false;
+ }
+
+ if (cli.hasOption("export")) {
+ if (primeField == binaryField) {
+ System.err.print(Colors.error("Need to specify field with -fp or -f2m. (not both)"));
+ return false;
+ }
+ if (anyKeypart) {
+ System.err.println(Colors.error("Keys should not be specified when exporting curve params."));
+ return false;
+ }
+ if (outputs == null) {
+ System.err.println(Colors.error("You have to specify an output file for curve parameter export."));
+ return false;
+ }
+ if (all || bits == 0) {
+ System.err.println(Colors.error("You have to specify curve bit-size with -b"));
+ return false;
+ }
+ } else if (cli.hasOption("generate")) {
+ if (primeField == binaryField) {
+ System.err.print(Colors.error("Need to specify field with -fp or -f2m. (not both)"));
+ return false;
+ }
+ if (anyKeypart) {
+ System.err.println(Colors.error("Keys should not be specified when generating keys."));
+ return false;
+ }
+ if (outputs == null) {
+ System.err.println(Colors.error("You have to specify an output file for the key generation process."));
+ return false;
+ }
+ if (all || bits == 0) {
+ System.err.println(Colors.error("You have to specify curve bit-size with -b"));
+ return false;
+ }
+
+ generateAmount = Integer.parseInt(cli.getOptionValue("generate", "0"));
+ if (generateAmount < 0) {
+ System.err.println(Colors.error("Amount of keys generated cant be negative."));
+ return false;
+ }
+ } else if (cli.hasOption("test")) {
+ if (!(binaryField || primeField)) {
+ binaryField = true;
+ primeField = true;
+ }
+
+ String suiteOpt = cli.getOptionValue("test", "default").toLowerCase();
+ if (suiteOpt.contains(":")) {
+ String[] parts = suiteOpt.split(":");
+ testSuite = parts[0];
+ try {
+ testFrom = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException nfe) {
+ System.err.println("Invalid test_from number: " + parts[1] + ".");
+ return false;
+ }
+ if (parts.length == 3) {
+ try {
+ testTo = Integer.parseInt(parts[2]);
+ } catch (NumberFormatException nfe) {
+ System.err.println("Invalid test_to number: " + parts[2] + ".");
+ return false;
+ }
+ } else if (parts.length != 2) {
+ System.err.println("Invalid test suite selection.");
+ return false;
+ } else {
+ testTo = -1;
+ }
+ } else {
+ testSuite = suiteOpt;
+ testFrom = 0;
+ testTo = -1;
+ }
+
+ String[] tests = new String[]{"default", "composite", "compression", "invalid", "degenerate", "test-vectors", "wrong", "twist", "cofactor", "edge-cases", "miscellaneous", "signature"};
+ String selected = null;
+ for (String test : tests) {
+ if (test.startsWith(testSuite)) {
+ if (selected != null) {
+ System.err.println(Colors.error("Test suite ambiguous " + test + " or " + selected + "?"));
+ return false;
+ } else {
+ selected = test;
+ }
+ }
+ }
+ if (selected == null) {
+ System.err.println(Colors.error("Unknown test suite " + testSuite + ". Should be one of: " + Arrays.toString(tests)));
+ return false;
+ } else {
+ testSuite = selected;
+ }
+
+ String[] opts = cli.getOptionValue("test-options", "").split(",");
+ List<String> validOpts = Arrays.asList("preset", "random");
+ testOptions = new HashSet<>();
+ for (String opt : opts) {
+ if (opt.equals("")) {
+ continue;
+ }
+ if (!validOpts.contains(opt)) {
+ System.err.println(Colors.error("Unknown test option " + opt + ". Should be one of: " + Arrays.toString(validOpts.toArray())));
+ return false;
+ } else {
+ testOptions.add(opt);
+ }
+ }
+
+ if (testOptions.contains("preset") && testOptions.contains("random")) {
+ System.err.println("Cannot have both preset and random option enabled.");
+ return false;
+ }
+ } else if (cli.hasOption("ecdh")) {
+ if (primeField == binaryField) {
+ System.err.print(Colors.error("Need to specify field with -fp or -f2m. (not both)"));
+ return false;
+ }
+ if (all || bits == 0) {
+ System.err.println(Colors.error("You have to specify curve bit-size with -b"));
+ return false;
+ }
+
+ ECKACount = Integer.parseInt(cli.getOptionValue("ecdh", "1"));
+ if (ECKACount < 0) {
+ System.err.println(Colors.error("ECDH count cannot be < 0."));
+ return false;
+ }
+
+ ECKAType = CardUtil.parseKAType(cli.getOptionValue("ka-type", "1"));
+ } else if (cli.hasOption("ecdsa")) {
+ if (primeField == binaryField) {
+ System.err.print(Colors.error("Need to specify field with -fp or -f2m. (but not both)"));
+ return false;
+ }
+ if (all || bits == 0) {
+ System.err.println(Colors.error("You have to specify curve bit-size with -b"));
+ return false;
+ }
+
+ if ((anyPublicKey) != (anyPrivateKey) && !anyKey) {
+ System.err.println(Colors.error("You cannot only specify a part of a keypair."));
+ return false;
+ }
+
+ ECDSACount = Integer.parseInt(cli.getOptionValue("ecdsa", "1"));
+ if (ECDSACount < 0) {
+ System.err.println(Colors.error("ECDSA count cannot be < 0."));
+ return false;
+ }
+
+ ECDSAType = CardUtil.parseSigType(cli.getOptionValue("sig-type", "17"));
+ }
+ return true;
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/ISO7816_status_words.txt b/reader/src/main/java/cz/crcs/ectester/reader/ISO7816_status_words.txt
new file mode 100644
index 0000000..bf5af2b
--- /dev/null
+++ b/reader/src/main/java/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/reader/src/main/java/cz/crcs/ectester/reader/command/Command.java b/reader/src/main/java/cz/crcs/ectester/reader/command/Command.java
new file mode 100644
index 0000000..051d377
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/command/Command.java
@@ -0,0 +1,920 @@
+package cz.crcs.ectester.reader.command;
+
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Params;
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.common.util.ECUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.output.ResponseWriter;
+import cz.crcs.ectester.reader.response.Response;
+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 implements Cloneable {
+ CommandAPDU cmd;
+ CardMngr cardManager;
+ // Workaround for a stupid Java bug that went unfixed for !12! years,
+ // and for the even more stupid module system, which cannot properly work
+ // with the fact that JCardSim has some java.* packages...
+ final byte[] GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM = new byte[]{0};
+
+
+ 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;
+ }
+
+ public abstract String getDescription();
+
+ @Override
+ protected Command clone() throws CloneNotSupportedException {
+ return (Command) super.clone();
+ }
+
+ public static EC_Curve findCurve(ECTesterReader.Config cfg, short keyLength, byte keyClass) throws IOException {
+ if (cfg.customCurve) {
+ byte curveId = EC_Consts.getCurve(keyLength, keyClass);
+ return EC_Store.getInstance().getObject(EC_Curve.class, "secg", CardUtil.getCurveName(curveId));
+ } else if (cfg.namedCurve != null) {
+ EC_Curve curve = EC_Store.getInstance().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.");
+ }
+ return curve;
+ } else if (cfg.curveFile != null) {
+ EC_Curve curve = new EC_Curve(null, keyLength, keyClass);
+
+ FileInputStream in = new FileInputStream(cfg.curveFile);
+ curve.readCSV(in);
+ in.close();
+ return curve;
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * @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, ECTesterReader.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);
+ }
+
+ EC_Curve curve = findCurve(cfg, keyLength, keyClass);
+ if ((curve == null || curve.flatten() == null) && (cfg.namedCurve != null || cfg.curveFile != null)) {
+ if (cfg.namedCurve != null) {
+ throw new IOException("Couldn't read named curve data.");
+ }
+ throw new IOException("Couldn't read the curve file correctly.");
+ } else if (curve == null) {
+ return null;
+ }
+ return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, curve.getParams(), curve.flatten());
+ }
+
+
+ /**
+ * @param cardManager
+ * @param dataStore
+ * @param cfg
+ * @param keyPair which keyPair/s to set the key params on
+ * @param allowedParams
+ * @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, ECTesterReader.Config cfg, byte keyPair, short allowedParams) 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 = ECUtil.loadParams(EC_Consts.PARAMETERS_KEYPAIR, cfg.namedKey, cfg.key);
+ if (keypair == null) {
+ throw new IOException("KeyPair not found.");
+ }
+
+ data = keypair.flatten();
+ if (data == null) {
+ throw new IOException("Couldn't read the key file correctly.");
+ }
+ }
+
+ if ((cfg.publicKey != null || cfg.namedPublicKey != null) && ((allowedParams & EC_Consts.PARAMETER_W) != 0)) {
+ params |= EC_Consts.PARAMETER_W;
+ EC_Params pub = ECUtil.loadParams(EC_Consts.PARAMETER_W, cfg.namedPublicKey, cfg.publicKey);
+ if (pub == null) {
+ throw new IOException("Public key not found.");
+ }
+
+ 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) && ((allowedParams & EC_Consts.PARAMETER_S) != 0)) {
+ params |= EC_Consts.PARAMETER_S;
+ EC_Params priv = ECUtil.loadParams(EC_Consts.PARAMETER_S, cfg.namedPrivateKey, cfg.privateKey);
+ if (priv == null) {
+ throw new IOException("Private key not found.");
+ }
+
+ byte[] privkey = priv.flatten(EC_Consts.PARAMETER_S);
+ if (privkey == null) {
+ throw new IOException("Couldn't read the private key file correctly.");
+ }
+ data = ByteUtil.concatenate(data, privkey);
+ }
+ return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, params, data);
+ }
+
+ public static long dryRunTime(CardMngr cardManager, Command cmd, int num, ResponseWriter respWriter) throws CardException {
+ long time = 0;
+ respWriter.outputResponse(new Command.SetDryRunMode(cardManager, CardConsts.MODE_DRY_RUN).send());
+ for (int i = 0; i < num; ++i) {
+ Response dry = cmd.send();
+ respWriter.outputResponse(dry);
+ time += dry.getDuration();
+ }
+ time /= num;
+ respWriter.outputResponse(new Command.SetDryRunMode(cardManager, CardConsts.MODE_NORMAL).send());
+ return time;
+ }
+
+ /**
+ *
+ */
+ public static class AllocateKeyAgreement extends Command {
+ private byte kaType;
+
+ /**
+ * Creates the INS_ALLOCATE_KA instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param kaType which type of KeyAgreement to use
+ */
+ public AllocateKeyAgreement(CardMngr cardManager, byte kaType) {
+ super(cardManager);
+ this.kaType = kaType;
+ byte[] data = new byte[]{kaType};
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ALLOCATE_KA, 0x00, 0x00, data);
+ }
+
+ @Override
+ public Response.AllocateKeyAgreement send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.AllocateKeyAgreement(response, getDescription(), elapsed, kaType);
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format("Allocate KeyAgreement(%s) object", CardUtil.getKATypeString(kaType));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class AllocateSignature extends Command {
+ private byte sigType;
+
+ /**
+ * Creates the INS_ALLOCATE_SIG instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param sigType which type of Signature to use
+ */
+ public AllocateSignature(CardMngr cardManager, byte sigType) {
+ super(cardManager);
+ this.sigType = sigType;
+ byte[] data = new byte[]{sigType};
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ALLOCATE_SIG, 0x00, 0x00, data);
+ }
+
+ @Override
+ public Response.AllocateSignature send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.AllocateSignature(response, getDescription(), elapsed, sigType);
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format("Allocate Signature(%s) object", CardUtil.getSigTypeString(sigType));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Allocate extends Command {
+ private byte keyPair;
+ private byte build;
+ 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 build whether to use KeyBuilder or Keypair alloc
+ * @param keyLength key length to set
+ * @param keyClass key class to allocate
+ */
+ public Allocate(CardMngr cardManager, byte keyPair, byte build, short keyLength, byte keyClass) {
+ super(cardManager);
+ this.keyPair = keyPair;
+ this.build = build;
+ this.keyLength = keyLength;
+ this.keyClass = keyClass;
+
+ byte[] data = new byte[]{0, 0, keyClass};
+ ByteUtil.setShort(data, 0, keyLength);
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ALLOCATE, keyPair, build, data);
+ }
+
+ public Allocate(CardMngr cardManager, byte keyPair, short keyLength, byte keyClass) {
+ this(cardManager, keyPair, (byte) (CardConsts.BUILD_KEYPAIR | CardConsts.BUILD_KEYBUILDER), keyLength, keyClass);
+ }
+
+ @Override
+ public Response.Allocate send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Allocate(response, getDescription(), elapsed, keyPair, keyLength, keyClass);
+ }
+
+ @Override
+ public String getDescription() {
+ String field = keyClass == KeyPair.ALG_EC_FP ? "ALG_EC_FP" : "ALG_EC_F2M";
+ String key;
+ if (keyPair == CardConsts.KEYPAIR_BOTH) {
+ key = "both keypairs";
+ } else {
+ key = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return String.format("Allocate %s %db %s", key, keyLength, field);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Clear extends Command {
+ private byte keyPair;
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair which keyPair clear, local/remote (KEYPAIR_* || ...)
+ */
+ public Clear(CardMngr cardManager, byte keyPair) {
+ super(cardManager);
+ this.keyPair = keyPair;
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_CLEAR, keyPair, 0x00, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM);
+ }
+
+ @Override
+ public Response.Clear send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Clear(response, getDescription(), elapsed, keyPair);
+ }
+
+ @Override
+ public String getDescription() {
+ String key;
+ if (keyPair == CardConsts.KEYPAIR_BOTH) {
+ key = "both keypairs";
+ } else {
+ key = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return String.format("Clear %s", key);
+ }
+ }
+
+ /**
+ *
+ */
+ 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
+ */
+ public 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];
+ ByteUtil.setShort(data, 0, params);
+ if (external != null) {
+ System.arraycopy(external, 0, data, 2, external.length);
+ }
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.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, getDescription(), elapsed, keyPair, curve, params);
+ }
+
+ @Override
+ public String getDescription() {
+ String name = CardUtil.getCurveName(curve);
+ String what = CardUtil.getParameterString(params);
+
+ String pair;
+ if (keyPair == CardConsts.KEYPAIR_BOTH) {
+ pair = "both keypairs";
+ } else {
+ pair = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return String.format("Set %s %s parameters on %s", name, what, pair);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Transform extends Command {
+ private byte keyPair;
+ private byte key;
+ private short params;
+ private short transformation;
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair which keyPair to transform, local/remote (KEYPAIR_* || ...)
+ * @param key key to transform (EC_Consts.KEY_* | ...)
+ * @param params parameters to transform (EC_Consts.PARAMETER_* | ...)
+ * @param transformation transformation type (EC_Consts.TRANSFORMATION_*)
+ */
+ public Transform(CardMngr cardManager, byte keyPair, byte key, short params, short transformation) {
+ super(cardManager);
+ this.keyPair = keyPair;
+ this.key = key;
+ this.params = params;
+ this.transformation = transformation;
+
+ byte[] data = new byte[4];
+ ByteUtil.setShort(data, 0, params);
+ ByteUtil.setShort(data, 2, transformation);
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_TRANSFORM, keyPair, key, data);
+ }
+
+ @Override
+ public Response.Transform send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Transform(response, getDescription(), elapsed, keyPair, key, params, transformation);
+ }
+
+ @Override
+ public String getDescription() {
+ String stringParams = CardUtil.getParams(params);
+ String transform = CardUtil.getTransformation(transformation);
+
+ String pair;
+ if (keyPair == CardConsts.KEYPAIR_BOTH) {
+ pair = "both keypairs";
+ } else {
+ pair = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return String.format("Transform params %s of %s, %s", stringParams, pair, transform);
+ }
+ }
+
+ /**
+ *
+ */
+ 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_* || ...)
+ */
+ public Generate(CardMngr cardManager, byte keyPair) {
+ super(cardManager);
+ this.keyPair = keyPair;
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_GENERATE, keyPair, 0, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM);
+ }
+
+ @Override
+ public Response.Generate send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Generate(response, getDescription(), elapsed, keyPair);
+ }
+
+ @Override
+ public String getDescription() {
+ String key;
+ if (keyPair == CardConsts.KEYPAIR_BOTH) {
+ key = "both keypairs";
+ } else {
+ key = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return String.format("Generate %s", key);
+ }
+ }
+
+ /**
+ *
+ */
+ 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_* | ...)
+ */
+ public 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];
+ ByteUtil.setShort(data, 0, params);
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.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, getDescription(), elapsed, keyPair, key, params);
+ }
+
+ @Override
+ public String getDescription() {
+ String what = CardUtil.getParameterString(params);
+
+ 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 == CardConsts.KEYPAIR_BOTH) {
+ pair = "both keypairs";
+ } else {
+ pair = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair";
+ }
+ return String.format("Export %s params from %s of %s", what, source, pair);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class ECDH extends Command {
+ private byte pubkey;
+ private byte privkey;
+ private byte export;
+ private short transformation;
+ 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 transformation whether to transform the pubkey before ECDH (EC_Consts.TRANSFORMATION_* | ...)
+ * @param type ECDH algorithm type (EC_Consts.KA_* | ...)
+ */
+ public ECDH(CardMngr cardManager, byte pubkey, byte privkey, byte export, short transformation, byte type) {
+ super(cardManager);
+ this.pubkey = pubkey;
+ this.privkey = privkey;
+ this.export = export;
+ this.transformation = transformation;
+ this.type = type;
+
+ byte[] data = new byte[]{export, 0, 0, type};
+ ByteUtil.setShort(data, 1, transformation);
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.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, getDescription(), elapsed, pubkey, privkey, export, transformation, type);
+ }
+
+ @Override
+ public String getDescription() {
+ String algo = CardUtil.getKATypeString(type);
+
+ String pub = pubkey == CardConsts.KEYPAIR_LOCAL ? "local" : "remote";
+ String priv = privkey == CardConsts.KEYPAIR_LOCAL ? "local" : "remote";
+
+ String validity;
+ if (transformation == EC_Consts.TRANSFORMATION_NONE) {
+ validity = "";
+ } else {
+ validity = String.format("(%s point)", CardUtil.getTransformation(transformation));
+ }
+ return String.format("%s of %s pubkey and %s privkey%s", algo, pub, priv, validity);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class ECDH_direct extends Command {
+ private byte privkey;
+ private byte export;
+ private short transformation;
+ private byte type;
+ private byte[] pubkey;
+
+ /**
+ * Creates the INS_ECDH_DIRECT instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param privkey keyPair to use for private key, (KEYPAIR_LOCAL || KEYPAIR_REMOTE)
+ * @param export whether to export ECDH secret
+ * @param transformation whether to transform the pubkey before ECDH (EC_Consts.TRANSFORMATION_* | ...)
+ * @param type EC KeyAgreement type
+ * @param pubkey pubkey data to do ECDH with.
+ */
+ public ECDH_direct(CardMngr cardManager, byte privkey, byte export, short transformation, byte type, byte[] pubkey) {
+ super(cardManager);
+ this.privkey = privkey;
+ this.export = export;
+ this.transformation = transformation;
+ this.type = type;
+ this.pubkey = pubkey;
+
+ byte[] data = new byte[3 + pubkey.length];
+ ByteUtil.setShort(data, 0, transformation);
+ data[2] = type;
+ System.arraycopy(pubkey, 0, data, 3, pubkey.length);
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.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, getDescription(), elapsed, CardConsts.KEYPAIR_REMOTE, privkey, export, transformation, type);
+ }
+
+ @Override
+ public String getDescription() {
+ String algo = CardUtil.getKATypeString(type);
+
+ String priv = privkey == CardConsts.KEYPAIR_LOCAL ? "local" : "remote";
+
+ String validity;
+ if (transformation == EC_Consts.TRANSFORMATION_NONE) {
+ validity = "";
+ } else {
+ validity = String.format("(%s point)", CardUtil.getTransformation(transformation));
+ }
+ return String.format("%s of external pubkey and %s privkey%s", algo, priv, validity);
+ }
+ }
+
+ public static class ECDSA extends Command {
+ private byte keyPair;
+ private byte sigType;
+ 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 sigType Signature type to use
+ * @param export whether to export ECDSA signature
+ * @param raw data to sign, can be null, in which case random data is signed.
+ */
+ public ECDSA(CardMngr cardManager, byte keyPair, byte sigType, byte export, byte[] raw) {
+ super(cardManager);
+ if (keyPair == CardConsts.KEYPAIR_BOTH) {
+ throw new IllegalArgumentException();
+ }
+
+ this.keyPair = keyPair;
+ this.sigType = sigType;
+ this.export = export;
+ this.raw = raw;
+
+ int len = raw != null ? raw.length : 0;
+ byte[] data = new byte[3 + len];
+ data[0] = sigType;
+ ByteUtil.setShort(data, 1, (short) len);
+ if (raw != null) {
+ System.arraycopy(raw, 0, data, 3, len);
+ }
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.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, getDescription(), elapsed, keyPair, sigType, export, raw);
+ }
+
+ @Override
+ public String getDescription() {
+ String algo = CardUtil.getSigTypeString(sigType);
+ String key = keyPair == CardConsts.KEYPAIR_LOCAL ? "local" : "remote";
+ String data = raw == null ? "random" : "provided";
+ return String.format("%s with %s keypair(%s data)", algo, key, data);
+ }
+ }
+
+ public static class ECDSA_sign extends Command {
+ private byte keyPair;
+ private byte sigType;
+ private byte export;
+ private byte[] raw;
+
+ /**
+ * Creates the INS_ECDSA_SIGN instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair keyPair to use for signing and verification (KEYPAIR_LOCAL || KEYPAIR_REMOTE)
+ * @param sigType Signature type to use
+ * @param export whether to export ECDSA signature
+ * @param raw data to sign, can be null, in which case random data is signed.
+ */
+ public ECDSA_sign(CardMngr cardManager, byte keyPair, byte sigType, byte export, byte[] raw) {
+ super(cardManager);
+ if (keyPair == CardConsts.KEYPAIR_BOTH) {
+ throw new IllegalArgumentException();
+ }
+
+ this.keyPair = keyPair;
+ this.sigType = sigType;
+ this.export = export;
+ this.raw = raw;
+
+ int len = raw != null ? raw.length : 0;
+ byte[] data = new byte[3 + len];
+ data[0] = sigType;
+ ByteUtil.setShort(data, 1, (short) len);
+ if (raw != null) {
+ System.arraycopy(raw, 0, data, 3, len);
+ }
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ECDSA_SIGN, 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, getDescription(), elapsed, keyPair, sigType, export, raw);
+ }
+
+ @Override
+ public String getDescription() {
+ String algo = CardUtil.getSigTypeString(sigType);
+ String key = keyPair == CardConsts.KEYPAIR_LOCAL ? "local" : "remote";
+ String data = raw == null ? "random" : "provided";
+ return String.format("%s signature with %s keypair(%s data)", algo, key, data);
+ }
+ }
+
+ public static class ECDSA_verify extends Command {
+ private byte keyPair;
+ private byte sigType;
+ private byte[] raw;
+ private byte[] signature;
+
+ /**
+ * Creates the INS_ECDSA_VERIFY instruction.
+ *
+ * @param cardManager cardManager to send APDU through
+ * @param keyPair keyPair to use for signing and verification (KEYPAIR_LOCAL || KEYPAIR_REMOTE)
+ * @param sigType Signature type to use
+ * @param raw data to sign
+ * @param signature signature data
+ */
+ public ECDSA_verify(CardMngr cardManager, byte keyPair, byte sigType, byte[] raw, byte[] signature) {
+ super(cardManager);
+ if (keyPair == CardConsts.KEYPAIR_BOTH) {
+ throw new IllegalArgumentException();
+ }
+ if (raw == null || signature == null) {
+ throw new IllegalArgumentException();
+ }
+
+ this.keyPair = keyPair;
+ this.sigType = sigType;
+ this.raw = raw;
+ this.signature = signature;
+
+ byte[] data = new byte[4 + raw.length + signature.length];
+ ByteUtil.setShort(data, 0, (short) raw.length);
+ System.arraycopy(raw, 0, data, 2, raw.length);
+ ByteUtil.setShort(data, 2 + raw.length, (short) signature.length);
+ System.arraycopy(signature, 0, data, 2 + raw.length + 2, signature.length);
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ECDSA_VERIFY, keyPair, sigType, 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, getDescription(), elapsed, keyPair, sigType, CardConsts.EXPORT_FALSE, raw);
+ }
+
+ @Override
+ public String getDescription() {
+ String algo = CardUtil.getSigTypeString(sigType);
+ String key = keyPair == CardConsts.KEYPAIR_LOCAL ? "local" : "remote";
+ String data = raw == null ? "random" : "provided";
+ return String.format("%s verification with %s keypair(%s data)", algo, key, data);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Cleanup extends Command {
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ */
+ public Cleanup(CardMngr cardManager) {
+ super(cardManager);
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_CLEANUP, 0, 0, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM);
+ }
+
+ @Override
+ public Response.Cleanup send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.Cleanup(response, getDescription(), elapsed);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Request JCSystem object deletion";
+ }
+ }
+
+ /**
+ *
+ */
+ public static class GetInfo extends Command {
+
+ /**
+ * @param cardManager cardManager to send APDU through
+ */
+ public GetInfo(CardMngr cardManager) {
+ super(cardManager);
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_GET_INFO, 0, 0, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM);
+ }
+
+ @Override
+ public Response.GetInfo send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.GetInfo(response, getDescription(), elapsed);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Get applet info";
+ }
+ }
+
+ /**
+ *
+ */
+ public static class SetDryRunMode extends Command {
+ private byte dryRunMode;
+
+ /**
+ * @param cardManager
+ * @param dryRunMode
+ */
+ public SetDryRunMode(CardMngr cardManager, byte dryRunMode) {
+ super(cardManager);
+ this.dryRunMode = dryRunMode;
+
+ this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_SET_DRY_RUN_MODE, dryRunMode, 0, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM);
+ }
+
+ @Override
+ public Response.SetDryRunMode send() throws CardException {
+ long elapsed = -System.nanoTime();
+ ResponseAPDU response = cardManager.send(cmd);
+ elapsed += System.nanoTime();
+ return new Response.SetDryRunMode(response, getDescription(), elapsed);
+ }
+
+ @Override
+ public String getDescription() {
+ return (dryRunMode == CardConsts.MODE_NORMAL ? "Disable" : "Enable") + " dry run mode";
+ }
+ }
+}
+
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/FileTestWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/FileTestWriter.java
new file mode 100644
index 0000000..69e5f41
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/output/FileTestWriter.java
@@ -0,0 +1,53 @@
+package cz.crcs.ectester.reader.output;
+
+import cz.crcs.ectester.common.output.TeeTestWriter;
+import cz.crcs.ectester.common.output.TestWriter;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.util.regex.Pattern;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class FileTestWriter extends TeeTestWriter {
+
+ private static final Pattern PREFIX = Pattern.compile("(text|xml|yaml|yml):.+");
+
+ public FileTestWriter(String defaultFormat, boolean systemOut, String[] files) throws ParserConfigurationException, FileNotFoundException {
+ int fLength = files == null ? 0 : files.length;
+ writers = new TestWriter[systemOut ? fLength + 1 : fLength];
+ if (systemOut) {
+ writers[0] = createWriter(defaultFormat, System.out);
+ }
+ for (int i = 0; i < fLength; ++i) {
+ String fName = files[i];
+ String format = null;
+ if (PREFIX.matcher(fName).matches()) {
+ String[] split = fName.split(":", 2);
+ format = split[0];
+ fName = split[1];
+ }
+ writers[i + 1] = createWriter(format, new PrintStream(new FileOutputStream(fName)));
+ }
+ }
+
+ private TestWriter createWriter(String format, PrintStream out) throws ParserConfigurationException {
+ if (format == null) {
+ return new TextTestWriter(out);
+ }
+ switch (format) {
+ case "text":
+ return new TextTestWriter(out);
+ case "xml":
+ return new XMLTestWriter(out);
+ case "yaml":
+ case "yml":
+ return new YAMLTestWriter(out);
+ default:
+ return null;
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/ResponseWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/ResponseWriter.java
new file mode 100644
index 0000000..85bf79a
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/output/ResponseWriter.java
@@ -0,0 +1,43 @@
+package cz.crcs.ectester.reader.output;
+
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.reader.response.Response;
+
+import java.io.PrintStream;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class ResponseWriter {
+ private PrintStream output;
+
+ public ResponseWriter(PrintStream output) {
+ this.output = output;
+ }
+
+ public String responseSuffix(Response r) {
+ StringBuilder suffix = new StringBuilder();
+ for (int j = 0; j < r.getNumSW(); ++j) {
+ short sw = r.getSW(j);
+ if (sw != 0) {
+ suffix.append(" ").append(CardUtil.getSWString(sw));
+ }
+ }
+ if (suffix.length() == 0) {
+ suffix.append(" [").append(CardUtil.getSW(r.getNaturalSW())).append(String.format(" 0x%04x", r.getNaturalSW())).append("]");
+ }
+ return String.format("%4d ms ┃ %s", r.getDuration() / 1000000, suffix);
+ }
+
+ public String responseString(Response r) {
+ String out = "";
+ out += String.format("%-70s", r.getDescription()) + " ┃ ";
+ out += responseSuffix(r);
+ return out;
+ }
+
+ public void outputResponse(Response r) {
+ output.println(responseString(r));
+ output.flush();
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/TextTestWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/TextTestWriter.java
new file mode 100644
index 0000000..532ace5
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/output/TextTestWriter.java
@@ -0,0 +1,71 @@
+package cz.crcs.ectester.reader.output;
+
+import cz.crcs.ectester.common.cli.Colors;
+import cz.crcs.ectester.common.output.BaseTextTestWriter;
+import cz.crcs.ectester.common.test.TestSuite;
+import cz.crcs.ectester.common.test.Testable;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+import cz.crcs.ectester.reader.test.CardTestSuite;
+import cz.crcs.ectester.reader.test.CommandTestable;
+
+import javax.smartcardio.CardException;
+import java.io.PrintStream;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TextTestWriter extends BaseTextTestWriter {
+ private ResponseWriter writer;
+
+ public TextTestWriter(PrintStream output) {
+ super(output);
+ this.writer = new ResponseWriter(output);
+ }
+
+ @Override
+ protected String testableString(Testable t) {
+ if (t instanceof CommandTestable) {
+ CommandTestable cmd = (CommandTestable) t;
+ Response response = cmd.getResponse();
+ if (response != null) {
+ return writer.responseSuffix(response);
+ }
+ }
+ return "";
+ }
+
+ @Override
+ protected String deviceString(TestSuite suite) {
+ if (suite instanceof CardTestSuite) {
+ CardTestSuite cardSuite = (CardTestSuite) suite;
+ StringBuilder sb = new StringBuilder();
+ try {
+ sb.append("═══ ").append(Colors.underline("ECTester version:")).append(" ").append(ECTesterReader.VERSION).append(ECTesterReader.GIT_COMMIT).append(System.lineSeparator());
+ Response.GetInfo info = new Command.GetInfo(cardSuite.getCard()).send();
+ sb.append("═══ ").append(Colors.underline("ECTester applet version:")).append(" ").append(info.getVersion()).append(info.getBase() == CardConsts.BASE_221 ? "" : " (extended length)").append(System.lineSeparator());
+ sb.append("═══ ").append(Colors.underline("Card ATR:")).append(" ").append(ByteUtil.bytesToHex(cardSuite.getCard().getATR().getBytes(), false)).append(System.lineSeparator());
+ sb.append("═══ ").append(Colors.underline("JavaCard version:")).append(" ").append(info.getJavaCardVersion()).append(System.lineSeparator());
+ sb.append("═══ ").append(Colors.underline("Array sizes (apduBuf, ram, ram2, apduArr):")).append(" ").append(String.format("%d %d %d %d", info.getApduBufferLength(), info.getRamArrayLength(), info.getRamArray2Length(), info.getApduArrayLength())).append(System.lineSeparator());
+ sb.append("═══ ").append(Colors.underline("Test options:")).append(" ").append(String.join(",", cardSuite.getCfg().testOptions)).append(System.lineSeparator());
+ CardMngr.CPLC cplc = cardSuite.getCard().getCPLC();
+ if (!cplc.values().isEmpty()) {
+ sb.append("═══ ").append(Colors.underline("Card CPLC data:")).append(System.lineSeparator());
+ for (Map.Entry<CardMngr.CPLC.Field, byte[]> entry : cplc.values().entrySet()) {
+ CardMngr.CPLC.Field field = entry.getKey();
+ byte[] value = entry.getValue();
+ sb.append("═══ ").append(field.name()).append(": ").append(CardMngr.mapCPLCField(field, value));
+ }
+ }
+ } catch (CardException ignored) {
+ }
+ return sb.toString();
+ }
+ return "";
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/XMLTestWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/XMLTestWriter.java
new file mode 100644
index 0000000..fc41805
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/output/XMLTestWriter.java
@@ -0,0 +1,159 @@
+package cz.crcs.ectester.reader.output;
+
+import cz.crcs.ectester.common.output.BaseXMLTestWriter;
+import cz.crcs.ectester.common.test.TestSuite;
+import cz.crcs.ectester.common.test.Testable;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+import cz.crcs.ectester.reader.test.CardTestSuite;
+import cz.crcs.ectester.reader.test.CommandTestable;
+import org.w3c.dom.Element;
+
+import javax.smartcardio.CardException;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.OutputStream;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class XMLTestWriter extends BaseXMLTestWriter {
+ public XMLTestWriter(OutputStream output) throws ParserConfigurationException {
+ super(output);
+ }
+
+ private Element commandElement(Command c) {
+ Element commandElem = doc.createElement("command");
+ if (c == null) {
+ return commandElem;
+ }
+
+ Element apdu = doc.createElement("apdu");
+ apdu.setTextContent(ByteUtil.bytesToHex(c.getAPDU().getBytes()));
+ commandElem.appendChild(apdu);
+
+ Element description = doc.createElement("desc");
+ description.setTextContent(c.getDescription());
+ commandElem.appendChild(description);
+
+ return commandElem;
+ }
+
+ private Element responseElement(Response r) {
+ Element responseElem = doc.createElement("response");
+ if (r == null) {
+ return responseElem;
+ }
+
+ responseElem.setAttribute("successful", r.successful() ? "true" : "false");
+
+ Element apdu = doc.createElement("apdu");
+ apdu.setTextContent(ByteUtil.bytesToHex(r.getAPDU().getBytes()));
+ responseElem.appendChild(apdu);
+
+ Element naturalSW = doc.createElement("natural-sw");
+ naturalSW.setTextContent(String.valueOf(Short.toUnsignedInt(r.getNaturalSW())));
+ responseElem.appendChild(naturalSW);
+
+ Element sws = doc.createElement("sws");
+ for (int i = 0; i < r.getNumSW(); ++i) {
+ Element sw = doc.createElement("sw");
+ sw.setTextContent(String.valueOf(Short.toUnsignedInt(r.getSW(i))));
+ sws.appendChild(sw);
+ }
+ responseElem.appendChild(sws);
+
+ Element duration = doc.createElement("duration");
+ duration.setTextContent(String.valueOf(r.getDuration()));
+ responseElem.appendChild(duration);
+
+ Element description = doc.createElement("desc");
+ description.setTextContent(r.getDescription());
+ responseElem.appendChild(description);
+
+ return responseElem;
+ }
+
+ @Override
+ protected Element testableElement(Testable t) {
+ if (t instanceof CommandTestable) {
+ CommandTestable cmd = (CommandTestable) t;
+ Element result = doc.createElement("test");
+ result.setAttribute("type", "command");
+ result.appendChild(commandElement(cmd.getCommand()));
+ result.appendChild(responseElement(cmd.getResponse()));
+ return result;
+ }
+ return null;
+ }
+
+ private Element cplcElement(CardMngr card) {
+ Element result = doc.createElement("cplc");
+ try {
+ CardMngr.CPLC cplc = card.getCPLC();
+ if (!cplc.values().isEmpty()) {
+ for (Map.Entry<CardMngr.CPLC.Field, byte[]> entry : cplc.values().entrySet()) {
+ CardMngr.CPLC.Field field = entry.getKey();
+ byte[] value = entry.getValue();
+ Element keyVal = doc.createElement(field.name());
+ keyVal.setTextContent(ByteUtil.bytesToHex(value, false));
+ result.appendChild(keyVal);
+ }
+ }
+ } catch (CardException ignored) {
+ }
+ return result;
+ }
+
+ private Element appletElement(CardMngr card) {
+ Element result = doc.createElement("applet");
+ try {
+ Response.GetInfo info = new Command.GetInfo(card).send();
+ result.setAttribute("version", info.getVersion());
+ result.setAttribute("javacard", String.format("%.1f", info.getJavaCardVersion()));
+ result.setAttribute("base", String.format("%#x", info.getBase()));
+ result.setAttribute("cleanup", String.valueOf(info.getCleanupSupport()));
+ Element arrays = doc.createElement("arrays");
+ Element apduBuf = doc.createElement("length");
+ apduBuf.setAttribute("name", "apduBuf");
+ apduBuf.setTextContent(String.valueOf(info.getApduBufferLength()));
+ Element ramArray = doc.createElement("length");
+ ramArray.setAttribute("name", "ramArray");
+ ramArray.setTextContent(String.valueOf(info.getRamArrayLength()));
+ Element ramArray2 = doc.createElement("length");
+ ramArray2.setAttribute("name", "ramArray2");
+ ramArray2.setTextContent(String.valueOf(info.getRamArray2Length()));
+ Element apduArray = doc.createElement("length");
+ apduArray.setAttribute("name", "apduArray");
+ apduArray.setTextContent(String.valueOf(info.getApduArrayLength()));
+ arrays.appendChild(apduBuf);
+ arrays.appendChild(ramArray);
+ arrays.appendChild(ramArray2);
+ arrays.appendChild(apduArray);
+ result.appendChild(arrays);
+ } catch (CardException ignored) {
+ }
+ return result;
+ }
+
+ @Override
+ protected Element deviceElement(TestSuite suite) {
+ if (suite instanceof CardTestSuite) {
+ CardTestSuite cardSuite = (CardTestSuite) suite;
+ Element result = doc.createElement("device");
+ result.setAttribute("type", "card");
+ result.setAttribute("ectester", ECTesterReader.VERSION + ECTesterReader.GIT_COMMIT);
+ result.appendChild(cplcElement(cardSuite.getCard()));
+ result.appendChild(appletElement(cardSuite.getCard()));
+
+ Element atr = doc.createElement("ATR");
+ atr.setTextContent(ByteUtil.bytesToHex(cardSuite.getCard().getATR().getBytes(), false));
+ result.appendChild(atr);
+ return result;
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/YAMLTestWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/YAMLTestWriter.java
new file mode 100644
index 0000000..56ecb71
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/output/YAMLTestWriter.java
@@ -0,0 +1,119 @@
+package cz.crcs.ectester.reader.output;
+
+import cz.crcs.ectester.common.output.BaseYAMLTestWriter;
+import cz.crcs.ectester.common.test.TestSuite;
+import cz.crcs.ectester.common.test.Testable;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+import cz.crcs.ectester.reader.test.CardTestSuite;
+import cz.crcs.ectester.reader.test.CommandTestable;
+
+import javax.smartcardio.CardException;
+import java.io.PrintStream;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class YAMLTestWriter extends BaseYAMLTestWriter {
+ public YAMLTestWriter(PrintStream output) {
+ super(output);
+ }
+
+ private Map<String, Object> commandObject(Command c) {
+ Map<String, Object> commandObj = new LinkedHashMap<>();
+ if (c == null) {
+ return commandObj;
+ }
+ commandObj.put("apdu", ByteUtil.bytesToHex(c.getAPDU().getBytes()));
+ commandObj.put("desc", c.getDescription());
+ return commandObj;
+ }
+
+ private Map<String, Object> responseObject(Response r) {
+ Map<String, Object> responseObj = new LinkedHashMap<>();
+ if (r == null) {
+ return responseObj;
+ }
+ responseObj.put("successful", r.successful());
+ responseObj.put("apdu", ByteUtil.bytesToHex(r.getAPDU().getBytes()));
+ responseObj.put("natural_sw", Short.toUnsignedInt(r.getNaturalSW()));
+ List<Integer> sws = new LinkedList<>();
+ for (int i = 0; i < r.getNumSW(); ++i) {
+ sws.add(Short.toUnsignedInt(r.getSW(i)));
+ }
+ responseObj.put("sws", sws);
+ responseObj.put("duration", r.getDuration());
+ responseObj.put("desc", r.getDescription());
+ return responseObj;
+ }
+
+ @Override
+ protected Map<String, Object> testableObject(Testable t) {
+ if (t instanceof CommandTestable) {
+ CommandTestable cmd = (CommandTestable) t;
+ Map<String, Object> result = new LinkedHashMap<>();
+ result.put("type", "command");
+ result.put("command", commandObject(cmd.getCommand()));
+ result.put("response", responseObject(cmd.getResponse()));
+ return result;
+ }
+ return null;
+ }
+
+ private Map<String, Object> cplcObject(CardMngr card) {
+ Map<String, Object> result = new LinkedHashMap<>();
+ try {
+ CardMngr.CPLC cplc = card.getCPLC();
+ if (!cplc.values().isEmpty()) {
+ for (Map.Entry<CardMngr.CPLC.Field, byte[]> entry : cplc.values().entrySet()) {
+ CardMngr.CPLC.Field field = entry.getKey();
+ byte[] value = entry.getValue();
+ result.put(field.name(), ByteUtil.bytesToHex(value, false));
+ }
+ }
+ } catch (CardException ignored) {
+ }
+ return result;
+ }
+
+ private Map<String, Object> appletObject(CardMngr card) {
+ Map<String, Object> result = new LinkedHashMap<>();
+ try {
+ Response.GetInfo info = new Command.GetInfo(card).send();
+ result.put("version", info.getVersion());
+ result.put("javacard", info.getJavaCardVersion());
+ result.put("base", info.getBase());
+ result.put("cleanup", info.getCleanupSupport());
+ Map<String, Integer> arrays = new LinkedHashMap<>();
+ arrays.put("apduBuf", Short.toUnsignedInt(info.getApduBufferLength()));
+ arrays.put("ramArray", Short.toUnsignedInt(info.getRamArrayLength()));
+ arrays.put("ramArray2", Short.toUnsignedInt(info.getRamArray2Length()));
+ arrays.put("apduArray", Short.toUnsignedInt(info.getApduArrayLength()));
+ result.put("arrays", arrays);
+ } catch (CardException ignored) {
+ }
+ return result;
+ }
+
+ @Override
+ protected Map<String, Object> deviceObject(TestSuite suite) {
+ if (suite instanceof CardTestSuite) {
+ CardTestSuite cardSuite = (CardTestSuite) suite;
+ Map<String, Object> result = new LinkedHashMap<>();
+ result.put("type", "card");
+ result.put("ectester", ECTesterReader.VERSION + ECTesterReader.GIT_COMMIT);
+ result.put("cplc", cplcObject(cardSuite.getCard()));
+ result.put("applet", appletObject(cardSuite.getCard()));
+ result.put("ATR", ByteUtil.bytesToHex(cardSuite.getCard().getATR().getBytes(), false));
+ return result;
+ }
+ return null;
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/response/Response.java b/reader/src/main/java/cz/crcs/ectester/reader/response/Response.java
new file mode 100644
index 0000000..8db255e
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/response/Response.java
@@ -0,0 +1,526 @@
+package cz.crcs.ectester.reader.response;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import javacard.framework.ISO7816;
+
+import javax.smartcardio.ResponseAPDU;
+
+/**
+ * @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;
+ private boolean success = true;
+ private boolean error = false;
+ private String description;
+
+ public Response(ResponseAPDU response, String description, long time) {
+ this.resp = response;
+ this.description = description;
+ this.time = time;
+ }
+
+ boolean 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 = ByteUtil.getShort(data, offset);
+ offset += 2;
+ sws[i] = sw;
+ if (sw != ISO7816.SW_NO_ERROR) {
+ success = false;
+ }
+ } else {
+ success = false;
+ error = true;
+ }
+ }
+
+ if ((short) resp.getSW() != ISO7816.SW_NO_ERROR) {
+ success = false;
+ error = true;
+ }
+
+
+ //try to parse numParams..
+ params = new byte[numParams][];
+ for (int i = 0; i < numParams; i++) {
+ if (data.length - offset < 2) {
+ success = false;
+ error = true;
+ break;
+ }
+ short paramLength = ByteUtil.getShort(data, offset);
+ offset += 2;
+ if (data.length < offset + paramLength) {
+ error = true;
+ success = false;
+ break;
+ }
+ params[i] = new byte[paramLength];
+ System.arraycopy(data, offset, params[i], 0, paramLength);
+ offset += paramLength;
+ }
+ return success;
+ }
+
+ public ResponseAPDU getAPDU() {
+ return resp;
+ }
+
+ public byte[] getData() {
+ return resp.getData();
+ }
+
+ public long getDuration() {
+ return time;
+ }
+
+ public short getNaturalSW() {
+ return (short) resp.getSW();
+ }
+
+ public short[] getSWs() {
+ return sws;
+ }
+
+ 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;
+ }
+
+ public boolean error() {
+ return this.error;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ *
+ */
+ public static class AllocateKeyAgreement extends Response {
+ private byte kaType;
+
+ public AllocateKeyAgreement(ResponseAPDU response, String description, long time, byte kaType) {
+ super(response, description, time);
+ this.kaType = kaType;
+
+ parse(1, 0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class AllocateSignature extends Response {
+ private byte sigType;
+
+ public AllocateSignature(ResponseAPDU response, String description, long time, byte sigType) {
+ super(response, description, time);
+ this.sigType = sigType;
+
+ parse(1, 0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Allocate extends Response {
+ private byte keyPair;
+ private short keyLength;
+ private byte keyClass;
+
+ public Allocate(ResponseAPDU response, String description, long time, byte keyPair, short keyLength, byte keyClass) {
+ super(response, description, time);
+ this.keyPair = keyPair;
+ this.keyLength = keyLength;
+ this.keyClass = keyClass;
+
+ int pairs = 0;
+ if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) pairs++;
+ if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) pairs++;
+ parse(pairs, 0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Clear extends Response {
+ private byte keyPair;
+
+ public Clear(ResponseAPDU response, String description, long time, byte keyPair) {
+ super(response, description, time);
+ this.keyPair = keyPair;
+
+ int pairs = 0;
+ if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) pairs++;
+ if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) pairs++;
+ parse(pairs, 0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Set extends Response {
+ private byte keyPair;
+ private byte curve;
+ private short parameters;
+
+ public Set(ResponseAPDU response, String description, long time, byte keyPair, byte curve, short parameters) {
+ super(response, description, time);
+ this.keyPair = keyPair;
+ this.curve = curve;
+ this.parameters = parameters;
+
+ int pairs = 0;
+ if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) pairs++;
+ if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) pairs++;
+
+ parse(pairs, 0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Transform extends Response {
+ private byte keyPair;
+ private byte key;
+ private short params;
+ private short transformation;
+
+ public Transform(ResponseAPDU response, String description, long time, byte keyPair, byte key, short params, short transformation) {
+ super(response, description, time);
+ this.keyPair = keyPair;
+ this.key = key;
+ this.params = params;
+ this.transformation = transformation;
+
+ int pairs = 0;
+ if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) pairs++;
+ if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) pairs++;
+
+ parse(pairs, 0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Generate extends Response {
+ private byte keyPair;
+
+ public Generate(ResponseAPDU response, String description, long time, byte keyPair) {
+ super(response, description, time);
+ this.keyPair = keyPair;
+
+ int generated = 0;
+ if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) generated++;
+ if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) generated++;
+ parse(generated, 0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Export extends Response {
+ private byte keyPair;
+ private byte key;
+ private short parameters;
+
+ public Export(ResponseAPDU response, String description, long time, byte keyPair, byte key, short parameters) {
+ super(response, description, time);
+ this.keyPair = keyPair;
+ this.key = key;
+ this.parameters = parameters;
+
+ int exported = 0;
+ if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) exported++;
+ if ((keyPair & CardConsts.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 = CardConsts.KEYPAIR_LOCAL;
+ int index = 0;
+ while (pair <= CardConsts.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 & this.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));
+ }
+ }
+
+ /**
+ *
+ */
+ public static class ECDH extends Response {
+ private byte pubkey;
+ private byte privkey;
+ private byte export;
+ private short transformation;
+ private byte type;
+
+ public ECDH(ResponseAPDU response, String description, long time, byte pubkey, byte privkey, byte export, short transformation, byte type) {
+ super(response, description, time);
+ this.pubkey = pubkey;
+ this.privkey = privkey;
+ this.export = export;
+ this.transformation = transformation;
+ this.type = type;
+
+ parse(1, (export == CardConsts.EXPORT_TRUE) ? 1 : 0);
+ }
+
+ public short getTransformation() {
+ return transformation;
+ }
+
+ public byte getType() {
+ return type;
+ }
+
+ public boolean hasSecret() {
+ return hasParam(0);
+ }
+
+ public byte[] getSecret() {
+ return getParam(0);
+ }
+
+ public int secretLength() {
+ return getParamLength(0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class ECDSA extends Response {
+ private byte keyPair;
+ private byte sigType;
+ private byte export;
+ private byte[] raw;
+
+ public ECDSA(ResponseAPDU response, String description, long time, byte keyPair, byte sigType, byte export, byte[] raw) {
+ super(response, description, time);
+ this.keyPair = keyPair;
+ this.sigType = sigType;
+ this.export = export;
+ this.raw = raw;
+
+ parse(1, (export == CardConsts.EXPORT_TRUE) ? 1 : 0);
+ }
+
+ public boolean hasSignature() {
+ return hasParam(0);
+ }
+
+ public byte[] getSignature() {
+ return getParam(0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class Cleanup extends Response {
+
+ public Cleanup(ResponseAPDU response, String description, long time) {
+ super(response, description, time);
+
+ parse(1, 0);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class GetInfo extends Response {
+ private short base;
+ private short jcVersion;
+ private short cleanupSupport;
+ private short apduBufferLength;
+ private short ramArrayLength;
+ private short ramArray2Length;
+ private short apduArrayLength;
+
+ public GetInfo(ResponseAPDU response, String description, long time) {
+ super(response, description, time);
+
+ parse(1, 1);
+ int offset = 2 + 2 + getParamLength(0);
+ byte[] data = getData();
+ base = ByteUtil.getShort(data, offset);
+ offset += 2;
+ jcVersion = ByteUtil.getShort(data, offset);
+ offset += 2;
+ cleanupSupport = ByteUtil.getShort(data, offset);
+ offset += 2;
+ apduBufferLength = ByteUtil.getShort(data, offset);
+ offset += 2;
+ ramArrayLength = ByteUtil.getShort(data, offset);
+ offset += 2;
+ ramArray2Length = ByteUtil.getShort(data, offset);
+ offset += 2;
+ apduArrayLength = ByteUtil.getShort(data, offset);
+ }
+
+ public String getVersion() {
+ return new String(getParam(0));
+ }
+
+ public short getBase() {
+ return base;
+ }
+
+ public float getJavaCardVersion() {
+ byte major = (byte) (jcVersion >> 8);
+ byte minor = (byte) (jcVersion & 0xff);
+ int minorSize;
+ if (minor == 0) {
+ minorSize = 1;
+ } else {
+ minorSize = (int) Math.ceil(Math.log10(minor));
+ }
+ return (major + ((float) (minor) / (minorSize * 10)));
+ }
+
+ public boolean getCleanupSupport() {
+ return cleanupSupport == 1;
+ }
+
+ public short getApduBufferLength() {
+ return apduBufferLength;
+ }
+
+ public short getRamArrayLength() {
+ return ramArrayLength;
+ }
+
+ public short getRamArray2Length() {
+ return ramArray2Length;
+ }
+
+ public short getApduArrayLength() {
+ return apduArrayLength;
+ }
+ }
+
+ /**
+ *
+ */
+ public static class SetDryRunMode extends Response {
+
+ public SetDryRunMode(ResponseAPDU response, String description, long time) {
+ super(response, description, time);
+
+ parse(1, 0);
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardCofactorSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCofactorSuite.java
new file mode 100644
index 0000000..01e9d02
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCofactorSuite.java
@@ -0,0 +1,65 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Key;
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static cz.crcs.ectester.common.test.Result.ExpectedValue;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardCofactorSuite extends CardTestSuite {
+ public CardCofactorSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "cofactor", new String[]{"preset", "random"}, "The cofactor test suite tests whether the card correctly rejects points on the curve",
+ "but not in the subgroup generated by the generator(so of small order, dividing the cofactor) during ECDH.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "cofactor");
+ Map<EC_Curve, List<EC_Key.Public>> curveList = EC_Store.mapKeyToCurve(pubkeys.values());
+ for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curveList.entrySet()) {
+ EC_Curve curve = e.getKey();
+ List<EC_Key.Public> keys = e.getValue();
+
+ Test allocate = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS);
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS);
+ Test generate = setupKeypairs(curve, ExpectedValue.SUCCESS, CardConsts.KEYPAIR_LOCAL);
+
+ Test prepare = CompoundTest.all(ExpectedValue.SUCCESS, "Prepare and generate keypair on " + curve.getId() + ".", allocate, set, generate);
+
+ List<Test> ecdhTests = new LinkedList<>();
+ for (EC_Key.Public pub : keys) {
+ Test setPub = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE);
+ Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE);
+ Test objectEcdh = CompoundTest.any(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with cofactor pubkey.", setPub, ecdh);
+ Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten());
+ Test rawEcdh = CommandTest.expect(ecdhCommand, ExpectedValue.FAILURE, "Card correctly rejected point on non-generator subgroup.", "Card incorrectly accepted point on non-generator subgroup.");
+ ecdhTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, pub.getId() + " cofactor key test.", objectEcdh, rawEcdh));
+ }
+ Test ecdh = CompoundTest.all(ExpectedValue.SUCCESS, "Perform ECDH with public points on non-generator subgroup.", ecdhTests.toArray(new Test[0]));
+
+ if (cfg.cleanup) {
+ Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY);
+ doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Cofactor test of " + curve.getId() + ".", prepare, ecdh, cleanup));
+ } else {
+ doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Cofactor test of " + curve.getId() + ".", prepare, ecdh));
+ }
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompositeSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompositeSuite.java
new file mode 100644
index 0000000..a28c2a7
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompositeSuite.java
@@ -0,0 +1,122 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Key;
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static cz.crcs.ectester.common.test.Result.ExpectedValue;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardCompositeSuite extends CardTestSuite {
+
+ public CardCompositeSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "composite", new String[]{"preset", "random"}, "The composite suite runs ECDH over curves with composite order.",
+ "Various types of compositeness is tested: smooth numbers, Carmichael pseudo-prime, prime square, product of two large primes.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ Map<String, EC_Key> keys = EC_Store.getInstance().getObjects(EC_Key.class, "composite");
+ Map<EC_Curve, List<EC_Key>> mappedKeys = EC_Store.mapKeyToCurve(keys.values());
+ for (Map.Entry<EC_Curve, List<EC_Key>> curveKeys : mappedKeys.entrySet()) {
+ EC_Curve curve = curveKeys.getKey();
+ List<Test> tests = new LinkedList<>();
+ Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_LOCAL, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS));
+ if (!allocate.ok()) {
+ doTest(CompoundTest.all(ExpectedValue.SUCCESS, "No support for " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()) + ".", allocate));
+ continue;
+ }
+ tests.add(allocate);
+ tests.add(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.ANY));
+
+ String name;
+ if (cfg.testOptions.contains("preset")) {
+ name = "preset semi-random private key";
+ } else {
+ name = "generated private key";
+ }
+ tests.add(setupKeypairs(curve, ExpectedValue.ANY, CardConsts.KEYPAIR_LOCAL));
+ for (EC_Key key : curveKeys.getValue()) {
+ Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, key.flatten());
+ Test ecdh = CommandTest.expect(ecdhCommand, ExpectedValue.FAILURE, "Card correctly rejected to do ECDH over a composite order curve.", "Card incorrectly does ECDH over a composite order curve, leaks bits of private key.");
+ tests.add(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Composite test of " + curve.getId() + ", with " + name + ", " + key.getDesc(), ecdh));
+ }
+ doTest(CompoundTest.all(ExpectedValue.SUCCESS, "Composite test of " + curve.getId() + ".", tests.toArray(new Test[0])));
+ }
+
+
+ Map<String, EC_Curve> results = EC_Store.getInstance().getObjects(EC_Curve.class, "composite");
+ Map<String, List<EC_Curve>> groups = EC_Store.mapToPrefix(results.values());
+ /* Test the whole curves with both keypairs generated on card(no small-order public points provided).
+ */
+ List<EC_Curve> wholeCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("whole")).findFirst().get().getValue();
+ testGroup(wholeCurves, "Composite generator order", ExpectedValue.FAILURE, "Card rejected to do ECDH with composite order generator.", "Card did not reject to do ECDH with composite order generator.");
+
+ /* Also test having a G of small order, so small R.
+ */
+ List<EC_Curve> smallRCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("small")).findFirst().get().getValue();
+ testGroup(smallRCurves, "Small generator order", ExpectedValue.FAILURE, "Card correctly rejected to do ECDH over a small order generator.", "Card incorrectly does ECDH over a small order generator.");
+
+ /* Test increasingly larger prime R, to determine where/if card behavior changes.
+ */
+ List<EC_Curve> varyingCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("varying")).findFirst().get().getValue();
+ testGroup(varyingCurves, null, ExpectedValue.ANY, "", "");
+
+ /* Also test having a G of large but composite order, R = p * q,
+ */
+ List<EC_Curve> pqCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("pq")).findFirst().get().getValue();
+ testGroup(pqCurves, null, ExpectedValue.ANY, "", "");
+
+ /* Also test having G or large order being a Carmichael pseudoprime, R = p * q * r,
+ */
+ List<EC_Curve> ppCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("pp")).findFirst().get().getValue();
+ testGroup(ppCurves, "Generator order = Carmichael pseudo-prime", ExpectedValue.ANY, "", "");
+
+ /* Also test rg0 curves.
+ */
+ List<EC_Curve> rg0Curves = groups.entrySet().stream().filter((e) -> e.getKey().equals("rg0")).findFirst().get().getValue();
+ testGroup(rg0Curves, null, ExpectedValue.ANY, "", "");
+ }
+
+ private void testGroup(List<EC_Curve> curves, String testName, ExpectedValue dhValue, String ok, String nok) throws Exception {
+ for (EC_Curve curve : curves) {
+ Test allocate = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS);
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.ANY);
+ Test generate = setupKeypairs(curve, ExpectedValue.ANY, CardConsts.KEYPAIR_BOTH);
+ Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), dhValue, ok, nok);
+ Test ecdsa = CommandTest.expect(new Command.ECDSA_sign(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.Signature_ALG_ECDSA_SHA, CardConsts.EXPORT_FALSE, null), dhValue, ok, nok);
+
+ String description;
+ if (testName == null) {
+ description = curve.getDesc() + " test of " + curve.getId() + ".";
+ } else {
+ description = testName + " test of " + curve.getId() + ".";
+ }
+
+ Test perform = CompoundTest.all(ExpectedValue.SUCCESS, "Perform ECDH and ECDSA.", ecdh, ecdsa);
+
+ if (cfg.cleanup) {
+ Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY);
+ doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, description, allocate, set, generate, perform, cleanup));
+ } else {
+ doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, description, allocate, set, generate, perform));
+ }
+ }
+
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompressionSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompressionSuite.java
new file mode 100644
index 0000000..ab1f46a
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompressionSuite.java
@@ -0,0 +1,155 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Key;
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.common.util.ECUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+import javacard.security.KeyPair;
+
+import java.security.spec.ECPoint;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardCompressionSuite extends CardTestSuite {
+ public CardCompressionSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "compression", null, "The compression test suite tests cards support for compressed points in ECDH (as per ANSI X9.62).",
+ "It also tests for handling of bogus input in ECDH by using the point at infinity and a hybrid point with the y coordinate corrupted.",
+ "It also tests handling of compressed point in ECDH, where the x coordinate is invalid and therefore",
+ "a quadratic non-residue will be computed and (square root-ed) during decompression.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ //iterate over default curve sizes
+ // for Fp
+ // - allocate, set custom curve, generate keypairs, -> export generated.
+ // - test ecdh with local and remote simply(no compression)
+ // - test local privkey, remote pubkey (compressed)
+ // - test local privkey, remote pubkey (hybrid)
+ // - test local privkey, remote pubkey (hybrid with wrong y)
+ // - test local privkey, remote pubkey (point at infinity)
+ if (cfg.primeField) {
+ runCompression(KeyPair.ALG_EC_FP);
+ }
+ // for F2m
+ // - allocate, set custom curve, generate keypairs, -> export generated.
+ // - test ecdh with local and remote simply(no compression)
+ // - test local privkey, remote pubkey (compressed)
+ // - test local privkey, remote pubkey (hybrid)
+ // - test local privkey, remote pubkey (hybrid with wrong y)
+ // - test local privkey, remote pubkey (point at infinity)
+ if (cfg.binaryField) {
+ runCompression(KeyPair.ALG_EC_F2M);
+ }
+
+ // Now, do ECDH over SECG curves and give the implementation a compressed key that is not a quadratic residue in
+ // decompression.
+ runNonResidue();
+ }
+
+ private void runCompression(byte field) throws Exception {
+ short[] keySizes = field == KeyPair.ALG_EC_FP ? EC_Consts.FP_SIZES : EC_Consts.F2M_SIZES;
+ short domain = field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M;
+
+ for (short keyLength : keySizes) {
+ String spec = keyLength + "b " + CardUtil.getKeyTypeString(field);
+ byte curveId = EC_Consts.getCurve(keyLength, field);
+
+ Test allocateFirst = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, field), Result.ExpectedValue.SUCCESS));
+ if (!allocateFirst.ok()) {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for compression test on " + spec + ".", allocateFirst));
+ continue;
+ }
+
+ List<Test> compressionTests = new LinkedList<>();
+ compressionTests.add(allocateFirst);
+ Test setCustom = runTest(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, curveId, domain, null), Result.ExpectedValue.SUCCESS));
+ Test genCustom = runTest(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), Result.ExpectedValue.SUCCESS));
+ compressionTests.add(setCustom);
+ compressionTests.add(genCustom);
+
+ Response.Export key = new Command.Export(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.KEY_PUBLIC, EC_Consts.PARAMETER_W).send();
+ byte[] pubkey = key.getParameter(CardConsts.KEYPAIR_REMOTE, EC_Consts.KEY_PUBLIC);
+ EC_Curve secgCurve = EC_Store.getInstance().getObject(EC_Curve.class, "secg", CardUtil.getCurveName(curveId));
+ ECPoint pub;
+ try {
+ pub = ECUtil.fromX962(pubkey, secgCurve.toCurve());
+ } catch (IllegalArgumentException iae) {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "", compressionTests.toArray(new Test[0])));
+ continue;
+ }
+
+ List<Test> kaTests = new LinkedList<>();
+ for (byte kaType : EC_Consts.KA_TYPES) {
+ List<Test> thisTests = new LinkedList<>();
+ Test allocate = runTest(CommandTest.expect(new Command.AllocateKeyAgreement(this.card, kaType), Result.ExpectedValue.SUCCESS));
+ if (allocate.ok()) {
+ Test ka = runTest(CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType), Result.ExpectedValue.SUCCESS));
+
+ thisTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, "KeyAgreement setup and basic test.", allocate, ka));
+ if (ka.ok()) {
+ // tests of the good stuff
+ Test kaCompressed = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_COMPRESS, kaType), Result.ExpectedValue.SUCCESS);
+ Test kaHybrid = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_COMPRESS_HYBRID, kaType), Result.ExpectedValue.SUCCESS);
+ thisTests.add(CompoundTest.any(Result.ExpectedValue.SUCCESS, "Tests of compressed and hybrid form.", kaCompressed, kaHybrid));
+
+ // tests the bad stuff here
+ byte[] pubHybrid = ECUtil.toX962Hybrid(pub, keyLength);
+ pubHybrid[pubHybrid.length - 1] ^= 1;
+ byte[] pubHybridEncoded = ByteUtil.prependLength(pubHybrid);
+ Test kaBadHybrid = CommandTest.expect(new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType, pubHybridEncoded), Result.ExpectedValue.FAILURE);
+
+ byte[] pubInfinityEncoded = {0x01, 0x00};
+ Test kaBadInfinity = CommandTest.expect(new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType, pubInfinityEncoded), Result.ExpectedValue.FAILURE);
+ thisTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Tests of corrupted hybrid form and infinity.", kaBadHybrid, kaBadInfinity));
+ }
+ kaTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, "KeyAgreement tests of " + CardUtil.getKATypeString(kaType) + ".", thisTests.toArray(new Test[0])));
+ }
+ }
+ compressionTests.addAll(kaTests);
+ if (cfg.cleanup) {
+ compressionTests.add(CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY));
+ }
+
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Compression test of " + spec + ".", compressionTests.toArray(new Test[0])));
+ }
+ }
+
+ private void runNonResidue() {
+ Map<String, EC_Key.Public> otherKeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "misc");
+ List<EC_Key.Public> compressionKeys = EC_Store.mapToPrefix(otherKeys.values()).get("compression");
+
+ for (EC_Key.Public key : compressionKeys) {
+ EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, key.getCurve());
+ List<Test> tests = new LinkedList<>();
+ Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_LOCAL, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS));
+ if (!allocate.ok()) {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for non-residue test on " + curve.getBits() + "b " + curve.getId() + ".", allocate));
+ continue;
+ }
+ tests.add(allocate);
+ tests.add(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS));
+ tests.add(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS));
+ byte[] pointData = ECUtil.toX962Compressed(key.getParam(EC_Consts.PARAMETER_W));
+ byte[] pointDataEncoded = ByteUtil.prependLength(pointData);
+ tests.add(CommandTest.expect(new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pointDataEncoded), Result.ExpectedValue.FAILURE));
+ doTest(CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Non-residue test of " + curve.getId() + ".", tests.toArray(new Test[0])));
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardDefaultSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardDefaultSuite.java
new file mode 100644
index 0000000..df46767
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardDefaultSuite.java
@@ -0,0 +1,141 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+import javacard.security.KeyPair;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static cz.crcs.ectester.common.test.Result.ExpectedValue;
+import static cz.crcs.ectester.common.test.Result.Value;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardDefaultSuite extends CardTestSuite {
+
+ public CardDefaultSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "default", null, "The default test suite tests basic support and performance of ECDH and ECDSA.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ if (cfg.primeField) {
+ runDefault(KeyPair.ALG_EC_FP);
+ }
+ if (cfg.binaryField) {
+ runDefault(KeyPair.ALG_EC_F2M);
+ }
+ }
+
+ private void runDefault(byte field) throws Exception {
+ short[] keySizes = field == KeyPair.ALG_EC_FP ? EC_Consts.FP_SIZES : EC_Consts.F2M_SIZES;
+ short domain = field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M;
+ for (short keyLength : keySizes) {
+
+ List<Test> supportTests = new LinkedList<>();
+ Test allocateFirst = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, field), ExpectedValue.SUCCESS));
+ if (!allocateFirst.ok()) {
+ doTest(CompoundTest.all(ExpectedValue.SUCCESS, "No support for " + keyLength + "b " + CardUtil.getKeyTypeString(field) + ".", allocateFirst));
+ continue;
+ }
+ supportTests.add(allocateFirst);
+
+ Test genDefault = runTest(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), ExpectedValue.SUCCESS));
+ Test allocateSecond = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, field), ExpectedValue.SUCCESS));
+ Test setCustom = runTest(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.getCurve(keyLength, field), domain, null), ExpectedValue.SUCCESS));
+ Test genCustom = runTest(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), ExpectedValue.SUCCESS));
+ supportTests.add(genDefault);
+ supportTests.add(allocateSecond);
+ supportTests.add(setCustom);
+ supportTests.add(genCustom);
+
+ List<Test> kaTests = new LinkedList<>();
+ for (byte kaType : EC_Consts.KA_TYPES) {
+ Test allocate = runTest(CommandTest.expect(new Command.AllocateKeyAgreement(this.card, kaType), ExpectedValue.SUCCESS));
+ if (allocate.ok()) {
+ Command ecdh = new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType);
+ Test ka = runTest(CommandTest.expect(ecdh, ExpectedValue.SUCCESS));
+ Test kaCompressed = runTest(CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_COMPRESS, kaType), ExpectedValue.SUCCESS));
+
+ String kaDesc = "Test of the " + CardUtil.getKATypeString(kaType) + " KeyAgreement.";
+ Function<Test[], Result> kaCallback = (tests) -> {
+ if (tests[1].ok() || tests[2].ok()) {
+ return new Result(Value.SUCCESS, "Some ECDH is supported.");
+ } else {
+ return new Result(Value.FAILURE, "ECDH failed.");
+ }
+ };
+
+ Test compound;
+ if (ka.ok()) {
+ Test perfTest = runTest(PerformanceTest.repeat(this.card, ecdh, 10));
+ compound = runTest(CompoundTest.function(kaCallback, kaDesc, allocate, ka, kaCompressed, perfTest));
+ } else {
+ compound = runTest(CompoundTest.function(kaCallback, kaDesc, allocate, ka, kaCompressed));
+ }
+
+ kaTests.add(compound);
+ } else {
+ runTest(allocate);
+ kaTests.add(allocate);
+ }
+ }
+ Test kaTest = runTest(CompoundTest.any(ExpectedValue.SUCCESS, "KeyAgreement tests.", kaTests.toArray(new Test[0])));
+ supportTests.add(kaTest);
+
+ List<Test> signTests = new LinkedList<>();
+ for (byte sigType : EC_Consts.SIG_TYPES) {
+ Test allocate = runTest(CommandTest.expect(new Command.AllocateSignature(this.card, sigType), ExpectedValue.SUCCESS));
+ if (allocate.ok()) {
+ Command ecdsa = new Command.ECDSA(this.card, CardConsts.KEYPAIR_LOCAL, sigType, CardConsts.EXPORT_FALSE, null);
+ Test expect = runTest(CommandTest.expect(ecdsa, ExpectedValue.SUCCESS));
+
+ String signDesc = "Test of the " + CardUtil.getSigTypeString(sigType) + " signature.";
+
+ Random rand = new Random();
+ byte[] sigData = new byte[64];
+ rand.nextBytes(sigData);
+
+ Test compound;
+ if (expect.ok()) {
+ Command ecdsaSign = new Command.ECDSA_sign(this.card, CardConsts.KEYPAIR_LOCAL, sigType, CardConsts.EXPORT_TRUE, sigData);
+ PerformanceTest signTest = runTest(PerformanceTest.repeat(this.card, "Sign", ecdsaSign, 10));
+ byte[] signature = signTest.getResponses()[0].getParam(0);
+ Command ecdsaVerify = new Command.ECDSA_verify(this.card, CardConsts.KEYPAIR_LOCAL, sigType, sigData, signature);
+ PerformanceTest verifyTest = runTest(PerformanceTest.repeat(this.card, "Verify", ecdsaVerify, 10));
+ compound = runTest(CompoundTest.all(ExpectedValue.SUCCESS, signDesc, allocate, expect, signTest, verifyTest));
+ } else {
+ compound = runTest(CompoundTest.all(ExpectedValue.SUCCESS, signDesc, allocate, expect));
+ }
+ signTests.add(compound);
+ } else {
+ signTests.add(allocate);
+ }
+ }
+ Test signTest = runTest(CompoundTest.any(ExpectedValue.SUCCESS, "Signature tests.", signTests.toArray(new Test[0])));
+ supportTests.add(signTest);
+ ExpectedValue[] testExpects = {ExpectedValue.SUCCESS, ExpectedValue.ANY, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS};
+ List<ExpectedValue> expects = Stream.of(testExpects).collect(Collectors.toList());
+ if (cfg.cleanup) {
+ supportTests.add(CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY));
+ expects.add(ExpectedValue.ANY);
+ }
+
+ doTest(CompoundTest.mask(expects.toArray(new ExpectedValue[0]), "Tests of " + keyLength + "b " + CardUtil.getKeyTypeString(field) + " support.", supportTests.toArray(new Test[0])));
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardDegenerateSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardDegenerateSuite.java
new file mode 100644
index 0000000..e2c07da
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardDegenerateSuite.java
@@ -0,0 +1,69 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Key;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardDegenerateSuite extends CardTestSuite {
+
+ public CardDegenerateSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "degenerate", null, "The degenerate suite tests whether the card rejects points outside of the curve during ECDH.",
+ "The tested points lie on a part of the plane for which some Edwards, Hessian and Huff form addition formulas degenerate into exponentiation in the base finite field.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "degenerate");
+ Map<EC_Curve, List<EC_Key.Public>> curveList = EC_Store.mapKeyToCurve(pubkeys.values());
+ for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curveList.entrySet()) {
+ EC_Curve curve = e.getKey();
+ List<EC_Key.Public> keys = e.getValue();
+
+ Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS));
+ if (!allocate.ok()) {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for " + curve.getId() + ".", allocate));
+ continue;
+ }
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS);
+ Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS);
+
+ Test prepare = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Prepare and generate keypair on " + curve.getId() + ".", allocate, set, generate);
+
+ List<Test> ecdhTests = new LinkedList<>();
+ for (EC_Key.Public pub : keys) {
+ Test setPub = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE);
+ Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE, "Card correctly rejected point on degenerate curve.", "Card incorrectly accepted point on degenerate curve.");
+ Test objectEcdh = CompoundTest.any(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with degenerate pubkey.", setPub, ecdh);
+ Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten());
+ Test rawEcdh = CommandTest.expect(ecdhCommand, Result.ExpectedValue.FAILURE, "Card correctly rejected point on degenerate curve.", "Card incorrectly accepted point on degenerate curve.");
+ ecdhTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, pub.getId() + " degenerate key test.", objectEcdh, rawEcdh));
+ //TODO: actually get the result of ECDH here, as well as export privkey and compare to exponentiation in Fp^*.
+ }
+ Test ecdh = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Perform ECDH with degenerate public points.", ecdhTests.toArray(new Test[0]));
+ if (cfg.cleanup) {
+ Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY);
+ doTest(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Degenerate curve test of " + curve.getId() + ".", prepare, ecdh, cleanup));
+ } else {
+ doTest(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Degenerate curve test of " + curve.getId() + ".", prepare, ecdh));
+ }
+
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardEdgeCasesSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardEdgeCasesSuite.java
new file mode 100644
index 0000000..98a59bc
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardEdgeCasesSuite.java
@@ -0,0 +1,332 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.*;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.test.TestCallback;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.common.util.ECUtil;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+import javacard.security.CryptoException;
+import javacard.security.KeyPair;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardEdgeCasesSuite extends CardTestSuite {
+ public CardEdgeCasesSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "edge-cases", null, "The edge-cases test suite tests various inputs to ECDH which may cause an implementation to achieve a certain edge-case state during it.",
+ "Some of the data is from the google/Wycheproof project. Tests include CVE-2017-10176 and CVE-2017-8932.",
+ "Also tests values of the private key and public key that would trigger the OpenSSL modular multiplication bug on the P-256 curve.",
+ "Various edge private key values are also tested.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ Map<String, EC_KAResult> results = EC_Store.getInstance().getObjects(EC_KAResult.class, "wycheproof");
+ Map<String, List<EC_KAResult>> groups = EC_Store.mapToPrefix(results.values());
+ for (Map.Entry<String, List<EC_KAResult>> e : groups.entrySet()) {
+ String description = null;
+ switch (e.getKey()) {
+ case "addsub":
+ description = "Tests for addition-subtraction chains.";
+ break;
+ case "cve_2017_10176":
+ description = "Tests for CVE-2017-10176.";
+ break;
+ case "cve_2017_8932":
+ description = "Tests for CVE-2017-8932.";
+ break;
+ }
+
+ List<Test> groupTests = new LinkedList<>();
+ Map<EC_Curve, List<EC_KAResult>> curveList = EC_Store.mapResultToCurve(e.getValue());
+ for (Map.Entry<EC_Curve, List<EC_KAResult>> c : curveList.entrySet()) {
+ EC_Curve curve = c.getKey();
+
+ List<Test> curveTests = new LinkedList<>();
+ Test allocate = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS);
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS);
+ Test prepareCurve = CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Prepare curve", allocate, set);
+
+ List<EC_KAResult> values = c.getValue();
+ for (EC_KAResult value : values) {
+ String id = value.getId();
+ String privkeyId = value.getOneKey();
+ String pubkeyId = value.getOtherKey();
+
+ EC_Key.Private privkey = EC_Store.getInstance().getObject(EC_Key.Private.class, privkeyId);
+ EC_Key.Public pubkey = EC_Store.getInstance().getObject(EC_Key.Public.class, pubkeyId);
+
+ Test setPrivkey = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, privkey.getParams(), privkey.flatten()), Result.ExpectedValue.SUCCESS);
+ Test setPubkey = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pubkey.getParams(), pubkey.flatten()), Result.ExpectedValue.SUCCESS);
+ Test ecdhPreTest = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.SUCCESS);
+ Test ecdh = CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, value.getJavaCardKA()), new TestCallback<CommandTestable>() {
+ @Override
+ public Result apply(CommandTestable testable) {
+ Response.ECDH dh = (Response.ECDH) testable.getResponse();
+ if (dh.getSW(0) == CryptoException.NO_SUCH_ALGORITHM) {
+ return new Result(Result.Value.SUCCESS, "ECDH algorithm unsupported.");
+ }
+ if (!dh.successful())
+ return new Result(Result.Value.FAILURE, "ECDH was unsuccessful.");
+ if (!dh.hasSecret())
+ return new Result(Result.Value.FAILURE, "ECDH response did not contain the derived secret.");
+ if (!ByteUtil.compareBytes(dh.getSecret(), 0, value.getData(0), 0, dh.secretLength())) {
+ int firstDiff = ByteUtil.diffBytes(dh.getSecret(), 0, value.getData(0), 0, dh.secretLength());
+ System.err.println(ByteUtil.bytesToHex(dh.getSecret()));
+ System.err.println(ByteUtil.bytesToHex(value.getData(0)));
+ return new Result(Result.Value.FAILURE, "ECDH derived secret does not match the test-vector, first difference was at byte " + firstDiff + ".");
+ }
+ return new Result(Result.Value.SUCCESS);
+ }
+ });
+
+ Test prepare = CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Prepare", setPrivkey, setPubkey);
+ Test ka = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Do", ecdhPreTest, ecdh);
+
+ Test one = CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Test " + id + ".", prepare, ka);
+ curveTests.add(one);
+ }
+
+ if (cfg.cleanup) {
+ curveTests.add(CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY));
+ }
+
+ Test curveTest = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Tests", curveTests.toArray(new Test[0]));
+ groupTests.add(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Tests on " + curve.getId() + ".", prepareCurve, curveTest));
+ }
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, description, groupTests.toArray(new Test[0])));
+ }
+
+ {
+ EC_KAResult openssl_bug = EC_Store.getInstance().getObject(EC_KAResult.class, "misc", "openssl-bug");
+ EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, openssl_bug.getCurve());
+ EC_Key.Private skey = EC_Store.getInstance().getObject(EC_Key.Private.class, openssl_bug.getOtherKey());
+ EC_Key.Public pkey = EC_Store.getInstance().getObject(EC_Key.Public.class, openssl_bug.getOneKey());
+ Test key = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), KeyPair.ALG_EC_FP), Result.ExpectedValue.SUCCESS);
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS);
+ Test setPrivate = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, skey.flatten(EC_Consts.PARAMETER_S)), Result.ExpectedValue.SUCCESS);
+ Test setPublic = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, pkey.flatten(EC_Consts.PARAMETER_W)), Result.ExpectedValue.SUCCESS);
+ Test ecdh = CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, openssl_bug.getJavaCardKA()), new TestCallback<CommandTestable>() {
+ @Override
+ public Result apply(CommandTestable testable) {
+ Response.ECDH dh = (Response.ECDH) testable.getResponse();
+ if (!dh.successful())
+ return new Result(Result.Value.FAILURE, "ECDH was unsuccessful.");
+ if (!dh.hasSecret())
+ return new Result(Result.Value.FAILURE, "ECDH response did not contain the derived secret.");
+ if (ByteUtil.compareBytes(dh.getSecret(), 0, openssl_bug.getData(0), 0, dh.secretLength())) {
+ return new Result(Result.Value.FAILURE, "OpenSSL bug is present, derived secret matches example.");
+ }
+ return new Result(Result.Value.SUCCESS);
+ }
+ });
+
+ doTest(CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Test OpenSSL modular reduction bug.", key, set, setPrivate, setPublic, ecdh));
+ }
+
+ Map<String, EC_Curve> curveMap = EC_Store.getInstance().getObjects(EC_Curve.class, "secg");
+ List<EC_Curve> curves = curveMap.entrySet().stream().filter((e) -> e.getKey().endsWith("r1") && e.getValue().getField() == KeyPair.ALG_EC_FP).map(Map.Entry::getValue).collect(Collectors.toList());
+ curves.add(EC_Store.getInstance().getObject(EC_Curve.class, "cofactor/cofactor128p2"));
+ curves.add(EC_Store.getInstance().getObject(EC_Curve.class, "cofactor/cofactor160p4"));
+ Random rand = new Random();
+ for (EC_Curve curve : curves) {
+ Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), KeyPair.ALG_EC_FP), Result.ExpectedValue.SUCCESS));
+ if (!key.ok()) {
+ doTest(CompoundTest.all(Result.ExpectedValue.FAILURE, "No support for " + curve.getBits() + "b " + curve.getId() + ".", key));
+ continue;
+ }
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS);
+ Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS);
+ CommandTest export = CommandTest.expect(new Command.Export(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, EC_Consts.PARAMETER_W), Result.ExpectedValue.SUCCESS);
+ Test setup = runTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "KeyPair setup.", key, set, generate, export));
+
+ /*
+ byte[] pParam = curve.getParam(EC_Consts.PARAMETER_FP)[0];
+ BigInteger p = new BigInteger(1, pParam);
+ byte[] wParam = ((Response.Export) export.getResponse()).getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W);
+ byte[] xValue = new byte[(wParam.length - 1) / 2];
+ byte[] yValue = new byte[(wParam.length - 1) / 2];
+ System.arraycopy(wParam, 1, xValue, 0, xValue.length);
+ System.arraycopy(wParam, (wParam.length / 2) + 1, yValue, 0, yValue.length);
+ BigInteger y = new BigInteger(1, yValue);
+ BigInteger negY = p.subtract(y);
+ byte[] newY = ECUtil.toByteArray(negY, curve.getBits());
+
+ EC_Params negYParams = new EC_Params(EC_Consts.PARAMETER_W, new byte[][]{xValue, newY});
+ Test negYTest = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, negYParams.getParams(), negYParams.flatten()), "ECDH with pubkey negated.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE);
+ */
+
+ Test zeroS = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, EC_Consts.TRANSFORMATION_ZERO), "ECDH with S = 0.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE);
+ Test oneS = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, EC_Consts.TRANSFORMATION_ONE), "ECDH with S = 1.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE);
+
+ byte[] rParam = curve.getParam(EC_Consts.PARAMETER_R)[0];
+ BigInteger R = new BigInteger(1, rParam);
+ BigInteger smaller = new BigInteger(curve.getBits(), rand).mod(R);
+ BigInteger diff = R.divide(BigInteger.valueOf(10));
+ BigInteger randDiff = new BigInteger(diff.bitLength(), rand).mod(diff);
+ BigInteger larger = R.add(randDiff);
+
+ BigInteger full = BigInteger.valueOf(1).shiftLeft(R.bitLength() - 1).subtract(BigInteger.ONE);
+
+ BigInteger alternate = full;
+ for (int i = 0; i < R.bitLength(); i += 2) {
+ alternate = alternate.clearBit(i);
+ }
+
+ BigInteger alternateOther = alternate.xor(full);
+
+ EC_Params alternateParams = makeParams(alternate);
+ Test alternateS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, alternateParams.getParams(), alternateParams.flatten()), "ECDH with S = 101010101...01010.", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS);
+
+ EC_Params alternateOtherParams = makeParams(alternateOther);
+ Test alternateOtherS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, alternateOtherParams.getParams(), alternateOtherParams.flatten()), "ECDH with S = 010101010...10101.", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS);
+
+ EC_Params fullParams = makeParams(full);
+ Test fullS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, fullParams.getParams(), fullParams.flatten()), "ECDH with S = 111111111...11111 (but < r).", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS);
+
+ EC_Params smallerParams = makeParams(smaller);
+ Test smallerS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, smallerParams.getParams(), smallerParams.flatten()), "ECDH with S < r.", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS);
+
+ EC_Params exactParams = makeParams(R);
+ Test exactS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, exactParams.getParams(), exactParams.flatten()), "ECDH with S = r.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE);
+
+ EC_Params largerParams = makeParams(larger);
+ Test largerS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, largerParams.getParams(), largerParams.flatten()), "ECDH with S > r.", Result.ExpectedValue.ANY, Result.ExpectedValue.ANY);
+
+ BigInteger rm1 = R.subtract(BigInteger.ONE);
+ BigInteger rp1 = R.add(BigInteger.ONE);
+
+ EC_Params rm1Params = makeParams(rm1);
+ Test rm1S = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, rm1Params.getParams(), rm1Params.flatten()), "ECDH with S = r - 1.", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS);
+
+ EC_Params rp1Params = makeParams(rp1);
+ Test rp1S = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, rp1Params.getParams(), rp1Params.flatten()), "ECDH with S = r + 1.", Result.ExpectedValue.ANY, Result.ExpectedValue.ANY);
+
+ byte[] k = curve.getParam(EC_Consts.PARAMETER_K)[0];
+ BigInteger K = new BigInteger(1, k);
+ BigInteger kr = K.multiply(R);
+ BigInteger krm1 = kr.subtract(BigInteger.ONE);
+ BigInteger krp1 = kr.add(BigInteger.ONE);
+
+ Result.ExpectedValue kExpected = K.equals(BigInteger.ONE) ? Result.ExpectedValue.SUCCESS : Result.ExpectedValue.FAILURE;
+
+ EC_Params krParams = makeParams(kr);
+ Test krS /*ONE!*/ = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, krParams.getParams(), krParams.flatten()), "ECDH with S = k * r.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE);
+
+ EC_Params krm1Params = makeParams(krm1);
+ Test krm1S = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, krm1Params.getParams(), krm1Params.flatten()), "ECDH with S = (k * r) - 1.", kExpected, kExpected);
+
+ EC_Params krp1Params = makeParams(krp1);
+ Test krp1S = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, krp1Params.getParams(), krp1Params.flatten()), "ECDH with S = (k * r) + 1.", Result.ExpectedValue.ANY, Result.ExpectedValue.ANY);
+
+ if (cfg.cleanup) {
+ Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY);
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Tests with edge-case private key values over " + curve.getId() + ".", setup, zeroS, oneS, alternateS, alternateOtherS, fullS, smallerS, exactS, largerS, rm1S, rp1S, krS, krm1S, krp1S, cleanup));
+ } else {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Tests with edge-case private key values over " + curve.getId() + ".", setup, zeroS, oneS, alternateS, alternateOtherS, fullS, smallerS, exactS, largerS, rm1S, rp1S, krS, krm1S, krp1S));
+ }
+ }
+
+ EC_Curve secp160r1 = EC_Store.getInstance().getObject(EC_Curve.class, "secg/secp160r1");
+ byte[] pData = secp160r1.getParam(EC_Consts.PARAMETER_FP)[0];
+ BigInteger p = new BigInteger(1, pData);
+ byte[] rData = secp160r1.getParam(EC_Consts.PARAMETER_R)[0];
+ BigInteger r = new BigInteger(1, rData);
+
+ BigInteger range = r.subtract(p);
+ BigInteger deviation = range.divide(BigInteger.valueOf(5));
+ BigDecimal dev = new BigDecimal(deviation);
+ BigDecimal smallDev = new BigDecimal(10000);
+ int n = 10;
+ BigInteger[] rs = new BigInteger[n];
+ BigInteger[] ps = new BigInteger[n];
+ BigInteger[] zeros = new BigInteger[n];
+ for (int i = 0; i < n; ++i) {
+ double sample;
+ do {
+ sample = rand.nextGaussian();
+ } while (sample >= -1 && sample <= 1);
+ BigInteger where = dev.multiply(new BigDecimal(sample)).toBigInteger();
+ rs[i] = where.add(r);
+ ps[i] = where.add(p);
+ zeros[i] = smallDev.multiply(new BigDecimal(sample)).toBigInteger().abs();
+ }
+ Arrays.sort(rs);
+ Arrays.sort(ps);
+ Arrays.sort(zeros);
+
+ Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, secp160r1.getBits(), KeyPair.ALG_EC_FP), Result.ExpectedValue.SUCCESS));
+ if (!key.ok()) {
+ doTest(CompoundTest.all(Result.ExpectedValue.FAILURE, "No support for " + secp160r1.getBits() + "b secp160r1.", key));
+ return;
+ }
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, secp160r1.getParams(), secp160r1.flatten()), Result.ExpectedValue.SUCCESS);
+ Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS);
+ Test setup = CompoundTest.all(Result.ExpectedValue.SUCCESS, "KeyPair setup.", key, set, generate);
+
+ Test[] zeroTests = new Test[n];
+ int i = 0;
+ for (BigInteger nearZero : zeros) {
+ EC_Params params = makeParams(nearZero);
+ zeroTests[i++] = ecdhTestBoth(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, params.getParams(), params.flatten()), nearZero.toString(16), Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS);
+ }
+ Test zeroTest = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Near zero.", zeroTests);
+
+ Test[] pTests = new Test[n];
+ i = 0;
+ for (BigInteger nearP : ps) {
+ EC_Params params = makeParams(nearP);
+ pTests[i++] = ecdhTestBoth(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, params.getParams(), params.flatten()), nearP.toString(16) + (nearP.compareTo(p) > 0 ? " (>p)" : " (<=p)"), Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS);
+ }
+ Test pTest = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Near p.", pTests);
+
+ Test[] rTests = new Test[n];
+ i = 0;
+ for (BigInteger nearR : rs) {
+ EC_Params params = makeParams(nearR);
+ if (nearR.compareTo(r) >= 0) {
+ rTests[i++] = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, params.getParams(), params.flatten()), nearR.toString(16) + " (>=r)", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE);
+ } else {
+ rTests[i++] = ecdhTestBoth(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, params.getParams(), params.flatten()), nearR.toString(16) + " (<r)", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS);
+ }
+ }
+ Test rTest = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Near r.", rTests);
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Test private key values near zero, near p and near/larger than the order.", setup, zeroTest, pTest, rTest));
+ }
+
+ private Test ecdhTestBoth(Command setPriv, String desc, Result.ExpectedValue setExpect, Result.ExpectedValue ecdhExpect) {
+ Test set = CommandTest.expect(setPriv, setExpect);
+ Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), ecdhExpect);
+
+ return CompoundTest.all(Result.ExpectedValue.SUCCESS, desc, set, ecdh);
+ }
+
+ private Test ecdhTest(Command setPriv, String desc, Result.ExpectedValue setExpect, Result.ExpectedValue ecdhExpect) {
+ Test set = CommandTest.expect(setPriv, setExpect);
+ Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), ecdhExpect);
+
+ return CompoundTest.any(Result.ExpectedValue.SUCCESS, desc, set, ecdh);
+ }
+
+ private EC_Params makeParams(BigInteger s) {
+ return makeParams(ECUtil.toByteArray(s, s.bitLength()));
+ }
+
+ private EC_Params makeParams(byte[] s) {
+ return new EC_Params(EC_Consts.PARAMETER_S, new byte[][]{s});
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardInvalidSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardInvalidSuite.java
new file mode 100644
index 0000000..4657de0
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardInvalidSuite.java
@@ -0,0 +1,72 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Key;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static cz.crcs.ectester.common.test.Result.ExpectedValue;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardInvalidSuite extends CardTestSuite {
+
+ public CardInvalidSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "invalid", null, "The invalid curve suite tests whether the card rejects points outside of the curve during ECDH.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ /* Set original curves (secg/nist/brainpool). Generate local.
+ * Try ECDH with invalid public keys of increasing order.
+ */
+ Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "invalid");
+ Map<EC_Curve, List<EC_Key.Public>> curveList = EC_Store.mapKeyToCurve(pubkeys.values());
+ for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curveList.entrySet()) {
+ EC_Curve curve = e.getKey();
+ List<EC_Key.Public> keys = e.getValue();
+
+ Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS));
+ if (!allocate.ok()) {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for " + curve.getId() + ".", allocate));
+ continue;
+ }
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS);
+ Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), ExpectedValue.SUCCESS);
+
+ Test prepare = CompoundTest.all(ExpectedValue.SUCCESS, "Prepare and generate keypair on " + curve.getId() + ".", allocate, set, generate);
+
+ List<Test> ecdhTests = new LinkedList<>();
+ for (EC_Key.Public pub : keys) {
+ Test setPub = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE);
+ Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE);
+ Test objectEcdh = CompoundTest.any(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with invalid pubkey.", setPub, ecdh);
+ Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten());
+ Test rawEcdh = CommandTest.expect(ecdhCommand, ExpectedValue.FAILURE, "Card correctly rejected point on invalid curve.", "Card incorrectly accepted point on invalid curve.");
+ ecdhTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, pub.getId() + " invalid key test.", objectEcdh, rawEcdh));
+ }
+ Test ecdh = CompoundTest.all(ExpectedValue.SUCCESS, "Perform ECDH with invalid public points.", ecdhTests.toArray(new Test[0]));
+
+ if (cfg.cleanup) {
+ Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY);
+ doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Invalid curve test of " + curve.getId() + ".", prepare, ecdh, cleanup));
+ } else {
+ doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Invalid curve test of " + curve.getId() + ".", prepare, ecdh));
+ }
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardMiscSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardMiscSuite.java
new file mode 100644
index 0000000..da4c0b5
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardMiscSuite.java
@@ -0,0 +1,77 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardMiscSuite extends CardTestSuite {
+
+ public CardMiscSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "miscellaneous", new String[]{"preset", "random"}, "Some miscellaneous tests, tries ECDH and ECDSA over supersingular curves, anomalous curves,",
+ "Barreto-Naehrig curves with small embedding degree and CM discriminant, MNT curves,",
+ "some Montgomery curves transformed to short Weierstrass form and Curve25519 transformed to short Weierstrass form.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ Map<String, EC_Curve> anCurves = EC_Store.getInstance().getObjects(EC_Curve.class, "anomalous");
+ Map<String, EC_Curve> ssCurves = EC_Store.getInstance().getObjects(EC_Curve.class, "supersingular");
+ Map<String, EC_Curve> bnCurves = EC_Store.getInstance().getObjects(EC_Curve.class, "Barreto-Naehrig");
+ Map<String, EC_Curve> mntCurves = EC_Store.getInstance().getObjects(EC_Curve.class, "MNT");
+ List<EC_Curve> mCurves = new ArrayList<>();
+ mCurves.add(EC_Store.getInstance().getObject(EC_Curve.class, "other", "M-221"));
+ mCurves.add(EC_Store.getInstance().getObject(EC_Curve.class, "other", "M-383"));
+ mCurves.add(EC_Store.getInstance().getObject(EC_Curve.class, "other", "M-511"));
+ EC_Curve curve25519 = EC_Store.getInstance().getObject(EC_Curve.class, "other", "Curve25519");
+
+ testCurves(anCurves.values(), "anomalous", Result.ExpectedValue.FAILURE);
+ testCurves(ssCurves.values(), "supersingular", Result.ExpectedValue.FAILURE);
+ testCurves(bnCurves.values(), "Barreto-Naehrig", Result.ExpectedValue.SUCCESS);
+ testCurves(mntCurves.values(), "MNT", Result.ExpectedValue.SUCCESS);
+ testCurves(mCurves, "Montgomery", Result.ExpectedValue.SUCCESS);
+ testCurve(curve25519, "Montgomery", Result.ExpectedValue.SUCCESS);
+ }
+
+ private void testCurve(EC_Curve curve, String catName, Result.ExpectedValue expected) {
+ Test allocateFirst = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS));
+ if (!allocateFirst.ok()) {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for " + curve.getBits() + "b " + catName + " curve: " + curve.getId() + ".", allocateFirst));
+ return;
+ }
+
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS);
+ Test generate = setupKeypairs(curve, Result.ExpectedValue.ANY, CardConsts.KEYPAIR_BOTH);
+ Test ka = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), expected);
+ Test sig = CommandTest.expect(new Command.ECDSA_sign(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.Signature_ALG_ECDSA_SHA, CardConsts.EXPORT_FALSE, null), expected);
+ Test perform = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Perform ECDH and ECDSA.", ka, sig);
+
+ if (cfg.cleanup) {
+ Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY);
+ doTest(CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Tests over " + curve.getBits() + "b " + catName + " curve: " + curve.getId() + ".", allocateFirst, set, generate, perform, cleanup));
+ } else {
+ doTest(CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Tests over " + curve.getBits() + "b " + catName + " curve: " + curve.getId() + ".", allocateFirst, set, generate, perform));
+ }
+ }
+
+ private void testCurves(Collection<EC_Curve> curves, String catName, Result.ExpectedValue expected) {
+ for (EC_Curve curve : curves) {
+ testCurve(curve, catName, expected);
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardSignatureSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardSignatureSuite.java
new file mode 100644
index 0000000..086654a
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardSignatureSuite.java
@@ -0,0 +1,68 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Key;
+import cz.crcs.ectester.common.ec.EC_SigResult;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardSignatureSuite extends CardTestSuite {
+ public CardSignatureSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "signature", null, "The signature test suite tests verifying various malformed and well-formed but invalid ECDSA signatures.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ Map<String, EC_SigResult> results = EC_Store.getInstance().getObjects(EC_SigResult.class, "wrong");
+ Map<String, List<EC_SigResult>> groups = EC_Store.mapToPrefix(results.values());
+
+ List<EC_SigResult> nok = groups.entrySet().stream().filter((e) -> e.getKey().equals("nok")).findFirst().get().getValue();
+
+ byte[] data = "Some stuff that is not the actual data".getBytes();
+ for (EC_SigResult sig : nok) {
+ ecdsaTest(sig, Result.ExpectedValue.FAILURE, data);
+ }
+
+ List<EC_SigResult> ok = groups.entrySet().stream().filter((e) -> e.getKey().equals("ok")).findFirst().get().getValue();
+ for (EC_SigResult sig : ok) {
+ ecdsaTest(sig, Result.ExpectedValue.SUCCESS, null);
+ }
+ }
+
+ private void ecdsaTest(EC_SigResult sig, Result.ExpectedValue expected, byte[] defaultData) {
+ EC_Key.Public pubkey = EC_Store.getInstance().getObject(EC_Key.Public.class, sig.getVerifyKey());
+
+ byte[] data = sig.getSigData();
+ if (data == null) {
+ data = defaultData;
+ }
+
+ EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, sig.getCurve());
+ Test allocate = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_LOCAL, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS);
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS);
+ Test setVerifyKey = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, pubkey.getParams(), pubkey.flatten()), Result.ExpectedValue.SUCCESS);
+ Test ecdsaVerify = CommandTest.expect(new Command.ECDSA_verify(this.card, CardConsts.KEYPAIR_LOCAL, sig.getJavaCardSig(), data, sig.getData(0)), expected);
+
+ if (cfg.cleanup) {
+ Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY);
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "ECDSA test of " + sig.getId() + ".", allocate, set, setVerifyKey, ecdsaVerify, cleanup));
+ } else {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "ECDSA test of " + sig.getId() + ".", allocate, set, setVerifyKey, ecdsaVerify));
+ }
+
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestSuite.java
new file mode 100644
index 0000000..15c4469
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestSuite.java
@@ -0,0 +1,94 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Params;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.test.TestSuite;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.common.util.ECUtil;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+
+import java.util.Arrays;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class CardTestSuite extends TestSuite {
+ ECTesterReader.Config cfg;
+ CardMngr card;
+ String[] options;
+
+ CardTestSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager, String name, String[] options, String... description) {
+ super(writer, name, description);
+ this.card = cardManager;
+ this.cfg = cfg;
+ this.options = options;
+ }
+
+ public CardMngr getCard() {
+ return card;
+ }
+
+ public ECTesterReader.Config getCfg() {
+ return cfg;
+ }
+
+ public String[] getOptions() {
+ if (options != null) {
+ return options.clone();
+ } else {
+ return options;
+ }
+ }
+
+ public Test setupKeypairs(EC_Curve curve, Result.ExpectedValue expected, byte keyPair) {
+ if ((Arrays.asList(options).contains("preset") && cfg.testOptions.contains("preset")) || (Arrays.asList(options).contains("random") && cfg.testOptions.contains("random"))) {
+ Test setLocal = null;
+ if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) {
+ EC_Params priv;
+ if (cfg.testOptions.contains("preset")) {
+ priv = ECUtil.fixedRandomKey(curve);
+ } else {
+ priv = ECUtil.fullRandomKey(curve);
+ }
+ setLocal = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, priv.getParams(), priv.flatten()), expected);
+ }
+ Test setRemote = null;
+ if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) {
+ EC_Params pub;
+ if (cfg.testOptions.contains("preset")) {
+ pub = ECUtil.fixedRandomPoint(curve);
+ } else {
+ pub = ECUtil.fullRandomPoint(curve);
+ }
+ if (pub == null) {
+ setRemote = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_REMOTE), expected);
+ } else {
+ setRemote = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), expected);
+ }
+ }
+
+ if (keyPair == CardConsts.KEYPAIR_LOCAL) {
+ return setLocal;
+ } else if (keyPair == CardConsts.KEYPAIR_REMOTE) {
+ return setRemote;
+ } else {
+ String desc;
+ if (cfg.testOptions.contains("preset")) {
+ desc = "Set semi-random parameters.";
+ } else {
+ desc = "Set fully-random parameters.";
+ }
+ return CompoundTest.all(expected, desc, setLocal, setRemote);
+ }
+ } else {
+ return CommandTest.expect(new Command.Generate(this.card, keyPair), expected);
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestVectorSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestVectorSuite.java
new file mode 100644
index 0000000..056be17
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestVectorSuite.java
@@ -0,0 +1,234 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.*;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.test.TestCallback;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.common.util.ECUtil;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+import javacard.security.KeyPair;
+
+import javax.crypto.KeyAgreement;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.*;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+import static cz.crcs.ectester.common.test.Result.ExpectedValue;
+import static cz.crcs.ectester.common.test.Result.Value;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardTestVectorSuite extends CardTestSuite {
+
+ public CardTestVectorSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "test-vectors", null, "The test-vectors suite contains a collection of test vectors which test basic ECDH correctness.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ /* 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 = EC_Store.getInstance().getObjects(EC_KAResult.class, "test");
+ for (EC_KAResult result : results.values()) {
+ EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, result.getCurve());
+ EC_Params onekey = EC_Store.getInstance().getObject(EC_Keypair.class, result.getOneKey());
+ if (onekey == null) {
+ onekey = EC_Store.getInstance().getObject(EC_Key.Private.class, result.getOneKey());
+ }
+ EC_Params otherkey = EC_Store.getInstance().getObject(EC_Keypair.class, result.getOtherKey());
+ if (otherkey == null) {
+ otherkey = EC_Store.getInstance().getObject(EC_Key.Public.class, result.getOtherKey());
+ }
+ if (onekey == null || otherkey == null) {
+ throw new IOException("Test vector keys couldn't be located.");
+ }
+ List<Test> testVector = new LinkedList<>();
+ Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS));
+ if (!allocate.ok()) {
+ doTest(CompoundTest.all(ExpectedValue.SUCCESS, "No support for " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()) + ".", allocate));
+ continue;
+ }
+
+ testVector.add(allocate);
+ testVector.add(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS));
+ testVector.add(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, onekey.flatten(EC_Consts.PARAMETER_S)), ExpectedValue.SUCCESS));
+ testVector.add(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, otherkey.flatten(EC_Consts.PARAMETER_W)), ExpectedValue.SUCCESS));
+ testVector.add(CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, result.getJavaCardKA()), new TestCallback<CommandTestable>() {
+ @Override
+ public Result apply(CommandTestable testable) {
+ Response.ECDH dh = (Response.ECDH) testable.getResponse();
+ if (!dh.successful())
+ return new Result(Value.FAILURE, "ECDH was unsuccessful.");
+ if (!dh.hasSecret())
+ return new Result(Value.FAILURE, "ECDH response did not contain the derived secret.");
+ if (!ByteUtil.compareBytes(dh.getSecret(), 0, result.getData(0), 0, dh.secretLength())) {
+ int firstDiff = ByteUtil.diffBytes(dh.getSecret(), 0, result.getData(0), 0, dh.secretLength());
+ return new Result(Value.FAILURE, "ECDH derived secret does not match the test-vector, first difference was at byte " + firstDiff + ".");
+ }
+ return new Result(Value.SUCCESS);
+ }
+ }));
+ if (cfg.cleanup) {
+ testVector.add(CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY));
+ }
+ doTest(CompoundTest.greedyAll(ExpectedValue.SUCCESS, "Test vector " + result.getId() + ".", testVector.toArray(new Test[0])));
+ }
+
+ KeyAgreement ka;
+ Signature sig;
+ KeyFactory kf;
+ MessageDigest md;
+ try {
+ ka = KeyAgreement.getInstance("ECDH", "BC");
+ sig = Signature.getInstance("ECDSAwithSHA1", "BC");
+ kf = KeyFactory.getInstance("ECDH", "BC");
+ md = MessageDigest.getInstance("SHA1", "BC");
+ } catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
+ return;
+ }
+
+ List<EC_Curve> testCurves = new ArrayList<>();
+ testCurves.addAll(EC_Store.getInstance().getObjects(EC_Curve.class, "secg").values().stream().filter((curve) -> curve.getField() == KeyPair.ALG_EC_FP).collect(Collectors.toList()));
+ testCurves.addAll(EC_Store.getInstance().getObjects(EC_Curve.class, "brainpool").values().stream().filter((curve) -> curve.getField() == KeyPair.ALG_EC_FP).collect(Collectors.toList()));
+ for (EC_Curve curve : testCurves) {
+ List<Test> testVector = new LinkedList<>();
+ Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS));
+ if (!allocate.ok()) {
+ doTest(CompoundTest.all(ExpectedValue.SUCCESS, "No support for " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()) + ".", allocate));
+ continue;
+ }
+ testVector.add(allocate);
+ testVector.add(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS));
+ testVector.add(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), ExpectedValue.SUCCESS));
+ CommandTest exportLocal = CommandTest.expect(new Command.Export(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, EC_Consts.PARAMETER_W), ExpectedValue.ANY);
+ CommandTest exportRemote = CommandTest.expect(new Command.Export(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.KEY_PRIVATE, EC_Consts.PARAMETER_S), ExpectedValue.ANY);
+ testVector.add(exportLocal);
+ testVector.add(exportRemote);
+ BiFunction<Response.Export, Response.Export, Key[]> getKeys = (localData, remoteData) -> {
+ byte[] pkey = localData.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W);
+ byte[] skey = remoteData.getParameter(CardConsts.KEYPAIR_REMOTE, EC_Consts.PARAMETER_S);
+ ECParameterSpec spec = curve.toSpec();
+ ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(new BigInteger(1, skey), spec);
+ ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(ECUtil.fromX962(pkey, curve.toCurve()), spec);
+ PrivateKey privKey;
+ PublicKey pubKey;
+ try {
+ privKey = kf.generatePrivate(privKeySpec);
+ pubKey = kf.generatePublic(pubKeySpec);
+ } catch (InvalidKeySpecException ex) {
+ return null;
+ }
+ return new Key[]{privKey, pubKey};
+ };
+ TestCallback<CommandTestable> kaCallback = new TestCallback<CommandTestable>() {
+ @Override
+ public Result apply(CommandTestable testable) {
+ Response.ECDH ecdhData = (Response.ECDH) testable.getResponse();
+ if (!ecdhData.successful())
+ return new Result(Value.FAILURE, "ECDH was unsuccessful.");
+ if (!ecdhData.hasSecret()) {
+ return new Result(Value.FAILURE, "ECDH response did not contain the derived secret.");
+ }
+ byte[] secret = ecdhData.getSecret();
+ Response.Export localData = (Response.Export) exportLocal.getResponse();
+ Response.Export remoteData = (Response.Export) exportRemote.getResponse();
+ Key[] keys = getKeys.apply(localData, remoteData);
+ if (keys == null) {
+ return new Result(Value.SUCCESS, "Result could not be verified. keyData unavailable.");
+ }
+ PrivateKey privKey = (PrivateKey) keys[0];
+ PublicKey pubKey = (PublicKey) keys[1];
+ try {
+ ka.init(privKey);
+ ka.doPhase(pubKey, true);
+ byte[] derived = ka.generateSecret();
+ int fieldSize = (curve.getBits() + 7) / 8;
+ if (derived.length < fieldSize) {
+ byte[] padded = new byte[fieldSize];
+ System.arraycopy(derived, 0, padded, fieldSize - derived.length, derived.length);
+ derived = padded;
+ }
+ if (ecdhData.getType() == EC_Consts.KeyAgreement_ALG_EC_SVDP_DH || ecdhData.getType() == EC_Consts.KeyAgreement_ALG_EC_SVDP_DHC) {
+ derived = md.digest(derived);
+ }
+ if (secret.length != derived.length) {
+ if (secret.length < derived.length) {
+ return new Result(Value.FAILURE, String.format("Derived secret was shorter than expected: %d vs %d (expected).", secret.length, derived.length));
+ } else {
+ return new Result(Value.FAILURE, String.format("Derived secret was longer than expected: %d vs %d (expected).", secret.length, derived.length));
+ }
+ }
+ int diff = ByteUtil.diffBytes(derived, 0, secret, 0, secret.length);
+ if (diff == secret.length) {
+ return new Result(Value.SUCCESS, "Derived secret matched expected value.");
+ } else {
+ return new Result(Value.FAILURE, "Derived secret does not match expected value, first difference was at byte " + diff + ".");
+ }
+ } catch (InvalidKeyException ex) {
+ return new Result(Value.SUCCESS, "Result could not be verified. " + ex.getMessage());
+ }
+ }
+ };
+ Test ecdhTest = CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), kaCallback);
+ Test ecdhRawTest = CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH_PLAIN), kaCallback);
+ byte[] data = new byte[32];
+ TestCallback<CommandTestable> sigCallback = new TestCallback<CommandTestable>() {
+ @Override
+ public Result apply(CommandTestable testable) {
+ Response.ECDSA ecdsaData = (Response.ECDSA) testable.getResponse();
+ if (!ecdsaData.successful())
+ return new Result(Value.FAILURE, "ECDSA was unsuccessful.");
+ if (!ecdsaData.hasSignature()) {
+ return new Result(Value.FAILURE, "ECDSA response did not contain the signature.");
+ }
+ byte[] signature = ecdsaData.getSignature();
+ Response.Export localData = (Response.Export) exportLocal.getResponse();
+ Response.Export remoteData = (Response.Export) exportRemote.getResponse();
+ Key[] keys = getKeys.apply(localData, remoteData);
+ if (keys == null) {
+ return new Result(Value.SUCCESS, "Result could not be verified. keyData unavailable.");
+ }
+ PublicKey pubKey = (PublicKey) keys[1];
+ try {
+ sig.initVerify(pubKey);
+ sig.update(data);
+ if (sig.verify(signature)) {
+ return new Result(Value.SUCCESS, "Signature verified.");
+ } else {
+ return new Result(Value.FAILURE, "Signature failed to verify.");
+ }
+ } catch (InvalidKeyException | SignatureException ex) {
+ return new Result(Value.SUCCESS, "Result could not be verified. " + ex.getMessage());
+ }
+ }
+ };
+ Test ecdsaTest = CommandTest.function(new Command.ECDSA_sign(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.Signature_ALG_ECDSA_SHA, CardConsts.EXPORT_TRUE, data), sigCallback);
+ testVector.add(CompoundTest.all(ExpectedValue.SUCCESS, "Test.", ecdhTest, ecdhRawTest, ecdsaTest));
+ if (cfg.cleanup) {
+ testVector.add(CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY));
+ }
+ doTest(CompoundTest.greedyAll(ExpectedValue.SUCCESS, "Validation test on " + curve.getId() + ".", testVector.toArray(new Test[0])));
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardTwistSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTwistSuite.java
new file mode 100644
index 0000000..5c35be7
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTwistSuite.java
@@ -0,0 +1,67 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Key;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardTwistSuite extends CardTestSuite {
+ public CardTwistSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "twist", null, "The twist test suite tests whether the card correctly rejects points on the quadratic twist of the curve during ECDH.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "twist");
+ Map<EC_Curve, List<EC_Key.Public>> curveList = EC_Store.mapKeyToCurve(pubkeys.values());
+ for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curveList.entrySet()) {
+ EC_Curve curve = e.getKey();
+ List<EC_Key.Public> keys = e.getValue();
+
+ Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS));
+ if (!allocate.ok()) {
+ doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for " + curve.getId() + ".", allocate));
+ continue;
+ }
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS);
+ Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS);
+
+ Test prepare = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Prepare and generate keypair on " + curve.getId() + ".", allocate, set, generate);
+
+ List<Test> ecdhTests = new LinkedList<>();
+ for (EC_Key.Public pub : keys) {
+ Test setPub = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE);
+ Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE, "Card correctly rejected point on twist.", "Card incorrectly accepted point on twist.");
+ Test objectEcdh = CompoundTest.any(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with twist pubkey.", setPub, ecdh);
+ Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten());
+ Test rawEcdh = CommandTest.expect(ecdhCommand, Result.ExpectedValue.FAILURE, "Card correctly rejected point on twist.", "Card incorrectly accepted point on twist.");
+ ecdhTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, pub.getId() + " twist key test.", objectEcdh, rawEcdh));
+ }
+ Test ecdh = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Perform ECDH with public points on twist.", ecdhTests.toArray(new Test[0]));
+
+ Test tests = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Do tests.", ecdh);
+ if (cfg.cleanup) {
+ Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY);
+ doTest(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Twist test of " + curve.getId() + ".", prepare, tests, cleanup));
+ } else {
+ doTest(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Twist test of " + curve.getId() + ".", prepare, tests));
+ }
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardWrongSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardWrongSuite.java
new file mode 100644
index 0000000..c69396b
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardWrongSuite.java
@@ -0,0 +1,232 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.ec.EC_Consts;
+import cz.crcs.ectester.common.ec.EC_Curve;
+import cz.crcs.ectester.common.ec.EC_Params;
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.common.util.CardUtil;
+import cz.crcs.ectester.common.util.ECUtil;
+import cz.crcs.ectester.data.EC_Store;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.ECTesterReader;
+import cz.crcs.ectester.reader.command.Command;
+import javacard.security.KeyPair;
+
+import java.math.BigInteger;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import static cz.crcs.ectester.common.test.Result.ExpectedValue;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardWrongSuite extends CardTestSuite {
+
+ public CardWrongSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) {
+ super(writer, cfg, cardManager, "wrong", new String[]{"preset", "random"}, "The wrong curve suite tests whether the card rejects domain parameters which are not curves.");
+ }
+
+ @Override
+ protected void runTests() throws Exception {
+ /* Just do the default run on the wrong curves.
+ * These should generally fail, the curves aren't curves.
+ */
+ Map<String, EC_Curve> curves = EC_Store.getInstance().getObjects(EC_Curve.class, "wrong");
+ for (Map.Entry<String, EC_Curve> e : curves.entrySet()) {
+ EC_Curve curve = e.getValue();
+ List<Test> tests = new LinkedList<>();
+ Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS));
+ if (!key.ok()) {
+ doTest(CompoundTest.all(ExpectedValue.FAILURE, "No support for " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()), key));
+ continue;
+ }
+ tests.add(key);
+ Test set = runTest(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.FAILURE));
+ Test generate = runTest(setupKeypairs(curve, ExpectedValue.SUCCESS, CardConsts.KEYPAIR_BOTH));
+ Test setup = runTest(CompoundTest.any(ExpectedValue.SUCCESS, "Set wrong curve and generate keypairs.", set, generate));
+ tests.add(setup);
+
+ for (byte kaType : EC_Consts.KA_TYPES) {
+ Test allocate = runTest(CommandTest.expect(new Command.AllocateKeyAgreement(this.card, kaType), ExpectedValue.SUCCESS));
+ if (allocate.ok()) {
+ Test ka = runTest(CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType), ExpectedValue.FAILURE));
+ Test kaTest = runTest(CompoundTest.all(ExpectedValue.SUCCESS, "Allocate and perform KA.", allocate, ka));
+ tests.add(kaTest);
+ }
+ }
+ doTest(CompoundTest.function((tsts) -> {
+ for (int i = 0; i < tsts.length; ++i) {
+ if (i != 1 && !tsts[i].ok()) {
+ return new Result(Result.Value.FAILURE, "Some tests did not have the expected result.");
+ }
+ }
+ return new Result(Result.Value.SUCCESS, "All tests had the expected result.");
+ }, "Wrong curve test of " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()), tests.toArray(new Test[0])));
+ }
+ /*
+ * Do some interesting tests with corrupting the custom curves.
+ * For prime field:
+ * - p = 0
+ * - p = 1
+ * - p is a square of a prime
+ * - p is a composite q * s with q, s primes
+ * - TODO: p divides discriminant
+ */
+ Random r = new Random();
+ for (short keyLength : EC_Consts.FP_SIZES) {
+ byte curve = EC_Consts.getCurve(keyLength, KeyPair.ALG_EC_FP);
+ Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, KeyPair.ALG_EC_FP), ExpectedValue.SUCCESS));
+ if (!key.ok()) {
+ doTest(CompoundTest.all(ExpectedValue.FAILURE, "No support for " + keyLength + "b ALG_EC_FP.", key));
+ continue;
+ }
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, curve, EC_Consts.PARAMETERS_DOMAIN_FP, null), ExpectedValue.SUCCESS);
+ Test setup = CompoundTest.all(ExpectedValue.SUCCESS, "KeyPair setup.", key, set);
+
+ Test prime0 = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_FP, EC_Consts.TRANSFORMATION_ZERO), "Set p = 0.", "ECDH with p = 0.");
+ Test prime1 = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_FP, EC_Consts.TRANSFORMATION_ONE), "Set p = 1.", "ECDH with p = 1.");
+
+ short keyHalf = (short) (keyLength / 2);
+ BigInteger prime = new BigInteger(keyHalf, 50, r);
+ BigInteger primePow = prime.pow(2);
+ byte[] primePowBytes = ECUtil.toByteArray(primePow, keyLength);
+ EC_Params primePowData = new EC_Params(EC_Consts.PARAMETER_FP, new byte[][]{primePowBytes});
+ Test primePower = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, primePowData.getParams(), primePowData.flatten()), "Set p = square of a prime.", "ECDH with p = q^2.");
+
+ BigInteger q = new BigInteger(keyHalf, r);
+ BigInteger s = new BigInteger(keyHalf, r);
+ BigInteger compositeValue = q.multiply(s);
+ byte[] compositeBytes = ECUtil.toByteArray(compositeValue, keyLength);
+ EC_Params compositeData = new EC_Params(EC_Consts.PARAMETER_FP, new byte[][]{compositeBytes});
+ Test composite = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, compositeData.getParams(), compositeData.flatten()), "Set p = product of two primes.", "ECDH with p = q * s.");
+
+ Test wrongPrime = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted prime parameter.", prime0, prime1, primePower, composite);
+
+ Test resetSetup = CompoundTest.all(ExpectedValue.SUCCESS, "Reset keypair.", set.clone());
+
+ Test randomG = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_G, (short) (EC_Consts.TRANSFORMATION_FULLRANDOM | EC_Consts.TRANSFORMATION_04_MASK)), "Set G = random non-point/point-like.", "ECDH with non-point G.");
+ Test fullRandomG = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_G, EC_Consts.TRANSFORMATION_FULLRANDOM), "Set G = random data.", "ECDH with G = random data.");
+ Test zeroG = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_G, EC_Consts.TRANSFORMATION_INFINITY), "Set G = inifnity.", "ECDH with G = infinity.");
+ Test wrongG = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted G parameter.", randomG, fullRandomG, zeroG);
+
+ byte[] originalR = new byte[((keyLength + 7) / 8) + 1];
+ short origRlen = EC_Consts.getCurveParameter(curve, EC_Consts.PARAMETER_R, originalR, (short) 0);
+ if (origRlen != originalR.length) {
+ byte[] copyR = new byte[origRlen];
+ System.arraycopy(originalR, 0, copyR, 0, origRlen);
+ originalR = copyR;
+ }
+ BigInteger originalBigR = new BigInteger(1, originalR);
+
+ Test zeroR = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, EC_Consts.PARAMETER_R, EC_Consts.TRANSFORMATION_ZERO), "Set R = 0.", "ECDH with R = 0.");
+ Test oneR = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, EC_Consts.PARAMETER_R, EC_Consts.TRANSFORMATION_ONE), "Set R = 1.", "ECDH with R = 1.");
+
+ BigInteger prevPrimeR;
+ do {
+ prevPrimeR = BigInteger.probablePrime(originalBigR.bitLength() - 1, r);
+ } while (prevPrimeR.compareTo(originalBigR) >= 0);
+ byte[] prevRBytes = ECUtil.toByteArray(prevPrimeR, keyLength);
+ EC_Params prevRData = new EC_Params(EC_Consts.PARAMETER_R, new byte[][]{prevRBytes});
+ Test prevprimeWrongR = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, prevRData.getParams(), prevRData.flatten()), "Set R = some prime (but [r]G != infinity) smaller than original R.", "ECDH with wrong R, prevprime.");
+
+ BigInteger nextPrimeR = originalBigR.nextProbablePrime();
+ byte[] nextRBytes = ECUtil.toByteArray(nextPrimeR, keyLength);
+ EC_Params nextRData = new EC_Params(EC_Consts.PARAMETER_R, new byte[][]{nextRBytes});
+ Test nextprimeWrongR = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, nextRData.getParams(), nextRData.flatten()), "Set R = some prime (but [r]G != infinity) larger than original R.", "ECDH with wrong R, nextprime.");
+
+ byte[] nonprimeRBytes = nextRBytes.clone();
+ nonprimeRBytes[nonprimeRBytes.length - 1] ^= 1;
+ EC_Params nonprimeWrongRData = new EC_Params(EC_Consts.PARAMETER_R, new byte[][]{nonprimeRBytes});
+ Test nonprimeWrongR = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, nonprimeWrongRData.getParams(), nonprimeWrongRData.flatten()), "Set R = some composite (but [r]G != infinity).", "ECDH with wrong R, composite.");
+
+ Test wrongR = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted R parameter.", zeroR, oneR, prevprimeWrongR, nextprimeWrongR, nonprimeWrongR);
+
+ byte[] kRaw = new byte[]{(byte) 0xff};
+ EC_Params kData = new EC_Params(EC_Consts.PARAMETER_K, new byte[][]{kRaw});
+ Test bigK = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, kData.getParams(), kData.flatten()), "", "");
+
+ byte[] kZero = new byte[]{(byte) 0};
+ EC_Params kZeroData = new EC_Params(EC_Consts.PARAMETER_K, new byte[][]{kZero});
+ Test zeroK = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, kZeroData.getParams(), kZeroData.flatten()), "", "");
+
+ Test wrongK = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted K parameter.", bigK, zeroK);
+
+ doTest(CompoundTest.all(ExpectedValue.SUCCESS, "Tests of " + keyLength + "b " + CardUtil.getKeyTypeString(KeyPair.ALG_EC_FP), setup, wrongPrime, resetSetup, wrongG, resetSetup.clone(), wrongR, resetSetup.clone(), wrongK, resetSetup.clone()));
+ }
+
+ /*
+ * For binary field:
+ * - e1, e2 or e3 is larger than m.
+ * - e1 = e2 = e3 = 0
+ */
+ for (short keyLength : EC_Consts.F2M_SIZES) {
+ byte curve = EC_Consts.getCurve(keyLength, KeyPair.ALG_EC_F2M);
+ Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, KeyPair.ALG_EC_F2M), ExpectedValue.SUCCESS));
+ if (!key.ok()) {
+ doTest(CompoundTest.all(ExpectedValue.FAILURE, "No support for " + keyLength + "b ALG_EC_F2M.", key));
+ continue;
+ }
+ Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, curve, EC_Consts.PARAMETERS_DOMAIN_F2M, null), ExpectedValue.SUCCESS);
+ Test setup = CompoundTest.all(ExpectedValue.SUCCESS, "KeyPair setup.", key, set);
+
+ Test coeff0 = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_F2M, EC_Consts.TRANSFORMATION_ZERO), "Set e1 = e2 = e3 = 0.", "ECDH with wrong field polynomial: x^" + keyLength);
+
+ short e1 = (short) (2 * keyLength);
+ short e2 = (short) (3 * keyLength);
+ short e3 = (short) (4 * keyLength);
+ byte[][] coeffBytes = new byte[][]{
+ ByteUtil.shortToBytes(keyLength),
+ ByteUtil.shortToBytes(e1),
+ ByteUtil.shortToBytes(e2),
+ ByteUtil.shortToBytes(e3)};
+ EC_Params coeffParams = new EC_Params(EC_Consts.PARAMETER_F2M, coeffBytes);
+ Test coeffLarger = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, coeffParams.getParams(), coeffParams.flatten()), "Set e1=" + e1 + ", e2=" + e2 + ", e3=" + e3, "ECDH with wrong field poly, powers larger than " + keyLength);
+
+ Test wrong = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted field polynomial parameter.", coeff0, coeffLarger);
+ doTest(CompoundTest.all(ExpectedValue.SUCCESS, "Tests of " + keyLength + "b " + CardUtil.getKeyTypeString(KeyPair.ALG_EC_F2M), setup, wrong));
+ }
+
+ /*
+ * TODO: tests for both Fp and F2m:
+ * - generator not on curve,
+ * - generator not on proper subgroup of curve(as specified by order/cofactor),
+ * - wrong order,
+ * - wrong cofactor.
+ */
+ }
+
+ private Test ecdhTest(Command setupCmd, String prepareDesc, String fullDesc) {
+ Test setup = CommandTest.expect(setupCmd, ExpectedValue.FAILURE);
+ Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), ExpectedValue.FAILURE);
+ Test preparePhase = CompoundTest.any(ExpectedValue.SUCCESS, prepareDesc, setup, generate);
+ Test allocateECDH = CommandTest.expect(new Command.AllocateKeyAgreement(this.card, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), ExpectedValue.SUCCESS);
+ Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), ExpectedValue.FAILURE);
+
+
+ return CompoundTest.function((tests) -> {
+ if (preparePhase.ok() | !allocateECDH.ok() | ecdh.ok()) {
+ return new Result(Result.Value.SUCCESS, "All tests had the expected result.");
+ } else {
+ return new Result(Result.Value.FAILURE, "Some tests did not have the expected result.");
+ }
+ }, (tests) -> {
+ preparePhase.run();
+ if (preparePhase.ok()) {
+ return;
+ }
+ allocateECDH.run();
+ if (!allocateECDH.ok()) {
+ return;
+ }
+ ecdh.run();
+ }, fullDesc, preparePhase, allocateECDH, ecdh);
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTest.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTest.java
new file mode 100644
index 0000000..b05d3e4
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTest.java
@@ -0,0 +1,85 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.SimpleTest;
+import cz.crcs.ectester.common.test.TestCallback;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+
+import java.util.Arrays;
+
+/**
+ * A simple test that runs one Command to get and evaluate one Response
+ * to get a Result and compare it with the expected one.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CommandTest extends SimpleTest<CommandTestable> {
+ private CommandTest(CommandTestable command, TestCallback<CommandTestable> callback) {
+ super(command, callback);
+ }
+
+ public static CommandTest function(CommandTestable command, TestCallback<CommandTestable> callback) {
+ return new CommandTest(command, callback);
+ }
+
+ public static CommandTest function(Command command, TestCallback<CommandTestable> callback) {
+ return function(new CommandTestable(command), callback);
+ }
+
+ public static CommandTest expect(CommandTestable command, Result.ExpectedValue expected, String ok, String nok) {
+ return new CommandTest(command, new TestCallback<CommandTestable>() {
+ @Override
+ public Result apply(CommandTestable commandTestable) {
+ Result.Value resultValue = Result.Value.fromExpected(expected, commandTestable.ok(), commandTestable.error());
+ return new Result(resultValue, commandTestable.error() ? commandTestable.errorCause() : (resultValue.ok() ? ok : nok));
+ }
+ });
+ }
+
+ public static CommandTest expect(Command command, Result.ExpectedValue expectedValue, String ok, String nok) {
+ return expect(new CommandTestable(command), expectedValue, ok, nok);
+ }
+
+ public static CommandTest expect(CommandTestable command, Result.ExpectedValue expected) {
+ return expect(command, expected, null, null);
+ }
+
+ public static CommandTest expect(Command command, Result.ExpectedValue expectedValue) {
+ return expect(command, expectedValue, null, null);
+ }
+
+ public static CommandTest expectSW(CommandTestable command, short... expectedSWS) {
+ return new CommandTest(command, new TestCallback<CommandTestable>() {
+ @Override
+ public Result apply(CommandTestable commandTestable) {
+ if (Arrays.equals(commandTestable.getResponse().getSWs(), expectedSWS)) {
+ return new Result(Result.Value.SUCCESS);
+ } else {
+ return new Result(Result.Value.FAILURE);
+ }
+ }
+ });
+ }
+
+ public static CommandTest expectSW(Command command, short... expectedSWS) {
+ return expectSW(new CommandTestable(command), expectedSWS);
+ }
+
+ public Command getCommand() {
+ return testable.getCommand();
+ }
+
+ public Response getResponse() {
+ return testable.getResponse();
+ }
+
+ @Override
+ public String getDescription() {
+ if (hasRun) {
+ return testable.getResponse().getDescription();
+ } else {
+ return testable.getCommand().getDescription();
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTestable.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTestable.java
new file mode 100644
index 0000000..f670534
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTestable.java
@@ -0,0 +1,44 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.test.BaseTestable;
+import cz.crcs.ectester.common.test.TestException;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+
+import javax.smartcardio.CardException;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CommandTestable extends BaseTestable {
+ private Command command;
+ private Response response;
+
+ public CommandTestable(Command command) {
+ this.command = command;
+ }
+
+ public Command getCommand() {
+ return command;
+ }
+
+ public Response getResponse() {
+ return response;
+ }
+
+ @Override
+ public void run() {
+ try {
+ response = command.send();
+ } catch (CardException e) {
+ throw new TestException(e);
+ }
+
+ hasRun = true;
+ if (response.error()) {
+ error = true;
+ } else if (response.successful()) {
+ ok = true;
+ }
+ }
+}
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/PerformanceTest.java b/reader/src/main/java/cz/crcs/ectester/reader/test/PerformanceTest.java
new file mode 100644
index 0000000..a725dc2
--- /dev/null
+++ b/reader/src/main/java/cz/crcs/ectester/reader/test/PerformanceTest.java
@@ -0,0 +1,141 @@
+package cz.crcs.ectester.reader.test;
+
+import cz.crcs.ectester.common.test.Result;
+import cz.crcs.ectester.common.test.SimpleTest;
+import cz.crcs.ectester.common.test.TestCallback;
+import cz.crcs.ectester.common.test.TestException;
+import cz.crcs.ectester.common.util.CardConsts;
+import cz.crcs.ectester.reader.CardMngr;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+
+import javax.smartcardio.CardException;
+import java.util.Arrays;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class PerformanceTest extends SimpleTest<CommandTestable> {
+ private CardMngr cardManager;
+ private long[] times;
+ private long[] reducedTimes;
+ private Response[] responses;
+ private long mean;
+ private long median;
+ private long mode;
+ private int count;
+ private String desc;
+
+ private PerformanceTest(CardMngr cardManager, CommandTestable testable, int count, String desc) {
+ super(testable, new TestCallback<CommandTestable>() {
+ @Override
+ public Result apply(CommandTestable testable) {
+ return new Result(Result.Value.SUCCESS);
+ }
+ });
+ this.cardManager = cardManager;
+ this.count = count;
+ this.desc = desc;
+ }
+
+ public static PerformanceTest repeat(CardMngr cardManager, Command cmd, int count) {
+ return new PerformanceTest(cardManager, new CommandTestable(cmd), count, null);
+ }
+
+ public static PerformanceTest repeat(CardMngr cardManager, String desc, Command cmd, int count) {
+ return new PerformanceTest(cardManager, new CommandTestable(cmd), count, desc);
+ }
+
+ @Override
+ public String getDescription() {
+ String rest = String.format("Mean = %d ns, Median = %d ns, Mode = %d ns", mean, median, mode);
+ return (desc == null ? rest : desc + " (" + rest + ")");
+ }
+
+ @Override
+ protected void runSelf() {
+ long baseTime;
+ try {
+ new Command.SetDryRunMode(cardManager, CardConsts.MODE_DRY_RUN).send();
+ testable.run();
+ baseTime = testable.getResponse().getDuration();
+ testable.reset();
+ testable.run();
+ baseTime += testable.getResponse().getDuration();
+ testable.reset();
+ baseTime /= 2;
+ new Command.SetDryRunMode(cardManager, CardConsts.MODE_NORMAL).send();
+ } catch (CardException ce) {
+ throw new TestException(ce);
+ }
+
+ times = new long[count];
+ reducedTimes = new long[count];
+ responses = new Response[count];
+ for (int i = 0; i < count; ++i) {
+ testable.run();
+ responses[i] = testable.getResponse();
+ times[i] = responses[i].getDuration();
+ reducedTimes[i] = times[i] - baseTime;
+ testable.reset();
+ }
+
+ mean = Arrays.stream(reducedTimes).sum() / count;
+
+ long[] sorted = reducedTimes.clone();
+ Arrays.sort(sorted);
+ if (count % 2 == 0) {
+ median = (sorted[(count / 2) - 1] + sorted[count / 2]) / 2;
+ } else {
+ median = sorted[count / 2];
+ }
+
+ long max_occurences = 0;
+ int i = 0;
+ while (i < count) {
+ long current_value = sorted[i];
+ long current_occurences = 0;
+ while (i < count && sorted[i] == current_value) {
+ i++;
+ current_occurences++;
+ }
+ if (current_occurences > max_occurences) {
+ max_occurences = current_occurences;
+ mode = current_value;
+ }
+ }
+ result = callback.apply(testable);
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public Command getCommand() {
+ return testable.getCommand();
+ }
+
+ public Response[] getResponses() {
+ return responses;
+ }
+
+ public long[] getTimes() {
+ return times;
+ }
+
+ public long[] getReducedTimes() {
+ return reducedTimes;
+ }
+
+ public long getMean() {
+ return mean;
+ }
+
+ public long getMedian() {
+ return median;
+ }
+
+ public long getMode() {
+ return mode;
+ }
+}