From 59a043192903918a68e8d9df629c09221a13c641 Mon Sep 17 00:00:00 2001 From: J08nY Date: Fri, 10 Nov 2017 21:17:54 +0100 Subject: Split the ec package into common package, rename reader part of the project. --- build.xml | 22 - manifest.mf | 2 +- nbproject/project.properties | 2 +- src/cz/crcs/ectester/common/ec/EC_Category.java | 142 ++++ src/cz/crcs/ectester/common/ec/EC_Curve.java | 52 ++ src/cz/crcs/ectester/common/ec/EC_Data.java | 200 +++++ src/cz/crcs/ectester/common/ec/EC_KAResult.java | 63 ++ src/cz/crcs/ectester/common/ec/EC_Key.java | 83 ++ src/cz/crcs/ectester/common/ec/EC_Keypair.java | 41 + src/cz/crcs/ectester/common/ec/EC_Params.java | 151 ++++ src/cz/crcs/ectester/data/EC_Store.java | 2 +- src/cz/crcs/ectester/reader/ECTester.java | 856 --------------------- src/cz/crcs/ectester/reader/ECTesterReader.java | 856 +++++++++++++++++++++ src/cz/crcs/ectester/reader/command/Command.java | 14 +- src/cz/crcs/ectester/reader/ec/EC_Category.java | 142 ---- src/cz/crcs/ectester/reader/ec/EC_Curve.java | 52 -- src/cz/crcs/ectester/reader/ec/EC_Data.java | 200 ----- src/cz/crcs/ectester/reader/ec/EC_KAResult.java | 63 -- src/cz/crcs/ectester/reader/ec/EC_Key.java | 83 -- src/cz/crcs/ectester/reader/ec/EC_Keypair.java | 41 - src/cz/crcs/ectester/reader/ec/EC_Params.java | 151 ---- src/cz/crcs/ectester/reader/response/Response.java | 1 - .../ectester/reader/test/CompositeCurvesSuite.java | 8 +- src/cz/crcs/ectester/reader/test/DefaultSuite.java | 4 +- .../ectester/reader/test/InvalidCurvesSuite.java | 8 +- src/cz/crcs/ectester/reader/test/TestSuite.java | 8 +- .../crcs/ectester/reader/test/TestVectorSuite.java | 6 +- .../ectester/reader/test/WrongCurvesSuite.java | 4 +- 28 files changed, 1617 insertions(+), 1640 deletions(-) create mode 100644 src/cz/crcs/ectester/common/ec/EC_Category.java create mode 100644 src/cz/crcs/ectester/common/ec/EC_Curve.java create mode 100644 src/cz/crcs/ectester/common/ec/EC_Data.java create mode 100644 src/cz/crcs/ectester/common/ec/EC_KAResult.java create mode 100644 src/cz/crcs/ectester/common/ec/EC_Key.java create mode 100644 src/cz/crcs/ectester/common/ec/EC_Keypair.java create mode 100644 src/cz/crcs/ectester/common/ec/EC_Params.java delete mode 100644 src/cz/crcs/ectester/reader/ECTester.java create mode 100644 src/cz/crcs/ectester/reader/ECTesterReader.java delete mode 100644 src/cz/crcs/ectester/reader/ec/EC_Category.java delete mode 100644 src/cz/crcs/ectester/reader/ec/EC_Curve.java delete mode 100644 src/cz/crcs/ectester/reader/ec/EC_Data.java delete mode 100644 src/cz/crcs/ectester/reader/ec/EC_KAResult.java delete mode 100644 src/cz/crcs/ectester/reader/ec/EC_Key.java delete mode 100644 src/cz/crcs/ectester/reader/ec/EC_Keypair.java delete mode 100644 src/cz/crcs/ectester/reader/ec/EC_Params.java diff --git a/build.xml b/build.xml index 3d778f3..f89b743 100644 --- a/build.xml +++ b/build.xml @@ -69,28 +69,6 @@ properties which you can use, check the target you are overriding in the nbproject/build-impl.xml file. - --> - diff --git a/manifest.mf b/manifest.mf index 2cb1a50..cbfea93 100644 --- a/manifest.mf +++ b/manifest.mf @@ -1,4 +1,4 @@ Manifest-Version: 1.0 Class-Path: lib/jcardsim-3.0.4-SNAPSHOT.jar lib/commons-cli-1.3.1.jar lib/snakeyaml-1.19.jar -Main-Class: cz.crcs.ectester.reader.ECTester +Main-Class: cz.crcs.ectester.reader.ECTesterReader diff --git a/nbproject/project.properties b/nbproject/project.properties index 152dc9c..8c65b4e 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -59,7 +59,7 @@ javadoc.splitindex=true javadoc.use=true javadoc.version=false javadoc.windowtitle= -main.class=cz.crcs.ectester.reader.ECTester +main.class=cz.crcs.ectester.reader.ECTesterReader manifest.file=manifest.mf meta.inf.dir=${src.dir}/META-INF mkdist.disabled=false 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 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 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 getObjects() { + return Collections.unmodifiableMap(objects); + } + + public Map getObjects(Class cls) { + Map objs = new TreeMap<>(); + for (Map.Entry entry : objects.entrySet()) { + if (cls.isInstance(entry.getValue())) { + objs.put(entry.getKey(), cls.cast(entry.getValue())); + } + } + return Collections.unmodifiableMap(objs); + } + + public T getObject(Class 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 curves = getObjects(EC_Curve.class); + int size = curves.size(); + if (size > 0) { + out.append("\t\tCurves: "); + for (Map.Entry curve : curves.entrySet()) { + out.append(curve.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map keys = getObjects(EC_Key.class); + size = keys.size(); + if (size > 0) { + out.append("\t\tKeys: "); + for (Map.Entry key : keys.entrySet()) { + out.append(key.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map keypairs = getObjects(EC_Keypair.class); + size = keypairs.size(); + if (size > 0) { + out.append("\t\tKeypairs: "); + for (Map.Entry key : keypairs.entrySet()) { + out.append(key.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map results = getObjects(EC_KAResult.class); + size = results.size(); + if (size > 0) { + out.append("\t\tResults: "); + for (Map.Entry 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..19cfe2d --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Curve.java @@ -0,0 +1,52 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.applet.EC_Consts; +import javacard.security.KeyPair; + +/** + * An Elliptic curve, contains parameters Fp/F2M, A, B, G, R, (K)?. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Curve extends EC_Params { + 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); + } +} 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..da97208 --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Data.java @@ -0,0 +1,200 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.reader.Util; + +import java.io.*; +import java.util.*; +import java.util.regex.Pattern; + +/** + * A list of byte arrays for holding EC data. + * + * The data can be read from a byte array via readBytes(), from a CSV via readCSV(). + * The data can be exported to a byte array via flatten() or to a string array via expand(). + * @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 boolean hasData() { + return data != null; + } + + public byte[] getParam(int index) { + return data[index]; + } + + public byte[] flatten() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (byte[] param : data) { + byte[] length = new byte[2]; + Util.setShort(length, 0, (short) param.length); + + out.write(length, 0, 2); + out.write(param, 0, param.length); + } + + return out.toByteArray(); + } + + public String[] expand() { + List out = new ArrayList<>(count); + for (byte[] param : data) { + out.add(Util.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 = Util.hexToBytes(param.substring(2)); + } else { + data = Util.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 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) { + int offset = 0; + for (int i = 0; i < count; i++) { + if (bytes.length - offset < 2) { + return false; + } + short paramLength = Util.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 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..b215d0e --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_KAResult.java @@ -0,0 +1,63 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.reader.Util; + +/** + * A result of EC based Key agreement operation. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_KAResult extends EC_Data { + + private byte ka; + private String curve; + private String oneKey; + private String otherKey; + + private String desc; + + public EC_KAResult(byte 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, byte ka, String curve, String oneKey, String otherKey) { + this(ka, curve, oneKey, otherKey); + this.id = id; + } + + public EC_KAResult(String id, byte ka, String curve, String oneKey, String otherKey, String desc) { + this(id, ka, curve, oneKey, otherKey); + this.desc = desc; + } + + public byte getKA() { + return 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() { + String algo = Util.getKA(ka); + return "<" + getId() + "> " + algo + " 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..5b8295e --- /dev/null +++ b/src/cz/crcs/ectester/common/ec/EC_Params.java @@ -0,0 +1,151 @@ +package cz.crcs.ectester.common.ec; + +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.reader.Util; + +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 flatten() or to a comma delimited + * string via expand(). + * @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 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 = Util.concatenate(param, data[i + 1]); + if (!Util.allValue(data[i + 2], (byte) 0)) { + param = Util.concatenate(param, data[i + 2]); + } + if (!Util.allValue(data[i + 3], (byte) 0)) { + param = Util.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 = Util.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); + 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 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(Util.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)); + index += 2; + } else { + out.add(Util.bytesToHex(param, false)); + index++; + } + } + paramMask = (short) (paramMask << 1); + } + return out.toArray(new String[out.size()]); + } +} diff --git a/src/cz/crcs/ectester/data/EC_Store.java b/src/cz/crcs/ectester/data/EC_Store.java index 9b1f5bb..3103c1d 100644 --- a/src/cz/crcs/ectester/data/EC_Store.java +++ b/src/cz/crcs/ectester/data/EC_Store.java @@ -1,7 +1,7 @@ package cz.crcs.ectester.data; import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.reader.ec.*; +import cz.crcs.ectester.common.ec.*; import javacard.security.KeyPair; import org.w3c.dom.Document; import org.w3c.dom.Element; diff --git a/src/cz/crcs/ectester/reader/ECTester.java b/src/cz/crcs/ectester/reader/ECTester.java deleted file mode 100644 index 550e070..0000000 --- a/src/cz/crcs/ectester/reader/ECTester.java +++ /dev/null @@ -1,856 +0,0 @@ -/* - * Copyright (c) 2016-2017 Petr Svenda - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package cz.crcs.ectester.reader; - -import cz.crcs.ectester.applet.ECTesterApplet; -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.data.EC_Store; -import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.EC_Category; -import cz.crcs.ectester.reader.ec.EC_Data; -import cz.crcs.ectester.reader.ec.EC_Params; -import cz.crcs.ectester.reader.output.*; -import cz.crcs.ectester.reader.response.Response; -import cz.crcs.ectester.reader.test.*; -import javacard.security.KeyPair; -import org.apache.commons.cli.*; - -import javax.smartcardio.CardException; -import javax.xml.parsers.ParserConfigurationException; -import java.io.*; -import java.nio.file.Files; -import java.util.*; - -import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH; - -/** - * Reader part of ECTester, a tool for testing Elliptic curve support on javacards. - * - * @author Petr Svenda petr@svenda.com - * @author Jan Jancar johny@neuromancer.sk - * @version v0.1.0 - */ -public class ECTester { - - private CardMngr cardManager; - private OutputLogger logger; - private TestWriter testWriter; - private ResponseWriter respWriter; - private EC_Store dataStore; - private Config cfg; - - private Options opts = new Options(); - private static final String VERSION = "v0.1.0"; - private static final String DESCRIPTION = "ECTester " + VERSION + ", a javacard Elliptic Curve Cryptograhy support tester/utility."; - private static final String LICENSE = "MIT Licensed\nCopyright (c) 2016-2017 Petr Svenda "; - private static final String CLI_HEADER = "\n" + DESCRIPTION + "\n\n"; - private static final String CLI_FOOTER = "\n" + LICENSE; - - private static final byte[] SELECT_ECTESTERAPPLET = {(byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0a, - (byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31}; - private static final byte[] AID = {(byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31}; - private static final byte[] INSTALL_DATA = new byte[10]; - - private void run(String[] args) { - try { - CommandLine cli = parseArgs(args); - - //if help, print and quit - if (cli.hasOption("help")) { - help(); - return; - } else if (cli.hasOption("version")) { - System.out.println(DESCRIPTION); - System.out.println(LICENSE); - return; - } - cfg = new Config(); - - //if not, read other options first, into attributes, then do action - if (!cfg.readOptions(cli)) { - return; - } - - dataStore = new EC_Store(); - //if list, print and quit - if (cli.hasOption("list-named")) { - list(); - return; - } - - //init CardManager - cardManager = new CardMngr(cfg.verbose, cfg.simulate); - - //connect or simulate connection - if (cfg.simulate) { - if (!cardManager.prepareLocalSimulatorApplet(AID, INSTALL_DATA, ECTesterApplet.class)) { - System.err.println("Failed to establish a simulator."); - System.exit(1); - } - } else { - if (!cardManager.connectToCardSelect()) { - System.err.println("Failed to connect to card."); - System.exit(1); - } - cardManager.send(SELECT_ECTESTERAPPLET); - } - - // Setup logger, testWriter and respWriter - logger = new OutputLogger(true, cfg.log); - if (cfg.format == null) { - testWriter = new TextTestWriter(logger.getPrintStream()); - } else { - switch (cfg.format) { - case "text": - testWriter = new TextTestWriter(logger.getPrintStream()); - break; - case "xml": - testWriter = new XMLTestWriter(logger.getOutputStream()); - break; - case "yaml": - case "yml": - testWriter = new YAMLTestWriter(logger.getPrintStream()); - break; - } - } - respWriter = new ResponseWriter(logger.getPrintStream()); - - //do action - if (cli.hasOption("export")) { - export(); - } else if (cli.hasOption("generate")) { - generate(); - } else if (cli.hasOption("test")) { - test(); - } else if (cli.hasOption("ecdh") || cli.hasOption("ecdhc")) { - ecdh(); - } else if (cli.hasOption("ecdsa")) { - ecdsa(); - } - - //disconnect - cardManager.disconnectFromCard(); - logger.close(); - - } catch (MissingOptionException moex) { - System.err.println("Missing required options, one of:"); - for (Object opt : moex.getMissingOptions().toArray()) { - if (opt instanceof OptionGroup) { - for (Option o : ((OptionGroup) opt).getOptions()) { - System.err.print("-" + o.getOpt()); - - if (o.hasLongOpt()) { - System.err.print("\t/ --" + o.getLongOpt() + " "); - } - - if (o.hasArg()) { - if (o.hasOptionalArg()) { - System.err.print("[" + o.getArgName() + "] "); - } else { - System.err.print("<" + o.getArgName() + "> "); - } - } - - if (o.getDescription() != null) { - System.err.print("\t\t\t" + o.getDescription()); - } - System.err.println(); - } - } else if (opt instanceof String) { - System.err.println(opt); - } - } - } catch (MissingArgumentException maex) { - System.err.println("Option, " + maex.getOption().getOpt() + " requires an argument: " + maex.getOption().getArgName()); - } catch (NumberFormatException nfex) { - System.err.println("Not a number. " + nfex.getMessage()); - } catch (FileNotFoundException fnfe) { - System.err.println("File " + fnfe.getMessage() + " not found."); - } catch (ParseException | IOException ex) { - System.err.println(ex.getMessage()); - } catch (CardException ex) { - if (logger != null) - logger.println(ex.getMessage()); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } finally { - if (logger != null) - logger.flush(); - } - } - - /** - * Parses command-line options. - * - * @param args cli arguments - * @return parsed CommandLine object - * @throws ParseException if there are any problems encountered while parsing the command line tokens - */ - private CommandLine parseArgs(String[] args) throws ParseException { - /* - * Actions: - * -V / --version - * -h / --help - * -e / --export - * -g / --generate [amount] - * -t / --test [test_suite] - * -dh / --ecdh [count] - * -dhc / --ecdhc [count] - * -dsa / --ecdsa [count] - * -ln / --list-named [obj] - * - * Options: - * -b / --bit-size // -a / --all - * - * -fp / --prime-field - * -f2m / --binary-field - * - * -u / --custom - * -nc / --named-curve - * -c / --curve field,a,b,gx,gy,r,k - * - * -pub / --public wx,wy - * -npub / --named-public - * - * -priv / --private s - * -npriv / --named-private - * - * -k / --key wx,wy,s - * -nk / --named-key - * - * -v / --verbose - * - * -i / --input - * -o / --output - * --format - * -l / --log [log_file] - * - * -f / --fresh - * -s / --simulate - * -y / --yes - * -ka/ --ka-type - */ - OptionGroup actions = new OptionGroup(); - actions.setRequired(true); - actions.addOption(Option.builder("V").longOpt("version").desc("Print version info.").build()); - actions.addOption(Option.builder("h").longOpt("help").desc("Print help.").build()); - actions.addOption(Option.builder("ln").longOpt("list-named").desc("Print the list of supported named curves and keys.").hasArg().argName("what").optionalArg(true).build()); - actions.addOption(Option.builder("e").longOpt("export").desc("Export the defaut curve parameters of the card(if any).").build()); - actions.addOption(Option.builder("g").longOpt("generate").desc("Generate [amount] of EC keys.").hasArg().argName("amount").optionalArg(true).build()); - actions.addOption(Option.builder("t").longOpt("test").desc("Test ECC support. [test_suite]:\n- default:\n- invalid:\n- wrong:\n- composite:\n- test-vectors:").hasArg().argName("test_suite").optionalArg(true).build()); - actions.addOption(Option.builder("dh").longOpt("ecdh").desc("Do ECDH, [count] times.").hasArg().argName("count").optionalArg(true).build()); - actions.addOption(Option.builder("dhc").longOpt("ecdhc").desc("Do ECDHC, [count] times.").hasArg().argName("count").optionalArg(true).build()); - actions.addOption(Option.builder("dsa").longOpt("ecdsa").desc("Sign data with ECDSA, [count] times.").hasArg().argName("count").optionalArg(true).build()); - - opts.addOptionGroup(actions); - - OptionGroup size = new OptionGroup(); - size.addOption(Option.builder("b").longOpt("bit-size").desc("Set curve size.").hasArg().argName("bits").build()); - size.addOption(Option.builder("a").longOpt("all").desc("Test all curve sizes.").build()); - opts.addOptionGroup(size); - - opts.addOption(Option.builder("fp").longOpt("prime-field").desc("Use a prime field.").build()); - opts.addOption(Option.builder("f2m").longOpt("binary-field").desc("Use a binary field.").build()); - - OptionGroup curve = new OptionGroup(); - curve.addOption(Option.builder("nc").longOpt("named-curve").desc("Use a named curve, from CurveDB: ").hasArg().argName("cat/id").build()); - curve.addOption(Option.builder("c").longOpt("curve").desc("Use curve from file (field,a,b,gx,gy,r,k).").hasArg().argName("curve_file").build()); - curve.addOption(Option.builder("u").longOpt("custom").desc("Use a custom curve (applet-side embedded, SECG curves).").build()); - opts.addOptionGroup(curve); - - OptionGroup pub = new OptionGroup(); - pub.addOption(Option.builder("npub").longOpt("named-public").desc("Use public key from KeyDB: ").hasArg().argName("cat/id").build()); - pub.addOption(Option.builder("pub").longOpt("public").desc("Use public key from file (wx,wy).").hasArg().argName("pubkey_file").build()); - opts.addOptionGroup(pub); - - OptionGroup priv = new OptionGroup(); - priv.addOption(Option.builder("npriv").longOpt("named-private").desc("Use private key from KeyDB: ").hasArg().argName("cat/id").build()); - priv.addOption(Option.builder("priv").longOpt("private").desc("Use private key from file (s).").hasArg().argName("privkey_file").build()); - opts.addOptionGroup(priv); - - OptionGroup key = new OptionGroup(); - key.addOption(Option.builder("nk").longOpt("named-key").desc("Use keyPair from KeyDB: ").hasArg().argName("cat/id").build()); - key.addOption(Option.builder("k").longOpt("key").desc("Use keyPair from file  (wx,wy,s).").hasArg().argName("key_file").build()); - opts.addOptionGroup(key); - - opts.addOption(Option.builder("i").longOpt("input").desc("Input from file , for ECDSA signing.").hasArg().argName("input_file").build()); - opts.addOption(Option.builder("o").longOpt("output").desc("Output into file .").hasArg().argName("output_file").build()); - opts.addOption(Option.builder("l").longOpt("log").desc("Log output into file [log_file].").hasArg().argName("log_file").optionalArg(true).build()); - opts.addOption(Option.builder("v").longOpt("verbose").desc("Turn on verbose logging.").build()); - opts.addOption(Option.builder().longOpt("format").desc("Output format to use. One of: text,yml,xml.").hasArg().argName("format").build()); - - opts.addOption(Option.builder("f").longOpt("fresh").desc("Generate fresh keys (set domain parameters before every generation).").build()); - opts.addOption(Option.builder("s").longOpt("simulate").desc("Simulate a card with jcardsim instead of using a terminal.").build()); - opts.addOption(Option.builder("y").longOpt("yes").desc("Accept all warnings and prompts.").build()); - - opts.addOption(Option.builder("ka").longOpt("ka-type").desc("Set KeyAgreement object [type], corresponds to JC.KeyAgreement constants.").hasArg().argName("type").optionalArg(true).build()); - - CommandLineParser parser = new DefaultParser(); - return parser.parse(opts, args); - } - - /** - * Prints help. - */ - private void help() { - HelpFormatter help = new HelpFormatter(); - help.setOptionComparator(null); - help.printHelp("ECTester.jar", CLI_HEADER, opts, CLI_FOOTER, true); - } - - /** - * List categories and named curves. - */ - private void list() { - Map categories = dataStore.getCategories(); - if (cfg.listNamed == null) { - // print all categories, briefly - for (EC_Category cat : categories.values()) { - System.out.println(cat); - } - } else if (categories.containsKey(cfg.listNamed)) { - // print given category - System.out.println(categories.get(cfg.listNamed)); - } else { - // print given object - EC_Data object = dataStore.getObject(EC_Data.class, cfg.listNamed); - if (object != null) { - System.out.println(object); - } else { - System.err.println("Named object " + cfg.listNamed + " not found!"); - } - } - } - - /** - * Exports default card/simulation EC domain parameters to output file. - * - * @throws CardException if APDU transmission fails - * @throws IOException if an IO error occurs when writing to key file. - */ - private void export() throws CardException, IOException { - byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; - - List sent = new LinkedList<>(); - sent.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send()); - sent.add(new Command.Clear(cardManager, ECTesterApplet.KEYPAIR_LOCAL).send()); - sent.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL).send()); - - // Cofactor generally isn't set on the default curve parameters on cards, - // since its not necessary for ECDH, only ECDHC which not many cards implement - // TODO: check if its assumend to be == 1? - short domainAll = cfg.primeField ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M; - short domain = (short) (domainAll ^ EC_Consts.PARAMETER_K); - Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domainAll).send(); - if (!export.successful()) { - export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domain).send(); - } - sent.add(export); - - for (Response r : sent) { - respWriter.outputResponse(r); - } - - EC_Params exported = new EC_Params(domain, export.getParams()); - - FileOutputStream out = new FileOutputStream(cfg.output); - exported.writeCSV(out); - out.close(); - } - - /** - * Generates EC keyPairs and outputs them to output file. - * - * @throws CardException if APDU transmission fails - * @throws IOException if an IO error occurs when writing to key file. - */ - private void generate() throws CardException, IOException { - byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; - - new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send(); - Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); - - FileWriter keysFile = new FileWriter(cfg.output); - keysFile.write("index;time;pubW;privS\n"); - - int generated = 0; - int retry = 0; - while (generated < cfg.generateAmount || cfg.generateAmount == 0) { - if ((cfg.fresh || generated == 0) && curve != null) { - Response fresh = curve.send(); - respWriter.outputResponse(fresh); - } - - Command.Generate generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL); - Response.Generate response = generate.send(); - long elapsed = response.getDuration(); - - Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR).send(); - - if (!response.successful() || !export.successful()) { - if (retry < 10) { - retry++; - continue; - } else { - System.err.println("Keys could not be generated."); - break; - } - } - respWriter.outputResponse(response); - - String pub = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false); - String priv = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false); - String line = String.format("%d;%d;%s;%s\n", generated, elapsed / 1000000, pub, priv); - keysFile.write(line); - keysFile.flush(); - generated++; - } - Response cleanup = new Command.Cleanup(cardManager).send(); - respWriter.outputResponse(cleanup); - - keysFile.close(); - } - - /** - * Tests Elliptic curve support for a given curve/curves. - * - * @throws CardException if APDU transmission fails - * @throws IOException if an IO error occurs when writing to key file. - */ - private void test() throws IOException, CardException { - TestSuite suite; - - switch (cfg.testSuite) { - case "default": - suite = new DefaultSuite(dataStore, cfg); - break; - case "test-vectors": - suite = new TestVectorSuite(dataStore, cfg); - break; - default: - // These tests are dangerous, prompt before them. - System.out.println("The test you selected (" + cfg.testSuite + ") is potentially dangerous."); - System.out.println("Some of these tests have caused temporary DoS of some cards."); - if (!cfg.yes) { - System.out.print("Do you want to proceed? (y/n): "); - Scanner in = new Scanner(System.in); - String confirmation = in.nextLine().toLowerCase(); - if (!Arrays.asList("yes", "y").contains(confirmation)) { - return; - } - in.close(); - } - - - switch (cfg.testSuite) { - case "wrong": - suite = new WrongCurvesSuite(dataStore, cfg); - break; - case "composite": - suite = new CompositeCurvesSuite(dataStore, cfg); - break; - case "invalid": - suite = new InvalidCurvesSuite(dataStore, cfg); - break; - default: - System.err.println("Unknown test suite."); - return; - } - break; - } - - TestRunner runner = new TestRunner(suite, testWriter); - suite.setup(cardManager); - runner.run(); - } - - /** - * Performs ECDH key exchange. - * - * @throws CardException if APDU transmission fails - * @throws IOException if an IO error occurs when writing to key file. - */ - private void ecdh() throws IOException, CardException { - byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; - List prepare = new LinkedList<>(); - prepare.add(new Command.AllocateKeyAgreement(cardManager, cfg.kaType).send()); // Prepare KeyAgreement or required type - prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass).send()); - Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass); - if (curve != null) - prepare.add(curve.send()); - - for (Response r : prepare) { - respWriter.outputResponse(r); - } - - byte pubkey = (cfg.anyPublicKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL; - byte privkey = (cfg.anyPrivateKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL; - - List generate = new LinkedList<>(); - generate.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH)); - if (cfg.anyPublicKey || cfg.anyPrivateKey || cfg.anyKey) { - generate.add(Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_REMOTE)); - } - - FileWriter out = null; - if (cfg.output != null) { - out = new FileWriter(cfg.output); - out.write("index;time;secret\n"); - } - - int retry = 0; - int done = 0; - while (done < cfg.ECDHCount) { - List ecdh = Command.sendAll(generate); - - Response.ECDH perform = new Command.ECDH(cardManager, pubkey, privkey, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, cfg.ECDHKA).send(); - ecdh.add(perform); - for (Response r : ecdh) { - respWriter.outputResponse(r); - } - - if (!perform.successful() || !perform.hasSecret()) { - if (retry < 10) { - ++retry; - continue; - } else { - System.err.println("Couldn't obtain ECDH secret from card response."); - break; - } - } - - if (out != null) { - out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSecret(), false))); - } - - ++done; - } - Response cleanup = new Command.Cleanup(cardManager).send(); - respWriter.outputResponse(cleanup); - - if (out != null) - out.close(); - } - - /** - * Performs ECDSA signature, on random or provided data. - * - * @throws CardException if APDU transmission fails - * @throws IOException if an IO error occurs when writing to key file. - */ - private void ecdsa() throws CardException, IOException { - //read file, if asked to sign - byte[] data = null; - if (cfg.input != null) { - File in = new File(cfg.input); - long len = in.length(); - if (len == 0) { - throw new FileNotFoundException(cfg.input); - } - data = Files.readAllBytes(in.toPath()); - } - - Command generate; - if (cfg.anyKeypart) { - generate = Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL); - } else { - generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL); - } - - byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; - List prepare = new LinkedList<>(); - prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send()); - Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); - if (curve != null) - prepare.add(curve.send()); - - for (Response r : prepare) { - respWriter.outputResponse(r); - } - - FileWriter out = null; - if (cfg.output != null) { - out = new FileWriter(cfg.output); - out.write("index;time;signature\n"); - } - - int retry = 0; - int done = 0; - while (done < cfg.ECDSACount) { - List ecdsa = new LinkedList<>(); - ecdsa.add(generate.send()); - - Response.ECDSA perform = new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, data).send(); - ecdsa.add(perform); - for (Response r : ecdsa) { - respWriter.outputResponse(r); - } - - if (!perform.successful() || !perform.hasSignature()) { - if (retry < 10) { - ++retry; - continue; - } else { - System.err.println("Couldn't obtain ECDSA signature from card response."); - break; - } - } - - if (out != null) { - out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSignature(), false))); - } - - ++done; - } - Response cleanup = new Command.Cleanup(cardManager).send(); - respWriter.outputResponse(cleanup); - - if (out != null) - out.close(); - } - - public static void main(String[] args) { - ECTester app = new ECTester(); - app.run(args); - } - - public static class Config { - - //Options - public int bits; - public boolean all; - public boolean primeField = false; - public boolean binaryField = false; - public byte kaType = KeyAgreement_ALG_EC_SVDP_DH; - - public String namedCurve; - public String curveFile; - public boolean customCurve = false; - - public boolean anyPublicKey = false; - public String namedPublicKey; - public String publicKey; - - public boolean anyPrivateKey = false; - public String namedPrivateKey; - public String privateKey; - - public boolean anyKey = false; - public String namedKey; - public String key; - - public boolean anyKeypart = false; - - public String log; - - public boolean verbose = false; - public String input; - public String output; - public boolean fresh = false; - public boolean simulate = false; - public boolean yes = false; - public String format; - - //Action-related options - public String listNamed; - public String testSuite; - public int generateAmount; - public int ECDHCount; - public byte ECDHKA; - public int ECDSACount; - - /** - * Reads and validates options, also sets defaults. - * - * @param cli cli object, with parsed args - * @return whether the options are valid. - */ - boolean readOptions(CommandLine cli) { - bits = Integer.parseInt(cli.getOptionValue("bit-size", "0")); - all = cli.hasOption("all"); - primeField = cli.hasOption("fp"); - binaryField = cli.hasOption("f2m"); - kaType = Byte.parseByte(cli.getOptionValue("ka-type", "1")); - - namedCurve = cli.getOptionValue("named-curve"); - customCurve = cli.hasOption("custom"); - curveFile = cli.getOptionValue("curve"); - - namedPublicKey = cli.getOptionValue("named-public"); - publicKey = cli.getOptionValue("public"); - anyPublicKey = (publicKey != null) || (namedPublicKey != null); - - namedPrivateKey = cli.getOptionValue("named-private"); - privateKey = cli.getOptionValue("private"); - anyPrivateKey = (privateKey != null) || (namedPrivateKey != null); - - namedKey = cli.getOptionValue("named-key"); - key = cli.getOptionValue("key"); - anyKey = (key != null) || (namedKey != null); - anyKeypart = anyKey || anyPublicKey || anyPrivateKey; - - if (cli.hasOption("log")) { - log = cli.getOptionValue("log", String.format("ECTESTER_log_%d.log", System.currentTimeMillis() / 1000)); - } - - verbose = cli.hasOption("verbose"); - input = cli.getOptionValue("input"); - output = cli.getOptionValue("output"); - fresh = cli.hasOption("fresh"); - simulate = cli.hasOption("simulate"); - yes = cli.hasOption("yes"); - - if (cli.hasOption("list-named")) { - listNamed = cli.getOptionValue("list-named"); - return true; - } - - format = cli.getOptionValue("format", "text"); - String formats[] = new String[]{"text", "xml", "yaml", "yml"}; - if (!Arrays.asList(formats).contains(format)) { - System.err.println("Wrong output format " + format + ". Should be one of " + Arrays.toString(formats)); - return false; - } - - if ((key != null || namedKey != null) && (anyPublicKey || anyPrivateKey)) { - System.err.print("Can only specify the whole key with --key/--named-key or pubkey and privkey with --public/--named-public and --private/--named-private."); - return false; - } - if (bits < 0) { - System.err.println("Bit-size must not be negative."); - return false; - } - if (bits == 0 && !all) { - System.err.println("You must specify either bit-size with -b or all bit-sizes with -a."); - return false; - } - - if (key != null && namedKey != null || publicKey != null && namedPublicKey != null || privateKey != null && namedPrivateKey != null) { - System.err.println("You cannot specify both a named key and a key file."); - return false; - } - - if (cli.hasOption("export")) { - if (primeField == binaryField) { - System.err.print("Need to specify field with -fp or -f2m. (not both)"); - return false; - } - if (anyKeypart) { - System.err.println("Keys should not be specified when exporting curve params."); - return false; - } - if (namedCurve != null || customCurve || curveFile != null) { - System.err.println("Specifying a curve for curve export makes no sense."); - return false; - } - if (output == null) { - System.err.println("You have to specify an output file for curve parameter export."); - return false; - } - if (all) { - System.err.println("You have to specify curve bit-size with -b"); - return false; - } - - } else if (cli.hasOption("generate")) { - if (primeField == binaryField) { - System.err.print("Need to specify field with -fp or -f2m. (not both)"); - return false; - } - if (anyKeypart) { - System.err.println("Keys should not be specified when generating keys."); - return false; - } - if (output == null) { - System.err.println("You have to specify an output file for the key generation process."); - return false; - } - if (all) { - System.err.println("You have to specify curve bit-size with -b"); - return false; - } - - generateAmount = Integer.parseInt(cli.getOptionValue("generate", "0")); - if (generateAmount < 0) { - System.err.println("Amount of keys generated cant be negative."); - return false; - } - } else if (cli.hasOption("test")) { - if (!(binaryField || primeField)) { - binaryField = true; - primeField = true; - } - - testSuite = cli.getOptionValue("test", "default").toLowerCase(); - String[] tests = new String[]{"default", "composite", "invalid", "test-vectors", "wrong"}; - if (!Arrays.asList(tests).contains(testSuite)) { - System.err.println("Unknown test suite " + testSuite + ". Should be one of: " + Arrays.toString(tests)); - return false; - } - - } else if (cli.hasOption("ecdh") || cli.hasOption("ecdhc")) { - if (primeField == binaryField) { - System.err.print("Need to specify field with -fp or -f2m. (not both)"); - return false; - } - if (all) { - System.err.println("You have to specify curve bit-size with -b"); - return false; - } - - if (cli.hasOption("ecdh")) { - ECDHCount = Integer.parseInt(cli.getOptionValue("ecdh", "1")); - ECDHKA = EC_Consts.KA_ECDH; - } else if (cli.hasOption("ecdhc")) { - ECDHCount = Integer.parseInt(cli.getOptionValue("ecdhc", "1")); - ECDHKA = EC_Consts.KA_ECDHC; - } - if (ECDHCount <= 0) { - System.err.println("ECDH count cannot be <= 0."); - return false; - } - - } else if (cli.hasOption("ecdsa")) { - if (primeField == binaryField) { - System.err.print("Need to specify field with -fp or -f2m. (but not both)"); - return false; - } - if (all) { - System.err.println("You have to specify curve bit-size with -b"); - return false; - } - - if ((anyPublicKey) != (anyPrivateKey) && !anyKey) { - System.err.println("You cannot only specify a part of a keypair."); - return false; - } - - ECDSACount = Integer.parseInt(cli.getOptionValue("ecdsa", "1")); - if (ECDSACount <= 0) { - System.err.println("ECDSA count cannot be <= 0."); - return false; - } - } - return true; - } - } -} diff --git a/src/cz/crcs/ectester/reader/ECTesterReader.java b/src/cz/crcs/ectester/reader/ECTesterReader.java new file mode 100644 index 0000000..4eadfd3 --- /dev/null +++ b/src/cz/crcs/ectester/reader/ECTesterReader.java @@ -0,0 +1,856 @@ +/* + * Copyright (c) 2016-2017 Petr Svenda + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.data.EC_Store; +import cz.crcs.ectester.reader.command.Command; +import cz.crcs.ectester.common.ec.EC_Category; +import cz.crcs.ectester.common.ec.EC_Data; +import cz.crcs.ectester.common.ec.EC_Params; +import cz.crcs.ectester.reader.output.*; +import cz.crcs.ectester.reader.response.Response; +import cz.crcs.ectester.reader.test.*; +import javacard.security.KeyPair; +import org.apache.commons.cli.*; + +import javax.smartcardio.CardException; +import javax.xml.parsers.ParserConfigurationException; +import java.io.*; +import java.nio.file.Files; +import java.util.*; + +import static cz.crcs.ectester.applet.ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH; + +/** + * Reader part of ECTester, a tool for testing Elliptic curve support on javacards. + * + * @author Petr Svenda petr@svenda.com + * @author Jan Jancar johny@neuromancer.sk + * @version v0.1.0 + */ +public class ECTesterReader { + + private CardMngr cardManager; + private OutputLogger logger; + private TestWriter testWriter; + private ResponseWriter respWriter; + private EC_Store dataStore; + private Config cfg; + + private Options opts = new Options(); + private static final String VERSION = "v0.1.0"; + private static final String DESCRIPTION = "ECTesterReader " + VERSION + ", a javacard Elliptic Curve Cryptograhy support tester/utility."; + private static final String LICENSE = "MIT Licensed\nCopyright (c) 2016-2017 Petr Svenda "; + private static final String CLI_HEADER = "\n" + DESCRIPTION + "\n\n"; + private static final String CLI_FOOTER = "\n" + LICENSE; + + private static final byte[] SELECT_ECTESTERAPPLET = {(byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0a, + (byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31}; + private static final byte[] AID = {(byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31}; + private static final byte[] INSTALL_DATA = new byte[10]; + + private void run(String[] args) { + try { + CommandLine cli = parseArgs(args); + + //if help, print and quit + if (cli.hasOption("help")) { + help(); + return; + } else if (cli.hasOption("version")) { + System.out.println(DESCRIPTION); + System.out.println(LICENSE); + return; + } + cfg = new Config(); + + //if not, read other options first, into attributes, then do action + if (!cfg.readOptions(cli)) { + return; + } + + dataStore = new EC_Store(); + //if list, print and quit + if (cli.hasOption("list-named")) { + list(); + return; + } + + //init CardManager + cardManager = new CardMngr(cfg.verbose, cfg.simulate); + + //connect or simulate connection + if (cfg.simulate) { + if (!cardManager.prepareLocalSimulatorApplet(AID, INSTALL_DATA, ECTesterApplet.class)) { + System.err.println("Failed to establish a simulator."); + System.exit(1); + } + } else { + if (!cardManager.connectToCardSelect()) { + System.err.println("Failed to connect to card."); + System.exit(1); + } + cardManager.send(SELECT_ECTESTERAPPLET); + } + + // Setup logger, testWriter and respWriter + logger = new OutputLogger(true, cfg.log); + if (cfg.format == null) { + testWriter = new TextTestWriter(logger.getPrintStream()); + } else { + switch (cfg.format) { + case "text": + testWriter = new TextTestWriter(logger.getPrintStream()); + break; + case "xml": + testWriter = new XMLTestWriter(logger.getOutputStream()); + break; + case "yaml": + case "yml": + testWriter = new YAMLTestWriter(logger.getPrintStream()); + break; + } + } + respWriter = new ResponseWriter(logger.getPrintStream()); + + //do action + if (cli.hasOption("export")) { + export(); + } else if (cli.hasOption("generate")) { + generate(); + } else if (cli.hasOption("test")) { + test(); + } else if (cli.hasOption("ecdh") || cli.hasOption("ecdhc")) { + ecdh(); + } else if (cli.hasOption("ecdsa")) { + ecdsa(); + } + + //disconnect + cardManager.disconnectFromCard(); + logger.close(); + + } catch (MissingOptionException moex) { + System.err.println("Missing required options, one of:"); + for (Object opt : moex.getMissingOptions().toArray()) { + if (opt instanceof OptionGroup) { + for (Option o : ((OptionGroup) opt).getOptions()) { + System.err.print("-" + o.getOpt()); + + if (o.hasLongOpt()) { + System.err.print("\t/ --" + o.getLongOpt() + " "); + } + + if (o.hasArg()) { + if (o.hasOptionalArg()) { + System.err.print("[" + o.getArgName() + "] "); + } else { + System.err.print("<" + o.getArgName() + "> "); + } + } + + if (o.getDescription() != null) { + System.err.print("\t\t\t" + o.getDescription()); + } + System.err.println(); + } + } else if (opt instanceof String) { + System.err.println(opt); + } + } + } catch (MissingArgumentException maex) { + System.err.println("Option, " + maex.getOption().getOpt() + " requires an argument: " + maex.getOption().getArgName()); + } catch (NumberFormatException nfex) { + System.err.println("Not a number. " + nfex.getMessage()); + } catch (FileNotFoundException fnfe) { + System.err.println("File " + fnfe.getMessage() + " not found."); + } catch (ParseException | IOException ex) { + System.err.println(ex.getMessage()); + } catch (CardException ex) { + if (logger != null) + logger.println(ex.getMessage()); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } finally { + if (logger != null) + logger.flush(); + } + } + + /** + * Parses command-line options. + * + * @param args cli arguments + * @return parsed CommandLine object + * @throws ParseException if there are any problems encountered while parsing the command line tokens + */ + private CommandLine parseArgs(String[] args) throws ParseException { + /* + * Actions: + * -V / --version + * -h / --help + * -e / --export + * -g / --generate [amount] + * -t / --test [test_suite] + * -dh / --ecdh [count] + * -dhc / --ecdhc [count] + * -dsa / --ecdsa [count] + * -ln / --list-named [obj] + * + * Options: + * -b / --bit-size // -a / --all + * + * -fp / --prime-field + * -f2m / --binary-field + * + * -u / --custom + * -nc / --named-curve + * -c / --curve field,a,b,gx,gy,r,k + * + * -pub / --public wx,wy + * -npub / --named-public + * + * -priv / --private s + * -npriv / --named-private + * + * -k / --key wx,wy,s + * -nk / --named-key + * + * -v / --verbose + * + * -i / --input + * -o / --output + * --format + * -l / --log [log_file] + * + * -f / --fresh + * -s / --simulate + * -y / --yes + * -ka/ --ka-type + */ + OptionGroup actions = new OptionGroup(); + actions.setRequired(true); + actions.addOption(Option.builder("V").longOpt("version").desc("Print version info.").build()); + actions.addOption(Option.builder("h").longOpt("help").desc("Print help.").build()); + actions.addOption(Option.builder("ln").longOpt("list-named").desc("Print the list of supported named curves and keys.").hasArg().argName("what").optionalArg(true).build()); + actions.addOption(Option.builder("e").longOpt("export").desc("Export the defaut curve parameters of the card(if any).").build()); + actions.addOption(Option.builder("g").longOpt("generate").desc("Generate [amount] of EC keys.").hasArg().argName("amount").optionalArg(true).build()); + actions.addOption(Option.builder("t").longOpt("test").desc("Test ECC support. [test_suite]:\n- default:\n- invalid:\n- wrong:\n- composite:\n- test-vectors:").hasArg().argName("test_suite").optionalArg(true).build()); + actions.addOption(Option.builder("dh").longOpt("ecdh").desc("Do ECDH, [count] times.").hasArg().argName("count").optionalArg(true).build()); + actions.addOption(Option.builder("dhc").longOpt("ecdhc").desc("Do ECDHC, [count] times.").hasArg().argName("count").optionalArg(true).build()); + actions.addOption(Option.builder("dsa").longOpt("ecdsa").desc("Sign data with ECDSA, [count] times.").hasArg().argName("count").optionalArg(true).build()); + + opts.addOptionGroup(actions); + + OptionGroup size = new OptionGroup(); + size.addOption(Option.builder("b").longOpt("bit-size").desc("Set curve size.").hasArg().argName("bits").build()); + size.addOption(Option.builder("a").longOpt("all").desc("Test all curve sizes.").build()); + opts.addOptionGroup(size); + + opts.addOption(Option.builder("fp").longOpt("prime-field").desc("Use a prime field.").build()); + opts.addOption(Option.builder("f2m").longOpt("binary-field").desc("Use a binary field.").build()); + + OptionGroup curve = new OptionGroup(); + curve.addOption(Option.builder("nc").longOpt("named-curve").desc("Use a named curve, from CurveDB: ").hasArg().argName("cat/id").build()); + curve.addOption(Option.builder("c").longOpt("curve").desc("Use curve from file (field,a,b,gx,gy,r,k).").hasArg().argName("curve_file").build()); + curve.addOption(Option.builder("u").longOpt("custom").desc("Use a custom curve (applet-side embedded, SECG curves).").build()); + opts.addOptionGroup(curve); + + OptionGroup pub = new OptionGroup(); + pub.addOption(Option.builder("npub").longOpt("named-public").desc("Use public key from KeyDB: ").hasArg().argName("cat/id").build()); + pub.addOption(Option.builder("pub").longOpt("public").desc("Use public key from file (wx,wy).").hasArg().argName("pubkey_file").build()); + opts.addOptionGroup(pub); + + OptionGroup priv = new OptionGroup(); + priv.addOption(Option.builder("npriv").longOpt("named-private").desc("Use private key from KeyDB: ").hasArg().argName("cat/id").build()); + priv.addOption(Option.builder("priv").longOpt("private").desc("Use private key from file (s).").hasArg().argName("privkey_file").build()); + opts.addOptionGroup(priv); + + OptionGroup key = new OptionGroup(); + key.addOption(Option.builder("nk").longOpt("named-key").desc("Use keyPair from KeyDB: ").hasArg().argName("cat/id").build()); + key.addOption(Option.builder("k").longOpt("key").desc("Use keyPair from file  (wx,wy,s).").hasArg().argName("key_file").build()); + opts.addOptionGroup(key); + + opts.addOption(Option.builder("i").longOpt("input").desc("Input from file , for ECDSA signing.").hasArg().argName("input_file").build()); + opts.addOption(Option.builder("o").longOpt("output").desc("Output into file .").hasArg().argName("output_file").build()); + opts.addOption(Option.builder("l").longOpt("log").desc("Log output into file [log_file].").hasArg().argName("log_file").optionalArg(true).build()); + opts.addOption(Option.builder("v").longOpt("verbose").desc("Turn on verbose logging.").build()); + opts.addOption(Option.builder().longOpt("format").desc("Output format to use. One of: text,yml,xml.").hasArg().argName("format").build()); + + opts.addOption(Option.builder("f").longOpt("fresh").desc("Generate fresh keys (set domain parameters before every generation).").build()); + opts.addOption(Option.builder("s").longOpt("simulate").desc("Simulate a card with jcardsim instead of using a terminal.").build()); + opts.addOption(Option.builder("y").longOpt("yes").desc("Accept all warnings and prompts.").build()); + + opts.addOption(Option.builder("ka").longOpt("ka-type").desc("Set KeyAgreement object [type], corresponds to JC.KeyAgreement constants.").hasArg().argName("type").optionalArg(true).build()); + + CommandLineParser parser = new DefaultParser(); + return parser.parse(opts, args); + } + + /** + * Prints help. + */ + private void help() { + HelpFormatter help = new HelpFormatter(); + help.setOptionComparator(null); + help.printHelp("ECTesterReader.jar", CLI_HEADER, opts, CLI_FOOTER, true); + } + + /** + * List categories and named curves. + */ + private void list() { + Map categories = dataStore.getCategories(); + if (cfg.listNamed == null) { + // print all categories, briefly + for (EC_Category cat : categories.values()) { + System.out.println(cat); + } + } else if (categories.containsKey(cfg.listNamed)) { + // print given category + System.out.println(categories.get(cfg.listNamed)); + } else { + // print given object + EC_Data object = dataStore.getObject(EC_Data.class, cfg.listNamed); + if (object != null) { + System.out.println(object); + } else { + System.err.println("Named object " + cfg.listNamed + " not found!"); + } + } + } + + /** + * Exports default card/simulation EC domain parameters to output file. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void export() throws CardException, IOException { + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + + List sent = new LinkedList<>(); + sent.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send()); + sent.add(new Command.Clear(cardManager, ECTesterApplet.KEYPAIR_LOCAL).send()); + sent.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL).send()); + + // Cofactor generally isn't set on the default curve parameters on cards, + // since its not necessary for ECDH, only ECDHC which not many cards implement + // TODO: check if its assumend to be == 1? + short domainAll = cfg.primeField ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M; + short domain = (short) (domainAll ^ EC_Consts.PARAMETER_K); + Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domainAll).send(); + if (!export.successful()) { + export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domain).send(); + } + sent.add(export); + + for (Response r : sent) { + respWriter.outputResponse(r); + } + + EC_Params exported = new EC_Params(domain, export.getParams()); + + FileOutputStream out = new FileOutputStream(cfg.output); + exported.writeCSV(out); + out.close(); + } + + /** + * Generates EC keyPairs and outputs them to output file. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void generate() throws CardException, IOException { + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + + new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send(); + Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); + + FileWriter keysFile = new FileWriter(cfg.output); + keysFile.write("index;time;pubW;privS\n"); + + int generated = 0; + int retry = 0; + while (generated < cfg.generateAmount || cfg.generateAmount == 0) { + if ((cfg.fresh || generated == 0) && curve != null) { + Response fresh = curve.send(); + respWriter.outputResponse(fresh); + } + + Command.Generate generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL); + Response.Generate response = generate.send(); + long elapsed = response.getDuration(); + + Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR).send(); + + if (!response.successful() || !export.successful()) { + if (retry < 10) { + retry++; + continue; + } else { + System.err.println("Keys could not be generated."); + break; + } + } + respWriter.outputResponse(response); + + String pub = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false); + String priv = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false); + String line = String.format("%d;%d;%s;%s\n", generated, elapsed / 1000000, pub, priv); + keysFile.write(line); + keysFile.flush(); + generated++; + } + Response cleanup = new Command.Cleanup(cardManager).send(); + respWriter.outputResponse(cleanup); + + keysFile.close(); + } + + /** + * Tests Elliptic curve support for a given curve/curves. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void test() throws IOException, CardException { + TestSuite suite; + + switch (cfg.testSuite) { + case "default": + suite = new DefaultSuite(dataStore, cfg); + break; + case "test-vectors": + suite = new TestVectorSuite(dataStore, cfg); + break; + default: + // These tests are dangerous, prompt before them. + System.out.println("The test you selected (" + cfg.testSuite + ") is potentially dangerous."); + System.out.println("Some of these tests have caused temporary DoS of some cards."); + if (!cfg.yes) { + System.out.print("Do you want to proceed? (y/n): "); + Scanner in = new Scanner(System.in); + String confirmation = in.nextLine().toLowerCase(); + if (!Arrays.asList("yes", "y").contains(confirmation)) { + return; + } + in.close(); + } + + + switch (cfg.testSuite) { + case "wrong": + suite = new WrongCurvesSuite(dataStore, cfg); + break; + case "composite": + suite = new CompositeCurvesSuite(dataStore, cfg); + break; + case "invalid": + suite = new InvalidCurvesSuite(dataStore, cfg); + break; + default: + System.err.println("Unknown test suite."); + return; + } + break; + } + + TestRunner runner = new TestRunner(suite, testWriter); + suite.setup(cardManager); + runner.run(); + } + + /** + * Performs ECDH key exchange. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void ecdh() throws IOException, CardException { + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + List prepare = new LinkedList<>(); + prepare.add(new Command.AllocateKeyAgreement(cardManager, cfg.kaType).send()); // Prepare KeyAgreement or required type + prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass).send()); + Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass); + if (curve != null) + prepare.add(curve.send()); + + for (Response r : prepare) { + respWriter.outputResponse(r); + } + + byte pubkey = (cfg.anyPublicKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL; + byte privkey = (cfg.anyPrivateKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL; + + List generate = new LinkedList<>(); + generate.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH)); + if (cfg.anyPublicKey || cfg.anyPrivateKey || cfg.anyKey) { + generate.add(Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_REMOTE)); + } + + FileWriter out = null; + if (cfg.output != null) { + out = new FileWriter(cfg.output); + out.write("index;time;secret\n"); + } + + int retry = 0; + int done = 0; + while (done < cfg.ECDHCount) { + List ecdh = Command.sendAll(generate); + + Response.ECDH perform = new Command.ECDH(cardManager, pubkey, privkey, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, cfg.ECDHKA).send(); + ecdh.add(perform); + for (Response r : ecdh) { + respWriter.outputResponse(r); + } + + if (!perform.successful() || !perform.hasSecret()) { + if (retry < 10) { + ++retry; + continue; + } else { + System.err.println("Couldn't obtain ECDH secret from card response."); + break; + } + } + + if (out != null) { + out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSecret(), false))); + } + + ++done; + } + Response cleanup = new Command.Cleanup(cardManager).send(); + respWriter.outputResponse(cleanup); + + if (out != null) + out.close(); + } + + /** + * Performs ECDSA signature, on random or provided data. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void ecdsa() throws CardException, IOException { + //read file, if asked to sign + byte[] data = null; + if (cfg.input != null) { + File in = new File(cfg.input); + long len = in.length(); + if (len == 0) { + throw new FileNotFoundException(cfg.input); + } + data = Files.readAllBytes(in.toPath()); + } + + Command generate; + if (cfg.anyKeypart) { + generate = Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL); + } else { + generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL); + } + + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + List prepare = new LinkedList<>(); + prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send()); + Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); + if (curve != null) + prepare.add(curve.send()); + + for (Response r : prepare) { + respWriter.outputResponse(r); + } + + FileWriter out = null; + if (cfg.output != null) { + out = new FileWriter(cfg.output); + out.write("index;time;signature\n"); + } + + int retry = 0; + int done = 0; + while (done < cfg.ECDSACount) { + List ecdsa = new LinkedList<>(); + ecdsa.add(generate.send()); + + Response.ECDSA perform = new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, data).send(); + ecdsa.add(perform); + for (Response r : ecdsa) { + respWriter.outputResponse(r); + } + + if (!perform.successful() || !perform.hasSignature()) { + if (retry < 10) { + ++retry; + continue; + } else { + System.err.println("Couldn't obtain ECDSA signature from card response."); + break; + } + } + + if (out != null) { + out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSignature(), false))); + } + + ++done; + } + Response cleanup = new Command.Cleanup(cardManager).send(); + respWriter.outputResponse(cleanup); + + if (out != null) + out.close(); + } + + public static void main(String[] args) { + ECTesterReader app = new ECTesterReader(); + app.run(args); + } + + public static class Config { + + //Options + public int bits; + public boolean all; + public boolean primeField = false; + public boolean binaryField = false; + public byte kaType = KeyAgreement_ALG_EC_SVDP_DH; + + public String namedCurve; + public String curveFile; + public boolean customCurve = false; + + public boolean anyPublicKey = false; + public String namedPublicKey; + public String publicKey; + + public boolean anyPrivateKey = false; + public String namedPrivateKey; + public String privateKey; + + public boolean anyKey = false; + public String namedKey; + public String key; + + public boolean anyKeypart = false; + + public String log; + + public boolean verbose = false; + public String input; + public String output; + public boolean fresh = false; + public boolean simulate = false; + public boolean yes = false; + public String format; + + //Action-related options + public String listNamed; + public String testSuite; + public int generateAmount; + public int ECDHCount; + public byte ECDHKA; + public int ECDSACount; + + /** + * Reads and validates options, also sets defaults. + * + * @param cli cli object, with parsed args + * @return whether the options are valid. + */ + boolean readOptions(CommandLine cli) { + bits = Integer.parseInt(cli.getOptionValue("bit-size", "0")); + all = cli.hasOption("all"); + primeField = cli.hasOption("fp"); + binaryField = cli.hasOption("f2m"); + kaType = Byte.parseByte(cli.getOptionValue("ka-type", "1")); + + namedCurve = cli.getOptionValue("named-curve"); + customCurve = cli.hasOption("custom"); + curveFile = cli.getOptionValue("curve"); + + namedPublicKey = cli.getOptionValue("named-public"); + publicKey = cli.getOptionValue("public"); + anyPublicKey = (publicKey != null) || (namedPublicKey != null); + + namedPrivateKey = cli.getOptionValue("named-private"); + privateKey = cli.getOptionValue("private"); + anyPrivateKey = (privateKey != null) || (namedPrivateKey != null); + + namedKey = cli.getOptionValue("named-key"); + key = cli.getOptionValue("key"); + anyKey = (key != null) || (namedKey != null); + anyKeypart = anyKey || anyPublicKey || anyPrivateKey; + + if (cli.hasOption("log")) { + log = cli.getOptionValue("log", String.format("ECTESTER_log_%d.log", System.currentTimeMillis() / 1000)); + } + + verbose = cli.hasOption("verbose"); + input = cli.getOptionValue("input"); + output = cli.getOptionValue("output"); + fresh = cli.hasOption("fresh"); + simulate = cli.hasOption("simulate"); + yes = cli.hasOption("yes"); + + if (cli.hasOption("list-named")) { + listNamed = cli.getOptionValue("list-named"); + return true; + } + + format = cli.getOptionValue("format", "text"); + String formats[] = new String[]{"text", "xml", "yaml", "yml"}; + if (!Arrays.asList(formats).contains(format)) { + System.err.println("Wrong output format " + format + ". Should be one of " + Arrays.toString(formats)); + return false; + } + + if ((key != null || namedKey != null) && (anyPublicKey || anyPrivateKey)) { + System.err.print("Can only specify the whole key with --key/--named-key or pubkey and privkey with --public/--named-public and --private/--named-private."); + return false; + } + if (bits < 0) { + System.err.println("Bit-size must not be negative."); + return false; + } + if (bits == 0 && !all) { + System.err.println("You must specify either bit-size with -b or all bit-sizes with -a."); + return false; + } + + if (key != null && namedKey != null || publicKey != null && namedPublicKey != null || privateKey != null && namedPrivateKey != null) { + System.err.println("You cannot specify both a named key and a key file."); + return false; + } + + if (cli.hasOption("export")) { + if (primeField == binaryField) { + System.err.print("Need to specify field with -fp or -f2m. (not both)"); + return false; + } + if (anyKeypart) { + System.err.println("Keys should not be specified when exporting curve params."); + return false; + } + if (namedCurve != null || customCurve || curveFile != null) { + System.err.println("Specifying a curve for curve export makes no sense."); + return false; + } + if (output == null) { + System.err.println("You have to specify an output file for curve parameter export."); + return false; + } + if (all) { + System.err.println("You have to specify curve bit-size with -b"); + return false; + } + + } else if (cli.hasOption("generate")) { + if (primeField == binaryField) { + System.err.print("Need to specify field with -fp or -f2m. (not both)"); + return false; + } + if (anyKeypart) { + System.err.println("Keys should not be specified when generating keys."); + return false; + } + if (output == null) { + System.err.println("You have to specify an output file for the key generation process."); + return false; + } + if (all) { + System.err.println("You have to specify curve bit-size with -b"); + return false; + } + + generateAmount = Integer.parseInt(cli.getOptionValue("generate", "0")); + if (generateAmount < 0) { + System.err.println("Amount of keys generated cant be negative."); + return false; + } + } else if (cli.hasOption("test")) { + if (!(binaryField || primeField)) { + binaryField = true; + primeField = true; + } + + testSuite = cli.getOptionValue("test", "default").toLowerCase(); + String[] tests = new String[]{"default", "composite", "invalid", "test-vectors", "wrong"}; + if (!Arrays.asList(tests).contains(testSuite)) { + System.err.println("Unknown test suite " + testSuite + ". Should be one of: " + Arrays.toString(tests)); + return false; + } + + } else if (cli.hasOption("ecdh") || cli.hasOption("ecdhc")) { + if (primeField == binaryField) { + System.err.print("Need to specify field with -fp or -f2m. (not both)"); + return false; + } + if (all) { + System.err.println("You have to specify curve bit-size with -b"); + return false; + } + + if (cli.hasOption("ecdh")) { + ECDHCount = Integer.parseInt(cli.getOptionValue("ecdh", "1")); + ECDHKA = EC_Consts.KA_ECDH; + } else if (cli.hasOption("ecdhc")) { + ECDHCount = Integer.parseInt(cli.getOptionValue("ecdhc", "1")); + ECDHKA = EC_Consts.KA_ECDHC; + } + if (ECDHCount <= 0) { + System.err.println("ECDH count cannot be <= 0."); + return false; + } + + } else if (cli.hasOption("ecdsa")) { + if (primeField == binaryField) { + System.err.print("Need to specify field with -fp or -f2m. (but not both)"); + return false; + } + if (all) { + System.err.println("You have to specify curve bit-size with -b"); + return false; + } + + if ((anyPublicKey) != (anyPrivateKey) && !anyKey) { + System.err.println("You cannot only specify a part of a keypair."); + return false; + } + + ECDSACount = Integer.parseInt(cli.getOptionValue("ecdsa", "1")); + if (ECDSACount <= 0) { + System.err.println("ECDSA count cannot be <= 0."); + return false; + } + } + return true; + } + } +} diff --git a/src/cz/crcs/ectester/reader/command/Command.java b/src/cz/crcs/ectester/reader/command/Command.java index 3c11456..3668fbb 100644 --- a/src/cz/crcs/ectester/reader/command/Command.java +++ b/src/cz/crcs/ectester/reader/command/Command.java @@ -4,13 +4,13 @@ import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; +import cz.crcs.ectester.reader.ECTesterReader; import cz.crcs.ectester.reader.response.Response; import cz.crcs.ectester.reader.Util; -import cz.crcs.ectester.reader.ec.EC_Curve; -import cz.crcs.ectester.reader.ec.EC_Key; -import cz.crcs.ectester.reader.ec.EC_Keypair; -import cz.crcs.ectester.reader.ec.EC_Params; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.ec.EC_Keypair; +import cz.crcs.ectester.common.ec.EC_Params; import javacard.security.KeyPair; import javax.smartcardio.CardException; @@ -54,7 +54,7 @@ public abstract class Command { * @return a Command to send in order to prepare the curve on the keypairs. * @throws IOException if curve file cannot be found/opened */ - public static Command prepareCurve(CardMngr cardManager, EC_Store dataStore, ECTester.Config cfg, byte keyPair, short keyLength, byte keyClass) throws IOException { + public static Command prepareCurve(CardMngr cardManager, EC_Store dataStore, ECTesterReader.Config cfg, byte keyPair, short keyLength, byte keyClass) throws IOException { if (cfg.customCurve) { // Set custom curve (one of the SECG curves embedded applet-side) @@ -109,7 +109,7 @@ public abstract class Command { * @return a CommandAPDU setting params loaded on the keyPair/s * @throws IOException if any of the key files cannot be found/opened */ - public static Command prepareKey(CardMngr cardManager, EC_Store dataStore, ECTester.Config cfg, byte keyPair) throws IOException { + public static Command prepareKey(CardMngr cardManager, EC_Store dataStore, ECTesterReader.Config cfg, byte keyPair) throws IOException { short params = EC_Consts.PARAMETERS_NONE; byte[] data = null; diff --git a/src/cz/crcs/ectester/reader/ec/EC_Category.java b/src/cz/crcs/ectester/reader/ec/EC_Category.java deleted file mode 100644 index 41cbad8..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Category.java +++ /dev/null @@ -1,142 +0,0 @@ -package cz.crcs.ectester.reader.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 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 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 getObjects() { - return Collections.unmodifiableMap(objects); - } - - public Map getObjects(Class cls) { - Map objs = new TreeMap<>(); - for (Map.Entry entry : objects.entrySet()) { - if (cls.isInstance(entry.getValue())) { - objs.put(entry.getKey(), cls.cast(entry.getValue())); - } - } - return Collections.unmodifiableMap(objs); - } - - public T getObject(Class 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 curves = getObjects(EC_Curve.class); - int size = curves.size(); - if (size > 0) { - out.append("\t\tCurves: "); - for (Map.Entry curve : curves.entrySet()) { - out.append(curve.getKey()); - size--; - if (size > 0) - out.append(", "); - } - out.append(System.lineSeparator()); - } - - Map keys = getObjects(EC_Key.class); - size = keys.size(); - if (size > 0) { - out.append("\t\tKeys: "); - for (Map.Entry key : keys.entrySet()) { - out.append(key.getKey()); - size--; - if (size > 0) - out.append(", "); - } - out.append(System.lineSeparator()); - } - - Map keypairs = getObjects(EC_Keypair.class); - size = keypairs.size(); - if (size > 0) { - out.append("\t\tKeypairs: "); - for (Map.Entry key : keypairs.entrySet()) { - out.append(key.getKey()); - size--; - if (size > 0) - out.append(", "); - } - out.append(System.lineSeparator()); - } - - Map results = getObjects(EC_KAResult.class); - size = results.size(); - if (size > 0) { - out.append("\t\tResults: "); - for (Map.Entry 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/reader/ec/EC_Curve.java b/src/cz/crcs/ectester/reader/ec/EC_Curve.java deleted file mode 100644 index cb4a2df..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Curve.java +++ /dev/null @@ -1,52 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.applet.EC_Consts; -import javacard.security.KeyPair; - -/** - * An Elliptic curve, contains parameters Fp/F2M, A, B, G, R, (K)?. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_Curve extends EC_Params { - 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); - } -} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Data.java b/src/cz/crcs/ectester/reader/ec/EC_Data.java deleted file mode 100644 index 0ceddef..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Data.java +++ /dev/null @@ -1,200 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.reader.Util; - -import java.io.*; -import java.util.*; -import java.util.regex.Pattern; - -/** - * A list of byte arrays for holding EC data. - * - * The data can be read from a byte array via readBytes(), from a CSV via readCSV(). - * The data can be exported to a byte array via flatten() or to a string array via expand(). - * @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 boolean hasData() { - return data != null; - } - - public byte[] getParam(int index) { - return data[index]; - } - - public byte[] flatten() { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - for (byte[] param : data) { - byte[] length = new byte[2]; - Util.setShort(length, 0, (short) param.length); - - out.write(length, 0, 2); - out.write(param, 0, param.length); - } - - return out.toByteArray(); - } - - public String[] expand() { - List out = new ArrayList<>(count); - for (byte[] param : data) { - out.add(Util.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 = Util.hexToBytes(param.substring(2)); - } else { - data = Util.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 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) { - int offset = 0; - for (int i = 0; i < count; i++) { - if (bytes.length - offset < 2) { - return false; - } - short paramLength = Util.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 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/reader/ec/EC_KAResult.java b/src/cz/crcs/ectester/reader/ec/EC_KAResult.java deleted file mode 100644 index 4a67bbe..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_KAResult.java +++ /dev/null @@ -1,63 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.reader.Util; - -/** - * A result of EC based Key agreement operation. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_KAResult extends EC_Data { - - private byte ka; - private String curve; - private String oneKey; - private String otherKey; - - private String desc; - - public EC_KAResult(byte 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, byte ka, String curve, String oneKey, String otherKey) { - this(ka, curve, oneKey, otherKey); - this.id = id; - } - - public EC_KAResult(String id, byte ka, String curve, String oneKey, String otherKey, String desc) { - this(id, ka, curve, oneKey, otherKey); - this.desc = desc; - } - - public byte getKA() { - return 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() { - String algo = Util.getKA(ka); - return "<" + getId() + "> " + algo + " result over " + curve + ", " + oneKey + " + " + otherKey + (desc == null ? "" : ": " + desc); - } - -} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Key.java b/src/cz/crcs/ectester/reader/ec/EC_Key.java deleted file mode 100644 index 5077d5b..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Key.java +++ /dev/null @@ -1,83 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.applet.EC_Consts; - -/** - * An abstract-like EC key. Concrete implementations create a public and private keys. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_Key extends EC_Params { - - 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/reader/ec/EC_Keypair.java b/src/cz/crcs/ectester/reader/ec/EC_Keypair.java deleted file mode 100644 index 2643346..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Keypair.java +++ /dev/null @@ -1,41 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.applet.EC_Consts; - -/** - * An EC keypair, contains both the W and S parameters. - * - * @author Jan Jancar johny@neuromancer.sk - */ -public class EC_Keypair extends EC_Params { - 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/reader/ec/EC_Params.java b/src/cz/crcs/ectester/reader/ec/EC_Params.java deleted file mode 100644 index 6fb164b..0000000 --- a/src/cz/crcs/ectester/reader/ec/EC_Params.java +++ /dev/null @@ -1,151 +0,0 @@ -package cz.crcs.ectester.reader.ec; - -import cz.crcs.ectester.applet.EC_Consts; -import cz.crcs.ectester.reader.Util; - -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 flatten() or to a comma delimited - * string via expand(). - * @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 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 = Util.concatenate(param, data[i + 1]); - if (!Util.allValue(data[i + 2], (byte) 0)) { - param = Util.concatenate(param, data[i + 2]); - } - if (!Util.allValue(data[i + 3], (byte) 0)) { - param = Util.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 = Util.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); - 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 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(Util.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)); - index += 2; - } else { - out.add(Util.bytesToHex(param, false)); - index++; - } - } - paramMask = (short) (paramMask << 1); - } - return out.toArray(new String[out.size()]); - } -} diff --git a/src/cz/crcs/ectester/reader/response/Response.java b/src/cz/crcs/ectester/reader/response/Response.java index 4abfd14..b816a97 100644 --- a/src/cz/crcs/ectester/reader/response/Response.java +++ b/src/cz/crcs/ectester/reader/response/Response.java @@ -3,7 +3,6 @@ package cz.crcs.ectester.reader.response; import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; import cz.crcs.ectester.reader.Util; -import cz.crcs.ectester.reader.ec.EC_Data; import javacard.framework.ISO7816; import javacard.security.KeyPair; diff --git a/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java b/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java index 8e7ca31..9c8393d 100644 --- a/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java +++ b/src/cz/crcs/ectester/reader/test/CompositeCurvesSuite.java @@ -4,10 +4,10 @@ import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; +import cz.crcs.ectester.reader.ECTesterReader; import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.EC_Curve; -import cz.crcs.ectester.reader.ec.EC_Key; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; import javacard.security.KeyPair; import java.util.Map; @@ -19,7 +19,7 @@ import static cz.crcs.ectester.reader.test.Result.ExpectedValue; */ public class CompositeCurvesSuite extends TestSuite { - public CompositeCurvesSuite(EC_Store dataStore, ECTester.Config cfg) { + public CompositeCurvesSuite(EC_Store dataStore, ECTesterReader.Config cfg) { super(dataStore, cfg, "composite", "The composite suite tests ECDH over curves with composite order. This should generally fail, as using such a curve is unsafe."); } diff --git a/src/cz/crcs/ectester/reader/test/DefaultSuite.java b/src/cz/crcs/ectester/reader/test/DefaultSuite.java index 736b7c5..b487a6e 100644 --- a/src/cz/crcs/ectester/reader/test/DefaultSuite.java +++ b/src/cz/crcs/ectester/reader/test/DefaultSuite.java @@ -4,7 +4,7 @@ import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; +import cz.crcs.ectester.reader.ECTesterReader; import cz.crcs.ectester.reader.command.Command; import javacard.security.KeyPair; @@ -17,7 +17,7 @@ import static cz.crcs.ectester.reader.test.Result.ExpectedValue; */ public class DefaultSuite extends TestSuite { - public DefaultSuite(EC_Store dataStore, ECTester.Config cfg) { + public DefaultSuite(EC_Store dataStore, ECTesterReader.Config cfg) { super(dataStore, cfg, "default", "The default test suite tests basic support of ECDH and ECDSA."); } diff --git a/src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java b/src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java index f61b695..3dcabb3 100644 --- a/src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java +++ b/src/cz/crcs/ectester/reader/test/InvalidCurvesSuite.java @@ -4,10 +4,10 @@ import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; +import cz.crcs.ectester.reader.ECTesterReader; import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.EC_Curve; -import cz.crcs.ectester.reader.ec.EC_Key; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; import javacard.security.KeyPair; import java.io.IOException; @@ -23,7 +23,7 @@ import static cz.crcs.ectester.reader.test.Result.ExpectedValue; */ public class InvalidCurvesSuite extends TestSuite { - public InvalidCurvesSuite(EC_Store dataStore, ECTester.Config cfg) { + public InvalidCurvesSuite(EC_Store dataStore, ECTesterReader.Config cfg) { super(dataStore, cfg, "invalid", "The invalid curve suite tests whether the card rejects points outside of the curve during ECDH."); } diff --git a/src/cz/crcs/ectester/reader/test/TestSuite.java b/src/cz/crcs/ectester/reader/test/TestSuite.java index f13317c..3b6af5a 100644 --- a/src/cz/crcs/ectester/reader/test/TestSuite.java +++ b/src/cz/crcs/ectester/reader/test/TestSuite.java @@ -4,9 +4,9 @@ import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; +import cz.crcs.ectester.reader.ECTesterReader; import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Curve; import java.io.IOException; import java.util.Collections; @@ -23,12 +23,12 @@ import static cz.crcs.ectester.reader.test.Result.Value; */ public abstract class TestSuite { EC_Store dataStore; - ECTester.Config cfg; + ECTesterReader.Config cfg; String name; String description; List tests = new LinkedList<>(); - TestSuite(EC_Store dataStore, ECTester.Config cfg, String name, String description) { + TestSuite(EC_Store dataStore, ECTesterReader.Config cfg, String name, String description) { this.dataStore = dataStore; this.cfg = cfg; this.name = name; diff --git a/src/cz/crcs/ectester/reader/test/TestVectorSuite.java b/src/cz/crcs/ectester/reader/test/TestVectorSuite.java index ff46feb..6a3121b 100644 --- a/src/cz/crcs/ectester/reader/test/TestVectorSuite.java +++ b/src/cz/crcs/ectester/reader/test/TestVectorSuite.java @@ -4,10 +4,10 @@ import cz.crcs.ectester.applet.ECTesterApplet; import cz.crcs.ectester.applet.EC_Consts; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; +import cz.crcs.ectester.reader.ECTesterReader; import cz.crcs.ectester.reader.Util; import cz.crcs.ectester.reader.command.Command; -import cz.crcs.ectester.reader.ec.*; +import cz.crcs.ectester.common.ec.*; import cz.crcs.ectester.reader.response.Response; import javacard.security.KeyPair; @@ -24,7 +24,7 @@ import static cz.crcs.ectester.reader.test.Result.Value; */ public class TestVectorSuite extends TestSuite { - public TestVectorSuite(EC_Store dataStore, ECTester.Config cfg) { + public TestVectorSuite(EC_Store dataStore, ECTesterReader.Config cfg) { super(dataStore, cfg, "test", "The test-vectors suite contains a collection of test vectors which test basic ECDH correctness."); } diff --git a/src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java b/src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java index e9389b4..09f10d3 100644 --- a/src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java +++ b/src/cz/crcs/ectester/reader/test/WrongCurvesSuite.java @@ -2,7 +2,7 @@ package cz.crcs.ectester.reader.test; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; -import cz.crcs.ectester.reader.ECTester; +import cz.crcs.ectester.reader.ECTesterReader; import javacard.security.KeyPair; import java.io.IOException; @@ -14,7 +14,7 @@ import static cz.crcs.ectester.reader.test.Result.ExpectedValue; */ public class WrongCurvesSuite extends TestSuite { - public WrongCurvesSuite(EC_Store dataStore, ECTester.Config cfg) { + public WrongCurvesSuite(EC_Store dataStore, ECTesterReader.Config cfg) { super(dataStore, cfg, "wrong", "The wrong curve suite tests whether the card rejects domain parameters which are not curves."); } -- cgit v1.2.3-70-g09d2