aboutsummaryrefslogtreecommitdiff
path: root/src/cz/crcs/ectester/common
diff options
context:
space:
mode:
authorJ08nY2017-12-12 12:32:25 +0100
committerJ08nY2017-12-12 12:32:25 +0100
commit4dbc748a207bcee2c8fbe22566646b27a9f61dc0 (patch)
treefdf069ac9a25ff82f9509826425bd4796c514da0 /src/cz/crcs/ectester/common
parente329190e496ecf847cfd7afa886ac08cacb2fc92 (diff)
parent9e8fbbc907f4157988ac425af4dce541f7e42328 (diff)
downloadECTester-4dbc748a207bcee2c8fbe22566646b27a9f61dc0.tar.gz
ECTester-4dbc748a207bcee2c8fbe22566646b27a9f61dc0.tar.zst
ECTester-4dbc748a207bcee2c8fbe22566646b27a9f61dc0.zip
Merge branch 'feature/standalone-lib-testing' into devel
Diffstat (limited to 'src/cz/crcs/ectester/common')
-rw-r--r--src/cz/crcs/ectester/common/cli/Argument.java29
-rw-r--r--src/cz/crcs/ectester/common/cli/CLITools.java140
-rw-r--r--src/cz/crcs/ectester/common/cli/ParserOptions.java38
-rw-r--r--src/cz/crcs/ectester/common/cli/TreeCommandLine.java178
-rw-r--r--src/cz/crcs/ectester/common/cli/TreeParser.java120
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Curve.java80
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Data.java31
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_KAResult.java4
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Params.java24
-rw-r--r--src/cz/crcs/ectester/common/output/TestWriter.java15
-rw-r--r--src/cz/crcs/ectester/common/output/TestableWriter.java65
-rw-r--r--src/cz/crcs/ectester/common/output/TextTestWriter.java79
-rw-r--r--src/cz/crcs/ectester/common/output/XMLTestWriter.java204
-rw-r--r--src/cz/crcs/ectester/common/output/YAMLTestWriter.java161
-rw-r--r--src/cz/crcs/ectester/common/test/BaseTestable.java36
-rw-r--r--src/cz/crcs/ectester/common/test/SimpleTest.java19
-rw-r--r--src/cz/crcs/ectester/common/test/Test.java18
-rw-r--r--src/cz/crcs/ectester/common/test/TestCallback.java12
-rw-r--r--src/cz/crcs/ectester/common/test/TestRunner.java27
-rw-r--r--src/cz/crcs/ectester/common/test/TestSuite.java36
-rw-r--r--src/cz/crcs/ectester/common/test/Testable.java23
-rw-r--r--src/cz/crcs/ectester/common/util/ByteUtil.java122
-rw-r--r--src/cz/crcs/ectester/common/util/CardUtil.java (renamed from src/cz/crcs/ectester/common/Util.java)120
-rw-r--r--src/cz/crcs/ectester/common/util/ECUtil.java172
24 files changed, 1611 insertions, 142 deletions
diff --git a/src/cz/crcs/ectester/common/cli/Argument.java b/src/cz/crcs/ectester/common/cli/Argument.java
new file mode 100644
index 0000000..e9b6688
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/Argument.java
@@ -0,0 +1,29 @@
+package cz.crcs.ectester.common.cli;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class Argument {
+ private String name;
+ private String desc;
+ private boolean required;
+
+ public Argument(String name, String desc, boolean isRequired) {
+ this.name = name;
+ this.desc = desc;
+ this.required = isRequired;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/cli/CLITools.java b/src/cz/crcs/ectester/common/cli/CLITools.java
new file mode 100644
index 0000000..91f121f
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/CLITools.java
@@ -0,0 +1,140 @@
+package cz.crcs.ectester.common.cli;
+
+import cz.crcs.ectester.common.ec.EC_Category;
+import cz.crcs.ectester.common.ec.EC_Data;
+import cz.crcs.ectester.data.EC_Store;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CLITools {
+
+ /**
+ * Print help.
+ */
+ public static void help(String prog, String header, Options options, String footer, boolean usage) {
+ HelpFormatter help = new HelpFormatter();
+ help.setOptionComparator(null);
+ help.printHelp(prog, header, options, footer, usage);
+ }
+
+ private static void help(HelpFormatter help, PrintWriter pw, CommandLineParser cli, Options opts, int depth) {
+ if (opts.getOptions().size() > 0) {
+ help.printOptions(pw, HelpFormatter.DEFAULT_WIDTH, opts, HelpFormatter.DEFAULT_LEFT_PAD + depth, HelpFormatter.DEFAULT_DESC_PAD);
+ }
+ if (cli instanceof TreeParser) {
+ TreeParser tp = (TreeParser) cli;
+ for (Argument arg : tp.getArgs()) {
+ String argname = arg.isRequired() ? "<" + arg.getName() + ">" : "[" + arg.getName() + "]";
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, String.format("%" + (depth + 1) + "s" + argname + " " + arg.getDesc(), " "));
+ }
+ tp.getParsers().forEach((key, value) -> {
+ pw.println();
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, String.format("%" + depth + "s" + key + ":", " "));
+ CLITools.help(help, pw, value.getParser(), value.getOptions(), depth + 1);
+ });
+ }
+ }
+
+ private static void usage(HelpFormatter help, PrintWriter pw, CommandLineParser cli, Options opts) {
+ StringWriter sw = new StringWriter();
+ PrintWriter upw = new PrintWriter(sw);
+ help.printUsage(upw, HelpFormatter.DEFAULT_WIDTH, "", opts);
+ if (cli instanceof TreeParser) {
+ upw.print(" ");
+ TreeParser tp = (TreeParser) cli;
+ String[] keys = tp.getParsers().keySet().toArray(new String[tp.getParsers().size()]);
+ if (keys.length > 0 && !tp.isRequired()) {
+ upw.print("[ ");
+ }
+
+ for (int i = 0; i < keys.length; ++i) {
+ String key = keys[i];
+ ParserOptions value = tp.getParsers().get(key);
+ upw.print("(" + key);
+ usage(help, upw, value.getParser(), value.getOptions());
+ upw.print(")");
+ if (i != keys.length - 1) {
+ upw.print(" | ");
+ }
+ }
+
+ if (keys.length > 0 && !tp.isRequired()) {
+ upw.print(" ]");
+ }
+
+ Argument[] args = tp.getArgs().toArray(new Argument[tp.getArgs().size()]);
+ if (args.length > 0) {
+ String[] argss = new String[tp.getArgs().size()];
+ for (int i = 0; i < args.length; ++i) {
+ Argument arg = args[i];
+ argss[i] = arg.isRequired() ? "<" + arg.getName() + ">" : "[" + arg.getName() + "]";
+ }
+ upw.print(" " + String.join(" ", argss));
+ }
+ }
+ pw.println(sw.toString().replaceAll("usage:( )?", "").replace("\n", ""));
+ }
+
+ /**
+ * Print tree help.
+ */
+ public static void help(String prog, String header, Options baseOpts, TreeParser baseParser, String footer, boolean printUsage) {
+ HelpFormatter help = new HelpFormatter();
+ help.setOptionComparator(null);
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, header);
+ if (printUsage) {
+ StringWriter uw = new StringWriter();
+ PrintWriter upw = new PrintWriter(uw);
+ usage(help, upw, baseParser, baseOpts);
+ pw.print("usage: " + prog);
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, uw.toString());
+ upw.close();
+ pw.println();
+ }
+ help(help, pw, baseParser, baseOpts, 1);
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, footer);
+ System.out.println(sw.toString());
+ }
+
+ /**
+ * Print version info.
+ */
+ public static void version(String description, String license) {
+ System.out.println(description);
+ System.out.println(license);
+ }
+
+ /**
+ * List categories and named curves.
+ */
+ public static void listNamed(EC_Store dataStore, String named) {
+ Map<String, EC_Category> categories = dataStore.getCategories();
+ if (named == null) {
+ // print all categories, briefly
+ for (EC_Category cat : categories.values()) {
+ System.out.println(cat);
+ }
+ } else if (categories.containsKey(named)) {
+ // print given category
+ System.out.println(categories.get(named));
+ } else {
+ // print given object
+ EC_Data object = dataStore.getObject(EC_Data.class, named);
+ if (object != null) {
+ System.out.println(object);
+ } else {
+ System.err.println("Named object " + named + " not found!");
+ }
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/common/cli/ParserOptions.java b/src/cz/crcs/ectester/common/cli/ParserOptions.java
new file mode 100644
index 0000000..ee2097e
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/ParserOptions.java
@@ -0,0 +1,38 @@
+package cz.crcs.ectester.common.cli;
+
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.Options;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class ParserOptions {
+ private CommandLineParser parser;
+ private Options options;
+ private List<Argument> arguments;
+
+ public ParserOptions(CommandLineParser parser, Options options) {
+ this.parser = parser;
+ this.options = options;
+ }
+
+ public ParserOptions(CommandLineParser parser, Options options, List<Argument> arguments) {
+ this(parser, options);
+ this.arguments = arguments;
+ }
+
+ public CommandLineParser getParser() {
+ return parser;
+ }
+
+ public Options getOptions() {
+ return options;
+ }
+
+ public List<Argument> getArguments() {
+ return Collections.unmodifiableList(arguments);
+ }
+}
diff --git a/src/cz/crcs/ectester/common/cli/TreeCommandLine.java b/src/cz/crcs/ectester/common/cli/TreeCommandLine.java
new file mode 100644
index 0000000..6a044d2
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/TreeCommandLine.java
@@ -0,0 +1,178 @@
+package cz.crcs.ectester.common.cli;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.function.BiFunction;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TreeCommandLine extends CommandLine {
+ private String name = "";
+ private TreeCommandLine next;
+ private CommandLine cli;
+
+ public TreeCommandLine(CommandLine cli, TreeCommandLine next) {
+ this.cli = cli;
+ this.next = next;
+ }
+
+ public TreeCommandLine(String name, CommandLine cli, TreeCommandLine next) {
+ this(cli, next);
+ this.name = name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getNextName() {
+ if (next != null) {
+ return next.getName();
+ }
+ return null;
+ }
+
+ public TreeCommandLine getNext() {
+ return next;
+ }
+
+ public boolean isNext(String next) {
+ return Objects.equals(getNextName(), next);
+ }
+
+ public CommandLine getThis() {
+ return cli;
+ }
+
+ public int getDepth() {
+ if (next == null) {
+ return 0;
+ }
+ return next.getDepth() + 1;
+ }
+
+ private <T> T getOption(String opt, BiFunction<CommandLine, String, T> getter, T defaultValue) {
+ if (opt.contains(".")) {
+ String[] parts = opt.split("\\.", 2);
+ if (next != null && parts[0].equals(next.getName())) {
+ T result = getter.apply(next, parts[1]);
+ if (result != null)
+ return result;
+ return defaultValue;
+ }
+ return defaultValue;
+ }
+ return getter.apply(cli, opt);
+ }
+
+ @Override
+ public boolean hasOption(String opt) {
+ return getOption(opt, CommandLine::hasOption, false);
+ }
+
+ @Override
+ public boolean hasOption(char opt) {
+ return cli.hasOption(opt);
+ }
+
+ @Override
+ public Object getParsedOptionValue(String opt) throws ParseException {
+ if (opt.contains(".")) {
+ String[] parts = opt.split("\\.", 2);
+ if (next != null && parts[0].equals(next.getName())) {
+ return next.getParsedOptionValue(parts[1]);
+ }
+ return null;
+ }
+ return cli.getParsedOptionValue(opt);
+ }
+
+ @Override
+ public Object getOptionObject(char opt) {
+ return cli.getOptionObject(opt);
+ }
+
+ @Override
+ public String getOptionValue(String opt) {
+ return getOption(opt, CommandLine::getOptionValue, null);
+ }
+
+ @Override
+ public String getOptionValue(char opt) {
+ return cli.getOptionValue(opt);
+ }
+
+ @Override
+ public String[] getOptionValues(String opt) {
+ return getOption(opt, CommandLine::getOptionValues, null);
+ }
+
+ @Override
+ public String[] getOptionValues(char opt) {
+ return cli.getOptionValues(opt);
+ }
+
+ @Override
+ public String getOptionValue(String opt, String defaultValue) {
+ return getOption(opt, CommandLine::getOptionValue, defaultValue);
+ }
+
+ @Override
+ public String getOptionValue(char opt, String defaultValue) {
+ return cli.getOptionValue(opt, defaultValue);
+ }
+
+ @Override
+ public Properties getOptionProperties(String opt) {
+ return getOption(opt, CommandLine::getOptionProperties, new Properties());
+ }
+
+ @Override
+ public Iterator<Option> iterator() {
+ return cli.iterator();
+ }
+
+ @Override
+ public Option[] getOptions() {
+ return cli.getOptions();
+ }
+
+ public boolean hasArg(int index) {
+ return getArg(index) != null;
+ }
+
+ public String getArg(int index) {
+ if (next != null) {
+ return next.getArg(index);
+ }
+ String[] args = cli.getArgs();
+ if (index >= args.length) {
+ return null;
+ }
+ if (index < 0 && -index > args.length) {
+ return null;
+ }
+ return index < 0 ? args[args.length + index] : args[index];
+ }
+
+ @Override
+ public String[] getArgs() {
+ return cli.getArgs();
+ }
+
+ @Override
+ public List<String> getArgList() {
+ return cli.getArgList();
+ }
+}
diff --git a/src/cz/crcs/ectester/common/cli/TreeParser.java b/src/cz/crcs/ectester/common/cli/TreeParser.java
new file mode 100644
index 0000000..77cce30
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/TreeParser.java
@@ -0,0 +1,120 @@
+package cz.crcs.ectester.common.cli;
+
+import org.apache.commons.cli.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TreeParser implements CommandLineParser {
+ private Map<String, ParserOptions> parsers;
+ private boolean required;
+ private List<Argument> args = Collections.emptyList();
+
+ public TreeParser(Map<String, ParserOptions> parsers, boolean required) {
+ this.parsers = parsers;
+ this.required = required;
+ }
+
+ public TreeParser(Map<String, ParserOptions> parsers, boolean required, List<Argument> args) {
+ this(parsers, required);
+ this.args = args;
+ }
+
+ public Map<String, ParserOptions> getParsers() {
+ return Collections.unmodifiableMap(parsers);
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+
+ public List<Argument> getArgs() {
+ return Collections.unmodifiableList(args);
+ }
+
+ @Override
+ public TreeCommandLine parse(Options options, String[] arguments) throws ParseException {
+ return this.parse(options, arguments, null);
+ }
+
+ public TreeCommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException {
+ return this.parse(options, arguments, properties, false);
+ }
+
+ @Override
+ public TreeCommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException {
+ return this.parse(options, arguments, null, stopAtNonOption);
+ }
+
+ public TreeCommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption) throws ParseException {
+ DefaultParser thisParser = new DefaultParser();
+ CommandLine cli = thisParser.parse(options, arguments, properties, true);
+
+ CommandLine subCli = null;
+ String[] cliArgs = cli.getArgs();
+ String sub = null;
+ if (cliArgs.length != 0) {
+ sub = cliArgs[0];
+
+ List<String> matches = new LinkedList<>();
+ String finalSub = sub;
+ for (Map.Entry<String, ParserOptions> entry : parsers.entrySet()) {
+ if (entry.getKey().equalsIgnoreCase(finalSub)) {
+ matches.clear();
+ matches.add(finalSub);
+ break;
+ } else if (entry.getKey().startsWith(finalSub)) {
+ matches.add(entry.getKey());
+ }
+ }
+
+ if (matches.size() == 1) {
+ sub = matches.get(0);
+ ParserOptions subparser = parsers.get(sub);
+ String[] remainingArgs = new String[cliArgs.length - 1];
+ System.arraycopy(cliArgs, 1, remainingArgs, 0, cliArgs.length - 1);
+ subCli = subparser.getParser().parse(subparser.getOptions(), remainingArgs, true);
+ } else if (matches.size() > 1) {
+ throw new AmbiguousOptionException(sub, matches);
+ }
+ } else {
+ if (required) {
+ throw new MissingOptionException(new ArrayList(parsers.keySet()));
+ }
+ }
+
+ long requiredArgs = args.stream().filter(Argument::isRequired).count();
+ String reqArgs = String.join(" ", args.stream().filter(Argument::isRequired).map(Argument::getName).collect(Collectors.toList()));
+
+ if (subCli instanceof TreeCommandLine) {
+ TreeCommandLine subTreeCli = (TreeCommandLine) subCli;
+
+ TreeCommandLine lastCli = subTreeCli;
+ while (lastCli.getNext() != null) {
+ lastCli = lastCli.getNext();
+ }
+
+ if (lastCli.getArgs().length < requiredArgs) {
+ throw new MissingArgumentException("Not enough arguments: " + reqArgs);
+ }
+
+ subTreeCli.setName(sub);
+ return new TreeCommandLine(cli, subTreeCli);
+ } else if (subCli != null) {
+ if (subCli.getArgs().length < requiredArgs) {
+ throw new MissingArgumentException("Not enough arguments: " + reqArgs);
+ }
+
+ TreeCommandLine subTreeCli = new TreeCommandLine(sub, subCli, null);
+ return new TreeCommandLine(cli, subTreeCli);
+ } else {
+ if (cliArgs.length < requiredArgs) {
+ throw new MissingArgumentException("Not enough arguments: " + reqArgs);
+ }
+ return new TreeCommandLine(cli, null);
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Curve.java b/src/cz/crcs/ectester/common/ec/EC_Curve.java
index 19cfe2d..173fd29 100644
--- a/src/cz/crcs/ectester/common/ec/EC_Curve.java
+++ b/src/cz/crcs/ectester/common/ec/EC_Curve.java
@@ -1,8 +1,12 @@
package cz.crcs.ectester.common.ec;
import cz.crcs.ectester.applet.EC_Consts;
+import cz.crcs.ectester.common.util.ByteUtil;
import javacard.security.KeyPair;
+import java.math.BigInteger;
+import java.security.spec.*;
+
/**
* An Elliptic curve, contains parameters Fp/F2M, A, B, G, R, (K)?.
*
@@ -49,4 +53,80 @@ public class EC_Curve extends EC_Params {
public String toString() {
return "<" + getId() + "> " + (field == KeyPair.ALG_EC_FP ? "Prime" : "Binary") + " field Elliptic curve (" + String.valueOf(bits) + "b)" + (desc == null ? "" : ": " + desc);
}
+
+ public ECParameterSpec toSpec() {
+ ECField field;
+ if (this.field == KeyPair.ALG_EC_FP) {
+ field = new ECFieldFp(new BigInteger(1, getData(0)));
+ } else {
+ byte[][] fieldData = getParam(EC_Consts.PARAMETER_F2M);
+ int m = ByteUtil.getShort(fieldData[0], 0);
+ int e1 = ByteUtil.getShort(fieldData[1], 0);
+ int e2 = ByteUtil.getShort(fieldData[2], 0);
+ int e3 = ByteUtil.getShort(fieldData[3], 0);
+ int[] powers = new int[]{e1, e2, e3};
+ field = new ECFieldF2m(m, powers);
+ }
+
+ BigInteger a = new BigInteger(1, getParam(EC_Consts.PARAMETER_A)[0]);
+ BigInteger b = new BigInteger(1, getParam(EC_Consts.PARAMETER_B)[0]);
+
+ EllipticCurve curve = new EllipticCurve(field, a, b);
+
+ byte[][] G = getParam(EC_Consts.PARAMETER_G);
+ BigInteger gx = new BigInteger(1, G[0]);
+ BigInteger gy = new BigInteger(1, G[1]);
+ ECPoint generator = new ECPoint(gx, gy);
+
+ BigInteger n = new BigInteger(1, getParam(EC_Consts.PARAMETER_R)[0]);
+
+ int h = ByteUtil.getShort(getParam(EC_Consts.PARAMETER_K)[0], 0);
+
+ return new ECParameterSpec(curve, generator, n, h);
+ }
+
+ public static EC_Curve fromSpec(ECParameterSpec spec) {
+ EllipticCurve curve = spec.getCurve();
+ ECField field = curve.getField();
+
+ short bits = (short) field.getFieldSize();
+ byte[][] params;
+ int paramIndex = 0;
+ byte fieldType;
+ if (field instanceof ECFieldFp) {
+ ECFieldFp primeField = (ECFieldFp) field;
+ params = new byte[7][];
+ params[paramIndex++] = primeField.getP().toByteArray();
+ fieldType = KeyPair.ALG_EC_FP;
+ } else if (field instanceof ECFieldF2m) {
+ ECFieldF2m binaryField = (ECFieldF2m) field;
+ params = new byte[10][];
+ params[paramIndex] = new byte[2];
+ ByteUtil.setShort(params[paramIndex++], 0, (short) binaryField.getM());
+ int[] powers = binaryField.getMidTermsOfReductionPolynomial();
+ for (int i = 0; i < 3; ++i) {
+ params[paramIndex] = new byte[2];
+ ByteUtil.setShort(params[paramIndex++], 0, (short) powers[i]);
+ }
+ fieldType = KeyPair.ALG_EC_F2M;
+ } else {
+ throw new IllegalArgumentException("ECParameterSpec with an unknown field.");
+ }
+
+ ECPoint generator = spec.getGenerator();
+
+ params[paramIndex++] = curve.getA().toByteArray();
+ params[paramIndex++] = curve.getB().toByteArray();
+
+ params[paramIndex++] = generator.getAffineX().toByteArray();
+ params[paramIndex++] = generator.getAffineY().toByteArray();
+
+ params[paramIndex++] = spec.getOrder().toByteArray();
+ params[paramIndex] = new byte[2];
+ ByteUtil.setShort(params[paramIndex], 0, (short) spec.getCofactor());
+
+ EC_Curve result = new EC_Curve(bits, fieldType);
+ result.readByteArray(params);
+ return result;
+ }
}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Data.java b/src/cz/crcs/ectester/common/ec/EC_Data.java
index acd282a..c048ef7 100644
--- a/src/cz/crcs/ectester/common/ec/EC_Data.java
+++ b/src/cz/crcs/ectester/common/ec/EC_Data.java
@@ -1,6 +1,6 @@
package cz.crcs.ectester.common.ec;
-import cz.crcs.ectester.common.Util;
+import cz.crcs.ectester.common.util.ByteUtil;
import java.io.*;
import java.util.*;
@@ -8,9 +8,10 @@ import java.util.regex.Pattern;
/**
* A list of byte arrays for holding EC data.
- *
+ * <p>
* 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 {
@@ -67,7 +68,7 @@ public abstract class EC_Data {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (byte[] param : data) {
byte[] length = new byte[2];
- Util.setShort(length, 0, (short) param.length);
+ ByteUtil.setShort(length, 0, (short) param.length);
out.write(length, 0, 2);
out.write(param, 0, param.length);
@@ -79,7 +80,7 @@ public abstract class EC_Data {
public String[] expand() {
List<String> out = new ArrayList<>(count);
for (byte[] param : data) {
- out.add(Util.bytesToHex(param, false));
+ out.add(ByteUtil.bytesToHex(param, false));
}
return out.toArray(new String[out.size()]);
@@ -97,9 +98,9 @@ public abstract class EC_Data {
private static byte[] parse(String param) {
byte[] data;
if (param.startsWith("0x") || param.startsWith("0X")) {
- data = Util.hexToBytes(param.substring(2));
+ data = ByteUtil.hexToBytes(param.substring(2));
} else {
- data = Util.hexToBytes(param);
+ data = ByteUtil.hexToBytes(param);
}
if (data == null)
return new byte[0];
@@ -141,12 +142,16 @@ public abstract class EC_Data {
}
public boolean readBytes(byte[] bytes) {
+ if (bytes == null) {
+ return false;
+ }
+
int offset = 0;
for (int i = 0; i < count; i++) {
if (bytes.length - offset < 2) {
return false;
}
- short paramLength = Util.getShort(bytes, offset);
+ short paramLength = ByteUtil.getShort(bytes, offset);
offset += 2;
if (bytes.length < offset + paramLength) {
return false;
@@ -158,6 +163,18 @@ public abstract class EC_Data {
return true;
}
+ public boolean readByteArray(byte[][] bytes) {
+ if (bytes == null || count != bytes.length) {
+ return false;
+ }
+
+ for (int i = 0; i < count; ++i) {
+ data[i] = new byte[bytes[i].length];
+ System.arraycopy(bytes[i], 0, data[i], 0, bytes[i].length);
+ }
+ return true;
+ }
+
public void writeCSV(OutputStream out) throws IOException {
Writer w = new OutputStreamWriter(out);
w.write(String.join(",", expand()));
diff --git a/src/cz/crcs/ectester/common/ec/EC_KAResult.java b/src/cz/crcs/ectester/common/ec/EC_KAResult.java
index 3b74c57..a7b3cd5 100644
--- a/src/cz/crcs/ectester/common/ec/EC_KAResult.java
+++ b/src/cz/crcs/ectester/common/ec/EC_KAResult.java
@@ -1,6 +1,6 @@
package cz.crcs.ectester.common.ec;
-import cz.crcs.ectester.common.Util;
+import cz.crcs.ectester.common.util.CardUtil;
/**
* A result of EC based Key agreement operation.
@@ -56,7 +56,7 @@ public class EC_KAResult extends EC_Data {
@Override
public String toString() {
- String algo = Util.getKA(ka);
+ String algo = CardUtil.getKA(ka);
return "<" + getId() + "> " + algo + " result over " + curve + ", " + oneKey + " + " + otherKey + (desc == null ? "" : ": " + desc);
}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Params.java b/src/cz/crcs/ectester/common/ec/EC_Params.java
index 3fada93..1c066e7 100644
--- a/src/cz/crcs/ectester/common/ec/EC_Params.java
+++ b/src/cz/crcs/ectester/common/ec/EC_Params.java
@@ -1,7 +1,7 @@
package cz.crcs.ectester.common.ec;
import cz.crcs.ectester.applet.EC_Consts;
-import cz.crcs.ectester.common.Util;
+import cz.crcs.ectester.common.util.ByteUtil;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
@@ -125,12 +125,12 @@ public class EC_Params extends EC_Data {
byte[] param = data[i];
if (masked == EC_Consts.PARAMETER_F2M) {
//add m, e_1, e_2, e_3
- param = Util.concatenate(param, data[i + 1]);
- if (!Util.allValue(data[i + 2], (byte) 0)) {
- param = Util.concatenate(param, data[i + 2]);
+ param = ByteUtil.concatenate(param, data[i + 1]);
+ if (!ByteUtil.allValue(data[i + 2], (byte) 0)) {
+ param = ByteUtil.concatenate(param, data[i + 2]);
}
- if (!Util.allValue(data[i + 3], (byte) 0)) {
- param = Util.concatenate(param, data[i + 3]);
+ if (!ByteUtil.allValue(data[i + 3], (byte) 0)) {
+ param = ByteUtil.concatenate(param, data[i + 3]);
}
if (!(param.length == 4 || param.length == 8))
throw new RuntimeException("PARAMETER_F2M length is not 8.(should be)");
@@ -138,14 +138,14 @@ public class EC_Params extends EC_Data {
if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) {
//read another param (the y coord) and put into X962 format.
byte[] y = data[i + 1];
- param = Util.concatenate(new byte[]{4}, param, y); //<- ugly but works!
+ param = ByteUtil.concatenate(new byte[]{4}, param, y); //<- ugly but works!
}
if (param.length == 0)
throw new RuntimeException("Empty parameter read?");
//write length
byte[] length = new byte[2];
- Util.setShort(length, 0, (short) param.length);
+ ByteUtil.setShort(length, 0, (short) param.length);
out.write(length, 0, 2);
//write data
out.write(param, 0, param.length);
@@ -175,15 +175,15 @@ public class EC_Params extends EC_Data {
byte[] param = data[index];
if (masked == EC_Consts.PARAMETER_F2M) {
for (int i = 0; i < 4; ++i) {
- out.add(Util.bytesToHex(data[index + i], false));
+ out.add(ByteUtil.bytesToHex(data[index + i], false));
}
index += 4;
} else if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) {
- out.add(Util.bytesToHex(param, false));
- out.add(Util.bytesToHex(data[index + 1], false));
+ out.add(ByteUtil.bytesToHex(param, false));
+ out.add(ByteUtil.bytesToHex(data[index + 1], false));
index += 2;
} else {
- out.add(Util.bytesToHex(param, false));
+ out.add(ByteUtil.bytesToHex(param, false));
index++;
}
}
diff --git a/src/cz/crcs/ectester/common/output/TestWriter.java b/src/cz/crcs/ectester/common/output/TestWriter.java
new file mode 100644
index 0000000..0ecfd5a
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/TestWriter.java
@@ -0,0 +1,15 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.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/common/output/TestableWriter.java b/src/cz/crcs/ectester/common/output/TestableWriter.java
new file mode 100644
index 0000000..9876064
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/TestableWriter.java
@@ -0,0 +1,65 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.BaseTestable;
+import cz.crcs.ectester.reader.output.ResponseWriter;
+import cz.crcs.ectester.reader.response.Response;
+import cz.crcs.ectester.reader.test.CommandTestable;
+import cz.crcs.ectester.standalone.test.KeyAgreementTestable;
+import cz.crcs.ectester.standalone.test.KeyGeneratorTestable;
+import cz.crcs.ectester.standalone.test.SignatureTestable;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TestableWriter {
+ private PrintStream output;
+ private ResponseWriter respWriter;
+
+ public TestableWriter(PrintStream output) {
+ this.output = output;
+ this.respWriter = new ResponseWriter(output);
+ }
+
+ public TestableWriter(OutputStream output) {
+ this(new PrintStream(output));
+ }
+
+ public String outputTestableSuffix(BaseTestable t) {
+ if (t instanceof CommandTestable) {
+ Response r = ((CommandTestable) t).getResponse();
+ return respWriter.responseSuffix(r);
+ } else if (t instanceof KeyAgreementTestable) {
+
+ } else if (t instanceof KeyGeneratorTestable) {
+
+ } else if (t instanceof SignatureTestable) {
+
+ }
+ return null;
+ }
+
+ public void writeTestableSuffix(BaseTestable t) {
+ output.println(outputTestableSuffix(t));
+ }
+
+ public String outputTestable(BaseTestable t) {
+ if (t instanceof CommandTestable) {
+ CommandTestable testable = (CommandTestable) t;
+ return respWriter.responseString(testable.getResponse());
+ } else if (t instanceof KeyAgreementTestable) {
+
+ } else if (t instanceof KeyGeneratorTestable) {
+
+ } else if (t instanceof SignatureTestable) {
+
+ }
+ return null;
+ }
+
+ public void writeTestable(BaseTestable t) {
+ output.println(outputTestable(t));
+ }
+}
diff --git a/src/cz/crcs/ectester/common/output/TextTestWriter.java b/src/cz/crcs/ectester/common/output/TextTestWriter.java
new file mode 100644
index 0000000..2691ccb
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/TextTestWriter.java
@@ -0,0 +1,79 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.SimpleTest;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.test.TestSuite;
+
+import java.io.PrintStream;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TextTestWriter implements TestWriter {
+ private PrintStream output;
+ private TestableWriter testableWriter;
+
+ public static int BASE_WIDTH = 76;
+
+ public TextTestWriter(PrintStream output) {
+ this.output = output;
+ this.testableWriter = new TestableWriter(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();
+ out.append(t.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", t.getResultValue().name()));
+ out.append(" ┃ ");
+
+ if (t instanceof CompoundTest) {
+ CompoundTest test = (CompoundTest) t;
+ 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());
+ }
+ }
+ } else {
+ SimpleTest test = (SimpleTest) t;
+ out.append(testableWriter.outputTestableSuffix(test.getTestable()));
+ }
+ 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/common/output/XMLTestWriter.java b/src/cz/crcs/ectester/common/output/XMLTestWriter.java
new file mode 100644
index 0000000..4139330
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/XMLTestWriter.java
@@ -0,0 +1,204 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.test.TestSuite;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+import cz.crcs.ectester.reader.test.CommandTest;
+import cz.crcs.ectester.standalone.test.*;
+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;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * @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(ByteUtil.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(ByteUtil.bytesToHex(r.getAPDU().getBytes()));
+ responseElem.appendChild(apdu);
+
+ Element naturalSW = doc.createElement("natural-sw");
+ naturalSW.setTextContent(String.valueOf(Short.toUnsignedInt(r.getNaturalSW())));
+ responseElem.appendChild(naturalSW);
+
+ Element sws = doc.createElement("sws");
+ for (int i = 0; i < r.getNumSW(); ++i) {
+ Element sw = doc.createElement("sw");
+ sw.setTextContent(String.valueOf(Short.toUnsignedInt(r.getSW(i))));
+ sws.appendChild(sw);
+ }
+ responseElem.appendChild(sws);
+
+ Element duration = doc.createElement("duration");
+ duration.setTextContent(String.valueOf(r.getDuration()));
+ responseElem.appendChild(duration);
+
+ Element description = doc.createElement("desc");
+ description.setTextContent(r.getDescription());
+ responseElem.appendChild(description);
+
+ return responseElem;
+ }
+
+ private Element kaElement(KeyAgreementTestable kat) {
+ Element katElem = doc.createElement("key-agreement");
+
+ Element secret = doc.createElement("secret");
+ secret.setTextContent(ByteUtil.bytesToHex(kat.getSecret()));
+ katElem.appendChild(secret);
+
+ return katElem;
+ }
+
+ private Element kgtElement(KeyGeneratorTestable kgt) {
+ Element kgtElem = doc.createElement("key-pair-generator");
+
+ Element keyPair = doc.createElement("key-pair");
+ Element pubkey = doc.createElement("pubkey");
+ PublicKey pkey = kgt.getKeyPair().getPublic();
+ pubkey.setAttribute("algorithm", pkey.getAlgorithm());
+ pubkey.setAttribute("format", pkey.getFormat());
+ pubkey.setTextContent(ByteUtil.bytesToHex(pkey.getEncoded()));
+ keyPair.appendChild(pubkey);
+
+ Element privkey = doc.createElement("privkey");
+ PrivateKey skey = kgt.getKeyPair().getPrivate();
+ privkey.setAttribute("algorithm", skey.getAlgorithm());
+ privkey.setAttribute("format", skey.getFormat());
+ privkey.setTextContent(ByteUtil.bytesToHex(skey.getEncoded()));
+ keyPair.appendChild(privkey);
+
+ return kgtElem;
+ }
+
+ private Element sigElement(SignatureTestable sig) {
+ Element sigElem = doc.createElement("signature");
+ sigElem.setAttribute("verified", sig.getVerified() ? "true" : "false");
+
+ Element raw = doc.createElement("raw");
+ raw.setTextContent(ByteUtil.bytesToHex(sig.getSignature()));
+ sigElem.appendChild(raw);
+
+ return sigElem;
+ }
+
+ private Element testElement(Test t) {
+ Element testElem = doc.createElement("test");
+
+ if (t instanceof CommandTest) {
+ CommandTest test = (CommandTest) t;
+ testElem.setAttribute("type", "command");
+ testElem.appendChild(commandElement(test.getCommand()));
+ testElem.appendChild(responseElement(test.getResponse()));
+ } else if (t instanceof KeyAgreementTest) {
+ KeyAgreementTest test = (KeyAgreementTest) t;
+ testElem.setAttribute("type", "key-agreement");
+ testElem.appendChild(kaElement(test.getTestable()));
+ } else if (t instanceof KeyGeneratorTest) {
+ KeyGeneratorTest test = (KeyGeneratorTest) t;
+ testElem.setAttribute("type", "key-pair-generator");
+ testElem.appendChild(kgtElement(test.getTestable()));
+ } else if (t instanceof SignatureTest) {
+ SignatureTest test = (SignatureTest) t;
+ testElem.setAttribute("type", "signature");
+ testElem.appendChild(sigElement(test.getTestable()));
+ } else if (t instanceof CompoundTest) {
+ CompoundTest test = (CompoundTest) 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/common/output/YAMLTestWriter.java b/src/cz/crcs/ectester/common/output/YAMLTestWriter.java
new file mode 100644
index 0000000..ba9fa43
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/YAMLTestWriter.java
@@ -0,0 +1,161 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.CompoundTest;
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.test.TestSuite;
+import cz.crcs.ectester.common.util.ByteUtil;
+import cz.crcs.ectester.reader.command.Command;
+import cz.crcs.ectester.reader.response.Response;
+import cz.crcs.ectester.reader.test.CommandTest;
+import cz.crcs.ectester.standalone.test.*;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.PrintStream;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+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", ByteUtil.bytesToHex(c.getAPDU().getBytes()));
+ return commandObj;
+ }
+
+ private Map<String, Object> responseObject(Response r) {
+ Map<String, Object> responseObj = new HashMap<>();
+ responseObj.put("successful", r.successful());
+ responseObj.put("apdu", ByteUtil.bytesToHex(r.getAPDU().getBytes()));
+ responseObj.put("natural_sw", Short.toUnsignedInt(r.getNaturalSW()));
+ List<Integer> sws = new LinkedList<>();
+ for (int i = 0; i < r.getNumSW(); ++i) {
+ sws.add(Short.toUnsignedInt(r.getSW(i)));
+ }
+ responseObj.put("sws", sws);
+ responseObj.put("duration", r.getDuration());
+ responseObj.put("desc", r.getDescription());
+ return responseObj;
+ }
+
+ private Map<String, Object> kaObject(KeyAgreementTestable kat) {
+ Map<String, Object> katObject = new HashMap<>();
+ katObject.put("secret", ByteUtil.bytesToHex(kat.getSecret()));
+ return katObject;
+ }
+
+ private Map<String, Object> kgtObject(KeyGeneratorTestable kgt) {
+ Map<String, Object> kgtObject = new HashMap<>();
+ Map<String, Object> pubObject = new HashMap<>();
+ PublicKey pkey = kgt.getKeyPair().getPublic();
+ pubObject.put("algorithm", pkey.getAlgorithm());
+ pubObject.put("format", pkey.getFormat());
+ pubObject.put("raw", ByteUtil.bytesToHex(pkey.getEncoded()));
+ kgtObject.put("pubkey", pubObject);
+
+ Map<String, Object> privObject = new HashMap<>();
+ PrivateKey skey = kgt.getKeyPair().getPrivate();
+ privObject.put("algorithm", skey.getAlgorithm());
+ privObject.put("format", skey.getFormat());
+ privObject.put("raw", ByteUtil.bytesToHex(skey.getEncoded()));
+ kgtObject.put("privkey", privObject);
+ return kgtObject;
+ }
+
+ private Map<String, Object> sigObject(SignatureTestable sig) {
+ Map<String, Object> sigObject = new HashMap<>();
+ sigObject.put("verified", sig.getVerified());
+ sigObject.put("raw", ByteUtil.bytesToHex(sig.getSignature()));
+ return sigObject;
+ }
+
+ private Map<String, Object> testObject(Test t) {
+ Map<String, Object> testObj = new HashMap<>();
+
+ if (t instanceof CommandTest) {
+ CommandTest test = (CommandTest) t;
+ testObj.put("type", "command");
+ testObj.put("command", commandObject(test.getCommand()));
+ testObj.put("response", responseObject(test.getResponse()));
+ } else if (t instanceof KeyAgreementTest) {
+ KeyAgreementTest test = (KeyAgreementTest) t;
+ testObj.put("type", "key-agreement");
+ testObj.put("key-agreement", kaObject(test.getTestable()));
+ } else if (t instanceof KeyGeneratorTest) {
+ KeyGeneratorTest test = (KeyGeneratorTest) t;
+ testObj.put("type", "key-pair-generator");
+ testObj.put("key-pair-generator", kgtObject(test.getTestable()));
+ } else if (t instanceof SignatureTest) {
+ SignatureTest test = (SignatureTest) t;
+ testObj.put("type", "signature");
+ testObj.put("signature", sigObject(test.getTestable()));
+ } else if (t instanceof CompoundTest) {
+ CompoundTest test = (CompoundTest) 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/common/test/BaseTestable.java b/src/cz/crcs/ectester/common/test/BaseTestable.java
new file mode 100644
index 0000000..f8ebf48
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/BaseTestable.java
@@ -0,0 +1,36 @@
+package cz.crcs.ectester.common.test;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class BaseTestable implements Testable {
+ protected boolean hasRun;
+ protected boolean ok;
+ protected boolean error;
+
+ protected Map<String, Object> meta = new TreeMap<>();
+
+ @Override
+ public boolean hasRun() {
+ return hasRun;
+ }
+
+ @Override
+ public boolean ok() {
+ return ok;
+ }
+
+ @Override
+ public boolean error() {
+ return error;
+ }
+
+ @Override
+ public Map<String, Object> meta() {
+ return Collections.unmodifiableMap(meta);
+ }
+}
diff --git a/src/cz/crcs/ectester/common/test/SimpleTest.java b/src/cz/crcs/ectester/common/test/SimpleTest.java
new file mode 100644
index 0000000..f68320a
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/SimpleTest.java
@@ -0,0 +1,19 @@
+package cz.crcs.ectester.common.test;
+
+/**
+ * @param <T>
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class SimpleTest<T extends BaseTestable> extends Test {
+ protected T testable;
+ protected TestCallback<T> callback;
+
+ public SimpleTest(T testable, TestCallback<T> callback) {
+ this.testable = testable;
+ this.callback = callback;
+ }
+
+ public T getTestable() {
+ return testable;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/test/Test.java b/src/cz/crcs/ectester/common/test/Test.java
index 750a410..5f55337 100644
--- a/src/cz/crcs/ectester/common/test/Test.java
+++ b/src/cz/crcs/ectester/common/test/Test.java
@@ -1,5 +1,8 @@
package cz.crcs.ectester.common.test;
+import java.util.Collections;
+import java.util.Map;
+
import static cz.crcs.ectester.common.test.Result.Value;
/**
@@ -8,8 +11,9 @@ import static cz.crcs.ectester.common.test.Result.Value;
* @author Jan Jancar johny@neuromancer.sk
*/
public abstract class Test implements Testable {
- protected boolean hasRun = false;
+ protected boolean hasRun;
protected Result result;
+ protected Map<String, Object> meta;
public Result getResult() {
if (!hasRun) {
@@ -39,6 +43,7 @@ public abstract class Test implements Testable {
return result.ok();
}
+ @Override
public boolean error() {
if (!hasRun) {
return false;
@@ -46,12 +51,19 @@ public abstract class Test implements Testable {
return result.compareTo(Value.ERROR);
}
- public abstract String getDescription();
-
+ @Override
public boolean hasRun() {
return hasRun;
}
+ @Override
+ public Map<String, Object> meta() {
+ return Collections.unmodifiableMap(meta);
+ }
+
+ public abstract String getDescription();
+
+ @Override
public abstract void run() throws TestException;
}
diff --git a/src/cz/crcs/ectester/common/test/TestCallback.java b/src/cz/crcs/ectester/common/test/TestCallback.java
new file mode 100644
index 0000000..ce6000b
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/TestCallback.java
@@ -0,0 +1,12 @@
+package cz.crcs.ectester.common.test;
+
+import java.util.function.Function;
+
+/**
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ * @param <T>
+ */
+public abstract class TestCallback<T extends Testable> implements Function<T, Result> {
+
+}
diff --git a/src/cz/crcs/ectester/common/test/TestRunner.java b/src/cz/crcs/ectester/common/test/TestRunner.java
new file mode 100644
index 0000000..ef448c2
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/TestRunner.java
@@ -0,0 +1,27 @@
+package cz.crcs.ectester.common.test;
+
+import cz.crcs.ectester.common.output.TestWriter;
+
+/**
+ * @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 TestException {
+ 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/common/test/TestSuite.java b/src/cz/crcs/ectester/common/test/TestSuite.java
new file mode 100644
index 0000000..74a24af
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/TestSuite.java
@@ -0,0 +1,36 @@
+package cz.crcs.ectester.common.test;
+
+import cz.crcs.ectester.data.EC_Store;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class TestSuite {
+ protected String name;
+ protected String description;
+ protected List<Test> tests = new LinkedList<>();
+ protected EC_Store dataStore;
+
+ public TestSuite(EC_Store dataStore, String name, String description) {
+ this.dataStore = dataStore;
+ this.name = name;
+ this.description = description;
+ }
+
+ public List<Test> getTests() {
+ return Collections.unmodifiableList(tests);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+}
diff --git a/src/cz/crcs/ectester/common/test/Testable.java b/src/cz/crcs/ectester/common/test/Testable.java
index d05d31e..e8eb321 100644
--- a/src/cz/crcs/ectester/common/test/Testable.java
+++ b/src/cz/crcs/ectester/common/test/Testable.java
@@ -1,15 +1,38 @@
package cz.crcs.ectester.common.test;
+import java.util.Map;
+
/**
* @author Jan Jancar johny@neuromancer.sk
*/
public interface Testable {
+ /**
+ * @return Whether this testable was run.
+ */
boolean hasRun();
+ /**
+ * Run this Testable.
+ *
+ * @throws TestException
+ */
void run() throws TestException;
+ /**
+ * @return Whether this Testable was OK.
+ */
boolean ok();
+ /**
+ * @return Whether an error happened.
+ */
boolean error();
+
+ /**
+ * Get the metadata of this Testable.
+ *
+ * @return The metadata of the testable.
+ */
+ Map<String, Object> meta();
}
diff --git a/src/cz/crcs/ectester/common/util/ByteUtil.java b/src/cz/crcs/ectester/common/util/ByteUtil.java
new file mode 100644
index 0000000..939e487
--- /dev/null
+++ b/src/cz/crcs/ectester/common/util/ByteUtil.java
@@ -0,0 +1,122 @@
+package cz.crcs.ectester.common.util;
+
+/**
+ * Utility class, some byte/hex manipulation, convenient byte[] methods.
+ *
+ * @author Petr Svenda petr@svenda.com
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class ByteUtil {
+ public static short getShort(byte[] array, int offset) {
+ return (short) (((array[offset] & 0xFF) << 8) | (array[offset + 1] & 0xFF));
+ }
+
+ public static void setShort(byte[] array, int offset, short value) {
+ array[offset + 1] = (byte) (value & 0xFF);
+ array[offset] = (byte) ((value >> 8) & 0xFF);
+ }
+
+ public static int diffBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) {
+ for (int i = 0; i < length; ++i) {
+ byte a = one[i + oneOffset];
+ byte b = other[i + otherOffset];
+ if (a != b) {
+ return i;
+ }
+ }
+ return length;
+ }
+
+ public static boolean compareBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) {
+ return diffBytes(one, oneOffset, other, otherOffset, length) == length;
+ }
+
+ public static boolean allValue(byte[] array, byte value) {
+ for (byte a : array) {
+ if (a != value)
+ return false;
+ }
+ return true;
+ }
+
+ public static byte[] hexToBytes(String hex) {
+ return hexToBytes(hex, true);
+ }
+
+ public static byte[] hexToBytes(String hex, boolean bigEndian) {
+ hex = hex.replace(" ", "");
+ int len = hex.length();
+ StringBuilder sb = new StringBuilder();
+
+ if (len % 2 == 1) {
+ sb.append("0");
+ ++len;
+ }
+
+ if (bigEndian) {
+ sb.append(hex);
+ } else {
+ for (int i = 0; i < len / 2; ++i) {
+ if (sb.length() >= 2) {
+ sb.insert(sb.length() - 2, hex.substring(2 * i, 2 * i + 2));
+ } else {
+ sb.append(hex.substring(2 * i, 2 * i + 2));
+ }
+
+ }
+ }
+
+ String data = sb.toString();
+ byte[] result = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ result[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4)
+ + (Character.digit(data.charAt(i + 1), 16)));
+ }
+ return result;
+ }
+
+ public static String byteToHex(byte data) {
+ return String.format("%02x", data);
+ }
+
+ public static String bytesToHex(byte[] data) {
+ return bytesToHex(data, true);
+ }
+
+ public static String bytesToHex(byte[] data, boolean addSpace) {
+ return bytesToHex(data, 0, data.length, addSpace);
+ }
+
+ public static String bytesToHex(byte[] data, int offset, int len) {
+ return bytesToHex(data, offset, len, true);
+ }
+
+ public static String bytesToHex(byte[] data, int offset, int len, boolean addSpace) {
+ StringBuilder buf = new StringBuilder();
+ for (int i = offset; i < (offset + len); i++) {
+ buf.append(byteToHex(data[i]));
+ if (addSpace && i != (offset + len - 1)) {
+ buf.append(" ");
+ }
+ }
+ return (buf.toString());
+ }
+
+ public static byte[] concatenate(byte[]... arrays) {
+ int len = 0;
+ for (byte[] array : arrays) {
+ if (array == null)
+ continue;
+ len += array.length;
+ }
+ byte[] out = new byte[len];
+ int offset = 0;
+ for (byte[] array : arrays) {
+ if (array == null || array.length == 0)
+ continue;
+ System.arraycopy(array, 0, out, offset, array.length);
+ offset += array.length;
+ }
+ return out;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/Util.java b/src/cz/crcs/ectester/common/util/CardUtil.java
index 0136493..edcb510 100644
--- a/src/cz/crcs/ectester/common/Util.java
+++ b/src/cz/crcs/ectester/common/util/CardUtil.java
@@ -1,4 +1,4 @@
-package cz.crcs.ectester.common;
+package cz.crcs.ectester.common.util;
import cz.crcs.ectester.applet.ECTesterApplet;
import cz.crcs.ectester.applet.EC_Consts;
@@ -8,126 +8,10 @@ import javacard.security.CryptoException;
import static cz.crcs.ectester.applet.ECTesterApplet.*;
/**
- * Utility class, some byte/hex manipulation, convenient byte[] methods.
- *
* @author Petr Svenda petr@svenda.com
* @author Jan Jancar johny@neuromancer.sk
*/
-public class Util {
-
- public static short getShort(byte[] array, int offset) {
- return (short) (((array[offset] & 0xFF) << 8) | (array[offset + 1] & 0xFF));
- }
-
- public static void setShort(byte[] array, int offset, short value) {
- array[offset + 1] = (byte) (value & 0xFF);
- array[offset] = (byte) ((value >> 8) & 0xFF);
- }
-
- public static int diffBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) {
- for (int i = 0; i < length; ++i) {
- byte a = one[i + oneOffset];
- byte b = other[i + otherOffset];
- if (a != b) {
- return i;
- }
- }
- return length;
- }
-
- public static boolean compareBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) {
- return diffBytes(one, oneOffset, other, otherOffset, length) == length;
- }
-
- public static boolean allValue(byte[] array, byte value) {
- for (byte a : array) {
- if (a != value)
- return false;
- }
- return true;
- }
-
- public static byte[] hexToBytes(String hex) {
- return hexToBytes(hex, true);
- }
-
- public static byte[] hexToBytes(String hex, boolean bigEndian) {
- hex = hex.replace(" ", "");
- int len = hex.length();
- StringBuilder sb = new StringBuilder();
-
- if (len % 2 == 1) {
- sb.append("0");
- ++len;
- }
-
- if (bigEndian) {
- sb.append(hex);
- } else {
- for (int i = 0; i < len / 2; ++i) {
- if (sb.length() >= 2) {
- sb.insert(sb.length() - 2, hex.substring(2 * i, 2 * i + 2));
- } else {
- sb.append(hex.substring(2 * i, 2 * i + 2));
- }
-
- }
- }
-
- String data = sb.toString();
- byte[] result = new byte[len / 2];
- for (int i = 0; i < len; i += 2) {
- result[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4)
- + (Character.digit(data.charAt(i + 1), 16)));
- }
- return result;
- }
-
- public static String byteToHex(byte data) {
- return String.format("%02x", data);
- }
-
- public static String bytesToHex(byte[] data) {
- return bytesToHex(data, true);
- }
-
- public static String bytesToHex(byte[] data, boolean addSpace) {
- return bytesToHex(data, 0, data.length, addSpace);
- }
-
- public static String bytesToHex(byte[] data, int offset, int len) {
- return bytesToHex(data, offset, len, true);
- }
-
- public static String bytesToHex(byte[] data, int offset, int len, boolean addSpace) {
- StringBuilder buf = new StringBuilder();
- for (int i = offset; i < (offset + len); i++) {
- buf.append(byteToHex(data[i]));
- if (addSpace && i != (offset + len - 1)) {
- buf.append(" ");
- }
- }
- return (buf.toString());
- }
-
- public static byte[] concatenate(byte[]... arrays) {
- int len = 0;
- for (byte[] array : arrays) {
- if (array == null)
- continue;
- len += array.length;
- }
- byte[] out = new byte[len];
- int offset = 0;
- for (byte[] array : arrays) {
- if (array == null || array.length == 0)
- continue;
- System.arraycopy(array, 0, out, offset, array.length);
- offset += array.length;
- }
- return out;
- }
-
+public class CardUtil {
public static String getSWSource(short sw) {
switch (sw) {
case ISO7816.SW_NO_ERROR:
diff --git a/src/cz/crcs/ectester/common/util/ECUtil.java b/src/cz/crcs/ectester/common/util/ECUtil.java
new file mode 100644
index 0000000..973b813
--- /dev/null
+++ b/src/cz/crcs/ectester/common/util/ECUtil.java
@@ -0,0 +1,172 @@
+package cz.crcs.ectester.common.util;
+
+import java.math.BigInteger;
+import java.security.spec.*;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class ECUtil {
+
+ public static byte[] toByteArray(BigInteger what, int bits) {
+ byte[] raw = what.toByteArray();
+ int bytes = (bits + 7) / 8;
+ if (raw.length < bytes) {
+ byte[] result = new byte[bytes];
+ System.arraycopy(raw, 0, result, bytes - raw.length, raw.length);
+ return result;
+ }
+ if (bytes < raw.length) {
+ byte[] result = new byte[bytes];
+ System.arraycopy(raw, raw.length - bytes, result, 0, bytes);
+ return result;
+ }
+ return raw;
+ }
+
+ public static byte[] toX962Compressed(ECPoint point, int bits) {
+ if (point.equals(ECPoint.POINT_INFINITY)) {
+ return new byte[]{0};
+ }
+ byte[] x = toByteArray(point.getAffineX(), bits);
+ byte marker = (byte) (0x02 | point.getAffineY().mod(BigInteger.valueOf(2)).byteValue());
+ return ByteUtil.concatenate(new byte[]{marker}, x);
+ }
+
+ public static byte[] toX962Compressed(ECPoint point, EllipticCurve curve) {
+ return toX962Compressed(point, curve.getField().getFieldSize());
+ }
+
+ public static byte[] toX962Compressed(ECPoint point, ECParameterSpec spec) {
+ return toX962Compressed(point, spec.getCurve());
+ }
+
+ public static byte[] toX962Uncompressed(ECPoint point, int bits) {
+ if (point.equals(ECPoint.POINT_INFINITY)) {
+ return new byte[]{0};
+ }
+ byte[] x = toByteArray(point.getAffineX(), bits);
+ byte[] y = toByteArray(point.getAffineY(), bits);
+ return ByteUtil.concatenate(new byte[]{0x04}, x, y);
+ }
+
+ public static byte[] toX962Uncompressed(ECPoint point, EllipticCurve curve) {
+ return toX962Uncompressed(point, curve.getField().getFieldSize());
+ }
+
+ public static byte[] toX962Uncompressed(ECPoint point, ECParameterSpec spec) {
+ return toX962Uncompressed(point, spec.getCurve());
+ }
+
+ public static byte[] toX962Hybrid(ECPoint point, int bits) {
+ if (point.equals(ECPoint.POINT_INFINITY)) {
+ return new byte[]{0};
+ }
+ byte[] x = toByteArray(point.getAffineX(), bits);
+ byte[] y = toByteArray(point.getAffineY(), bits);
+ byte marker = (byte) (0x06 | point.getAffineY().mod(BigInteger.valueOf(2)).byteValue());
+ return ByteUtil.concatenate(new byte[]{marker}, x, y);
+ }
+
+ public static byte[] toX962Hybrid(ECPoint point, EllipticCurve curve) {
+ return toX962Hybrid(point, curve.getField().getFieldSize());
+ }
+
+ public static byte[] toX962Hybrid(ECPoint point, ECParameterSpec spec) {
+ return toX962Hybrid(point, spec.getCurve());
+ }
+
+ private static boolean isResidue(BigInteger a, BigInteger p) {
+ BigInteger exponent = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2));
+ BigInteger result = a.modPow(exponent, p);
+ return result.intValueExact() == 1;
+ }
+
+ private static BigInteger modSqrt(BigInteger a, BigInteger p) {
+ BigInteger q = p.subtract(BigInteger.ONE);
+ int s = 0;
+ while (q.mod(BigInteger.valueOf(2)).equals(BigInteger.ZERO)) {
+ q = q.divide(BigInteger.valueOf(2));
+ s++;
+ }
+
+ BigInteger z = BigInteger.ONE;
+ do {
+ z = z.add(BigInteger.ONE);
+ } while (isResidue(z, p));
+
+ BigInteger m = BigInteger.valueOf(s);
+ BigInteger c = z.modPow(q, p);
+ BigInteger t = a.modPow(q, p);
+ BigInteger rExponent = q.add(BigInteger.ONE).divide(BigInteger.valueOf(2));
+ BigInteger r = a.modPow(rExponent, p);
+
+ while (!t.equals(BigInteger.ONE)) {
+ int i = 0;
+ BigInteger exponent;
+ do {
+ exponent = BigInteger.valueOf(2).pow(++i);
+ } while (!t.modPow(exponent, p).equals(BigInteger.ONE));
+
+ BigInteger twoExponent = m.subtract(BigInteger.valueOf(i + 1));
+ BigInteger b = c.modPow(BigInteger.valueOf(2).modPow(twoExponent, p), p);
+ m = BigInteger.valueOf(i);
+ c = b.modPow(BigInteger.valueOf(2), p);
+ t = t.multiply(c).mod(p);
+ r = r.multiply(b).mod(p);
+ }
+ return r;
+ }
+
+ public static ECPoint fromX962(byte[] data, EllipticCurve curve) {
+ if (data == null) {
+ return null;
+ }
+ if (data[0] == 0x04 || data[0] == 0x06 || data[0] == 0x07) {
+ int len = (data.length - 1) / 2;
+ byte[] xbytes = new byte[len];
+ System.arraycopy(data, 1, xbytes, 0, len);
+ byte[] ybytes = new byte[len];
+ System.arraycopy(data, 1 + len, ybytes, 0, len);
+ return new ECPoint(new BigInteger(1, xbytes), new BigInteger(1, ybytes));
+ } else if (data[0] == 0x02 || data[0] == 0x03) {
+ if (curve == null) {
+ throw new IllegalArgumentException();
+ }
+ byte[] xbytes = new byte[data.length - 1];
+ System.arraycopy(data, 1, xbytes, 0, data.length - 1);
+ BigInteger x = new BigInteger(1, xbytes);
+ BigInteger a = curve.getA();
+ BigInteger b = curve.getB();
+
+ ECField field = curve.getField();
+ if (field instanceof ECFieldFp) {
+ BigInteger p = ((ECFieldFp) field).getP();
+ BigInteger alpha = x.modPow(BigInteger.valueOf(3), p);
+ alpha = alpha.add(x.multiply(a));
+ alpha = alpha.add(b);
+
+ BigInteger beta = modSqrt(alpha, p);
+ if (beta.getLowestSetBit() == 0) {
+ // rightmost bit is one
+ if (data[0] == 0x02) {
+ beta = beta.negate();
+ }
+ } else {
+ // rightmost bit is zero
+ if (data[0] == 0x03) {
+ beta = beta.negate();
+ }
+ }
+
+ return new ECPoint(x, beta);
+ } else if (field instanceof ECFieldF2m) {
+ //TODO
+ throw new UnsupportedOperationException();
+ }
+ return null;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+}