diff options
| author | J08nY | 2017-12-12 12:32:25 +0100 |
|---|---|---|
| committer | J08nY | 2017-12-12 12:32:25 +0100 |
| commit | 4dbc748a207bcee2c8fbe22566646b27a9f61dc0 (patch) | |
| tree | fdf069ac9a25ff82f9509826425bd4796c514da0 /src/cz/crcs/ectester/common | |
| parent | e329190e496ecf847cfd7afa886ac08cacb2fc92 (diff) | |
| parent | 9e8fbbc907f4157988ac425af4dce541f7e42328 (diff) | |
| download | ECTester-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')
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(); + } + } +} |
