diff options
| author | J08nY | 2017-01-17 02:55:31 +0100 |
|---|---|---|
| committer | J08nY | 2017-01-17 03:17:08 +0100 |
| commit | 4debe5adb4bb486f488878e348ee7bcf386c43f2 (patch) | |
| tree | 2cacbee1b1fac0c6afb686f5c2ce6f64bc4e1499 /src/cz/crcs/ectester/reader | |
| parent | bffdcc6925d806d74179a76b2dc57a619e9c1886 (diff) | |
| download | ECTester-4debe5adb4bb486f488878e348ee7bcf386c43f2.tar.gz ECTester-4debe5adb4bb486f488878e348ee7bcf386c43f2.tar.zst ECTester-4debe5adb4bb486f488878e348ee7bcf386c43f2.zip | |
major changes, ECTester rewrite, moved to valid package
reader: ECTester, mostly rewritten SimpleAPDU
- communication with applet now done through simpler
instructions: allocate, set, generate, ecdh, ecdsa
- moved to a valid Java package dir cz.crcs.ectester
- SimpleAPDU: renamed to ECTester
- CardMngr: seamlessly supports simulation vs real card
- DirtyLogger: takes a nullable String and creates file
- ECTester: currently only supports key generation,
curve testing under way
- supports external curve setting, example files
in data package
- tests can be done through files, to achieve a more
modular approach
- Util: static utility class
- ParamReader: reads curve domain parameters and keys from
simple csv-like human-readable files with hex strings
applet: ECTesterApplet, rewrite of SimpleECCApplet
- more granularity in instructions
- moved complexity over to the reader side
- ECKeyGenerator: now a class that takes KeyPair as param
- ECKeyTester: now a class that takes KeyPair as param
- EC_Consts: removed ecsp curves(now done externally),
removed unused methods
- ECTesterApplet: currently only tested instructions are:
allocate, set, generate
data: contains several curve and pubkey files in format supported
by ParamReader
- Prime field curves:
p,a,b,gx,gy,r,k
- Binary field curves:
e1,a,b,gx,gy,r,k or e1,e2,e3,a,b,gx,gy,r,k
- Public key:
wx,wy
- Private key:
s
- Key:
wx,wy,s
- all values are hex strings
Diffstat (limited to 'src/cz/crcs/ectester/reader')
| -rw-r--r-- | src/cz/crcs/ectester/reader/CardMngr.java | 289 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/DirtyLogger.java | 55 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ECTester.java | 489 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ISO7816_status_words.txt | 71 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ParamReader.java | 134 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/SimpleAPDU.java | 459 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/Util.java | 82 |
7 files changed, 1579 insertions, 0 deletions
diff --git a/src/cz/crcs/ectester/reader/CardMngr.java b/src/cz/crcs/ectester/reader/CardMngr.java new file mode 100644 index 0000000..d7a5c5f --- /dev/null +++ b/src/cz/crcs/ectester/reader/CardMngr.java @@ -0,0 +1,289 @@ +package cz.crcs.ectester.reader; + +import com.licel.jcardsim.io.CAD; +import com.licel.jcardsim.io.JavaxSmartCardInterface; +import java.util.List; +import java.util.Scanner; +import javacard.framework.AID; + +import javax.smartcardio.*; + +/** + * @author Petr Svenda petr@svenda.com + * @author Jan Jancar johny@neuromancer.sk + */ +public class CardMngr { + private CardTerminal m_terminal = null; + private CardChannel m_channel = null; + private Card m_card = null; + + // Simulator related attributes + private CAD m_cad = null; + private JavaxSmartCardInterface m_simulator = null; + + private boolean simulate = 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() { + this(false); + } + + public CardMngr(boolean simulate) { + this.simulate = simulate; + } + + 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.out.println("No terminals found"); + return false; + } + + //List numbers of Card readers + boolean cardFound = false; + for (int i = 0; i < terminalList.size(); i++) { + System.out.println(i + " : " + terminalList.get(i)); + m_terminal = terminalList.get(i); + if (m_terminal.isCardPresent()) { + m_card = m_terminal.connect("*"); + System.out.println("card: " + m_card); + m_channel = m_card.getBasicChannel(); + + //reset the card + System.out.println(Util.bytesToHex(m_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.out.println("ERROR: No suitable reader with card detected. Please check your reader connection"); + return false; + } else { + if (terminalList.size() == 1) { + m_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() + " - " + Util.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 + m_terminal = terminalList.get(answ); + } + } + + if (m_terminal != null) { + m_card = m_terminal.connect("*"); + System.out.println("card: " + m_card); + m_channel = m_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 || m_card != null; + } + + public void disconnectFromCard() throws CardException { + if (simulate) + return; + + if (m_card != null) { + m_card.disconnect(false); + m_card = null; + } + } + + public byte[] getCPLCData() throws Exception { + byte[] data; + + // TODO: Modify to obtain CPLC data + byte apdu[] = new byte[HEADER_LENGTH]; + apdu[OFFSET_CLA] = (byte) 0x00; + apdu[OFFSET_INS] = (byte) 0x00; + apdu[OFFSET_P1] = (byte) 0x00; + apdu[OFFSET_P2] = (byte) 0x00; + apdu[OFFSET_LC] = (byte) 0x00; + + ResponseAPDU resp = send(apdu); + if (resp.getSW() != 0x9000) { // 0x9000 is "OK" + System.out.println("Fail to obtain card's response data"); + data = null; + } else { + byte temp[] = resp.getBytes(); + data = new byte[temp.length - 2]; + System.arraycopy(temp, 0, data, 0, temp.length - 2); + // Last two bytes are status word (also obtainable by resp.getSW()) + // Take a look at ISO7816_status_words.txt for common codes + } + + return data; + } + + public void probeCardCommands() throws Exception { + // TODO: modify to probe for instruction + for (int i = 0; i <= 0; i++) { + byte apdu[] = new byte[HEADER_LENGTH]; + apdu[OFFSET_CLA] = (byte) 0x00; + apdu[OFFSET_INS] = (byte) 0x00; + apdu[OFFSET_P1] = (byte) 0x00; + apdu[OFFSET_P2] = (byte) 0x00; + apdu[OFFSET_LC] = (byte) 0x00; + + ResponseAPDU resp = send(apdu); + + System.out.println("Response: " + Integer.toHexString(resp.getSW())); + + if (resp.getSW() != 0x6D00) { // Note: 0x6D00 is SW_INS_NOT_SUPPORTED + // something? + } + } + } + + public static List<CardTerminal> getReaderList() { + try { + TerminalFactory factory = TerminalFactory.getDefault(); + return factory.terminals().list(); + } catch (CardException ex) { + System.out.println("Exception : " + ex); + return null; + } + } + + public ResponseAPDU sendAPDU(CommandAPDU apdu) throws CardException { + System.out.println(">>>>"); + System.out.println(apdu); + + System.out.println(Util.bytesToHex(apdu.getBytes())); + + long elapsed = -System.nanoTime(); + + ResponseAPDU responseAPDU = m_channel.transmit(apdu); + + elapsed += System.nanoTime(); + + System.out.println(responseAPDU); + System.out.println(Util.bytesToHex(responseAPDU.getBytes())); + + if (responseAPDU.getSW1() == (byte) 0x61) { + CommandAPDU apduToSend = new CommandAPDU((byte) 0x00, + (byte) 0xC0, (byte) 0x00, (byte) 0x00, + responseAPDU.getSW1()); + + responseAPDU = m_channel.transmit(apduToSend); + System.out.println(Util.bytesToHex(responseAPDU.getBytes())); + } + + System.out.println("<<<<"); + System.out.println("Elapsed time (ms): " + elapsed / 1000000); + 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 appletClass) { + System.setProperty("com.licel.jcardsim.terminal.type", "2"); + m_cad = new CAD(System.getProperties()); + m_simulator = (JavaxSmartCardInterface) m_cad.getCardInterface(); + AID appletAID = new AID(appletAIDArray, (short) 0, (byte) appletAIDArray.length); + + AID appletAIDRes = m_simulator.installApplet(appletAID, appletClass, installData, (short) 0, (byte) installData.length); + return m_simulator.selectApplet(appletAID); + } + + public ResponseAPDU sendAPDUSimulator(CommandAPDU apdu) { + System.out.println(">>>>"); + System.out.println(Util.bytesToHex(apdu.getBytes())); + + ResponseAPDU response = m_simulator.transmitCommand(apdu); + byte[] responseBytes = response.getBytes(); + + System.out.println(Util.bytesToHex(responseBytes)); + System.out.println("<<<<"); + + return response; + } + + public ResponseAPDU sendAPDUSimulator(byte[] apdu) { + 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/src/cz/crcs/ectester/reader/DirtyLogger.java b/src/cz/crcs/ectester/reader/DirtyLogger.java new file mode 100644 index 0000000..f69557d --- /dev/null +++ b/src/cz/crcs/ectester/reader/DirtyLogger.java @@ -0,0 +1,55 @@ +package cz.crcs.ectester.reader; + +import java.io.FileWriter; +import java.io.IOException; + +/** + * @author Petr Svenda petr@svenda.com + * @author Jan Jancar johny@neuromancer.sk + */ +public class DirtyLogger { + FileWriter log; + boolean systemOut; + + public DirtyLogger(String filePath) throws IOException { + this(filePath, true); + } + + public DirtyLogger(String filePath, boolean systemOut) throws IOException { + if (filePath != null) + this.log = new FileWriter(filePath); + this.systemOut = systemOut; + } + + public void println() { + print("\n"); + } + + public void println(String logLine) { + logLine += "\n"; + print(logLine); + } + + public void print(String logLine) { + if (systemOut) { + System.out.print(logLine); + } + if (log != null) { + try { + log.write(logLine); + } catch (IOException ignored) { + } + } + } + + void flush() { + try { + if (log != null) log.flush(); + } catch (IOException ignored) { + } + } + + void close() throws IOException { + if (log != null) log.close(); + } +} diff --git a/src/cz/crcs/ectester/reader/ECTester.java b/src/cz/crcs/ectester/reader/ECTester.java new file mode 100644 index 0000000..8c00ea9 --- /dev/null +++ b/src/cz/crcs/ectester/reader/ECTester.java @@ -0,0 +1,489 @@ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import javacard.security.KeyPair; +import org.apache.commons.cli.*; + +import javax.smartcardio.CardException; +import javax.smartcardio.CommandAPDU; +import javax.smartcardio.ResponseAPDU; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; + +/** + * @author Petr Svenda petr@svenda.com + * @author Jan Jancar johny@neuromancer.sk + */ +public class ECTester { + + private CardMngr cardManager = null; + private DirtyLogger systemOutLogger = null; + private FileOutputStream outputFile = null; + + //Options + private int optBits; + private boolean optAll; + private boolean optPrimeField = false; + private boolean optBinaryField = false; + private boolean optNamed = false; + private String optCurve = null; + private String optPublic = null; + private String optPrivate = null; + private String optKey = null; + private String optLog = null; + private String optOutput = null; + private boolean optSimulate = false; + + private int optGenerateAmount; + private String optECDSASign; + + private Options opts = new Options(); + private static final String CLI_HEADER = ""; + private static final String CLI_FOOTER = ""; + + + private static final byte[] SELECT_ECTESTERAPPLET = {(byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0a, + (byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31}; + private static final byte[] AID = {(byte) 0x4C, (byte) 0x61, (byte) 0x62, (byte) 0x61, (byte) 0x6B, (byte) 0x41, (byte) 0x70, (byte) 0x70, (byte) 0x6C, (byte) 0x65, (byte) 0x74}; + private static final byte[] INSTALL_DATA = new byte[10]; + + /* + private static final byte[] ALLOCATE = { + (byte) 0xB0, + (byte) 0x5a, //INS ALLOCATE + (byte) 0x00, //P1 *byte keypair + (byte) 0x00, //P2 + (byte) 0x03, //LC + (byte) 0x00, //DATA *short keyLength + (byte) 0x00, + (byte) 0x00 // *byte keyClass + }; + + private static final byte[] SET = { + (byte) 0xB0, + (byte) 0x5B, //INS SET + (byte) 0x00, //P1 *byte keypair + (byte) 0x00, //P2 *byte export + (byte) 0x06, //LC + (byte) 0x00, //DATA *byte curve + (byte) 0x00, // *short params + (byte) 0x00, // + (byte) 0x00, // *short corruptedParams + (byte) 0x00, // + (byte) 0x00 // *byte corruptionType + // [short paramLength, byte[] param] for all params in params + }; + + private static final byte[] GENERATE = { + (byte) 0xB0, + (byte) 0x5C, //INS GENERATE + (byte) 0x00, //P1 *byte keypair + (byte) 0x00, //P2 *byte export + (byte) 0x00 //LC + }; + + private static final byte[] ECDH = { + (byte) 0xB0, + (byte) 0x5D, //INS ECDH + (byte) 0x00, //P1 *byte keypair + (byte) 0x00, //P2 *byte export + (byte) 0x01, //LC + (byte) 0x00 //DATA *byte valid + }; + + private static final byte[] ECDSA = { + (byte) 0xB0, + (byte) 0x5E, //INS ECDSA + (byte) 0x00, //P1 *byte keypair + (byte) 0x00, //P2 *byte export + (byte) 0x00, //LC + //DATA [*short dataLength, byte[] data] + }; + */ + + private void run(String[] args) { + try { + CommandLine cli = parseArgs(args); + + //if help, print and quit + if (cli.hasOption("help")) { + help(); + return; + } + //if not, read other options first, into attributes, then do action + if (!readOptions(cli)) { + return; + } + cardManager = new CardMngr(optSimulate); + + if (optSimulate) { + if (!cardManager.prepareLocalSimulatorApplet(AID, INSTALL_DATA, ECTesterApplet.class)) { + System.err.println("Failed to establish a simulator."); + return; + } + } else { + if (!cardManager.connectToCardSelect()) { + System.err.println("Failed to connect to card."); + return; + } + cardManager.send(SELECT_ECTESTERAPPLET); + } + + systemOutLogger = new DirtyLogger(optLog, true); + + //do action + if (cli.hasOption("generate")) { + generate(); + } else if (cli.hasOption("test")) { + test(); + } else if (cli.hasOption("ecdh")) { + ecdh(); + } else if (cli.hasOption("ecdsa")) { + ecdsa(); + } + + cardManager.disconnectFromCard(); + systemOutLogger.close(); + + } catch (MissingOptionException moex) { + System.err.println("Missing required options, one of:"); + for (Object opt : moex.getMissingOptions().toArray()) { + if (opt instanceof OptionGroup) { + for (Option o : ((OptionGroup) opt).getOptions()) { + System.err.println(o); + } + } else if (opt instanceof String) { + System.err.println(opt); + } + } + } catch (MissingArgumentException maex) { + System.err.println("Option, " + maex.getOption().getOpt() + " requires an argument: " + maex.getOption().getArgName()); + } catch (AlreadySelectedException asex) { + System.err.println(asex.getMessage()); + } catch (ParseException | CardException pex) { + pex.printStackTrace(); + } catch (NumberFormatException nfex) { + System.err.println("Not a number. " + nfex.getMessage()); + nfex.printStackTrace(System.err); + } catch (FileNotFoundException fnfe) { + System.err.println("File " + fnfe.getMessage() + " not found."); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private CommandLine parseArgs(String[] args) throws ParseException { + /* + * Actions: + * -h / --help + * -g / --generate [amount] + * -t / --test + * -dh / --ecdh + * -dsa / --ecdsa [data_file] + * + * Options: + * -b / --bit-size [b] / -a / --all + * -fp / --prime-field + * -f2m / --binary-field + * -n / --named + * -c / --curve [curve_file] field,a,b,gx,gy,r,k + * --public [pubkey_file] wx,wy + * --private [privkey_file] s + * -k / --key [key_file] wx,wy,s + * -o / --output [output_file] + * -s / --simulate + */ + OptionGroup actions = new OptionGroup(); + actions.setRequired(true); + actions.addOption(Option.builder("h").longOpt("help").desc("Print help.").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.").build()); + actions.addOption(Option.builder("dh").longOpt("ecdh").desc("Do ECDH.").build()); + actions.addOption(Option.builder("dsa").longOpt("ecdsa").desc("Sign data with ECDSA.").hasArg().argName("data_file").optionalArg(true).build()); + opts.addOptionGroup(actions); + + OptionGroup size = new OptionGroup(); + size.addOption(Option.builder("b").longOpt("bit-size").desc("Set curve size.").hasArg().argName("b").build()); + size.addOption(Option.builder("a").longOpt("all").desc("Test all curve sizes.").build()); + opts.addOptionGroup(size); + + opts.addOption(Option.builder("fp").longOpt("prime-field").desc("Use prime field curve.").build()); + opts.addOption(Option.builder("f2m").longOpt("binary-field").desc("Use binary field curve.").build()); + opts.addOption(Option.builder("n").longOpt("named").desc("Use a named curve.").build()); + opts.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()); + opts.addOption(Option.builder("pub").longOpt("public").desc("Use public key from file [pubkey_file] (wx,wy).").hasArg().argName("pubkey_file").build()); + opts.addOption(Option.builder("priv").longOpt("private").desc("Use private key from file [privkey_file] (s).").hasArg().argName("privkey_file").build()); + opts.addOption(Option.builder("k").longOpt("key").desc("Use keypair from fileĀ [key_file] (wx,wy,s).").hasArg().argName("key_file").build()); + opts.addOption(Option.builder("o").longOpt("output").desc("Output into file [output_file].").hasArg().argName("output_file").build()); + opts.addOption(Option.builder("l").longOpt("log").desc("Log output into file [log_file].").hasArg().argName("log_file").optionalArg(true).build()); + opts.addOption(Option.builder("s").longOpt("simulate").desc("Simulate a card with jcardsim instead of using a terminal.").build()); + + CommandLineParser parser = new DefaultParser(); + return parser.parse(opts, args); + } + + /** + * Reads and validates options. + * + * @param cli cli object, with parsed args + * @return whether the options are valid. + */ + private boolean readOptions(CommandLine cli) { + optBits = Integer.parseInt(cli.getOptionValue("bit-size", "0")); + optAll = cli.hasOption("all"); + optPrimeField = cli.hasOption("fp"); + optBinaryField = cli.hasOption("f2m"); + optNamed = cli.hasOption("named"); + optCurve = cli.getOptionValue("curve"); + optPublic = cli.getOptionValue("public"); + optPrivate = cli.getOptionValue("private"); + optKey = cli.getOptionValue("key"); + if (cli.hasOption("log")) { + optLog = cli.getOptionValue("log", String.format("ECTESTER_log_%d.log", System.currentTimeMillis() / 1000)); + } + optOutput = cli.getOptionValue("output"); + optSimulate = cli.hasOption("simulate"); + + if (optKey != null && (optPublic != null || optPrivate != null)) { + System.err.print("Can only specify the whole key with --key or pubkey and privkey with --public and --private."); + return false; + } + if (optBits < 0) { + System.err.println("Bit-size must not be negative."); + return false; + } + if (optNamed && optCurve != null) { + System.err.println("Can only specify a named curve with --named or an external curve with --curve. (not both)"); + return false; + } + if (optBits == 0 || optAll) { + System.err.println("You have to specify curve bit-size."); + return false; + } + + if (cli.hasOption("generate")) { + if (optPrimeField == optBinaryField) { + System.err.print("Need to specify field with -fp or -f2m. (not both)"); + return false; + } + if (optKey != null || optPublic != null || optPrivate != null) { + System.err.println("Keys should not be specified when generating keys."); + return false; + } + + if (optOutput == null) { + System.err.println("You have to specify an output file for the key generation process."); + return false; + } + + optGenerateAmount = Integer.parseInt(cli.getOptionValue("generate", "0")); + if (optGenerateAmount < 0) { + System.err.println("Amount of keys generated cant be negative."); + return false; + } + } else if (cli.hasOption("test")) { + if (!optBinaryField && !optPrimeField) { + optBinaryField = true; + optPrimeField = true; + } + + } else if (cli.hasOption("ecdh")) { + } else if (cli.hasOption("ecdsa")) { + optECDSASign = cli.getOptionValue("ecdsa"); + } + + return true; + } + + /** + * Prints help. + */ + private void help() { + HelpFormatter help = new HelpFormatter(); + help.printHelp("ECTester.jar", CLI_HEADER, opts, CLI_FOOTER); + } + + /** + * Generates EC keypairs and outputs them to log. + */ + private void generate() throws CardException, IOException { + ///// + short keyLength = (short) optBits; + byte keyClass = optPrimeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + short params = optPrimeField ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M; + + cmdAllocate(ECTesterApplet.KEYPAIR_LOCAL, keyLength, keyClass); + + if (optNamed) { + cmdSet(ECTesterApplet.KEYPAIR_LOCAL, (byte) 0, EC_Consts.getCurve(keyLength, keyClass), params, EC_Consts.PARAMETERS_NONE, EC_Consts.CORRUPTION_NONE, null); + } else if (optCurve != null) { + byte[] external = ParamReader.flatten(params, ParamReader.readFile(optCurve)); + cmdSet(ECTesterApplet.KEYPAIR_LOCAL, (byte) 0, EC_Consts.CURVE_external, params, EC_Consts.PARAMETERS_NONE, EC_Consts.CORRUPTION_NONE, external); + } + ///// + + FileWriter keysFile = new FileWriter(optOutput); + keysFile.write("index;time;pubW;privS\n"); + + int generated = 0; + int retry = 0; + while (generated < optGenerateAmount || optGenerateAmount == 0) { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cmdGenerate(ECTesterApplet.KEYPAIR_LOCAL, (byte) (ECTesterApplet.EXPORT_BOTH | ECTesterApplet.KEYPAIR_LOCAL)); + elapsed += System.nanoTime(); + + byte[] bytes = response.getData(); + if (bytes.length <= 2) { + //error, retry 10 times + if (retry < 10) { + retry++; + } else { + System.err.println("Keys could not be generated."); + break; + } + } else { + short publicLength = Util.getShort(bytes, 2); + String pubkey = Util.bytesToHex(bytes, 4, publicLength, false); + short privateLength = Util.getShort(bytes, 4 + publicLength); + String privkey = Util.bytesToHex(bytes, 6 + publicLength, privateLength, false); + + keysFile.write(String.format("%d;%d;%s;%s\n", generated, elapsed / 1000000, pubkey, privkey)); + keysFile.flush(); + generated++; + } + } + keysFile.close(); + } + + /** + * + */ + private void test() { + //TODO + // allocate + // set custom + // generate + // ecdh local, local, valid + // ecdh local, local, invalid + // ecdsa local, local, 00? + + } + + /** + * + */ + private void ecdh() { + //TODO + //allocate local + remote + //set curve if specified + // + } + + /** + */ + private void ecdsa() { + //TODO + } + + /** + * Sends the INS_ALLOCATE instruction to the card/simulation. + * + * @param keypair + * @param keyLength + * @param keyClass + * @return card response + * @throws CardException + */ + private ResponseAPDU cmdAllocate(byte keypair, short keyLength, byte keyClass) throws CardException { + byte[] data = new byte[]{0, 0, keyClass}; + Util.setShort(data, 0, keyLength); + + CommandAPDU allocate = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ALLOCATE, keypair, 0x00, data); + return cardManager.send(allocate); + } + + /** + * Sends the INS_SET instruction to the card/simulation. + * + * @param keypair + * @param export + * @param curve + * @param params + * @param corrupted + * @param corruption + * @param external + * @return card response + * @throws CardException + */ + private ResponseAPDU cmdSet(byte keypair, byte export, byte curve, short params, short corrupted, byte corruption, byte[] external) throws CardException { + int len = external != null ? 6 + 2 + external.length : 6; + byte[] data = new byte[len]; + data[0] = curve; + Util.setShort(data, 1, params); + Util.setShort(data, 3, corrupted); + data[5] = corruption; + if (external != null) { + System.arraycopy(external, 0, data, 6, external.length); + } + + CommandAPDU set = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_SET, keypair, export, data); + return cardManager.send(set); + } + + /** + * Sends the INS_GENERATE instruction to the card/simulation. + * + * @param keypair + * @param export + * @return card response + */ + private ResponseAPDU cmdGenerate(byte keypair, byte export) throws CardException { + CommandAPDU generate = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_GENERATE, keypair, export); + return cardManager.send(generate); + } + + /** + * Sends the INS_ECDH instruction to the card/simulation. + * + * @param keypair + * @param export + * @param valid + * @return card response + * @throws CardException + */ + private ResponseAPDU cmdECDH(byte keypair, byte export, byte valid) throws CardException { + byte[] data = new byte[1]; + data[0] = valid; + + CommandAPDU ecdh = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ECDH, keypair, export, data); + return cardManager.send(ecdh); + } + + /** + * Sends the INS_ECDSA instruction to the card/simulation. + * + * @param keypair + * @param export + * @param raw + * @return card response + */ + private ResponseAPDU cmdECDSA(byte keypair, byte export, byte[] raw) throws CardException { + int len = raw != null ? raw.length : 0; + byte[] data = new byte[2 + len]; + Util.setShort(data, 0, (short) len); + if (raw != null) { + System.arraycopy(raw, 0, data, 2, len); + } + + CommandAPDU ecdsa = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ECDSA, keypair, export, data); + return cardManager.send(ecdsa); + } + + public static void main(String[] args) { + ECTester app = new ECTester(); + app.run(args); + } +} diff --git a/src/cz/crcs/ectester/reader/ISO7816_status_words.txt b/src/cz/crcs/ectester/reader/ISO7816_status_words.txt new file mode 100644 index 0000000..bf5af2b --- /dev/null +++ b/src/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/src/cz/crcs/ectester/reader/ParamReader.java b/src/cz/crcs/ectester/reader/ParamReader.java new file mode 100644 index 0000000..5232326 --- /dev/null +++ b/src/cz/crcs/ectester/reader/ParamReader.java @@ -0,0 +1,134 @@ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.EC_Consts; + +import java.io.*; +import java.util.LinkedList; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Pattern; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class ParamReader { + private static final Pattern hex = Pattern.compile("[a-fA-F\\d]+"); + + /** + * Flattens params read from String[] data into a byte[] with their lengths prepended as short entries. + * @param params (EC_Consts.PARAMETER_* | ...) + * @param data data read by readString, readFile, readResource + * @return byte[] with params flattened + */ + public static byte[] flatten(short params, String[] data) { + if (!validate(data)) { + return null; + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + short paramMask = EC_Consts.PARAMETER_FP; + int i = 0; + while (paramMask <= EC_Consts.PARAMETER_S) { + short masked = (short) (params & paramMask); + if (masked != 0) { + byte[] param = parse(data[i]); + if (masked == EC_Consts.PARAMETER_F2M && data.length == 9) { + //read and pad and append e_2, e_3 + param = Util.concatenate(param, parse(data[i + 1]), parse(data[i + 2])); + i += 2; + if (param.length != 6) + return null; + } + if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) { + //read another param (the y coord) and put into X962 format. + byte[] y = parse(data[i + 1]); + param = Util.concatenate(new byte[]{4}, param, y); + i++; + } + if (param.length == 0) + return null; + + //write length + byte[] length = new byte[2]; + Util.setShort(length, 0, (short) param.length); + out.write(length, 0, 2); + //write data + out.write(param, 0, param.length); + i++; + } + paramMask = (short) (paramMask << 1); + } + + return (out.size() == 0) ? null : out.toByteArray(); + } + + /** + * Reads hex params from a CSV String data. + * @param data String containing CSV data(hex) + * @return String array containing the CSV entries + */ + public static String[] readString(String data) { + return read(new ByteArrayInputStream(data.getBytes())); + } + + /** + * Reads hex params from a CSV Resource (inside jar). + * @param resourcePath path to the resourse + * @return String array containing the CSV entries + */ + public static String[] readResource(String resourcePath) { + return read(ParamReader.class.getResourceAsStream(resourcePath)); + } + + /** + * Reads hex params from a CSV file. + * @param filePath path to the file + * @return String array containing the CSV entries + * @throws FileNotFoundException if the file cannot be opened + */ + public static String[] readFile(String filePath) throws FileNotFoundException { + return read(new FileInputStream(filePath)); + } + + private static String[] read(InputStream in) { + Scanner s = new Scanner(in); + + s.useDelimiter(",|;"); + List<String> data = new LinkedList<String>(); + while (s.hasNext()) { + String field = s.next(); + data.add(field.replaceAll("\\s+", "")); + } + return data.toArray(new String[data.size()]); + } + + private static boolean validate(String[] data) { + if (data == null || data.length == 0) { + return false; + } + for (String param : data) { + if (!hex.matcher(param).matches()) { + return false; + } + } + return true; + } + + private static byte[] parse(String hex) { + byte[] data = Util.hexToBytes(hex); + if (data == null) + return new byte[0]; + if (data.length < 2) + return pad(data); + return data; + } + + private static byte[] pad(byte[] data) { + if (data.length == 1) { + return new byte[]{(byte) 0, data[0]}; + } else if (data.length == 0 || data.length > 2) { + return data; + } + return null; + } +} diff --git a/src/cz/crcs/ectester/reader/SimpleAPDU.java b/src/cz/crcs/ectester/reader/SimpleAPDU.java new file mode 100644 index 0000000..f120608 --- /dev/null +++ b/src/cz/crcs/ectester/reader/SimpleAPDU.java @@ -0,0 +1,459 @@ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.EC_Consts; +import javacard.framework.ISO7816; +import javacard.security.CryptoException; +import javacard.security.KeyPair; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +import javax.smartcardio.ResponseAPDU; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; + + +/** + * @author Petr Svenda petr@svenda.com + */ +public class SimpleAPDU { + private CardMngr cardManager = new CardMngr(); + private DirtyLogger systemOutLogger = null; + + private CommandLineParser cliParser = new DefaultParser(); + private Options opts = new Options(); + private static final String cliHeader = ""; + private static final String cliFooter = ""; + + private final static byte SELECT_ECTESTERAPPLET[] = {(byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0a, + (byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31}; + + private static final byte TESTECSUPPORTALL_FP[] = {(byte) 0xB0, (byte) 0x5E, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + private static final byte TESTECSUPPORTALL_F2M[] = {(byte) 0xB0, (byte) 0x5F, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + private static final byte TESTECSUPPORT_GIVENALG[] = {(byte) 0xB0, (byte) 0x71, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + private static final short TESTECSUPPORT_ALG_OFFSET = 5; + private static final short TESTECSUPPORT_KEYLENGTH_OFFSET = 6; + + private static final byte TESTECSUPPORTALL_LASTUSEDPARAMS[] = {(byte) 0xB0, (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + + private static final byte TESTECSUPPORTALL_FP_KEYGEN_INVALIDCURVEB[] = {(byte) 0xB0, (byte) 0x70, (byte) 0x00, (byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + private static final short INVALIDCURVEB_NUMREPEATS_OFFSET = 5; + private static final short INVALIDCURVEB_CORRUPTIONTYPE_OFFSET = 7; + private static final short INVALIDCURVEB_REWINDONSUCCESS_OFFSET = 9; + + private static final byte TESTECSUPPORT_GENERATEECCKEY[] = {(byte) 0xB0, (byte) 0x5a, (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + private static final short GENERATEECKEY_ALG_OFFSET = 5; + private static final short GENERATEECKEY_KEYLENGTH_OFFSET = 6; + private static final short GENERATEECKEY_ANOMALOUS_OFFSET = 8; + + + private void run(String[] args) { + try { + //parse cmd args + CommandLine cli = parseArgs(args); + + //byte[] installData = new byte[10]; + //byte[] AID = {(byte) 0x4C, (byte) 0x61, (byte) 0x62, (byte) 0x61, (byte) 0x6B, (byte) 0x41, (byte) 0x70, (byte) 0x70, (byte) 0x6C, (byte) 0x65, (byte) 0x74}; + //cardManager.prepareLocalSimulatorApplet(AID, installData, SimpleECCApplet.class); + + //do stuff + if (cli.hasOption("help")) { + HelpFormatter help = new HelpFormatter(); + help.printHelp("SimpleAPDU", cliHeader, opts, cliFooter); + } else { + //open log(only when actually doing something) + String logFileName = cli.getOptionValue("output-file", String.format("ECTESTER_log_%d.log", System.currentTimeMillis())); + FileOutputStream stdoutStream = new FileOutputStream(logFileName); + systemOutLogger = new DirtyLogger(stdoutStream, true); + + boolean fp = cli.hasOption("fp"); + boolean f2m = cli.hasOption("f2m"); + if (!fp && !f2m) { + fp = true; + f2m = true; + } + int genAmount = Integer.parseInt(cli.getOptionValue("generate", "0")); + int keyLength = Integer.parseInt(cli.getOptionValue("b", "192")); + + if (cli.hasOption("generate")) { + //generate EC keys + if (fp) { + generateECKeys(genAmount, KeyPair.ALG_EC_FP, (short) keyLength, cli.hasOption("anomalous")); + } + if (f2m) { + generateECKeys(genAmount, KeyPair.ALG_EC_F2M, (short) keyLength, cli.hasOption("anomalous")); + } + } else if (cli.hasOption("test")) { + if (cli.hasOption("bit-size")) { + //test only one bitsize + if (fp) { + testSupportECFp((short) keyLength); + } + if (f2m) { + testSupportECFp((short) keyLength); + } + } else { + //test default bit sizes + testSupportECAll(fp, f2m); + testFPkeyGen((short) 10, EC_Consts.CORRUPTION_ONEBYTERANDOM, true); + } + } else { + systemOutLogger.println("You need to specify one of -t / -g [num] commands."); + } + + //close log + systemOutLogger.close(); + } + + //disconnect + cardManager.disconnectFromCard(); + } catch (Exception ex) { + if (systemOutLogger != null) { + systemOutLogger.println("Exception : " + ex); + } + } + } + + private CommandLine parseArgs(String[] args) throws ParseException { + + opts.addOption("h", "help", false, "show this help"); + opts.addOption(Option.builder("g") + .longOpt("generate") + .hasArg() + .optionalArg(true) + .argName("num") + .desc("generate EC keys").build()); + opts.addOption("t", "test", false, "test EC support (default)"); + opts.addOption(Option.builder("b") + .longOpt("bit-size") + .hasArg() + .argName("bits") + .desc("set EC bit size").build()); + opts.addOption("f2m", "use EC over binary-fields"); + opts.addOption("fp", "user EC over prime-fields (default)"); + opts.addOption("s", "anomalous", false, "generate anomalous (non-prime order, small pubkey order) curves"); + opts.addOption(Option.builder("o") + .longOpt("output-file") + .hasArg() + .argName("file") + .desc("output file to log to").build()); + return cliParser.parse(opts, args); + } + + static short getShort(byte[] array, int offset) { + return (short) (((array[offset] & 0xFF) << 8) | (array[offset + 1] & 0xFF)); + } + + static void setShort(byte[] array, int offset, short value) { + array[offset + 1] = (byte) (value & 0xFF); + array[offset] = (byte) ((value >> 8) & 0xFF); + } + + private boolean ReconnnectToCard() throws Exception { + if (cardManager.connected()) { + cardManager.disconnectFromCard(); + } + + boolean result = cardManager.connectToCard(); + if (result) { + // Select our application on card + cardManager.sendAPDU(SELECT_ECTESTERAPPLET); + } + return result; + } + + private void testFPkeyGen(short numRepeats, short corruptionType, boolean bRewind) throws Exception { + byte[] apdu = Arrays.copyOf(TESTECSUPPORTALL_FP_KEYGEN_INVALIDCURVEB, TESTECSUPPORTALL_FP_KEYGEN_INVALIDCURVEB.length); + setShort(apdu, INVALIDCURVEB_NUMREPEATS_OFFSET, numRepeats); + setShort(apdu, INVALIDCURVEB_CORRUPTIONTYPE_OFFSET, corruptionType); + apdu[INVALIDCURVEB_REWINDONSUCCESS_OFFSET] = bRewind ? (byte) 1 : (byte) 0; + + ReconnnectToCard(); + ResponseAPDU resp_fp_keygen = cardManager.sendAPDU(apdu); + ResponseAPDU resp_keygen_params = cardManager.sendAPDU(TESTECSUPPORTALL_LASTUSEDPARAMS); + PrintECKeyGenInvalidCurveB(resp_fp_keygen); + PrintECKeyGenInvalidCurveB_lastUserParams(resp_keygen_params); + } + + private void testSupportECGivenAlg(short keyLength, byte keyClass) throws Exception { + byte[] apdu = Arrays.copyOf(TESTECSUPPORT_GIVENALG, TESTECSUPPORT_GIVENALG.length); + apdu[TESTECSUPPORT_ALG_OFFSET] = keyClass; + setShort(apdu, TESTECSUPPORT_KEYLENGTH_OFFSET, keyLength); + + ReconnnectToCard(); + ResponseAPDU resp = cardManager.sendAPDU(apdu); + //byte[] resp = cardManager.sendAPDUSimulator(apdu); + PrintECSupport(resp); + } + + private void testSupportECFp(short keyLength) throws Exception { + testSupportECGivenAlg(keyLength, KeyPair.ALG_EC_FP); + } + + private void testSupportECF2m(short keyLength) throws Exception { + testSupportECGivenAlg(keyLength, KeyPair.ALG_EC_F2M); + } + + private void testSupportECAll(boolean testFp, boolean testF2m) throws Exception { + if (testFp) { + testSupportECFp((short) 128); + testSupportECFp((short) 192); + testSupportECFp((short) 224); + testSupportECFp((short) 256); + testSupportECFp((short) 384); + testSupportECFp((short) 521); + } + + if (testF2m) { + testSupportECF2m((short) 113); + testSupportECF2m((short) 131); + testSupportECF2m((short) 163); + testSupportECF2m((short) 193); + } + } + + private void generateECKeys(int amount, byte keyClass, short keyLength, boolean anomalous) throws Exception { + if (cardManager.connectToCardSelect()) { + cardManager.sendAPDU(SELECT_ECTESTERAPPLET); + + String keyFileName = String.format("ECKEYS_%s_%d.log", keyClass == KeyPair.ALG_EC_FP ? "fp" : "f2m", System.currentTimeMillis()); + FileOutputStream keysFile = new FileOutputStream(keyFileName); + + String message = "index;time;pubW;privS\n"; + keysFile.write(message.getBytes()); + byte[] gatherKeyAPDU = Arrays.copyOf(TESTECSUPPORT_GENERATEECCKEY, TESTECSUPPORT_GENERATEECCKEY.length); + // Prepare keypair object + gatherKeyAPDU[ISO7816.OFFSET_P1] = SimpleECCApplet.P1_SETCURVE; + gatherKeyAPDU[GENERATEECKEY_ALG_OFFSET] = keyClass; + setShort(gatherKeyAPDU, GENERATEECKEY_KEYLENGTH_OFFSET, keyLength); + gatherKeyAPDU[GENERATEECKEY_ANOMALOUS_OFFSET] = anomalous ? (byte) 1 : (byte) 0; + + ResponseAPDU respGather = cardManager.sendAPDU(gatherKeyAPDU); + if (respGather.getSW() != ISO7816.SW_NO_ERROR) { + systemOutLogger.println(String.format("Card error: %x", respGather.getSW())); + keysFile.close(); + return; + } + + // Generate new keypair + gatherKeyAPDU[ISO7816.OFFSET_P1] = SimpleECCApplet.P1_GENERATEKEYPAIR; + int counter = 0; + while (true) { + counter++; + long elapsed = -System.nanoTime(); + respGather = cardManager.sendAPDU(gatherKeyAPDU); + elapsed += System.nanoTime(); + + if (respGather.getSW() != ISO7816.SW_NO_ERROR) { + systemOutLogger.println(String.format("Card error: %x", respGather.getSW())); + break; + } + byte[] data = respGather.getData(); + int offset = 0; + String pubKeyW = ""; + String privKeyS = ""; + if (data[offset] == EC_Consts.TAG_ECPUBKEY) { + offset++; + short len = getShort(data, offset); + offset += 2; + pubKeyW = Util.bytesToHex(data, offset, len, false); + offset += len; + } + if (data[offset] == EC_Consts.TAG_ECPRIVKEY) { + offset++; + short len = getShort(data, offset); + offset += 2; + privKeyS = Util.bytesToHex(data, offset, len, false); + offset += len; + } + + message = String.format("%d;%d;%s;%s\n", counter, elapsed / 1000000, pubKeyW, privKeyS); + keysFile.write(message.getBytes()); + + this.systemOutLogger.flush(); + keysFile.flush(); + + //stop when we have enough keys, go on forever with 0 + if (counter >= amount && amount != 0) + break; + } + keysFile.close(); + } + } + + private static String getPrintError(short code) { + if (code == ISO7816.SW_NO_ERROR) { + return "OK\t(0x9000)"; + } else { + String codeStr = "unknown"; + if (code == CryptoException.ILLEGAL_VALUE) { + codeStr = "ILLEGAL_VALUE"; + } + if (code == CryptoException.UNINITIALIZED_KEY) { + codeStr = "UNINITIALIZED_KEY"; + } + if (code == CryptoException.NO_SUCH_ALGORITHM) { + codeStr = "NO_SUCH_ALG"; + } + if (code == CryptoException.INVALID_INIT) { + codeStr = "INVALID_INIT"; + } + if (code == CryptoException.ILLEGAL_USE) { + codeStr = "ILLEGAL_USE"; + } + if (code == SimpleECCApplet.SW_SKIPPED) { + codeStr = "skipped"; + } + if (code == SimpleECCApplet.SW_KEYPAIR_GENERATED_INVALID) { + codeStr = "SW_KEYPAIR_GENERATED_INVALID"; + } + if (code == SimpleECCApplet.SW_INVALID_CORRUPTION_TYPE) { + codeStr = "SW_INVALID_CORRUPTION_TYPE"; + } + if (code == SimpleECCApplet.SW_SIG_VERIFY_FAIL) { + codeStr = "SW_SIG_VERIFY_FAIL"; + } + return String.format("fail\t(%s,\t0x%4x)", codeStr, code); + } + } + + enum ExpResult { + SHOULD_SUCCEED, + MAY_FAIL, + MUST_FAIL + } + + private int VerifyPrintResult(String message, byte expectedTag, byte[] buffer, int bufferOffset, ExpResult expRes) { + if (bufferOffset >= buffer.length) { + systemOutLogger.println(" No more data returned"); + } else { + if (buffer[bufferOffset] != expectedTag) { + systemOutLogger.println(" ERROR: mismatched tag"); + assert (buffer[bufferOffset] == expectedTag); + } + bufferOffset++; + short resCode = getShort(buffer, bufferOffset); + bufferOffset += 2; + + boolean bHiglight = false; + if ((expRes == ExpResult.MUST_FAIL) && (resCode == ISO7816.SW_NO_ERROR)) { + bHiglight = true; + } + if ((expRes == ExpResult.SHOULD_SUCCEED) && (resCode != ISO7816.SW_NO_ERROR)) { + bHiglight = true; + } + if (bHiglight) { + systemOutLogger.println(String.format("!! %-53s%s", message, getPrintError(resCode))); + } else { + systemOutLogger.println(String.format(" %-53s%s", message, getPrintError(resCode))); + } + } + return bufferOffset; + } + + private void PrintECSupport(ResponseAPDU resp) { + PrintECSupport(resp.getData()); + } + + private void PrintECSupport(byte[] buffer) { + systemOutLogger.println(); + systemOutLogger.println("### Test for support and with valid and invalid EC curves"); + int bufferOffset = 0; + while (bufferOffset < buffer.length) { + assert (buffer[bufferOffset] == SimpleECCApplet.ECTEST_SEPARATOR); + bufferOffset++; + String ecType = "unknown"; + if (buffer[bufferOffset] == KeyPair.ALG_EC_FP) { + ecType = "ALG_EC_FP"; + } + if (buffer[bufferOffset] == KeyPair.ALG_EC_F2M) { + ecType = "ALG_EC_F2M"; + } + systemOutLogger.println(String.format("%-56s%s", "EC type:", ecType)); + bufferOffset++; + short keyLen = getShort(buffer, bufferOffset); + systemOutLogger.println(String.format("%-56s%d bits", "EC key length (bits):", keyLen)); + bufferOffset += 2; + + bufferOffset = VerifyPrintResult("KeyPair object allocation:", SimpleECCApplet.ECTEST_ALLOCATE_KEYPAIR, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + bufferOffset = VerifyPrintResult("Generate key with def curve (fails if no def):", SimpleECCApplet.ECTEST_GENERATE_KEYPAIR_DEFCURVE, buffer, bufferOffset, ExpResult.MAY_FAIL); + bufferOffset = VerifyPrintResult("Set valid custom curve:", SimpleECCApplet.ECTEST_SET_VALIDCURVE, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + bufferOffset = VerifyPrintResult("Generate key with valid curve:", SimpleECCApplet.ECTEST_GENERATE_KEYPAIR_CUSTOMCURVE, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + bufferOffset = VerifyPrintResult("ECDH agreement with valid point:", SimpleECCApplet.ECTEST_ECDH_AGREEMENT_VALID_POINT, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + bufferOffset = VerifyPrintResult("ECDH agreement with invalid point (fail is good):", SimpleECCApplet.ECTEST_ECDH_AGREEMENT_INVALID_POINT, buffer, bufferOffset, ExpResult.MUST_FAIL); + bufferOffset = VerifyPrintResult("ECDSA signature on random data:", SimpleECCApplet.ECTEST_ECDSA_SIGNATURE, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + bufferOffset = VerifyPrintResult("Set anomalous custom curve (may fail):", SimpleECCApplet.ECTEST_SET_ANOMALOUSCURVE, buffer, bufferOffset, ExpResult.MAY_FAIL); + bufferOffset = VerifyPrintResult("Generate key with anomalous curve (may fail):", SimpleECCApplet.ECTEST_GENERATE_KEYPAIR_ANOMALOUSCURVE, buffer, bufferOffset, ExpResult.MAY_FAIL); + bufferOffset = VerifyPrintResult("ECDH agreement with small order point (fail is good):", SimpleECCApplet.ECTEST_ECDH_AGREEMENT_SMALL_DEGREE_POINT, buffer, bufferOffset, ExpResult.MUST_FAIL); + bufferOffset = VerifyPrintResult("Set invalid custom curve (may fail):", SimpleECCApplet.ECTEST_SET_INVALIDCURVE, buffer, bufferOffset, ExpResult.MAY_FAIL); + bufferOffset = VerifyPrintResult("Generate key with invalid curve (fail is good):", SimpleECCApplet.ECTEST_GENERATE_KEYPAIR_INVALIDCUSTOMCURVE, buffer, bufferOffset, ExpResult.MUST_FAIL); + bufferOffset = VerifyPrintResult("Set invalid field (may fail):", SimpleECCApplet.ECTEST_SET_INVALIDFIELD, buffer, bufferOffset, ExpResult.MAY_FAIL); + bufferOffset = VerifyPrintResult("Generate key with invalid field (fail si good):", SimpleECCApplet.ECTEST_GENERATE_KEYPAIR_INVALIDFIELD, buffer, bufferOffset, ExpResult.MUST_FAIL); + + systemOutLogger.println(); + } + } + + private void PrintECKeyGenInvalidCurveB(ResponseAPDU resp) { + PrintECKeyGenInvalidCurveB(resp.getData()); + } + + private void PrintECKeyGenInvalidCurveB(byte[] buffer) { + systemOutLogger.println(); + systemOutLogger.println("### Test for computation with invalid parameter B for EC curve"); + int bufferOffset = 0; + while (bufferOffset < buffer.length) { + assert (buffer[bufferOffset] == SimpleECCApplet.ECTEST_SEPARATOR); + bufferOffset++; + String ecType = "unknown"; + if (buffer[bufferOffset] == KeyPair.ALG_EC_FP) { + ecType = "ALG_EC_FP"; + } + if (buffer[bufferOffset] == KeyPair.ALG_EC_F2M) { + ecType = "ALG_EC_F2M"; + } + systemOutLogger.println(String.format("%-53s%s", "EC type:", ecType)); + bufferOffset++; + short keyLen = getShort(buffer, bufferOffset); + systemOutLogger.println(String.format("%-53s%d bits", "EC key length (bits):", keyLen)); + bufferOffset += 2; + + short numRepeats = getShort(buffer, bufferOffset); + bufferOffset += 2; + systemOutLogger.println(String.format("%-53s%d times", "Executed repeats before unexpected error: ", numRepeats)); + + bufferOffset = VerifyPrintResult("KeyPair object allocation:", SimpleECCApplet.ECTEST_ALLOCATE_KEYPAIR, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + while (bufferOffset < buffer.length) { + bufferOffset = VerifyPrintResult("Set invalid custom curve:", SimpleECCApplet.ECTEST_SET_INVALIDCURVE, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + bufferOffset = VerifyPrintResult("Generate key with invalid curve (fail is good):", SimpleECCApplet.ECTEST_GENERATE_KEYPAIR_INVALIDCUSTOMCURVE, buffer, bufferOffset, ExpResult.MUST_FAIL); + if (buffer[bufferOffset] == SimpleECCApplet.ECTEST_DH_GENERATESECRET) { + bufferOffset = VerifyPrintResult("ECDH agreement with invalid point (fail is good):", SimpleECCApplet.ECTEST_DH_GENERATESECRET, buffer, bufferOffset, ExpResult.MUST_FAIL); + } + bufferOffset = VerifyPrintResult("Set valid custom curve:", SimpleECCApplet.ECTEST_SET_VALIDCURVE, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + bufferOffset = VerifyPrintResult("Generate key with valid curve:", SimpleECCApplet.ECTEST_GENERATE_KEYPAIR_CUSTOMCURVE, buffer, bufferOffset, ExpResult.SHOULD_SUCCEED); + } + + systemOutLogger.println(); + } + } + + private void PrintECKeyGenInvalidCurveB_lastUserParams(ResponseAPDU resp) { + byte[] buffer = resp.getData(); + short offset = 0; + systemOutLogger.print("Last used value of B: "); + while (offset < buffer.length) { + systemOutLogger.print(String.format("%x ", buffer[offset])); + offset++; + } + } + + public static void main(String[] args) throws IOException { + SimpleAPDU app = new SimpleAPDU(); + app.run(args); + } +} diff --git a/src/cz/crcs/ectester/reader/Util.java b/src/cz/crcs/ectester/reader/Util.java new file mode 100644 index 0000000..7af6e9c --- /dev/null +++ b/src/cz/crcs/ectester/reader/Util.java @@ -0,0 +1,82 @@ +package cz.crcs.ectester.reader; + +/** + * @author Petr Svenda petr@svenda.com + * @author Jan Jancar johny@neuromancer.sk + */ +public class Util { + + public static short getShort(byte[] array, int offset) { + return (short) (((array[offset] & 0xFF) << 8) | (array[offset + 1] & 0xFF)); + } + + public static void setShort(byte[] array, int offset, short value) { + array[offset + 1] = (byte) (value & 0xFF); + array[offset] = (byte) ((value >> 8) & 0xFF); + } + + public static byte[] hexToBytes(String hex) { + return hexToBytes(hex, true); + } + + public static byte[] hexToBytes(String hex, boolean bigEndian) { + StringBuilder sb = new StringBuilder(hex.replace(" ", "")); + if (!bigEndian) { + sb.reverse(); + } + int len = sb.length(); + if (len % 2 == 1) { + sb.insert(0, "0"); + ++len; + } + + String data = sb.toString(); + byte[] result = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + result[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4) + + (Character.digit(data.charAt(i + 1), 16))); + } + return result; + } + + public static String byteToHex(byte data) { + return String.format("%02x", data); + } + + public static String bytesToHex(byte[] data) { + return bytesToHex(data, 0, data.length, true); + } + + public static String bytesToHex(byte[] data, int offset, int len) { + return bytesToHex(data, offset, len, true); + } + + public static String bytesToHex(byte[] data, int offset, int len, boolean bAddSpace) { + StringBuilder buf = new StringBuilder(); + for (int i = offset; i < (offset + len); i++) { + buf.append(byteToHex(data[i])); + if (bAddSpace && i != (offset + len - 1)) { + buf.append(" "); + } + } + return (buf.toString()); + } + + public static byte[] concatenate(byte[]... arrays) { + int len = 0; + for (byte[] array : arrays) { + if (array == null) + continue; + len += array.length; + } + byte[] out = new byte[len]; + int offset = 0; + for (byte[] array : arrays) { + if (array == null) + continue; + System.arraycopy(array, 0, out, offset, array.length); + offset += array.length; + } + return out; + } +} |
