aboutsummaryrefslogtreecommitdiff
path: root/src/cz/crcs/ectester/reader
diff options
context:
space:
mode:
authorJ08nY2017-11-10 12:56:47 +0100
committerJ08nY2017-11-10 12:56:47 +0100
commit588aa02a37ffd6e0ee2d164540023d1692109e89 (patch)
tree609505ab9461fa341bb468caf092af6a563dcc61 /src/cz/crcs/ectester/reader
parent631edcfe145a558dd645dae5d83f3825a74471b1 (diff)
parent4248ae9d9e3f6b79c9874c49ba901fba0f332211 (diff)
downloadECTester-588aa02a37ffd6e0ee2d164540023d1692109e89.tar.gz
ECTester-588aa02a37ffd6e0ee2d164540023d1692109e89.tar.zst
ECTester-588aa02a37ffd6e0ee2d164540023d1692109e89.zip
Merge branch 'devel'
Diffstat (limited to '')
-rw-r--r--src/cz/crcs/ectester/reader/DirtyLogger.java56
-rw-r--r--src/cz/crcs/ectester/reader/ECTester.java119
-rw-r--r--src/cz/crcs/ectester/reader/Test.java82
-rw-r--r--src/cz/crcs/ectester/reader/TestSuite.java314
-rw-r--r--src/cz/crcs/ectester/reader/Util.java14
-rw-r--r--src/cz/crcs/ectester/reader/command/Command.java (renamed from src/cz/crcs/ectester/reader/Command.java)42
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Category.java2
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Curve.java2
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Data.java4
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_KAResult.java2
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Key.java23
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Keypair.java7
-rw-r--r--src/cz/crcs/ectester/reader/ec/EC_Params.java5
-rw-r--r--src/cz/crcs/ectester/reader/output/OutputLogger.java60
-rw-r--r--src/cz/crcs/ectester/reader/output/ResponseWriter.java39
-rw-r--r--src/cz/crcs/ectester/reader/output/TeeOutputStream.java36
-rw-r--r--src/cz/crcs/ectester/reader/output/TestWriter.java15
-rw-r--r--src/cz/crcs/ectester/reader/output/TextTestWriter.java85
-rw-r--r--src/cz/crcs/ectester/reader/output/XMLTestWriter.java145
-rw-r--r--src/cz/crcs/ectester/reader/output/YAMLTestWriter.java113
-rw-r--r--src/cz/crcs/ectester/reader/response/Response.java (renamed from src/cz/crcs/ectester/reader/Response.java)130
-rw-r--r--src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java53
-rw-r--r--src/cz/crcs/ectester/reader/test/DefaultSuite.java69
-rw-r--r--src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java68
-rw-r--r--src/cz/crcs/ectester/reader/test/Result.java94
-rw-r--r--src/cz/crcs/ectester/reader/test/Test.java217
-rw-r--r--src/cz/crcs/ectester/reader/test/TestRunner.java29
-rw-r--r--src/cz/crcs/ectester/reader/test/TestSuite.java135
-rw-r--r--src/cz/crcs/ectester/reader/test/TestVectorSuite.java83
-rw-r--r--src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java34
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));
+ }
+ }
+}