diff options
| author | J08nY | 2017-11-10 12:56:47 +0100 |
|---|---|---|
| committer | J08nY | 2017-11-10 12:56:47 +0100 |
| commit | 588aa02a37ffd6e0ee2d164540023d1692109e89 (patch) | |
| tree | 609505ab9461fa341bb468caf092af6a563dcc61 /src/cz/crcs/ectester/reader | |
| parent | 631edcfe145a558dd645dae5d83f3825a74471b1 (diff) | |
| parent | 4248ae9d9e3f6b79c9874c49ba901fba0f332211 (diff) | |
| download | ECTester-588aa02a37ffd6e0ee2d164540023d1692109e89.tar.gz ECTester-588aa02a37ffd6e0ee2d164540023d1692109e89.tar.zst ECTester-588aa02a37ffd6e0ee2d164540023d1692109e89.zip | |
Merge branch 'devel'
Diffstat (limited to '')
30 files changed, 1487 insertions, 590 deletions
diff --git a/src/cz/crcs/ectester/reader/DirtyLogger.java b/src/cz/crcs/ectester/reader/DirtyLogger.java deleted file mode 100644 index 7a2c70d..0000000 --- a/src/cz/crcs/ectester/reader/DirtyLogger.java +++ /dev/null @@ -1,56 +0,0 @@ -package cz.crcs.ectester.reader; - -import java.io.FileWriter; -import java.io.IOException; -import java.io.OutputStream; - -/** - * @author Petr Svenda petr@svenda.com - * @author Jan Jancar johny@neuromancer.sk - */ -public class DirtyLogger { - FileWriter log; - boolean systemOut; - - public DirtyLogger(String filePath) throws IOException { - this(filePath, true); - } - - public DirtyLogger(String filePath, boolean systemOut) throws IOException { - if (filePath != null) - this.log = new FileWriter(filePath); - this.systemOut = systemOut; - } - - public void println() { - print("\n"); - } - - public void println(String logLine) { - logLine += "\n"; - print(logLine); - } - - public void print(String logLine) { - if (systemOut) { - System.out.print(logLine); - } - if (log != null) { - try { - log.write(logLine); - } catch (IOException ignored) { - } - } - } - - void flush() { - try { - if (log != null) log.flush(); - } catch (IOException ignored) { - } - } - - void close() throws IOException { - if (log != null) log.close(); - } -} diff --git a/src/cz/crcs/ectester/reader/ECTester.java b/src/cz/crcs/ectester/reader/ECTester.java index bb555f9..3de6094 100644 --- a/src/cz/crcs/ectester/reader/ECTester.java +++ b/src/cz/crcs/ectester/reader/ECTester.java @@ -22,20 +22,26 @@ package cz.crcs.ectester.reader; import cz.crcs.ectester.applet.ECTesterApplet; -import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH; import cz.crcs.ectester.applet.EC_Consts; 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.response.Response; +import cz.crcs.ectester.reader.test.*; import javacard.security.KeyPair; import org.apache.commons.cli.*; import javax.smartcardio.CardException; +import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.nio.file.Files; import java.util.*; +import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH; + /** * Reader part of ECTester, a tool for testing Elliptic curve support on javacards. * @@ -45,7 +51,9 @@ import java.util.*; public class ECTester { private CardMngr cardManager; - private DirtyLogger systemOutLogger; + private OutputLogger logger; + private TestWriter testWriter; + private ResponseWriter respWriter; private EC_Store dataStore; private Config cfg; @@ -98,7 +106,25 @@ public class ECTester { cardManager.send(SELECT_ECTESTERAPPLET); } - systemOutLogger = new DirtyLogger(cfg.log, true); + // 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 if (cli.hasOption("export")) { @@ -115,7 +141,7 @@ public class ECTester { //disconnect cardManager.disconnectFromCard(); - systemOutLogger.close(); + logger.close(); } catch (MissingOptionException moex) { System.err.println("Missing required options, one of:"); @@ -154,11 +180,13 @@ public class ECTester { } catch (ParseException | IOException ex) { System.err.println(ex.getMessage()); } catch (CardException ex) { - if (systemOutLogger != null) - systemOutLogger.println(ex.getMessage()); + if (logger != null) + logger.println(ex.getMessage()); + } catch (ParserConfigurationException e) { + e.printStackTrace(); } finally { - if (systemOutLogger != null) - systemOutLogger.flush(); + if (logger != null) + logger.flush(); } } @@ -204,11 +232,13 @@ public class ECTester { * * -i / --input <input_file> * -o / --output <output_file> + * --format <format> * -l / --log [log_file] * * -f / --fresh * -s / --simulate * -y / --yes + * -ka/ --ka-type <type> */ OptionGroup actions = new OptionGroup(); actions.setRequired(true); @@ -216,11 +246,11 @@ 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- nonprime:\n- test-vectors:").hasArg().argName("test_suite").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("dsa").longOpt("ecdsa").desc("Sign data with ECDSA, [count] times.").hasArg().argName("count").optionalArg(true).build()); - + opts.addOptionGroup(actions); OptionGroup size = new OptionGroup(); @@ -256,6 +286,7 @@ public class ECTester { opts.addOption(Option.builder("o").longOpt("output").desc("Output into file <output_file>.").hasArg().argName("output_file").build()); opts.addOption(Option.builder("l").longOpt("log").desc("Log output into file [log_file].").hasArg().argName("log_file").optionalArg(true).build()); opts.addOption(Option.builder("v").longOpt("verbose").desc("Turn on verbose logging.").build()); + opts.addOption(Option.builder().longOpt("format").desc("Output format to use. One of: text,yml,xml.").hasArg().argName("format").build()); opts.addOption(Option.builder("f").longOpt("fresh").desc("Generate fresh keys (set domain parameters before every generation).").build()); opts.addOption(Option.builder("s").longOpt("simulate").desc("Simulate a card with jcardsim instead of using a terminal.").build()); @@ -325,7 +356,9 @@ public class ECTester { } sent.add(export); - systemOutLogger.println(Response.toString(sent)); + for (Response r : sent) { + respWriter.outputResponse(r); + } EC_Params exported = new EC_Params(domain, export.getParams()); @@ -354,7 +387,7 @@ public class ECTester { while (generated < cfg.generateAmount || cfg.generateAmount == 0) { if ((cfg.fresh || generated == 0) && curve != null) { Response fresh = curve.send(); - systemOutLogger.println(fresh.toString()); + respWriter.outputResponse(fresh); } Command.Generate generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL); @@ -372,7 +405,7 @@ public class ECTester { break; } } - systemOutLogger.println(response.toString()); + 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); @@ -382,7 +415,7 @@ public class ECTester { generated++; } Response cleanup = new Command.Cleanup(cardManager).send(); - systemOutLogger.println(cleanup.toString()); + respWriter.outputResponse(cleanup); keysFile.close(); } @@ -398,10 +431,10 @@ public class ECTester { switch (cfg.testSuite) { case "default": - suite = new TestSuite.Default(dataStore, cfg, systemOutLogger); + suite = new DefaultSuite(dataStore, cfg); break; case "test-vectors": - suite = new TestSuite.TestVectors(dataStore, cfg, systemOutLogger); + suite = new TestVectorSuite(dataStore, cfg); break; default: // These tests are dangerous, prompt before them. @@ -410,8 +443,8 @@ public class ECTester { if (!cfg.yes) { System.out.print("Do you want to proceed? (y/n): "); Scanner in = new Scanner(System.in); - String confirmation = in.nextLine(); - if (!Arrays.asList("yes", "YES", "y", "Y").contains(confirmation)) { + String confirmation = in.nextLine().toLowerCase(); + if (!Arrays.asList("yes", "y").contains(confirmation)) { return; } in.close(); @@ -420,13 +453,13 @@ public class ECTester { switch (cfg.testSuite) { case "wrong": - suite = new TestSuite.Wrong(dataStore, cfg, systemOutLogger); + suite = new WrongCurvesSuite(dataStore, cfg); break; - case "nonprime": - suite = new TestSuite.NonPrime(dataStore, cfg, systemOutLogger); + case "composite": + suite = new CompositeCurvesSuite(dataStore, cfg); break; case "invalid": - suite = new TestSuite.Invalid(dataStore, cfg, systemOutLogger); + suite = new InvalidCurvesSuite(dataStore, cfg); break; default: System.err.println("Unknown test suite."); @@ -434,7 +467,10 @@ public class ECTester { } break; } - suite.run(cardManager); + + TestRunner runner = new TestRunner(suite, testWriter); + suite.setup(cardManager); + runner.run(); } /** @@ -452,7 +488,9 @@ public class ECTester { if (curve != null) prepare.add(curve.send()); - systemOutLogger.println(Response.toString(prepare)); + for (Response r : prepare) { + respWriter.outputResponse(r); + } byte pubkey = (cfg.anyPublicKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL; byte privkey = (cfg.anyPrivateKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL; @@ -476,7 +514,9 @@ public class ECTester { Response.ECDH perform = new Command.ECDH(cardManager, pubkey, privkey, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, cfg.ECDHKA).send(); ecdh.add(perform); - systemOutLogger.println(Response.toString(ecdh)); + for (Response r : ecdh) { + respWriter.outputResponse(r); + } if (!perform.successful() || !perform.hasSecret()) { if (retry < 10) { @@ -495,7 +535,7 @@ public class ECTester { ++done; } Response cleanup = new Command.Cleanup(cardManager).send(); - systemOutLogger.println(cleanup.toString()); + respWriter.outputResponse(cleanup); if (out != null) out.close(); @@ -533,7 +573,9 @@ public class ECTester { if (curve != null) prepare.add(curve.send()); - systemOutLogger.println(Response.toString(prepare)); + for (Response r : prepare) { + respWriter.outputResponse(r); + } FileWriter out = null; if (cfg.output != null) { @@ -549,7 +591,9 @@ public class ECTester { Response.ECDSA perform = new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, data).send(); ecdsa.add(perform); - systemOutLogger.println(Response.toString(ecdsa)); + for (Response r : ecdsa) { + respWriter.outputResponse(r); + } if (!perform.successful() || !perform.hasSignature()) { if (retry < 10) { @@ -568,7 +612,7 @@ public class ECTester { ++done; } Response cleanup = new Command.Cleanup(cardManager).send(); - systemOutLogger.println(cleanup.toString()); + respWriter.outputResponse(cleanup); if (out != null) out.close(); @@ -614,8 +658,9 @@ public class ECTester { public boolean fresh = false; public boolean simulate = false; public boolean yes = false; + public String format; - //Action-related ions + //Action-related options public String listNamed; public String testSuite; public int generateAmount; @@ -669,6 +714,13 @@ public class ECTester { return true; } + format = cli.getOptionValue("format", "text"); + String formats[] = new String[]{"text", "xml", "yaml", "yml"}; + if (!Arrays.asList(formats).contains(format)) { + System.err.println("Wrong output format " + format + ". Should be one of " + Arrays.toString(formats)); + return false; + } + if ((key != null || namedKey != null) && (anyPublicKey || anyPrivateKey)) { System.err.print("Can only specify the whole key with --key/--named-key or pubkey and privkey with --public/--named-public and --private/--named-private."); return false; @@ -739,10 +791,9 @@ public class ECTester { } testSuite = cli.getOptionValue("test", "default").toLowerCase(); - String[] tests = new String[]{"default", "nonprime", "invalid", "test-vectors", "wrong"}; - List<String> testsList = Arrays.asList(tests); - if (!testsList.contains(testSuite)) { - System.err.println("Unknown test case. Should be one of: " + Arrays.toString(tests)); + String[] tests = new String[]{"default", "composite", "invalid", "test-vectors", "wrong"}; + if (!Arrays.asList(tests).contains(testSuite)) { + System.err.println("Unknown test suite " + testSuite + ". Should be one of: " + Arrays.toString(tests)); return false; } diff --git a/src/cz/crcs/ectester/reader/Test.java b/src/cz/crcs/ectester/reader/Test.java deleted file mode 100644 index 157e360..0000000 --- a/src/cz/crcs/ectester/reader/Test.java +++ /dev/null @@ -1,82 +0,0 @@ -package cz.crcs.ectester.reader; - -import javax.smartcardio.CardException; -import java.util.function.BiFunction; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public class Test { - private boolean hasRun = false; - private BiFunction<Command, Response, Result> callback; - private Result result; - private Result expected; - private Command command; - private Response response; - - public Test(Command command, Result expected) { - this.command = command; - this.expected = expected; - } - - public Test(Command command, Result expected, BiFunction<Command, Response, Result> callback) { - this(command, expected); - this.callback = callback; - } - - public Command getCommand() { - return command; - } - - public Response getResponse() { - return response; - } - - public Result getResult() { - if (!hasRun) { - return null; - } - return result; - } - - public Result getExpected() { - return expected; - } - - public boolean ok() { - return result == expected || expected == Result.ANY; - } - - public void run() throws CardException { - response = command.send(); - if (callback != null) { - result = callback.apply(command, response); - } else { - if (response.successful()) { - result = Result.SUCCESS; - } else { - result = Result.FAILURE; - } - } - hasRun = true; - } - - public boolean hasRun() { - return hasRun; - } - - @Override - public String toString() { - if (hasRun) { - return (ok() ? "OK " : "NOK") + " " + response.toString(); - } else { - return ""; - } - } - - public enum Result { - SUCCESS, - FAILURE, - ANY - } -} diff --git a/src/cz/crcs/ectester/reader/TestSuite.java b/src/cz/crcs/ectester/reader/TestSuite.java deleted file mode 100644 index 7118dd8..0000000 --- a/src/cz/crcs/ectester/reader/TestSuite.java +++ /dev/null @@ -1,314 +0,0 @@ -package cz.crcs.ectester.reader; - -import cz.crcs.ectester.applet.ECTesterApplet; -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.data.EC_Store; -import cz.crcs.ectester.reader.ec.*; -import javacard.security.KeyPair; - -import javax.smartcardio.CardException; -import java.io.IOException; -import java.util.*; - -/** - * @author Jan Jancar johny@neuromancer.sk - */ -public abstract class TestSuite { - - EC_Store dataStore; - ECTester.Config cfg; - DirtyLogger systemOut; - String name; - List<Test> tests = new LinkedList<>(); - - TestSuite(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut, String name) { - this.dataStore = dataStore; - this.cfg = cfg; - this.systemOut = systemOut; - this.name = name; - } - - public List<Test> run(CardMngr cardManager) throws CardException, IOException { - for (Test t : tests) { - if (!t.hasRun()) { - t.run(); - systemOut.println(t.toString()); - } - } - return tests; - } - - public List<Test> getTests() { - return Collections.unmodifiableList(tests); - } - - public String getName() { - return name; - } - - /** - * @param cardManager cardManager to send APDU through - * @param generateExpected expected result of the Generate command - * @param ecdhExpected expected result of the ordinary ECDH command - * @param ecdsaExpected expected result of the ordinary ECDSA command - * @return tests to run - */ - List<Test> testCurve(CardMngr cardManager, Test.Result generateExpected, Test.Result ecdhExpected, Test.Result ecdsaExpected) { - List<Test> tests = new LinkedList<>(); - - tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH), generateExpected)); - tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH), ecdhExpected)); - tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_COMPRESS, EC_Consts.KA_ECDH), ecdhExpected)); - tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_ONE, EC_Consts.KA_ECDH), Test.Result.FAILURE)); - tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_ZERO, EC_Consts.KA_ECDH), Test.Result.FAILURE)); - tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_MAX, EC_Consts.KA_ECDH), Test.Result.FAILURE)); - tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_FULLRANDOM, EC_Consts.KA_ECDH), Test.Result.FAILURE)); - tests.add(new Test(new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, null), ecdsaExpected)); - - return tests; - } - - /** - * @param cardManager cardManager to send APDU through - * @param category category to test - * @param field field to test (KeyPair.ALG_EC_FP || KeyPair.ALG_EC_F2M) - * @param setExpected expected result of the Set (curve) command - * @param generateExpected expected result of the Generate command - * @param ecdhExpected expected result of the ordinary ECDH command - * @param ecdsaExpected expected result of the ordinary ECDSA command - * @return tests to run - */ - List<Test> testCategory(CardMngr cardManager, String category, byte field, Test.Result setExpected, Test.Result generateExpected, Test.Result ecdhExpected, Test.Result ecdsaExpected) { - List<Test> tests = new LinkedList<>(); - Map<String, EC_Curve> curves = dataStore.getObjects(EC_Curve.class, category); - if (curves == null) - return tests; - for (Map.Entry<String, EC_Curve> entry : curves.entrySet()) { - EC_Curve curve = entry.getValue(); - if (curve.getField() == field && (curve.getBits() == cfg.bits || cfg.all)) { - tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), field), Test.Result.SUCCESS)); - tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), setExpected)); - tests.addAll(testCurve(cardManager, generateExpected, ecdhExpected, ecdsaExpected)); - tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); - } - } - - return tests; - } - - public static class Default extends TestSuite { - - public Default(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { - super(dataStore, cfg, systemOut, "default"); - } - - @Override - public List<Test> run(CardMngr cardManager) throws IOException, CardException { - tests.add(new Test(new Command.Support(cardManager), Test.Result.ANY)); - if (cfg.namedCurve != null) { - if (cfg.primeField) { - tests.addAll(testCategory(cardManager, cfg.namedCurve, KeyPair.ALG_EC_FP, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS)); - } - if (cfg.binaryField) { - tests.addAll(testCategory(cardManager, cfg.namedCurve, KeyPair.ALG_EC_F2M, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS)); - } - } else { - if (cfg.all) { - if (cfg.primeField) { - //iterate over prime curve sizes used: EC_Consts.FP_SIZES - for (short keyLength : EC_Consts.FP_SIZES) { - defaultTests(cardManager, keyLength, KeyPair.ALG_EC_FP); - } - } - if (cfg.binaryField) { - //iterate over binary curve sizes used: EC_Consts.F2M_SIZES - for (short keyLength : EC_Consts.F2M_SIZES) { - defaultTests(cardManager, keyLength, KeyPair.ALG_EC_F2M); - } - } - } else { - if (cfg.primeField) { - defaultTests(cardManager, (short) cfg.bits, KeyPair.ALG_EC_FP); - } - - if (cfg.binaryField) { - defaultTests(cardManager, (short) cfg.bits, KeyPair.ALG_EC_F2M); - } - } - } - return super.run(cardManager); - } - - private void defaultTests(CardMngr cardManager, short keyLength, byte keyType) throws IOException { - tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, keyLength, keyType), Test.Result.SUCCESS)); - Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, keyLength, keyType); - if (curve != null) - tests.add(new Test(curve, Test.Result.SUCCESS)); - tests.addAll(testCurve(cardManager, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS)); - tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); - } - } - - public static class TestVectors extends TestSuite { - - public TestVectors(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { - super(dataStore, cfg, systemOut, "test"); - } - - @Override - public List<Test> run(CardMngr cardManager) throws IOException, CardException { - /* Set original curves (secg/nist/brainpool). Set keypairs from test vectors. - * Do ECDH both ways, export and verify that the result is correct. - */ - Map<String, EC_KAResult> results = dataStore.getObjects(EC_KAResult.class, "test"); - for (EC_KAResult result : results.values()) { - EC_Curve curve = dataStore.getObject(EC_Curve.class, result.getCurve()); - if (cfg.namedCurve != null && !(result.getCurve().startsWith(cfg.namedCurve) || result.getCurve().equals(cfg.namedCurve))) { - continue; - } - if (curve.getBits() != cfg.bits && !cfg.all) { - continue; - } - if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { - continue; - } - EC_Params onekey = dataStore.getObject(EC_Keypair.class, result.getOneKey()); - if (onekey == null) { - onekey = dataStore.getObject(EC_Key.Private.class, result.getOneKey()); - } - EC_Params otherkey = dataStore.getObject(EC_Keypair.class, result.getOtherKey()); - if (otherkey == null) { - otherkey = dataStore.getObject(EC_Key.Public.class, result.getOtherKey()); - } - if (onekey == null || otherkey == null) { - throw new IOException("Test vector keys couldn't be located."); - } - - tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS)); - tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.SUCCESS)); - //tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH), Test.Result.SUCCESS)); - tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, onekey.flatten(EC_Consts.PARAMETER_S)), Test.Result.SUCCESS)); - tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, otherkey.flatten(EC_Consts.PARAMETER_W)), Test.Result.SUCCESS)); - tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, result.getKA()), Test.Result.SUCCESS, (command, response) -> { - Response.ECDH dh = (Response.ECDH) response; - if (!dh.successful() || !dh.hasSecret()) - return Test.Result.FAILURE; - if (!Util.compareBytes(dh.getSecret(), 0, result.getParam(0), 0, dh.secretLength())) { - return Test.Result.FAILURE; - } - return Test.Result.SUCCESS; - })); - tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); - - } - return super.run(cardManager); - } - } - - public static class NonPrime extends TestSuite { - - public NonPrime(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { - super(dataStore, cfg, systemOut, "nonprime"); - } - - @Override - public List<Test> run(CardMngr cardManager) throws IOException, CardException { - /* Do the default tests with the public keys set to provided smallorder keys - * over non-prime order curves. Essentially small subgroup attacks. - * These should fail, the curves aren't safe so that if the computation with - * a small order public key succeeds the private key modulo the public key order - * is revealed. - */ - Map<String, EC_Key> keys = dataStore.getObjects(EC_Key.class, "nonprime"); - for (EC_Key key : keys.values()) { - EC_Curve curve = dataStore.getObject(EC_Curve.class, key.getCurve()); - if (cfg.namedCurve != null && !(key.getCurve().startsWith(cfg.namedCurve) || key.getCurve().equals(cfg.namedCurve))) { - continue; - } - if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { - continue; - } - if ((curve.getBits() == cfg.bits || cfg.all)) { - tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS)); - tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.ANY)); - tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL), Test.Result.ANY)); - - //tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, key.getParams(), key.flatten()), Test.Result.ANY)); - //tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH), Test.Result.FAILURE)); - tests.add(new Test(new Command.ECDH_direct(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH, key.flatten()), Test.Result.FAILURE)); - - tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); - } - } - return super.run(cardManager); - } - } - - public static class Invalid extends TestSuite { - - public Invalid(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { - super(dataStore, cfg, systemOut, "invalid"); - } - - @Override - public List<Test> run(CardMngr cardManager) throws CardException, IOException { - /* Set original curves (secg/nist/brainpool). Generate local. - * Try ECDH with invalid public keys of increasing (or decreasing) order. - */ - Map<String, EC_Key.Public> pubkeys = dataStore.getObjects(EC_Key.Public.class, "invalid"); - Map<EC_Curve, List<EC_Key.Public>> curves = new HashMap<>(); - for (EC_Key.Public key : pubkeys.values()) { - EC_Curve curve = dataStore.getObject(EC_Curve.class, key.getCurve()); - if (cfg.namedCurve != null && !(key.getCurve().startsWith(cfg.namedCurve) || key.getCurve().equals(cfg.namedCurve))) { - continue; - } - if (curve.getBits() != cfg.bits && !cfg.all) { - continue; - } - if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { - continue; - } - List<EC_Key.Public> keys = curves.getOrDefault(curve, new LinkedList<>()); - keys.add(key); - curves.putIfAbsent(curve, keys); - } - for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curves.entrySet()) { - EC_Curve curve = e.getKey(); - List<EC_Key.Public> keys = e.getValue(); - - tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS)); - tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.SUCCESS)); - tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL), Test.Result.SUCCESS)); - for (EC_Key.Public pub : keys) { - // tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Test.Result.ANY)); - // tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ANY), Test.Result.FAILURE)); - tests.add(new Test(new Command.ECDH_direct(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ANY, pub.flatten()), Test.Result.FAILURE)); - } - tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); - } - - return super.run(cardManager); - } - } - - public static class Wrong extends TestSuite { - - public Wrong(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { - super(dataStore, cfg, systemOut, "wrong"); - } - - @Override - public List<Test> run(CardMngr cardManager) throws CardException, IOException { - /* Just do the default tests on the wrong curves. - * These should generally fail, the curves aren't curves. - */ - if (cfg.primeField) { - tests.addAll(testCategory(cardManager, cfg.testSuite, KeyPair.ALG_EC_FP, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE)); - } - if (cfg.binaryField) { - tests.addAll(testCategory(cardManager, cfg.testSuite, KeyPair.ALG_EC_F2M, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE)); - } - return super.run(cardManager); - } - } -} diff --git a/src/cz/crcs/ectester/reader/Util.java b/src/cz/crcs/ectester/reader/Util.java index 754cda3..4e1154b 100644 --- a/src/cz/crcs/ectester/reader/Util.java +++ b/src/cz/crcs/ectester/reader/Util.java @@ -28,15 +28,19 @@ public class Util { array[offset] = (byte) ((value >> 8) & 0xFF); } - public static boolean compareBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) { + 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 false; + return i; } } - return true; + 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) { @@ -283,10 +287,10 @@ public class Util { public static String getSWString(short sw) { if (sw == ISO7816.SW_NO_ERROR) { - return "OK\t(0x9000)"; + return "OK (0x9000)"; } else { String str = getSW(sw); - return String.format("fail\t(%s, 0x%04x)", str, sw); + return String.format("fail (%s, 0x%04x)", str, sw); } } diff --git a/src/cz/crcs/ectester/reader/Command.java b/src/cz/crcs/ectester/reader/command/Command.java index 8cb3a30..3c11456 100644 --- a/src/cz/crcs/ectester/reader/Command.java +++ b/src/cz/crcs/ectester/reader/command/Command.java @@ -1,8 +1,12 @@ -package cz.crcs.ectester.reader; +package cz.crcs.ectester.reader.command; 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.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; @@ -160,7 +164,7 @@ public abstract class Command { priv.readCSV(in); in.close(); } else { - priv = dataStore.getObject(EC_Key.Public.class, cfg.namedPrivateKey); + priv = dataStore.getObject(EC_Key.Private.class, cfg.namedPrivateKey); if (priv == null) { priv = dataStore.getObject(EC_Keypair.class, cfg.namedPrivateKey); } @@ -192,7 +196,7 @@ public abstract class Command { * @param keyLength key length to set * @param keyClass key class to allocate */ - protected Allocate(CardMngr cardManager, byte keyPair, short keyLength, byte keyClass) { + public Allocate(CardMngr cardManager, byte keyPair, short keyLength, byte keyClass) { super(cardManager); this.keyPair = keyPair; this.keyLength = keyLength; @@ -223,7 +227,7 @@ public abstract class Command { * @param cardManager cardManager to send APDU through * @param kaType which type of KeyAgreement to use */ - protected AllocateKeyAgreement(CardMngr cardManager, byte kaType) { + public AllocateKeyAgreement(CardMngr cardManager, byte kaType) { super(cardManager); this.kaType = kaType; byte[] data = new byte[]{kaType}; @@ -249,7 +253,7 @@ public abstract class Command { * @param cardManager cardManager to send APDU through * @param keyPair which keyPair clear, local/remote (KEYPAIR_* || ...) */ - protected Clear(CardMngr cardManager, byte keyPair) { + public Clear(CardMngr cardManager, byte keyPair) { super(cardManager); this.keyPair = keyPair; @@ -283,7 +287,7 @@ public abstract class Command { * @param params parameters to set (EC_Consts.PARAMETER_* | ...) * @param external external curve data, can be null */ - protected Set(CardMngr cardManager, byte keyPair, byte curve, short params, byte[] external) { + public Set(CardMngr cardManager, byte keyPair, byte curve, short params, byte[] external) { super(cardManager); this.keyPair = keyPair; this.curve = curve; @@ -325,7 +329,7 @@ public abstract class Command { * @param params parameters to corrupt (EC_Consts.PARAMETER_* | ...) * @param corruption corruption type (EC_Consts.CORRUPTION_*) */ - protected Corrupt(CardMngr cardManager, byte keyPair, byte key, short params, byte corruption) { + public Corrupt(CardMngr cardManager, byte keyPair, byte key, short params, byte corruption) { super(cardManager); this.keyPair = keyPair; this.key = key; @@ -360,7 +364,7 @@ public abstract class Command { * @param cardManager cardManager to send APDU through * @param keyPair which keyPair to generate, local/remote (KEYPAIR_* || ...) */ - protected Generate(CardMngr cardManager, byte keyPair) { + public Generate(CardMngr cardManager, byte keyPair) { super(cardManager); this.keyPair = keyPair; @@ -392,7 +396,7 @@ public abstract class Command { * @param key key to export from (EC_Consts.KEY_* | ...) * @param params params to export (EC_Consts.PARAMETER_* | ...) */ - protected Export(CardMngr cardManager, byte keyPair, byte key, short params) { + public Export(CardMngr cardManager, byte keyPair, byte key, short params) { super(cardManager); this.keyPair = keyPair; this.key = key; @@ -433,7 +437,7 @@ public abstract class Command { * @param corruption whether to invalidate the pubkey before ECDH (EC_Consts.CORRUPTION_* | ...) * @param type ECDH algorithm type (EC_Consts.KA_* | ...) */ - protected ECDH(CardMngr cardManager, byte pubkey, byte privkey, byte export, short corruption, byte type) { + public ECDH(CardMngr cardManager, byte pubkey, byte privkey, byte export, short corruption, byte type) { super(cardManager); this.pubkey = pubkey; this.privkey = privkey; @@ -466,7 +470,17 @@ public abstract class Command { private byte type; private byte[] pubkey; - protected ECDH_direct(CardMngr cardManager, byte privkey, byte export, short corruption, byte type, byte[] pubkey) { + /** + * Creates the INS_ECDH_DIRECT instruction. + * + * @param cardManager cardManager to send APDU through + * @param privkey keyPair to use for private key, (KEYPAIR_LOCAL || KEYPAIR_REMOTE) + * @param export whether to export ECDH secret + * @param corruption whether to invalidate the pubkey before ECDH (EC_Consts.CORRUPTION_* | ...) + * @param type ECDH algorithm type (EC_Consts.KA_* | ...) + * @param pubkey pubkey data to do ECDH with. + */ + public ECDH_direct(CardMngr cardManager, byte privkey, byte export, short corruption, byte type, byte[] pubkey) { super(cardManager); this.privkey = privkey; this.export = export; @@ -504,7 +518,7 @@ public abstract class Command { * @param export whether to export ECDSA signature * @param raw data to sign, can be null, in which case random data is signed. */ - protected ECDSA(CardMngr cardManager, byte keyPair, byte export, byte[] raw) { + public ECDSA(CardMngr cardManager, byte keyPair, byte export, byte[] raw) { super(cardManager); this.keyPair = keyPair; this.export = export; @@ -537,7 +551,7 @@ public abstract class Command { /** * @param cardManager cardManager to send APDU through */ - protected Cleanup(CardMngr cardManager) { + public Cleanup(CardMngr cardManager) { super(cardManager); this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_CLEANUP, 0, 0); @@ -560,7 +574,7 @@ public abstract class Command { /** * @param cardManager cardManager to send APDU through */ - protected Support(CardMngr cardManager) { + public Support(CardMngr cardManager) { super(cardManager); this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_SUPPORT, 0, 0); diff --git a/src/cz/crcs/ectester/reader/ec/EC_Category.java b/src/cz/crcs/ectester/reader/ec/EC_Category.java index 97dd1b4..41cbad8 100644 --- a/src/cz/crcs/ectester/reader/ec/EC_Category.java +++ b/src/cz/crcs/ectester/reader/ec/EC_Category.java @@ -6,6 +6,8 @@ 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 { diff --git a/src/cz/crcs/ectester/reader/ec/EC_Curve.java b/src/cz/crcs/ectester/reader/ec/EC_Curve.java index 45080fb..cb4a2df 100644 --- a/src/cz/crcs/ectester/reader/ec/EC_Curve.java +++ b/src/cz/crcs/ectester/reader/ec/EC_Curve.java @@ -4,6 +4,8 @@ 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 { diff --git a/src/cz/crcs/ectester/reader/ec/EC_Data.java b/src/cz/crcs/ectester/reader/ec/EC_Data.java index 9dcbbe0..0ceddef 100644 --- a/src/cz/crcs/ectester/reader/ec/EC_Data.java +++ b/src/cz/crcs/ectester/reader/ec/EC_Data.java @@ -7,6 +7,10 @@ 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 { diff --git a/src/cz/crcs/ectester/reader/ec/EC_KAResult.java b/src/cz/crcs/ectester/reader/ec/EC_KAResult.java index 28115f7..4a67bbe 100644 --- a/src/cz/crcs/ectester/reader/ec/EC_KAResult.java +++ b/src/cz/crcs/ectester/reader/ec/EC_KAResult.java @@ -3,6 +3,8 @@ 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 { diff --git a/src/cz/crcs/ectester/reader/ec/EC_Key.java b/src/cz/crcs/ectester/reader/ec/EC_Key.java index cecd228..5077d5b 100644 --- a/src/cz/crcs/ectester/reader/ec/EC_Key.java +++ b/src/cz/crcs/ectester/reader/ec/EC_Key.java @@ -3,6 +3,8 @@ 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 { @@ -20,11 +22,6 @@ public class EC_Key extends EC_Params { this.desc = desc; } - private EC_Key(String id, short mask, String curve) { - this(mask, curve); - this.id = id; - } - private EC_Key(String id, short mask, String curve, String desc) { this(mask, curve, desc); this.id = id; @@ -38,6 +35,9 @@ public class EC_Key extends EC_Params { return desc; } + /** + * An EC public key, contains the W parameter. + */ public static class Public extends EC_Key { public Public(String curve) { @@ -48,12 +48,19 @@ public class EC_Key extends EC_Params { 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 "EC Public key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc()); + 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) { @@ -64,6 +71,10 @@ public class EC_Key extends EC_Params { 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 index 924906e..2643346 100644 --- a/src/cz/crcs/ectester/reader/ec/EC_Keypair.java +++ b/src/cz/crcs/ectester/reader/ec/EC_Keypair.java @@ -3,6 +3,8 @@ 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 { @@ -19,6 +21,11 @@ public class EC_Keypair extends EC_Params { this.desc = desc; } + public EC_Keypair(String id, String curve, String desc) { + this(curve, desc); + this.id = id; + } + public String getCurve() { return curve; } diff --git a/src/cz/crcs/ectester/reader/ec/EC_Params.java b/src/cz/crcs/ectester/reader/ec/EC_Params.java index ea2e633..6fb164b 100644 --- a/src/cz/crcs/ectester/reader/ec/EC_Params.java +++ b/src/cz/crcs/ectester/reader/ec/EC_Params.java @@ -8,6 +8,11 @@ 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 { diff --git a/src/cz/crcs/ectester/reader/output/OutputLogger.java b/src/cz/crcs/ectester/reader/output/OutputLogger.java new file mode 100644 index 0000000..bf47a1f --- /dev/null +++ b/src/cz/crcs/ectester/reader/output/OutputLogger.java @@ -0,0 +1,60 @@ +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 new file mode 100644 index 0000000..c357233 --- /dev/null +++ b/src/cz/crcs/ectester/reader/output/ResponseWriter.java @@ -0,0 +1,39 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.reader.Util; +import cz.crcs.ectester.reader.response.Response; + +import java.io.PrintStream; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class ResponseWriter { + private PrintStream output; + + public ResponseWriter(PrintStream output) { + this.output = output; + } + + public String responseSuffix(Response r) { + StringBuilder suffix = new StringBuilder(); + for (int j = 0; j < r.getNumSW(); ++j) { + short sw = r.getSW(j); + if (sw != 0) { + suffix.append(" ").append(Util.getSWString(sw)); + } + } + if (suffix.length() == 0) { + suffix.append(" [").append(Util.getSW(r.getNaturalSW())).append("]"); + } + return String.format("%4d ms ┃ %s", r.getDuration() / 1000000, suffix); + } + + public void outputResponse(Response r) { + String out = ""; + out += String.format("%-70s", r.getDescription()) + " ┃ "; + out += responseSuffix(r); + output.println(out); + output.flush(); + } +} diff --git a/src/cz/crcs/ectester/reader/output/TeeOutputStream.java b/src/cz/crcs/ectester/reader/output/TeeOutputStream.java new file mode 100644 index 0000000..2a1af99 --- /dev/null +++ b/src/cz/crcs/ectester/reader/output/TeeOutputStream.java @@ -0,0 +1,36 @@ +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 new file mode 100644 index 0000000..74c76fb --- /dev/null +++ b/src/cz/crcs/ectester/reader/output/TestWriter.java @@ -0,0 +1,15 @@ +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 new file mode 100644 index 0000000..bcebcd5 --- /dev/null +++ b/src/cz/crcs/ectester/reader/output/TextTestWriter.java @@ -0,0 +1,85 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.reader.test.Test; +import cz.crcs.ectester.reader.test.TestSuite; + +import java.io.PrintStream; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class TextTestWriter implements TestWriter { + private PrintStream output; + private ResponseWriter respWriter; + + public static int BASE_WIDTH = 76; + + public TextTestWriter(PrintStream output) { + this.output = output; + this.respWriter = 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; + } + + 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()); + } + } + } + + return out.toString(); + } + + @Override + public void outputTest(Test t) { + if (!t.hasRun()) + return; + output.println(testString(t, 0)); + output.flush(); + } + + @Override + public void end() { + } +} diff --git a/src/cz/crcs/ectester/reader/output/XMLTestWriter.java b/src/cz/crcs/ectester/reader/output/XMLTestWriter.java new file mode 100644 index 0000000..beb758c --- /dev/null +++ b/src/cz/crcs/ectester/reader/output/XMLTestWriter.java @@ -0,0 +1,145 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.reader.Util; +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 org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +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; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class XMLTestWriter implements TestWriter { + private OutputStream output; + private DocumentBuilder db; + private Document doc; + private Node root; + + 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); + } + + private Element commandElement(Command c) { + Element commandElem = doc.createElement("command"); + + Element apdu = doc.createElement("apdu"); + apdu.setTextContent(Util.bytesToHex(c.getAPDU().getBytes())); + commandElem.appendChild(apdu); + + return commandElem; + } + + private Element responseElement(Response r) { + Element responseElem = doc.createElement("response"); + responseElem.setAttribute("successful", r.successful() ? "true" : "false"); + + Element apdu = doc.createElement("apdu"); + apdu.setTextContent(Util.bytesToHex(r.getAPDU().getBytes())); + responseElem.appendChild(apdu); + + Element naturalSW = doc.createElement("natural-sw"); + naturalSW.setTextContent(String.valueOf(Short.toUnsignedInt(r.getNaturalSW()))); + responseElem.appendChild(naturalSW); + + Element sws = doc.createElement("sws"); + for (int i = 0; i < r.getNumSW(); ++i) { + Element sw = doc.createElement("sw"); + sw.setTextContent(String.valueOf(Short.toUnsignedInt(r.getSW(i)))); + sws.appendChild(sw); + } + responseElem.appendChild(sws); + + Element duration = doc.createElement("duration"); + duration.setTextContent(String.valueOf(r.getDuration())); + responseElem.appendChild(duration); + + Element description = doc.createElement("desc"); + description.setTextContent(r.getDescription()); + responseElem.appendChild(description); + + return responseElem; + } + + private Element testElement(Test t) { + Element testElem = doc.createElement("test"); + + 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)); + } + } + + 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; + } + + @Override + public void outputTest(Test t) { + if (!t.hasRun()) + return; + root.appendChild(testElement(t)); + } + + @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(); + } + } +}
\ 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 new file mode 100644 index 0000000..3b2b72d --- /dev/null +++ b/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java @@ -0,0 +1,113 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.reader.Util; +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 java.io.PrintStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +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 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); + } + + private Map<String, Object> commandObject(Command c) { + Map<String, Object> commandObj = new HashMap<>(); + commandObj.put("apdu", Util.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("natural_sw", Short.toUnsignedInt(r.getNaturalSW())); + List<Integer> sws = new LinkedList<>(); + for (int i = 0; i < r.getNumSW(); ++i) { + sws.add(Short.toUnsignedInt(r.getSW(i))); + } + responseObj.put("sws", sws); + responseObj.put("duration", r.getDuration()); + responseObj.put("desc", r.getDescription()); + return responseObj; + } + + 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); + } + + 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; + } + + @Override + public void outputTest(Test t) { + if (!t.hasRun()) + return; + tests.add(testObject(t)); + } + + @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("---"); + } +} diff --git a/src/cz/crcs/ectester/reader/Response.java b/src/cz/crcs/ectester/reader/response/Response.java index 3df956e..4abfd14 100644 --- a/src/cz/crcs/ectester/reader/Response.java +++ b/src/cz/crcs/ectester/reader/response/Response.java @@ -1,13 +1,13 @@ -package cz.crcs.ectester.reader; +package cz.crcs.ectester.reader.response; import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.reader.ec.EC_Curve; +import cz.crcs.ectester.reader.Util; +import cz.crcs.ectester.reader.ec.EC_Data; import javacard.framework.ISO7816; import javacard.security.KeyPair; import javax.smartcardio.ResponseAPDU; -import java.util.List; /** * @author Jan Jancar johny@neuromancer.sk @@ -19,15 +19,15 @@ public abstract class Response { private short[] sws; private int numSW = 0; private byte[][] params; - //TODO replace params with EC_Data? private boolean success = true; + private boolean error = false; - protected Response(ResponseAPDU response, long time) { + public Response(ResponseAPDU response, long time) { this.resp = response; this.time = time; } - protected void parse(int numSW, int numParams) { + void parse(int numSW, int numParams) { this.numSW = numSW; this.sws = new short[numSW]; @@ -45,22 +45,28 @@ public abstract class Response { } } else { success = false; + error = true; } } - if ((short) resp.getSW() != ISO7816.SW_NO_ERROR) + if ((short) resp.getSW() != ISO7816.SW_NO_ERROR) { success = false; + error = true; + } + //try to parse numParams.. params = new byte[numParams][]; for (int i = 0; i < numParams; i++) { if (data.length - offset < 2) { success = false; + error = true; break; } short paramLength = Util.getShort(data, offset); offset += 2; if (data.length < offset + paramLength) { + error = true; success = false; break; } @@ -114,53 +120,19 @@ public abstract class Response { return this.success; } - @Override - public abstract String toString(); - - public String toString(String inner) { - StringBuilder suffix = new StringBuilder(); - for (int j = 0; j < getNumSW(); ++j) { - short sw = getSW(j); - if (sw != 0) { - suffix.append(" ").append(Util.getSWString(sw)); - } - } - if (suffix.length() == 0) { - suffix.append(" [").append(Util.getSW(getNaturalSW())).append("]"); - } - return String.format("%-62s:%4d ms : %s", inner, time / 1000000, suffix); - } - - public static String toString(List<Response> responses) { - return toString(responses, null); - } - - public static String toString(List<Response> responses, String prefix) { - if (prefix != null) - prefix += " | "; - StringBuilder out = new StringBuilder(); - for (int i = 0; i < responses.size(); ++i) { - Response r = responses.get(i); - - if (prefix != null) - out.append(prefix); - - String message = r.toString(); - out.append(message); - if (i < responses.size() - 1) { - out.append("\n"); - } - } - return out.toString(); + public boolean error() { + return this.error; } + public abstract String getDescription(); /** * */ public static class AllocateKeyAgreement extends Response { byte kaType; - protected AllocateKeyAgreement(ResponseAPDU response, long time, byte kaType) { + + public AllocateKeyAgreement(ResponseAPDU response, long time, byte kaType) { super(response, time); this.kaType = kaType; @@ -168,19 +140,19 @@ public abstract class Response { } @Override - public String toString() { - return super.toString(String.format("Allocate KeyAgreement(%s) object", Util.getKATypeString(this.kaType))); + public String getDescription() { + return String.format("Allocated KeyAgreement(%s) object", Util.getKATypeString(this.kaType)); } } - + public static class Allocate extends Response { private byte keyPair; private short keyLength; private byte keyClass; - protected Allocate(ResponseAPDU response, long time, byte keyPair, short keyLength, byte keyClass) { + public Allocate(ResponseAPDU response, long time, byte keyPair, short keyLength, byte keyClass) { super(response, time); this.keyPair = keyPair; this.keyLength = keyLength; @@ -193,7 +165,7 @@ public abstract class Response { } @Override - public String toString() { + public String getDescription() { String field = keyClass == KeyPair.ALG_EC_FP ? "ALG_EC_FP" : "ALG_EC_F2M"; String key; if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { @@ -201,7 +173,7 @@ public abstract class Response { } else { key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; } - return super.toString(String.format("Allocated %s %db %s", key, keyLength, field)); + return String.format("Allocated %s %db %s", key, keyLength, field); } } @@ -212,7 +184,7 @@ public abstract class Response { private byte keyPair; - protected Clear(ResponseAPDU response, long time, byte keyPair) { + public Clear(ResponseAPDU response, long time, byte keyPair) { super(response, time); this.keyPair = keyPair; @@ -223,14 +195,14 @@ public abstract class Response { } @Override - public String toString() { + public String getDescription() { String key; if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { key = "both keypairs"; } else { key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; } - return super.toString(String.format("Cleared %s", key)); + return String.format("Cleared %s", key); } } @@ -243,7 +215,7 @@ public abstract class Response { private byte curve; private short parameters; - protected Set(ResponseAPDU response, long time, byte keyPair, byte curve, short parameters) { + public Set(ResponseAPDU response, long time, byte keyPair, byte curve, short parameters) { super(response, time); this.keyPair = keyPair; this.curve = curve; @@ -257,7 +229,7 @@ public abstract class Response { } @Override - public String toString() { + public String getDescription() { String name; switch (curve) { case EC_Consts.CURVE_default: @@ -287,7 +259,7 @@ public abstract class Response { } else { pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; } - return super.toString(String.format("Set %s %s parameters on %s", name, what, pair)); + return String.format("Set %s %s parameters on %s", name, what, pair); } } @@ -302,7 +274,7 @@ public abstract class Response { private short params; private byte corruption; - protected Corrupt(ResponseAPDU response, long time, byte keyPair, byte key, short params, byte corruption) { + public Corrupt(ResponseAPDU response, long time, byte keyPair, byte key, short params, byte corruption) { super(response, time); this.keyPair = keyPair; this.key = key; @@ -317,7 +289,7 @@ public abstract class Response { } @Override - public String toString() { + public String getDescription() { String corrupt = Util.getCorruption(corruption); String pair; @@ -326,7 +298,7 @@ public abstract class Response { } else { pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; } - return super.toString(String.format("Corrupted params of %s, %s", pair, corrupt)); + return String.format("Corrupted params of %s, %s", pair, corrupt); } } @@ -337,7 +309,7 @@ public abstract class Response { private byte keyPair; - protected Generate(ResponseAPDU response, long time, byte keyPair) { + public Generate(ResponseAPDU response, long time, byte keyPair) { super(response, time); this.keyPair = keyPair; @@ -348,14 +320,14 @@ public abstract class Response { } @Override - public String toString() { + public String getDescription() { String key; if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { key = "both keypairs"; } else { key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; } - return super.toString(String.format("Generated %s", key)); + return String.format("Generated %s", key); } } @@ -369,7 +341,7 @@ public abstract class Response { private byte key; private short parameters; - protected Export(ResponseAPDU response, long time, byte keyPair, byte key, short parameters) { + public Export(ResponseAPDU response, long time, byte keyPair, byte key, short parameters) { super(response, time); this.keyPair = keyPair; this.key = key; @@ -452,7 +424,7 @@ public abstract class Response { } @Override - public String toString() { + public String getDescription() { String source; if (key == EC_Consts.KEY_BOTH) { source = "both keys"; @@ -465,7 +437,7 @@ public abstract class Response { } else { pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; } - return super.toString(String.format("Exported params from %s of %s", source, pair)); + return String.format("Exported params from %s of %s", source, pair); } } @@ -480,7 +452,7 @@ public abstract class Response { private short corruption; private byte type; - protected ECDH(ResponseAPDU response, long time, byte pubkey, byte privkey, byte export, short corruption, byte type) { + public ECDH(ResponseAPDU response, long time, byte pubkey, byte privkey, byte export, short corruption, byte type) { super(response, time); this.pubkey = pubkey; this.privkey = privkey; @@ -504,7 +476,7 @@ public abstract class Response { } @Override - public String toString() { + public String getDescription() { String algo = Util.getKA(type); String pub = pubkey == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote"; @@ -516,7 +488,7 @@ public abstract class Response { } else { validity = Util.getCorruption(corruption); } - return super.toString(String.format("%s of %s pubkey and %s privkey(%s point)", algo, pub, priv, validity)); + return String.format("%s of %s pubkey and %s privkey(%s point)", algo, pub, priv, validity); } } @@ -529,7 +501,7 @@ public abstract class Response { private byte export; private byte[] raw; - protected ECDSA(ResponseAPDU response, long time, byte keyPair, byte export, byte[] raw) { + public ECDSA(ResponseAPDU response, long time, byte keyPair, byte export, byte[] raw) { super(response, time); this.keyPair = keyPair; this.export = export; @@ -547,10 +519,10 @@ public abstract class Response { } @Override - public String toString() { + public String getDescription() { String key = keyPair == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote"; String data = raw == null ? "random" : "provided"; - return super.toString(String.format("ECDSA with %s keypair(%s data)", key, data)); + return String.format("ECDSA with %s keypair(%s data)", key, data); } } @@ -559,15 +531,15 @@ public abstract class Response { */ public static class Cleanup extends Response { - protected Cleanup(ResponseAPDU response, long time) { + public Cleanup(ResponseAPDU response, long time) { super(response, time); parse(1, 0); } @Override - public String toString() { - return super.toString("Requested JCSystem object deletion"); + public String getDescription() { + return "Requested JCSystem object deletion"; } } @@ -577,15 +549,15 @@ public abstract class Response { */ public static class Support extends Response { - protected Support(ResponseAPDU response, long time) { + public Support(ResponseAPDU response, long time) { super(response, time); parse(3, 0); } @Override - public String toString() { - return super.toString("Support of ECDH, ECDHC, ECDSA"); + public String getDescription() { + return "Support of ECDH, ECDHC, ECDSA allocation"; } } } diff --git a/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java b/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java new file mode 100644 index 0000000..8e7ca31 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java @@ -0,0 +1,53 @@ +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 new file mode 100644 index 0000000..736b7c5 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/DefaultSuite.java @@ -0,0 +1,69 @@ +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 new file mode 100644 index 0000000..f61b695 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java @@ -0,0 +1,68 @@ +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/Result.java b/src/cz/crcs/ectester/reader/test/Result.java new file mode 100644 index 0000000..82f0f32 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/Result.java @@ -0,0 +1,94 @@ +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 new file mode 100644 index 0000000..022ad56 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/Test.java @@ -0,0 +1,217 @@ +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 new file mode 100644 index 0000000..baab6a8 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/TestRunner.java @@ -0,0 +1,29 @@ +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 new file mode 100644 index 0000000..f13317c --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/TestSuite.java @@ -0,0 +1,135 @@ +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 new file mode 100644 index 0000000..ff46feb --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/TestVectorSuite.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.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 new file mode 100644 index 0000000..e9389b4 --- /dev/null +++ b/src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java @@ -0,0 +1,34 @@ +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)); + } + } +} |
