diff options
Diffstat (limited to 'reader/src/main')
26 files changed, 5538 insertions, 0 deletions
diff --git a/reader/src/main/java/cz/crcs/ectester/reader/CardMngr.java b/reader/src/main/java/cz/crcs/ectester/reader/CardMngr.java new file mode 100644 index 0000000..0a01d9e --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/CardMngr.java @@ -0,0 +1,478 @@ +package cz.crcs.ectester.reader; + +import com.licel.jcardsim.io.JavaxSmartCardInterface; +import cz.crcs.ectester.common.util.ByteUtil; +import javacard.framework.AID; +import javacard.framework.Applet; +import javacard.framework.ISO7816; + +import javax.smartcardio.*; +import java.util.*; + +/** + * @author Petr Svenda petr@svenda.com + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardMngr { + private CardTerminal terminal = null; + private CardChannel channel = null; + private Card card = null; + + // Simulator related attributes + private JavaxSmartCardInterface simulator = null; + + private boolean simulate = false; + private boolean verbose = true; + private boolean chunking = false; + + private final byte[] selectCM = { + (byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x07, (byte) 0xa0, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x18, (byte) 0x43, (byte) 0x4d}; + + public static final byte OFFSET_CLA = 0x00; + public static final byte OFFSET_INS = 0x01; + public static final byte OFFSET_P1 = 0x02; + public static final byte OFFSET_P2 = 0x03; + public static final byte OFFSET_LC = 0x04; + public static final byte OFFSET_DATA = 0x05; + public static final byte HEADER_LENGTH = 0x05; + + public static final short DATA_RECORD_LENGTH = (short) 0x80; // 128B per record + public static final short NUMBER_OF_RECORDS = (short) 0x0a; // 10 records + + public CardMngr() { + } + + public CardMngr(boolean verbose) { + this.verbose = verbose; + } + + public CardMngr(boolean verbose, boolean simulate) { + this(verbose); + this.simulate = simulate; + } + + private void connectWithHighest() throws CardException { + try { + card = terminal.connect("T=1"); + } catch (CardException ex) { + if (verbose) + System.out.println("T=1 failed, trying protocol '*'"); + card = terminal.connect("*"); + if (card.getProtocol().equals("T=0")) { + chunking = true; + } + } + } + + public boolean connectToCard() throws CardException { + if (simulate) + return true; + + // TRY ALL READERS, FIND FIRST SELECTABLE + List<CardTerminal> terminalList = getReaderList(); + + if (terminalList == null || terminalList.isEmpty()) { + System.err.println("No terminals found"); + return false; + } + + //List numbers of Card readers + boolean cardFound = false; + for (int i = 0; i < terminalList.size(); i++) { + + if (verbose) + System.out.println(i + " : " + terminalList.get(i)); + + terminal = terminalList.get(i); + if (terminal.isCardPresent()) { + connectWithHighest(); + + if (verbose) + System.out.println("card: " + card); + channel = card.getBasicChannel(); + + //reset the card + if (verbose) + System.out.println(ByteUtil.bytesToHex(card.getATR().getBytes())); + + cardFound = true; + } + } + + return cardFound; + } + + public boolean connectToCardSelect() throws CardException { + if (simulate) + return true; + + // Test available card - if more present, let user to select one + List<CardTerminal> terminalList = CardMngr.getReaderList(); + if (terminalList == null || terminalList.isEmpty()) { + System.err.println("ERROR: No suitable reader with card detected. Please check your reader connection"); + return false; + } else { + if (terminalList.size() == 1) { + terminal = terminalList.get(0); // return first and only reader + } else { + int terminalIndex = 1; + // Let user select target terminal + for (CardTerminal terminal : terminalList) { + Card card; + try { + card = terminal.connect("*"); + ATR atr = card.getATR(); + System.out.println(terminalIndex + " : " + terminal.getName() + " - " + ByteUtil.bytesToHex(atr.getBytes())); + terminalIndex++; + } catch (CardException ex) { + ex.printStackTrace(System.out); + } + } + System.out.print("Select index of target reader you like to use 1.." + (terminalIndex - 1) + ": "); + Scanner sc = new Scanner(System.in); + int answ = sc.nextInt(); + System.out.println(String.format("%d", answ)); + answ--; // is starting with 0 + // BUGBUG; verify allowed index range + terminal = terminalList.get(answ); + } + } + + if (terminal != null) { + connectWithHighest(); + if (verbose) + System.out.println("card: " + card); + channel = card.getBasicChannel(); + } + + return true; + } + + public boolean reconnectToCard(byte[] selectAPDU) throws CardException { + if (simulate) + return true; + + if (connected()) { + disconnectFromCard(); + } + + boolean result = connectToCard(); + if (result) { + // Select our application on card + send(selectAPDU); + } + return result; + } + + public boolean connected() { + return simulate || card != null; + } + + public void disconnectFromCard() throws CardException { + if (simulate) + return; + + if (card != null) { + card.disconnect(false); + card = null; + } + } + + public void setChunking(boolean state) { + chunking = state; + } + + public String getProtocol() { + if (simulate) { + return simulator.getProtocol(); + } else { + if (card != null) { + return card.getProtocol(); + } else { + return null; + } + } + } + + // Functions for CPLC taken and modified from https://github.com/martinpaljak/GlobalPlatformPro + private static final byte CLA_GP = (byte) 0x80; + private static final byte ISO7816_INS_GET_DATA = (byte) 0xCA; + private static final byte[] FETCH_GP_CPLC_APDU = {CLA_GP, ISO7816_INS_GET_DATA, (byte) 0x9F, (byte) 0x7F, (byte) 0x00}; + private static final byte[] FETCH_ISO_CPLC_APDU = {ISO7816.CLA_ISO7816, ISO7816_INS_GET_DATA, (byte) 0x9F, (byte) 0x7F, (byte) 0x00}; + private static final byte[] FETCH_GP_CARDDATA_APDU = {CLA_GP, ISO7816_INS_GET_DATA, (byte) 0x00, (byte) 0x66, (byte) 0x00}; + + public byte[] fetchCPLC() throws CardException { + // Try CPLC via GP + ResponseAPDU resp = send(FETCH_GP_CPLC_APDU); + // If GP CLA fails, try with ISO + if (resp.getSW() == (ISO7816.SW_CLA_NOT_SUPPORTED & 0xffff)) { + resp = send(FETCH_ISO_CPLC_APDU); + } + if (resp.getSW() == (ISO7816.SW_NO_ERROR & 0xffff)) { + return resp.getData(); + } + return null; + } + + public static final class CPLC { + public enum Field { + ICFabricator, + ICType, + OperatingSystemID, + OperatingSystemReleaseDate, + OperatingSystemReleaseLevel, + ICFabricationDate, + ICSerialNumber, + ICBatchIdentifier, + ICModuleFabricator, + ICModulePackagingDate, + ICCManufacturer, + ICEmbeddingDate, + ICPrePersonalizer, + ICPrePersonalizationEquipmentDate, + ICPrePersonalizationEquipmentID, + ICPersonalizer, + ICPersonalizationDate, + ICPersonalizationEquipmentID + } + + private Map<Field, byte[]> values = new TreeMap<>(); + + public CPLC(byte[] data) { + if (data == null) { + return; + } + if (data.length < 3 || data[2] != 0x2A) { + throw new IllegalArgumentException("CPLC must be 0x2A bytes long"); + } + //offset = TLVUtils.skipTag(data, offset, (short)0x9F7F); + short offset = 3; + values.put(Field.ICFabricator, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICType, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.OperatingSystemID, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.OperatingSystemReleaseDate, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.OperatingSystemReleaseLevel, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICFabricationDate, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICSerialNumber, Arrays.copyOfRange(data, offset, offset + 4)); + offset += 4; + values.put(Field.ICBatchIdentifier, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICModuleFabricator, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICModulePackagingDate, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICCManufacturer, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICEmbeddingDate, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICPrePersonalizer, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICPrePersonalizationEquipmentDate, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICPrePersonalizationEquipmentID, Arrays.copyOfRange(data, offset, offset + 4)); + offset += 4; + values.put(Field.ICPersonalizer, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICPersonalizationDate, Arrays.copyOfRange(data, offset, offset + 2)); + offset += 2; + values.put(Field.ICPersonalizationEquipmentID, Arrays.copyOfRange(data, offset, offset + 4)); + offset += 4; + } + + public Map<Field, byte[]> values() { + return values; + } + } + + public ATR getATR() { + if (simulate) { + return new ATR(simulator.getATR()); + } else { + if (card != null) { + return card.getATR(); + } else { + return null; + } + } + } + + public CPLC getCPLC() throws CardException { + byte[] data = fetchCPLC(); + return new CPLC(data); + } + + public static String mapCPLCField(CPLC.Field field, byte[] value) { + switch (field) { + case ICFabricator: + String id = ByteUtil.bytesToHex(value, false); + String fabricatorName = "unknown"; + if (id.equals("3060")) { + fabricatorName = "Renesas"; + } + if (id.equals("4090")) { + fabricatorName = "Infineon"; + } + if (id.equals("4180")) { + fabricatorName = "Atmel"; + } + if (id.equals("4250")) { + fabricatorName = "Samsung"; + } + if (id.equals("4790")) { + fabricatorName = "NXP"; + } + return id + " (" + fabricatorName + ")"; + default: + return ByteUtil.bytesToHex(value, false); + } + } + + + public static List<CardTerminal> getReaderList() { + try { + TerminalFactory factory = TerminalFactory.getDefault(); + return factory.terminals().list(); + } catch (CardException ex) { + System.err.println("Exception : " + ex); + return null; + } + } + + private CommandAPDU chunk(CommandAPDU apdu) throws CardException { + if (verbose) { + System.out.print("Chunking:"); + } + byte[] data = apdu.getBytes(); + int numChunks = (data.length + 254) / 255; + for (int i = 0; i < numChunks; ++i) { + int chunkStart = i * 255; + int chunkLength = 255; + if (chunkStart + chunkLength > data.length) { + chunkLength = data.length - chunkStart; + } + if (verbose) { + System.out.print(" " + chunkLength); + } + byte[] chunk = new byte[chunkLength]; + System.arraycopy(data, chunkStart, chunk, 0, chunkLength); + CommandAPDU cmd = new CommandAPDU(apdu.getCLA(), 0x7a, 0, 0, chunk); + ResponseAPDU resp; + if (simulate) { + resp = simulator.transmitCommand(cmd); + } else { + resp = channel.transmit(cmd); + } + if ((short) resp.getSW() != ISO7816.SW_NO_ERROR) { + throw new CardException("Chunking failed!"); + } + } + if (verbose) + System.out.println(); + return new CommandAPDU(apdu.getCLA(), 0x7b, 0, 0, 0xff); + } + + public ResponseAPDU sendAPDU(CommandAPDU apdu) throws CardException { + if (verbose) { + System.out.println(">>>>"); + System.out.println(apdu); + + System.out.println(ByteUtil.bytesToHex(apdu.getBytes())); + } + + long elapsed; + if (chunking && apdu.getNc() >= 0xff) { + apdu = chunk(apdu); + } + + elapsed = -System.nanoTime(); + + ResponseAPDU responseAPDU = channel.transmit(apdu); + + elapsed += System.nanoTime(); + + if (verbose) { + System.out.println(responseAPDU); + System.out.println(ByteUtil.bytesToHex(responseAPDU.getBytes())); + } + + if (responseAPDU.getSW1() == (byte) 0x61) { + CommandAPDU apduToSend = new CommandAPDU((byte) 0x00, + (byte) 0xC0, (byte) 0x00, (byte) 0x00, + responseAPDU.getSW2()); + + responseAPDU = channel.transmit(apduToSend); + if (verbose) + System.out.println(ByteUtil.bytesToHex(responseAPDU.getBytes())); + } + + if (verbose) { + System.out.println("<<<<"); + System.out.println("Elapsed time (ms): " + elapsed / 1000000); + System.out.println("---------------------------------------------------------"); + } + return responseAPDU; + } + + public ResponseAPDU sendAPDU(byte[] apdu) throws CardException { + CommandAPDU commandAPDU = new CommandAPDU(apdu); + return sendAPDU(commandAPDU); + } + + public boolean prepareLocalSimulatorApplet(byte[] appletAIDArray, byte[] installData, Class<? extends Applet> appletClass) { + simulator = new JavaxSmartCardInterface(); + AID appletAID = new AID(appletAIDArray, (short) 0, (byte) appletAIDArray.length); + + simulator.installApplet(appletAID, appletClass, installData, (short) 0, (byte) installData.length); + return simulator.selectApplet(appletAID); + } + + public ResponseAPDU sendAPDUSimulator(CommandAPDU apdu) throws CardException { + if (verbose) { + System.out.println(">>>>"); + System.out.println(apdu); + System.out.println(ByteUtil.bytesToHex(apdu.getBytes())); + } + + if (chunking && apdu.getNc() >= 0xff) { + apdu = chunk(apdu); + } + + ResponseAPDU response = simulator.transmitCommand(apdu); + byte[] responseBytes = response.getBytes(); + + if (verbose) { + System.out.println(response); + System.out.println(ByteUtil.bytesToHex(responseBytes)); + System.out.println("<<<<"); + } + + return response; + } + + public ResponseAPDU sendAPDUSimulator(byte[] apdu) throws CardException { + CommandAPDU commandAPDU = new CommandAPDU(apdu); + return sendAPDUSimulator(commandAPDU); + } + + public ResponseAPDU send(CommandAPDU apdu) throws CardException { + ResponseAPDU response; + if (simulate) { + response = sendAPDUSimulator(apdu); + } else { + response = sendAPDU(apdu); + } + return response; + } + + public ResponseAPDU send(byte[] apdu) throws CardException { + CommandAPDU commandAPDU = new CommandAPDU(apdu); + return send(commandAPDU); + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/ECTesterReader.java b/reader/src/main/java/cz/crcs/ectester/reader/ECTesterReader.java new file mode 100644 index 0000000..33111d0 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/ECTesterReader.java @@ -0,0 +1,1100 @@ +/* + * ECTester, tool for testing Elliptic curve cryptography implementations. + * Copyright (c) 2016-2019 Petr Svenda <petr@svenda.com> + * Copyright (c) 2016-2019 Jan Jancar <johny@neuromancer.sk> + * + * 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.common.cli.CLITools; +import cz.crcs.ectester.common.cli.Colors; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.output.OutputLogger; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.util.*; +import cz.crcs.ectester.data.EC_Store; +import cz.crcs.ectester.reader.command.Command; +import cz.crcs.ectester.reader.output.FileTestWriter; +import cz.crcs.ectester.reader.output.ResponseWriter; +import cz.crcs.ectester.reader.response.Response; +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; +import javax.xml.parsers.ParserConfigurationException; +import java.io.*; +import java.math.BigInteger; +import java.net.URL; +import java.nio.file.Files; +import java.security.Security; +import java.security.spec.ECParameterSpec; +import java.util.*; +import java.util.jar.Manifest; + +/** + * 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.3.3 + */ +public class ECTesterReader { + private CardMngr cardManager; + private OutputLogger logger; + private ResponseWriter respWriter; + private Config cfg; + + private Options opts = new Options(); + public static final String VERSION = "v0.3.3"; + public static String GIT_COMMIT = ""; + private static String DESCRIPTION; + private static String LICENSE = "MIT Licensed\nCopyright © 2016-2019 Petr Svenda <petr@svenda.com>\nCopyright © 2016-2019 Jan Jancar <johny@neuromancer.sk>"; + private static String CLI_HEADER; + private static String CLI_FOOTER = "\n" + LICENSE; + + private static final byte[] SELECT_PREFIX = {(byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0c}; + private static final byte[] AID_PREFIX = {(byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72}; + private static final byte[] AID_CURRENT_VERSION = {(byte) 0x30, (byte) 0x33, (byte) 0x33}; // VERSION v0.3.3 + private static final byte[] AID_SUFFIX_221 = {(byte) 0x62}; + private static final byte[] AID_SUFFIX_222 = {(byte) 0x78}; + private static final byte[] INSTALL_DATA = new byte[10]; + private static final int TRY_VERSIONS = 10; + + static { + ClassLoader cl = ECTesterReader.class.getClassLoader(); + try { + URL url = cl.getResource("META-INF/MANIFEST.MF"); + Manifest manifest = new Manifest(url.openStream()); + String commit = manifest.getMainAttributes().getValue("Git-Commit"); + GIT_COMMIT = (commit == null) ? "" : "(git " + commit + ")"; + } catch (Exception ignored) { + } + + DESCRIPTION = "ECTesterReader " + VERSION + GIT_COMMIT + ", a javacard Elliptic Curve Cryptography support tester/utility."; + CLI_HEADER = "\n" + DESCRIPTION + "\n\n"; + } + + private void run(String[] args) { + try { + CommandLine cli = parseArgs(args); + + cfg = new Config(); + boolean optsOk = cfg.readOptions(cli); + + //if help, print and quit + if (cli.hasOption("help")) { + CLITools.help("ECTesterReader.jar", CLI_HEADER, opts, CLI_FOOTER, true); + return; + } else if (cli.hasOption("version")) { + CLITools.version(DESCRIPTION, LICENSE); + return; + } + + //if opts failed, quit + if (!optsOk) { + return; + } + + //if list, print and quit + if (cli.hasOption("list-named")) { + CLITools.listNamed(EC_Store.getInstance(), cli.getOptionValue("list-named")); + return; + } + + if (cli.hasOption("list-suites")) { + listSuites(); + return; + } + + //init CardManager + cardManager = new CardMngr(cfg.verbose, cfg.simulate); + + //connect or simulate connection + if (cfg.simulate) { + if (!cardManager.prepareLocalSimulatorApplet(ByteUtil.concatenate(AID_PREFIX, AID_CURRENT_VERSION, AID_SUFFIX_221), INSTALL_DATA, ECTesterApplet.class)) { + System.err.println(Colors.error("Failed to establish a simulator.")); + System.exit(1); + } else { + cardManager.setChunking(true); + } + } else { + if (!cardManager.connectToCardSelect()) { + System.err.println(Colors.error("Failed to connect to card.")); + System.exit(1); + } + //Try the highest known version first + byte[] versionByte = AID_CURRENT_VERSION.clone(); + boolean selected = false; + for (int i = 0; i < TRY_VERSIONS; ++i) { + byte[] select222 = ByteUtil.concatenate(SELECT_PREFIX, AID_PREFIX, versionByte, AID_SUFFIX_222); + ResponseAPDU selectResp = cardManager.send(select222); + if ((short) selectResp.getSW() != ISO7816.SW_NO_ERROR) { + byte[] select221 = ByteUtil.concatenate(SELECT_PREFIX, AID_PREFIX, versionByte, AID_SUFFIX_221); + selectResp = cardManager.send(select221); + if ((short) selectResp.getSW() == ISO7816.SW_NO_ERROR) { + cardManager.setChunking(true); + selected = true; + break; + } + } else { + selected = true; + break; + } + // Count down by versions + if (versionByte[2] == 0x30) { + if (versionByte[1] == 0x30) { + if (versionByte[0] == 0x30) { + break; + } else { + versionByte[0]--; + versionByte[1] = 0x39; + versionByte[2] = 0x39; + } + } else { + versionByte[1]--; + versionByte[2] = 0x39; + } + } else { + versionByte[2]--; + } + } + if (!selected) { + System.err.println(Colors.error("Failed to select ECTester applet, is it installed?")); + cardManager.disconnectFromCard(); + System.exit(1); + } + } + + // Setup logger and respWriter + 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) { + } + // Make BouncyCastle more lenient when we work with signatures in ASN.1 DER format, + // cards sometimes are not fully compliant. + System.setProperty("org.bouncycastle.asn1.allow_unsafe_integer", "true"); + + //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(); + } else if (cli.hasOption("info")) { + info(); + } + + //disconnect + cardManager.disconnectFromCard(); + logger.close(); + + } catch (MissingOptionException moex) { + System.err.println(Colors.error("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(Colors.error("Option, " + maex.getOption().getOpt() + " requires an argument: " + maex.getOption().getArgName())); + } catch (NumberFormatException nfex) { + System.err.println(Colors.error("Not a number. " + nfex.getMessage())); + } catch (FileNotFoundException fnfe) { + System.err.println(Colors.error("File " + fnfe.getMessage() + " not found.")); + } catch (ParseException | IOException ex) { + System.err.println(Colors.error(ex.getMessage())); + } catch (CardException ex) { + if (logger != null) + logger.println(ex.getMessage()); + ex.printStackTrace(); + } 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 { + 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("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()); + actions.addOption(Option.builder("dh").longOpt("ecdh").desc("Do EC KeyAgreement (ECDH...), [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()); + actions.addOption(Option.builder("nf").longOpt("info").desc("Get applet info.").build()); + + opts.addOptionGroup(actions); + + opts.addOption(Option.builder("b").longOpt("bit-size").desc("Set curve size.").hasArg().argName("bits").build()); + 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: <cat/id>").hasArg().argName("cat/id").build()); + curve.addOption(Option.builder("c").longOpt("curve").desc("Use curve from file <curve_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: <cat/id>").hasArg().argName("cat/id").build()); + pub.addOption(Option.builder("pub").longOpt("public").desc("Use public key from file <pubkey_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: <cat/id>").hasArg().argName("cat/id").build()); + priv.addOption(Option.builder("priv").longOpt("private").desc("Use private key from file <privkey_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: <cat/id>").hasArg().argName("cat/id").build()); + key.addOption(Option.builder("k").longOpt("key").desc("Use keyPair from file <key_file> (wx,wy,s).").hasArg().argName("key_file").build()); + opts.addOptionGroup(key); + + opts.addOption(Option.builder("i").longOpt("input").desc("Input from file <input_file>, for ECDSA signing.").hasArg().argName("input_file").build()); + opts.addOption(Option.builder("o").longOpt("output").desc("Output into file <output_file>. The file can be prefixed by the format (one of text,yml,xml), such as: xml:<output_file>.").hasArgs().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("kb").longOpt("key-builder").desc("Allocate KeyPair using KeyBuilder.").build()); + opts.addOption(Option.builder().longOpt("fixed").desc("Generate key(s) only once, keep them for later operations.").build()); + opts.addOption(Option.builder().longOpt("fixed-private").desc("Generate private key only once, keep it for later ECDH.").build()); + opts.addOption(Option.builder().longOpt("fixed-public").desc("Generate public key only once, keep it for later ECDH.").build()); + opts.addOption(Option.builder("f").longOpt("fresh").desc("Generate fresh keys (set domain parameters before every generation).").build()); + opts.addOption(Option.builder().longOpt("time").desc("Output better timing values, by running command in dry run mode and normal mode, and subtracting the two.").build()); + opts.addOption(Option.builder().longOpt("time-unit").desc("Use given time unit in measurement, one of: milli, micro, nano.").hasArg().argName("unit").build()); + opts.addOption(Option.builder().longOpt("cleanup").desc("Send the cleanup command trigerring JCSystem.requestObjectDeletion() after some operations.").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("to").longOpt("test-options").desc("Test options to use:\n- preset: Use preset semi-random private keys (derived from curve) instead of generating keypairs on the cards when the test needs one.\n- random: Use fully random private keys instead of generating keypairs.").hasArg().argName("options").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()); + opts.addOption(Option.builder("sig").longOpt("sig-type").desc("Set Signature object [type], corresponds to JC.Signature constants.").hasArg().argName("type").optionalArg(true).build()); + opts.addOption(Option.builder("C").longOpt("color").desc("Print stuff with color, requires ANSI terminal.").build()); + + CommandLineParser parser = new DefaultParser(); + return parser.parse(opts, args); + } + + private void listSuites() { + CardTestSuite[] suites = new CardTestSuite[]{ + new CardDefaultSuite(null, null, null), + new CardTestVectorSuite(null, null, null), + new CardCompressionSuite(null, null, null), + new CardWrongSuite(null, null, null), + new CardDegenerateSuite(null, null, null), + new CardCofactorSuite(null, null, null), + new CardCompositeSuite(null, null, null), + new CardInvalidSuite(null, null, null), + new CardEdgeCasesSuite(null, null, null), + new CardSignatureSuite(null, null, null), + new CardTwistSuite(null, null, null), + new CardMiscSuite(null, null, null)}; + for (CardTestSuite suite : suites) { + System.out.println(" - " + Colors.bold(suite.getName())); + for (String line : suite.getDescription()) { + System.out.println("\t" + line); + } + if (suite.getOptions() != null) { + System.out.println("\t" + Colors.underline("Options:") + " " + Arrays.toString(suite.getOptions())); + } + } + System.out.println(); + System.out.println("For more information, look at the documentation at https://github.com/crocs-muni/ECTester."); + } + + private void info() throws CardException { + Response.GetInfo info = new Command.GetInfo(cardManager).send(); + System.out.println(String.format("Card ATR:\t\t\t\t%s", ByteUtil.bytesToHex(cardManager.getATR().getBytes(), false))); + System.out.println(String.format("Card protocol:\t\t\t\t%s", cardManager.getProtocol())); + System.out.println(String.format("ECTester applet version:\t\t%s", info.getVersion())); + System.out.println(String.format("ECTester applet APDU support:\t\t%s", (info.getBase() == CardConsts.BASE_221) ? "basic" : "extended length")); + System.out.println(String.format("JavaCard API version:\t\t\t%.1f", info.getJavaCardVersion())); + System.out.println(String.format("JavaCard supports system cleanup:\t%s", info.getCleanupSupport())); + System.out.println(String.format("Array sizes (apduBuf,ram,ram2,apduArr):\t%d %d %d %d", info.getApduBufferLength(), info.getRamArrayLength(), info.getRamArray2Length(), info.getApduArrayLength())); + } + + /** + * 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<Response> sent = new LinkedList<>(); + sent.add(new Command.Allocate(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.keyBuilder, cfg.bits, keyClass).send()); + //sent.add(new Command.Clear(cardManager, CardConsts.KEYPAIR_LOCAL).send()); + sent.add(new Command.Generate(cardManager, CardConsts.KEYPAIR_LOCAL).send()); + + // Also support exporting set parameters, to verify they are set correctly. + Command curve = Command.prepareCurve(cardManager, cfg, CardConsts.KEYPAIR_LOCAL, cfg.bits, keyClass); + if (curve != null) { + sent.add(curve.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 domain = cfg.primeField ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M; + Response.Export export = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domain).send(); + if (!export.successful()) { + domain = (short) (domain ^ EC_Consts.PARAMETER_K); + export = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domain).send(); + } + sent.add(export); + + for (Response r : sent) { + respWriter.outputResponse(r); + } + if (cfg.cleanup) { + Response cleanup = new Command.Cleanup(cardManager).send(); + respWriter.outputResponse(cleanup); + } + + PrintStream out = new PrintStream(FileUtil.openStream(cfg.outputs)); + byte[][] params = export.getParams(); + for (int i = 0; i < params.length; ++i) { + out.print(ByteUtil.bytesToHex(params[i], false)); + if (i != params.length - 1) { + out.print(","); + } + } + 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; + Command curve = Command.prepareCurve(cardManager, cfg, CardConsts.KEYPAIR_LOCAL, cfg.bits, keyClass); + + Response allocate = new Command.Allocate(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.keyBuilder, cfg.bits, keyClass).send(); + respWriter.outputResponse(allocate); + + OutputStreamWriter keysFile = FileUtil.openFiles(cfg.outputs); + keysFile.write(String.format("index;genTime[%s];exportTime[%s];pubW;privS\n", cfg.timeUnit, cfg.timeUnit)); + + 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, CardConsts.KEYPAIR_LOCAL); + long time = 0; + if (cfg.time) { + time = -Command.dryRunTime(cardManager, generate, 2, respWriter); + } + Response.Generate response = generate.send(); + time += response.getDuration(); + respWriter.outputResponse(response); + + Response.Export export = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR).send(); + respWriter.outputResponse(export); + + if (!response.successful() || !export.successful()) { + if (retry < 10) { + retry++; + continue; + } else { + System.err.println(Colors.error("Keys could not be generated/exported.")); + break; + } + } + + String pub = ByteUtil.bytesToHex(export.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false); + String priv = ByteUtil.bytesToHex(export.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false); + String line = String.format("%d;%d;%d;%s;%s\n", generated, Util.convertTime(time, cfg.timeUnit), Util.convertTime(export.getDuration(), cfg.timeUnit), pub, priv); + keysFile.write(line); + keysFile.flush(); + generated++; + } + if (cfg.cleanup) { + Response cleanup = new Command.Cleanup(cardManager).send(); + respWriter.outputResponse(cleanup); + } + + keysFile.close(); + } + + /** + * Tests Elliptic curve support for a given curve/curves. + * + * @throws IOException if an IO error occurs + */ + private void test() throws ParserConfigurationException, IOException { + TestWriter writer = new FileTestWriter(cfg.format, true, cfg.outputs); + + CardTestSuite suite; + + switch (cfg.testSuite) { + case "default": + suite = new CardDefaultSuite(writer, cfg, cardManager); + break; + case "test-vectors": + suite = new CardTestVectorSuite(writer, cfg, cardManager); + break; + case "compression": + suite = new CardCompressionSuite(writer, cfg, cardManager); + break; + case "miscellaneous": + suite = new CardMiscSuite(writer, cfg, cardManager); + break; + case "signature": + suite = new CardSignatureSuite(writer, cfg, cardManager); + break; + default: + // These run are dangerous, prompt before them. + System.out.println("The test you selected (" + cfg.testSuite + ") is potentially dangerous."); + System.out.println("Some of these run have caused temporary(or even permanent) 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 CardWrongSuite(writer, cfg, cardManager); + break; + case "composite": + suite = new CardCompositeSuite(writer, cfg, cardManager); + break; + case "invalid": + suite = new CardInvalidSuite(writer, cfg, cardManager); + break; + case "degenerate": + suite = new CardDegenerateSuite(writer, cfg, cardManager); + break; + case "twist": + suite = new CardTwistSuite(writer, cfg, cardManager); + break; + case "cofactor": + suite = new CardCofactorSuite(writer, cfg, cardManager); + break; + case "edge-cases": + suite = new CardEdgeCasesSuite(writer, cfg, cardManager); + break; + default: + System.err.println(Colors.error("Unknown test suite.")); + return; + } + break; + } + + suite.run(cfg.testFrom, cfg.testTo); + } + + /** + * 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; + Command curve = Command.prepareCurve(cardManager, cfg, CardConsts.KEYPAIR_BOTH, cfg.bits, keyClass); + List<Response> prepare = new LinkedList<>(); + prepare.add(new Command.AllocateKeyAgreement(cardManager, cfg.ECKAType).send()); // Prepare KeyAgreement or required type + prepare.add(new Command.Allocate(cardManager, CardConsts.KEYPAIR_BOTH, cfg.keyBuilder, cfg.bits, keyClass).send()); + if (curve != null) + prepare.add(curve.send()); + + for (Response r : prepare) { + respWriter.outputResponse(r); + } + + OutputStreamWriter out = null; + if (cfg.outputs != null) { + out = FileUtil.openFiles(cfg.outputs); + out.write(String.format("index;time[%s];pubW;privS;secret[%s]\n", cfg.timeUnit, CardUtil.getKexHashName(cfg.ECKAType))); + } + + Response gen = new Command.Generate(cardManager, CardConsts.KEYPAIR_BOTH).send(); + respWriter.outputResponse(gen); + if (cfg.anyPublicKey || cfg.anyKey) { + Response prep = Command.prepareKey(cardManager, EC_Store.getInstance(), cfg, CardConsts.KEYPAIR_REMOTE, EC_Consts.PARAMETER_W).send(); + respWriter.outputResponse(prep); + } + if (cfg.anyPrivateKey || cfg.anyKey) { + Response prep = Command.prepareKey(cardManager, EC_Store.getInstance(), cfg, CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S).send(); + respWriter.outputResponse(prep); + } + + byte kp = CardConsts.KEYPAIR_BOTH; + if (cfg.fixedPrivate || cfg.anyPrivateKey) { + kp ^= CardConsts.KEYPAIR_LOCAL; + } + if (cfg.fixedPublic || cfg.anyPublicKey) { + kp ^= CardConsts.KEYPAIR_REMOTE; + } + if (cfg.fixedKey || cfg.anyKey) { + kp = 0; + } + + Command generate = null; + if (kp != 0) { + generate = new Command.Generate(cardManager, kp); + } + + int retry = 0; + int done = 0; + while (done < cfg.ECKACount || cfg.ECKACount == 0) { + if (generate != null) { + Response regen = generate.send(); + respWriter.outputResponse(regen); + } + + Response.Export exportRemote = new Command.Export(cardManager, CardConsts.KEYPAIR_REMOTE, EC_Consts.KEY_PUBLIC, EC_Consts.PARAMETER_W).send(); + respWriter.outputResponse(exportRemote); + Response.Export exportLocal = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PRIVATE, EC_Consts.PARAMETER_S).send(); + respWriter.outputResponse(exportLocal); + byte[] pubkey_bytes = exportRemote.getParameter(CardConsts.KEYPAIR_REMOTE, EC_Consts.PARAMETER_W); + byte[] privkey_bytes = exportLocal.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S); + + Command.ECDH perform = new Command.ECDH(cardManager, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, cfg.ECKAType); + + long time = 0; + if (cfg.time) { + time = -Command.dryRunTime(cardManager, perform, 2, respWriter); + } + + Response.ECDH result = perform.send(); + respWriter.outputResponse(result); + + if (!result.successful() || !result.hasSecret()) { + if (retry < 10) { + ++retry; + continue; + } else { + System.err.println(Colors.error("Couldn't obtain ECDH secret from card response.")); + break; + } + } + + if (out != null) { + time += result.getDuration(); + + out.write(String.format("%d;%d;%s;%s;%s\n", done, Util.convertTime(time, cfg.timeUnit), ByteUtil.bytesToHex(pubkey_bytes, false), ByteUtil.bytesToHex(privkey_bytes, false), ByteUtil.bytesToHex(result.getSecret(), false))); + out.flush(); + } + + ++done; + } + if (cfg.cleanup) { + 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; + 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()); + } else { + Random rand = new Random(); + data = new byte[32]; + rand.nextBytes(data); + } + + Command generate; + if (cfg.anyKeypart) { + generate = Command.prepareKey(cardManager, EC_Store.getInstance(), cfg, CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETERS_KEYPAIR); + } else { + generate = new Command.Generate(cardManager, CardConsts.KEYPAIR_LOCAL); + } + + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + List<Response> prepare = new LinkedList<>(); + prepare.add(new Command.AllocateSignature(cardManager, cfg.ECDSAType).send()); + prepare.add(new Command.Allocate(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.keyBuilder, cfg.bits, keyClass).send()); + Command curve = Command.prepareCurve(cardManager, cfg, CardConsts.KEYPAIR_LOCAL, cfg.bits, keyClass); + if (curve != null) + prepare.add(curve.send()); + + for (Response r : prepare) { + respWriter.outputResponse(r); + } + + OutputStreamWriter out = FileUtil.openFiles(cfg.outputs); + if (out != null) { + out.write(String.format("index;signTime[%s];verifyTime[%s];data;pubW;privS;signature[%s];nonce;valid\n", cfg.timeUnit, cfg.timeUnit, CardUtil.getSigHashAlgo(cfg.ECDSAType))); + } + + Command.Export export = new Command.Export(cardManager, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR); + Response.Export exported = null; + if (cfg.fixedKey) { + respWriter.outputResponse(generate.send()); + exported = export.send(); + respWriter.outputResponse(exported); + } + + int retry = 0; + int done = 0; + while (done < cfg.ECDSACount || cfg.ECDSACount == 0) { + if (!cfg.fixedKey) { + respWriter.outputResponse(generate.send()); + exported = export.send(); + respWriter.outputResponse(exported); + } + + Command.ECDSA_sign sign = new Command.ECDSA_sign(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.ECDSAType, CardConsts.EXPORT_TRUE, data); + + long signTime = 0; + if (cfg.time) { + signTime = -Command.dryRunTime(cardManager, sign, 2, respWriter); + } + + Response.ECDSA signResp = sign.send(); + signTime += signResp.getDuration(); + respWriter.outputResponse(signResp); + if (!signResp.successful() || !signResp.hasSignature()) { + if (retry < 10) { + ++retry; + continue; + } else { + System.err.println(Colors.error("Couldn't obtain ECDSA signature from card response.")); + break; + } + } + byte[] signature = signResp.getSignature(); + Command.ECDSA_verify verify = new Command.ECDSA_verify(cardManager, CardConsts.KEYPAIR_LOCAL, cfg.ECDSAType, data, signature); + long verifyTime = 0; + if (cfg.time) { + verifyTime = -Command.dryRunTime(cardManager, verify, 2, respWriter); + } + Response.ECDSA verifyResp = verify.send(); + verifyTime += verifyResp.getDuration(); + respWriter.outputResponse(verifyResp); + + if (verifyResp.error()) { + if (retry < 10) { + ++retry; + continue; + } else { + System.err.println(Colors.error("Couldn't obtain ECDSA signature from card response.")); + break; + } + } + + if (out != null) { + String pub = ByteUtil.bytesToHex(exported.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false); + String priv = ByteUtil.bytesToHex(exported.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false); + String dataString = (cfg.input != null) ? "" : ByteUtil.bytesToHex(data, false); + BigInteger privkey = new BigInteger(1, exported.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S)); + EC_Curve actualCurve = Command.findCurve(cfg, cfg.bits, keyClass); + String k = ""; + if (actualCurve != null) { + ECParameterSpec params = actualCurve.toSpec(); + BigInteger kValue = ECUtil.recoverSignatureNonce(signature, data, privkey, params, CardUtil.getSigHashName(cfg.ECDSAType), "ECDSA"); + if (kValue != null) { + k = ByteUtil.bytesToHex(kValue.toByteArray(), false); + } + } + out.write(String.format("%d;%d;%d;%s;%s;%s;%s;%s;%d\n", done, Util.convertTime(signTime, cfg.timeUnit), Util.convertTime(verifyTime, cfg.timeUnit), dataString, pub, priv, ByteUtil.bytesToHex(signature, false), k, verifyResp.successful() ? 1 : 0)); + out.flush(); + } + + ++done; + } + if (cfg.cleanup) { + 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 short bits; + public boolean all; + public boolean primeField = false; + public boolean binaryField = false; + + + 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 boolean fixedKey = false; + public boolean fixedPrivate = false; + public boolean fixedPublic = false; + public byte keyBuilder; + + public String log; + + public boolean verbose = false; + public String input; + public String[] outputs; + public boolean fresh = false; + public boolean time = false; + public String timeUnit; + public boolean cleanup = false; + public boolean simulate = false; + public boolean yes = false; + public String format; + public boolean color; + + //Action-related options + public String listNamed; + public String testSuite; + public int testFrom; + public int testTo; + public int generateAmount; + public int ECKACount; + public byte ECKAType = EC_Consts.KeyAgreement_ALG_EC_SVDP_DH; + public int ECDSACount; + public byte ECDSAType = EC_Consts.Signature_ALG_ECDSA_SHA; + public Set<String> testOptions; + + /** + * 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 = Short.parseShort(cli.getOptionValue("bit-size", "0")); + all = cli.hasOption("all"); + primeField = cli.hasOption("fp"); + binaryField = cli.hasOption("f2m"); + + + 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; + fixedKey = cli.hasOption("fixed"); + fixedPrivate = cli.hasOption("fixed-private"); + fixedPublic = cli.hasOption("fixed-public"); + keyBuilder = cli.hasOption("key-builder") ? CardConsts.BUILD_KEYBUILDER : CardConsts.BUILD_KEYPAIR; + + 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"); + outputs = cli.getOptionValues("output"); + fresh = cli.hasOption("fresh"); + time = cli.hasOption("time"); + cleanup = cli.hasOption("cleanup"); + simulate = cli.hasOption("simulate"); + yes = cli.hasOption("yes"); + color = cli.hasOption("color"); + Colors.enabled = color; + + timeUnit = cli.getOptionValue("time-unit", "micro"); + String[] times = new String[]{"milli", "micro", "nano"}; + if (!Arrays.asList(times).contains(timeUnit)) { + System.err.println(Colors.error("Wrong time unit " + timeUnit + ". Should be one of " + Arrays.toString(times))); + return false; + } + + if (cli.hasOption("list-named")) { + listNamed = cli.getOptionValue("list-named"); + return true; + } + + format = cli.getOptionValue("format"); + String[] formats = new String[]{"text", "xml", "yaml", "yml"}; + if (format != null && !Arrays.asList(formats).contains(format)) { + System.err.println(Colors.error("Wrong output format " + format + ". Should be one of " + Arrays.toString(formats))); + return false; + } + + if ((key != null || namedKey != null) && (anyPublicKey || anyPrivateKey)) { + System.err.print(Colors.error("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(Colors.error("Bit-size must not be negative.")); + return false; + } + + if (key != null && namedKey != null || publicKey != null && namedPublicKey != null || privateKey != null && namedPrivateKey != null) { + System.err.println(Colors.error("You cannot specify both a named key and a key file.")); + return false; + } + + if (cli.hasOption("export")) { + if (primeField == binaryField) { + System.err.print(Colors.error("Need to specify field with -fp or -f2m. (not both)")); + return false; + } + if (anyKeypart) { + System.err.println(Colors.error("Keys should not be specified when exporting curve params.")); + return false; + } + if (outputs == null) { + System.err.println(Colors.error("You have to specify an output file for curve parameter export.")); + return false; + } + if (all || bits == 0) { + System.err.println(Colors.error("You have to specify curve bit-size with -b")); + return false; + } + } else if (cli.hasOption("generate")) { + if (primeField == binaryField) { + System.err.print(Colors.error("Need to specify field with -fp or -f2m. (not both)")); + return false; + } + if (anyKeypart) { + System.err.println(Colors.error("Keys should not be specified when generating keys.")); + return false; + } + if (outputs == null) { + System.err.println(Colors.error("You have to specify an output file for the key generation process.")); + return false; + } + if (all || bits == 0) { + System.err.println(Colors.error("You have to specify curve bit-size with -b")); + return false; + } + + generateAmount = Integer.parseInt(cli.getOptionValue("generate", "0")); + if (generateAmount < 0) { + System.err.println(Colors.error("Amount of keys generated cant be negative.")); + return false; + } + } else if (cli.hasOption("test")) { + if (!(binaryField || primeField)) { + binaryField = true; + primeField = true; + } + + String suiteOpt = cli.getOptionValue("test", "default").toLowerCase(); + if (suiteOpt.contains(":")) { + String[] parts = suiteOpt.split(":"); + testSuite = parts[0]; + try { + testFrom = Integer.parseInt(parts[1]); + } catch (NumberFormatException nfe) { + System.err.println("Invalid test_from number: " + parts[1] + "."); + return false; + } + if (parts.length == 3) { + try { + testTo = Integer.parseInt(parts[2]); + } catch (NumberFormatException nfe) { + System.err.println("Invalid test_to number: " + parts[2] + "."); + return false; + } + } else if (parts.length != 2) { + System.err.println("Invalid test suite selection."); + return false; + } else { + testTo = -1; + } + } else { + testSuite = suiteOpt; + testFrom = 0; + testTo = -1; + } + + String[] tests = new String[]{"default", "composite", "compression", "invalid", "degenerate", "test-vectors", "wrong", "twist", "cofactor", "edge-cases", "miscellaneous", "signature"}; + String selected = null; + for (String test : tests) { + if (test.startsWith(testSuite)) { + if (selected != null) { + System.err.println(Colors.error("Test suite ambiguous " + test + " or " + selected + "?")); + return false; + } else { + selected = test; + } + } + } + if (selected == null) { + System.err.println(Colors.error("Unknown test suite " + testSuite + ". Should be one of: " + Arrays.toString(tests))); + return false; + } else { + testSuite = selected; + } + + String[] opts = cli.getOptionValue("test-options", "").split(","); + List<String> validOpts = Arrays.asList("preset", "random"); + testOptions = new HashSet<>(); + for (String opt : opts) { + if (opt.equals("")) { + continue; + } + if (!validOpts.contains(opt)) { + System.err.println(Colors.error("Unknown test option " + opt + ". Should be one of: " + Arrays.toString(validOpts.toArray()))); + return false; + } else { + testOptions.add(opt); + } + } + + if (testOptions.contains("preset") && testOptions.contains("random")) { + System.err.println("Cannot have both preset and random option enabled."); + return false; + } + } else if (cli.hasOption("ecdh")) { + if (primeField == binaryField) { + System.err.print(Colors.error("Need to specify field with -fp or -f2m. (not both)")); + return false; + } + if (all || bits == 0) { + System.err.println(Colors.error("You have to specify curve bit-size with -b")); + return false; + } + + ECKACount = Integer.parseInt(cli.getOptionValue("ecdh", "1")); + if (ECKACount < 0) { + System.err.println(Colors.error("ECDH count cannot be < 0.")); + return false; + } + + ECKAType = CardUtil.parseKAType(cli.getOptionValue("ka-type", "1")); + } else if (cli.hasOption("ecdsa")) { + if (primeField == binaryField) { + System.err.print(Colors.error("Need to specify field with -fp or -f2m. (but not both)")); + return false; + } + if (all || bits == 0) { + System.err.println(Colors.error("You have to specify curve bit-size with -b")); + return false; + } + + if ((anyPublicKey) != (anyPrivateKey) && !anyKey) { + System.err.println(Colors.error("You cannot only specify a part of a keypair.")); + return false; + } + + ECDSACount = Integer.parseInt(cli.getOptionValue("ecdsa", "1")); + if (ECDSACount < 0) { + System.err.println(Colors.error("ECDSA count cannot be < 0.")); + return false; + } + + ECDSAType = CardUtil.parseSigType(cli.getOptionValue("sig-type", "17")); + } + return true; + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/ISO7816_status_words.txt b/reader/src/main/java/cz/crcs/ectester/reader/ISO7816_status_words.txt new file mode 100644 index 0000000..bf5af2b --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/ISO7816_status_words.txt @@ -0,0 +1,71 @@ +public interface ISO7816 { + + // Fields + public static final byte INS_EXTERNAL_AUTHENTICATE = -126; + public static final byte INS_SELECT = -92; + public static final byte CLA_ISO7816 = 0; + public static final byte OFFSET_CDATA = 5; + public static final byte OFFSET_LC = 4; + public static final byte OFFSET_P2 = 3; + public static final byte OFFSET_P1 = 2; + public static final byte OFFSET_INS = 1; + public static final byte OFFSET_CLA = 0; + public static final short SW_FILE_FULL = 27268; 0x6A84 + public static final short SW_UNKNOWN = 28416; 0x6F00 + public static final short SW_CLA_NOT_SUPPORTED = 28160; 0x6E00 + public static final short SW_INS_NOT_SUPPORTED = 27904; 0x6D00 + public static final short SW_CORRECT_LENGTH_00 = 27648; 0x6C00 + public static final short SW_WRONG_P1P2 = 27392; 0x6B00 + public static final short SW_INCORRECT_P1P2 = 27270; 0x6A86 + public static final short SW_RECORD_NOT_FOUND = 27267; 0x6A83 + public static final short SW_FILE_NOT_FOUND = 27266; 0x6A82 + public static final short SW_FUNC_NOT_SUPPORTED = 27265; 0x6A81 + public static final short SW_WRONG_DATA = 27264; 0x6A80 + public static final short SW_APPLET_SELECT_FAILED = 27033; 0x6999 + public static final short SW_COMMAND_NOT_ALLOWED = 27014; 0x6986 + public static final short SW_CONDITIONS_NOT_SATISFIED = 27013; 0x6985 + public static final short SW_DATA_INVALID = 27012; 0x6984 + public static final short SW_FILE_INVALID = 27011; 0x6983 + public static final short SW_SECURITY_STATUS_NOT_SATISFIED = 27010; 0x6982 + public static final short SW_WRONG_LENGTH = 26368; 0x6700 + public static final short SW_BYTES_REMAINING_00 = 24832; 0x6100 + public static final short SW_NO_ERROR = -28672; 0x9000 +} + +public interface JCStatus { +static int ALGORITHM_NOT_SUPPORTED = 0x9484; +static int APPLET_INVALIDATED = 0x6283; +static int APPLET_SELECT_FAILED = 0x6999 +static int AUTHENTICATION_FAILED = 0x6300 +static int AUTHORIZATION_FAILED = 0x9482 +static int CHECKSUM_FAILED = 0x9584 +static int CLA_NOT_SUPPORTED = 0x6E00 +static int COMMAND_NOT_ALLOWED = 0x6986 +static int CONDITIONS_NOT_SATISFIED = 0x6985 +static int CORRECT_LENGTH_00 = 0x6C00 +static int DATA_INVALID = 0x6984 +static int DECRYPTION_FAILED = 0x9583 +static int FILE_FULL = 0x6A84 +static int FILE_INVALID = 0x6983 +static int FILE_NOT_FOUND = 0x6A82 +static int FUNC_NOT_SUPPORTED = 0x6A81 +static int INCORRECT_P1P2 = 0x6A86 +static int INS_NOT_SUPPORTED = 0x6D00 +static int INSTALLATION_FAILED = 0x9585 +static int INVALID_STATE = 0x9481 +static int NO_ERROR = 0x9000 +static int NO_SPECIFIC_DIAGNOSIS = 0x6400 +static int PIN_REQUIRED = 0x6982 +static int RECORD_NOT_FOUND = 0x6A83 +static int REFERENCE_DATA_NOT_FOUND = 0x6A88 +static int REGISTRATION_FAILED = 0x9586 +static int SECURITY_STATUS_NOT_SATISFIED = 0x6982 +static int SIGNATURE_CHECK_FAILED = 0x9582 +static int SM_INCORRECT = 0x6988 +static int SM_MISSING = 0x6987 +static int TRUNCATED_DATA = 0x6100 +static int UNKNOWN = 0x6F00 +static int WRONG_DATA = 0x6A80 +static int WRONG_LENGTH = 0x6700 +static int WRONG_P1P2 = 0x6B00 +}
\ No newline at end of file diff --git a/reader/src/main/java/cz/crcs/ectester/reader/command/Command.java b/reader/src/main/java/cz/crcs/ectester/reader/command/Command.java new file mode 100644 index 0000000..051d377 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/command/Command.java @@ -0,0 +1,920 @@ +package cz.crcs.ectester.reader.command; + +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Params; +import cz.crcs.ectester.common.ec.EC_Consts; +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.common.util.CardConsts; +import cz.crcs.ectester.data.EC_Store; +import cz.crcs.ectester.reader.CardMngr; +import cz.crcs.ectester.reader.ECTesterReader; +import cz.crcs.ectester.reader.output.ResponseWriter; +import cz.crcs.ectester.reader.response.Response; +import javacard.security.KeyPair; + +import javax.smartcardio.CardException; +import javax.smartcardio.CommandAPDU; +import javax.smartcardio.ResponseAPDU; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class Command implements Cloneable { + CommandAPDU cmd; + CardMngr cardManager; + // Workaround for a stupid Java bug that went unfixed for !12! years, + // and for the even more stupid module system, which cannot properly work + // with the fact that JCardSim has some java.* packages... + final byte[] GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM = new byte[]{0}; + + + Command(CardMngr cardManager) { + this.cardManager = cardManager; + } + + public CommandAPDU getAPDU() { + return cmd; + } + + public abstract Response send() throws CardException; + + public static List<Response> sendAll(List<Command> commands) throws CardException { + List<Response> result = new ArrayList<>(); + for (Command cmd : commands) { + result.add(cmd.send()); + } + return result; + } + + public abstract String getDescription(); + + @Override + protected Command clone() throws CloneNotSupportedException { + return (Command) super.clone(); + } + + public static EC_Curve findCurve(ECTesterReader.Config cfg, short keyLength, byte keyClass) throws IOException { + if (cfg.customCurve) { + byte curveId = EC_Consts.getCurve(keyLength, keyClass); + return EC_Store.getInstance().getObject(EC_Curve.class, "secg", CardUtil.getCurveName(curveId)); + } else if (cfg.namedCurve != null) { + EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, cfg.namedCurve); + if (curve == null) { + throw new IOException("Curve could no be found."); + } + if (curve.getBits() != keyLength) { + throw new IOException("Curve bits mismatch: " + curve.getBits() + " vs " + keyLength + " entered."); + } + if (curve.getField() != keyClass) { + throw new IOException("Curve field mismatch."); + } + return curve; + } else if (cfg.curveFile != null) { + EC_Curve curve = new EC_Curve(null, keyLength, keyClass); + + FileInputStream in = new FileInputStream(cfg.curveFile); + curve.readCSV(in); + in.close(); + return curve; + } else { + return null; + } + } + + + /** + * @param keyPair which keyPair/s (local/remote) to set curve domain parameters on + * @param keyLength key length to choose + * @param keyClass key class to choose + * @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, 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) + short domainParams = keyClass == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M; + return new Command.Set(cardManager, keyPair, EC_Consts.getCurve(keyLength, keyClass), domainParams, null); + } + + EC_Curve curve = findCurve(cfg, keyLength, keyClass); + if ((curve == null || curve.flatten() == null) && (cfg.namedCurve != null || cfg.curveFile != null)) { + if (cfg.namedCurve != null) { + throw new IOException("Couldn't read named curve data."); + } + throw new IOException("Couldn't read the curve file correctly."); + } else if (curve == null) { + return null; + } + return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()); + } + + + /** + * @param cardManager + * @param dataStore + * @param cfg + * @param keyPair which keyPair/s to set the key params on + * @param allowedParams + * @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, ECTesterReader.Config cfg, byte keyPair, short allowedParams) throws IOException { + short params = EC_Consts.PARAMETERS_NONE; + byte[] data = null; + + if (cfg.key != null || cfg.namedKey != null) { + params |= EC_Consts.PARAMETERS_KEYPAIR; + EC_Params keypair = ECUtil.loadParams(EC_Consts.PARAMETERS_KEYPAIR, cfg.namedKey, cfg.key); + if (keypair == null) { + throw new IOException("KeyPair not found."); + } + + data = keypair.flatten(); + if (data == null) { + throw new IOException("Couldn't read the key file correctly."); + } + } + + if ((cfg.publicKey != null || cfg.namedPublicKey != null) && ((allowedParams & EC_Consts.PARAMETER_W) != 0)) { + params |= EC_Consts.PARAMETER_W; + EC_Params pub = ECUtil.loadParams(EC_Consts.PARAMETER_W, cfg.namedPublicKey, cfg.publicKey); + if (pub == null) { + throw new IOException("Public key not found."); + } + + byte[] pubkey = pub.flatten(EC_Consts.PARAMETER_W); + if (pubkey == null) { + throw new IOException("Couldn't read the public key file correctly."); + } + data = pubkey; + } + + if ((cfg.privateKey != null || cfg.namedPrivateKey != null) && ((allowedParams & EC_Consts.PARAMETER_S) != 0)) { + params |= EC_Consts.PARAMETER_S; + EC_Params priv = ECUtil.loadParams(EC_Consts.PARAMETER_S, cfg.namedPrivateKey, cfg.privateKey); + if (priv == null) { + throw new IOException("Private key not found."); + } + + byte[] privkey = priv.flatten(EC_Consts.PARAMETER_S); + if (privkey == null) { + throw new IOException("Couldn't read the private key file correctly."); + } + data = ByteUtil.concatenate(data, privkey); + } + return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, params, data); + } + + public static long dryRunTime(CardMngr cardManager, Command cmd, int num, ResponseWriter respWriter) throws CardException { + long time = 0; + respWriter.outputResponse(new Command.SetDryRunMode(cardManager, CardConsts.MODE_DRY_RUN).send()); + for (int i = 0; i < num; ++i) { + Response dry = cmd.send(); + respWriter.outputResponse(dry); + time += dry.getDuration(); + } + time /= num; + respWriter.outputResponse(new Command.SetDryRunMode(cardManager, CardConsts.MODE_NORMAL).send()); + return time; + } + + /** + * + */ + public static class AllocateKeyAgreement extends Command { + private byte kaType; + + /** + * Creates the INS_ALLOCATE_KA instruction. + * + * @param cardManager cardManager to send APDU through + * @param kaType which type of KeyAgreement to use + */ + public AllocateKeyAgreement(CardMngr cardManager, byte kaType) { + super(cardManager); + this.kaType = kaType; + byte[] data = new byte[]{kaType}; + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ALLOCATE_KA, 0x00, 0x00, data); + } + + @Override + public Response.AllocateKeyAgreement send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.AllocateKeyAgreement(response, getDescription(), elapsed, kaType); + } + + @Override + public String getDescription() { + return String.format("Allocate KeyAgreement(%s) object", CardUtil.getKATypeString(kaType)); + } + } + + /** + * + */ + public static class AllocateSignature extends Command { + private byte sigType; + + /** + * Creates the INS_ALLOCATE_SIG instruction. + * + * @param cardManager cardManager to send APDU through + * @param sigType which type of Signature to use + */ + public AllocateSignature(CardMngr cardManager, byte sigType) { + super(cardManager); + this.sigType = sigType; + byte[] data = new byte[]{sigType}; + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ALLOCATE_SIG, 0x00, 0x00, data); + } + + @Override + public Response.AllocateSignature send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.AllocateSignature(response, getDescription(), elapsed, sigType); + } + + @Override + public String getDescription() { + return String.format("Allocate Signature(%s) object", CardUtil.getSigTypeString(sigType)); + } + } + + /** + * + */ + public static class Allocate extends Command { + private byte keyPair; + private byte build; + private short keyLength; + private byte keyClass; + + /** + * Creates the INS_ALLOCATE instruction. + * + * @param cardManager cardManager to send APDU through + * @param keyPair which keyPair to use, local/remote (KEYPAIR_* | ...) + * @param build whether to use KeyBuilder or Keypair alloc + * @param keyLength key length to set + * @param keyClass key class to allocate + */ + public Allocate(CardMngr cardManager, byte keyPair, byte build, short keyLength, byte keyClass) { + super(cardManager); + this.keyPair = keyPair; + this.build = build; + this.keyLength = keyLength; + this.keyClass = keyClass; + + byte[] data = new byte[]{0, 0, keyClass}; + ByteUtil.setShort(data, 0, keyLength); + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ALLOCATE, keyPair, build, data); + } + + public Allocate(CardMngr cardManager, byte keyPair, short keyLength, byte keyClass) { + this(cardManager, keyPair, (byte) (CardConsts.BUILD_KEYPAIR | CardConsts.BUILD_KEYBUILDER), keyLength, keyClass); + } + + @Override + public Response.Allocate send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Allocate(response, getDescription(), elapsed, keyPair, keyLength, keyClass); + } + + @Override + public String getDescription() { + String field = keyClass == KeyPair.ALG_EC_FP ? "ALG_EC_FP" : "ALG_EC_F2M"; + String key; + if (keyPair == CardConsts.KEYPAIR_BOTH) { + key = "both keypairs"; + } else { + key = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return String.format("Allocate %s %db %s", key, keyLength, field); + } + } + + /** + * + */ + public static class Clear extends Command { + private byte keyPair; + + /** + * @param cardManager cardManager to send APDU through + * @param keyPair which keyPair clear, local/remote (KEYPAIR_* || ...) + */ + public Clear(CardMngr cardManager, byte keyPair) { + super(cardManager); + this.keyPair = keyPair; + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_CLEAR, keyPair, 0x00, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM); + } + + @Override + public Response.Clear send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Clear(response, getDescription(), elapsed, keyPair); + } + + @Override + public String getDescription() { + String key; + if (keyPair == CardConsts.KEYPAIR_BOTH) { + key = "both keypairs"; + } else { + key = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return String.format("Clear %s", key); + } + } + + /** + * + */ + public static class Set extends Command { + private byte keyPair; + private byte curve; + private short params; + private byte[] external; + + /** + * Creates the INS_SET instruction. + * + * @param cardManager cardManager to send APDU through + * @param keyPair which keyPair to set params on, local/remote (KEYPAIR_* || ...) + * @param curve curve to set (EC_Consts.CURVE_*) + * @param params parameters to set (EC_Consts.PARAMETER_* | ...) + * @param external external curve data, can be null + */ + public Set(CardMngr cardManager, byte keyPair, byte curve, short params, byte[] external) { + super(cardManager); + this.keyPair = keyPair; + this.curve = curve; + this.params = params; + this.external = external; + + int len = external != null ? 2 + external.length : 2; + byte[] data = new byte[len]; + ByteUtil.setShort(data, 0, params); + if (external != null) { + System.arraycopy(external, 0, data, 2, external.length); + } + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_SET, keyPair, curve, data); + } + + @Override + public Response.Set send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Set(response, getDescription(), elapsed, keyPair, curve, params); + } + + @Override + public String getDescription() { + String name = CardUtil.getCurveName(curve); + String what = CardUtil.getParameterString(params); + + String pair; + if (keyPair == CardConsts.KEYPAIR_BOTH) { + pair = "both keypairs"; + } else { + pair = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return String.format("Set %s %s parameters on %s", name, what, pair); + } + } + + /** + * + */ + public static class Transform extends Command { + private byte keyPair; + private byte key; + private short params; + private short transformation; + + /** + * @param cardManager cardManager to send APDU through + * @param keyPair which keyPair to transform, local/remote (KEYPAIR_* || ...) + * @param key key to transform (EC_Consts.KEY_* | ...) + * @param params parameters to transform (EC_Consts.PARAMETER_* | ...) + * @param transformation transformation type (EC_Consts.TRANSFORMATION_*) + */ + public Transform(CardMngr cardManager, byte keyPair, byte key, short params, short transformation) { + super(cardManager); + this.keyPair = keyPair; + this.key = key; + this.params = params; + this.transformation = transformation; + + byte[] data = new byte[4]; + ByteUtil.setShort(data, 0, params); + ByteUtil.setShort(data, 2, transformation); + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_TRANSFORM, keyPair, key, data); + } + + @Override + public Response.Transform send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Transform(response, getDescription(), elapsed, keyPair, key, params, transformation); + } + + @Override + public String getDescription() { + String stringParams = CardUtil.getParams(params); + String transform = CardUtil.getTransformation(transformation); + + String pair; + if (keyPair == CardConsts.KEYPAIR_BOTH) { + pair = "both keypairs"; + } else { + pair = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return String.format("Transform params %s of %s, %s", stringParams, pair, transform); + } + } + + /** + * + */ + public static class Generate extends Command { + private byte keyPair; + + /** + * Creates the INS_GENERATE instruction. + * + * @param cardManager cardManager to send APDU through + * @param keyPair which keyPair to generate, local/remote (KEYPAIR_* || ...) + */ + public Generate(CardMngr cardManager, byte keyPair) { + super(cardManager); + this.keyPair = keyPair; + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_GENERATE, keyPair, 0, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM); + } + + @Override + public Response.Generate send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Generate(response, getDescription(), elapsed, keyPair); + } + + @Override + public String getDescription() { + String key; + if (keyPair == CardConsts.KEYPAIR_BOTH) { + key = "both keypairs"; + } else { + key = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return String.format("Generate %s", key); + } + } + + /** + * + */ + public static class Export extends Command { + private byte keyPair; + private byte key; + private short params; + + /** + * Creates the INS_EXPORT instruction. + * + * @param cardManager cardManager to send APDU through + * @param keyPair keyPair to export from (KEYPAIR_* | ...) + * @param key key to export from (EC_Consts.KEY_* | ...) + * @param params params to export (EC_Consts.PARAMETER_* | ...) + */ + public Export(CardMngr cardManager, byte keyPair, byte key, short params) { + super(cardManager); + this.keyPair = keyPair; + this.key = key; + this.params = params; + + byte[] data = new byte[2]; + ByteUtil.setShort(data, 0, params); + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_EXPORT, keyPair, key, data); + } + + @Override + public Response.Export send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Export(response, getDescription(), elapsed, keyPair, key, params); + } + + @Override + public String getDescription() { + String what = CardUtil.getParameterString(params); + + String source; + if (key == EC_Consts.KEY_BOTH) { + source = "both keys"; + } else { + source = ((key == EC_Consts.KEY_PUBLIC) ? "public" : "private") + " key"; + } + String pair; + if (keyPair == CardConsts.KEYPAIR_BOTH) { + pair = "both keypairs"; + } else { + pair = ((keyPair == CardConsts.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return String.format("Export %s params from %s of %s", what, source, pair); + } + } + + /** + * + */ + public static class ECDH extends Command { + private byte pubkey; + private byte privkey; + private byte export; + private short transformation; + private byte type; + + /** + * Creates the INS_ECDH instruction. + * + * @param cardManager cardManager to send APDU through + * @param pubkey keyPair to use for public key, (KEYPAIR_LOCAL || KEYPAIR_REMOTE) + * @param privkey keyPair to use for private key, (KEYPAIR_LOCAL || KEYPAIR_REMOTE) + * @param export whether to export ECDH secret + * @param transformation whether to transform the pubkey before ECDH (EC_Consts.TRANSFORMATION_* | ...) + * @param type ECDH algorithm type (EC_Consts.KA_* | ...) + */ + public ECDH(CardMngr cardManager, byte pubkey, byte privkey, byte export, short transformation, byte type) { + super(cardManager); + this.pubkey = pubkey; + this.privkey = privkey; + this.export = export; + this.transformation = transformation; + this.type = type; + + byte[] data = new byte[]{export, 0, 0, type}; + ByteUtil.setShort(data, 1, transformation); + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ECDH, pubkey, privkey, data); + } + + @Override + public Response.ECDH send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.ECDH(response, getDescription(), elapsed, pubkey, privkey, export, transformation, type); + } + + @Override + public String getDescription() { + String algo = CardUtil.getKATypeString(type); + + String pub = pubkey == CardConsts.KEYPAIR_LOCAL ? "local" : "remote"; + String priv = privkey == CardConsts.KEYPAIR_LOCAL ? "local" : "remote"; + + String validity; + if (transformation == EC_Consts.TRANSFORMATION_NONE) { + validity = ""; + } else { + validity = String.format("(%s point)", CardUtil.getTransformation(transformation)); + } + return String.format("%s of %s pubkey and %s privkey%s", algo, pub, priv, validity); + } + } + + /** + * + */ + public static class ECDH_direct extends Command { + private byte privkey; + private byte export; + private short transformation; + private byte type; + private byte[] pubkey; + + /** + * Creates the INS_ECDH_DIRECT instruction. + * + * @param cardManager cardManager to send APDU through + * @param privkey keyPair to use for private key, (KEYPAIR_LOCAL || KEYPAIR_REMOTE) + * @param export whether to export ECDH secret + * @param transformation whether to transform the pubkey before ECDH (EC_Consts.TRANSFORMATION_* | ...) + * @param type EC KeyAgreement type + * @param pubkey pubkey data to do ECDH with. + */ + public ECDH_direct(CardMngr cardManager, byte privkey, byte export, short transformation, byte type, byte[] pubkey) { + super(cardManager); + this.privkey = privkey; + this.export = export; + this.transformation = transformation; + this.type = type; + this.pubkey = pubkey; + + byte[] data = new byte[3 + pubkey.length]; + ByteUtil.setShort(data, 0, transformation); + data[2] = type; + System.arraycopy(pubkey, 0, data, 3, pubkey.length); + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ECDH_DIRECT, privkey, export, data); + } + + @Override + public Response.ECDH send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.ECDH(response, getDescription(), elapsed, CardConsts.KEYPAIR_REMOTE, privkey, export, transformation, type); + } + + @Override + public String getDescription() { + String algo = CardUtil.getKATypeString(type); + + String priv = privkey == CardConsts.KEYPAIR_LOCAL ? "local" : "remote"; + + String validity; + if (transformation == EC_Consts.TRANSFORMATION_NONE) { + validity = ""; + } else { + validity = String.format("(%s point)", CardUtil.getTransformation(transformation)); + } + return String.format("%s of external pubkey and %s privkey%s", algo, priv, validity); + } + } + + public static class ECDSA extends Command { + private byte keyPair; + private byte sigType; + private byte export; + private byte[] raw; + + /** + * Creates the INS_ECDSA instruction. + * + * @param cardManager cardManager to send APDU through + * @param keyPair keyPair to use for signing and verification (KEYPAIR_LOCAL || KEYPAIR_REMOTE) + * @param sigType Signature type to use + * @param export whether to export ECDSA signature + * @param raw data to sign, can be null, in which case random data is signed. + */ + public ECDSA(CardMngr cardManager, byte keyPair, byte sigType, byte export, byte[] raw) { + super(cardManager); + if (keyPair == CardConsts.KEYPAIR_BOTH) { + throw new IllegalArgumentException(); + } + + this.keyPair = keyPair; + this.sigType = sigType; + this.export = export; + this.raw = raw; + + int len = raw != null ? raw.length : 0; + byte[] data = new byte[3 + len]; + data[0] = sigType; + ByteUtil.setShort(data, 1, (short) len); + if (raw != null) { + System.arraycopy(raw, 0, data, 3, len); + } + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ECDSA, keyPair, export, data); + } + + @Override + public Response.ECDSA send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.ECDSA(response, getDescription(), elapsed, keyPair, sigType, export, raw); + } + + @Override + public String getDescription() { + String algo = CardUtil.getSigTypeString(sigType); + String key = keyPair == CardConsts.KEYPAIR_LOCAL ? "local" : "remote"; + String data = raw == null ? "random" : "provided"; + return String.format("%s with %s keypair(%s data)", algo, key, data); + } + } + + public static class ECDSA_sign extends Command { + private byte keyPair; + private byte sigType; + private byte export; + private byte[] raw; + + /** + * Creates the INS_ECDSA_SIGN instruction. + * + * @param cardManager cardManager to send APDU through + * @param keyPair keyPair to use for signing and verification (KEYPAIR_LOCAL || KEYPAIR_REMOTE) + * @param sigType Signature type to use + * @param export whether to export ECDSA signature + * @param raw data to sign, can be null, in which case random data is signed. + */ + public ECDSA_sign(CardMngr cardManager, byte keyPair, byte sigType, byte export, byte[] raw) { + super(cardManager); + if (keyPair == CardConsts.KEYPAIR_BOTH) { + throw new IllegalArgumentException(); + } + + this.keyPair = keyPair; + this.sigType = sigType; + this.export = export; + this.raw = raw; + + int len = raw != null ? raw.length : 0; + byte[] data = new byte[3 + len]; + data[0] = sigType; + ByteUtil.setShort(data, 1, (short) len); + if (raw != null) { + System.arraycopy(raw, 0, data, 3, len); + } + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ECDSA_SIGN, keyPair, export, data); + } + + @Override + public Response.ECDSA send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.ECDSA(response, getDescription(), elapsed, keyPair, sigType, export, raw); + } + + @Override + public String getDescription() { + String algo = CardUtil.getSigTypeString(sigType); + String key = keyPair == CardConsts.KEYPAIR_LOCAL ? "local" : "remote"; + String data = raw == null ? "random" : "provided"; + return String.format("%s signature with %s keypair(%s data)", algo, key, data); + } + } + + public static class ECDSA_verify extends Command { + private byte keyPair; + private byte sigType; + private byte[] raw; + private byte[] signature; + + /** + * Creates the INS_ECDSA_VERIFY instruction. + * + * @param cardManager cardManager to send APDU through + * @param keyPair keyPair to use for signing and verification (KEYPAIR_LOCAL || KEYPAIR_REMOTE) + * @param sigType Signature type to use + * @param raw data to sign + * @param signature signature data + */ + public ECDSA_verify(CardMngr cardManager, byte keyPair, byte sigType, byte[] raw, byte[] signature) { + super(cardManager); + if (keyPair == CardConsts.KEYPAIR_BOTH) { + throw new IllegalArgumentException(); + } + if (raw == null || signature == null) { + throw new IllegalArgumentException(); + } + + this.keyPair = keyPair; + this.sigType = sigType; + this.raw = raw; + this.signature = signature; + + byte[] data = new byte[4 + raw.length + signature.length]; + ByteUtil.setShort(data, 0, (short) raw.length); + System.arraycopy(raw, 0, data, 2, raw.length); + ByteUtil.setShort(data, 2 + raw.length, (short) signature.length); + System.arraycopy(signature, 0, data, 2 + raw.length + 2, signature.length); + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_ECDSA_VERIFY, keyPair, sigType, data); + } + + @Override + public Response.ECDSA send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.ECDSA(response, getDescription(), elapsed, keyPair, sigType, CardConsts.EXPORT_FALSE, raw); + } + + @Override + public String getDescription() { + String algo = CardUtil.getSigTypeString(sigType); + String key = keyPair == CardConsts.KEYPAIR_LOCAL ? "local" : "remote"; + String data = raw == null ? "random" : "provided"; + return String.format("%s verification with %s keypair(%s data)", algo, key, data); + } + } + + /** + * + */ + public static class Cleanup extends Command { + + /** + * @param cardManager cardManager to send APDU through + */ + public Cleanup(CardMngr cardManager) { + super(cardManager); + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_CLEANUP, 0, 0, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM); + } + + @Override + public Response.Cleanup send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Cleanup(response, getDescription(), elapsed); + } + + @Override + public String getDescription() { + return "Request JCSystem object deletion"; + } + } + + /** + * + */ + public static class GetInfo extends Command { + + /** + * @param cardManager cardManager to send APDU through + */ + public GetInfo(CardMngr cardManager) { + super(cardManager); + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_GET_INFO, 0, 0, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM); + } + + @Override + public Response.GetInfo send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.GetInfo(response, getDescription(), elapsed); + } + + @Override + public String getDescription() { + return "Get applet info"; + } + } + + /** + * + */ + public static class SetDryRunMode extends Command { + private byte dryRunMode; + + /** + * @param cardManager + * @param dryRunMode + */ + public SetDryRunMode(CardMngr cardManager, byte dryRunMode) { + super(cardManager); + this.dryRunMode = dryRunMode; + + this.cmd = new CommandAPDU(CardConsts.CLA_ECTESTERAPPLET, CardConsts.INS_SET_DRY_RUN_MODE, dryRunMode, 0, GOD_DAMN_JAVA_BUG_6474858_AND_GOD_DAMN_JAVA_12_MODULE_SYSTEM); + } + + @Override + public Response.SetDryRunMode send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.SetDryRunMode(response, getDescription(), elapsed); + } + + @Override + public String getDescription() { + return (dryRunMode == CardConsts.MODE_NORMAL ? "Disable" : "Enable") + " dry run mode"; + } + } +} + diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/FileTestWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/FileTestWriter.java new file mode 100644 index 0000000..69e5f41 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/output/FileTestWriter.java @@ -0,0 +1,53 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.common.output.TeeTestWriter; +import cz.crcs.ectester.common.output.TestWriter; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.util.regex.Pattern; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class FileTestWriter extends TeeTestWriter { + + private static final Pattern PREFIX = Pattern.compile("(text|xml|yaml|yml):.+"); + + public FileTestWriter(String defaultFormat, boolean systemOut, String[] files) throws ParserConfigurationException, FileNotFoundException { + int fLength = files == null ? 0 : files.length; + writers = new TestWriter[systemOut ? fLength + 1 : fLength]; + if (systemOut) { + writers[0] = createWriter(defaultFormat, System.out); + } + for (int i = 0; i < fLength; ++i) { + String fName = files[i]; + String format = null; + if (PREFIX.matcher(fName).matches()) { + String[] split = fName.split(":", 2); + format = split[0]; + fName = split[1]; + } + writers[i + 1] = createWriter(format, new PrintStream(new FileOutputStream(fName))); + } + } + + private TestWriter createWriter(String format, PrintStream out) throws ParserConfigurationException { + if (format == null) { + return new TextTestWriter(out); + } + switch (format) { + case "text": + return new TextTestWriter(out); + case "xml": + return new XMLTestWriter(out); + case "yaml": + case "yml": + return new YAMLTestWriter(out); + default: + return null; + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/ResponseWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/ResponseWriter.java new file mode 100644 index 0000000..85bf79a --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/output/ResponseWriter.java @@ -0,0 +1,43 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.common.util.CardUtil; +import cz.crcs.ectester.reader.response.Response; + +import java.io.PrintStream; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class ResponseWriter { + private PrintStream output; + + public ResponseWriter(PrintStream output) { + this.output = output; + } + + public String responseSuffix(Response r) { + StringBuilder suffix = new StringBuilder(); + for (int j = 0; j < r.getNumSW(); ++j) { + short sw = r.getSW(j); + if (sw != 0) { + suffix.append(" ").append(CardUtil.getSWString(sw)); + } + } + if (suffix.length() == 0) { + suffix.append(" [").append(CardUtil.getSW(r.getNaturalSW())).append(String.format(" 0x%04x", r.getNaturalSW())).append("]"); + } + return String.format("%4d ms ┃ %s", r.getDuration() / 1000000, suffix); + } + + public String responseString(Response r) { + String out = ""; + out += String.format("%-70s", r.getDescription()) + " ┃ "; + out += responseSuffix(r); + return out; + } + + public void outputResponse(Response r) { + output.println(responseString(r)); + output.flush(); + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/TextTestWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/TextTestWriter.java new file mode 100644 index 0000000..532ace5 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/output/TextTestWriter.java @@ -0,0 +1,71 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.common.cli.Colors; +import cz.crcs.ectester.common.output.BaseTextTestWriter; +import cz.crcs.ectester.common.test.TestSuite; +import cz.crcs.ectester.common.test.Testable; +import cz.crcs.ectester.common.util.ByteUtil; +import cz.crcs.ectester.common.util.CardConsts; +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 cz.crcs.ectester.reader.test.CardTestSuite; +import cz.crcs.ectester.reader.test.CommandTestable; + +import javax.smartcardio.CardException; +import java.io.PrintStream; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class TextTestWriter extends BaseTextTestWriter { + private ResponseWriter writer; + + public TextTestWriter(PrintStream output) { + super(output); + this.writer = new ResponseWriter(output); + } + + @Override + protected String testableString(Testable t) { + if (t instanceof CommandTestable) { + CommandTestable cmd = (CommandTestable) t; + Response response = cmd.getResponse(); + if (response != null) { + return writer.responseSuffix(response); + } + } + return ""; + } + + @Override + protected String deviceString(TestSuite suite) { + if (suite instanceof CardTestSuite) { + CardTestSuite cardSuite = (CardTestSuite) suite; + StringBuilder sb = new StringBuilder(); + try { + sb.append("═══ ").append(Colors.underline("ECTester version:")).append(" ").append(ECTesterReader.VERSION).append(ECTesterReader.GIT_COMMIT).append(System.lineSeparator()); + Response.GetInfo info = new Command.GetInfo(cardSuite.getCard()).send(); + sb.append("═══ ").append(Colors.underline("ECTester applet version:")).append(" ").append(info.getVersion()).append(info.getBase() == CardConsts.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()); + sb.append("═══ ").append(Colors.underline("Test options:")).append(" ").append(String.join(",", cardSuite.getCfg().testOptions)).append(System.lineSeparator()); + CardMngr.CPLC cplc = cardSuite.getCard().getCPLC(); + if (!cplc.values().isEmpty()) { + sb.append("═══ ").append(Colors.underline("Card CPLC data:")).append(System.lineSeparator()); + for (Map.Entry<CardMngr.CPLC.Field, byte[]> entry : cplc.values().entrySet()) { + CardMngr.CPLC.Field field = entry.getKey(); + byte[] value = entry.getValue(); + sb.append("═══ ").append(field.name()).append(": ").append(CardMngr.mapCPLCField(field, value)); + } + } + } catch (CardException ignored) { + } + return sb.toString(); + } + return ""; + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/XMLTestWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/XMLTestWriter.java new file mode 100644 index 0000000..fc41805 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/output/XMLTestWriter.java @@ -0,0 +1,159 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.common.output.BaseXMLTestWriter; +import cz.crcs.ectester.common.test.TestSuite; +import cz.crcs.ectester.common.test.Testable; +import cz.crcs.ectester.common.util.ByteUtil; +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 cz.crcs.ectester.reader.test.CardTestSuite; +import cz.crcs.ectester.reader.test.CommandTestable; +import org.w3c.dom.Element; + +import javax.smartcardio.CardException; +import javax.xml.parsers.ParserConfigurationException; +import java.io.OutputStream; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class XMLTestWriter extends BaseXMLTestWriter { + public XMLTestWriter(OutputStream output) throws ParserConfigurationException { + super(output); + } + + private Element commandElement(Command c) { + Element commandElem = doc.createElement("command"); + if (c == null) { + return commandElem; + } + + Element apdu = doc.createElement("apdu"); + apdu.setTextContent(ByteUtil.bytesToHex(c.getAPDU().getBytes())); + commandElem.appendChild(apdu); + + Element description = doc.createElement("desc"); + description.setTextContent(c.getDescription()); + commandElem.appendChild(description); + + return commandElem; + } + + private Element responseElement(Response r) { + Element responseElem = doc.createElement("response"); + if (r == null) { + return responseElem; + } + + responseElem.setAttribute("successful", r.successful() ? "true" : "false"); + + Element apdu = doc.createElement("apdu"); + apdu.setTextContent(ByteUtil.bytesToHex(r.getAPDU().getBytes())); + responseElem.appendChild(apdu); + + Element naturalSW = doc.createElement("natural-sw"); + naturalSW.setTextContent(String.valueOf(Short.toUnsignedInt(r.getNaturalSW()))); + responseElem.appendChild(naturalSW); + + Element sws = doc.createElement("sws"); + for (int i = 0; i < r.getNumSW(); ++i) { + Element sw = doc.createElement("sw"); + sw.setTextContent(String.valueOf(Short.toUnsignedInt(r.getSW(i)))); + sws.appendChild(sw); + } + responseElem.appendChild(sws); + + Element duration = doc.createElement("duration"); + duration.setTextContent(String.valueOf(r.getDuration())); + responseElem.appendChild(duration); + + Element description = doc.createElement("desc"); + description.setTextContent(r.getDescription()); + responseElem.appendChild(description); + + return responseElem; + } + + @Override + protected Element testableElement(Testable t) { + if (t instanceof CommandTestable) { + CommandTestable cmd = (CommandTestable) t; + Element result = doc.createElement("test"); + result.setAttribute("type", "command"); + result.appendChild(commandElement(cmd.getCommand())); + result.appendChild(responseElement(cmd.getResponse())); + return result; + } + return null; + } + + private Element cplcElement(CardMngr card) { + Element result = doc.createElement("cplc"); + try { + CardMngr.CPLC cplc = card.getCPLC(); + if (!cplc.values().isEmpty()) { + for (Map.Entry<CardMngr.CPLC.Field, byte[]> entry : cplc.values().entrySet()) { + CardMngr.CPLC.Field field = entry.getKey(); + byte[] value = entry.getValue(); + Element keyVal = doc.createElement(field.name()); + keyVal.setTextContent(ByteUtil.bytesToHex(value, false)); + result.appendChild(keyVal); + } + } + } catch (CardException ignored) { + } + return result; + } + + private Element appletElement(CardMngr card) { + Element result = doc.createElement("applet"); + try { + Response.GetInfo info = new Command.GetInfo(card).send(); + result.setAttribute("version", info.getVersion()); + 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; + } + + @Override + protected Element deviceElement(TestSuite suite) { + if (suite instanceof CardTestSuite) { + CardTestSuite cardSuite = (CardTestSuite) suite; + Element result = doc.createElement("device"); + result.setAttribute("type", "card"); + result.setAttribute("ectester", ECTesterReader.VERSION + ECTesterReader.GIT_COMMIT); + result.appendChild(cplcElement(cardSuite.getCard())); + result.appendChild(appletElement(cardSuite.getCard())); + + Element atr = doc.createElement("ATR"); + atr.setTextContent(ByteUtil.bytesToHex(cardSuite.getCard().getATR().getBytes(), false)); + result.appendChild(atr); + return result; + } + return null; + } +}
\ No newline at end of file diff --git a/reader/src/main/java/cz/crcs/ectester/reader/output/YAMLTestWriter.java b/reader/src/main/java/cz/crcs/ectester/reader/output/YAMLTestWriter.java new file mode 100644 index 0000000..56ecb71 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/output/YAMLTestWriter.java @@ -0,0 +1,119 @@ +package cz.crcs.ectester.reader.output; + +import cz.crcs.ectester.common.output.BaseYAMLTestWriter; +import cz.crcs.ectester.common.test.TestSuite; +import cz.crcs.ectester.common.test.Testable; +import cz.crcs.ectester.common.util.ByteUtil; +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 cz.crcs.ectester.reader.test.CardTestSuite; +import cz.crcs.ectester.reader.test.CommandTestable; + +import javax.smartcardio.CardException; +import java.io.PrintStream; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class YAMLTestWriter extends BaseYAMLTestWriter { + public YAMLTestWriter(PrintStream output) { + super(output); + } + + private Map<String, Object> commandObject(Command c) { + Map<String, Object> commandObj = new LinkedHashMap<>(); + if (c == null) { + return commandObj; + } + commandObj.put("apdu", ByteUtil.bytesToHex(c.getAPDU().getBytes())); + commandObj.put("desc", c.getDescription()); + return commandObj; + } + + private Map<String, Object> responseObject(Response r) { + Map<String, Object> responseObj = new LinkedHashMap<>(); + if (r == null) { + return responseObj; + } + responseObj.put("successful", r.successful()); + responseObj.put("apdu", ByteUtil.bytesToHex(r.getAPDU().getBytes())); + responseObj.put("natural_sw", Short.toUnsignedInt(r.getNaturalSW())); + List<Integer> sws = new LinkedList<>(); + for (int i = 0; i < r.getNumSW(); ++i) { + sws.add(Short.toUnsignedInt(r.getSW(i))); + } + responseObj.put("sws", sws); + responseObj.put("duration", r.getDuration()); + responseObj.put("desc", r.getDescription()); + return responseObj; + } + + @Override + protected Map<String, Object> testableObject(Testable t) { + if (t instanceof CommandTestable) { + CommandTestable cmd = (CommandTestable) t; + Map<String, Object> result = new LinkedHashMap<>(); + result.put("type", "command"); + result.put("command", commandObject(cmd.getCommand())); + result.put("response", responseObject(cmd.getResponse())); + return result; + } + return null; + } + + private Map<String, Object> cplcObject(CardMngr card) { + Map<String, Object> result = new LinkedHashMap<>(); + try { + CardMngr.CPLC cplc = card.getCPLC(); + if (!cplc.values().isEmpty()) { + for (Map.Entry<CardMngr.CPLC.Field, byte[]> entry : cplc.values().entrySet()) { + CardMngr.CPLC.Field field = entry.getKey(); + byte[] value = entry.getValue(); + result.put(field.name(), ByteUtil.bytesToHex(value, false)); + } + } + } catch (CardException ignored) { + } + return result; + } + + private Map<String, Object> appletObject(CardMngr card) { + Map<String, Object> result = new LinkedHashMap<>(); + try { + Response.GetInfo info = new Command.GetInfo(card).send(); + result.put("version", info.getVersion()); + 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; + } + + @Override + protected Map<String, Object> deviceObject(TestSuite suite) { + if (suite instanceof CardTestSuite) { + CardTestSuite cardSuite = (CardTestSuite) suite; + Map<String, Object> result = new LinkedHashMap<>(); + result.put("type", "card"); + result.put("ectester", ECTesterReader.VERSION + ECTesterReader.GIT_COMMIT); + result.put("cplc", cplcObject(cardSuite.getCard())); + result.put("applet", appletObject(cardSuite.getCard())); + result.put("ATR", ByteUtil.bytesToHex(cardSuite.getCard().getATR().getBytes(), false)); + return result; + } + return null; + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/response/Response.java b/reader/src/main/java/cz/crcs/ectester/reader/response/Response.java new file mode 100644 index 0000000..8db255e --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/response/Response.java @@ -0,0 +1,526 @@ +package cz.crcs.ectester.reader.response; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.util.ByteUtil; +import cz.crcs.ectester.common.util.CardConsts; +import javacard.framework.ISO7816; + +import javax.smartcardio.ResponseAPDU; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class Response { + private ResponseAPDU resp; + private long time; + private short[] sws; + private int numSW = 0; + private byte[][] params; + private boolean success = true; + private boolean error = false; + private String description; + + public Response(ResponseAPDU response, String description, long time) { + this.resp = response; + this.description = description; + this.time = time; + } + + boolean parse(int numSW, int numParams) { + this.numSW = numSW; + this.sws = new short[numSW]; + + byte[] data = resp.getData(); + int offset = 0; + + //parse SWs in response + for (int i = 0; i < numSW; ++i) { + if (getLength() >= (offset + 2)) { + short sw = ByteUtil.getShort(data, offset); + offset += 2; + sws[i] = sw; + if (sw != ISO7816.SW_NO_ERROR) { + success = false; + } + } else { + success = false; + error = true; + } + } + + if ((short) resp.getSW() != ISO7816.SW_NO_ERROR) { + success = false; + error = true; + } + + + //try to parse numParams.. + params = new byte[numParams][]; + for (int i = 0; i < numParams; i++) { + if (data.length - offset < 2) { + success = false; + error = true; + break; + } + short paramLength = ByteUtil.getShort(data, offset); + offset += 2; + if (data.length < offset + paramLength) { + error = true; + success = false; + break; + } + params[i] = new byte[paramLength]; + System.arraycopy(data, offset, params[i], 0, paramLength); + offset += paramLength; + } + return success; + } + + public ResponseAPDU getAPDU() { + return resp; + } + + public byte[] getData() { + return resp.getData(); + } + + public long getDuration() { + return time; + } + + public short getNaturalSW() { + return (short) resp.getSW(); + } + + public short[] getSWs() { + return sws; + } + + public short getSW(int index) { + return sws[index]; + } + + public int getNumSW() { + return numSW; + } + + public boolean hasParam(int index) { + return params.length >= index + 1 && params[index] != null; + } + + public int getParamLength(int index) { + return params[index].length; + } + + public byte[] getParam(int index) { + return params[index]; + } + + public byte[][] getParams() { + return params; + } + + public int getLength() { + return resp.getNr(); + } + + public boolean successful() { + return this.success; + } + + public boolean error() { + return this.error; + } + + public String getDescription() { + return description; + } + + /** + * + */ + public static class AllocateKeyAgreement extends Response { + private byte kaType; + + public AllocateKeyAgreement(ResponseAPDU response, String description, long time, byte kaType) { + super(response, description, time); + this.kaType = kaType; + + parse(1, 0); + } + } + + /** + * + */ + public static class AllocateSignature extends Response { + private byte sigType; + + public AllocateSignature(ResponseAPDU response, String description, long time, byte sigType) { + super(response, description, time); + this.sigType = sigType; + + parse(1, 0); + } + } + + /** + * + */ + public static class Allocate extends Response { + private byte keyPair; + private short keyLength; + private byte keyClass; + + public Allocate(ResponseAPDU response, String description, long time, byte keyPair, short keyLength, byte keyClass) { + super(response, description, time); + this.keyPair = keyPair; + this.keyLength = keyLength; + this.keyClass = keyClass; + + int pairs = 0; + if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) pairs++; + if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) pairs++; + parse(pairs, 0); + } + } + + /** + * + */ + public static class Clear extends Response { + private byte keyPair; + + public Clear(ResponseAPDU response, String description, long time, byte keyPair) { + super(response, description, time); + this.keyPair = keyPair; + + int pairs = 0; + if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) pairs++; + if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) pairs++; + parse(pairs, 0); + } + } + + /** + * + */ + public static class Set extends Response { + private byte keyPair; + private byte curve; + private short parameters; + + public Set(ResponseAPDU response, String description, long time, byte keyPair, byte curve, short parameters) { + super(response, description, time); + this.keyPair = keyPair; + this.curve = curve; + this.parameters = parameters; + + int pairs = 0; + if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) pairs++; + if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) pairs++; + + parse(pairs, 0); + } + } + + /** + * + */ + public static class Transform extends Response { + private byte keyPair; + private byte key; + private short params; + private short transformation; + + public Transform(ResponseAPDU response, String description, long time, byte keyPair, byte key, short params, short transformation) { + super(response, description, time); + this.keyPair = keyPair; + this.key = key; + this.params = params; + this.transformation = transformation; + + int pairs = 0; + if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) pairs++; + if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) pairs++; + + parse(pairs, 0); + } + } + + /** + * + */ + public static class Generate extends Response { + private byte keyPair; + + public Generate(ResponseAPDU response, String description, long time, byte keyPair) { + super(response, description, time); + this.keyPair = keyPair; + + int generated = 0; + if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) generated++; + if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) generated++; + parse(generated, 0); + } + } + + /** + * + */ + public static class Export extends Response { + private byte keyPair; + private byte key; + private short parameters; + + public Export(ResponseAPDU response, String description, long time, byte keyPair, byte key, short parameters) { + super(response, description, time); + this.keyPair = keyPair; + this.key = key; + this.parameters = parameters; + + int exported = 0; + if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) exported++; + if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) exported++; + int keys = 0; + if ((key & EC_Consts.KEY_PUBLIC) != 0) keys++; + if ((key & EC_Consts.KEY_PRIVATE) != 0) keys++; + int paramCount = 0; + short mask = EC_Consts.PARAMETER_FP; + while (mask <= EC_Consts.PARAMETER_K) { + if ((mask & parameters) != 0) { + paramCount++; + } + mask = (short) (mask << 1); + } + int other = 0; + if ((key & EC_Consts.KEY_PUBLIC) != 0 && (parameters & EC_Consts.PARAMETER_W) != 0) other++; + if ((key & EC_Consts.KEY_PRIVATE) != 0 && (parameters & EC_Consts.PARAMETER_S) != 0) other++; + + parse(exported, exported * keys * paramCount + exported * other); + } + + private int getIndex(byte keyPair, short param) { + byte pair = CardConsts.KEYPAIR_LOCAL; + int index = 0; + while (pair <= CardConsts.KEYPAIR_REMOTE) { + short mask = EC_Consts.PARAMETER_FP; + while (mask <= EC_Consts.PARAMETER_S) { + if (pair == keyPair && param == mask) { + return index; + } + if ((parameters & mask) != 0 && (pair & this.keyPair) != 0) { + if (mask == EC_Consts.PARAMETER_W) { + if ((key & EC_Consts.KEY_PUBLIC) != 0) + index++; + } else if (mask == EC_Consts.PARAMETER_S) { + if ((key & EC_Consts.KEY_PRIVATE) != 0) + index++; + } else { + index++; + } + } + mask = (short) (mask << 1); + } + + pair = (byte) (pair << 1); + } + return -1; + } + + public boolean hasParameters(byte keyPair, short params) { + if ((keyPair & this.keyPair) == 0 || (params ^ parameters) != 0) { + return false; + } + short param = EC_Consts.PARAMETER_FP; + while (param <= EC_Consts.PARAMETER_S) { + short masked = (short) (param & params); + if (masked != 0 && !hasParameter(keyPair, masked)) { + return false; + } + param = (short) (param << 1); + } + return true; + } + + public boolean hasParameter(byte keyPair, short param) { + if ((keyPair & this.keyPair) == 0 || (parameters & param) == 0) { + return false; + } + int index = getIndex(keyPair, param); + return index != -1 && hasParam(index); + } + + public byte[] getParameter(byte keyPair, short param) { + return getParam(getIndex(keyPair, param)); + } + } + + /** + * + */ + public static class ECDH extends Response { + private byte pubkey; + private byte privkey; + private byte export; + private short transformation; + private byte type; + + public ECDH(ResponseAPDU response, String description, long time, byte pubkey, byte privkey, byte export, short transformation, byte type) { + super(response, description, time); + this.pubkey = pubkey; + this.privkey = privkey; + this.export = export; + this.transformation = transformation; + this.type = type; + + parse(1, (export == CardConsts.EXPORT_TRUE) ? 1 : 0); + } + + public short getTransformation() { + return transformation; + } + + public byte getType() { + return type; + } + + public boolean hasSecret() { + return hasParam(0); + } + + public byte[] getSecret() { + return getParam(0); + } + + public int secretLength() { + return getParamLength(0); + } + } + + /** + * + */ + public static class ECDSA extends Response { + private byte keyPair; + private byte sigType; + private byte export; + private byte[] raw; + + public ECDSA(ResponseAPDU response, String description, long time, byte keyPair, byte sigType, byte export, byte[] raw) { + super(response, description, time); + this.keyPair = keyPair; + this.sigType = sigType; + this.export = export; + this.raw = raw; + + parse(1, (export == CardConsts.EXPORT_TRUE) ? 1 : 0); + } + + public boolean hasSignature() { + return hasParam(0); + } + + public byte[] getSignature() { + return getParam(0); + } + } + + /** + * + */ + public static class Cleanup extends Response { + + public Cleanup(ResponseAPDU response, String description, long time) { + super(response, description, time); + + parse(1, 0); + } + } + + /** + * + */ + public static class GetInfo extends 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); + + parse(1, 1); + int offset = 2 + 2 + getParamLength(0); + byte[] data = getData(); + base = ByteUtil.getShort(data, offset); + offset += 2; + 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() { + return new String(getParam(0)); + } + + public short getBase() { + return base; + } + + public float getJavaCardVersion() { + byte major = (byte) (jcVersion >> 8); + byte minor = (byte) (jcVersion & 0xff); + int minorSize; + if (minor == 0) { + minorSize = 1; + } else { + minorSize = (int) Math.ceil(Math.log10(minor)); + } + return (major + ((float) (minor) / (minorSize * 10))); + } + + 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; + } + } + + /** + * + */ + public static class SetDryRunMode extends Response { + + public SetDryRunMode(ResponseAPDU response, String description, long time) { + super(response, description, time); + + parse(1, 0); + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardCofactorSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCofactorSuite.java new file mode 100644 index 0000000..01e9d02 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCofactorSuite.java @@ -0,0 +1,65 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.CardUtil; +import cz.crcs.ectester.common.util.CardConsts; +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 java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static cz.crcs.ectester.common.test.Result.ExpectedValue; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardCofactorSuite extends CardTestSuite { + public CardCofactorSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "cofactor", new String[]{"preset", "random"}, "The cofactor test suite tests whether the card correctly rejects points on the curve", + "but not in the subgroup generated by the generator(so of small order, dividing the cofactor) during ECDH."); + } + + @Override + protected void runTests() throws Exception { + Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "cofactor"); + Map<EC_Curve, List<EC_Key.Public>> curveList = EC_Store.mapKeyToCurve(pubkeys.values()); + for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curveList.entrySet()) { + EC_Curve curve = e.getKey(); + List<EC_Key.Public> keys = e.getValue(); + + Test allocate = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS); + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS); + Test generate = setupKeypairs(curve, ExpectedValue.SUCCESS, CardConsts.KEYPAIR_LOCAL); + + Test prepare = CompoundTest.all(ExpectedValue.SUCCESS, "Prepare and generate keypair on " + curve.getId() + ".", allocate, set, generate); + + List<Test> ecdhTests = new LinkedList<>(); + for (EC_Key.Public pub : keys) { + Test setPub = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE); + Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE); + Test objectEcdh = CompoundTest.any(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with cofactor pubkey.", setPub, ecdh); + Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.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)); + } + Test ecdh = CompoundTest.all(ExpectedValue.SUCCESS, "Perform ECDH with public points on non-generator subgroup.", ecdhTests.toArray(new Test[0])); + + if (cfg.cleanup) { + Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY); + doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Cofactor test of " + curve.getId() + ".", prepare, ecdh, cleanup)); + } else { + doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Cofactor test of " + curve.getId() + ".", prepare, ecdh)); + } + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompositeSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompositeSuite.java new file mode 100644 index 0000000..a28c2a7 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompositeSuite.java @@ -0,0 +1,122 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.CardUtil; +import cz.crcs.ectester.common.util.CardConsts; +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 java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static cz.crcs.ectester.common.test.Result.ExpectedValue; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardCompositeSuite extends CardTestSuite { + + public CardCompositeSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "composite", new String[]{"preset", "random"}, "The composite suite runs ECDH over curves with composite order.", + "Various types of compositeness is tested: smooth numbers, Carmichael pseudo-prime, prime square, product of two large primes."); + } + + @Override + protected void runTests() throws Exception { + Map<String, EC_Key> keys = EC_Store.getInstance().getObjects(EC_Key.class, "composite"); + Map<EC_Curve, List<EC_Key>> mappedKeys = EC_Store.mapKeyToCurve(keys.values()); + for (Map.Entry<EC_Curve, List<EC_Key>> curveKeys : mappedKeys.entrySet()) { + EC_Curve curve = curveKeys.getKey(); + List<Test> tests = new LinkedList<>(); + Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_LOCAL, 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; + } + tests.add(allocate); + tests.add(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.ANY)); + + String name; + if (cfg.testOptions.contains("preset")) { + name = "preset semi-random private key"; + } else { + name = "generated private key"; + } + tests.add(setupKeypairs(curve, ExpectedValue.ANY, CardConsts.KEYPAIR_LOCAL)); + for (EC_Key key : curveKeys.getValue()) { + Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, key.flatten()); + Test ecdh = CommandTest.expect(ecdhCommand, ExpectedValue.FAILURE, "Card correctly rejected to do ECDH over a composite order curve.", "Card incorrectly does ECDH over a composite order curve, leaks bits of private key."); + tests.add(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Composite test of " + curve.getId() + ", with " + name + ", " + key.getDesc(), ecdh)); + } + doTest(CompoundTest.all(ExpectedValue.SUCCESS, "Composite test of " + curve.getId() + ".", tests.toArray(new Test[0]))); + } + + + Map<String, EC_Curve> results = EC_Store.getInstance().getObjects(EC_Curve.class, "composite"); + Map<String, List<EC_Curve>> groups = EC_Store.mapToPrefix(results.values()); + /* Test the whole curves with both keypairs generated on card(no small-order public points provided). + */ + List<EC_Curve> wholeCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("whole")).findFirst().get().getValue(); + testGroup(wholeCurves, "Composite generator order", ExpectedValue.FAILURE, "Card rejected to do ECDH with composite order generator.", "Card did not reject to do ECDH with composite order generator."); + + /* Also test having a G of small order, so small R. + */ + List<EC_Curve> smallRCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("small")).findFirst().get().getValue(); + testGroup(smallRCurves, "Small generator order", ExpectedValue.FAILURE, "Card correctly rejected to do ECDH over a small order generator.", "Card incorrectly does ECDH over a small order generator."); + + /* Test increasingly larger prime R, to determine where/if card behavior changes. + */ + List<EC_Curve> varyingCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("varying")).findFirst().get().getValue(); + testGroup(varyingCurves, null, ExpectedValue.ANY, "", ""); + + /* Also test having a G of large but composite order, R = p * q, + */ + List<EC_Curve> pqCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("pq")).findFirst().get().getValue(); + testGroup(pqCurves, null, ExpectedValue.ANY, "", ""); + + /* Also test having G or large order being a Carmichael pseudoprime, R = p * q * r, + */ + List<EC_Curve> ppCurves = groups.entrySet().stream().filter((e) -> e.getKey().equals("pp")).findFirst().get().getValue(); + testGroup(ppCurves, "Generator order = Carmichael pseudo-prime", ExpectedValue.ANY, "", ""); + + /* Also test rg0 curves. + */ + List<EC_Curve> rg0Curves = groups.entrySet().stream().filter((e) -> e.getKey().equals("rg0")).findFirst().get().getValue(); + testGroup(rg0Curves, null, ExpectedValue.ANY, "", ""); + } + + private void testGroup(List<EC_Curve> curves, String testName, ExpectedValue dhValue, String ok, String nok) throws Exception { + for (EC_Curve curve : curves) { + Test allocate = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS); + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.ANY); + Test generate = setupKeypairs(curve, ExpectedValue.ANY, CardConsts.KEYPAIR_BOTH); + Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), dhValue, ok, nok); + Test ecdsa = CommandTest.expect(new Command.ECDSA_sign(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.Signature_ALG_ECDSA_SHA, CardConsts.EXPORT_FALSE, null), dhValue, ok, nok); + + String description; + if (testName == null) { + description = curve.getDesc() + " test of " + curve.getId() + "."; + } else { + description = testName + " test of " + curve.getId() + "."; + } + + Test perform = CompoundTest.all(ExpectedValue.SUCCESS, "Perform ECDH and ECDSA.", ecdh, ecdsa); + + if (cfg.cleanup) { + Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY); + doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, description, allocate, set, generate, perform, cleanup)); + } else { + doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, description, allocate, set, generate, perform)); + } + } + + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompressionSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompressionSuite.java new file mode 100644 index 0000000..ab1f46a --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardCompressionSuite.java @@ -0,0 +1,155 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +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.common.util.CardConsts; +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 java.security.spec.ECPoint; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardCompressionSuite extends CardTestSuite { + public CardCompressionSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "compression", null, "The compression test suite tests cards support for compressed points in ECDH (as per ANSI X9.62).", + "It also tests for handling of bogus input in ECDH by using the point at infinity and a hybrid point with the y coordinate corrupted.", + "It also tests handling of compressed point in ECDH, where the x coordinate is invalid and therefore", + "a quadratic non-residue will be computed and (square root-ed) during decompression."); + } + + @Override + protected void runTests() throws Exception { + //iterate over default curve sizes + // for Fp + // - allocate, set custom curve, generate keypairs, -> export generated. + // - test ecdh with local and remote simply(no compression) + // - test local privkey, remote pubkey (compressed) + // - test local privkey, remote pubkey (hybrid) + // - test local privkey, remote pubkey (hybrid with wrong y) + // - test local privkey, remote pubkey (point at infinity) + if (cfg.primeField) { + runCompression(KeyPair.ALG_EC_FP); + } + // for F2m + // - allocate, set custom curve, generate keypairs, -> export generated. + // - test ecdh with local and remote simply(no compression) + // - test local privkey, remote pubkey (compressed) + // - test local privkey, remote pubkey (hybrid) + // - test local privkey, remote pubkey (hybrid with wrong y) + // - test local privkey, remote pubkey (point at infinity) + if (cfg.binaryField) { + runCompression(KeyPair.ALG_EC_F2M); + } + + // Now, do ECDH over SECG curves and give the implementation a compressed key that is not a quadratic residue in + // decompression. + runNonResidue(); + } + + private void runCompression(byte field) throws Exception { + short[] keySizes = field == KeyPair.ALG_EC_FP ? EC_Consts.FP_SIZES : EC_Consts.F2M_SIZES; + short domain = field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M; + + for (short keyLength : keySizes) { + String spec = keyLength + "b " + CardUtil.getKeyTypeString(field); + byte curveId = EC_Consts.getCurve(keyLength, field); + + Test allocateFirst = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, field), Result.ExpectedValue.SUCCESS)); + if (!allocateFirst.ok()) { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for compression test on " + spec + ".", allocateFirst)); + continue; + } + + List<Test> compressionTests = new LinkedList<>(); + compressionTests.add(allocateFirst); + Test setCustom = runTest(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, curveId, domain, null), Result.ExpectedValue.SUCCESS)); + Test genCustom = runTest(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), Result.ExpectedValue.SUCCESS)); + compressionTests.add(setCustom); + compressionTests.add(genCustom); + + Response.Export key = new Command.Export(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.KEY_PUBLIC, EC_Consts.PARAMETER_W).send(); + byte[] pubkey = key.getParameter(CardConsts.KEYPAIR_REMOTE, EC_Consts.KEY_PUBLIC); + EC_Curve secgCurve = EC_Store.getInstance().getObject(EC_Curve.class, "secg", CardUtil.getCurveName(curveId)); + ECPoint pub; + try { + pub = ECUtil.fromX962(pubkey, secgCurve.toCurve()); + } catch (IllegalArgumentException iae) { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "", compressionTests.toArray(new Test[0]))); + continue; + } + + List<Test> kaTests = new LinkedList<>(); + for (byte kaType : EC_Consts.KA_TYPES) { + List<Test> thisTests = new LinkedList<>(); + Test allocate = runTest(CommandTest.expect(new Command.AllocateKeyAgreement(this.card, kaType), Result.ExpectedValue.SUCCESS)); + if (allocate.ok()) { + Test ka = runTest(CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType), Result.ExpectedValue.SUCCESS)); + + thisTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, "KeyAgreement setup and basic test.", allocate, ka)); + if (ka.ok()) { + // tests of the good stuff + Test kaCompressed = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_COMPRESS, kaType), Result.ExpectedValue.SUCCESS); + Test kaHybrid = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_COMPRESS_HYBRID, kaType), Result.ExpectedValue.SUCCESS); + thisTests.add(CompoundTest.any(Result.ExpectedValue.SUCCESS, "Tests of compressed and hybrid form.", kaCompressed, kaHybrid)); + + // tests the bad stuff here + byte[] pubHybrid = ECUtil.toX962Hybrid(pub, keyLength); + pubHybrid[pubHybrid.length - 1] ^= 1; + byte[] pubHybridEncoded = ByteUtil.prependLength(pubHybrid); + Test kaBadHybrid = CommandTest.expect(new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType, pubHybridEncoded), Result.ExpectedValue.FAILURE); + + byte[] pubInfinityEncoded = {0x01, 0x00}; + Test kaBadInfinity = CommandTest.expect(new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType, pubInfinityEncoded), Result.ExpectedValue.FAILURE); + thisTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Tests of corrupted hybrid form and infinity.", kaBadHybrid, kaBadInfinity)); + } + kaTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, "KeyAgreement tests of " + CardUtil.getKATypeString(kaType) + ".", thisTests.toArray(new Test[0]))); + } + } + compressionTests.addAll(kaTests); + if (cfg.cleanup) { + compressionTests.add(CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY)); + } + + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Compression test of " + spec + ".", compressionTests.toArray(new Test[0]))); + } + } + + private void runNonResidue() { + Map<String, EC_Key.Public> otherKeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "misc"); + List<EC_Key.Public> compressionKeys = EC_Store.mapToPrefix(otherKeys.values()).get("compression"); + + for (EC_Key.Public key : compressionKeys) { + EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, key.getCurve()); + List<Test> tests = new LinkedList<>(); + Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_LOCAL, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS)); + if (!allocate.ok()) { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for non-residue test on " + curve.getBits() + "b " + curve.getId() + ".", allocate)); + continue; + } + tests.add(allocate); + tests.add(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS)); + tests.add(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS)); + byte[] pointData = ECUtil.toX962Compressed(key.getParam(EC_Consts.PARAMETER_W)); + byte[] pointDataEncoded = ByteUtil.prependLength(pointData); + tests.add(CommandTest.expect(new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pointDataEncoded), Result.ExpectedValue.FAILURE)); + doTest(CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Non-residue test of " + curve.getId() + ".", tests.toArray(new Test[0]))); + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardDefaultSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardDefaultSuite.java new file mode 100644 index 0000000..df46767 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardDefaultSuite.java @@ -0,0 +1,141 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.CardConsts; +import cz.crcs.ectester.common.util.CardUtil; +import cz.crcs.ectester.reader.CardMngr; +import cz.crcs.ectester.reader.ECTesterReader; +import cz.crcs.ectester.reader.command.Command; +import javacard.security.KeyPair; + +import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static cz.crcs.ectester.common.test.Result.ExpectedValue; +import static cz.crcs.ectester.common.test.Result.Value; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardDefaultSuite extends CardTestSuite { + + public CardDefaultSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "default", null, "The default test suite tests basic support and performance of ECDH and ECDSA."); + } + + @Override + protected void runTests() throws Exception { + if (cfg.primeField) { + runDefault(KeyPair.ALG_EC_FP); + } + if (cfg.binaryField) { + runDefault(KeyPair.ALG_EC_F2M); + } + } + + private void runDefault(byte field) throws Exception { + short[] keySizes = field == KeyPair.ALG_EC_FP ? EC_Consts.FP_SIZES : EC_Consts.F2M_SIZES; + short domain = field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M; + for (short keyLength : keySizes) { + + List<Test> supportTests = new LinkedList<>(); + Test allocateFirst = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, field), ExpectedValue.SUCCESS)); + if (!allocateFirst.ok()) { + doTest(CompoundTest.all(ExpectedValue.SUCCESS, "No support for " + keyLength + "b " + CardUtil.getKeyTypeString(field) + ".", allocateFirst)); + continue; + } + supportTests.add(allocateFirst); + + Test genDefault = runTest(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), ExpectedValue.SUCCESS)); + Test allocateSecond = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, field), ExpectedValue.SUCCESS)); + Test setCustom = runTest(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.getCurve(keyLength, field), domain, null), ExpectedValue.SUCCESS)); + Test genCustom = runTest(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), ExpectedValue.SUCCESS)); + supportTests.add(genDefault); + supportTests.add(allocateSecond); + supportTests.add(setCustom); + supportTests.add(genCustom); + + List<Test> kaTests = new LinkedList<>(); + for (byte kaType : EC_Consts.KA_TYPES) { + Test allocate = runTest(CommandTest.expect(new Command.AllocateKeyAgreement(this.card, kaType), ExpectedValue.SUCCESS)); + if (allocate.ok()) { + Command ecdh = new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType); + Test ka = runTest(CommandTest.expect(ecdh, ExpectedValue.SUCCESS)); + Test kaCompressed = runTest(CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_COMPRESS, kaType), ExpectedValue.SUCCESS)); + + String kaDesc = "Test of the " + CardUtil.getKATypeString(kaType) + " KeyAgreement."; + Function<Test[], Result> kaCallback = (tests) -> { + if (tests[1].ok() || tests[2].ok()) { + return new Result(Value.SUCCESS, "Some ECDH is supported."); + } else { + return new Result(Value.FAILURE, "ECDH failed."); + } + }; + + Test compound; + if (ka.ok()) { + Test perfTest = runTest(PerformanceTest.repeat(this.card, ecdh, 10)); + compound = runTest(CompoundTest.function(kaCallback, kaDesc, allocate, ka, kaCompressed, perfTest)); + } else { + compound = runTest(CompoundTest.function(kaCallback, kaDesc, allocate, ka, kaCompressed)); + } + + kaTests.add(compound); + } else { + runTest(allocate); + kaTests.add(allocate); + } + } + Test kaTest = runTest(CompoundTest.any(ExpectedValue.SUCCESS, "KeyAgreement tests.", kaTests.toArray(new Test[0]))); + supportTests.add(kaTest); + + List<Test> signTests = new LinkedList<>(); + for (byte sigType : EC_Consts.SIG_TYPES) { + Test allocate = runTest(CommandTest.expect(new Command.AllocateSignature(this.card, sigType), ExpectedValue.SUCCESS)); + if (allocate.ok()) { + Command ecdsa = new Command.ECDSA(this.card, CardConsts.KEYPAIR_LOCAL, sigType, CardConsts.EXPORT_FALSE, null); + Test expect = runTest(CommandTest.expect(ecdsa, ExpectedValue.SUCCESS)); + + String signDesc = "Test of the " + CardUtil.getSigTypeString(sigType) + " signature."; + + Random rand = new Random(); + byte[] sigData = new byte[64]; + rand.nextBytes(sigData); + + Test compound; + if (expect.ok()) { + Command ecdsaSign = new Command.ECDSA_sign(this.card, CardConsts.KEYPAIR_LOCAL, sigType, CardConsts.EXPORT_TRUE, sigData); + PerformanceTest signTest = runTest(PerformanceTest.repeat(this.card, "Sign", ecdsaSign, 10)); + byte[] signature = signTest.getResponses()[0].getParam(0); + Command ecdsaVerify = new Command.ECDSA_verify(this.card, CardConsts.KEYPAIR_LOCAL, sigType, sigData, signature); + PerformanceTest verifyTest = runTest(PerformanceTest.repeat(this.card, "Verify", ecdsaVerify, 10)); + compound = runTest(CompoundTest.all(ExpectedValue.SUCCESS, signDesc, allocate, expect, signTest, verifyTest)); + } else { + compound = runTest(CompoundTest.all(ExpectedValue.SUCCESS, signDesc, allocate, expect)); + } + signTests.add(compound); + } else { + signTests.add(allocate); + } + } + Test signTest = runTest(CompoundTest.any(ExpectedValue.SUCCESS, "Signature tests.", signTests.toArray(new Test[0]))); + supportTests.add(signTest); + ExpectedValue[] testExpects = {ExpectedValue.SUCCESS, ExpectedValue.ANY, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS, ExpectedValue.SUCCESS}; + List<ExpectedValue> expects = Stream.of(testExpects).collect(Collectors.toList()); + if (cfg.cleanup) { + supportTests.add(CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY)); + expects.add(ExpectedValue.ANY); + } + + doTest(CompoundTest.mask(expects.toArray(new ExpectedValue[0]), "Tests of " + keyLength + "b " + CardUtil.getKeyTypeString(field) + " support.", supportTests.toArray(new Test[0]))); + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardDegenerateSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardDegenerateSuite.java new file mode 100644 index 0000000..e2c07da --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardDegenerateSuite.java @@ -0,0 +1,69 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.CardConsts; +import cz.crcs.ectester.common.util.CardUtil; +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 java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardDegenerateSuite extends CardTestSuite { + + public CardDegenerateSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "degenerate", null, "The degenerate suite tests whether the card rejects points outside of the curve during ECDH.", + "The tested points lie on a part of the plane for which some Edwards, Hessian and Huff form addition formulas degenerate into exponentiation in the base finite field."); + } + + @Override + protected void runTests() throws Exception { + Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "degenerate"); + Map<EC_Curve, List<EC_Key.Public>> curveList = EC_Store.mapKeyToCurve(pubkeys.values()); + for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curveList.entrySet()) { + EC_Curve curve = e.getKey(); + List<EC_Key.Public> keys = e.getValue(); + + Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS)); + if (!allocate.ok()) { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for " + curve.getId() + ".", allocate)); + continue; + } + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS); + Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS); + + Test prepare = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Prepare and generate keypair on " + curve.getId() + ".", allocate, set, generate); + + List<Test> ecdhTests = new LinkedList<>(); + for (EC_Key.Public pub : keys) { + Test setPub = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE); + Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE, "Card correctly rejected point on degenerate curve.", "Card incorrectly accepted point on degenerate curve."); + 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, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten()); + Test rawEcdh = CommandTest.expect(ecdhCommand, Result.ExpectedValue.FAILURE, "Card correctly rejected point on degenerate curve.", "Card incorrectly accepted point on degenerate curve."); + ecdhTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, pub.getId() + " degenerate key test.", objectEcdh, rawEcdh)); + //TODO: actually get the result of ECDH here, as well as export privkey and compare to exponentiation in Fp^*. + } + Test ecdh = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Perform ECDH with degenerate public points.", ecdhTests.toArray(new Test[0])); + if (cfg.cleanup) { + Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY); + doTest(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Degenerate curve test of " + curve.getId() + ".", prepare, ecdh, cleanup)); + } else { + doTest(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Degenerate curve test of " + curve.getId() + ".", prepare, ecdh)); + } + + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardEdgeCasesSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardEdgeCasesSuite.java new file mode 100644 index 0000000..98a59bc --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardEdgeCasesSuite.java @@ -0,0 +1,332 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.*; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +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.CardConsts; +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.CryptoException; +import javacard.security.KeyPair; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardEdgeCasesSuite extends CardTestSuite { + public CardEdgeCasesSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "edge-cases", null, "The edge-cases test suite tests various inputs to ECDH which may cause an implementation to achieve a certain edge-case state during it.", + "Some of the data is from the google/Wycheproof project. Tests include CVE-2017-10176 and CVE-2017-8932.", + "Also tests values of the private key and public key that would trigger the OpenSSL modular multiplication bug on the P-256 curve.", + "Various edge private key values are also tested."); + } + + @Override + protected void runTests() throws Exception { + Map<String, EC_KAResult> results = EC_Store.getInstance().getObjects(EC_KAResult.class, "wycheproof"); + Map<String, List<EC_KAResult>> groups = EC_Store.mapToPrefix(results.values()); + for (Map.Entry<String, List<EC_KAResult>> e : groups.entrySet()) { + String description = null; + switch (e.getKey()) { + case "addsub": + description = "Tests for addition-subtraction chains."; + break; + case "cve_2017_10176": + description = "Tests for CVE-2017-10176."; + break; + case "cve_2017_8932": + description = "Tests for CVE-2017-8932."; + break; + } + + List<Test> groupTests = new LinkedList<>(); + Map<EC_Curve, List<EC_KAResult>> curveList = EC_Store.mapResultToCurve(e.getValue()); + for (Map.Entry<EC_Curve, List<EC_KAResult>> c : curveList.entrySet()) { + EC_Curve curve = c.getKey(); + + List<Test> curveTests = new LinkedList<>(); + Test allocate = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS); + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS); + Test prepareCurve = CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Prepare curve", allocate, set); + + List<EC_KAResult> values = c.getValue(); + for (EC_KAResult value : values) { + String id = value.getId(); + String privkeyId = value.getOneKey(); + String pubkeyId = value.getOtherKey(); + + EC_Key.Private privkey = EC_Store.getInstance().getObject(EC_Key.Private.class, privkeyId); + EC_Key.Public pubkey = EC_Store.getInstance().getObject(EC_Key.Public.class, pubkeyId); + + Test setPrivkey = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, privkey.getParams(), privkey.flatten()), Result.ExpectedValue.SUCCESS); + Test setPubkey = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pubkey.getParams(), pubkey.flatten()), Result.ExpectedValue.SUCCESS); + Test ecdhPreTest = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.SUCCESS); + Test ecdh = CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, value.getJavaCardKA()), new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable testable) { + Response.ECDH dh = (Response.ECDH) testable.getResponse(); + if (dh.getSW(0) == CryptoException.NO_SUCH_ALGORITHM) { + return new Result(Result.Value.SUCCESS, "ECDH algorithm unsupported."); + } + if (!dh.successful()) + return new Result(Result.Value.FAILURE, "ECDH was unsuccessful."); + if (!dh.hasSecret()) + return new Result(Result.Value.FAILURE, "ECDH response did not contain the derived secret."); + if (!ByteUtil.compareBytes(dh.getSecret(), 0, value.getData(0), 0, dh.secretLength())) { + int firstDiff = ByteUtil.diffBytes(dh.getSecret(), 0, value.getData(0), 0, dh.secretLength()); + System.err.println(ByteUtil.bytesToHex(dh.getSecret())); + System.err.println(ByteUtil.bytesToHex(value.getData(0))); + return new Result(Result.Value.FAILURE, "ECDH derived secret does not match the test-vector, first difference was at byte " + firstDiff + "."); + } + return new Result(Result.Value.SUCCESS); + } + }); + + Test prepare = CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Prepare", setPrivkey, setPubkey); + Test ka = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Do", ecdhPreTest, ecdh); + + Test one = CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Test " + id + ".", prepare, ka); + curveTests.add(one); + } + + if (cfg.cleanup) { + curveTests.add(CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY)); + } + + Test curveTest = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Tests", curveTests.toArray(new Test[0])); + groupTests.add(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Tests on " + curve.getId() + ".", prepareCurve, curveTest)); + } + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, description, groupTests.toArray(new Test[0]))); + } + + { + EC_KAResult openssl_bug = EC_Store.getInstance().getObject(EC_KAResult.class, "misc", "openssl-bug"); + EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, openssl_bug.getCurve()); + EC_Key.Private skey = EC_Store.getInstance().getObject(EC_Key.Private.class, openssl_bug.getOtherKey()); + EC_Key.Public pkey = EC_Store.getInstance().getObject(EC_Key.Public.class, openssl_bug.getOneKey()); + Test key = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), KeyPair.ALG_EC_FP), Result.ExpectedValue.SUCCESS); + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS); + Test setPrivate = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, skey.flatten(EC_Consts.PARAMETER_S)), Result.ExpectedValue.SUCCESS); + Test setPublic = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, pkey.flatten(EC_Consts.PARAMETER_W)), Result.ExpectedValue.SUCCESS); + Test ecdh = CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, openssl_bug.getJavaCardKA()), new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable testable) { + Response.ECDH dh = (Response.ECDH) testable.getResponse(); + if (!dh.successful()) + return new Result(Result.Value.FAILURE, "ECDH was unsuccessful."); + if (!dh.hasSecret()) + return new Result(Result.Value.FAILURE, "ECDH response did not contain the derived secret."); + if (ByteUtil.compareBytes(dh.getSecret(), 0, openssl_bug.getData(0), 0, dh.secretLength())) { + return new Result(Result.Value.FAILURE, "OpenSSL bug is present, derived secret matches example."); + } + return new Result(Result.Value.SUCCESS); + } + }); + + doTest(CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Test OpenSSL modular reduction bug.", key, set, setPrivate, setPublic, ecdh)); + } + + Map<String, EC_Curve> curveMap = EC_Store.getInstance().getObjects(EC_Curve.class, "secg"); + List<EC_Curve> curves = curveMap.entrySet().stream().filter((e) -> e.getKey().endsWith("r1") && e.getValue().getField() == KeyPair.ALG_EC_FP).map(Map.Entry::getValue).collect(Collectors.toList()); + curves.add(EC_Store.getInstance().getObject(EC_Curve.class, "cofactor/cofactor128p2")); + curves.add(EC_Store.getInstance().getObject(EC_Curve.class, "cofactor/cofactor160p4")); + Random rand = new Random(); + for (EC_Curve curve : curves) { + Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), KeyPair.ALG_EC_FP), Result.ExpectedValue.SUCCESS)); + if (!key.ok()) { + doTest(CompoundTest.all(Result.ExpectedValue.FAILURE, "No support for " + curve.getBits() + "b " + curve.getId() + ".", key)); + continue; + } + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS); + Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS); + CommandTest export = CommandTest.expect(new Command.Export(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, EC_Consts.PARAMETER_W), Result.ExpectedValue.SUCCESS); + Test setup = runTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "KeyPair setup.", key, set, generate, export)); + + /* + byte[] pParam = curve.getParam(EC_Consts.PARAMETER_FP)[0]; + BigInteger p = new BigInteger(1, pParam); + byte[] wParam = ((Response.Export) export.getResponse()).getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W); + byte[] xValue = new byte[(wParam.length - 1) / 2]; + byte[] yValue = new byte[(wParam.length - 1) / 2]; + System.arraycopy(wParam, 1, xValue, 0, xValue.length); + System.arraycopy(wParam, (wParam.length / 2) + 1, yValue, 0, yValue.length); + BigInteger y = new BigInteger(1, yValue); + BigInteger negY = p.subtract(y); + byte[] newY = ECUtil.toByteArray(negY, curve.getBits()); + + EC_Params negYParams = new EC_Params(EC_Consts.PARAMETER_W, new byte[][]{xValue, newY}); + Test negYTest = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, negYParams.getParams(), negYParams.flatten()), "ECDH with pubkey negated.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE); + */ + + Test zeroS = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, EC_Consts.TRANSFORMATION_ZERO), "ECDH with S = 0.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE); + Test oneS = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, EC_Consts.TRANSFORMATION_ONE), "ECDH with S = 1.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE); + + byte[] rParam = curve.getParam(EC_Consts.PARAMETER_R)[0]; + BigInteger R = new BigInteger(1, rParam); + BigInteger smaller = new BigInteger(curve.getBits(), rand).mod(R); + BigInteger diff = R.divide(BigInteger.valueOf(10)); + BigInteger randDiff = new BigInteger(diff.bitLength(), rand).mod(diff); + BigInteger larger = R.add(randDiff); + + BigInteger full = BigInteger.valueOf(1).shiftLeft(R.bitLength() - 1).subtract(BigInteger.ONE); + + BigInteger alternate = full; + for (int i = 0; i < R.bitLength(); i += 2) { + alternate = alternate.clearBit(i); + } + + BigInteger alternateOther = alternate.xor(full); + + EC_Params alternateParams = makeParams(alternate); + Test alternateS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, alternateParams.getParams(), alternateParams.flatten()), "ECDH with S = 101010101...01010.", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS); + + EC_Params alternateOtherParams = makeParams(alternateOther); + Test alternateOtherS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, alternateOtherParams.getParams(), alternateOtherParams.flatten()), "ECDH with S = 010101010...10101.", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS); + + EC_Params fullParams = makeParams(full); + Test fullS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, fullParams.getParams(), fullParams.flatten()), "ECDH with S = 111111111...11111 (but < r).", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS); + + EC_Params smallerParams = makeParams(smaller); + Test smallerS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, smallerParams.getParams(), smallerParams.flatten()), "ECDH with S < r.", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS); + + EC_Params exactParams = makeParams(R); + Test exactS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, exactParams.getParams(), exactParams.flatten()), "ECDH with S = r.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE); + + EC_Params largerParams = makeParams(larger); + Test largerS = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, largerParams.getParams(), largerParams.flatten()), "ECDH with S > r.", Result.ExpectedValue.ANY, Result.ExpectedValue.ANY); + + BigInteger rm1 = R.subtract(BigInteger.ONE); + BigInteger rp1 = R.add(BigInteger.ONE); + + EC_Params rm1Params = makeParams(rm1); + Test rm1S = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, rm1Params.getParams(), rm1Params.flatten()), "ECDH with S = r - 1.", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS); + + EC_Params rp1Params = makeParams(rp1); + Test rp1S = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, rp1Params.getParams(), rp1Params.flatten()), "ECDH with S = r + 1.", Result.ExpectedValue.ANY, Result.ExpectedValue.ANY); + + byte[] k = curve.getParam(EC_Consts.PARAMETER_K)[0]; + BigInteger K = new BigInteger(1, k); + BigInteger kr = K.multiply(R); + BigInteger krm1 = kr.subtract(BigInteger.ONE); + BigInteger krp1 = kr.add(BigInteger.ONE); + + Result.ExpectedValue kExpected = K.equals(BigInteger.ONE) ? Result.ExpectedValue.SUCCESS : Result.ExpectedValue.FAILURE; + + EC_Params krParams = makeParams(kr); + Test krS /*ONE!*/ = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, krParams.getParams(), krParams.flatten()), "ECDH with S = k * r.", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE); + + EC_Params krm1Params = makeParams(krm1); + Test krm1S = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, krm1Params.getParams(), krm1Params.flatten()), "ECDH with S = (k * r) - 1.", kExpected, kExpected); + + EC_Params krp1Params = makeParams(krp1); + Test krp1S = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, krp1Params.getParams(), krp1Params.flatten()), "ECDH with S = (k * r) + 1.", Result.ExpectedValue.ANY, Result.ExpectedValue.ANY); + + if (cfg.cleanup) { + Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY); + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Tests with edge-case private key values over " + curve.getId() + ".", setup, zeroS, oneS, alternateS, alternateOtherS, fullS, smallerS, exactS, largerS, rm1S, rp1S, krS, krm1S, krp1S, cleanup)); + } else { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Tests with edge-case private key values over " + curve.getId() + ".", setup, zeroS, oneS, alternateS, alternateOtherS, fullS, smallerS, exactS, largerS, rm1S, rp1S, krS, krm1S, krp1S)); + } + } + + EC_Curve secp160r1 = EC_Store.getInstance().getObject(EC_Curve.class, "secg/secp160r1"); + byte[] pData = secp160r1.getParam(EC_Consts.PARAMETER_FP)[0]; + BigInteger p = new BigInteger(1, pData); + byte[] rData = secp160r1.getParam(EC_Consts.PARAMETER_R)[0]; + BigInteger r = new BigInteger(1, rData); + + BigInteger range = r.subtract(p); + BigInteger deviation = range.divide(BigInteger.valueOf(5)); + BigDecimal dev = new BigDecimal(deviation); + BigDecimal smallDev = new BigDecimal(10000); + int n = 10; + BigInteger[] rs = new BigInteger[n]; + BigInteger[] ps = new BigInteger[n]; + BigInteger[] zeros = new BigInteger[n]; + for (int i = 0; i < n; ++i) { + double sample; + do { + sample = rand.nextGaussian(); + } while (sample >= -1 && sample <= 1); + BigInteger where = dev.multiply(new BigDecimal(sample)).toBigInteger(); + rs[i] = where.add(r); + ps[i] = where.add(p); + zeros[i] = smallDev.multiply(new BigDecimal(sample)).toBigInteger().abs(); + } + Arrays.sort(rs); + Arrays.sort(ps); + Arrays.sort(zeros); + + Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, secp160r1.getBits(), KeyPair.ALG_EC_FP), Result.ExpectedValue.SUCCESS)); + if (!key.ok()) { + doTest(CompoundTest.all(Result.ExpectedValue.FAILURE, "No support for " + secp160r1.getBits() + "b secp160r1.", key)); + return; + } + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, secp160r1.getParams(), secp160r1.flatten()), Result.ExpectedValue.SUCCESS); + Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS); + Test setup = CompoundTest.all(Result.ExpectedValue.SUCCESS, "KeyPair setup.", key, set, generate); + + Test[] zeroTests = new Test[n]; + int i = 0; + for (BigInteger nearZero : zeros) { + EC_Params params = makeParams(nearZero); + zeroTests[i++] = ecdhTestBoth(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, params.getParams(), params.flatten()), nearZero.toString(16), Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS); + } + Test zeroTest = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Near zero.", zeroTests); + + Test[] pTests = new Test[n]; + i = 0; + for (BigInteger nearP : ps) { + EC_Params params = makeParams(nearP); + pTests[i++] = ecdhTestBoth(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, params.getParams(), params.flatten()), nearP.toString(16) + (nearP.compareTo(p) > 0 ? " (>p)" : " (<=p)"), Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS); + } + Test pTest = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Near p.", pTests); + + Test[] rTests = new Test[n]; + i = 0; + for (BigInteger nearR : rs) { + EC_Params params = makeParams(nearR); + if (nearR.compareTo(r) >= 0) { + rTests[i++] = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, params.getParams(), params.flatten()), nearR.toString(16) + " (>=r)", Result.ExpectedValue.FAILURE, Result.ExpectedValue.FAILURE); + } else { + rTests[i++] = ecdhTestBoth(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, params.getParams(), params.flatten()), nearR.toString(16) + " (<r)", Result.ExpectedValue.SUCCESS, Result.ExpectedValue.SUCCESS); + } + } + Test rTest = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Near r.", rTests); + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "Test private key values near zero, near p and near/larger than the order.", setup, zeroTest, pTest, rTest)); + } + + private Test ecdhTestBoth(Command setPriv, String desc, Result.ExpectedValue setExpect, Result.ExpectedValue ecdhExpect) { + Test set = CommandTest.expect(setPriv, setExpect); + Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), ecdhExpect); + + return CompoundTest.all(Result.ExpectedValue.SUCCESS, desc, set, ecdh); + } + + private Test ecdhTest(Command setPriv, String desc, Result.ExpectedValue setExpect, Result.ExpectedValue ecdhExpect) { + Test set = CommandTest.expect(setPriv, setExpect); + Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), ecdhExpect); + + return CompoundTest.any(Result.ExpectedValue.SUCCESS, desc, set, ecdh); + } + + private EC_Params makeParams(BigInteger s) { + return makeParams(ECUtil.toByteArray(s, s.bitLength())); + } + + private EC_Params makeParams(byte[] s) { + return new EC_Params(EC_Consts.PARAMETER_S, new byte[][]{s}); + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardInvalidSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardInvalidSuite.java new file mode 100644 index 0000000..4657de0 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardInvalidSuite.java @@ -0,0 +1,72 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.CardConsts; +import cz.crcs.ectester.common.util.CardUtil; +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 java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static cz.crcs.ectester.common.test.Result.ExpectedValue; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardInvalidSuite extends CardTestSuite { + + public CardInvalidSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "invalid", null, "The invalid curve suite tests whether the card rejects points outside of the curve during ECDH."); + } + + @Override + protected void runTests() throws Exception { + /* Set original curves (secg/nist/brainpool). Generate local. + * Try ECDH with invalid public keys of increasing order. + */ + Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "invalid"); + Map<EC_Curve, List<EC_Key.Public>> curveList = EC_Store.mapKeyToCurve(pubkeys.values()); + for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curveList.entrySet()) { + EC_Curve curve = e.getKey(); + List<EC_Key.Public> keys = e.getValue(); + + Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); + if (!allocate.ok()) { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for " + curve.getId() + ".", allocate)); + continue; + } + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS); + Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), ExpectedValue.SUCCESS); + + Test prepare = CompoundTest.all(ExpectedValue.SUCCESS, "Prepare and generate keypair on " + curve.getId() + ".", allocate, set, generate); + + List<Test> ecdhTests = new LinkedList<>(); + for (EC_Key.Public pub : keys) { + Test setPub = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE); + Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE); + Test objectEcdh = CompoundTest.any(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with invalid pubkey.", setPub, ecdh); + Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.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 invalid curve.", "Card incorrectly accepted point on invalid curve."); + ecdhTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, pub.getId() + " invalid key test.", objectEcdh, rawEcdh)); + } + Test ecdh = CompoundTest.all(ExpectedValue.SUCCESS, "Perform ECDH with invalid public points.", ecdhTests.toArray(new Test[0])); + + if (cfg.cleanup) { + Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), ExpectedValue.ANY); + doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Invalid curve test of " + curve.getId() + ".", prepare, ecdh, cleanup)); + } else { + doTest(CompoundTest.greedyAllTry(ExpectedValue.SUCCESS, "Invalid curve test of " + curve.getId() + ".", prepare, ecdh)); + } + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardMiscSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardMiscSuite.java new file mode 100644 index 0000000..da4c0b5 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardMiscSuite.java @@ -0,0 +1,77 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.CardConsts; +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 java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardMiscSuite extends CardTestSuite { + + public CardMiscSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "miscellaneous", new String[]{"preset", "random"}, "Some miscellaneous tests, tries ECDH and ECDSA over supersingular curves, anomalous curves,", + "Barreto-Naehrig curves with small embedding degree and CM discriminant, MNT curves,", + "some Montgomery curves transformed to short Weierstrass form and Curve25519 transformed to short Weierstrass form."); + } + + @Override + protected void runTests() throws Exception { + Map<String, EC_Curve> anCurves = EC_Store.getInstance().getObjects(EC_Curve.class, "anomalous"); + Map<String, EC_Curve> ssCurves = EC_Store.getInstance().getObjects(EC_Curve.class, "supersingular"); + Map<String, EC_Curve> bnCurves = EC_Store.getInstance().getObjects(EC_Curve.class, "Barreto-Naehrig"); + Map<String, EC_Curve> mntCurves = EC_Store.getInstance().getObjects(EC_Curve.class, "MNT"); + List<EC_Curve> mCurves = new ArrayList<>(); + mCurves.add(EC_Store.getInstance().getObject(EC_Curve.class, "other", "M-221")); + mCurves.add(EC_Store.getInstance().getObject(EC_Curve.class, "other", "M-383")); + mCurves.add(EC_Store.getInstance().getObject(EC_Curve.class, "other", "M-511")); + EC_Curve curve25519 = EC_Store.getInstance().getObject(EC_Curve.class, "other", "Curve25519"); + + testCurves(anCurves.values(), "anomalous", Result.ExpectedValue.FAILURE); + testCurves(ssCurves.values(), "supersingular", Result.ExpectedValue.FAILURE); + testCurves(bnCurves.values(), "Barreto-Naehrig", Result.ExpectedValue.SUCCESS); + testCurves(mntCurves.values(), "MNT", Result.ExpectedValue.SUCCESS); + testCurves(mCurves, "Montgomery", Result.ExpectedValue.SUCCESS); + testCurve(curve25519, "Montgomery", Result.ExpectedValue.SUCCESS); + } + + private void testCurve(EC_Curve curve, String catName, Result.ExpectedValue expected) { + Test allocateFirst = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS)); + if (!allocateFirst.ok()) { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for " + curve.getBits() + "b " + catName + " curve: " + curve.getId() + ".", allocateFirst)); + return; + } + + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS); + Test generate = setupKeypairs(curve, Result.ExpectedValue.ANY, CardConsts.KEYPAIR_BOTH); + Test ka = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), expected); + Test sig = CommandTest.expect(new Command.ECDSA_sign(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.Signature_ALG_ECDSA_SHA, CardConsts.EXPORT_FALSE, null), expected); + Test perform = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Perform ECDH and ECDSA.", ka, sig); + + if (cfg.cleanup) { + Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY); + doTest(CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Tests over " + curve.getBits() + "b " + catName + " curve: " + curve.getId() + ".", allocateFirst, set, generate, perform, cleanup)); + } else { + doTest(CompoundTest.greedyAll(Result.ExpectedValue.SUCCESS, "Tests over " + curve.getBits() + "b " + catName + " curve: " + curve.getId() + ".", allocateFirst, set, generate, perform)); + } + } + + private void testCurves(Collection<EC_Curve> curves, String catName, Result.ExpectedValue expected) { + for (EC_Curve curve : curves) { + testCurve(curve, catName, expected); + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardSignatureSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardSignatureSuite.java new file mode 100644 index 0000000..086654a --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardSignatureSuite.java @@ -0,0 +1,68 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.ec.EC_SigResult; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.CardConsts; +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 java.util.List; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardSignatureSuite extends CardTestSuite { + public CardSignatureSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "signature", null, "The signature test suite tests verifying various malformed and well-formed but invalid ECDSA signatures."); + } + + @Override + protected void runTests() throws Exception { + Map<String, EC_SigResult> results = EC_Store.getInstance().getObjects(EC_SigResult.class, "wrong"); + Map<String, List<EC_SigResult>> groups = EC_Store.mapToPrefix(results.values()); + + List<EC_SigResult> nok = groups.entrySet().stream().filter((e) -> e.getKey().equals("nok")).findFirst().get().getValue(); + + byte[] data = "Some stuff that is not the actual data".getBytes(); + for (EC_SigResult sig : nok) { + ecdsaTest(sig, Result.ExpectedValue.FAILURE, data); + } + + List<EC_SigResult> ok = groups.entrySet().stream().filter((e) -> e.getKey().equals("ok")).findFirst().get().getValue(); + for (EC_SigResult sig : ok) { + ecdsaTest(sig, Result.ExpectedValue.SUCCESS, null); + } + } + + private void ecdsaTest(EC_SigResult sig, Result.ExpectedValue expected, byte[] defaultData) { + EC_Key.Public pubkey = EC_Store.getInstance().getObject(EC_Key.Public.class, sig.getVerifyKey()); + + byte[] data = sig.getSigData(); + if (data == null) { + data = defaultData; + } + + EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, sig.getCurve()); + Test allocate = CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_LOCAL, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS); + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS); + Test setVerifyKey = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, pubkey.getParams(), pubkey.flatten()), Result.ExpectedValue.SUCCESS); + Test ecdsaVerify = CommandTest.expect(new Command.ECDSA_verify(this.card, CardConsts.KEYPAIR_LOCAL, sig.getJavaCardSig(), data, sig.getData(0)), expected); + + if (cfg.cleanup) { + Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY); + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "ECDSA test of " + sig.getId() + ".", allocate, set, setVerifyKey, ecdsaVerify, cleanup)); + } else { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "ECDSA test of " + sig.getId() + ".", allocate, set, setVerifyKey, ecdsaVerify)); + } + + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestSuite.java new file mode 100644 index 0000000..15c4469 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestSuite.java @@ -0,0 +1,94 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Params; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.test.TestSuite; +import cz.crcs.ectester.common.util.CardConsts; +import cz.crcs.ectester.common.util.ECUtil; +import cz.crcs.ectester.reader.CardMngr; +import cz.crcs.ectester.reader.ECTesterReader; +import cz.crcs.ectester.reader.command.Command; + +import java.util.Arrays; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class CardTestSuite extends TestSuite { + ECTesterReader.Config cfg; + CardMngr card; + String[] options; + + CardTestSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager, String name, String[] options, String... description) { + super(writer, name, description); + this.card = cardManager; + this.cfg = cfg; + this.options = options; + } + + public CardMngr getCard() { + return card; + } + + public ECTesterReader.Config getCfg() { + return cfg; + } + + public String[] getOptions() { + if (options != null) { + return options.clone(); + } else { + return options; + } + } + + public Test setupKeypairs(EC_Curve curve, Result.ExpectedValue expected, byte keyPair) { + if ((Arrays.asList(options).contains("preset") && cfg.testOptions.contains("preset")) || (Arrays.asList(options).contains("random") && cfg.testOptions.contains("random"))) { + Test setLocal = null; + if ((keyPair & CardConsts.KEYPAIR_LOCAL) != 0) { + EC_Params priv; + if (cfg.testOptions.contains("preset")) { + priv = ECUtil.fixedRandomKey(curve); + } else { + priv = ECUtil.fullRandomKey(curve); + } + setLocal = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.CURVE_external, priv.getParams(), priv.flatten()), expected); + } + Test setRemote = null; + if ((keyPair & CardConsts.KEYPAIR_REMOTE) != 0) { + EC_Params pub; + if (cfg.testOptions.contains("preset")) { + pub = ECUtil.fixedRandomPoint(curve); + } else { + pub = ECUtil.fullRandomPoint(curve); + } + if (pub == null) { + setRemote = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_REMOTE), expected); + } else { + setRemote = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), expected); + } + } + + if (keyPair == CardConsts.KEYPAIR_LOCAL) { + return setLocal; + } else if (keyPair == CardConsts.KEYPAIR_REMOTE) { + return setRemote; + } else { + String desc; + if (cfg.testOptions.contains("preset")) { + desc = "Set semi-random parameters."; + } else { + desc = "Set fully-random parameters."; + } + return CompoundTest.all(expected, desc, setLocal, setRemote); + } + } else { + return CommandTest.expect(new Command.Generate(this.card, keyPair), expected); + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestVectorSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestVectorSuite.java new file mode 100644 index 0000000..056be17 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTestVectorSuite.java @@ -0,0 +1,234 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.*; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +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.CardConsts; +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.function.BiFunction; +import java.util.stream.Collectors; + +import static cz.crcs.ectester.common.test.Result.ExpectedValue; +import static cz.crcs.ectester.common.test.Result.Value; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardTestVectorSuite extends CardTestSuite { + + public CardTestVectorSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "test-vectors", null, "The test-vectors suite contains a collection of test vectors which test basic ECDH correctness."); + } + + @Override + protected void runTests() throws Exception { + /* Set original curves (secg/nist/brainpool). Set keypairs from test vectors. + * Do ECDH both ways, export and verify that the result is correct. + */ + Map<String, EC_KAResult> results = EC_Store.getInstance().getObjects(EC_KAResult.class, "test"); + for (EC_KAResult result : results.values()) { + EC_Curve curve = EC_Store.getInstance().getObject(EC_Curve.class, result.getCurve()); + EC_Params onekey = EC_Store.getInstance().getObject(EC_Keypair.class, result.getOneKey()); + if (onekey == null) { + onekey = EC_Store.getInstance().getObject(EC_Key.Private.class, result.getOneKey()); + } + EC_Params otherkey = EC_Store.getInstance().getObject(EC_Keypair.class, result.getOtherKey()); + if (otherkey == null) { + otherkey = EC_Store.getInstance().getObject(EC_Key.Public.class, result.getOtherKey()); + } + if (onekey == null || otherkey == null) { + 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, CardConsts.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, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS)); + testVector.add(CommandTest.expect(new Command.Set(this.card, CardConsts.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, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, otherkey.flatten(EC_Consts.PARAMETER_W)), ExpectedValue.SUCCESS)); + testVector.add(CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, result.getJavaCardKA()), new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable testable) { + Response.ECDH dh = (Response.ECDH) testable.getResponse(); + if (!dh.successful()) + return new Result(Value.FAILURE, "ECDH was unsuccessful."); + if (!dh.hasSecret()) + return new Result(Value.FAILURE, "ECDH response did not contain the derived secret."); + if (!ByteUtil.compareBytes(dh.getSecret(), 0, result.getData(0), 0, dh.secretLength())) { + int firstDiff = ByteUtil.diffBytes(dh.getSecret(), 0, result.getData(0), 0, dh.secretLength()); + return new Result(Value.FAILURE, "ECDH derived secret does not match the test-vector, first difference was at byte " + firstDiff + "."); + } + return new Result(Value.SUCCESS); + } + })); + 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]))); + } + + KeyAgreement ka; + Signature sig; + KeyFactory kf; + MessageDigest md; + try { + ka = KeyAgreement.getInstance("ECDH", "BC"); + sig = Signature.getInstance("ECDSAwithSHA1", "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, CardConsts.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, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.SUCCESS)); + testVector.add(CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), ExpectedValue.SUCCESS)); + CommandTest exportLocal = CommandTest.expect(new Command.Export(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, EC_Consts.PARAMETER_W), ExpectedValue.ANY); + CommandTest exportRemote = CommandTest.expect(new Command.Export(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.KEY_PRIVATE, EC_Consts.PARAMETER_S), ExpectedValue.ANY); + testVector.add(exportLocal); + testVector.add(exportRemote); + BiFunction<Response.Export, Response.Export, Key[]> getKeys = (localData, remoteData) -> { + byte[] pkey = localData.getParameter(CardConsts.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W); + byte[] skey = remoteData.getParameter(CardConsts.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); + } catch (InvalidKeySpecException ex) { + return null; + } + return new Key[]{privKey, pubKey}; + }; + 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 localData = (Response.Export) exportLocal.getResponse(); + Response.Export remoteData = (Response.Export) exportRemote.getResponse(); + Key[] keys = getKeys.apply(localData, remoteData); + if (keys == null) { + return new Result(Value.SUCCESS, "Result could not be verified. keyData unavailable."); + } + PrivateKey privKey = (PrivateKey) keys[0]; + PublicKey pubKey = (PublicKey) keys[1]; + try { + ka.init(privKey); + ka.doPhase(pubKey, true); + byte[] derived = ka.generateSecret(); + int fieldSize = (curve.getBits() + 7) / 8; + if (derived.length < fieldSize) { + byte[] padded = new byte[fieldSize]; + System.arraycopy(derived, 0, padded, fieldSize - derived.length, derived.length); + derived = padded; + } + if (ecdhData.getType() == EC_Consts.KeyAgreement_ALG_EC_SVDP_DH || ecdhData.getType() == EC_Consts.KeyAgreement_ALG_EC_SVDP_DHC) { + derived = md.digest(derived); + } + 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 " + diff + "."); + } + } catch (InvalidKeyException ex) { + return new Result(Value.SUCCESS, "Result could not be verified. " + ex.getMessage()); + } + } + }; + Test ecdhTest = CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), kaCallback); + Test ecdhRawTest = CommandTest.function(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_TRUE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH_PLAIN), kaCallback); + byte[] data = new byte[32]; + TestCallback<CommandTestable> sigCallback = new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable testable) { + Response.ECDSA ecdsaData = (Response.ECDSA) testable.getResponse(); + if (!ecdsaData.successful()) + return new Result(Value.FAILURE, "ECDSA was unsuccessful."); + if (!ecdsaData.hasSignature()) { + return new Result(Value.FAILURE, "ECDSA response did not contain the signature."); + } + byte[] signature = ecdsaData.getSignature(); + Response.Export localData = (Response.Export) exportLocal.getResponse(); + Response.Export remoteData = (Response.Export) exportRemote.getResponse(); + Key[] keys = getKeys.apply(localData, remoteData); + if (keys == null) { + return new Result(Value.SUCCESS, "Result could not be verified. keyData unavailable."); + } + PublicKey pubKey = (PublicKey) keys[1]; + try { + sig.initVerify(pubKey); + sig.update(data); + if (sig.verify(signature)) { + return new Result(Value.SUCCESS, "Signature verified."); + } else { + return new Result(Value.FAILURE, "Signature failed to verify."); + } + } catch (InvalidKeyException | SignatureException ex) { + return new Result(Value.SUCCESS, "Result could not be verified. " + ex.getMessage()); + } + } + }; + Test ecdsaTest = CommandTest.function(new Command.ECDSA_sign(this.card, CardConsts.KEYPAIR_LOCAL, EC_Consts.Signature_ALG_ECDSA_SHA, CardConsts.EXPORT_TRUE, data), sigCallback); + testVector.add(CompoundTest.all(ExpectedValue.SUCCESS, "Test.", ecdhTest, ecdhRawTest, ecdsaTest)); + 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]))); + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardTwistSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTwistSuite.java new file mode 100644 index 0000000..5c35be7 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardTwistSuite.java @@ -0,0 +1,67 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Key; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.CardConsts; +import cz.crcs.ectester.common.util.CardUtil; +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 java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardTwistSuite extends CardTestSuite { + public CardTwistSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "twist", null, "The twist test suite tests whether the card correctly rejects points on the quadratic twist of the curve during ECDH."); + } + + @Override + protected void runTests() throws Exception { + Map<String, EC_Key.Public> pubkeys = EC_Store.getInstance().getObjects(EC_Key.Public.class, "twist"); + Map<EC_Curve, List<EC_Key.Public>> curveList = EC_Store.mapKeyToCurve(pubkeys.values()); + for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curveList.entrySet()) { + EC_Curve curve = e.getKey(); + List<EC_Key.Public> keys = e.getValue(); + + Test allocate = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Result.ExpectedValue.SUCCESS)); + if (!allocate.ok()) { + doTest(CompoundTest.all(Result.ExpectedValue.SUCCESS, "No support for " + curve.getId() + ".", allocate)); + continue; + } + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Result.ExpectedValue.SUCCESS); + Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_LOCAL), Result.ExpectedValue.SUCCESS); + + Test prepare = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Prepare and generate keypair on " + curve.getId() + ".", allocate, set, generate); + + List<Test> ecdhTests = new LinkedList<>(); + for (EC_Key.Public pub : keys) { + Test setPub = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Result.ExpectedValue.FAILURE); + Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), Result.ExpectedValue.FAILURE, "Card correctly rejected point on twist.", "Card incorrectly accepted point on twist."); + Test objectEcdh = CompoundTest.any(Result.ExpectedValue.SUCCESS, CardUtil.getKATypeString(EC_Consts.KeyAgreement_ALG_EC_SVDP_DH) + " test with twist pubkey.", setPub, ecdh); + Command ecdhCommand = new Command.ECDH_direct(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH, pub.flatten()); + Test rawEcdh = CommandTest.expect(ecdhCommand, Result.ExpectedValue.FAILURE, "Card correctly rejected point on twist.", "Card incorrectly accepted point on twist."); + ecdhTests.add(CompoundTest.all(Result.ExpectedValue.SUCCESS, pub.getId() + " twist key test.", objectEcdh, rawEcdh)); + } + Test ecdh = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Perform ECDH with public points on twist.", ecdhTests.toArray(new Test[0])); + + Test tests = CompoundTest.all(Result.ExpectedValue.SUCCESS, "Do tests.", ecdh); + if (cfg.cleanup) { + Test cleanup = CommandTest.expect(new Command.Cleanup(this.card), Result.ExpectedValue.ANY); + doTest(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Twist test of " + curve.getId() + ".", prepare, tests, cleanup)); + } else { + doTest(CompoundTest.greedyAllTry(Result.ExpectedValue.SUCCESS, "Twist test of " + curve.getId() + ".", prepare, tests)); + } + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CardWrongSuite.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CardWrongSuite.java new file mode 100644 index 0000000..c69396b --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CardWrongSuite.java @@ -0,0 +1,232 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.ec.EC_Consts; +import cz.crcs.ectester.common.ec.EC_Curve; +import cz.crcs.ectester.common.ec.EC_Params; +import cz.crcs.ectester.common.output.TestWriter; +import cz.crcs.ectester.common.test.CompoundTest; +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.Test; +import cz.crcs.ectester.common.util.ByteUtil; +import cz.crcs.ectester.common.util.CardConsts; +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 javacard.security.KeyPair; + +import java.math.BigInteger; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import static cz.crcs.ectester.common.test.Result.ExpectedValue; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardWrongSuite extends CardTestSuite { + + public CardWrongSuite(TestWriter writer, ECTesterReader.Config cfg, CardMngr cardManager) { + super(writer, cfg, cardManager, "wrong", new String[]{"preset", "random"}, "The wrong curve suite tests whether the card rejects domain parameters which are not curves."); + } + + @Override + protected void runTests() throws Exception { + /* Just do the default run on the wrong curves. + * These should generally fail, the curves aren't curves. + */ + Map<String, EC_Curve> curves = EC_Store.getInstance().getObjects(EC_Curve.class, "wrong"); + for (Map.Entry<String, EC_Curve> e : curves.entrySet()) { + EC_Curve curve = e.getValue(); + List<Test> tests = new LinkedList<>(); + Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, curve.getBits(), curve.getField()), ExpectedValue.SUCCESS)); + if (!key.ok()) { + doTest(CompoundTest.all(ExpectedValue.FAILURE, "No support for " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()), key)); + continue; + } + tests.add(key); + Test set = runTest(CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), ExpectedValue.FAILURE)); + Test generate = runTest(setupKeypairs(curve, ExpectedValue.SUCCESS, CardConsts.KEYPAIR_BOTH)); + Test setup = runTest(CompoundTest.any(ExpectedValue.SUCCESS, "Set wrong curve and generate keypairs.", set, generate)); + tests.add(setup); + + for (byte kaType : EC_Consts.KA_TYPES) { + Test allocate = runTest(CommandTest.expect(new Command.AllocateKeyAgreement(this.card, kaType), ExpectedValue.SUCCESS)); + if (allocate.ok()) { + Test ka = runTest(CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_REMOTE, CardConsts.KEYPAIR_LOCAL, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, kaType), ExpectedValue.FAILURE)); + Test kaTest = runTest(CompoundTest.all(ExpectedValue.SUCCESS, "Allocate and perform KA.", allocate, ka)); + tests.add(kaTest); + } + } + doTest(CompoundTest.function((tsts) -> { + for (int i = 0; i < tsts.length; ++i) { + if (i != 1 && !tsts[i].ok()) { + return new Result(Result.Value.FAILURE, "Some tests did not have the expected result."); + } + } + return new Result(Result.Value.SUCCESS, "All tests had the expected result."); + }, "Wrong curve test of " + curve.getBits() + "b " + CardUtil.getKeyTypeString(curve.getField()), tests.toArray(new Test[0]))); + } + /* + * Do some interesting tests with corrupting the custom curves. + * For prime field: + * - p = 0 + * - p = 1 + * - p is a square of a prime + * - p is a composite q * s with q, s primes + * - TODO: p divides discriminant + */ + Random r = new Random(); + for (short keyLength : EC_Consts.FP_SIZES) { + byte curve = EC_Consts.getCurve(keyLength, KeyPair.ALG_EC_FP); + Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, KeyPair.ALG_EC_FP), ExpectedValue.SUCCESS)); + if (!key.ok()) { + doTest(CompoundTest.all(ExpectedValue.FAILURE, "No support for " + keyLength + "b ALG_EC_FP.", key)); + continue; + } + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, curve, EC_Consts.PARAMETERS_DOMAIN_FP, null), ExpectedValue.SUCCESS); + Test setup = CompoundTest.all(ExpectedValue.SUCCESS, "KeyPair setup.", key, set); + + Test prime0 = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_FP, EC_Consts.TRANSFORMATION_ZERO), "Set p = 0.", "ECDH with p = 0."); + Test prime1 = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_FP, EC_Consts.TRANSFORMATION_ONE), "Set p = 1.", "ECDH with p = 1."); + + short keyHalf = (short) (keyLength / 2); + BigInteger prime = new BigInteger(keyHalf, 50, r); + BigInteger primePow = prime.pow(2); + byte[] primePowBytes = ECUtil.toByteArray(primePow, keyLength); + EC_Params primePowData = new EC_Params(EC_Consts.PARAMETER_FP, new byte[][]{primePowBytes}); + Test primePower = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, primePowData.getParams(), primePowData.flatten()), "Set p = square of a prime.", "ECDH with p = q^2."); + + BigInteger q = new BigInteger(keyHalf, r); + BigInteger s = new BigInteger(keyHalf, r); + BigInteger compositeValue = q.multiply(s); + byte[] compositeBytes = ECUtil.toByteArray(compositeValue, keyLength); + EC_Params compositeData = new EC_Params(EC_Consts.PARAMETER_FP, new byte[][]{compositeBytes}); + Test composite = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, compositeData.getParams(), compositeData.flatten()), "Set p = product of two primes.", "ECDH with p = q * s."); + + Test wrongPrime = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted prime parameter.", prime0, prime1, primePower, composite); + + Test resetSetup = CompoundTest.all(ExpectedValue.SUCCESS, "Reset keypair.", set.clone()); + + Test randomG = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_G, (short) (EC_Consts.TRANSFORMATION_FULLRANDOM | EC_Consts.TRANSFORMATION_04_MASK)), "Set G = random non-point/point-like.", "ECDH with non-point G."); + Test fullRandomG = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_G, EC_Consts.TRANSFORMATION_FULLRANDOM), "Set G = random data.", "ECDH with G = random data."); + Test zeroG = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_G, EC_Consts.TRANSFORMATION_INFINITY), "Set G = inifnity.", "ECDH with G = infinity."); + Test wrongG = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted G parameter.", randomG, fullRandomG, zeroG); + + byte[] originalR = new byte[((keyLength + 7) / 8) + 1]; + short origRlen = EC_Consts.getCurveParameter(curve, EC_Consts.PARAMETER_R, originalR, (short) 0); + if (origRlen != originalR.length) { + byte[] copyR = new byte[origRlen]; + System.arraycopy(originalR, 0, copyR, 0, origRlen); + originalR = copyR; + } + BigInteger originalBigR = new BigInteger(1, originalR); + + Test zeroR = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, EC_Consts.PARAMETER_R, EC_Consts.TRANSFORMATION_ZERO), "Set R = 0.", "ECDH with R = 0."); + Test oneR = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, EC_Consts.PARAMETER_R, EC_Consts.TRANSFORMATION_ONE), "Set R = 1.", "ECDH with R = 1."); + + BigInteger prevPrimeR; + do { + prevPrimeR = BigInteger.probablePrime(originalBigR.bitLength() - 1, r); + } while (prevPrimeR.compareTo(originalBigR) >= 0); + byte[] prevRBytes = ECUtil.toByteArray(prevPrimeR, keyLength); + EC_Params prevRData = new EC_Params(EC_Consts.PARAMETER_R, new byte[][]{prevRBytes}); + Test prevprimeWrongR = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, prevRData.getParams(), prevRData.flatten()), "Set R = some prime (but [r]G != infinity) smaller than original R.", "ECDH with wrong R, prevprime."); + + BigInteger nextPrimeR = originalBigR.nextProbablePrime(); + byte[] nextRBytes = ECUtil.toByteArray(nextPrimeR, keyLength); + EC_Params nextRData = new EC_Params(EC_Consts.PARAMETER_R, new byte[][]{nextRBytes}); + Test nextprimeWrongR = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, nextRData.getParams(), nextRData.flatten()), "Set R = some prime (but [r]G != infinity) larger than original R.", "ECDH with wrong R, nextprime."); + + byte[] nonprimeRBytes = nextRBytes.clone(); + nonprimeRBytes[nonprimeRBytes.length - 1] ^= 1; + EC_Params nonprimeWrongRData = new EC_Params(EC_Consts.PARAMETER_R, new byte[][]{nonprimeRBytes}); + Test nonprimeWrongR = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, nonprimeWrongRData.getParams(), nonprimeWrongRData.flatten()), "Set R = some composite (but [r]G != infinity).", "ECDH with wrong R, composite."); + + Test wrongR = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted R parameter.", zeroR, oneR, prevprimeWrongR, nextprimeWrongR, nonprimeWrongR); + + byte[] kRaw = new byte[]{(byte) 0xff}; + EC_Params kData = new EC_Params(EC_Consts.PARAMETER_K, new byte[][]{kRaw}); + Test bigK = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, kData.getParams(), kData.flatten()), "", ""); + + byte[] kZero = new byte[]{(byte) 0}; + EC_Params kZeroData = new EC_Params(EC_Consts.PARAMETER_K, new byte[][]{kZero}); + Test zeroK = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, kZeroData.getParams(), kZeroData.flatten()), "", ""); + + Test wrongK = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted K parameter.", bigK, zeroK); + + doTest(CompoundTest.all(ExpectedValue.SUCCESS, "Tests of " + keyLength + "b " + CardUtil.getKeyTypeString(KeyPair.ALG_EC_FP), setup, wrongPrime, resetSetup, wrongG, resetSetup.clone(), wrongR, resetSetup.clone(), wrongK, resetSetup.clone())); + } + + /* + * For binary field: + * - e1, e2 or e3 is larger than m. + * - e1 = e2 = e3 = 0 + */ + for (short keyLength : EC_Consts.F2M_SIZES) { + byte curve = EC_Consts.getCurve(keyLength, KeyPair.ALG_EC_F2M); + Test key = runTest(CommandTest.expect(new Command.Allocate(this.card, CardConsts.KEYPAIR_BOTH, keyLength, KeyPair.ALG_EC_F2M), ExpectedValue.SUCCESS)); + if (!key.ok()) { + doTest(CompoundTest.all(ExpectedValue.FAILURE, "No support for " + keyLength + "b ALG_EC_F2M.", key)); + continue; + } + Test set = CommandTest.expect(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, curve, EC_Consts.PARAMETERS_DOMAIN_F2M, null), ExpectedValue.SUCCESS); + Test setup = CompoundTest.all(ExpectedValue.SUCCESS, "KeyPair setup.", key, set); + + Test coeff0 = ecdhTest(new Command.Transform(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.KEY_BOTH, EC_Consts.PARAMETER_F2M, EC_Consts.TRANSFORMATION_ZERO), "Set e1 = e2 = e3 = 0.", "ECDH with wrong field polynomial: x^" + keyLength); + + short e1 = (short) (2 * keyLength); + short e2 = (short) (3 * keyLength); + short e3 = (short) (4 * keyLength); + byte[][] coeffBytes = new byte[][]{ + ByteUtil.shortToBytes(keyLength), + ByteUtil.shortToBytes(e1), + ByteUtil.shortToBytes(e2), + ByteUtil.shortToBytes(e3)}; + EC_Params coeffParams = new EC_Params(EC_Consts.PARAMETER_F2M, coeffBytes); + Test coeffLarger = ecdhTest(new Command.Set(this.card, CardConsts.KEYPAIR_BOTH, EC_Consts.CURVE_external, coeffParams.getParams(), coeffParams.flatten()), "Set e1=" + e1 + ", e2=" + e2 + ", e3=" + e3, "ECDH with wrong field poly, powers larger than " + keyLength); + + Test wrong = CompoundTest.all(ExpectedValue.SUCCESS, "Tests with corrupted field polynomial parameter.", coeff0, coeffLarger); + doTest(CompoundTest.all(ExpectedValue.SUCCESS, "Tests of " + keyLength + "b " + CardUtil.getKeyTypeString(KeyPair.ALG_EC_F2M), setup, wrong)); + } + + /* + * TODO: tests for both Fp and F2m: + * - generator not on curve, + * - generator not on proper subgroup of curve(as specified by order/cofactor), + * - wrong order, + * - wrong cofactor. + */ + } + + private Test ecdhTest(Command setupCmd, String prepareDesc, String fullDesc) { + Test setup = CommandTest.expect(setupCmd, ExpectedValue.FAILURE); + Test generate = CommandTest.expect(new Command.Generate(this.card, CardConsts.KEYPAIR_BOTH), ExpectedValue.FAILURE); + Test preparePhase = CompoundTest.any(ExpectedValue.SUCCESS, prepareDesc, setup, generate); + Test allocateECDH = CommandTest.expect(new Command.AllocateKeyAgreement(this.card, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), ExpectedValue.SUCCESS); + Test ecdh = CommandTest.expect(new Command.ECDH(this.card, CardConsts.KEYPAIR_LOCAL, CardConsts.KEYPAIR_REMOTE, CardConsts.EXPORT_FALSE, EC_Consts.TRANSFORMATION_NONE, EC_Consts.KeyAgreement_ALG_EC_SVDP_DH), ExpectedValue.FAILURE); + + + return CompoundTest.function((tests) -> { + if (preparePhase.ok() | !allocateECDH.ok() | ecdh.ok()) { + return new Result(Result.Value.SUCCESS, "All tests had the expected result."); + } else { + return new Result(Result.Value.FAILURE, "Some tests did not have the expected result."); + } + }, (tests) -> { + preparePhase.run(); + if (preparePhase.ok()) { + return; + } + allocateECDH.run(); + if (!allocateECDH.ok()) { + return; + } + ecdh.run(); + }, fullDesc, preparePhase, allocateECDH, ecdh); + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTest.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTest.java new file mode 100644 index 0000000..b05d3e4 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTest.java @@ -0,0 +1,85 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.SimpleTest; +import cz.crcs.ectester.common.test.TestCallback; +import cz.crcs.ectester.reader.command.Command; +import cz.crcs.ectester.reader.response.Response; + +import java.util.Arrays; + +/** + * A simple test that runs one Command to get and evaluate one Response + * to get a Result and compare it with the expected one. + * + * @author Jan Jancar johny@neuromancer.sk + */ +public class CommandTest extends SimpleTest<CommandTestable> { + private CommandTest(CommandTestable command, TestCallback<CommandTestable> callback) { + super(command, callback); + } + + public static CommandTest function(CommandTestable command, TestCallback<CommandTestable> callback) { + return new CommandTest(command, callback); + } + + public static CommandTest function(Command command, TestCallback<CommandTestable> callback) { + return function(new CommandTestable(command), callback); + } + + public static CommandTest expect(CommandTestable command, Result.ExpectedValue expected, String ok, String nok) { + return new CommandTest(command, new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable commandTestable) { + Result.Value resultValue = Result.Value.fromExpected(expected, commandTestable.ok(), commandTestable.error()); + return new Result(resultValue, commandTestable.error() ? commandTestable.errorCause() : (resultValue.ok() ? ok : nok)); + } + }); + } + + public static CommandTest expect(Command command, Result.ExpectedValue expectedValue, String ok, String nok) { + return expect(new CommandTestable(command), expectedValue, ok, nok); + } + + public static CommandTest expect(CommandTestable command, Result.ExpectedValue expected) { + return expect(command, expected, null, null); + } + + public static CommandTest expect(Command command, Result.ExpectedValue expectedValue) { + return expect(command, expectedValue, null, null); + } + + public static CommandTest expectSW(CommandTestable command, short... expectedSWS) { + return new CommandTest(command, new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable commandTestable) { + if (Arrays.equals(commandTestable.getResponse().getSWs(), expectedSWS)) { + return new Result(Result.Value.SUCCESS); + } else { + return new Result(Result.Value.FAILURE); + } + } + }); + } + + public static CommandTest expectSW(Command command, short... expectedSWS) { + return expectSW(new CommandTestable(command), expectedSWS); + } + + public Command getCommand() { + return testable.getCommand(); + } + + public Response getResponse() { + return testable.getResponse(); + } + + @Override + public String getDescription() { + if (hasRun) { + return testable.getResponse().getDescription(); + } else { + return testable.getCommand().getDescription(); + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTestable.java b/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTestable.java new file mode 100644 index 0000000..f670534 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/CommandTestable.java @@ -0,0 +1,44 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.test.BaseTestable; +import cz.crcs.ectester.common.test.TestException; +import cz.crcs.ectester.reader.command.Command; +import cz.crcs.ectester.reader.response.Response; + +import javax.smartcardio.CardException; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class CommandTestable extends BaseTestable { + private Command command; + private Response response; + + public CommandTestable(Command command) { + this.command = command; + } + + public Command getCommand() { + return command; + } + + public Response getResponse() { + return response; + } + + @Override + public void run() { + try { + response = command.send(); + } catch (CardException e) { + throw new TestException(e); + } + + hasRun = true; + if (response.error()) { + error = true; + } else if (response.successful()) { + ok = true; + } + } +} diff --git a/reader/src/main/java/cz/crcs/ectester/reader/test/PerformanceTest.java b/reader/src/main/java/cz/crcs/ectester/reader/test/PerformanceTest.java new file mode 100644 index 0000000..a725dc2 --- /dev/null +++ b/reader/src/main/java/cz/crcs/ectester/reader/test/PerformanceTest.java @@ -0,0 +1,141 @@ +package cz.crcs.ectester.reader.test; + +import cz.crcs.ectester.common.test.Result; +import cz.crcs.ectester.common.test.SimpleTest; +import cz.crcs.ectester.common.test.TestCallback; +import cz.crcs.ectester.common.test.TestException; +import cz.crcs.ectester.common.util.CardConsts; +import cz.crcs.ectester.reader.CardMngr; +import cz.crcs.ectester.reader.command.Command; +import cz.crcs.ectester.reader.response.Response; + +import javax.smartcardio.CardException; +import java.util.Arrays; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class PerformanceTest extends SimpleTest<CommandTestable> { + private CardMngr cardManager; + private long[] times; + private long[] reducedTimes; + private Response[] responses; + private long mean; + private long median; + private long mode; + private int count; + private String desc; + + private PerformanceTest(CardMngr cardManager, CommandTestable testable, int count, String desc) { + super(testable, new TestCallback<CommandTestable>() { + @Override + public Result apply(CommandTestable testable) { + return new Result(Result.Value.SUCCESS); + } + }); + this.cardManager = cardManager; + this.count = count; + this.desc = desc; + } + + public static PerformanceTest repeat(CardMngr cardManager, Command cmd, int count) { + return new PerformanceTest(cardManager, new CommandTestable(cmd), count, null); + } + + public static PerformanceTest repeat(CardMngr cardManager, String desc, Command cmd, int count) { + return new PerformanceTest(cardManager, new CommandTestable(cmd), count, desc); + } + + @Override + public String getDescription() { + String rest = String.format("Mean = %d ns, Median = %d ns, Mode = %d ns", mean, median, mode); + return (desc == null ? rest : desc + " (" + rest + ")"); + } + + @Override + protected void runSelf() { + long baseTime; + try { + new Command.SetDryRunMode(cardManager, CardConsts.MODE_DRY_RUN).send(); + testable.run(); + baseTime = testable.getResponse().getDuration(); + testable.reset(); + testable.run(); + baseTime += testable.getResponse().getDuration(); + testable.reset(); + baseTime /= 2; + new Command.SetDryRunMode(cardManager, CardConsts.MODE_NORMAL).send(); + } catch (CardException ce) { + throw new TestException(ce); + } + + times = new long[count]; + reducedTimes = new long[count]; + responses = new Response[count]; + for (int i = 0; i < count; ++i) { + testable.run(); + responses[i] = testable.getResponse(); + times[i] = responses[i].getDuration(); + reducedTimes[i] = times[i] - baseTime; + testable.reset(); + } + + mean = Arrays.stream(reducedTimes).sum() / count; + + long[] sorted = reducedTimes.clone(); + Arrays.sort(sorted); + if (count % 2 == 0) { + median = (sorted[(count / 2) - 1] + sorted[count / 2]) / 2; + } else { + median = sorted[count / 2]; + } + + long max_occurences = 0; + int i = 0; + while (i < count) { + long current_value = sorted[i]; + long current_occurences = 0; + while (i < count && sorted[i] == current_value) { + i++; + current_occurences++; + } + if (current_occurences > max_occurences) { + max_occurences = current_occurences; + mode = current_value; + } + } + result = callback.apply(testable); + } + + public long getCount() { + return count; + } + + public Command getCommand() { + return testable.getCommand(); + } + + public Response[] getResponses() { + return responses; + } + + public long[] getTimes() { + return times; + } + + public long[] getReducedTimes() { + return reducedTimes; + } + + public long getMean() { + return mean; + } + + public long getMedian() { + return median; + } + + public long getMode() { + return mode; + } +} |
