diff options
10 files changed, 187 insertions, 14 deletions
diff --git a/nbproject/reader/manifest.mf b/nbproject/reader/manifest.mf index c2a00ee..4bb6334 100644 --- a/nbproject/reader/manifest.mf +++ b/nbproject/reader/manifest.mf @@ -1,4 +1,4 @@ Manifest-Version: 1.0 -Class-Path: lib/jcardsim-3.0.4-SNAPSHOT.jar lib/commons-cli-1.4.jar lib/snakeyaml-1.19.jar +Class-Path: lib/bcprov-jdk15on-1.58.jar lib/jcardsim-3.0.4-SNAPSHOT.jar lib/commons-cli-1.4.jar lib/snakeyaml-1.19.jar Main-Class: cz.crcs.ectester.reader.ECTesterReader diff --git a/src/cz/crcs/ectester/applet/AppletBase.java b/src/cz/crcs/ectester/applet/AppletBase.java index 24272c5..4e488b5 100644 --- a/src/cz/crcs/ectester/applet/AppletBase.java +++ b/src/cz/crcs/ectester/applet/AppletBase.java @@ -873,6 +873,14 @@ public abstract class AppletBase extends Applet { length += 2; Util.setShort(buffer, (short) (offset + length), (short) (JCSystem.isObjectDeletionSupported() ? 1 : 0)); length += 2; + Util.setShort(buffer, (short) (offset + length), (short) buffer.length); + length += 2; + Util.setShort(buffer, (short) (offset + length), (short) ramArray.length); + length += 2; + Util.setShort(buffer, (short) (offset + length), (short) ramArray2.length); + length += 2; + Util.setShort(buffer, (short) (offset + length), (short) apduArray.length); + length += 2; return length; } } diff --git a/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java b/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java index f60f8bb..85b32a4 100644 --- a/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java +++ b/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java @@ -106,6 +106,9 @@ public abstract class BaseTextTestWriter implements TestWriter { } else { SimpleTest<? extends BaseTestable> test = (SimpleTest<? extends BaseTestable>) t; out.append(testableString(test.getTestable())); + if (t.getResult().getCause() != null) { + out.append(" ┃ ").append(t.getResult().getCause().toString()); + } } return line + out.toString(); } diff --git a/src/cz/crcs/ectester/reader/ECTesterReader.java b/src/cz/crcs/ectester/reader/ECTesterReader.java index 2a74ea7..377e2ff 100644 --- a/src/cz/crcs/ectester/reader/ECTesterReader.java +++ b/src/cz/crcs/ectester/reader/ECTesterReader.java @@ -40,6 +40,7 @@ import cz.crcs.ectester.reader.test.*; import javacard.framework.ISO7816; import javacard.security.KeyPair; import org.apache.commons.cli.*; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.smartcardio.CardException; import javax.smartcardio.ResponseAPDU; @@ -48,6 +49,7 @@ import java.io.*; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; +import java.security.Security; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -145,9 +147,9 @@ public class ECTesterReader { } ResponseAPDU selectResp = cardManager.send(SELECT_ECTESTERAPPLET); if ((short) selectResp.getSW() != ISO7816.SW_NO_ERROR) { - System.err.println(Colors.error("Failed to select ECTester applet, is it installed?")); - cardManager.disconnectFromCard(); - System.exit(1); + System.err.println(Colors.error("Failed to select ECTester applet, is it installed?")); + cardManager.disconnectFromCard(); + System.exit(1); } } @@ -155,6 +157,12 @@ public class ECTesterReader { logger = new OutputLogger(true, cfg.log); respWriter = new ResponseWriter(logger.getPrintStream()); + // Try adding the BouncyCastleProvider, which might be used in some parts of ECTester. + try { + Security.addProvider(new BouncyCastleProvider()); + } catch (SecurityException | NoClassDefFoundError ignored) { + } + //do action if (cli.hasOption("export")) { export(); @@ -167,7 +175,7 @@ public class ECTesterReader { } else if (cli.hasOption("ecdsa")) { ecdsa(); } else if (cli.hasOption("info")) { - info(); + info(); } //disconnect @@ -282,7 +290,7 @@ public class ECTesterReader { 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("ls").longOpt("list-suites").desc("List supported test suites.").build()); + actions.addOption(Option.builder("ls").longOpt("list-suites").desc("List supported test suites.").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. Optionally specify a test number to run only a part of a test suite. <test_suite>:\n- default:\n- compression:\n- invalid:\n- twist:\n- degenerate:\n- cofactor:\n- wrong:\n- signature:\n- composite:\n- test-vectors:\n- edge-cases:\n- miscellaneous:").hasArg().argName("test_suite[:from[:to]]").optionalArg(true).build()); @@ -359,11 +367,12 @@ public class ECTesterReader { } private void info() throws CardException { - Response.GetInfo info = new Command.GetInfo(cardManager).send(); - System.out.println(String.format("ECTester applet version: %s", info.getVersion())); - System.out.println(String.format("ECTester applet APDU support: %s", (info.getBase() == ECTesterApplet.BASE_221) ? "basic" : "extended length")); - System.out.println(String.format("JavaCard API version: %.1f", info.getJavaCardVersion())); - System.out.println(String.format("JavaCard supports system cleanup: %s", info.getCleanupSupport())); + Response.GetInfo info = new Command.GetInfo(cardManager).send(); + System.out.println(String.format("ECTester applet version: %s", info.getVersion())); + System.out.println(String.format("ECTester applet APDU support: %s", (info.getBase() == ECTesterApplet.BASE_221) ? "basic" : "extended length")); + System.out.println(String.format("JavaCard API version: %.1f", info.getJavaCardVersion())); + System.out.println(String.format("JavaCard supports system cleanup: %s", info.getCleanupSupport())); + System.out.println(String.format("Array sizes (apduBuf, ram, ram2, apduArr): %d %d %d %d", info.getApduBufferLength(), info.getRamArrayLength(), info.getRamArray2Length(), info.getApduArrayLength())); } /** @@ -446,6 +455,7 @@ public class ECTesterReader { respWriter.outputResponse(response); Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR).send(); + respWriter.outputResponse(export); if (!response.successful() || !export.successful()) { if (retry < 10) { diff --git a/src/cz/crcs/ectester/reader/output/TextTestWriter.java b/src/cz/crcs/ectester/reader/output/TextTestWriter.java index a755857..e89d403 100644 --- a/src/cz/crcs/ectester/reader/output/TextTestWriter.java +++ b/src/cz/crcs/ectester/reader/output/TextTestWriter.java @@ -51,6 +51,7 @@ public class TextTestWriter extends BaseTextTestWriter { sb.append("═══ ").append(Colors.underline("ECTester applet version:")).append(" ").append(info.getVersion()).append(info.getBase() == ECTesterApplet.BASE_221 ? "" : " (extended length)").append(System.lineSeparator()); sb.append("═══ ").append(Colors.underline("Card ATR:")).append(" ").append(ByteUtil.bytesToHex(cardSuite.getCard().getATR().getBytes(), false)).append(System.lineSeparator()); sb.append("═══ ").append(Colors.underline("JavaCard version:")).append(" ").append(info.getJavaCardVersion()).append(System.lineSeparator()); + sb.append("═══ ").append(Colors.underline("Array sizes (apduBuf, ram, ram2, apduArr):")).append(" ").append(String.format("%d %d %d %d", info.getApduBufferLength(), info.getRamArrayLength(), info.getRamArray2Length(), info.getApduArrayLength())).append(System.lineSeparator()); CardMngr.CPLC cplc = cardSuite.getCard().getCPLC(); if (!cplc.values().isEmpty()) { sb.append("═══ ").append(Colors.underline("Card CPLC data:")).append(System.lineSeparator()); diff --git a/src/cz/crcs/ectester/reader/output/XMLTestWriter.java b/src/cz/crcs/ectester/reader/output/XMLTestWriter.java index 8abdea5..9add072 100644 --- a/src/cz/crcs/ectester/reader/output/XMLTestWriter.java +++ b/src/cz/crcs/ectester/reader/output/XMLTestWriter.java @@ -116,6 +116,24 @@ public class XMLTestWriter extends BaseXMLTestWriter { result.setAttribute("javacard", String.format("%.1f", info.getJavaCardVersion())); result.setAttribute("base", String.format("%#x",info.getBase())); result.setAttribute("cleanup", String.valueOf(info.getCleanupSupport())); + Element arrays = doc.createElement("arrays"); + Element apduBuf = doc.createElement("length"); + apduBuf.setAttribute("name", "apduBuf"); + apduBuf.setTextContent(String.valueOf(info.getApduBufferLength())); + Element ramArray = doc.createElement("length"); + ramArray.setAttribute("name", "ramArray"); + ramArray.setTextContent(String.valueOf(info.getRamArrayLength())); + Element ramArray2 = doc.createElement("length"); + ramArray2.setAttribute("name", "ramArray2"); + ramArray2.setTextContent(String.valueOf(info.getRamArray2Length())); + Element apduArray = doc.createElement("length"); + apduArray.setAttribute("name", "apduArray"); + apduArray.setTextContent(String.valueOf(info.getApduArrayLength())); + arrays.appendChild(apduBuf); + arrays.appendChild(ramArray); + arrays.appendChild(ramArray2); + arrays.appendChild(apduArray); + result.appendChild(arrays); } catch (CardException ignored) { } return result; diff --git a/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java b/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java index 92de6c6..56ecb71 100644 --- a/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java +++ b/src/cz/crcs/ectester/reader/output/YAMLTestWriter.java @@ -91,6 +91,12 @@ public class YAMLTestWriter extends BaseYAMLTestWriter { result.put("javacard", info.getJavaCardVersion()); result.put("base", info.getBase()); result.put("cleanup", info.getCleanupSupport()); + Map<String, Integer> arrays = new LinkedHashMap<>(); + arrays.put("apduBuf", Short.toUnsignedInt(info.getApduBufferLength())); + arrays.put("ramArray", Short.toUnsignedInt(info.getRamArrayLength())); + arrays.put("ramArray2", Short.toUnsignedInt(info.getRamArray2Length())); + arrays.put("apduArray", Short.toUnsignedInt(info.getApduArrayLength())); + result.put("arrays", arrays); } catch (CardException ignored) { } return result; diff --git a/src/cz/crcs/ectester/reader/response/Response.java b/src/cz/crcs/ectester/reader/response/Response.java index 6f42ba1..235564e 100644 --- a/src/cz/crcs/ectester/reader/response/Response.java +++ b/src/cz/crcs/ectester/reader/response/Response.java @@ -436,6 +436,10 @@ public abstract class Response { private short base; private short jcVersion; private short cleanupSupport; + private short apduBufferLength; + private short ramArrayLength; + private short ramArray2Length; + private short apduArrayLength; public GetInfo(ResponseAPDU response, String description, long time) { super(response, description, time); @@ -448,6 +452,14 @@ public abstract class Response { jcVersion = ByteUtil.getShort(data, offset); offset += 2; cleanupSupport = ByteUtil.getShort(data, offset); + offset += 2; + apduBufferLength = ByteUtil.getShort(data, offset); + offset += 2; + ramArrayLength = ByteUtil.getShort(data, offset); + offset += 2; + ramArray2Length = ByteUtil.getShort(data, offset); + offset += 2; + apduArrayLength = ByteUtil.getShort(data, offset); } public String getVersion() { @@ -473,5 +485,21 @@ public abstract class Response { public boolean getCleanupSupport() { return cleanupSupport == 1; } + + public short getApduBufferLength() { + return apduBufferLength; + } + + public short getRamArrayLength() { + return ramArrayLength; + } + + public short getRamArray2Length() { + return ramArray2Length; + } + + public short getApduArrayLength() { + return apduArrayLength; + } } } diff --git a/src/cz/crcs/ectester/reader/test/CardCofactorSuite.java b/src/cz/crcs/ectester/reader/test/CardCofactorSuite.java index c8caa8b..172c8af 100644 --- a/src/cz/crcs/ectester/reader/test/CardCofactorSuite.java +++ b/src/cz/crcs/ectester/reader/test/CardCofactorSuite.java @@ -46,7 +46,7 @@ public class CardCofactorSuite extends CardTestSuite { for (EC_Key.Public pub : keys) { Test setPub = CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE); Test ecdh = CommandTest.expect(new Command.ECDH(this.card, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE); - Test objectEcdh = CompoundTest.all(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with degenerate pubkey.", setPub, ecdh); + Test objectEcdh = CompoundTest.any(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with degenerate pubkey.", setPub, ecdh); Command ecdhCommand = new Command.ECDH_direct(this.card, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten()); Test rawEcdh = CommandTest.expect(ecdhCommand, ExpectedValue.FAILURE, "Card correctly rejected point on non-generator subgroup.", "Card incorrectly accepted point on non-generator subgroup."); ecdhTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, pub.getId() + " cofactor key test.", objectEcdh, rawEcdh)); diff --git a/src/cz/crcs/ectester/reader/test/CardTestVectorSuite.java b/src/cz/crcs/ectester/reader/test/CardTestVectorSuite.java index fbdf103..3abcebb 100644 --- a/src/cz/crcs/ectester/reader/test/CardTestVectorSuite.java +++ b/src/cz/crcs/ectester/reader/test/CardTestVectorSuite.java @@ -9,16 +9,28 @@ import cz.crcs.ectester.common.test.Result; import cz.crcs.ectester.common.test.Test; import cz.crcs.ectester.common.test.TestCallback; import cz.crcs.ectester.common.util.ByteUtil; +import cz.crcs.ectester.common.util.CardUtil; +import cz.crcs.ectester.common.util.ECUtil; import cz.crcs.ectester.data.EC_Store; import cz.crcs.ectester.reader.CardMngr; import cz.crcs.ectester.reader.ECTesterReader; import cz.crcs.ectester.reader.command.Command; import cz.crcs.ectester.reader.response.Response; +import javacard.security.KeyPair; +import javax.crypto.KeyAgreement; import java.io.IOException; +import java.math.BigInteger; +import java.security.*; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static cz.crcs.ectester.common.test.Result.ExpectedValue; import static cz.crcs.ectester.common.test.Result.Value; @@ -52,8 +64,13 @@ public class CardTestVectorSuite extends CardTestSuite { throw new IOException("Test vector keys couldn't be located."); } List<Test> testVector = new LinkedList<>(); + Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); + if (!allocate.ok()) { + doTest(CompoundTest.all(ExpectedValue.SUCCESS, "No support for " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()) + ".", allocate)); + continue; + } - testVector.add(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); + testVector.add(allocate); testVector.add(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS)); testVector.add(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, onekey.flatten(EC_Consts.PARAMETER_S)), ExpectedValue.SUCCESS)); testVector.add(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, otherkey.flatten(EC_Consts.PARAMETER_W)), ExpectedValue.SUCCESS)); @@ -75,7 +92,89 @@ public class CardTestVectorSuite extends CardTestSuite { if (cfg.cleanup) { testVector.add(CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY)); } - doTest(CompoundTest.greedyAll(ExpectedValue.SUCCESS, "Test vector " + result.getId(), testVector.toArray(new Test[0]))); + doTest(CompoundTest.greedyAll(ExpectedValue.SUCCESS, "Test vector " + result.getId() + ".", testVector.toArray(new Test[0]))); + } + + KeyAgreement ka; + KeyFactory kf; + MessageDigest md; + try { + ka = KeyAgreement.getInstance("ECDH", "BC"); + kf = KeyFactory.getInstance("ECDH", "BC"); + md = MessageDigest.getInstance("SHA1", "BC"); + } catch (NoSuchAlgorithmException | NoSuchProviderException ex) { + return; + } + + List<EC_Curve> testCurves = new ArrayList<>(); + testCurves.addAll(EC_Store.getInstance().getObjects(EC_Curve.class, "secg").values().stream().filter((curve) -> curve.getField() == KeyPair.ALG_EC_FP).collect(Collectors.toList())); + testCurves.addAll(EC_Store.getInstance().getObjects(EC_Curve.class, "brainpool").values().stream().filter((curve) -> curve.getField() == KeyPair.ALG_EC_FP).collect(Collectors.toList())); + for (EC_Curve curve : testCurves) { + List<Test> testVector = new LinkedList<>(); + Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); + if (!allocate.ok()) { + doTest(CompoundTest.all(ExpectedValue.SUCCESS, "No support for " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()) + ".", allocate)); + continue; + } + testVector.add(allocate); + testVector.add(CommandTest.expect(new Command.Set(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS)); + testVector.add(CommandTest.expect(new Command.Generate(this.card, ECTesterApplet.KEYPAIR_BOTH), ExpectedValue.SUCCESS)); + CommandTest export = CommandTest.expect(new Command.Export(this.card, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR), ExpectedValue.ANY); + testVector.add(export); + TestCallback<CommandTestable> kaCallback = new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable testable) { + Response.ECDH ecdhData = (Response.ECDH) testable.getResponse(); + if (!ecdhData.successful()) + return new Result(Value.FAILURE, "ECDH was unsuccessful."); + if (!ecdhData.hasSecret()) { + return new Result(Value.FAILURE, "ECDH response did not contain the derived secret."); + } + byte[] secret = ecdhData.getSecret(); + Response.Export keyData = (Response.Export) export.getResponse(); + byte[] pkey = keyData.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W); + byte[] skey = keyData.getParameter(ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.PARAMETER_S); + ECParameterSpec spec = curve.toSpec(); + ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(new BigInteger(1, skey), spec); + ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(ECUtil.fromX962(pkey, curve.toCurve()), spec); + PrivateKey privKey; + PublicKey pubkey; + try { + privKey = kf.generatePrivate(privKeySpec); + pubkey = kf.generatePublic(pubKeySpec); + ka.init(privKey); + ka.doPhase(pubkey, true); + byte[] rawDerived = ka.generateSecret(); + int fieldSize = (curve.getBits() + 7) / 8; + if (rawDerived.length < fieldSize) { + byte[] padded = new byte[fieldSize]; + System.arraycopy(rawDerived, 0, padded, fieldSize - rawDerived.length, rawDerived.length); + rawDerived = padded; + } + byte[] derived = md.digest(rawDerived); + if (secret.length != derived.length) { + if (secret.length < derived.length) { + return new Result(Value.FAILURE, String.format("Derived secret was shorter than expected: %d vs %d (expected).", secret.length, derived.length)); + } else { + return new Result(Value.FAILURE, String.format("Derived secret was longer than expected: %d vs %d (expected).", secret.length, derived.length)); + } + } + int diff = ByteUtil.diffBytes(derived, 0, secret, 0, secret.length); + if (diff == secret.length) { + return new Result(Value.SUCCESS, "Derived secret matched expected value."); + } else { + return new Result(Value.FAILURE, "Derived secret does not match expected value, first difference was at byte " + String.valueOf(diff) + "."); + } + } catch (InvalidKeySpecException | InvalidKeyException ex) { + return new Result(Value.SUCCESS, "Result could not be verified. " + ex.getMessage()); + } + } + }; + testVector.add(CommandTest.function(new Command.ECDH(this.card, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), kaCallback)); + if (cfg.cleanup) { + testVector.add(CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY)); + } + doTest(CompoundTest.greedyAll(ExpectedValue.SUCCESS, "Validation test on " + curve.getId() + ".", testVector.toArray(new Test[0]))); } } } |
