diff options
Diffstat (limited to 'src/cz/crcs/ectester/reader')
38 files changed, 1190 insertions, 2470 deletions
diff --git a/src/cz/crcs/ectester/reader/CardMngr.java b/src/cz/crcs/ectester/reader/CardMngr.java index e11bcb3..1e42c52 100644 --- a/src/cz/crcs/ectester/reader/CardMngr.java +++ b/src/cz/crcs/ectester/reader/CardMngr.java @@ -2,11 +2,12 @@ package cz.crcs.ectester.reader; import com.licel.jcardsim.io.CAD; import com.licel.jcardsim.io.JavaxSmartCardInterface; +import cz.crcs.ectester.common.util.ByteUtil; import javacard.framework.AID; +import javacard.framework.ISO7816; import javax.smartcardio.*; -import java.util.List; -import java.util.Scanner; +import java.util.*; /** * @author Petr Svenda petr@svenda.com @@ -79,7 +80,7 @@ public class CardMngr { //reset the card if (verbose) - System.out.println(Util.bytesToHex(card.getATR().getBytes())); + System.out.println(ByteUtil.bytesToHex(card.getATR().getBytes())); cardFound = true; } @@ -108,7 +109,7 @@ public class CardMngr { try { card = terminal.connect("*"); ATR atr = card.getATR(); - System.out.println(terminalIndex + " : " + terminal.getName() + " - " + Util.bytesToHex(atr.getBytes())); + System.out.println(terminalIndex + " : " + terminal.getName() + " - " + ByteUtil.bytesToHex(atr.getBytes())); terminalIndex++; } catch (CardException ex) { ex.printStackTrace(System.out); @@ -164,50 +165,138 @@ public class CardMngr { } } - public byte[] getCPLCData() throws Exception { - byte[] data; + // 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}; - // TODO: Modify to obtain CPLC data - byte apdu[] = new byte[HEADER_LENGTH]; - apdu[OFFSET_CLA] = (byte) 0x00; - apdu[OFFSET_INS] = (byte) 0x00; - apdu[OFFSET_P1] = (byte) 0x00; - apdu[OFFSET_P2] = (byte) 0x00; - apdu[OFFSET_LC] = (byte) 0x00; + 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; + } - ResponseAPDU resp = send(apdu); - if (resp.getSW() != 0x9000) { // 0x9000 is "OK" - System.err.println("Fail to obtain card's response data"); - data = null; - } else { - byte temp[] = resp.getBytes(); - data = new byte[temp.length - 2]; - System.arraycopy(temp, 0, data, 0, temp.length - 2); - // Last two bytes are status word (also obtainable by resp.getSW()) - // Take a look at ISO7816_status_words.txt for common codes + 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 } - return data; - } + private Map<Field, byte[]> values = new TreeMap<>(); - public void probeCardCommands() throws Exception { - // TODO: modify to probe for instruction - for (int i = 0; i <= 0; i++) { - byte apdu[] = new byte[HEADER_LENGTH]; - apdu[OFFSET_CLA] = (byte) 0x00; - apdu[OFFSET_INS] = (byte) 0x00; - apdu[OFFSET_P1] = (byte) 0x00; - apdu[OFFSET_P2] = (byte) 0x00; - apdu[OFFSET_LC] = (byte) 0x00; + 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; + } - ResponseAPDU resp = send(apdu); + public Map<Field, byte[]> values() { + return values; + } + } - if (verbose) - System.out.println("Response: " + Integer.toHexString(resp.getSW())); + public CPLC getCPLC() throws CardException { + byte[] data = fetchCPLC(); + return new CPLC(data); + } - if (resp.getSW() != 0x6D00) { // Note: 0x6D00 is SW_INS_NOT_SUPPORTED - // something? - } + 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 ATR getATR() { + if (simulate) { + return new ATR(simulator.getATR()); + } else { + return card.getATR(); } } @@ -226,7 +315,7 @@ public class CardMngr { System.out.println(">>>>"); System.out.println(apdu); - System.out.println(Util.bytesToHex(apdu.getBytes())); + System.out.println(ByteUtil.bytesToHex(apdu.getBytes())); } long elapsed = -System.nanoTime(); @@ -237,7 +326,7 @@ public class CardMngr { if (verbose) { System.out.println(responseAPDU); - System.out.println(Util.bytesToHex(responseAPDU.getBytes())); + System.out.println(ByteUtil.bytesToHex(responseAPDU.getBytes())); } if (responseAPDU.getSW1() == (byte) 0x61) { @@ -247,7 +336,7 @@ public class CardMngr { responseAPDU = channel.transmit(apduToSend); if (verbose) - System.out.println(Util.bytesToHex(responseAPDU.getBytes())); + System.out.println(ByteUtil.bytesToHex(responseAPDU.getBytes())); } if (verbose) { @@ -276,7 +365,7 @@ public class CardMngr { if (verbose) { System.out.println(">>>>"); System.out.println(apdu); - System.out.println(Util.bytesToHex(apdu.getBytes())); + System.out.println(ByteUtil.bytesToHex(apdu.getBytes())); } ResponseAPDU response = simulator.transmitCommand(apdu); @@ -284,7 +373,7 @@ public class CardMngr { if (verbose) { System.out.println(response); - System.out.println(Util.bytesToHex(responseBytes)); + System.out.println(ByteUtil.bytesToHex(responseBytes)); System.out.println("<<<<"); } diff --git a/src/cz/crcs/ectester/reader/ECTester.java b/src/cz/crcs/ectester/reader/ECTesterReader.java index 550e070..5e3a3fe 100644 --- a/src/cz/crcs/ectester/reader/ECTester.java +++ b/src/cz/crcs/ectester/reader/ECTesterReader.java @@ -23,12 +23,19 @@ package cz.crcs.ectester.reader; import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.common.cli.CLITools; +import cz.crcs.ectester.common.ec.EC_Params; +import cz.crcs.ectester.common.output.OutputLogger; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.TestException; +import cz.crcs.ectester.common.util.ByteUtil; +import cz.crcs.ectester.common.util.CardUtil; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.EC_Category; -import cz.crcs.ectester.reader.ec.EC_Data; -import cz.crcs.ectester.reader.ec.EC_Params; -import cz.crcs.ectester.reader.output.*; +import cz.crcs.ectester.reader.output.ResponseWriter; +import cz.crcs.ectester.reader.output.TextTestWriter; +import cz.crcs.ectester.reader.output.XMLTestWriter; +import cz.crcs.ectester.reader.output.YAMLTestWriter; import cz.crcs.ectester.reader.response.Response; import cz.crcs.ectester.reader.test.*; import javacard.security.KeyPair; @@ -38,9 +45,13 @@ import javax.smartcardio.CardException; import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.nio.file.Files; -import java.util.*; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Scanner; import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH; +import static cz.crcs.ectester.applet.ECTesterApplet.Signature_ALG_ECDSA_SHA; /** * Reader part of ECTester, a tool for testing Elliptic curve support on javacards. @@ -49,18 +60,15 @@ import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH * @author Jan Jancar johny@neuromancer.sk * @version v0.1.0 */ -public class ECTester { - +public class ECTesterReader { private CardMngr cardManager; private OutputLogger logger; - private TestWriter testWriter; private ResponseWriter respWriter; - private EC_Store dataStore; private Config cfg; private Options opts = new Options(); private static final String VERSION = "v0.1.0"; - private static final String DESCRIPTION = "ECTester " + VERSION + ", a javacard Elliptic Curve Cryptograhy support tester/utility."; + private static final String DESCRIPTION = "ECTesterReader " + VERSION + ", a javacard Elliptic Curve Cryptography support tester/utility."; private static final String LICENSE = "MIT Licensed\nCopyright (c) 2016-2017 Petr Svenda <petr@svenda.com>"; private static final String CLI_HEADER = "\n" + DESCRIPTION + "\n\n"; private static final String CLI_FOOTER = "\n" + LICENSE; @@ -76,11 +84,10 @@ public class ECTester { //if help, print and quit if (cli.hasOption("help")) { - help(); + CLITools.help("ECTesterReader.jar", CLI_HEADER, opts, CLI_FOOTER, true); return; } else if (cli.hasOption("version")) { - System.out.println(DESCRIPTION); - System.out.println(LICENSE); + CLITools.version(DESCRIPTION, LICENSE); return; } cfg = new Config(); @@ -90,10 +97,9 @@ public class ECTester { return; } - dataStore = new EC_Store(); //if list, print and quit if (cli.hasOption("list-named")) { - list(); + CLITools.listNamed(EC_Store.getInstance(), cli.getOptionValue("list-named")); return; } @@ -116,22 +122,7 @@ public class ECTester { // Setup logger, testWriter and respWriter logger = new OutputLogger(true, cfg.log); - if (cfg.format == null) { - testWriter = new TextTestWriter(logger.getPrintStream()); - } else { - switch (cfg.format) { - case "text": - testWriter = new TextTestWriter(logger.getPrintStream()); - break; - case "xml": - testWriter = new XMLTestWriter(logger.getOutputStream()); - break; - case "yaml": - case "yml": - testWriter = new YAMLTestWriter(logger.getPrintStream()); - break; - } - } + respWriter = new ResponseWriter(logger.getPrintStream()); //do action @@ -187,9 +178,10 @@ public class ECTester { System.err.println("File " + fnfe.getMessage() + " not found."); } catch (ParseException | IOException ex) { System.err.println(ex.getMessage()); - } catch (CardException ex) { + } catch (CardException | TestException ex) { if (logger != null) logger.println(ex.getMessage()); + ex.printStackTrace(); } catch (ParserConfigurationException e) { e.printStackTrace(); } finally { @@ -213,8 +205,7 @@ public class ECTester { * -e / --export * -g / --generate [amount] * -t / --test [test_suite] - * -dh / --ecdh [count] - * -dhc / --ecdhc [count] + * -dh / --ecdh [count]] * -dsa / --ecdsa [count] * -ln / --list-named [obj] * @@ -248,6 +239,7 @@ public class ECTester { * -s / --simulate * -y / --yes * -ka/ --ka-type <type> + * -sig/--sig-type <type> */ OptionGroup actions = new OptionGroup(); actions.setRequired(true); @@ -256,9 +248,8 @@ public class ECTester { actions.addOption(Option.builder("ln").longOpt("list-named").desc("Print the list of supported named curves and keys.").hasArg().argName("what").optionalArg(true).build()); actions.addOption(Option.builder("e").longOpt("export").desc("Export the defaut curve parameters of the card(if any).").build()); actions.addOption(Option.builder("g").longOpt("generate").desc("Generate [amount] of EC keys.").hasArg().argName("amount").optionalArg(true).build()); - actions.addOption(Option.builder("t").longOpt("test").desc("Test ECC support. [test_suite]:\n- default:\n- invalid:\n- wrong:\n- composite:\n- test-vectors:").hasArg().argName("test_suite").optionalArg(true).build()); - actions.addOption(Option.builder("dh").longOpt("ecdh").desc("Do ECDH, [count] times.").hasArg().argName("count").optionalArg(true).build()); - actions.addOption(Option.builder("dhc").longOpt("ecdhc").desc("Do ECDHC, [count] times.").hasArg().argName("count").optionalArg(true).build()); + actions.addOption(Option.builder("t").longOpt("test").desc("Test ECC support. [test_suite]:\n- default:\n- invalid:\n- twist:\n- wrong:\n- composite:\n- test-vectors:").hasArg().argName("test_suite").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()); opts.addOptionGroup(actions); @@ -303,45 +294,13 @@ public class ECTester { opts.addOption(Option.builder("y").longOpt("yes").desc("Accept all warnings and prompts.").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()); CommandLineParser parser = new DefaultParser(); return parser.parse(opts, args); } /** - * Prints help. - */ - private void help() { - HelpFormatter help = new HelpFormatter(); - help.setOptionComparator(null); - help.printHelp("ECTester.jar", CLI_HEADER, opts, CLI_FOOTER, true); - } - - /** - * List categories and named curves. - */ - private void list() { - Map<String, EC_Category> categories = dataStore.getCategories(); - if (cfg.listNamed == null) { - // print all categories, briefly - for (EC_Category cat : categories.values()) { - System.out.println(cat); - } - } else if (categories.containsKey(cfg.listNamed)) { - // print given category - System.out.println(categories.get(cfg.listNamed)); - } else { - // print given object - EC_Data object = dataStore.getObject(EC_Data.class, cfg.listNamed); - if (object != null) { - System.out.println(object); - } else { - System.err.println("Named object " + cfg.listNamed + " not found!"); - } - } - } - - /** * Exports default card/simulation EC domain parameters to output file. * * @throws CardException if APDU transmission fails @@ -386,8 +345,9 @@ public class ECTester { private void generate() throws CardException, IOException { byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; - new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send(); - Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); + Response allocate = new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send(); + respWriter.outputResponse(allocate); + Command curve = Command.prepareCurve(cardManager, EC_Store.getInstance(), cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); FileWriter keysFile = new FileWriter(cfg.output); keysFile.write("index;time;pubW;privS\n"); @@ -417,8 +377,8 @@ public class ECTester { } respWriter.outputResponse(response); - String pub = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false); - String priv = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false); + String pub = ByteUtil.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false); + String priv = ByteUtil.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false); String line = String.format("%d;%d;%s;%s\n", generated, elapsed / 1000000, pub, priv); keysFile.write(line); keysFile.flush(); @@ -436,20 +396,38 @@ public class ECTester { * @throws CardException if APDU transmission fails * @throws IOException if an IO error occurs when writing to key file. */ - private void test() throws IOException, CardException { - TestSuite suite; + private void test() throws IOException, TestException, ParserConfigurationException { + TestWriter writer = null; + if (cfg.format == null) { + writer = new TextTestWriter(logger.getPrintStream()); + } else { + switch (cfg.format) { + case "text": + writer = new TextTestWriter(logger.getPrintStream()); + break; + case "xml": + writer = new XMLTestWriter(logger.getOutputStream()); + break; + case "yaml": + case "yml": + writer = new YAMLTestWriter(logger.getPrintStream()); + break; + } + } + + CardTestSuite suite; switch (cfg.testSuite) { case "default": - suite = new DefaultSuite(dataStore, cfg); + suite = new CardDefaultSuite(writer, cfg, cardManager); break; case "test-vectors": - suite = new TestVectorSuite(dataStore, cfg); + suite = new CardTestVectorSuite(writer, cfg, cardManager); break; default: - // These tests are dangerous, prompt before them. + // 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 tests have caused temporary DoS of some cards."); + System.out.println("Some of these run have caused temporary DoS of some cards."); if (!cfg.yes) { System.out.print("Do you want to proceed? (y/n): "); Scanner in = new Scanner(System.in); @@ -459,17 +437,18 @@ public class ECTester { } in.close(); } - - switch (cfg.testSuite) { case "wrong": - suite = new WrongCurvesSuite(dataStore, cfg); + suite = new CardWrongCurvesSuite(writer, cfg, cardManager); break; case "composite": - suite = new CompositeCurvesSuite(dataStore, cfg); + suite = new CardCompositeCurvesSuite(writer, cfg, cardManager); break; case "invalid": - suite = new InvalidCurvesSuite(dataStore, cfg); + suite = new CardInvalidCurvesSuite(writer, cfg, cardManager); + break; + case "twist": + suite = new CardTwistTestSuite(writer, cfg, cardManager); break; default: System.err.println("Unknown test suite."); @@ -478,9 +457,7 @@ public class ECTester { break; } - TestRunner runner = new TestRunner(suite, testWriter); - suite.setup(cardManager); - runner.run(); + suite.run(); } /** @@ -492,9 +469,9 @@ public class ECTester { private void ecdh() throws IOException, CardException { byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; List<Response> prepare = new LinkedList<>(); - prepare.add(new Command.AllocateKeyAgreement(cardManager, cfg.kaType).send()); // Prepare KeyAgreement or required type + prepare.add(new Command.AllocateKeyAgreement(cardManager, cfg.ECKAType).send()); // Prepare KeyAgreement or required type prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass).send()); - Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass); + Command curve = Command.prepareCurve(cardManager, EC_Store.getInstance(), cfg, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass); if (curve != null) prepare.add(curve.send()); @@ -508,21 +485,26 @@ public class ECTester { List<Command> generate = new LinkedList<>(); generate.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH)); if (cfg.anyPublicKey || cfg.anyPrivateKey || cfg.anyKey) { - generate.add(Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_REMOTE)); + generate.add(Command.prepareKey(cardManager, EC_Store.getInstance(), cfg, ECTesterApplet.KEYPAIR_REMOTE)); } FileWriter out = null; if (cfg.output != null) { out = new FileWriter(cfg.output); - out.write("index;time;secret\n"); + out.write("index;time;pubW;privS;secret\n"); } int retry = 0; int done = 0; - while (done < cfg.ECDHCount) { + while (done < cfg.ECKACount) { List<Response> ecdh = Command.sendAll(generate); - Response.ECDH perform = new Command.ECDH(cardManager, pubkey, privkey, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, cfg.ECDHKA).send(); + Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR).send(); + ecdh.add(export); + byte pubkey_bytes[] = export.getParameter(pubkey, EC_Consts.PARAMETER_W); + byte privkey_bytes[] = export.getParameter(privkey, EC_Consts.PARAMETER_S); + + Response.ECDH perform = new Command.ECDH(cardManager, pubkey, privkey, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, cfg.ECKAType).send(); ecdh.add(perform); for (Response r : ecdh) { respWriter.outputResponse(r); @@ -539,7 +521,7 @@ public class ECTester { } if (out != null) { - out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSecret(), false))); + out.write(String.format("%d;%d;%s;%s;%s\n", done, perform.getDuration() / 1000000, ByteUtil.bytesToHex(pubkey_bytes, false), ByteUtil.bytesToHex(privkey_bytes, false), ByteUtil.bytesToHex(perform.getSecret(), false))); } ++done; @@ -571,15 +553,16 @@ public class ECTester { Command generate; if (cfg.anyKeypart) { - generate = Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL); + generate = Command.prepareKey(cardManager, EC_Store.getInstance(), cfg, ECTesterApplet.KEYPAIR_LOCAL); } else { generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL); } byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; List<Response> prepare = new LinkedList<>(); + prepare.add(new Command.AllocateSignature(cardManager, cfg.ECDSAType).send()); prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send()); - Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); + Command curve = Command.prepareCurve(cardManager, EC_Store.getInstance(), cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); if (curve != null) prepare.add(curve.send()); @@ -599,7 +582,7 @@ public class ECTester { List<Response> ecdsa = new LinkedList<>(); ecdsa.add(generate.send()); - Response.ECDSA perform = new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, data).send(); + Response.ECDSA perform = new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, cfg.ECDSAType, ECTesterApplet.EXPORT_TRUE, data).send(); ecdsa.add(perform); for (Response r : ecdsa) { respWriter.outputResponse(r); @@ -616,7 +599,7 @@ public class ECTester { } if (out != null) { - out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSignature(), false))); + out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, ByteUtil.bytesToHex(perform.getSignature(), false))); } ++done; @@ -629,18 +612,18 @@ public class ECTester { } public static void main(String[] args) { - ECTester app = new ECTester(); + ECTesterReader app = new ECTesterReader(); app.run(args); } public static class Config { //Options - public int bits; + public short bits; public boolean all; public boolean primeField = false; public boolean binaryField = false; - public byte kaType = KeyAgreement_ALG_EC_SVDP_DH; + public String namedCurve; public String curveFile; @@ -674,9 +657,10 @@ public class ECTester { public String listNamed; public String testSuite; public int generateAmount; - public int ECDHCount; - public byte ECDHKA; + public int ECKACount; + public byte ECKAType = KeyAgreement_ALG_EC_SVDP_DH; public int ECDSACount; + public byte ECDSAType = Signature_ALG_ECDSA_SHA; /** * Reads and validates options, also sets defaults. @@ -685,11 +669,11 @@ public class ECTester { * @return whether the options are valid. */ boolean readOptions(CommandLine cli) { - bits = Integer.parseInt(cli.getOptionValue("bit-size", "0")); + bits = Short.parseShort(cli.getOptionValue("bit-size", "0")); all = cli.hasOption("all"); primeField = cli.hasOption("fp"); binaryField = cli.hasOption("f2m"); - kaType = Byte.parseByte(cli.getOptionValue("ka-type", "1")); + namedCurve = cli.getOptionValue("named-curve"); customCurve = cli.hasOption("custom"); @@ -770,7 +754,6 @@ public class ECTester { System.err.println("You have to specify curve bit-size with -b"); return false; } - } else if (cli.hasOption("generate")) { if (primeField == binaryField) { System.err.print("Need to specify field with -fp or -f2m. (not both)"); @@ -801,13 +784,12 @@ public class ECTester { } testSuite = cli.getOptionValue("test", "default").toLowerCase(); - String[] tests = new String[]{"default", "composite", "invalid", "test-vectors", "wrong"}; + String[] tests = new String[]{"default", "composite", "invalid", "test-vectors", "wrong", "twist"}; if (!Arrays.asList(tests).contains(testSuite)) { System.err.println("Unknown test suite " + testSuite + ". Should be one of: " + Arrays.toString(tests)); return false; } - - } else if (cli.hasOption("ecdh") || cli.hasOption("ecdhc")) { + } else if (cli.hasOption("ecdh")) { if (primeField == binaryField) { System.err.print("Need to specify field with -fp or -f2m. (not both)"); return false; @@ -817,18 +799,13 @@ public class ECTester { return false; } - if (cli.hasOption("ecdh")) { - ECDHCount = Integer.parseInt(cli.getOptionValue("ecdh", "1")); - ECDHKA = EC_Consts.KA_ECDH; - } else if (cli.hasOption("ecdhc")) { - ECDHCount = Integer.parseInt(cli.getOptionValue("ecdhc", "1")); - ECDHKA = EC_Consts.KA_ECDHC; - } - if (ECDHCount <= 0) { + ECKACount = Integer.parseInt(cli.getOptionValue("ecdh", "1")); + if (ECKACount <= 0) { System.err.println("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("Need to specify field with -fp or -f2m. (but not both)"); @@ -849,6 +826,8 @@ public class ECTester { System.err.println("ECDSA count cannot be <= 0."); return false; } + + ECDSAType = CardUtil.parseSigType(cli.getOptionValue("sig-type", "17")); } return true; } diff --git a/src/cz/crcs/ectester/reader/Util.java b/src/cz/crcs/ectester/reader/Util.java deleted file mode 100644 index 4e1154b..0000000 --- a/src/cz/crcs/ectester/reader/Util.java +++ /dev/null @@ -1,379 +0,0 @@ -package cz.crcs.ectester.reader; - -import cz.crcs.ectester.applet.ECTesterApplet; -import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_PACE_GM; -import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH; -import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DHC; -import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DHC_PLAIN; -import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH_PLAIN; -import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH_PLAIN_XY; -import cz.crcs.ectester.applet.EC_Consts; -import javacard.framework.ISO7816; -import javacard.security.CryptoException; - -/** - * Utility class, some byte/hex manipulation, convenient byte[] methods. - * - * @author Petr Svenda petr@svenda.com - * @author Jan Jancar johny@neuromancer.sk - */ -public class Util { - - public static short getShort(byte[] array, int offset) { - return (short) (((array[offset] & 0xFF) << 8) | (array[offset + 1] & 0xFF)); - } - - public static void setShort(byte[] array, int offset, short value) { - array[offset + 1] = (byte) (value & 0xFF); - array[offset] = (byte) ((value >> 8) & 0xFF); - } - - public static int diffBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) { - for (int i = 0; i < length; ++i) { - byte a = one[i + oneOffset]; - byte b = other[i + otherOffset]; - if (a != b) { - return i; - } - } - return length; - } - - public static boolean compareBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) { - return diffBytes(one, oneOffset, other, otherOffset, length) == length; - } - - public static boolean allValue(byte[] array, byte value) { - for (byte a : array) { - if (a != value) - return false; - } - return true; - } - - public static byte[] hexToBytes(String hex) { - return hexToBytes(hex, true); - } - - public static byte[] hexToBytes(String hex, boolean bigEndian) { - hex = hex.replace(" ", ""); - int len = hex.length(); - StringBuilder sb = new StringBuilder(); - - if (len % 2 == 1) { - sb.append("0"); - ++len; - } - - if (bigEndian) { - sb.append(hex); - } else { - for (int i = 0; i < len / 2; ++i) { - if (sb.length() >= 2) { - sb.insert(sb.length() - 2, hex.substring(2 * i, 2 * i + 2)); - } else { - sb.append(hex.substring(2 * i, 2 * i + 2)); - } - - } - } - - String data = sb.toString(); - byte[] result = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - result[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4) - + (Character.digit(data.charAt(i + 1), 16))); - } - return result; - } - - public static String byteToHex(byte data) { - return String.format("%02x", data); - } - - public static String bytesToHex(byte[] data) { - return bytesToHex(data, true); - } - - public static String bytesToHex(byte[] data, boolean addSpace) { - return bytesToHex(data, 0, data.length, addSpace); - } - - public static String bytesToHex(byte[] data, int offset, int len) { - return bytesToHex(data, offset, len, true); - } - - public static String bytesToHex(byte[] data, int offset, int len, boolean addSpace) { - StringBuilder buf = new StringBuilder(); - for (int i = offset; i < (offset + len); i++) { - buf.append(byteToHex(data[i])); - if (addSpace && i != (offset + len - 1)) { - buf.append(" "); - } - } - return (buf.toString()); - } - - public static byte[] concatenate(byte[]... arrays) { - int len = 0; - for (byte[] array : arrays) { - if (array == null) - continue; - len += array.length; - } - byte[] out = new byte[len]; - int offset = 0; - for (byte[] array : arrays) { - if (array == null || array.length == 0) - continue; - System.arraycopy(array, 0, out, offset, array.length); - offset += array.length; - } - return out; - } - - public static String getSWSource(short sw) { - switch (sw) { - case ISO7816.SW_NO_ERROR: - case ISO7816.SW_APPLET_SELECT_FAILED: - case ISO7816.SW_BYTES_REMAINING_00: - case ISO7816.SW_CLA_NOT_SUPPORTED: - case ISO7816.SW_COMMAND_NOT_ALLOWED: - case ISO7816.SW_CONDITIONS_NOT_SATISFIED: - case ISO7816.SW_CORRECT_LENGTH_00: - case ISO7816.SW_DATA_INVALID: - case ISO7816.SW_FILE_FULL: - case ISO7816.SW_FILE_INVALID: - case ISO7816.SW_FILE_NOT_FOUND: - case ISO7816.SW_FUNC_NOT_SUPPORTED: - case ISO7816.SW_INCORRECT_P1P2: - case ISO7816.SW_INS_NOT_SUPPORTED: - case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED: - case ISO7816.SW_RECORD_NOT_FOUND: - case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED: - case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED: - case ISO7816.SW_UNKNOWN: - case ISO7816.SW_WARNING_STATE_UNCHANGED: - case ISO7816.SW_WRONG_DATA: - case ISO7816.SW_WRONG_LENGTH: - case ISO7816.SW_WRONG_P1P2: - return "ISO"; - case CryptoException.ILLEGAL_VALUE: - case CryptoException.UNINITIALIZED_KEY: - case CryptoException.NO_SUCH_ALGORITHM: - case CryptoException.INVALID_INIT: - case CryptoException.ILLEGAL_USE: - return "CryptoException"; - case ECTesterApplet.SW_SIG_VERIFY_FAIL: - case ECTesterApplet.SW_DH_DHC_MISMATCH: - case ECTesterApplet.SW_KEYPAIR_NULL: - case ECTesterApplet.SW_KA_NULL: - case ECTesterApplet.SW_SIGNATURE_NULL: - case ECTesterApplet.SW_OBJECT_NULL: - return "ECTesterApplet"; - default: - return "?"; - } - } - - public static String getSW(short sw) { - String str; - switch (sw) { - case ISO7816.SW_APPLET_SELECT_FAILED: - str = "APPLET_SELECT_FAILED"; - break; - case ISO7816.SW_BYTES_REMAINING_00: - str = "BYTES_REMAINING"; - break; - case ISO7816.SW_CLA_NOT_SUPPORTED: - str = "CLA_NOT_SUPPORTED"; - break; - case ISO7816.SW_COMMAND_NOT_ALLOWED: - str = "COMMAND_NOT_ALLOWED"; - break; - case ISO7816.SW_CONDITIONS_NOT_SATISFIED: - str = "CONDITIONS_NOT_SATISFIED"; - break; - case ISO7816.SW_CORRECT_LENGTH_00: - str = "CORRECT_LENGTH"; - break; - case ISO7816.SW_DATA_INVALID: - str = "DATA_INVALID"; - break; - case ISO7816.SW_FILE_FULL: - str = "FILE_FULL"; - break; - case ISO7816.SW_FILE_INVALID: - str = "FILE_INVALID"; - break; - case ISO7816.SW_FILE_NOT_FOUND: - str = "FILE_NOT_FOUND"; - break; - case ISO7816.SW_FUNC_NOT_SUPPORTED: - str = "FUNC_NOT_SUPPORTED"; - break; - case ISO7816.SW_INCORRECT_P1P2: - str = "INCORRECT_P1P2"; - break; - case ISO7816.SW_INS_NOT_SUPPORTED: - str = "INS_NOT_SUPPORTED"; - break; - case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED: - str = "LOGICAL_CHANNEL_NOT_SUPPORTED"; - break; - case ISO7816.SW_RECORD_NOT_FOUND: - str = "RECORD_NOT_FOUND"; - break; - case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED: - str = "SECURE_MESSAGING_NOT_SUPPORTED"; - break; - case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED: - str = "SECURITY_STATUS_NOT_SATISFIED"; - break; - case ISO7816.SW_UNKNOWN: - str = "UNKNOWN"; - break; - case ISO7816.SW_WARNING_STATE_UNCHANGED: - str = "WARNING_STATE_UNCHANGED"; - break; - case ISO7816.SW_WRONG_DATA: - str = "WRONG_DATA"; - break; - case ISO7816.SW_WRONG_LENGTH: - str = "WRONG_LENGTH"; - break; - case ISO7816.SW_WRONG_P1P2: - str = "WRONG_P1P2"; - break; - case CryptoException.ILLEGAL_VALUE: - str = "ILLEGAL_VALUE"; - break; - case CryptoException.UNINITIALIZED_KEY: - str = "UNINITIALIZED_KEY"; - break; - case CryptoException.NO_SUCH_ALGORITHM: - str = "NO_SUCH_ALG"; - break; - case CryptoException.INVALID_INIT: - str = "INVALID_INIT"; - break; - case CryptoException.ILLEGAL_USE: - str = "ILLEGAL_USE"; - break; - case ECTesterApplet.SW_SIG_VERIFY_FAIL: - str = "SIG_VERIFY_FAIL"; - break; - case ECTesterApplet.SW_DH_DHC_MISMATCH: - str = "DH_DHC_MISMATCH"; - break; - case ECTesterApplet.SW_KEYPAIR_NULL: - str = "KEYPAIR_NULL"; - break; - case ECTesterApplet.SW_KA_NULL: - str = "KA_NULL"; - break; - case ECTesterApplet.SW_SIGNATURE_NULL: - str = "SIGNATURE_NULL"; - break; - case ECTesterApplet.SW_OBJECT_NULL: - str = "OBJECT_NULL"; - break; - default: - str = "unknown"; - break; - } - return str; - } - - public static String getSWString(short sw) { - if (sw == ISO7816.SW_NO_ERROR) { - return "OK (0x9000)"; - } else { - String str = getSW(sw); - return String.format("fail (%s, 0x%04x)", str, sw); - } - } - - public static String getCorruption(short corruptionType) { - String corrupt; - switch (corruptionType) { - case EC_Consts.CORRUPTION_NONE: - corrupt = "NONE"; - break; - case EC_Consts.CORRUPTION_FIXED: - corrupt = "FIXED"; - break; - case EC_Consts.CORRUPTION_ONE: - corrupt = "ONE"; - break; - case EC_Consts.CORRUPTION_ZERO: - corrupt = "ZERO"; - break; - case EC_Consts.CORRUPTION_ONEBYTERANDOM: - corrupt = "ONE_BYTE_RANDOM"; - break; - case EC_Consts.CORRUPTION_FULLRANDOM: - corrupt = "FULL_RANDOM"; - break; - case EC_Consts.CORRUPTION_INCREMENT: - corrupt = "INCREMENT"; - break; - case EC_Consts.CORRUPTION_INFINITY: - corrupt = "INFINITY"; - break; - case EC_Consts.CORRUPTION_COMPRESS: - corrupt = "COMPRESSED"; - break; - case EC_Consts.CORRUPTION_MAX: - corrupt = "MAX"; - break; - default: - corrupt = "unknown"; - break; - } - return corrupt; - } - - public static String getKA(byte ka) { - String algo = ""; - if ((ka & EC_Consts.KA_ECDH) != 0 || ka == EC_Consts.KA_ANY) { - algo += "ECDH"; - } - if (ka == EC_Consts.KA_BOTH) { - algo += "+"; - } else if (ka == EC_Consts.KA_ANY) { - algo += "/"; - } - if ((ka & EC_Consts.KA_ECDHC) != 0 || ka == EC_Consts.KA_ANY) { - algo += "ECDHC"; - } - return algo; - } - - public static String getKATypeString(byte kaType) { - String kaTypeString = "unknown"; - switch (kaType) { - case KeyAgreement_ALG_EC_SVDP_DH: - kaTypeString = "ALG_EC_SVDP_DH"; - break; - case KeyAgreement_ALG_EC_SVDP_DH_PLAIN: - kaTypeString = "ALG_EC_SVDP_DH_PLAIN"; - break; - case KeyAgreement_ALG_EC_PACE_GM: - kaTypeString = "ALG_EC_PACE_GM"; - break; - case KeyAgreement_ALG_EC_SVDP_DH_PLAIN_XY: - kaTypeString = "ALG_EC_SVDP_DH_PLAIN_XY"; - break; - case KeyAgreement_ALG_EC_SVDP_DHC: - kaTypeString = "ALG_EC_SVDP_DHC"; - break; - case KeyAgreement_ALG_EC_SVDP_DHC_PLAIN: - kaTypeString = "ALG_EC_SVDP_DHC_PLAIN"; - break; - default: - kaTypeString = "unknown"; - } - return kaTypeString; - } -} diff --git a/src/cz/crcs/ectester/reader/command/Command.java b/src/cz/crcs/ectester/reader/command/Command.java index 3c11456..5a6906c 100644 --- a/src/cz/crcs/ectester/reader/command/Command.java +++ b/src/cz/crcs/ectester/reader/command/Command.java @@ -2,15 +2,15 @@ package cz.crcs.ectester.reader.command; import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.common.util.ByteUtil; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; +import cz.crcs.ectester.reader.ECTesterReader; import cz.crcs.ectester.reader.response.Response; -import cz.crcs.ectester.reader.Util; -import cz.crcs.ectester.reader.ec.EC_Curve; -import cz.crcs.ectester.reader.ec.EC_Key; -import cz.crcs.ectester.reader.ec.EC_Keypair; -import cz.crcs.ectester.reader.ec.EC_Params; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.ec.EC_Keypair; +import cz.crcs.ectester.common.ec.EC_Params; import javacard.security.KeyPair; import javax.smartcardio.CardException; @@ -54,7 +54,7 @@ public abstract class Command { * @return a Command to send in order to prepare the curve on the keypairs. * @throws IOException if curve file cannot be found/opened */ - public static Command prepareCurve(CardMngr cardManager, EC_Store dataStore, ECTester.Config cfg, byte keyPair, short keyLength, byte keyClass) throws IOException { + public static Command prepareCurve(CardMngr cardManager, EC_Store dataStore, 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) @@ -109,7 +109,7 @@ public abstract class Command { * @return a CommandAPDU setting params loaded on the keyPair/s * @throws IOException if any of the key files cannot be found/opened */ - public static Command prepareKey(CardMngr cardManager, EC_Store dataStore, ECTester.Config cfg, byte keyPair) throws IOException { + public static Command prepareKey(CardMngr cardManager, EC_Store dataStore, ECTesterReader.Config cfg, byte keyPair) throws IOException { short params = EC_Consts.PARAMETERS_NONE; byte[] data = null; @@ -174,7 +174,7 @@ public abstract class Command { if (privkey == null) { throw new IOException("Couldn't read the private key file correctly."); } - data = Util.concatenate(data, privkey); + data = ByteUtil.concatenate(data, privkey); } return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, params, data); } @@ -203,7 +203,7 @@ public abstract class Command { this.keyClass = keyClass; byte[] data = new byte[]{0, 0, keyClass}; - Util.setShort(data, 0, keyLength); + ByteUtil.setShort(data, 0, keyLength); this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ALLOCATE, keyPair, 0x00, data); } @@ -214,13 +214,19 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.Allocate(response, elapsed, keyPair, keyLength, keyClass); } + + @Override + public String toString() { + return "Allocate"; + } } - - public static class AllocateKeyAgreement extends Command { + /** + * + */ + public static class AllocateKeyAgreement extends Command { private byte kaType; - /** * Creates the INS_ALLOCATE_KA instruction. * @@ -241,7 +247,45 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.AllocateKeyAgreement(response, elapsed, kaType); } - } + + @Override + public String toString() { + return "AllocateKeyAgreement"; + } + } + + /** + * + */ + 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(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.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, elapsed, sigType); + } + + @Override + public String toString() { + return "AllocateSignature"; + } + } /** * @@ -267,6 +311,11 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.Clear(response, elapsed, keyPair); } + + @Override + public String toString() { + return "Clear"; + } } /** @@ -296,7 +345,7 @@ public abstract class Command { int len = external != null ? 2 + external.length : 2; byte[] data = new byte[len]; - Util.setShort(data, 0, params); + ByteUtil.setShort(data, 0, params); if (external != null) { System.arraycopy(external, 0, data, 2, external.length); } @@ -311,6 +360,11 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.Set(response, elapsed, keyPair, curve, params); } + + @Override + public String toString() { + return "Set"; + } } /** @@ -337,7 +391,7 @@ public abstract class Command { this.corruption = corruption; byte[] data = new byte[3]; - Util.setShort(data, 0, params); + ByteUtil.setShort(data, 0, params); data[2] = corruption; this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_CORRUPT, keyPair, key, data); @@ -350,6 +404,11 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.Corrupt(response, elapsed, keyPair, key, params, corruption); } + + @Override + public String toString() { + return "Corrupt"; + } } /** @@ -378,6 +437,11 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.Generate(response, elapsed, keyPair); } + + @Override + public String toString() { + return "Generate"; + } } /** @@ -403,7 +467,7 @@ public abstract class Command { this.params = params; byte[] data = new byte[2]; - Util.setShort(data, 0, params); + ByteUtil.setShort(data, 0, params); this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_EXPORT, keyPair, key, data); } @@ -415,6 +479,11 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.Export(response, elapsed, keyPair, key, params); } + + @Override + public String toString() { + return "Export"; + } } /** @@ -446,7 +515,7 @@ public abstract class Command { this.type = type; byte[] data = new byte[]{export, 0,0, type}; - Util.setShort(data, 1, corruption); + ByteUtil.setShort(data, 1, corruption); this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ECDH, pubkey, privkey, data); } @@ -458,6 +527,11 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.ECDH(response, elapsed, pubkey, privkey, export, corruption, type); } + + @Override + public String toString() { + return "ECDH"; + } } /** @@ -477,7 +551,7 @@ public abstract class Command { * @param privkey keyPair to use for private key, (KEYPAIR_LOCAL || KEYPAIR_REMOTE) * @param export whether to export ECDH secret * @param corruption whether to invalidate the pubkey before ECDH (EC_Consts.CORRUPTION_* | ...) - * @param type ECDH algorithm type (EC_Consts.KA_* | ...) + * @param type EC KeyAgreement type * @param pubkey pubkey data to do ECDH with. */ public ECDH_direct(CardMngr cardManager, byte privkey, byte export, short corruption, byte type, byte[] pubkey) { @@ -489,7 +563,7 @@ public abstract class Command { this.pubkey = pubkey; byte[] data = new byte[3 + pubkey.length]; - Util.setShort(data, 0, corruption); + ByteUtil.setShort(data, 0, corruption); data[2] = type; System.arraycopy(pubkey, 0, data, 3, pubkey.length); @@ -503,10 +577,16 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.ECDH(response, elapsed, ECTesterApplet.KEYPAIR_REMOTE, privkey, export, corruption, type); } + + @Override + public String toString() { + return "ECDH_direct"; + } } public static class ECDSA extends Command { private byte keyPair; + private byte sigType; private byte export; private byte[] raw; @@ -515,20 +595,23 @@ public abstract class Command { * * @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 export, byte[] raw) { + public ECDSA(CardMngr cardManager, byte keyPair, byte sigType, byte export, byte[] raw) { super(cardManager); this.keyPair = keyPair; + this.sigType = sigType; this.export = export; this.raw = raw; int len = raw != null ? raw.length : 0; - byte[] data = new byte[2 + len]; - Util.setShort(data, 0, (short) len); + byte[] data = new byte[3 + len]; + data[0] = sigType; + ByteUtil.setShort(data, 1, (short) len); if (raw != null) { - System.arraycopy(raw, 0, data, 2, len); + System.arraycopy(raw, 0, data, 3, len); } this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ECDSA, keyPair, export, data); @@ -539,7 +622,12 @@ public abstract class Command { long elapsed = -System.nanoTime(); ResponseAPDU response = cardManager.send(cmd); elapsed += System.nanoTime(); - return new Response.ECDSA(response, elapsed, keyPair, export, raw); + return new Response.ECDSA(response, elapsed, keyPair, sigType, export, raw); + } + + @Override + public String toString() { + return "ECDSA"; } } @@ -564,28 +652,10 @@ public abstract class Command { elapsed += System.nanoTime(); return new Response.Cleanup(response, elapsed); } - } - - /** - * - */ - public static class Support extends Command { - - /** - * @param cardManager cardManager to send APDU through - */ - public Support(CardMngr cardManager) { - super(cardManager); - - this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_SUPPORT, 0, 0); - } @Override - public Response.Support send() throws CardException { - long elapsed = -System.nanoTime(); - ResponseAPDU response = cardManager.send(cmd); - elapsed += System.nanoTime(); - return new Response.Support(response, elapsed); + public String toString() { + return "Cleanup"; } } } diff --git a/src/cz/crcs/ectester/reader/ec/EC_Category.java b/src/cz/crcs/ectester/reader/ec/EC_Category.java deleted file mode 100644 index 41cbad8..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Category.java +++ /dev/null @@ -1,142 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; - -/** - * A category of EC_Data objects, has a name, description and represents a directory in - * the cz.crcs.ectester.data package. - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_Category { - - private String name; - private String directory; - private String desc; - - private Map<String, EC_Data> objects; - - - public EC_Category(String name, String directory) { - this.name = name; - this.directory = directory; - } - - public EC_Category(String name, String directory, String desc) { - this(name, directory); - this.desc = desc; - } - - public EC_Category(String name, String directory, String desc, Map<String, EC_Data> objects) { - this(name, directory, desc); - this.objects = objects; - } - - public String getName() { - return name; - } - - public String getDirectory() { - return directory; - } - - public String getDesc() { - return desc; - } - - public Map<String, EC_Data> getObjects() { - return Collections.unmodifiableMap(objects); - } - - public <T extends EC_Data> Map<String, T> getObjects(Class<T> cls) { - Map<String, T> objs = new TreeMap<>(); - for (Map.Entry<String, EC_Data> entry : objects.entrySet()) { - if (cls.isInstance(entry.getValue())) { - objs.put(entry.getKey(), cls.cast(entry.getValue())); - } - } - return Collections.unmodifiableMap(objs); - } - - public <T extends EC_Data> T getObject(Class<T> cls, String id) { - EC_Data obj = objects.get(id); - if (cls.isInstance(obj)) { - return cls.cast(obj); - } else { - return null; - } - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder(); - out.append("\t- ").append(name).append((desc == null || desc.equals("")) ? "" : ": " + desc); - out.append(System.lineSeparator()); - - Map<String, EC_Curve> curves = getObjects(EC_Curve.class); - int size = curves.size(); - if (size > 0) { - out.append("\t\tCurves: "); - for (Map.Entry<String, EC_Curve> curve : curves.entrySet()) { - out.append(curve.getKey()); - size--; - if (size > 0) - out.append(", "); - } - out.append(System.lineSeparator()); - } - - Map<String, EC_Key> keys = getObjects(EC_Key.class); - size = keys.size(); - if (size > 0) { - out.append("\t\tKeys: "); - for (Map.Entry<String, EC_Key> key : keys.entrySet()) { - out.append(key.getKey()); - size--; - if (size > 0) - out.append(", "); - } - out.append(System.lineSeparator()); - } - - Map<String, EC_Keypair> keypairs = getObjects(EC_Keypair.class); - size = keypairs.size(); - if (size > 0) { - out.append("\t\tKeypairs: "); - for (Map.Entry<String, EC_Keypair> key : keypairs.entrySet()) { - out.append(key.getKey()); - size--; - if (size > 0) - out.append(", "); - } - out.append(System.lineSeparator()); - } - - Map<String, EC_KAResult> results = getObjects(EC_KAResult.class); - size = results.size(); - if (size > 0) { - out.append("\t\tResults: "); - for (Map.Entry<String, EC_KAResult> result : results.entrySet()) { - out.append(result.getKey()); - size--; - if (size > 0) - out.append(", "); - } - out.append(System.lineSeparator()); - } - return out.toString(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof EC_Category && Objects.equals(this.name, ((EC_Category) obj).name); - } - - @Override - public int hashCode() { - return this.name.hashCode() ^ this.directory.hashCode(); - } - -} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Curve.java b/src/cz/crcs/ectester/reader/ec/EC_Curve.java deleted file mode 100644 index cb4a2df..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Curve.java +++ /dev/null @@ -1,52 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.applet.EC_Consts; -import javacard.security.KeyPair; - -/** - * An Elliptic curve, contains parameters Fp/F2M, A, B, G, R, (K)?. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_Curve extends EC_Params { - private short bits; - private byte field; - private String desc; - - /** - * @param bits - * @param field KeyPair.ALG_EC_FP or KeyPair.ALG_EC_F2M - */ - public EC_Curve(short bits, byte field) { - super(field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M); - this.bits = bits; - this.field = field; - } - - public EC_Curve(String id, short bits, byte field) { - this(bits, field); - this.id = id; - } - - public EC_Curve(String id, short bits, byte field, String desc) { - this(id, bits, field); - this.desc = desc; - } - - public short getBits() { - return bits; - } - - public byte getField() { - return field; - } - - public String getDesc() { - return desc; - } - - @Override - public String toString() { - return "<" + getId() + "> " + (field == KeyPair.ALG_EC_FP ? "Prime" : "Binary") + " field Elliptic curve (" + String.valueOf(bits) + "b)" + (desc == null ? "" : ": " + desc); - } -} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Data.java b/src/cz/crcs/ectester/reader/ec/EC_Data.java deleted file mode 100644 index 0ceddef..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Data.java +++ /dev/null @@ -1,200 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.reader.Util; - -import java.io.*; -import java.util.*; -import java.util.regex.Pattern; - -/** - * A list of byte arrays for holding EC data. - * - * The data can be read from a byte array via <code>readBytes()</code>, from a CSV via <code>readCSV()</code>. - * The data can be exported to a byte array via <code>flatten()</code> or to a string array via <code>expand()</code>. - * @author Jan Jancar johny@neuromancer.sk - */ -public abstract class EC_Data { - String id; - int count; - byte[][] data; - - private static final Pattern HEX = Pattern.compile("(0x|0X)?[a-fA-F\\d]+"); - - EC_Data() { - } - - EC_Data(int count) { - this.count = count; - this.data = new byte[count][]; - } - - EC_Data(byte[][] data) { - this.count = data.length; - this.data = data; - } - - EC_Data(String id, int count) { - this(count); - this.id = id; - } - - EC_Data(String id, byte[][] data) { - this(data); - this.id = id; - } - - public String getId() { - return id; - } - - public int getCount() { - return count; - } - - public byte[][] getData() { - return data; - } - - public boolean hasData() { - return data != null; - } - - public byte[] getParam(int index) { - return data[index]; - } - - public byte[] flatten() { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - for (byte[] param : data) { - byte[] length = new byte[2]; - Util.setShort(length, 0, (short) param.length); - - out.write(length, 0, 2); - out.write(param, 0, param.length); - } - - return out.toByteArray(); - } - - public String[] expand() { - List<String> out = new ArrayList<>(count); - for (byte[] param : data) { - out.add(Util.bytesToHex(param, false)); - } - - return out.toArray(new String[out.size()]); - } - - private static byte[] pad(byte[] data) { - if (data.length == 1) { - return new byte[]{(byte) 0, data[0]}; - } else if (data.length == 0 || data.length > 2) { - return data; - } - return null; - } - - private static byte[] parse(String param) { - byte[] data; - if (param.startsWith("0x") || param.startsWith("0X")) { - data = Util.hexToBytes(param.substring(2)); - } else { - data = Util.hexToBytes(param); - } - if (data == null) - return new byte[0]; - if (data.length < 2) - return pad(data); - return data; - } - - private boolean readHex(String[] hex) { - if (hex.length != count) { - return false; - } - - for (int i = 0; i < count; ++i) { - this.data[i] = parse(hex[i]); - } - return true; - } - - public boolean readCSV(InputStream in) { - Scanner s = new Scanner(in); - - s.useDelimiter(",|;"); - List<String> data = new LinkedList<>(); - while (s.hasNext()) { - String field = s.next(); - data.add(field.replaceAll("\\s+", "")); - } - - if (data.isEmpty()) { - return false; - } - for (String param : data) { - if (!HEX.matcher(param).matches()) { - return false; - } - } - return readHex(data.toArray(new String[data.size()])); - } - - public boolean readBytes(byte[] bytes) { - int offset = 0; - for (int i = 0; i < count; i++) { - if (bytes.length - offset < 2) { - return false; - } - short paramLength = Util.getShort(bytes, offset); - offset += 2; - if (bytes.length < offset + paramLength) { - return false; - } - data[i] = new byte[paramLength]; - System.arraycopy(bytes, offset, data[i], 0, paramLength); - offset += paramLength; - } - return true; - } - - public void writeCSV(OutputStream out) throws IOException { - Writer w = new OutputStreamWriter(out); - w.write(String.join(",", expand())); - w.flush(); - } - - @Override - public String toString() { - return String.join(",", expand()); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof EC_Data) { - EC_Data other = (EC_Data) obj; - if (this.id != null || other.id != null) { - return Objects.equals(this.id, other.id); - } - - if (this.count != other.count) - return false; - for (int i = 0; i < this.count; ++i) { - if (!Arrays.equals(this.data[i], other.data[i])) { - return false; - } - } - return true; - } else { - return false; - } - } - - @Override - public int hashCode() { - if (this.id != null) { - return this.id.hashCode(); - } - return Arrays.deepHashCode(this.data); - } -} diff --git a/src/cz/crcs/ectester/reader/ec/EC_KAResult.java b/src/cz/crcs/ectester/reader/ec/EC_KAResult.java deleted file mode 100644 index 4a67bbe..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_KAResult.java +++ /dev/null @@ -1,63 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.reader.Util; - -/** - * A result of EC based Key agreement operation. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_KAResult extends EC_Data { - - private byte ka; - private String curve; - private String oneKey; - private String otherKey; - - private String desc; - - public EC_KAResult(byte ka, String curve, String oneKey, String otherKey) { - super(1); - this.ka = ka; - this.curve = curve; - this.oneKey = oneKey; - this.otherKey = otherKey; - } - - public EC_KAResult(String id, byte ka, String curve, String oneKey, String otherKey) { - this(ka, curve, oneKey, otherKey); - this.id = id; - } - - public EC_KAResult(String id, byte ka, String curve, String oneKey, String otherKey, String desc) { - this(id, ka, curve, oneKey, otherKey); - this.desc = desc; - } - - public byte getKA() { - return ka; - } - - public String getCurve() { - return curve; - } - - public String getOneKey() { - return oneKey; - } - - public String getOtherKey() { - return otherKey; - } - - public String getDesc() { - return desc; - } - - @Override - public String toString() { - String algo = Util.getKA(ka); - return "<" + getId() + "> " + algo + " result over " + curve + ", " + oneKey + " + " + otherKey + (desc == null ? "" : ": " + desc); - } - -} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Key.java b/src/cz/crcs/ectester/reader/ec/EC_Key.java deleted file mode 100644 index 5077d5b..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Key.java +++ /dev/null @@ -1,83 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.applet.EC_Consts; - -/** - * An abstract-like EC key. Concrete implementations create a public and private keys. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_Key extends EC_Params { - - private String curve; - private String desc; - - private EC_Key(short mask, String curve) { - super(mask); - this.curve = curve; - } - - private EC_Key(short mask, String curve, String desc) { - this(mask, curve); - this.desc = desc; - } - - private EC_Key(String id, short mask, String curve, String desc) { - this(mask, curve, desc); - this.id = id; - } - - public String getCurve() { - return curve; - } - - public String getDesc() { - return desc; - } - - /** - * An EC public key, contains the W parameter. - */ - public static class Public extends EC_Key { - - public Public(String curve) { - super(EC_Consts.PARAMETER_W, curve); - } - - public Public(String curve, String desc) { - super(EC_Consts.PARAMETER_W, curve, desc); - } - - public Public(String id, String curve, String desc) { - super(id, EC_Consts.PARAMETER_W, curve, desc); - } - - @Override - public String toString() { - return "<" + getId() + "> EC Public key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc()); - } - } - - /** - * An EC private key, contains the S parameter. - */ - public static class Private extends EC_Key { - - public Private(String curve) { - super(EC_Consts.PARAMETER_S, curve); - } - - public Private(String curve, String desc) { - super(EC_Consts.PARAMETER_S, curve, desc); - } - - public Private(String id, String curve, String desc) { - super(id, EC_Consts.PARAMETER_S, curve, desc); - } - - @Override - public String toString() { - return "<" + getId() + "> EC Private key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc()); - } - } -} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Keypair.java b/src/cz/crcs/ectester/reader/ec/EC_Keypair.java deleted file mode 100644 index 2643346..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Keypair.java +++ /dev/null @@ -1,41 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.applet.EC_Consts; - -/** - * An EC keypair, contains both the W and S parameters. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_Keypair extends EC_Params { - private String curve; - private String desc; - - public EC_Keypair(String curve) { - super(EC_Consts.PARAMETERS_KEYPAIR); - this.curve = curve; - } - - public EC_Keypair(String curve, String desc) { - this(curve); - this.desc = desc; - } - - public EC_Keypair(String id, String curve, String desc) { - this(curve, desc); - this.id = id; - } - - public String getCurve() { - return curve; - } - - public String getDesc() { - return desc; - } - - @Override - public String toString() { - return "<" + getId() + "> EC Keypair, over " + curve + (desc == null ? "" : ": " + desc); - } -} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Params.java b/src/cz/crcs/ectester/reader/ec/EC_Params.java deleted file mode 100644 index 6fb164b..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Params.java +++ /dev/null @@ -1,151 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.reader.Util; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * A list of EC parameters, can contain a subset of the Fp/F2M, A, B, G, R, K, W, S parameters. - * - * The set of parameters is uniquely identified by a short bit string. - * The parameters can be exported to a byte array via <code>flatten()</code> or to a comma delimited - * string via <code>expand()</code>. - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_Params extends EC_Data { - private short params; - - public EC_Params(short params) { - this.params = params; - this.count = numParams(); - this.data = new byte[this.count][]; - } - - public EC_Params(short params, byte[][] data) { - this.params = params; - this.count = data.length; - this.data = data; - } - - public EC_Params(String id, short params) { - this(params); - this.id = id; - } - - public EC_Params(String id, short params, byte[][] data) { - this(params, data); - this.id = id; - } - - public short getParams() { - return params; - } - - public boolean hasParam(short param) { - return (params & param) != 0; - } - - public int numParams() { - short paramMask = EC_Consts.PARAMETER_FP; - int num = 0; - while (paramMask <= EC_Consts.PARAMETER_S) { - if ((paramMask & params) != 0) { - if (paramMask == EC_Consts.PARAMETER_F2M) { - num += 3; - } - if (paramMask == EC_Consts.PARAMETER_W || paramMask == EC_Consts.PARAMETER_G) { - num += 1; - } - ++num; - } - paramMask = (short) (paramMask << 1); - } - return num; - } - - @Override - public byte[] flatten() { - return flatten(params); - } - - public byte[] flatten(short params) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - short paramMask = EC_Consts.PARAMETER_FP; - int i = 0; - while (paramMask <= EC_Consts.PARAMETER_S) { - short masked = (short) (this.params & params & paramMask); - short shallow = (short) (this.params & paramMask); - if (masked != 0) { - byte[] param = data[i]; - if (masked == EC_Consts.PARAMETER_F2M) { - //add m, e_1, e_2, e_3 - param = Util.concatenate(param, data[i + 1]); - if (!Util.allValue(data[i + 2], (byte) 0)) { - param = Util.concatenate(param, data[i + 2]); - } - if (!Util.allValue(data[i + 3], (byte) 0)) { - param = Util.concatenate(param, data[i + 3]); - } - if (!(param.length == 4 || param.length == 8)) - throw new RuntimeException("PARAMETER_F2M length is not 8.(should be)"); - } - if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) { - //read another param (the y coord) and put into X962 format. - byte[] y = data[i + 1]; - param = Util.concatenate(new byte[]{4}, param, y); //<- ugly but works! - } - if (param.length == 0) - throw new RuntimeException("Empty parameter read?"); - - //write length - byte[] length = new byte[2]; - Util.setShort(length, 0, (short) param.length); - out.write(length, 0, 2); - //write data - out.write(param, 0, param.length); - } - if (shallow == EC_Consts.PARAMETER_F2M) { - i += 4; - } else if (shallow == EC_Consts.PARAMETER_G || shallow == EC_Consts.PARAMETER_W) { - i += 2; - } else if (shallow != 0) { - i++; - } - paramMask = (short) (paramMask << 1); - } - - return (out.size() == 0) ? null : out.toByteArray(); - } - - @Override - public String[] expand() { - List<String> out = new ArrayList<>(); - - short paramMask = EC_Consts.PARAMETER_FP; - int index = 0; - while (paramMask <= EC_Consts.PARAMETER_S) { - short masked = (short) (params & paramMask); - if (masked != 0) { - byte[] param = data[index]; - if (masked == EC_Consts.PARAMETER_F2M) { - for (int i = 0; i < 4; ++i) { - out.add(Util.bytesToHex(data[index + i], false)); - } - index += 4; - } else if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) { - out.add(Util.bytesToHex(param, false)); - out.add(Util.bytesToHex(data[index + 1], false)); - index += 2; - } else { - out.add(Util.bytesToHex(param, false)); - index++; - } - } - paramMask = (short) (paramMask << 1); - } - return out.toArray(new String[out.size()]); - } -} diff --git a/src/cz/crcs/ectester/reader/output/OutputLogger.java b/src/cz/crcs/ectester/reader/output/OutputLogger.java deleted file mode 100644 index bf47a1f..0000000 --- a/src/cz/crcs/ectester/reader/output/OutputLogger.java +++ /dev/null @@ -1,60 +0,0 @@ -package cz.crcs.ectester.reader.output; - -import java.io.*; -import java.util.LinkedList; -import java.util.List; - -/** - * @author Petr Svenda petr@svenda.com - * @author Jan Jancar johny@neuromancer.sk - */ -public class OutputLogger { - private OutputStream out; - private PrintStream print; - - public OutputLogger(boolean systemOut, String... filePaths) throws IOException { - List<OutputStream> streams = new LinkedList<>(); - for (String filePath : filePaths) { - if (filePath != null) { - streams.add(new FileOutputStream(filePath)); - } - } - if (systemOut) { - streams.add(System.out); - } - this.out = new TeeOutputStream(streams.toArray(new OutputStream[0])); - this.print = new PrintStream(this.out); - } - - public OutputLogger(String filePath) throws IOException { - this(true, filePath); - } - - public OutputStream getOutputStream() { - return this.out; - } - - public PrintStream getPrintStream() { - return this.print; - } - - public void println() { - print.println(); - } - - public void println(String logLine) { - print.println(logLine); - } - - public void print(String logLine) { - print.print(logLine); - } - - public void flush() { - print.flush(); - } - - public void close() { - print.close(); - } -} diff --git a/src/cz/crcs/ectester/reader/output/ResponseWriter.java b/src/cz/crcs/ectester/reader/output/ResponseWriter.java index c357233..85bf79a 100644 --- a/src/cz/crcs/ectester/reader/output/ResponseWriter.java +++ b/src/cz/crcs/ectester/reader/output/ResponseWriter.java @@ -1,6 +1,6 @@ package cz.crcs.ectester.reader.output; -import cz.crcs.ectester.reader.Util; +import cz.crcs.ectester.common.util.CardUtil; import cz.crcs.ectester.reader.response.Response; import java.io.PrintStream; @@ -20,20 +20,24 @@ public class ResponseWriter { for (int j = 0; j < r.getNumSW(); ++j) { short sw = r.getSW(j); if (sw != 0) { - suffix.append(" ").append(Util.getSWString(sw)); + suffix.append(" ").append(CardUtil.getSWString(sw)); } } if (suffix.length() == 0) { - suffix.append(" [").append(Util.getSW(r.getNaturalSW())).append("]"); + 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 void outputResponse(Response r) { + public String responseString(Response r) { String out = ""; out += String.format("%-70s", r.getDescription()) + " ┃ "; out += responseSuffix(r); - output.println(out); + return out; + } + + public void outputResponse(Response r) { + output.println(responseString(r)); output.flush(); } } diff --git a/src/cz/crcs/ectester/reader/output/TeeOutputStream.java b/src/cz/crcs/ectester/reader/output/TeeOutputStream.java deleted file mode 100644 index 2a1af99..0000000 --- a/src/cz/crcs/ectester/reader/output/TeeOutputStream.java +++ /dev/null @@ -1,36 +0,0 @@ -package cz.crcs.ectester.reader.output; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class TeeOutputStream extends OutputStream { - private OutputStream[] outputs; - - public TeeOutputStream(OutputStream... outputs) { - this.outputs = outputs; - } - - @Override - public void write(int b) throws IOException { - for (OutputStream out : outputs) { - out.write(b); - } - } - - @Override - public void flush() throws IOException { - for (OutputStream out : outputs) { - out.flush(); - } - } - - @Override - public void close() throws IOException { - for (OutputStream out : outputs) { - out.close(); - } - } -} diff --git a/src/cz/crcs/ectester/reader/output/TestWriter.java b/src/cz/crcs/ectester/reader/output/TestWriter.java deleted file mode 100644 index 74c76fb..0000000 --- a/src/cz/crcs/ectester/reader/output/TestWriter.java +++ /dev/null @@ -1,15 +0,0 @@ -package cz.crcs.ectester.reader.output; - -import cz.crcs.ectester.reader.test.Test; -import cz.crcs.ectester.reader.test.TestSuite; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public interface TestWriter { - void begin(TestSuite suite); - - void outputTest(Test t); - - void end(); -} diff --git a/src/cz/crcs/ectester/reader/output/TextTestWriter.java b/src/cz/crcs/ectester/reader/output/TextTestWriter.java index bcebcd5..eb52937 100644 --- a/src/cz/crcs/ectester/reader/output/TextTestWriter.java +++ b/src/cz/crcs/ectester/reader/output/TextTestWriter.java @@ -1,85 +1,57 @@ package cz.crcs.ectester.reader.output; -import cz.crcs.ectester.reader.test.Test; -import cz.crcs.ectester.reader.test.TestSuite; +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.reader.CardMngr; +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 implements TestWriter { - private PrintStream output; - private ResponseWriter respWriter; - - public static int BASE_WIDTH = 76; +public class TextTestWriter extends BaseTextTestWriter { + private ResponseWriter writer; public TextTestWriter(PrintStream output) { - this.output = output; - this.respWriter = new ResponseWriter(output); + super(output); + this.writer = new ResponseWriter(output); } @Override - public void begin(TestSuite suite) { - output.println("=== Running test suite: " + suite.getName() + " ==="); - output.println("=== " + suite.getDescription()); - } - - private String testString(Test t, int offset) { - if (!t.hasRun()) { - return null; + protected String testableString(Testable t) { + if (t instanceof CommandTestable) { + CommandTestable cmd = (CommandTestable) t; + return writer.responseSuffix(cmd.getResponse()); } + return ""; + } - StringBuilder out = new StringBuilder(); - if (t instanceof Test.Simple) { - Test.Simple test = (Test.Simple) t; - out.append(test.ok() ? "OK " : "NOK "); - out.append("━ "); - int width = BASE_WIDTH - (offset + out.length()); - String widthSpec = "%-" + String.valueOf(width) + "s"; - out.append(String.format(widthSpec, t.getDescription())); - out.append(" ┃ "); - out.append(String.format("%-9s", test.getResultValue().name())); - out.append(" ┃ "); - out.append(respWriter.responseSuffix(test.getResponse())); - } else { - Test.Compound test = (Test.Compound) t; - out.append(test.ok() ? "OK " : "NOK "); - out.append("┳ "); - int width = BASE_WIDTH - (offset + out.length()); - String widthSpec = "%-" + String.valueOf(width) + "s"; - out.append(String.format(widthSpec, t.getDescription())); - out.append(" ┃ "); - out.append(String.format("%-9s", test.getResultValue().name())); - out.append(" ┃ "); - out.append(test.getResultCause()); - out.append(System.lineSeparator()); - Test[] tests = test.getTests(); - for (int i = 0; i < tests.length; ++i) { - if (i == tests.length - 1) { - out.append(" ┗ "); - } else { - out.append(" ┣ "); - } - out.append(testString(tests[i], offset + 6)); - if (i != tests.length - 1) { - out.append(System.lineSeparator()); + @Override + protected String deviceString(TestSuite suite) { + if (suite instanceof CardTestSuite) { + CardTestSuite cardSuite = (CardTestSuite) suite; + StringBuilder sb = new StringBuilder(); + sb.append("═══ Card ATR: ").append(ByteUtil.bytesToHex(cardSuite.getCard().getATR().getBytes(), false)).append(System.lineSeparator()); + try { + CardMngr.CPLC cplc = cardSuite.getCard().getCPLC(); + if (!cplc.values().isEmpty()) { + sb.append("═══ 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 out.toString(); - } - - @Override - public void outputTest(Test t) { - if (!t.hasRun()) - return; - output.println(testString(t, 0)); - output.flush(); - } - - @Override - public void end() { + return ""; } } diff --git a/src/cz/crcs/ectester/reader/output/XMLTestWriter.java b/src/cz/crcs/ectester/reader/output/XMLTestWriter.java index beb758c..d3674e8 100644 --- a/src/cz/crcs/ectester/reader/output/XMLTestWriter.java +++ b/src/cz/crcs/ectester/reader/output/XMLTestWriter.java @@ -1,55 +1,34 @@ package cz.crcs.ectester.reader.output; -import cz.crcs.ectester.reader.Util; +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.command.Command; import cz.crcs.ectester.reader.response.Response; -import cz.crcs.ectester.reader.test.Test; -import cz.crcs.ectester.reader.test.TestSuite; -import org.w3c.dom.Document; +import cz.crcs.ectester.reader.test.CardTestSuite; +import cz.crcs.ectester.reader.test.CommandTestable; import org.w3c.dom.Element; -import org.w3c.dom.Node; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; +import javax.smartcardio.CardException; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; import java.io.OutputStream; +import java.util.Map; /** * @author Jan Jancar johny@neuromancer.sk */ -public class XMLTestWriter implements TestWriter { - private OutputStream output; - private DocumentBuilder db; - private Document doc; - private Node root; - +public class XMLTestWriter extends BaseXMLTestWriter { public XMLTestWriter(OutputStream output) throws ParserConfigurationException { - this.output = output; - this.db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - } - - @Override - public void begin(TestSuite suite) { - doc = db.newDocument(); - Element rootElem = doc.createElement("testSuite"); - rootElem.setAttribute("name", suite.getName()); - rootElem.setAttribute("desc", suite.getDescription()); - - root = rootElem; - doc.appendChild(root); + super(output); } private Element commandElement(Command c) { Element commandElem = doc.createElement("command"); Element apdu = doc.createElement("apdu"); - apdu.setTextContent(Util.bytesToHex(c.getAPDU().getBytes())); + apdu.setTextContent(ByteUtil.bytesToHex(c.getAPDU().getBytes())); commandElem.appendChild(apdu); return commandElem; @@ -60,7 +39,7 @@ public class XMLTestWriter implements TestWriter { responseElem.setAttribute("successful", r.successful() ? "true" : "false"); Element apdu = doc.createElement("apdu"); - apdu.setTextContent(Util.bytesToHex(r.getAPDU().getBytes())); + apdu.setTextContent(ByteUtil.bytesToHex(r.getAPDU().getBytes())); responseElem.appendChild(apdu); Element naturalSW = doc.createElement("natural-sw"); @@ -86,60 +65,50 @@ public class XMLTestWriter implements TestWriter { return responseElem; } - private Element testElement(Test t) { - Element testElem = doc.createElement("test"); + @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; + } - if (t instanceof Test.Simple) { - Test.Simple test = (Test.Simple) t; - testElem.setAttribute("type", "simple"); - testElem.appendChild(commandElement(test.getCommand())); - testElem.appendChild(responseElement(test.getResponse())); - } else if (t instanceof Test.Compound) { - Test.Compound test = (Test.Compound) t; - testElem.setAttribute("type", "compound"); - for (Test innerTest : test.getTests()) { - testElem.appendChild(testElement(innerTest)); + 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) { } - - Element description = doc.createElement("desc"); - description.setTextContent(t.getDescription()); - testElem.appendChild(description); - - Element result = doc.createElement("result"); - Element ok = doc.createElement("ok"); - ok.setTextContent(String.valueOf(t.ok())); - Element value = doc.createElement("value"); - value.setTextContent(t.getResultValue().name()); - Element cause = doc.createElement("cause"); - cause.setTextContent(t.getResultCause()); - result.appendChild(ok); - result.appendChild(value); - result.appendChild(cause); - testElem.appendChild(result); - - return testElem; + return result; } @Override - public void outputTest(Test t) { - if (!t.hasRun()) - return; - root.appendChild(testElement(t)); - } + protected Element deviceElement(TestSuite suite) { + if (suite instanceof CardTestSuite) { + CardTestSuite cardSuite = (CardTestSuite) suite; + Element result = doc.createElement("device"); + result.setAttribute("type", "card"); + result.appendChild(cplcElement(cardSuite.getCard())); - @Override - public void end() { - try { - DOMSource domSource = new DOMSource(doc); - StreamResult result = new StreamResult(output); - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer = tf.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - transformer.transform(domSource, result); - } catch (TransformerException e) { - e.printStackTrace(); + 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/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java b/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java index 3b2b72d..199f2c0 100644 --- a/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java +++ b/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java @@ -1,13 +1,16 @@ package cz.crcs.ectester.reader.output; -import cz.crcs.ectester.reader.Util; +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.command.Command; import cz.crcs.ectester.reader.response.Response; -import cz.crcs.ectester.reader.test.Test; -import cz.crcs.ectester.reader.test.TestSuite; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; +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.HashMap; import java.util.LinkedList; @@ -17,39 +20,21 @@ import java.util.Map; /** * @author Jan Jancar johny@neuromancer.sk */ -public class YAMLTestWriter implements TestWriter { - private PrintStream output; - private Map<String, Object> testRun; - private Map<String, String> testSuite; - private List<Object> tests; - +public class YAMLTestWriter extends BaseYAMLTestWriter { public YAMLTestWriter(PrintStream output) { - this.output = output; - } - - @Override - public void begin(TestSuite suite) { - output.println("---"); - testRun = new HashMap<>(); - testSuite = new HashMap<>(); - tests = new LinkedList<>(); - testSuite.put("name", suite.getName()); - testSuite.put("desc", suite.getDescription()); - - testRun.put("suite", testSuite); - testRun.put("tests", tests); + super(output); } private Map<String, Object> commandObject(Command c) { Map<String, Object> commandObj = new HashMap<>(); - commandObj.put("apdu", Util.bytesToHex(c.getAPDU().getBytes())); + commandObj.put("apdu", ByteUtil.bytesToHex(c.getAPDU().getBytes())); return commandObj; } private Map<String, Object> responseObject(Response r) { Map<String, Object> responseObj = new HashMap<>(); responseObj.put("successful", r.successful()); - responseObj.put("apdu", Util.bytesToHex(r.getAPDU().getBytes())); + 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) { @@ -61,53 +46,45 @@ public class YAMLTestWriter implements TestWriter { return responseObj; } - private Map<String, Object> testObject(Test t) { - Map<String, Object> testObj = new HashMap<>(); - - if (t instanceof Test.Simple) { - Test.Simple test = (Test.Simple) t; - testObj.put("type", "simple"); - testObj.put("command", commandObject(test.getCommand())); - testObj.put("response", responseObject(test.getResponse())); - } else if (t instanceof Test.Compound) { - Test.Compound test = (Test.Compound) t; - testObj.put("type", "compound"); - List<Map<String, Object>> tests = new LinkedList<>(); - for (Test innerTest : test.getTests()) { - tests.add(testObject(innerTest)); - } - testObj.put("tests", tests); + @Override + protected Map<String, Object> testableObject(Testable t) { + if (t instanceof CommandTestable) { + CommandTestable cmd = (CommandTestable) t; + Map<String, Object> result = new HashMap<>(); + result.put("type", "command"); + result.put("command", commandObject(cmd.getCommand())); + result.put("response", responseObject(cmd.getResponse())); + return result; } - - testObj.put("desc", t.getDescription()); - Map<String, Object> result = new HashMap<>(); - result.put("ok", t.ok()); - result.put("value", t.getResultValue().name()); - result.put("cause", t.getResultCause()); - testObj.put("result", result); - - return testObj; + return null; } - @Override - public void outputTest(Test t) { - if (!t.hasRun()) - return; - tests.add(testObject(t)); + private Map<String, Object> cplcObject(CardMngr card) { + Map<String, Object> result = new HashMap<>(); + 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; } @Override - public void end() { - DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - options.setPrettyFlow(true); - Yaml yaml = new Yaml(options); - - Map<String, Object> result = new HashMap<>(); - result.put("testRun", testRun); - String out = yaml.dump(result); - - output.println(out); - output.println("---"); + protected Map<String, Object> deviceObject(TestSuite suite) { + if (suite instanceof CardTestSuite) { + CardTestSuite cardSuite = (CardTestSuite) suite; + Map<String, Object> result = new HashMap<>(); + result.put("type", "card"); + result.put("cplc", cplcObject(cardSuite.getCard())); + result.put("ATR", ByteUtil.bytesToHex(cardSuite.getCard().getATR().getBytes(), false)); + return result; + } + return null; } } diff --git a/src/cz/crcs/ectester/reader/response/Response.java b/src/cz/crcs/ectester/reader/response/Response.java index 4abfd14..cbed3b2 100644 --- a/src/cz/crcs/ectester/reader/response/Response.java +++ b/src/cz/crcs/ectester/reader/response/Response.java @@ -2,8 +2,8 @@ package cz.crcs.ectester.reader.response; import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.reader.Util; -import cz.crcs.ectester.reader.ec.EC_Data; +import cz.crcs.ectester.common.util.ByteUtil; +import cz.crcs.ectester.common.util.CardUtil; import javacard.framework.ISO7816; import javacard.security.KeyPair; @@ -13,7 +13,6 @@ import javax.smartcardio.ResponseAPDU; * @author Jan Jancar johny@neuromancer.sk */ public abstract class Response { - private ResponseAPDU resp; private long time; private short[] sws; @@ -37,7 +36,7 @@ public abstract class Response { //parse SWs in response for (int i = 0; i < numSW; ++i) { if (getLength() >= (offset + 2)) { - short sw = Util.getShort(data, offset); + short sw = ByteUtil.getShort(data, offset); offset += 2; sws[i] = sw; if (sw != ISO7816.SW_NO_ERROR) { @@ -63,7 +62,7 @@ public abstract class Response { error = true; break; } - short paramLength = Util.getShort(data, offset); + short paramLength = ByteUtil.getShort(data, offset); offset += 2; if (data.length < offset + paramLength) { error = true; @@ -88,6 +87,10 @@ public abstract class Response { return (short) resp.getSW(); } + public short[] getSWs() { + return sws; + } + public short getSW(int index) { return sws[index]; } @@ -130,24 +133,44 @@ public abstract class Response { * */ public static class AllocateKeyAgreement extends Response { - byte kaType; + private byte kaType; public AllocateKeyAgreement(ResponseAPDU response, long time, byte kaType) { super(response, time); this.kaType = kaType; - parse(2, 0); + parse(1, 0); } @Override public String getDescription() { - return String.format("Allocated KeyAgreement(%s) object", Util.getKATypeString(this.kaType)); + return String.format("Allocated KeyAgreement(%s) object", CardUtil.getKATypeString(this.kaType)); } + } + + /** + * + */ + public static class AllocateSignature extends Response { + private byte sigType; + + public AllocateSignature(ResponseAPDU response, long time, byte sigType) { + super(response, time); + this.sigType = sigType; + parse(1, 0); + } + + @Override + public String getDescription() { + return String.format("Allocated Signature(%s) object", CardUtil.getSigTypeString(this.sigType)); + } } + /** + * + */ public static class Allocate extends Response { - private byte keyPair; private short keyLength; private byte keyClass; @@ -181,7 +204,6 @@ public abstract class Response { * */ public static class Clear extends Response { - private byte keyPair; public Clear(ResponseAPDU response, long time, byte keyPair) { @@ -210,7 +232,6 @@ public abstract class Response { * */ public static class Set extends Response { - private byte keyPair; private byte curve; private short parameters; @@ -268,7 +289,6 @@ public abstract class Response { * */ public static class Corrupt extends Response { - private byte keyPair; private byte key; private short params; @@ -290,7 +310,7 @@ public abstract class Response { @Override public String getDescription() { - String corrupt = Util.getCorruption(corruption); + String corrupt = CardUtil.getCorruption(corruption); String pair; if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { @@ -306,7 +326,6 @@ public abstract class Response { * */ public static class Generate extends Response { - private byte keyPair; public Generate(ResponseAPDU response, long time, byte keyPair) { @@ -336,7 +355,6 @@ public abstract class Response { * */ public static class Export extends Response { - private byte keyPair; private byte key; private short parameters; @@ -445,7 +463,6 @@ public abstract class Response { * */ public static class ECDH extends Response { - private byte pubkey; private byte privkey; private byte export; @@ -477,7 +494,7 @@ public abstract class Response { @Override public String getDescription() { - String algo = Util.getKA(type); + String algo = CardUtil.getKATypeString(type); String pub = pubkey == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote"; String priv = privkey == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote"; @@ -486,7 +503,7 @@ public abstract class Response { if (corruption == EC_Consts.CORRUPTION_NONE) { validity = "unchanged"; } else { - validity = Util.getCorruption(corruption); + validity = CardUtil.getCorruption(corruption); } return String.format("%s of %s pubkey and %s privkey(%s point)", algo, pub, priv, validity); } @@ -496,14 +513,15 @@ public abstract class Response { * */ public static class ECDSA extends Response { - private byte keyPair; + private byte sigType; private byte export; private byte[] raw; - public ECDSA(ResponseAPDU response, long time, byte keyPair, byte export, byte[] raw) { + public ECDSA(ResponseAPDU response, long time, byte keyPair, byte sigType, byte export, byte[] raw) { super(response, time); this.keyPair = keyPair; + this.sigType = sigType; this.export = export; this.raw = raw; @@ -520,9 +538,10 @@ public abstract class Response { @Override public String getDescription() { + String algo = CardUtil.getSigTypeString(sigType); String key = keyPair == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote"; String data = raw == null ? "random" : "provided"; - return String.format("ECDSA with %s keypair(%s data)", key, data); + return String.format("%s with %s keypair(%s data)", algo, key, data); } } @@ -543,21 +562,4 @@ public abstract class Response { } } - - /** - * - */ - public static class Support extends Response { - - public Support(ResponseAPDU response, long time) { - super(response, time); - - parse(3, 0); - } - - @Override - public String getDescription() { - return "Support of ECDH, ECDHC, ECDSA allocation"; - } - } } diff --git a/src/cz/crcs/ectester/reader/test/CardCompositeCurvesSuite.java b/src/cz/crcs/ectester/reader/test/CardCompositeCurvesSuite.java new file mode 100644 index 0000000..a53806c --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CardCompositeCurvesSuite.java @@ -0,0 +1,51 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.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.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.util.Map; + +import static cz.crcs.ectester.common.test.Result.ExpectedValue; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardCompositeCurvesSuite extends CardTestSuite { + + public CardCompositeCurvesSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "composite", "The composite suite run ECDH over curves with composite order. This should generally fail, as using such a curve is unsafe."); + } + + @Override + protected void runTests() throws Exception { + /* Do the default run with the public keys set to provided smallorder keys + * over composite order curves. Essentially small subgroup attacks. + * These should fail, the curves aren't safe so that if the computation with + * a small order public key succeeds the private key modulo the public key order + * is revealed. + */ + Map<String, EC_Key> keys = EC_Store.getInstance().getObjects(EC_Key.class, "composite"); + for (EC_Key key : keys.values()) { + EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, key.getCurve()); + if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { + continue; + } + if ((curve.getBits() == cfg.bits || cfg.all)) { + doTest(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); + doTest(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.ANY)); + doTest(CommandTest.expect(new Command.Generate(this.card, ECTesterApplet.KEYPAIR_LOCAL), ExpectedValue.ANY)); + Command ecdhCommand = new Command.ECDH_direct(this.card, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH, key.flatten()); + doTest(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.")); + new Command.Cleanup(this.card).send(); + } + } + } +} diff --git a/src/cz/crcs/ectester/reader/test/CardDefaultSuite.java b/src/cz/crcs/ectester/reader/test/CardDefaultSuite.java new file mode 100644 index 0000000..c3bd9c8 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CardDefaultSuite.java @@ -0,0 +1,94 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.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.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 static cz.crcs.ectester.common.test.Result.ExpectedValue; + +/** + * @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", "The default test suite run basic support 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; + for (short keyLength : keySizes) { + String description = "Tests of " + keyLength + "b " + (field == KeyPair.ALG_EC_FP ? "ALG_EC_FP" : "ALG_EC_F2M") + " support."; + + List<Test> supportTests = new LinkedList<>(); + Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, keyLength, field), ExpectedValue.SUCCESS)); + if (!key.ok()) { + doTest(CompoundTest.all(ExpectedValue.SUCCESS, description + " None.", key)); + continue; + } + supportTests.add(key); + + Test genDefault = runTest(CommandTest.expect(new Command.Generate(this.card, ECTesterApplet.KEYPAIR_BOTH), ExpectedValue.SUCCESS)); + Test setCustom = runTest(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.getCurve(keyLength, field), EC_Consts.PARAMETERS_DOMAIN_FP, null), ExpectedValue.SUCCESS)); + Test genCustom = runTest(CommandTest.expect(new Command.Generate(this.card, ECTesterApplet.KEYPAIR_BOTH), ExpectedValue.SUCCESS)); + supportTests.add(genDefault); + supportTests.add(setCustom); + supportTests.add(genCustom); + + 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, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, kaType); + Test ka = runTest(CommandTest.expect(ecdh, ExpectedValue.SUCCESS)); + Test kaCompressed = runTest(CommandTest.expect(new Command.ECDH(this.card, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_COMPRESS, kaType), ExpectedValue.SUCCESS)); + Test perfTest = null; + if (ka.ok()) { + perfTest = runTest(PerformanceTest.repeat(ecdh, 10)); + } + Test compound = runTest(CompoundTest.all(ExpectedValue.SUCCESS, "Test of the " + CardUtil.getKATypeString(kaType) + " KeyAgreement.", allocate, ka, kaCompressed, perfTest)); + supportTests.add(compound); + } else { + runTest(allocate); + supportTests.add(allocate); + } + } + 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, ECTesterApplet.KEYPAIR_LOCAL, sigType, ECTesterApplet.EXPORT_FALSE, null); + Test expect = runTest(CommandTest.expect(ecdsa, ExpectedValue.SUCCESS)); + Test perfTest = null; + if (expect.ok()) { + perfTest = runTest(PerformanceTest.repeat(ecdsa, 10)); + } + Test compound = runTest(CompoundTest.all(ExpectedValue.SUCCESS, "Test of the " + CardUtil.getSigTypeString(sigType) + " signature.", allocate, expect, perfTest)); + supportTests.add(compound); + } else { + supportTests.add(allocate); + } + } + doTest(CompoundTest.all(ExpectedValue.SUCCESS, description + " Some.", supportTests.toArray(new Test[0]))); + new Command.Cleanup(this.card).send(); + } + } +} diff --git a/src/cz/crcs/ectester/reader/test/CardInvalidCurvesSuite.java b/src/cz/crcs/ectester/reader/test/CardInvalidCurvesSuite.java new file mode 100644 index 0000000..8424d45 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CardInvalidCurvesSuite.java @@ -0,0 +1,67 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.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.Test; +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.util.HashMap; +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 CardInvalidCurvesSuite extends CardTestSuite { + + public CardInvalidCurvesSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "invalid", "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 (or decreasing) order. + */ + Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "invalid"); + Map<EC_Curve, List<EC_Key.Public>> curves = new HashMap<>(); + for (EC_Key.Public key : pubkeys.values()) { + EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, key.getCurve()); + if (curve.getBits() != cfg.bits && !cfg.all) { + continue; + } + if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { + continue; + } + List<EC_Key.Public> keys = curves.getOrDefault(curve, new LinkedList<>()); + keys.add(key); + curves.putIfAbsent(curve, keys); + } + for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curves.entrySet()) { + EC_Curve curve = e.getKey(); + List<EC_Key.Public> keys = e.getValue(); + + doTest(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); + doTest(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS)); + doTest(CommandTest.expect(new Command.Generate(this.card, ECTesterApplet.KEYPAIR_LOCAL), ExpectedValue.SUCCESS)); + List<Test> ecdhTests = new LinkedList<>(); + for (EC_Key.Public pub : keys) { + Command ecdhCommand = new Command.ECDH_direct(this.card, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten()); + ecdhTests.add(CommandTest.expect(ecdhCommand, ExpectedValue.FAILURE, "Card correctly rejected point on invalid curve.", "Card incorrectly accepted point on invalid curve.")); + } + doTest(CompoundTest.all(ExpectedValue.SUCCESS, "Invalid curve test of " + curve.getId(), ecdhTests.toArray(new Test[0]))); + new Command.Cleanup(this.card).send(); + } + } +} diff --git a/src/cz/crcs/ectester/reader/test/CardTestSuite.java b/src/cz/crcs/ectester/reader/test/CardTestSuite.java new file mode 100644 index 0000000..0eccd16 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CardTestSuite.java @@ -0,0 +1,24 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.TestSuite; +import cz.crcs.ectester.reader.CardMngr; +import cz.crcs.ectester.reader.ECTesterReader; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class CardTestSuite extends TestSuite { + ECTesterReader.Config cfg; + CardMngr card; + + CardTestSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager, String name, String description) { + super(writer, name, description); + this.card = cardManager; + this.cfg = cfg; + } + + public CardMngr getCard() { + return card; + } +} diff --git a/src/cz/crcs/ectester/reader/test/CardTestVectorSuite.java b/src/cz/crcs/ectester/reader/test/CardTestVectorSuite.java new file mode 100644 index 0000000..73c6621 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CardTestVectorSuite.java @@ -0,0 +1,83 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.common.ec.*; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.*; +import cz.crcs.ectester.common.util.ByteUtil; +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.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +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", "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()); + if (curve.getBits() != cfg.bits && !cfg.all) { + continue; + } + if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { + continue; + } + EC_Params onekey = 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<>(); + + testVector.add(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); + testVector.add(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS)); + testVector.add(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.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, ECTesterApplet.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, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_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 " + String.valueOf(firstDiff) + "."); + } + return new Result(Value.SUCCESS); + } + })); + doTest(CompoundTest.all(ExpectedValue.SUCCESS, "Test vector " + result.getId(), testVector.toArray(new Test[0]))); + new Command.Cleanup(this.card).send(); + } + } +} diff --git a/src/cz/crcs/ectester/reader/test/CardTwistTestSuite.java b/src/cz/crcs/ectester/reader/test/CardTwistTestSuite.java new file mode 100644 index 0000000..c43b234 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CardTwistTestSuite.java @@ -0,0 +1,62 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.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.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.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardTwistTestSuite extends CardTestSuite { + public CardTwistTestSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "twist", "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>> curves = new HashMap<>(); + for (EC_Key.Public key : pubkeys.values()) { + EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, key.getCurve()); + if (curve.getBits() != cfg.bits && !cfg.all) { + continue; + } + if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { + continue; + } + List<EC_Key.Public> keys = curves.getOrDefault(curve, new LinkedList<>()); + keys.add(key); + curves.putIfAbsent(curve, keys); + } + for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curves.entrySet()) { + EC_Curve curve = e.getKey(); + List<EC_Key.Public> keys = e.getValue(); + + doTest(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS)); + doTest(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS)); + doTest(CommandTest.expect(new Command.Generate(this.card, ECTesterApplet.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS)); + List<Test> ecdhTests = new LinkedList<>(); + for (EC_Key.Public pub : keys) { + Command ecdhCommand = new Command.ECDH_direct(this.card, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten()); + ecdhTests.add(CommandTest.expect(ecdhCommand, Result.ExpectedValue.FAILURE, "Card correctly rejected point on twist.", "Card incorrectly accepted point on twist.")); + } + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Twist test of " + curve.getId(), ecdhTests.toArray(new Test[0]))); + new Command.Cleanup(this.card).send(); + } + } +} diff --git a/src/cz/crcs/ectester/reader/test/CardWrongCurvesSuite.java b/src/cz/crcs/ectester/reader/test/CardWrongCurvesSuite.java new file mode 100644 index 0000000..cac8fab --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CardWrongCurvesSuite.java @@ -0,0 +1,58 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.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.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.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardWrongCurvesSuite extends CardTestSuite { + + public CardWrongCurvesSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "wrong", "The wrong curve suite run 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(); + if (curve.getBits() != cfg.bits && !cfg.all) { + continue; + } + if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { + continue; + } + Test key = doTest(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS)); + if (!key.ok()) { + continue; + } + Test set = runTest(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS)); + Test generate = runTest(CommandTest.expect(new Command.Generate(this.card, ECTesterApplet.KEYPAIR_BOTH), Result.ExpectedValue.SUCCESS)); + doTest(CompoundTest.any(Result.ExpectedValue.FAILURE, "Set wrong curve and generate keypairs, should fail." ,set, generate)); + + for (byte kaType : EC_Consts.KA_TYPES) { + 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, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, kaType), Result.ExpectedValue.FAILURE)); + doTest(CompoundTest.any(Result.ExpectedValue.FAILURE, "Allocate and perform KA, should fail.", allocate, ka)); + } + } + } + } +} diff --git a/src/cz/crcs/ectester/reader/test/CommandTest.java b/src/cz/crcs/ectester/reader/test/CommandTest.java new file mode 100644 index 0000000..a08d820 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CommandTest.java @@ -0,0 +1,76 @@ +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.reader.command.Command; +import cz.crcs.ectester.reader.response.Response; + +/** + * A simple test that runs one Command to get and evaluate one Response + * to get a Result and compare it with the expected one. + */ +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) { + Response resp = commandTestable.getResponse(); + Result.Value resultValue = Result.Value.fromExpected(expected, resp.successful(), resp.error()); + return new Result(resultValue, 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 Command getCommand() { + return testable.getCommand(); + } + + public Response getResponse() { + return testable.getResponse(); + } + + @Override + public void run() throws TestException { + if (hasRun) + return; + + testable.run(); + result = callback.apply(testable); + hasRun = true; + } + + @Override + public String getDescription() { + if (hasRun) { + return testable.getResponse().getDescription(); + } else { + return testable.getCommand().toString(); + } + } +} diff --git a/src/cz/crcs/ectester/reader/test/CommandTestable.java b/src/cz/crcs/ectester/reader/test/CommandTestable.java new file mode 100644 index 0000000..3bb55bf --- /dev/null +++ b/src/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() throws TestException { + 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/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java b/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java deleted file mode 100644 index 8e7ca31..0000000 --- a/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java +++ /dev/null @@ -1,53 +0,0 @@ -package cz.crcs.ectester.reader.test; - -import cz.crcs.ectester.applet.ECTesterApplet; -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.data.EC_Store; -import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; -import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.EC_Curve; -import cz.crcs.ectester.reader.ec.EC_Key; -import javacard.security.KeyPair; - -import java.util.Map; - -import static cz.crcs.ectester.reader.test.Result.ExpectedValue; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class CompositeCurvesSuite extends TestSuite { - - public CompositeCurvesSuite(EC_Store dataStore, ECTester.Config cfg) { - super(dataStore, cfg, "composite", "The composite suite tests ECDH over curves with composite order. This should generally fail, as using such a curve is unsafe."); - } - - @Override - public void setup(CardMngr cardManager) { - /* Do the default tests with the public keys set to provided smallorder keys - * over composite order curves. Essentially small subgroup attacks. - * These should fail, the curves aren't safe so that if the computation with - * a small order public key succeeds the private key modulo the public key order - * is revealed. - */ - Map<String, EC_Key> keys = dataStore.getObjects(EC_Key.class, "composite"); - for (EC_Key key : keys.values()) { - EC_Curve curve = dataStore.getObject(EC_Curve.class, key.getCurve()); - if (cfg.namedCurve != null && !(key.getCurve().startsWith(cfg.namedCurve) || key.getCurve().equals(cfg.namedCurve))) { - continue; - } - if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { - continue; - } - if ((curve.getBits() == cfg.bits || cfg.all)) { - tests.add(new Test.Simple(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); - tests.add(new Test.Simple(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.ANY)); - tests.add(new Test.Simple(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL), ExpectedValue.ANY)); - Command ecdhCommand = new Command.ECDH_direct(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH, key.flatten()); - tests.add(new Test.Simple(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(new Test.Simple(new Command.Cleanup(cardManager), ExpectedValue.ANY)); - } - } - } -} diff --git a/src/cz/crcs/ectester/reader/test/DefaultSuite.java b/src/cz/crcs/ectester/reader/test/DefaultSuite.java deleted file mode 100644 index 736b7c5..0000000 --- a/src/cz/crcs/ectester/reader/test/DefaultSuite.java +++ /dev/null @@ -1,69 +0,0 @@ -package cz.crcs.ectester.reader.test; - -import cz.crcs.ectester.applet.ECTesterApplet; -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.data.EC_Store; -import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; -import cz.crcs.ectester.reader.command.Command; -import javacard.security.KeyPair; - -import java.io.IOException; - -import static cz.crcs.ectester.reader.test.Result.ExpectedValue; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class DefaultSuite extends TestSuite { - - public DefaultSuite(EC_Store dataStore, ECTester.Config cfg) { - super(dataStore, cfg, "default", "The default test suite tests basic support of ECDH and ECDSA."); - } - - @Override - public void setup(CardMngr cardManager) throws IOException { - tests.add(new Test.Simple(new Command.Support(cardManager), ExpectedValue.ANY)); - if (cfg.namedCurve != null) { - String desc = "Default tests over the " + cfg.namedCurve + " curve category."; - if (cfg.primeField) { - tests.addAll(defaultCategoryTests(cardManager, cfg.namedCurve, KeyPair.ALG_EC_FP, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.ANY, ExpectedValue.SUCCESS, desc)); - } - if (cfg.binaryField) { - tests.addAll(defaultCategoryTests(cardManager, cfg.namedCurve, KeyPair.ALG_EC_F2M, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.ANY, ExpectedValue.SUCCESS, desc)); - } - } else { - if (cfg.all) { - if (cfg.primeField) { - //iterate over prime curve sizes used: EC_Consts.FP_SIZES - for (short keyLength : EC_Consts.FP_SIZES) { - defaultTests(cardManager, keyLength, KeyPair.ALG_EC_FP); - } - } - if (cfg.binaryField) { - //iterate over binary curve sizes used: EC_Consts.F2M_SIZES - for (short keyLength : EC_Consts.F2M_SIZES) { - defaultTests(cardManager, keyLength, KeyPair.ALG_EC_F2M); - } - } - } else { - if (cfg.primeField) { - defaultTests(cardManager, (short) cfg.bits, KeyPair.ALG_EC_FP); - } - - if (cfg.binaryField) { - defaultTests(cardManager, (short) cfg.bits, KeyPair.ALG_EC_F2M); - } - } - } - } - - private void defaultTests(CardMngr cardManager, short keyLength, byte keyType) throws IOException { - tests.add(new Test.Simple(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, keyLength, keyType), ExpectedValue.SUCCESS)); - Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, keyLength, keyType); - if (curve != null) - tests.add(new Test.Simple(curve, ExpectedValue.SUCCESS)); - tests.add(defaultCurveTests(cardManager, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.ANY, ExpectedValue.SUCCESS, "Default tests.")); - tests.add(new Test.Simple(new Command.Cleanup(cardManager), ExpectedValue.ANY)); - } -} diff --git a/src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java b/src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java deleted file mode 100644 index f61b695..0000000 --- a/src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java +++ /dev/null @@ -1,68 +0,0 @@ -package cz.crcs.ectester.reader.test; - -import cz.crcs.ectester.applet.ECTesterApplet; -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.data.EC_Store; -import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; -import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.EC_Curve; -import cz.crcs.ectester.reader.ec.EC_Key; -import javacard.security.KeyPair; - -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import static cz.crcs.ectester.reader.test.Result.ExpectedValue; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class InvalidCurvesSuite extends TestSuite { - - public InvalidCurvesSuite(EC_Store dataStore, ECTester.Config cfg) { - super(dataStore, cfg, "invalid", "The invalid curve suite tests whether the card rejects points outside of the curve during ECDH."); - } - - @Override - public void setup(CardMngr cardManager) throws IOException { - /* Set original curves (secg/nist/brainpool). Generate local. - * Try ECDH with invalid public keys of increasing (or decreasing) order. - */ - Map<String, EC_Key.Public> pubkeys = dataStore.getObjects(EC_Key.Public.class, "invalid"); - Map<EC_Curve, List<EC_Key.Public>> curves = new HashMap<>(); - for (EC_Key.Public key : pubkeys.values()) { - EC_Curve curve = dataStore.getObject(EC_Curve.class, key.getCurve()); - if (cfg.namedCurve != null && !(key.getCurve().startsWith(cfg.namedCurve) || key.getCurve().equals(cfg.namedCurve))) { - continue; - } - if (curve.getBits() != cfg.bits && !cfg.all) { - continue; - } - if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { - continue; - } - List<EC_Key.Public> keys = curves.getOrDefault(curve, new LinkedList<>()); - keys.add(key); - curves.putIfAbsent(curve, keys); - } - for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curves.entrySet()) { - EC_Curve curve = e.getKey(); - List<EC_Key.Public> keys = e.getValue(); - - tests.add(new Test.Simple(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); - tests.add(new Test.Simple(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS)); - tests.add(new Test.Simple(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL), ExpectedValue.SUCCESS)); - List<Test> ecdhTests = new LinkedList<>(); - for (EC_Key.Public pub : keys) { - Command ecdhCommand = new Command.ECDH_direct(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ANY, pub.flatten()); - ecdhTests.add(new Test.Simple(ecdhCommand, ExpectedValue.FAILURE, "Card correctly rejected point on invalid curve." , "Card incorrectly accepted point on invalid curve.")); - } - tests.add(Test.Compound.all(ExpectedValue.SUCCESS, "Invalid curve test of " + curve.getId(), ecdhTests.toArray(new Test[0]))); - tests.add(new Test.Simple(new Command.Cleanup(cardManager), ExpectedValue.ANY)); - } - } -} diff --git a/src/cz/crcs/ectester/reader/test/PerformanceTest.java b/src/cz/crcs/ectester/reader/test/PerformanceTest.java new file mode 100644 index 0000000..4a27bad --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/PerformanceTest.java @@ -0,0 +1,103 @@ +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.reader.command.Command; + +import java.util.Arrays; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class PerformanceTest extends SimpleTest<CommandTestable> { + private long[] times; + private long mean; + private long median; + private long mode; + private int count; + + private PerformanceTest(CommandTestable testable, int count) { + super(testable, new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable testable) { + return new Result(Result.Value.SUCCESS); + } + }); + this.count = count; + } + + public static PerformanceTest repeat(Command cmd, int count) { + return new PerformanceTest(new CommandTestable(cmd), count); + } + + @Override + public String getDescription() { + return String.format("Mean = %d ns, Median = %d ns, Mode = %d ns", mean, median, mode); + } + + @Override + public void run() throws TestException { + if (hasRun) + return; + + times = new long[count]; + for (int i = 0; i < count; ++i) { + testable.run(); + times[i] = testable.getResponse().getDuration(); + testable.reset(); + } + + mean = Arrays.stream(times).sum() / count; + + long[] sorted = times.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; + } + } + hasRun = true; + result = callback.apply(testable); + } + + public long getCount() { + return count; + } + + public Command getCommand() { + return testable.getCommand(); + } + + public long[] getTimes() { + return times; + } + + public long getMean() { + return mean; + } + + public long getMedian() { + return median; + } + + public long getMode() { + return mode; + } +} diff --git a/src/cz/crcs/ectester/reader/test/Result.java b/src/cz/crcs/ectester/reader/test/Result.java deleted file mode 100644 index 82f0f32..0000000 --- a/src/cz/crcs/ectester/reader/test/Result.java +++ /dev/null @@ -1,94 +0,0 @@ -package cz.crcs.ectester.reader.test; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class Result { - - private Value value; - private String cause; - - public Result(Value value) { - this.value = value; - } - - public Result(Value value, String cause) { - this(value); - this.cause = cause; - } - - public Value getValue() { - return value; - } - - public String getCause() { - return cause; - } - - public boolean ok() { - return value.ok(); - } - - public boolean compareTo(Result other) { - if (other == null) { - return false; - } - return value == other.value; - } - - public boolean compareTo(Value other) { - if (other == null) { - return false; - } - return value == other; - } - - /** - * - */ - public enum Value { - SUCCESS(true), - FAILURE(false), - UXSUCCESS(false), - XFAILURE(true), - ERROR(false); - - private boolean ok; - - Value(boolean ok) { - this.ok = ok; - } - - public static Value fromExpected(ExpectedValue expected, boolean successful) { - switch (expected) { - case SUCCESS: - return successful ? SUCCESS : FAILURE; - case FAILURE: - return successful ? UXSUCCESS : XFAILURE; - case ANY: - return SUCCESS; - } - return SUCCESS; - } - - public static Value fromExpected(ExpectedValue expected, boolean successful, boolean error) { - if (error) { - return ERROR; - } - return fromExpected(expected, successful); - } - - public boolean ok() { - return ok; - } - } - - /** - * - */ - public enum ExpectedValue { - SUCCESS, - FAILURE, - ANY - } -} diff --git a/src/cz/crcs/ectester/reader/test/Test.java b/src/cz/crcs/ectester/reader/test/Test.java deleted file mode 100644 index 022ad56..0000000 --- a/src/cz/crcs/ectester/reader/test/Test.java +++ /dev/null @@ -1,217 +0,0 @@ -package cz.crcs.ectester.reader.test; - -import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.response.Response; - -import javax.smartcardio.CardException; -import java.util.function.BiFunction; -import java.util.function.Function; - -import static cz.crcs.ectester.reader.test.Result.ExpectedValue; -import static cz.crcs.ectester.reader.test.Result.Value; - -/** - * An abstract test that can be run and has a Result. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public abstract class Test { - boolean hasRun = false; - Result result; - - public Result getResult() { - if (!hasRun) { - return null; - } - return result; - } - - public Value getResultValue() { - if (!hasRun) { - return null; - } - return result.getValue(); - } - - public String getResultCause() { - if (!hasRun) { - return null; - } - return result.getCause(); - } - - public boolean ok() { - if (!hasRun) { - return true; - } - return result.ok(); - } - - public abstract String getDescription(); - - public boolean hasRun() { - return hasRun; - } - - public abstract void run() throws CardException; - - /** - * A simple test that runs one Command to get and evaluate one Response - * to get a Result and compare it with the expected one. - */ - public static class Simple extends Test { - private BiFunction<Command, Response, Result> callback; - private Command command; - private Response response; - - public Simple(Command command, BiFunction<Command, Response, Result> callback) { - this.command = command; - this.callback = callback; - } - - public Simple(Command command, ExpectedValue expected, String ok, String nok) { - this(command, (cmd, resp) -> { - Value resultValue = Value.fromExpected(expected, resp.successful(), resp.error()); - return new Result(resultValue, resultValue.ok() ? ok : nok); - }); - } - - public Simple(Command command, ExpectedValue expected) { - this(command, expected, null, null); - } - - public Command getCommand() { - return command; - } - - public Response getResponse() { - return response; - } - - @Override - public void run() throws CardException { - if (hasRun) - return; - - response = command.send(); - if (callback != null) { - result = callback.apply(command, response); - } else { - if (response.successful()) { - result = new Result(Value.SUCCESS); - } else { - result = new Result(Value.FAILURE); - } - } - hasRun = true; - } - - @Override - public String getDescription() { - return response.getDescription(); - } - } - - /** - * A compound test that runs many Tests and has a Result dependent on all/some of their Results. - */ - public static class Compound extends Test { - private Function<Test[], Result> callback; - private Test[] tests; - private String description; - - private Compound(Function<Test[], Result> callback, Test... tests) { - this.callback = callback; - this.tests = tests; - } - - private Compound(Function<Test[], Result> callback, String descripiton, Test... tests) { - this(callback, tests); - this.description = descripiton; - } - - public static Compound function(Function<Test[], Result> callback, Test... tests) { - return new Compound(callback, tests); - } - - public static Compound function(Function<Test[], Result> callback, String description, Test... tests) { - return new Compound(callback, description, tests); - } - - public static Compound all(ExpectedValue what, Test... all) { - return new Compound((tests) -> { - for (Test test : tests) { - if (!Value.fromExpected(what, test.ok()).ok()) { - return new Result(Value.FAILURE, "At least one of the sub-tests did not have the expected result."); - } - } - return new Result(Value.SUCCESS, "All sub-tests had the expected result."); - }, all); - } - - public static Compound all(ExpectedValue what, String description, Test... all) { - Compound result = Compound.all(what, all); - result.setDescription(description); - return result; - } - - public static Compound any(ExpectedValue what, Test... any) { - return new Compound((tests) -> { - for (Test test : tests) { - if (Value.fromExpected(what, test.ok()).ok()) { - return new Result(Value.SUCCESS, "At least one of the sub-tests did have the expected result."); - } - } - return new Result(Value.FAILURE, "None of the sub-tests had the expected result."); - }, any); - } - - public static Compound any(ExpectedValue what, String description, Test... any) { - Compound result = Compound.any(what, any); - result.setDescription(description); - return result; - } - - public static Compound mask(ExpectedValue[] results, Test... masked) { - return new Compound((tests) -> { - for (int i = 0; i < results.length; ++i) { - if (!Value.fromExpected(results[i], tests[i].ok()).ok()) { - return new Result(Value.FAILURE, "At least one of the sub-tests did not match the result mask."); - } - } - return new Result(Value.SUCCESS, "All sub-tests matched the expected mask."); - }, masked); - } - - public static Compound mask(ExpectedValue[] results, String description, Test... masked) { - Compound result = Compound.mask(results, masked); - result.setDescription(description); - return result; - } - - public Test[] getTests() { - return tests; - } - - @Override - public void run() throws CardException { - if (hasRun) - return; - - for (Test test : tests) { - test.run(); - } - result = callback.apply(tests); - this.hasRun = true; - } - - public void setDescription(String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } - } -} diff --git a/src/cz/crcs/ectester/reader/test/TestRunner.java b/src/cz/crcs/ectester/reader/test/TestRunner.java deleted file mode 100644 index baab6a8..0000000 --- a/src/cz/crcs/ectester/reader/test/TestRunner.java +++ /dev/null @@ -1,29 +0,0 @@ -package cz.crcs.ectester.reader.test; - -import cz.crcs.ectester.reader.output.TestWriter; - -import javax.smartcardio.CardException; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class TestRunner { - private TestSuite suite; - private TestWriter writer; - - public TestRunner(TestSuite suite, TestWriter writer) { - this.suite = suite; - this.writer = writer; - } - - public void run() throws CardException { - writer.begin(suite); - for (Test t : suite.getTests()) { - if (!t.hasRun()) { - t.run(); - writer.outputTest(t); - } - } - writer.end(); - } -} diff --git a/src/cz/crcs/ectester/reader/test/TestSuite.java b/src/cz/crcs/ectester/reader/test/TestSuite.java deleted file mode 100644 index f13317c..0000000 --- a/src/cz/crcs/ectester/reader/test/TestSuite.java +++ /dev/null @@ -1,135 +0,0 @@ -package cz.crcs.ectester.reader.test; - -import cz.crcs.ectester.applet.ECTesterApplet; -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.data.EC_Store; -import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; -import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.EC_Curve; - -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import static cz.crcs.ectester.reader.test.Result.ExpectedValue; -import static cz.crcs.ectester.reader.test.Result.Value; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public abstract class TestSuite { - EC_Store dataStore; - ECTester.Config cfg; - String name; - String description; - List<Test> tests = new LinkedList<>(); - - TestSuite(EC_Store dataStore, ECTester.Config cfg, String name, String description) { - this.dataStore = dataStore; - this.cfg = cfg; - this.name = name; - this.description = description; - } - - public abstract void setup(CardMngr cardManager) throws IOException; - - public List<Test> getTests() { - return Collections.unmodifiableList(tests); - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - /** - * @param cardManager cardManager to send APDU through - * @param generateExpected expected result of the Generate command - * @param ecdhExpected expected result of the ordinary ECDH command - * @param ecdhCompressExpected expected result of ECDH with a compressed point - * @param ecdsaExpected expected result of the ordinary ECDSA command - * @param description compound test description - * @return test to run - */ - Test defaultCurveTests(CardMngr cardManager, ExpectedValue generateExpected, ExpectedValue ecdhExpected, ExpectedValue ecdhCompressExpected, ExpectedValue ecdsaExpected, String description) { - List<Test> tests = new LinkedList<>(); - - tests.add(new Test.Simple(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH), generateExpected)); - tests.add(new Test.Simple(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH), ecdhExpected)); - tests.add(new Test.Simple(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_COMPRESS, EC_Consts.KA_ECDH), ecdhExpected)); - tests.add(new Test.Simple(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_ONE, EC_Consts.KA_ECDH), ExpectedValue.FAILURE)); - tests.add(new Test.Simple(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_ZERO, EC_Consts.KA_ECDH), ExpectedValue.FAILURE)); - tests.add(new Test.Simple(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_MAX, EC_Consts.KA_ECDH), ExpectedValue.FAILURE)); - tests.add(new Test.Simple(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_FULLRANDOM, EC_Consts.KA_ECDH), ExpectedValue.FAILURE)); - tests.add(new Test.Simple(new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, null), ecdsaExpected)); - - return Test.Compound.function((testArray) -> { - Function<ExpectedValue, String> shouldHave = (expected) -> { - switch (expected) { - case SUCCESS: - return "succeeded"; - case FAILURE: - return "failed"; - case ANY: - default: - return ""; - } - }; - - for (int i = 0; i < testArray.length; ++i) { - Test t = testArray[i]; - if (!t.ok()) { - if (i == 0) { // generate - return new Result(Value.FAILURE, "The generation of a key should have " + shouldHave.apply(generateExpected) + ", but it did not."); - } else if (i == 2) { // ecdh compress - return new Result(Value.FAILURE, "The ECDH should have " + shouldHave.apply(ecdhExpected) + ", but it did not."); - } else if (i == 1) { // ecdh normal - return new Result(Value.FAILURE, "The ECDH of a compressed point should have " + shouldHave.apply(ecdhCompressExpected) + ", but it did not."); - } else if (i <= 6) { // ecdh wrong, should fail - return new Result(Value.FAILURE, "The ECDH of a corrupted point should have failed, but it did not."); - } else { // ecdsa - return new Result(Value.FAILURE, "The ECDSA should have " + shouldHave.apply(ecdsaExpected) + ", but it did not."); - } - } - } - return new Result(Value.SUCCESS); - }, description, tests.toArray(new Test[0])); - } - - /** - * @param cardManager cardManager to send APDU through - * @param category category to test - * @param field field to test (KeyPair.ALG_EC_FP || KeyPair.ALG_EC_F2M) - * @param setExpected expected result of the Set (curve) command - * @param generateExpected expected result of the Generate command - * @param ecdhExpected expected result of the ordinary ECDH command - * @param ecdhCompressedExpected expected result of the ECDH command with a compressed point. - * @param ecdsaExpected expected result of the ordinary ECDSA command - * @param description compound test description - * @return tests to run - */ - List<Test> defaultCategoryTests(CardMngr cardManager, String category, byte field, ExpectedValue setExpected, ExpectedValue generateExpected, ExpectedValue ecdhExpected, ExpectedValue ecdhCompressedExpected, ExpectedValue ecdsaExpected, String description) { - List<Test> tests = new LinkedList<>(); - Map<String, EC_Curve> curves = dataStore.getObjects(EC_Curve.class, category); - if (curves == null) - return tests; - for (Map.Entry<String, EC_Curve> entry : curves.entrySet()) { - EC_Curve curve = entry.getValue(); - if (curve.getField() == field && (curve.getBits() == cfg.bits || cfg.all)) { - tests.add(new Test.Simple(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), field), ExpectedValue.SUCCESS)); - tests.add(new Test.Simple(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), setExpected)); - tests.add(defaultCurveTests(cardManager, generateExpected, ecdhExpected, ecdhCompressedExpected, ecdsaExpected, description)); - tests.add(new Test.Simple(new Command.Cleanup(cardManager), ExpectedValue.ANY)); - } - } - - return tests; - } -} diff --git a/src/cz/crcs/ectester/reader/test/TestVectorSuite.java b/src/cz/crcs/ectester/reader/test/TestVectorSuite.java deleted file mode 100644 index ff46feb..0000000 --- a/src/cz/crcs/ectester/reader/test/TestVectorSuite.java +++ /dev/null @@ -1,83 +0,0 @@ -package cz.crcs.ectester.reader.test; - -import cz.crcs.ectester.applet.ECTesterApplet; -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.data.EC_Store; -import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; -import cz.crcs.ectester.reader.Util; -import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.*; -import cz.crcs.ectester.reader.response.Response; -import javacard.security.KeyPair; - -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import static cz.crcs.ectester.reader.test.Result.ExpectedValue; -import static cz.crcs.ectester.reader.test.Result.Value; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class TestVectorSuite extends TestSuite { - - public TestVectorSuite(EC_Store dataStore, ECTester.Config cfg) { - super(dataStore, cfg, "test", "The test-vectors suite contains a collection of test vectors which test basic ECDH correctness."); - } - - @Override - public void setup(CardMngr cardManager) throws IOException { - /* Set original curves (secg/nist/brainpool). Set keypairs from test vectors. - * Do ECDH both ways, export and verify that the result is correct. - */ - Map<String, EC_KAResult> results = dataStore.getObjects(EC_KAResult.class, "test"); - for (EC_KAResult result : results.values()) { - EC_Curve curve = dataStore.getObject(EC_Curve.class, result.getCurve()); - if (cfg.namedCurve != null && !(result.getCurve().startsWith(cfg.namedCurve) || result.getCurve().equals(cfg.namedCurve))) { - continue; - } - if (curve.getBits() != cfg.bits && !cfg.all) { - continue; - } - if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { - continue; - } - EC_Params onekey = dataStore.getObject(EC_Keypair.class, result.getOneKey()); - if (onekey == null) { - onekey = dataStore.getObject(EC_Key.Private.class, result.getOneKey()); - } - EC_Params otherkey = dataStore.getObject(EC_Keypair.class, result.getOtherKey()); - if (otherkey == null) { - otherkey = dataStore.getObject(EC_Key.Public.class, result.getOtherKey()); - } - if (onekey == null || otherkey == null) { - throw new IOException("Test vector keys couldn't be located."); - } - List<Test> testVector = new LinkedList<>(); - - testVector.add(new Test.Simple(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); - testVector.add(new Test.Simple(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS)); - //tests.add(new Test.Simple(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH), ExpectedValue.SUCCESS)); - testVector.add(new Test.Simple(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, onekey.flatten(EC_Consts.PARAMETER_S)), ExpectedValue.SUCCESS)); - testVector.add(new Test.Simple(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, otherkey.flatten(EC_Consts.PARAMETER_W)), ExpectedValue.SUCCESS)); - testVector.add(new Test.Simple(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, result.getKA()), (command, response) -> { - Response.ECDH dh = (Response.ECDH) response; - 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 (!Util.compareBytes(dh.getSecret(), 0, result.getParam(0), 0, dh.secretLength())) { - int firstDiff = Util.diffBytes(dh.getSecret(), 0, result.getParam(0), 0, dh.secretLength()); - return new Result(Value.FAILURE, "ECDH derived secret does not match the test, first difference was at byte " + String.valueOf(firstDiff) + "."); - } - return new Result(Value.SUCCESS); - })); - tests.add(Test.Compound.all(ExpectedValue.SUCCESS, "Test vector " + result.getId(), testVector.toArray(new Test[0]))); - tests.add(new Test.Simple(new Command.Cleanup(cardManager), ExpectedValue.ANY)); - - } - } -} diff --git a/src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java b/src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java deleted file mode 100644 index e9389b4..0000000 --- a/src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java +++ /dev/null @@ -1,34 +0,0 @@ -package cz.crcs.ectester.reader.test; - -import cz.crcs.ectester.data.EC_Store; -import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; -import javacard.security.KeyPair; - -import java.io.IOException; - -import static cz.crcs.ectester.reader.test.Result.ExpectedValue; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class WrongCurvesSuite extends TestSuite { - - public WrongCurvesSuite(EC_Store dataStore, ECTester.Config cfg) { - super(dataStore, cfg, "wrong", "The wrong curve suite tests whether the card rejects domain parameters which are not curves."); - } - - @Override - public void setup(CardMngr cardManager) throws IOException { - /* Just do the default tests on the wrong curves. - * These should generally fail, the curves aren't curves. - */ - String desc = "Default tests over wrong curve params."; - if (cfg.primeField) { - tests.addAll(defaultCategoryTests(cardManager, cfg.testSuite, KeyPair.ALG_EC_FP, ExpectedValue.FAILURE, ExpectedValue.FAILURE, ExpectedValue.FAILURE, ExpectedValue.FAILURE, ExpectedValue.FAILURE, desc)); - } - if (cfg.binaryField) { - tests.addAll(defaultCategoryTests(cardManager, cfg.testSuite, KeyPair.ALG_EC_F2M, ExpectedValue.FAILURE, ExpectedValue.FAILURE, ExpectedValue.FAILURE, ExpectedValue.FAILURE, ExpectedValue.FAILURE, desc)); - } - } -} |
