diff options
Diffstat (limited to 'src/cz/crcs/ectester/common')
30 files changed, 2784 insertions, 0 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..f1a1980 --- /dev/null +++ b/src/cz/crcs/ectester/common/cli/TreeParser.java @@ -0,0 +1,128 @@ +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())); + } + } + + int maxArgs = args.size(); + 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); + } else if (lastCli.getArgs().length > maxArgs) { + throw new MissingArgumentException("Too many arguments."); + } + + subTreeCli.setName(sub); + return new TreeCommandLine(cli, subTreeCli); + } else if (subCli != null) { + if (subCli.getArgs().length < requiredArgs) { + throw new MissingArgumentException("Not enough arguments: " + reqArgs); + } else if (subCli.getArgs().length > maxArgs) { + throw new MissingArgumentException("Too many arguments."); + } + + TreeCommandLine subTreeCli = new TreeCommandLine(sub, subCli, null); + return new TreeCommandLine(cli, subTreeCli); + } else { + if (cliArgs.length < requiredArgs) { + throw new MissingArgumentException("Not enough arguments: " + reqArgs); + } else if (cliArgs.length > maxArgs) { + throw new MissingArgumentException("Too many arguments."); + } + + return new TreeCommandLine(cli, null); + } + } +} diff --git a/src/cz/crcs/ectester/common/ec/EC_Category.java b/src/cz/crcs/ectester/common/ec/EC_Category.java new file mode 100644 index 0000000..32a788d --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Category.java @@ -0,0 +1,142 @@ +package cz.crcs.ectester.common.ec; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +/** + * A category of EC_Data objects, has a name, description and represents a directory in + * the cz.crcs.ectester.data package. + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Category { + + private String name; + private String directory; + private String desc; + + private Map<String, EC_Data> objects; + + + public EC_Category(String name, String directory) { + this.name = name; + this.directory = directory; + } + + public EC_Category(String name, String directory, String desc) { + this(name, directory); + this.desc = desc; + } + + public EC_Category(String name, String directory, String desc, Map<String, EC_Data> objects) { + this(name, directory, desc); + this.objects = objects; + } + + public String getName() { + return name; + } + + public String getDirectory() { + return directory; + } + + public String getDesc() { + return desc; + } + + public Map<String, EC_Data> getObjects() { + return Collections.unmodifiableMap(objects); + } + + public <T extends EC_Data> Map<String, T> getObjects(Class<T> cls) { + Map<String, T> objs = new TreeMap<>(); + for (Map.Entry<String, EC_Data> entry : objects.entrySet()) { + if (cls.isInstance(entry.getValue())) { + objs.put(entry.getKey(), cls.cast(entry.getValue())); + } + } + return Collections.unmodifiableMap(objs); + } + + public <T extends EC_Data> T getObject(Class<T> cls, String id) { + EC_Data obj = objects.get(id); + if (cls.isInstance(obj)) { + return cls.cast(obj); + } else { + return null; + } + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("\t- ").append(name).append((desc == null || desc.equals("")) ? "" : ": " + desc); + out.append(System.lineSeparator()); + + Map<String, EC_Curve> curves = getObjects(EC_Curve.class); + int size = curves.size(); + if (size > 0) { + out.append("\t\tCurves: "); + for (Map.Entry<String, EC_Curve> curve : curves.entrySet()) { + out.append(curve.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map<String, EC_Key> keys = getObjects(EC_Key.class); + size = keys.size(); + if (size > 0) { + out.append("\t\tKeys: "); + for (Map.Entry<String, EC_Key> key : keys.entrySet()) { + out.append(key.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map<String, EC_Keypair> keypairs = getObjects(EC_Keypair.class); + size = keypairs.size(); + if (size > 0) { + out.append("\t\tKeypairs: "); + for (Map.Entry<String, EC_Keypair> key : keypairs.entrySet()) { + out.append(key.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map<String, EC_KAResult> results = getObjects(EC_KAResult.class); + size = results.size(); + if (size > 0) { + out.append("\t\tResults: "); + for (Map.Entry<String, EC_KAResult> result : results.entrySet()) { + out.append(result.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + return out.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof EC_Category && Objects.equals(this.name, ((EC_Category) obj).name); + } + + @Override + public int hashCode() { + return this.name.hashCode() ^ this.directory.hashCode(); + } + +} diff --git a/src/cz/crcs/ectester/common/ec/EC_Curve.java b/src/cz/crcs/ectester/common/ec/EC_Curve.java new file mode 100644 index 0000000..173fd29 --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Curve.java @@ -0,0 +1,132 @@ +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)?. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Curve extends EC_Params { + private short bits; + private byte field; + private String desc; + + /** + * @param bits + * @param field KeyPair.ALG_EC_FP or KeyPair.ALG_EC_F2M + */ + public EC_Curve(short bits, byte field) { + super(field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M); + this.bits = bits; + this.field = field; + } + + public EC_Curve(String id, short bits, byte field) { + this(bits, field); + this.id = id; + } + + public EC_Curve(String id, short bits, byte field, String desc) { + this(id, bits, field); + this.desc = desc; + } + + public short getBits() { + return bits; + } + + public byte getField() { + return field; + } + + public String getDesc() { + return desc; + } + + @Override + public String toString() { + return "<" + getId() + "> " + (field == KeyPair.ALG_EC_FP ? "Prime" : "Binary") + " field Elliptic curve (" + String.valueOf(bits) + "b)" + (desc == null ? "" : ": " + desc); + } + + 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 new file mode 100644 index 0000000..c048ef7 --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Data.java @@ -0,0 +1,217 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.common.util.ByteUtil; + +import java.io.*; +import java.util.*; +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 { + String id; + int count; + byte[][] data; + + private static final Pattern HEX = Pattern.compile("(0x|0X)?[a-fA-F\\d]+"); + + EC_Data() { + } + + EC_Data(int count) { + this.count = count; + this.data = new byte[count][]; + } + + EC_Data(byte[][] data) { + this.count = data.length; + this.data = data; + } + + EC_Data(String id, int count) { + this(count); + this.id = id; + } + + EC_Data(String id, byte[][] data) { + this(data); + this.id = id; + } + + public String getId() { + return id; + } + + public int getCount() { + return count; + } + + public byte[][] getData() { + return data; + } + + public byte[] getData(int index) { + return data[index]; + } + + public boolean hasData() { + return data != null; + } + + public byte[] flatten() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (byte[] param : data) { + byte[] length = new byte[2]; + ByteUtil.setShort(length, 0, (short) param.length); + + out.write(length, 0, 2); + out.write(param, 0, param.length); + } + + return out.toByteArray(); + } + + public String[] expand() { + List<String> out = new ArrayList<>(count); + for (byte[] param : data) { + out.add(ByteUtil.bytesToHex(param, false)); + } + + return out.toArray(new String[out.size()]); + } + + private static byte[] pad(byte[] data) { + if (data.length == 1) { + return new byte[]{(byte) 0, data[0]}; + } else if (data.length == 0 || data.length > 2) { + return data; + } + return null; + } + + private static byte[] parse(String param) { + byte[] data; + if (param.startsWith("0x") || param.startsWith("0X")) { + data = ByteUtil.hexToBytes(param.substring(2)); + } else { + data = ByteUtil.hexToBytes(param); + } + if (data == null) + return new byte[0]; + if (data.length < 2) + return pad(data); + return data; + } + + private boolean readHex(String[] hex) { + if (hex.length != count) { + return false; + } + + for (int i = 0; i < count; ++i) { + this.data[i] = parse(hex[i]); + } + return true; + } + + public boolean readCSV(InputStream in) { + Scanner s = new Scanner(in); + + s.useDelimiter(",|;"); + List<String> data = new LinkedList<>(); + while (s.hasNext()) { + String field = s.next(); + data.add(field.replaceAll("\\s+", "")); + } + + if (data.isEmpty()) { + return false; + } + for (String param : data) { + if (!HEX.matcher(param).matches()) { + return false; + } + } + return readHex(data.toArray(new String[data.size()])); + } + + public boolean readBytes(byte[] bytes) { + if (bytes == null) { + return false; + } + + int offset = 0; + for (int i = 0; i < count; i++) { + if (bytes.length - offset < 2) { + return false; + } + short paramLength = ByteUtil.getShort(bytes, offset); + offset += 2; + if (bytes.length < offset + paramLength) { + return false; + } + data[i] = new byte[paramLength]; + System.arraycopy(bytes, offset, data[i], 0, paramLength); + offset += paramLength; + } + return true; + } + + public 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())); + w.flush(); + } + + @Override + public String toString() { + return String.join(",", expand()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof EC_Data) { + EC_Data other = (EC_Data) obj; + if (this.id != null || other.id != null) { + return Objects.equals(this.id, other.id); + } + + if (this.count != other.count) + return false; + for (int i = 0; i < this.count; ++i) { + if (!Arrays.equals(this.data[i], other.data[i])) { + return false; + } + } + return true; + } else { + return false; + } + } + + @Override + public int hashCode() { + if (this.id != null) { + return this.id.hashCode(); + } + return Arrays.deepHashCode(this.data); + } +} diff --git a/src/cz/crcs/ectester/common/ec/EC_KAResult.java b/src/cz/crcs/ectester/common/ec/EC_KAResult.java new file mode 100644 index 0000000..8a5fcb4 --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_KAResult.java @@ -0,0 +1,65 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.common.util.CardUtil; + +/** + * A result of EC based Key agreement operation. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_KAResult extends EC_Data { + private String ka; + private String curve; + private String oneKey; + private String otherKey; + + private String desc; + + public EC_KAResult(String ka, String curve, String oneKey, String otherKey) { + super(1); + this.ka = ka; + this.curve = curve; + this.oneKey = oneKey; + this.otherKey = otherKey; + } + + public EC_KAResult(String id, String ka, String curve, String oneKey, String otherKey) { + this(ka, curve, oneKey, otherKey); + this.id = id; + } + + public EC_KAResult(String id, String ka, String curve, String oneKey, String otherKey, String desc) { + this(id, ka, curve, oneKey, otherKey); + this.desc = desc; + } + + public String getKA() { + return ka; + } + + public byte getJavaCardKA() { + return CardUtil.getKA(ka); + } + + public String getCurve() { + return curve; + } + + public String getOneKey() { + return oneKey; + } + + public String getOtherKey() { + return otherKey; + } + + public String getDesc() { + return desc; + } + + @Override + public String toString() { + return "<" + getId() + "> " + ka + " result over " + curve + ", " + oneKey + " + " + otherKey + (desc == null ? "" : ": " + desc); + } + +} diff --git a/src/cz/crcs/ectester/common/ec/EC_Key.java b/src/cz/crcs/ectester/common/ec/EC_Key.java new file mode 100644 index 0000000..a34b0e7 --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Key.java @@ -0,0 +1,83 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.applet.EC_Consts; + +/** + * An abstract-like EC key. Concrete implementations create a public and private keys. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Key extends EC_Params { + + private String curve; + private String desc; + + private EC_Key(short mask, String curve) { + super(mask); + this.curve = curve; + } + + private EC_Key(short mask, String curve, String desc) { + this(mask, curve); + this.desc = desc; + } + + private EC_Key(String id, short mask, String curve, String desc) { + this(mask, curve, desc); + this.id = id; + } + + public String getCurve() { + return curve; + } + + public String getDesc() { + return desc; + } + + /** + * An EC public key, contains the W parameter. + */ + public static class Public extends EC_Key { + + public Public(String curve) { + super(EC_Consts.PARAMETER_W, curve); + } + + public Public(String curve, String desc) { + super(EC_Consts.PARAMETER_W, curve, desc); + } + + public Public(String id, String curve, String desc) { + super(id, EC_Consts.PARAMETER_W, curve, desc); + } + + @Override + public String toString() { + return "<" + getId() + "> EC Public key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc()); + } + } + + /** + * An EC private key, contains the S parameter. + */ + public static class Private extends EC_Key { + + public Private(String curve) { + super(EC_Consts.PARAMETER_S, curve); + } + + public Private(String curve, String desc) { + super(EC_Consts.PARAMETER_S, curve, desc); + } + + public Private(String id, String curve, String desc) { + super(id, EC_Consts.PARAMETER_S, curve, desc); + } + + @Override + public String toString() { + return "<" + getId() + "> EC Private key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc()); + } + } +} diff --git a/src/cz/crcs/ectester/common/ec/EC_Keypair.java b/src/cz/crcs/ectester/common/ec/EC_Keypair.java new file mode 100644 index 0000000..53632cd --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Keypair.java @@ -0,0 +1,41 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.applet.EC_Consts; + +/** + * An EC keypair, contains both the W and S parameters. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Keypair extends EC_Params { + private String curve; + private String desc; + + public EC_Keypair(String curve) { + super(EC_Consts.PARAMETERS_KEYPAIR); + this.curve = curve; + } + + public EC_Keypair(String curve, String desc) { + this(curve); + this.desc = desc; + } + + public EC_Keypair(String id, String curve, String desc) { + this(curve, desc); + this.id = id; + } + + public String getCurve() { + return curve; + } + + public String getDesc() { + return desc; + } + + @Override + public String toString() { + return "<" + getId() + "> EC Keypair, over " + curve + (desc == null ? "" : ": " + desc); + } +} diff --git a/src/cz/crcs/ectester/common/ec/EC_Params.java b/src/cz/crcs/ectester/common/ec/EC_Params.java new file mode 100644 index 0000000..1c066e7 --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Params.java @@ -0,0 +1,194 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.common.util.ByteUtil; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * A list of EC parameters, can contain a subset of the Fp/F2M, A, B, G, R, K, W, S parameters. + * + * The set of parameters is uniquely identified by a short bit string. + * The parameters can be exported to a byte array via <code>flatten()</code> or to a comma delimited + * string via <code>expand()</code>. + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Params extends EC_Data { + private short params; + + public EC_Params(short params) { + this.params = params; + this.count = numParams(); + this.data = new byte[this.count][]; + } + + public EC_Params(short params, byte[][] data) { + this.params = params; + this.count = data.length; + this.data = data; + } + + public EC_Params(String id, short params) { + this(params); + this.id = id; + } + + public EC_Params(String id, short params, byte[][] data) { + this(params, data); + this.id = id; + } + + public short getParams() { + return params; + } + + public byte[][] getParam(short param) { + if (!hasParam(param)) { + return null; + } + if (Integer.bitCount(param) != 1) { + return null; + } + short paramMask = EC_Consts.PARAMETER_FP; + byte[][] result = null; + int i = 0; + while (paramMask <= EC_Consts.PARAMETER_S) { + short masked = (short) (this.params & param & paramMask); + short shallow = (short) (this.params & paramMask); + if (masked != 0) { + if (masked == EC_Consts.PARAMETER_F2M) { + result = new byte[4][]; + result[0] = data[i].clone(); + result[1] = data[i+1].clone(); + result[2] = data[i+2].clone(); + result[3] = data[i+3].clone(); + break; + } + if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) { + result = new byte[2][]; + result[0] = data[i].clone(); + result[1] = data[i+1].clone(); + break; + } + result = new byte[1][]; + result[0] = data[i].clone(); + } + if (shallow == EC_Consts.PARAMETER_F2M) { + i += 4; + } else if (shallow == EC_Consts.PARAMETER_G || shallow == EC_Consts.PARAMETER_W) { + i += 2; + } else if (shallow != 0) { + i++; + } + paramMask = (short) (paramMask << 1); + } + return result; + } + + public boolean hasParam(short param) { + return (params & param) != 0; + } + + public int numParams() { + short paramMask = EC_Consts.PARAMETER_FP; + int num = 0; + while (paramMask <= EC_Consts.PARAMETER_S) { + if ((paramMask & params) != 0) { + if (paramMask == EC_Consts.PARAMETER_F2M) { + num += 3; + } + if (paramMask == EC_Consts.PARAMETER_W || paramMask == EC_Consts.PARAMETER_G) { + num += 1; + } + ++num; + } + paramMask = (short) (paramMask << 1); + } + return num; + } + + @Override + public byte[] flatten() { + return flatten(params); + } + + public byte[] flatten(short params) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + short paramMask = EC_Consts.PARAMETER_FP; + int i = 0; + while (paramMask <= EC_Consts.PARAMETER_S) { + short masked = (short) (this.params & params & paramMask); + short shallow = (short) (this.params & paramMask); + if (masked != 0) { + byte[] param = data[i]; + if (masked == EC_Consts.PARAMETER_F2M) { + //add m, e_1, e_2, e_3 + param = ByteUtil.concatenate(param, data[i + 1]); + if (!ByteUtil.allValue(data[i + 2], (byte) 0)) { + param = ByteUtil.concatenate(param, data[i + 2]); + } + 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)"); + } + 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 = 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]; + ByteUtil.setShort(length, 0, (short) param.length); + out.write(length, 0, 2); + //write data + out.write(param, 0, param.length); + } + if (shallow == EC_Consts.PARAMETER_F2M) { + i += 4; + } else if (shallow == EC_Consts.PARAMETER_G || shallow == EC_Consts.PARAMETER_W) { + i += 2; + } else if (shallow != 0) { + i++; + } + paramMask = (short) (paramMask << 1); + } + + return (out.size() == 0) ? null : out.toByteArray(); + } + + @Override + public String[] expand() { + List<String> out = new ArrayList<>(); + + short paramMask = EC_Consts.PARAMETER_FP; + int index = 0; + while (paramMask <= EC_Consts.PARAMETER_S) { + short masked = (short) (params & paramMask); + if (masked != 0) { + byte[] param = data[index]; + if (masked == EC_Consts.PARAMETER_F2M) { + for (int i = 0; i < 4; ++i) { + out.add(ByteUtil.bytesToHex(data[index + i], false)); + } + index += 4; + } else if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) { + out.add(ByteUtil.bytesToHex(param, false)); + out.add(ByteUtil.bytesToHex(data[index + 1], false)); + index += 2; + } else { + out.add(ByteUtil.bytesToHex(param, false)); + index++; + } + } + paramMask = (short) (paramMask << 1); + } + return out.toArray(new String[out.size()]); + } +} diff --git a/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java b/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java new file mode 100644 index 0000000..29eb671 --- /dev/null +++ b/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java @@ -0,0 +1,82 @@ +package cz.crcs.ectester.common.output; + +import cz.crcs.ectester.common.test.*; + +import java.io.PrintStream; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class BaseTextTestWriter implements TestWriter { + private PrintStream output; + + public static int BASE_WIDTH = 90; + + public BaseTextTestWriter(PrintStream output) { + this.output = output; + } + + @Override + public void begin(TestSuite suite) { + output.println("═══ Running test suite: " + suite.getName() + " ═══"); + output.println("═══ " + suite.getDescription()); + output.print(deviceString(suite)); + } + + protected abstract String testableString(Testable t); + + protected abstract String deviceString(TestSuite suite); + + private String testString(Test t, String prefix) { + if (!t.hasRun()) { + return null; + } + boolean compound = t instanceof CompoundTest; + + StringBuilder out = new StringBuilder(); + out.append(t.ok() ? " OK " : "NOK "); + out.append(compound ? "┳ " : "━ "); + int width = BASE_WIDTH - (prefix.length() + 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 (compound) { + 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(prefix).append(" ┗ "); + out.append(testString(tests[i], prefix + " ")); + } else { + out.append(prefix).append(" ┣ "); + out.append(testString(tests[i], prefix + " ┃ ")); + } + + if (i != tests.length - 1) { + out.append(System.lineSeparator()); + } + } + } else { + SimpleTest test = (SimpleTest) t; + out.append(testableString(test.getTestable())); + } + return out.toString(); + } + + @Override + public void outputTest(Test t) { + if (!t.hasRun()) + return; + output.println(testString(t, "")); + output.flush(); + } + + @Override + public void end() { + } +} diff --git a/src/cz/crcs/ectester/common/output/BaseXMLTestWriter.java b/src/cz/crcs/ectester/common/output/BaseXMLTestWriter.java new file mode 100644 index 0000000..f3e9411 --- /dev/null +++ b/src/cz/crcs/ectester/common/output/BaseXMLTestWriter.java @@ -0,0 +1,103 @@ +package cz.crcs.ectester.common.output; + +import cz.crcs.ectester.common.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; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class BaseXMLTestWriter implements TestWriter { + private OutputStream output; + private DocumentBuilder db; + protected Document doc; + private Node root; + + public BaseXMLTestWriter(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); + root.appendChild(deviceElement(suite)); + } + + protected abstract Element testableElement(Testable t); + + protected abstract Element deviceElement(TestSuite suite); + + private Element testElement(Test t) { + Element testElem; + if (t instanceof CompoundTest) { + CompoundTest test = (CompoundTest) t; + testElem = doc.createElement("test"); + testElem.setAttribute("type", "compound"); + for (Test innerTest : test.getTests()) { + testElem.appendChild(testElement(innerTest)); + } + } else { + SimpleTest test = (SimpleTest) t; + testElem = testableElement(test.getTestable()); + } + + 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(); + } + } +} diff --git a/src/cz/crcs/ectester/common/output/BaseYAMLTestWriter.java b/src/cz/crcs/ectester/common/output/BaseYAMLTestWriter.java new file mode 100644 index 0000000..0769e83 --- /dev/null +++ b/src/cz/crcs/ectester/common/output/BaseYAMLTestWriter.java @@ -0,0 +1,91 @@ +package cz.crcs.ectester.common.output; + +import cz.crcs.ectester.common.test.*; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class BaseYAMLTestWriter implements TestWriter { + private PrintStream output; + private Map<String, Object> testRun; + private Map<String, String> testSuite; + protected List<Object> tests; + + public BaseYAMLTestWriter(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("device", deviceObject(suite)); + testRun.put("tests", tests); + } + + abstract protected Map<String, Object> testableObject(Testable t); + + abstract protected Map<String, Object> deviceObject(TestSuite suite); + + private Map<String, Object> testObject(Test t) { + Map<String, Object> testObj; + if (t instanceof CompoundTest) { + CompoundTest test = (CompoundTest) t; + testObj = new HashMap<>(); + testObj.put("type", "compound"); + List<Map<String, Object>> innerTests = new LinkedList<>(); + for (Test innerTest : test.getTests()) { + innerTests.add(testObject(innerTest)); + } + testObj.put("tests", innerTests); + } else { + SimpleTest test = (SimpleTest) t; + testObj = testableObject(test.getTestable()); + } + + 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/output/OutputLogger.java b/src/cz/crcs/ectester/common/output/OutputLogger.java new file mode 100644 index 0000000..09b8f73 --- /dev/null +++ b/src/cz/crcs/ectester/common/output/OutputLogger.java @@ -0,0 +1,60 @@ +package cz.crcs.ectester.common.output; + +import java.io.*; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Petr Svenda petr@svenda.com + * @author Jan Jancar johny@neuromancer.sk + */ +public class OutputLogger { + private OutputStream out; + private PrintStream print; + + public OutputLogger(boolean systemOut, String... filePaths) throws IOException { + List<OutputStream> streams = new LinkedList<>(); + for (String filePath : filePaths) { + if (filePath != null) { + streams.add(new FileOutputStream(filePath)); + } + } + if (systemOut) { + streams.add(System.out); + } + this.out = new TeeOutputStream(streams.toArray(new OutputStream[0])); + this.print = new PrintStream(this.out); + } + + public OutputLogger(String filePath) throws IOException { + this(true, filePath); + } + + public OutputStream getOutputStream() { + return this.out; + } + + public PrintStream getPrintStream() { + return this.print; + } + + public void println() { + print.println(); + } + + public void println(String logLine) { + print.println(logLine); + } + + public void print(String logLine) { + print.print(logLine); + } + + public void flush() { + print.flush(); + } + + public void close() { + print.close(); + } +} diff --git a/src/cz/crcs/ectester/common/output/TeeOutputStream.java b/src/cz/crcs/ectester/common/output/TeeOutputStream.java new file mode 100644 index 0000000..2401fce --- /dev/null +++ b/src/cz/crcs/ectester/common/output/TeeOutputStream.java @@ -0,0 +1,36 @@ +package cz.crcs.ectester.common.output; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class TeeOutputStream extends OutputStream { + private OutputStream[] outputs; + + public TeeOutputStream(OutputStream... outputs) { + this.outputs = outputs; + } + + @Override + public void write(int b) throws IOException { + for (OutputStream out : outputs) { + out.write(b); + } + } + + @Override + public void flush() throws IOException { + for (OutputStream out : outputs) { + out.flush(); + } + } + + @Override + public void close() throws IOException { + for (OutputStream out : outputs) { + out.close(); + } + } +} diff --git a/src/cz/crcs/ectester/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/test/BaseTestable.java b/src/cz/crcs/ectester/common/test/BaseTestable.java new file mode 100644 index 0000000..a4b9a00 --- /dev/null +++ b/src/cz/crcs/ectester/common/test/BaseTestable.java @@ -0,0 +1,32 @@ +package cz.crcs.ectester.common.test; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class BaseTestable implements Testable { + protected boolean hasRun; + protected boolean ok; + protected boolean error; + + @Override + public boolean hasRun() { + return hasRun; + } + + @Override + public boolean ok() { + return ok; + } + + @Override + public boolean error() { + return error; + } + + @Override + public void reset() { + hasRun = false; + ok = false; + error = false; + } +} diff --git a/src/cz/crcs/ectester/common/test/CompoundTest.java b/src/cz/crcs/ectester/common/test/CompoundTest.java new file mode 100644 index 0000000..10ecf9c --- /dev/null +++ b/src/cz/crcs/ectester/common/test/CompoundTest.java @@ -0,0 +1,111 @@ +package cz.crcs.ectester.common.test; + +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Function; + +/** + * A compound test that runs many Tests and has a Result dependent on all/some of their Results. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class CompoundTest extends Test { + private Function<Test[], Result> callback; + private Test[] tests; + private String description; + + private CompoundTest(Function<Test[], Result> callback, Test... tests) { + this.callback = callback; + this.tests = Arrays.stream(tests).filter(Objects::nonNull).toArray(Test[]::new); + } + + private CompoundTest(Function<Test[], Result> callback, String descripiton, Test... tests) { + this(callback, tests); + this.description = descripiton; + } + + public static CompoundTest function(Function<Test[], Result> callback, Test... tests) { + return new CompoundTest(callback, tests); + } + + public static CompoundTest function(Function<Test[], Result> callback, String description, Test... tests) { + return new CompoundTest(callback, description, tests); + } + + public static CompoundTest all(Result.ExpectedValue what, Test... all) { + return new CompoundTest((tests) -> { + for (Test test : tests) { + if (!Result.Value.fromExpected(what, test.ok()).ok()) { + return new Result(Result.Value.FAILURE, "Some sub-tests did not have the expected result."); + } + } + return new Result(Result.Value.SUCCESS, "All sub-tests had the expected result."); + }, all); + } + + public static CompoundTest all(Result.ExpectedValue what, String description, Test... all) { + CompoundTest result = CompoundTest.all(what, all); + result.setDescription(description); + return result; + } + + public static CompoundTest any(Result.ExpectedValue what, Test... any) { + return new CompoundTest((tests) -> { + for (Test test : tests) { + if (Result.Value.fromExpected(what, test.ok()).ok()) { + return new Result(Result.Value.SUCCESS, "Some sub-tests did have the expected result."); + } + } + return new Result(Result.Value.FAILURE, "None of the sub-tests had the expected result."); + }, any); + } + + public static CompoundTest any(Result.ExpectedValue what, String description, Test... any) { + CompoundTest result = CompoundTest.any(what, any); + result.setDescription(description); + return result; + } + + public static CompoundTest mask(Result.ExpectedValue[] results, Test... masked) { + return new CompoundTest((tests) -> { + for (int i = 0; i < results.length; ++i) { + if (!Result.Value.fromExpected(results[i], tests[i].ok()).ok()) { + return new Result(Result.Value.FAILURE, "Some sub-tests did not match the result mask."); + } + } + return new Result(Result.Value.SUCCESS, "All sub-tests matched the expected mask."); + }, masked); + } + + public static CompoundTest mask(Result.ExpectedValue[] results, String description, Test... masked) { + CompoundTest result = CompoundTest.mask(results, masked); + result.setDescription(description); + return result; + } + + public Test[] getTests() { + return tests; + } + + @Override + public void run() throws TestException { + if (hasRun) + return; + + for (Test test : tests) { + test.run(); + } + + result = callback.apply(tests); + this.hasRun = true; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/src/cz/crcs/ectester/common/test/Result.java b/src/cz/crcs/ectester/common/test/Result.java new file mode 100644 index 0000000..11fcb4d --- /dev/null +++ b/src/cz/crcs/ectester/common/test/Result.java @@ -0,0 +1,96 @@ +package cz.crcs.ectester.common.test; + +/** + * A Result of a Test. Has a Value and an optional String cause. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class Result { + + private Value value; + private String cause; + + public Result(Value value) { + this.value = value; + } + + public Result(Value value, String cause) { + this(value); + this.cause = cause; + } + + public Value getValue() { + return value; + } + + public String getCause() { + return cause; + } + + public boolean ok() { + return value.ok(); + } + + public boolean compareTo(Result other) { + if (other == null) { + return false; + } + return value == other.value; + } + + public boolean compareTo(Value other) { + if (other == null) { + return false; + } + return value == other; + } + + /** + * A result value of a Test. + */ + public enum Value { + SUCCESS(true), + FAILURE(false), + UXSUCCESS(false), + XFAILURE(true), + ERROR(false); + + private boolean ok; + + Value(boolean ok) { + this.ok = ok; + } + + public static Value fromExpected(ExpectedValue expected, boolean successful) { + switch (expected) { + case SUCCESS: + return successful ? SUCCESS : FAILURE; + case FAILURE: + return successful ? UXSUCCESS : XFAILURE; + case ANY: + return SUCCESS; + } + return SUCCESS; + } + + public static Value fromExpected(ExpectedValue expected, boolean successful, boolean error) { + if (error) { + return ERROR; + } + return fromExpected(expected, successful); + } + + public boolean ok() { + return ok; + } + } + + /** + * A possible expected value result of a Test. + */ + public enum ExpectedValue { + SUCCESS, + FAILURE, + ANY + } +} 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 new file mode 100644 index 0000000..3d0baf6 --- /dev/null +++ b/src/cz/crcs/ectester/common/test/Test.java @@ -0,0 +1,66 @@ +package cz.crcs.ectester.common.test; + +import static cz.crcs.ectester.common.test.Result.Value; + +/** + * An abstract test that can be run and has a Result. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class Test implements Testable { + protected boolean hasRun; + protected Result result; + + public Result getResult() { + if (!hasRun) { + return null; + } + return result; + } + + public Value getResultValue() { + if (!hasRun) { + return null; + } + return result.getValue(); + } + + public String getResultCause() { + if (!hasRun) { + return null; + } + return result.getCause(); + } + + public boolean ok() { + if (!hasRun) { + return true; + } + return result.ok(); + } + + @Override + public boolean error() { + if (!hasRun) { + return false; + } + return result.compareTo(Value.ERROR); + } + + @Override + public boolean hasRun() { + return hasRun; + } + + @Override + public void reset() { + hasRun = false; + result = null; + } + + 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/TestException.java b/src/cz/crcs/ectester/common/test/TestException.java new file mode 100644 index 0000000..008e9f6 --- /dev/null +++ b/src/cz/crcs/ectester/common/test/TestException.java @@ -0,0 +1,13 @@ +package cz.crcs.ectester.common.test; + +/** + * A TestException is an Exception that can be thrown during the running of a Testable, + * or a TestSuite. It means that the Testable/TestSuite encountered an unexpected error + * during it's run which points to an error in ECTester or it's runtime environment.cd + * @author Jan Jancar johny@neuromancer.sk + */ +public class TestException extends Exception { + public TestException(Throwable e) { + super(e); + } +} 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..f4f30ee --- /dev/null +++ b/src/cz/crcs/ectester/common/test/TestSuite.java @@ -0,0 +1,56 @@ +package cz.crcs.ectester.common.test; + +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.data.EC_Store; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class TestSuite { + protected String name; + protected String description; + protected TestWriter writer; + + public TestSuite(TestWriter writer, String name, String description) { + this.writer = writer; + this.name = name; + this.description = description; + } + + public void run() throws TestException { + writer.begin(this); + try { + runTests(); + } catch (Exception e) { + throw new TestException(e); + } + writer.end(); + } + + protected Test runTest(Test t) throws TestException { + t.run(); + return t; + } + + protected Test doTest(Test t) throws TestException { + t.run(); + writer.outputTest(t); + return t; + } + + protected abstract void runTests() throws Exception; + + 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 new file mode 100644 index 0000000..33c9485 --- /dev/null +++ b/src/cz/crcs/ectester/common/test/Testable.java @@ -0,0 +1,33 @@ +package cz.crcs.ectester.common.test; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public interface Testable { + /** + * @return Whether this Testable was OK. + */ + boolean ok(); + + /** + * @return Whether an error happened. + */ + boolean error(); + + /** + * @return Whether this runnable was run. + */ + boolean hasRun(); + + /** + * + */ + void reset(); + + /** + * Run this Runnable. + * + * @throws TestException If an unexpected exception/error is encountered. + */ + void run() throws TestException; +} 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..90c6eaa --- /dev/null +++ b/src/cz/crcs/ectester/common/util/ByteUtil.java @@ -0,0 +1,128 @@ +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) { + if (data == null) { + return ""; + } + 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) { + if (data == null) { + return ""; + } + 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/CardUtil.java b/src/cz/crcs/ectester/common/util/CardUtil.java new file mode 100644 index 0000000..8285d8b --- /dev/null +++ b/src/cz/crcs/ectester/common/util/CardUtil.java @@ -0,0 +1,272 @@ +package cz.crcs.ectester.common.util; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import javacard.framework.ISO7816; +import javacard.security.CryptoException; + +import static cz.crcs.ectester.applet.ECTesterApplet.*; + +/** + * @author Petr Svenda petr@svenda.com + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardUtil { + public static byte getKA(String name) { + switch (name) { + case "DH": + case "ECDH": + return ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH; + case "DHC": + case "ECDHC": + return ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DHC; + default: + return ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH; + } + } + + public static String getSWSource(short sw) { + switch (sw) { + case ISO7816.SW_NO_ERROR: + case ISO7816.SW_APPLET_SELECT_FAILED: + case ISO7816.SW_BYTES_REMAINING_00: + case ISO7816.SW_CLA_NOT_SUPPORTED: + case ISO7816.SW_COMMAND_NOT_ALLOWED: + case ISO7816.SW_CONDITIONS_NOT_SATISFIED: + case ISO7816.SW_CORRECT_LENGTH_00: + case ISO7816.SW_DATA_INVALID: + case ISO7816.SW_FILE_FULL: + case ISO7816.SW_FILE_INVALID: + case ISO7816.SW_FILE_NOT_FOUND: + case ISO7816.SW_FUNC_NOT_SUPPORTED: + case ISO7816.SW_INCORRECT_P1P2: + case ISO7816.SW_INS_NOT_SUPPORTED: + case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED: + case ISO7816.SW_RECORD_NOT_FOUND: + case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED: + case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED: + case ISO7816.SW_UNKNOWN: + case ISO7816.SW_WARNING_STATE_UNCHANGED: + case ISO7816.SW_WRONG_DATA: + case ISO7816.SW_WRONG_LENGTH: + case ISO7816.SW_WRONG_P1P2: + return "ISO"; + case CryptoException.ILLEGAL_VALUE: + case CryptoException.UNINITIALIZED_KEY: + case CryptoException.NO_SUCH_ALGORITHM: + case CryptoException.INVALID_INIT: + case CryptoException.ILLEGAL_USE: + return "CryptoException"; + case ECTesterApplet.SW_SIG_VERIFY_FAIL: + case ECTesterApplet.SW_DH_DHC_MISMATCH: + case ECTesterApplet.SW_KEYPAIR_NULL: + case ECTesterApplet.SW_KA_NULL: + case ECTesterApplet.SW_SIGNATURE_NULL: + case ECTesterApplet.SW_OBJECT_NULL: + return "ECTesterApplet"; + default: + return "?"; + } + } + + public static String getSW(short sw) { + switch (sw) { + case ISO7816.SW_APPLET_SELECT_FAILED: + return "APPLET_SELECT_FAILED"; + case ISO7816.SW_BYTES_REMAINING_00: + return "BYTES_REMAINING"; + case ISO7816.SW_CLA_NOT_SUPPORTED: + return "CLA_NOT_SUPPORTED"; + case ISO7816.SW_COMMAND_NOT_ALLOWED: + return "COMMAND_NOT_ALLOWED"; + case ISO7816.SW_CONDITIONS_NOT_SATISFIED: + return "CONDITIONS_NOT_SATISFIED"; + case ISO7816.SW_CORRECT_LENGTH_00: + return "CORRECT_LENGTH"; + case ISO7816.SW_DATA_INVALID: + return "DATA_INVALID"; + case ISO7816.SW_FILE_FULL: + return "FILE_FULL"; + case ISO7816.SW_FILE_INVALID: + return "FILE_INVALID"; + case ISO7816.SW_FILE_NOT_FOUND: + return "FILE_NOT_FOUND"; + case ISO7816.SW_FUNC_NOT_SUPPORTED: + return "FUNC_NOT_SUPPORTED"; + case ISO7816.SW_INCORRECT_P1P2: + return "INCORRECT_P1P2"; + case ISO7816.SW_INS_NOT_SUPPORTED: + return "INS_NOT_SUPPORTED"; + case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED: + return "LOGICAL_CHANNEL_NOT_SUPPORTED"; + case ISO7816.SW_RECORD_NOT_FOUND: + return "RECORD_NOT_FOUND"; + case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED: + return "SECURE_MESSAGING_NOT_SUPPORTED"; + case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED: + return "SECURITY_STATUS_NOT_SATISFIED"; + case ISO7816.SW_UNKNOWN: + return "UNKNOWN"; + case ISO7816.SW_WARNING_STATE_UNCHANGED: + return "WARNING_STATE_UNCHANGED"; + case ISO7816.SW_WRONG_DATA: + return "WRONG_DATA"; + case ISO7816.SW_WRONG_LENGTH: + return "WRONG_LENGTH"; + case ISO7816.SW_WRONG_P1P2: + return "WRONG_P1P2"; + case CryptoException.ILLEGAL_VALUE: + return "ILLEGAL_VALUE"; + case CryptoException.UNINITIALIZED_KEY: + return "UNINITIALIZED_KEY"; + case CryptoException.NO_SUCH_ALGORITHM: + return "NO_SUCH_ALG"; + case CryptoException.INVALID_INIT: + return "INVALID_INIT"; + case CryptoException.ILLEGAL_USE: + return "ILLEGAL_USE"; + case ECTesterApplet.SW_SIG_VERIFY_FAIL: + return "SIG_VERIFY_FAIL"; + case ECTesterApplet.SW_DH_DHC_MISMATCH: + return "DH_DHC_MISMATCH"; + case ECTesterApplet.SW_KEYPAIR_NULL: + return "KEYPAIR_NULL"; + case ECTesterApplet.SW_KA_NULL: + return "KA_NULL"; + case ECTesterApplet.SW_SIGNATURE_NULL: + return "SIGNATURE_NULL"; + case ECTesterApplet.SW_OBJECT_NULL: + return "OBJECT_NULL"; + default: + return "unknown"; + } + } + + public static String getSWString(short sw) { + if (sw == ISO7816.SW_NO_ERROR) { + return "OK (0x9000)"; + } else { + String str = getSW(sw); + return String.format("fail (%s, 0x%04x)", str, sw); + } + } + + public static String getCorruption(short corruptionType) { + switch (corruptionType) { + case EC_Consts.CORRUPTION_NONE: + return "NONE"; + case EC_Consts.CORRUPTION_FIXED: + return "FIXED"; + case EC_Consts.CORRUPTION_ONE: + return "ONE"; + case EC_Consts.CORRUPTION_ZERO: + return "ZERO"; + case EC_Consts.CORRUPTION_ONEBYTERANDOM: + return "ONE_BYTE_RANDOM"; + case EC_Consts.CORRUPTION_FULLRANDOM: + return "FULL_RANDOM"; + case EC_Consts.CORRUPTION_INCREMENT: + return "INCREMENT"; + case EC_Consts.CORRUPTION_INFINITY: + return "INFINITY"; + case EC_Consts.CORRUPTION_COMPRESS: + return "COMPRESSED"; + case EC_Consts.CORRUPTION_MAX: + return "MAX"; + default: + return "unknown"; + } + } + + public static String getKATypeString(byte kaType) { + switch (kaType) { + case KeyAgreement_ALG_EC_SVDP_DH: + return "ALG_EC_SVDP_DH"; + case KeyAgreement_ALG_EC_SVDP_DH_PLAIN: + return "ALG_EC_SVDP_DH_PLAIN"; + case KeyAgreement_ALG_EC_PACE_GM: + return "ALG_EC_PACE_GM"; + case KeyAgreement_ALG_EC_SVDP_DH_PLAIN_XY: + return "ALG_EC_SVDP_DH_PLAIN_XY"; + case KeyAgreement_ALG_EC_SVDP_DHC: + return "ALG_EC_SVDP_DHC"; + case KeyAgreement_ALG_EC_SVDP_DHC_PLAIN: + return "ALG_EC_SVDP_DHC_PLAIN"; + default: + return "unknown"; + } + } + + public static byte getKAType(String kaTypeString) { + switch (kaTypeString) { + case "ALG_EC_SVDP_DH": + return KeyAgreement_ALG_EC_SVDP_DH; + case "ALG_EC_SVDP_DH_PLAIN": + return KeyAgreement_ALG_EC_SVDP_DH_PLAIN; + case "ALG_EC_PACE_GM": + return KeyAgreement_ALG_EC_PACE_GM; + case "ALG_EC_SVDP_DH_PLAIN_XY": + return KeyAgreement_ALG_EC_SVDP_DH_PLAIN_XY; + case "ALG_EC_SVDP_DHC": + return KeyAgreement_ALG_EC_SVDP_DHC; + case "ALG_EC_SVDP_DHC_PLAIN": + return KeyAgreement_ALG_EC_SVDP_DHC_PLAIN; + default: + return 0; + } + } + + public static byte parseKAType(String kaTypeString) { + byte kaType; + try { + kaType = Byte.parseByte(kaTypeString); + } catch (NumberFormatException nfex) { + kaType = getKAType(kaTypeString); + } + return kaType; + } + + public static String getSigTypeString(byte sigType) { + switch (sigType) { + case Signature_ALG_ECDSA_SHA: + return "ALG_ECDSA_SHA"; + case Signature_ALG_ECDSA_SHA_224: + return "ALG_ECDSA_SHA_224"; + case Signature_ALG_ECDSA_SHA_256: + return "ALG_ECDSA_SHA_256"; + case Signature_ALG_ECDSA_SHA_384: + return "ALG_ECDSA_SHA_384"; + case Signature_ALG_ECDSA_SHA_512: + return "ALG_ECDSA_SHA_512"; + default: + return "unknown"; + } + } + + public static byte getSigType(String sigTypeString) { + switch (sigTypeString) { + case "ALG_ECDSA_SHA": + return Signature_ALG_ECDSA_SHA; + case "ALG_ECDSA_SHA_224": + return Signature_ALG_ECDSA_SHA_224; + case "ALG_ECDSA_SHA_256": + return Signature_ALG_ECDSA_SHA_256; + case "ALG_ECDSA_SHA_384": + return Signature_ALG_ECDSA_SHA_384; + case "ALG_ECDSA_SHA_512": + return Signature_ALG_ECDSA_SHA_512; + default: + return 0; + } + } + + public static byte parseSigType(String sigTypeString) { + byte sigType; + try { + sigType = Byte.parseByte(sigTypeString); + } catch (NumberFormatException nfex) { + sigType = getSigType(sigTypeString); + } + return sigType; + } +} 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(); + } + } +} |
