diff options
| author | petrs | 2017-06-08 14:56:23 +0200 |
|---|---|---|
| committer | GitHub | 2017-06-08 14:56:23 +0200 |
| commit | 359b4bb5be1a822e389e54b9697504f4f0b43d34 (patch) | |
| tree | 771dac996ecab9e676a0b1b3c6d14692c5dad8a9 /src/cz/crcs/ectester/reader | |
| parent | 7cf4a9d7272c1abd97a160c92ec1534d5ff82373 (diff) | |
| parent | 3f1483635615a0c599ae2e75858be4e39eb08a64 (diff) | |
| download | ECTester-359b4bb5be1a822e389e54b9697504f4f0b43d34.tar.gz ECTester-359b4bb5be1a822e389e54b9697504f4f0b43d34.tar.zst ECTester-359b4bb5be1a822e389e54b9697504f4f0b43d34.zip | |
Merge pull request #1 from petrs/devel
Merge current dev version
Diffstat (limited to 'src/cz/crcs/ectester/reader')
| -rw-r--r-- | src/cz/crcs/ectester/reader/CardMngr.java | 313 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/Command.java | 551 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/DirtyLogger.java | 56 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ECTester.java | 788 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ISO7816_status_words.txt | 71 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/Response.java | 574 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/Test.java | 82 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/TestSuite.java | 314 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/Util.java | 342 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ec/EC_Category.java | 140 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ec/EC_Curve.java | 50 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ec/EC_Data.java | 196 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ec/EC_KAResult.java | 61 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ec/EC_Key.java | 72 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ec/EC_Keypair.java | 34 | ||||
| -rw-r--r-- | src/cz/crcs/ectester/reader/ec/EC_Params.java | 146 |
16 files changed, 3790 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..e11bcb3 --- /dev/null +++ b/src/cz/crcs/ectester/reader/CardMngr.java @@ -0,0 +1,313 @@ +package cz.crcs.ectester.reader; + +import com.licel.jcardsim.io.CAD; +import com.licel.jcardsim.io.JavaxSmartCardInterface; +import javacard.framework.AID; + +import javax.smartcardio.*; +import java.util.List; +import java.util.Scanner; + +/** + * @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 CAD cad = null; + private JavaxSmartCardInterface simulator = null; + + private boolean simulate = false; + private boolean verbose = true; + + 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; + } + + 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()) { + card = terminal.connect("*"); + if (verbose) + System.out.println("card: " + card); + channel = card.getBasicChannel(); + + //reset the card + if (verbose) + System.out.println(Util.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() + " - " + 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 + terminal = terminalList.get(answ); + } + } + + if (terminal != null) { + card = terminal.connect("*"); + 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 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.err.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); + + if (verbose) + 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.err.println("Exception : " + ex); + return null; + } + } + + public ResponseAPDU sendAPDU(CommandAPDU apdu) throws CardException { + if (verbose) { + System.out.println(">>>>"); + System.out.println(apdu); + + System.out.println(Util.bytesToHex(apdu.getBytes())); + } + + long elapsed = -System.nanoTime(); + + ResponseAPDU responseAPDU = channel.transmit(apdu); + + elapsed += System.nanoTime(); + + if (verbose) { + 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 = channel.transmit(apduToSend); + if (verbose) + System.out.println(Util.bytesToHex(responseAPDU.getBytes())); + } + + if (verbose) { + 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"); + cad = new CAD(System.getProperties()); + simulator = (JavaxSmartCardInterface) cad.getCardInterface(); + AID appletAID = new AID(appletAIDArray, (short) 0, (byte) appletAIDArray.length); + + AID appletAIDRes = simulator.installApplet(appletAID, appletClass, installData, (short) 0, (byte) installData.length); + return simulator.selectApplet(appletAID); + } + + public ResponseAPDU sendAPDUSimulator(CommandAPDU apdu) { + if (verbose) { + System.out.println(">>>>"); + System.out.println(apdu); + System.out.println(Util.bytesToHex(apdu.getBytes())); + } + + ResponseAPDU response = simulator.transmitCommand(apdu); + byte[] responseBytes = response.getBytes(); + + if (verbose) { + System.out.println(response); + 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/Command.java b/src/cz/crcs/ectester/reader/Command.java new file mode 100644 index 0000000..5b7be01 --- /dev/null +++ b/src/cz/crcs/ectester/reader/Command.java @@ -0,0 +1,551 @@ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.data.EC_Store; +import cz.crcs.ectester.reader.ec.EC_Curve; +import cz.crcs.ectester.reader.ec.EC_Key; +import cz.crcs.ectester.reader.ec.EC_Keypair; +import cz.crcs.ectester.reader.ec.EC_Params; +import 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 { + CommandAPDU cmd; + CardMngr cardManager; + + 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; + } + + + /** + * @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, EC_Store dataStore, ECTester.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); + } else if (cfg.namedCurve != null) { + // Set a named curve. + // parse cfg.namedCurve -> cat / id | cat | id + EC_Curve curve = dataStore.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."); + } + + byte[] external = curve.flatten(); + if (external == null) { + throw new IOException("Couldn't read named curve data."); + } + return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, curve.getParams(), external); + } else if (cfg.curveFile != null) { + // Set curve loaded from a file + EC_Curve curve = new EC_Curve(null, keyLength, keyClass); + + FileInputStream in = new FileInputStream(cfg.curveFile); + curve.readCSV(in); + in.close(); + + byte[] external = curve.flatten(); + if (external == null) { + throw new IOException("Couldn't read the curve file correctly."); + } + return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, curve.getParams(), external); + } else { + // Set default curve + /* This command was generally causing problems for simulating on jcardsim. + * Since there, .clearKey() resets all the keys values, even the domain. + * This might break some other stuff.. But should not. + */ + //commands.add(new Command.Clear(cardManager, keyPair)); + return null; + } + } + + + /** + * @param keyPair which keyPair/s to set the key params on + * @return a CommandAPDU setting params loaded on the keyPair/s + * @throws IOException if any of the key files cannot be found/opened + */ + public static Command prepareKey(CardMngr cardManager, EC_Store dataStore, ECTester.Config cfg, byte keyPair) throws IOException { + short params = EC_Consts.PARAMETERS_NONE; + byte[] data = null; + + if (cfg.key != null || cfg.namedKey != null) { + params |= EC_Consts.PARAMETERS_KEYPAIR; + EC_Params keypair; + if (cfg.key != null) { + keypair = new EC_Params(EC_Consts.PARAMETERS_KEYPAIR); + + FileInputStream in = new FileInputStream(cfg.key); + keypair.readCSV(in); + in.close(); + } else { + keypair = dataStore.getObject(EC_Keypair.class, cfg.namedKey); + } + + data = keypair.flatten(); + if (data == null) { + throw new IOException("Couldn't read the key file correctly."); + } + } + + if (cfg.publicKey != null || cfg.namedPublicKey != null) { + params |= EC_Consts.PARAMETER_W; + EC_Params pub; + if (cfg.publicKey != null) { + pub = new EC_Params(EC_Consts.PARAMETER_W); + + FileInputStream in = new FileInputStream(cfg.publicKey); + pub.readCSV(in); + in.close(); + } else { + pub = dataStore.getObject(EC_Key.Public.class, cfg.namedPublicKey); + if (pub == null) { + pub = dataStore.getObject(EC_Keypair.class, cfg.namedPublicKey); + } + } + + 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) { + params |= EC_Consts.PARAMETER_S; + EC_Params priv; + if (cfg.privateKey != null) { + priv = new EC_Params(EC_Consts.PARAMETER_S); + + FileInputStream in = new FileInputStream(cfg.privateKey); + priv.readCSV(in); + in.close(); + } else { + priv = dataStore.getObject(EC_Key.Public.class, cfg.namedPrivateKey); + if (priv == null) { + priv = dataStore.getObject(EC_Keypair.class, cfg.namedPrivateKey); + } + } + + byte[] privkey = priv.flatten(EC_Consts.PARAMETER_S); + if (privkey == null) { + throw new IOException("Couldn't read the private key file correctly."); + } + data = Util.concatenate(data, privkey); + } + return new Command.Set(cardManager, keyPair, EC_Consts.CURVE_external, params, data); + } + + + /** + * + */ + public static class Allocate extends Command { + private byte keyPair; + 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 keyLength key length to set + * @param keyClass key class to allocate + */ + protected Allocate(CardMngr cardManager, byte keyPair, short keyLength, byte keyClass) { + super(cardManager); + this.keyPair = keyPair; + this.keyLength = keyLength; + this.keyClass = keyClass; + + byte[] data = new byte[]{0, 0, keyClass}; + Util.setShort(data, 0, keyLength); + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_ALLOCATE, keyPair, 0x00, data); + } + + @Override + public Response.Allocate send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Allocate(response, elapsed, keyPair, keyLength, keyClass); + } + } + + /** + * + */ + public static class Clear extends Command { + private byte keyPair; + + /** + * @param cardManager cardManager to send APDU through + * @param keyPair which keyPair clear, local/remote (KEYPAIR_* || ...) + */ + protected Clear(CardMngr cardManager, byte keyPair) { + super(cardManager); + this.keyPair = keyPair; + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_CLEAR, keyPair, 0x00); + } + + @Override + public Response.Clear send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Clear(response, elapsed, keyPair); + } + } + + /** + * + */ + 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 + */ + protected 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]; + Util.setShort(data, 0, params); + if (external != null) { + System.arraycopy(external, 0, data, 2, external.length); + } + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.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, elapsed, keyPair, curve, params); + } + } + + /** + * + */ + public static class Corrupt extends Command { + private byte keyPair; + private byte key; + private short params; + private byte corruption; + + /** + * @param cardManager cardManager to send APDU through + * @param keyPair which keyPair to corrupt, local/remote (KEYPAIR_* || ...) + * @param key key to corrupt (EC_Consts.KEY_* | ...) + * @param params parameters to corrupt (EC_Consts.PARAMETER_* | ...) + * @param corruption corruption type (EC_Consts.CORRUPTION_*) + */ + protected Corrupt(CardMngr cardManager, byte keyPair, byte key, short params, byte corruption) { + super(cardManager); + this.keyPair = keyPair; + this.key = key; + this.params = params; + this.corruption = corruption; + + byte[] data = new byte[3]; + Util.setShort(data, 0, params); + data[2] = corruption; + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_CORRUPT, keyPair, key, data); + } + + @Override + public Response.Corrupt send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Corrupt(response, elapsed, keyPair, key, params, corruption); + } + } + + /** + * + */ + 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_* || ...) + */ + protected Generate(CardMngr cardManager, byte keyPair) { + super(cardManager); + this.keyPair = keyPair; + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_GENERATE, keyPair, 0); + } + + @Override + public Response.Generate send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Generate(response, elapsed, keyPair); + } + } + + /** + * + */ + 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_* | ...) + */ + protected 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]; + Util.setShort(data, 0, params); + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.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, elapsed, keyPair, key, params); + } + } + + /** + * + */ + public static class ECDH extends Command { + private byte pubkey; + private byte privkey; + private byte export; + private short corruption; + 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 corruption whether to invalidate the pubkey before ECDH (EC_Consts.CORRUPTION_* | ...) + * @param type ECDH algorithm type (EC_Consts.KA_* | ...) + */ + protected ECDH(CardMngr cardManager, byte pubkey, byte privkey, byte export, short corruption, byte type) { + super(cardManager); + this.pubkey = pubkey; + this.privkey = privkey; + this.export = export; + this.corruption = corruption; + this.type = type; + + byte[] data = new byte[]{export, 0,0, type}; + Util.setShort(data, 1, corruption); + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.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, elapsed, pubkey, privkey, export, corruption, type); + } + } + + /** + * + */ + public static class ECDH_direct extends Command { + private byte privkey; + private byte export; + private short corruption; + private byte type; + private byte[] pubkey; + + protected ECDH_direct(CardMngr cardManager, byte privkey, byte export, short corruption, byte type, byte[] pubkey) { + super(cardManager); + this.privkey = privkey; + this.export = export; + this.corruption = corruption; + this.type = type; + this.pubkey = pubkey; + + byte[] data = new byte[3 + pubkey.length]; + Util.setShort(data, 0, corruption); + data[2] = type; + System.arraycopy(pubkey, 0, data, 3, pubkey.length); + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.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, elapsed, ECTesterApplet.KEYPAIR_REMOTE, privkey, export, corruption, type); + } + } + + public static class ECDSA extends Command { + private byte keyPair; + 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 export whether to export ECDSA signature + * @param raw data to sign, can be null, in which case random data is signed. + */ + protected ECDSA(CardMngr cardManager, byte keyPair, byte export, byte[] raw) { + super(cardManager); + this.keyPair = keyPair; + this.export = export; + this.raw = raw; + + 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); + } + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.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, elapsed, keyPair, export, raw); + } + } + + /** + * + */ + public static class Cleanup extends Command { + + /** + * @param cardManager cardManager to send APDU through + */ + protected Cleanup(CardMngr cardManager) { + super(cardManager); + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_CLEANUP, 0, 0); + } + + @Override + public Response.Cleanup send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Cleanup(response, elapsed); + } + } + + /** + * + */ + public static class Support extends Command { + + /** + * @param cardManager cardManager to send APDU through + */ + protected Support(CardMngr cardManager) { + super(cardManager); + + this.cmd = new CommandAPDU(ECTesterApplet.CLA_ECTESTERAPPLET, ECTesterApplet.INS_SUPPORT, 0, 0); + } + + @Override + public Response.Support send() throws CardException { + long elapsed = -System.nanoTime(); + ResponseAPDU response = cardManager.send(cmd); + elapsed += System.nanoTime(); + return new Response.Support(response, elapsed); + } + } +} + diff --git a/src/cz/crcs/ectester/reader/DirtyLogger.java b/src/cz/crcs/ectester/reader/DirtyLogger.java new file mode 100644 index 0000000..7a2c70d --- /dev/null +++ b/src/cz/crcs/ectester/reader/DirtyLogger.java @@ -0,0 +1,56 @@ +package cz.crcs.ectester.reader; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; + +/** + * @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..3b5b84f --- /dev/null +++ b/src/cz/crcs/ectester/reader/ECTester.java @@ -0,0 +1,788 @@ +/* + * Copyright (c) 2016-2017 Petr Svenda <petr@svenda.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.data.EC_Store; +import cz.crcs.ectester.reader.ec.EC_Category; +import cz.crcs.ectester.reader.ec.EC_Data; +import cz.crcs.ectester.reader.ec.EC_Params; +import javacard.security.KeyPair; +import org.apache.commons.cli.*; + +import javax.smartcardio.CardException; +import java.io.*; +import java.nio.file.Files; +import java.util.*; + +/** + * 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 + */ +public class ECTester { + + private CardMngr cardManager; + private DirtyLogger systemOutLogger; + private EC_Store dataStore; + private Config cfg; + + private Options opts = new Options(); + private static final String CLI_HEADER = "\nECTester, a javacard Elliptic Curve Cryptograhy support tester/utility.\n\n"; + private static final String CLI_FOOTER = "\nMIT Licensed\nCopyright (c) 2016-2017 Petr Svenda <petr@svenda.com>"; + + private static final byte[] SELECT_ECTESTERAPPLET = {(byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0a, + (byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31}; + private static final byte[] AID = {(byte) 0x45, (byte) 0x43, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x30, (byte) 0x31}; + private static final byte[] INSTALL_DATA = new byte[10]; + + private void run(String[] args) { + try { + CommandLine cli = parseArgs(args); + + //if help, print and quit + if (cli.hasOption("help")) { + help(); + return; + } + cfg = new Config(); + + //if not, read other options first, into attributes, then do action + if (!cfg.readOptions(cli)) { + return; + } + + dataStore = new EC_Store(); + //if list, print and quit + if (cli.hasOption("list-named")) { + list(); + return; + } + + //init CardManager + cardManager = new CardMngr(cfg.verbose, cfg.simulate); + + //connect or simulate connection + if (cfg.simulate) { + if (!cardManager.prepareLocalSimulatorApplet(AID, INSTALL_DATA, ECTesterApplet.class)) { + System.err.println("Failed to establish a simulator."); + System.exit(1); + } + } else { + if (!cardManager.connectToCardSelect()) { + System.err.println("Failed to connect to card."); + System.exit(1); + } + cardManager.send(SELECT_ECTESTERAPPLET); + } + + systemOutLogger = new DirtyLogger(cfg.log, 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(); + } + + //disconnect + 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.print("-" + o.getOpt()); + + if (o.hasLongOpt()) { + System.err.print("\t/ --" + o.getLongOpt() + " "); + } + + if (o.hasArg()) { + if (o.hasOptionalArg()) { + System.err.print("[" + o.getArgName() + "] "); + } else { + System.err.print("<" + o.getArgName() + "> "); + } + } + + if (o.getDescription() != null) { + System.err.print("\t\t\t" + o.getDescription()); + } + System.err.println(); + } + } else if (opt instanceof String) { + System.err.println(opt); + } + } + } catch (MissingArgumentException maex) { + System.err.println("Option, " + maex.getOption().getOpt() + " requires an argument: " + maex.getOption().getArgName()); + } catch (NumberFormatException nfex) { + System.err.println("Not a number. " + nfex.getMessage()); + } catch (FileNotFoundException fnfe) { + System.err.println("File " + fnfe.getMessage() + " not found."); + } catch (ParseException | IOException ex) { + System.err.println(ex.getMessage()); + } catch (CardException ex) { + if (systemOutLogger != null) + systemOutLogger.println(ex.getMessage()); + } finally { + if (systemOutLogger != null) + systemOutLogger.flush(); + } + } + + /** + * Parses command-line options. + * + * @param args cli arguments + * @return parsed CommandLine object + * @throws ParseException if there are any problems encountered while parsing the command line tokens + */ + private CommandLine parseArgs(String[] args) throws ParseException { + /* + * Actions: + * -h / --help + * -e / --export + * -g / --generate [amount] + * -t / --test [test_suite] + * -dh / --ecdh [count] + * -dhc / --ecdhc [count] + * -dsa / --ecdsa [count] + * -ln / --list-named [obj] + * + * Options: + * -b / --bit-size <b> // -a / --all + * + * -fp / --prime-field + * -f2m / --binary-field + * + * -u / --custom + * -nc / --named-curve <cat/id> + * -c / --curve <curve_file> field,a,b,gx,gy,r,k + * + * -pub / --public <pubkey_file> wx,wy + * -npub / --named-public <cat/id> + * + * -priv / --private <privkey_file> s + * -npriv / --named-private <cat/id> + * + * -k / --key <key_file> wx,wy,s + * -nk / --named-key <cat/id> + * + * -v / --verbose + * + * -i / --input <input_file> + * -o / --output <output_file> + * -l / --log [log_file] + * + * -f / --fresh + * -s / --simulate + * -y / --yes + */ + OptionGroup actions = new OptionGroup(); + actions.setRequired(true); + actions.addOption(Option.builder("h").longOpt("help").desc("Print help.").build()); + actions.addOption(Option.builder("ln").longOpt("list-named").desc("Print the list of supported named curves and keys.").hasArg().argName("what").optionalArg(true).build()); + actions.addOption(Option.builder("e").longOpt("export").desc("Export the defaut curve parameters of the card(if any).").build()); + actions.addOption(Option.builder("g").longOpt("generate").desc("Generate [amount] of EC keys.").hasArg().argName("amount").optionalArg(true).build()); + actions.addOption(Option.builder("t").longOpt("test").desc("Test ECC support. [test_suite]:\n- default:\n- invalid:\n- wrong:\n- nonprime:\n- test-vectors:").hasArg().argName("test_suite").optionalArg(true).build()); + actions.addOption(Option.builder("dh").longOpt("ecdh").desc("Do ECDH, [count] times.").hasArg().argName("count").optionalArg(true).build()); + actions.addOption(Option.builder("dhc").longOpt("ecdhc").desc("Do ECDHC, [count] times.").hasArg().argName("count").optionalArg(true).build()); + actions.addOption(Option.builder("dsa").longOpt("ecdsa").desc("Sign data with ECDSA, [count] times.").hasArg().argName("count").optionalArg(true).build()); + opts.addOptionGroup(actions); + + OptionGroup size = new OptionGroup(); + size.addOption(Option.builder("b").longOpt("bit-size").desc("Set curve size.").hasArg().argName("bits").build()); + size.addOption(Option.builder("a").longOpt("all").desc("Test all curve sizes.").build()); + opts.addOptionGroup(size); + + opts.addOption(Option.builder("fp").longOpt("prime-field").desc("Use a prime field.").build()); + opts.addOption(Option.builder("f2m").longOpt("binary-field").desc("Use a binary field.").build()); + + OptionGroup curve = new OptionGroup(); + curve.addOption(Option.builder("nc").longOpt("named-curve").desc("Use a named curve, from CurveDB: <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>.").hasArg().argName("output_file").build()); + opts.addOption(Option.builder("l").longOpt("log").desc("Log output into file [log_file].").hasArg().argName("log_file").optionalArg(true).build()); + opts.addOption(Option.builder("v").longOpt("verbose").desc("Turn on verbose logging.").build()); + + opts.addOption(Option.builder("f").longOpt("fresh").desc("Generate fresh keys (set domain parameters before every generation).").build()); + opts.addOption(Option.builder("s").longOpt("simulate").desc("Simulate a card with jcardsim instead of using a terminal.").build()); + opts.addOption(Option.builder("y").longOpt("yes").desc("Accept all warnings and prompts.").build()); + + CommandLineParser parser = new DefaultParser(); + return parser.parse(opts, args); + } + + /** + * Prints help. + */ + private void help() { + HelpFormatter help = new HelpFormatter(); + help.setOptionComparator(null); + help.printHelp("ECTester.jar", CLI_HEADER, opts, CLI_FOOTER, true); + } + + /** + * List categories and named curves. + */ + private void list() { + Map<String, EC_Category> categories = dataStore.getCategories(); + if (cfg.listNamed == null) { + // print all categories, briefly + for (EC_Category cat : categories.values()) { + System.out.println(cat); + } + } else if (categories.containsKey(cfg.listNamed)) { + // print given category + System.out.println(categories.get(cfg.listNamed)); + } else { + // print given object + EC_Data object = dataStore.getObject(EC_Data.class, cfg.listNamed); + if (object != null) { + System.out.println(object); + } else { + System.err.println("Named object " + cfg.listNamed + " not found!"); + } + } + } + + /** + * Exports default card/simulation EC domain parameters to output file. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void export() throws CardException, IOException { + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + + List<Response> sent = new LinkedList<>(); + sent.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send()); + sent.add(new Command.Clear(cardManager, ECTesterApplet.KEYPAIR_LOCAL).send()); + sent.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL).send()); + + // Cofactor generally isn't set on the default curve parameters on cards, + // since its not necessary for ECDH, only ECDHC which not many cards implement + // TODO: check if its assumend to be == 1? + short domainAll = cfg.primeField ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M; + short domain = (short) (domainAll ^ EC_Consts.PARAMETER_K); + Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domainAll).send(); + if (!export.successful()) { + export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_PUBLIC, domain).send(); + } + sent.add(export); + + systemOutLogger.println(Response.toString(sent)); + + EC_Params exported = new EC_Params(domain, export.getParams()); + + FileOutputStream out = new FileOutputStream(cfg.output); + exported.writeCSV(out); + out.close(); + } + + /** + * Generates EC keyPairs and outputs them to output file. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void generate() throws CardException, IOException { + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + + new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send(); + Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); + + FileWriter keysFile = new FileWriter(cfg.output); + keysFile.write("index;time;pubW;privS\n"); + + int generated = 0; + int retry = 0; + while (generated < cfg.generateAmount || cfg.generateAmount == 0) { + if ((cfg.fresh || generated == 0) && curve != null) { + Response fresh = curve.send(); + systemOutLogger.println(fresh.toString()); + } + + Command.Generate generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL); + Response.Generate response = generate.send(); + long elapsed = response.getDuration(); + + Response.Export export = new Command.Export(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.KEY_BOTH, EC_Consts.PARAMETERS_KEYPAIR).send(); + + if (!response.successful() || !export.successful()) { + if (retry < 10) { + retry++; + continue; + } else { + System.err.println("Keys could not be generated."); + break; + } + } + systemOutLogger.println(response.toString()); + + String pub = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_W), false); + String priv = Util.bytesToHex(export.getParameter(ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.PARAMETER_S), false); + String line = String.format("%d;%d;%s;%s\n", generated, elapsed / 1000000, pub, priv); + keysFile.write(line); + keysFile.flush(); + generated++; + } + Response cleanup = new Command.Cleanup(cardManager).send(); + systemOutLogger.println(cleanup.toString()); + + keysFile.close(); + } + + /** + * Tests Elliptic curve support for a given curve/curves. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void test() throws IOException, CardException { + TestSuite suite; + + switch (cfg.testSuite) { + case "default": + suite = new TestSuite.Default(dataStore, cfg, systemOutLogger); + break; + case "test-vectors": + suite = new TestSuite.TestVectors(dataStore, cfg, systemOutLogger); + break; + default: + // These tests are dangerous, prompt before them. + System.out.println("The test you selected (" + cfg.testSuite + ") is potentially dangerous."); + System.out.println("Some of these tests have caused temporary DoS of some cards."); + if (!cfg.yes) { + System.out.print("Do you want to proceed? (y/n): "); + Scanner in = new Scanner(System.in); + String confirmation = in.nextLine(); + if (!Arrays.asList("yes", "YES", "y", "Y").contains(confirmation)) { + return; + } + in.close(); + } + + + switch (cfg.testSuite) { + case "wrong": + suite = new TestSuite.Wrong(dataStore, cfg, systemOutLogger); + break; + case "nonprime": + suite = new TestSuite.NonPrime(dataStore, cfg, systemOutLogger); + break; + case "invalid": + suite = new TestSuite.Invalid(dataStore, cfg, systemOutLogger); + break; + default: + System.err.println("Unknown test suite."); + return; + } + break; + } + suite.run(cardManager); + } + + /** + * Performs ECDH key exchange. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void ecdh() throws IOException, CardException { + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + List<Response> prepare = new LinkedList<>(); + prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass).send()); + Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, (short) cfg.bits, keyClass); + if (curve != null) + prepare.add(curve.send()); + + systemOutLogger.println(Response.toString(prepare)); + + byte pubkey = (cfg.anyPublicKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL; + byte privkey = (cfg.anyPrivateKey || cfg.anyKey) ? ECTesterApplet.KEYPAIR_REMOTE : ECTesterApplet.KEYPAIR_LOCAL; + + List<Command> generate = new LinkedList<>(); + generate.add(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH)); + if (cfg.anyPublicKey || cfg.anyPrivateKey || cfg.anyKey) { + generate.add(Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_REMOTE)); + } + + FileWriter out = null; + if (cfg.output != null) { + out = new FileWriter(cfg.output); + out.write("index;time;secret\n"); + } + + int retry = 0; + int done = 0; + while (done < cfg.ECDHCount) { + List<Response> ecdh = Command.sendAll(generate); + + Response.ECDH perform = new Command.ECDH(cardManager, pubkey, privkey, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, cfg.ECDHKA).send(); + ecdh.add(perform); + systemOutLogger.println(Response.toString(ecdh)); + + if (!perform.successful() || !perform.hasSecret()) { + if (retry < 10) { + ++retry; + continue; + } else { + System.err.println("Couldn't obtain ECDH secret from card response."); + break; + } + } + + if (out != null) { + out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSecret(), false))); + } + + ++done; + } + Response cleanup = new Command.Cleanup(cardManager).send(); + systemOutLogger.println(cleanup.toString()); + + if (out != null) + out.close(); + } + + /** + * Performs ECDSA signature, on random or provided data. + * + * @throws CardException if APDU transmission fails + * @throws IOException if an IO error occurs when writing to key file. + */ + private void ecdsa() throws CardException, IOException { + //read file, if asked to sign + byte[] data = null; + if (cfg.input != null) { + File in = new File(cfg.input); + long len = in.length(); + if (len == 0) { + throw new FileNotFoundException(cfg.input); + } + data = Files.readAllBytes(in.toPath()); + } + + Command generate; + if (cfg.anyKeypart) { + generate = Command.prepareKey(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL); + } else { + generate = new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL); + } + + byte keyClass = cfg.primeField ? KeyPair.ALG_EC_FP : KeyPair.ALG_EC_F2M; + List<Response> prepare = new LinkedList<>(); + prepare.add(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass).send()); + Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_LOCAL, (short) cfg.bits, keyClass); + if (curve != null) + prepare.add(curve.send()); + + systemOutLogger.println(Response.toString(prepare)); + + FileWriter out = null; + if (cfg.output != null) { + out = new FileWriter(cfg.output); + out.write("index;time;signature\n"); + } + + int retry = 0; + int done = 0; + while (done < cfg.ECDSACount) { + List<Response> ecdsa = new LinkedList<>(); + ecdsa.add(generate.send()); + + Response.ECDSA perform = new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, data).send(); + ecdsa.add(perform); + systemOutLogger.println(Response.toString(ecdsa)); + + if (!perform.successful() || !perform.hasSignature()) { + if (retry < 10) { + ++retry; + continue; + } else { + System.err.println("Couldn't obtain ECDSA signature from card response."); + break; + } + } + + if (out != null) { + out.write(String.format("%d;%d;%s\n", done, perform.getDuration() / 1000000, Util.bytesToHex(perform.getSignature(), false))); + } + + ++done; + } + Response cleanup = new Command.Cleanup(cardManager).send(); + systemOutLogger.println(cleanup.toString()); + + if (out != null) + out.close(); + } + + public static void main(String[] args) { + ECTester app = new ECTester(); + app.run(args); + } + + public static class Config { + + //Options + public int bits; + public boolean all; + public boolean primeField = false; + public boolean binaryField = false; + + public String namedCurve; + public String curveFile; + public boolean customCurve = false; + + public boolean anyPublicKey = false; + public String namedPublicKey; + public String publicKey; + + public boolean anyPrivateKey = false; + public String namedPrivateKey; + public String privateKey; + + public boolean anyKey = false; + public String namedKey; + public String key; + + public boolean anyKeypart = false; + + public String log; + + public boolean verbose = false; + public String input; + public String output; + public boolean fresh = false; + public boolean simulate = false; + public boolean yes = false; + + //Action-related ions + public String listNamed; + public String testSuite; + public int generateAmount; + public int ECDHCount; + public byte ECDHKA; + public int ECDSACount; + + /** + * Reads and validates options, also sets defaults. + * + * @param cli cli object, with parsed args + * @return whether the options are valid. + */ + boolean readOptions(CommandLine cli) { + bits = Integer.parseInt(cli.getOptionValue("bit-size", "0")); + all = cli.hasOption("all"); + primeField = cli.hasOption("fp"); + binaryField = cli.hasOption("f2m"); + + namedCurve = cli.getOptionValue("named-curve"); + customCurve = cli.hasOption("custom"); + curveFile = cli.getOptionValue("curve"); + + namedPublicKey = cli.getOptionValue("named-public"); + publicKey = cli.getOptionValue("public"); + anyPublicKey = (publicKey != null) || (namedPublicKey != null); + + namedPrivateKey = cli.getOptionValue("named-private"); + privateKey = cli.getOptionValue("private"); + anyPrivateKey = (privateKey != null) || (namedPrivateKey != null); + + namedKey = cli.getOptionValue("named-key"); + key = cli.getOptionValue("key"); + anyKey = (key != null) || (namedKey != null); + anyKeypart = anyKey || anyPublicKey || anyPrivateKey; + + if (cli.hasOption("log")) { + log = cli.getOptionValue("log", String.format("ECTESTER_log_%d.log", System.currentTimeMillis() / 1000)); + } + + verbose = cli.hasOption("verbose"); + input = cli.getOptionValue("input"); + output = cli.getOptionValue("output"); + fresh = cli.hasOption("fresh"); + simulate = cli.hasOption("simulate"); + yes = cli.hasOption("yes"); + + if (cli.hasOption("list-named")) { + listNamed = cli.getOptionValue("list-named"); + return true; + } + + if ((key != null || namedKey != null) && (anyPublicKey || anyPrivateKey)) { + System.err.print("Can only specify the whole key with --key/--named-key or pubkey and privkey with --public/--named-public and --private/--named-private."); + return false; + } + if (bits < 0) { + System.err.println("Bit-size must not be negative."); + return false; + } + if (bits == 0 && !all) { + System.err.println("You must specify either bit-size with -b or all bit-sizes with -a."); + return false; + } + + if (key != null && namedKey != null || publicKey != null && namedPublicKey != null || privateKey != null && namedPrivateKey != null) { + System.err.println("You cannot specify both a named key and a key file."); + return false; + } + + if (cli.hasOption("export")) { + if (primeField == binaryField) { + System.err.print("Need to specify field with -fp or -f2m. (not both)"); + return false; + } + if (anyKeypart) { + System.err.println("Keys should not be specified when exporting curve params."); + return false; + } + if (namedCurve != null || customCurve || curveFile != null) { + System.err.println("Specifying a curve for curve export makes no sense."); + return false; + } + if (output == null) { + System.err.println("You have to specify an output file for curve parameter export."); + return false; + } + if (all) { + System.err.println("You have to specify curve bit-size with -b"); + return false; + } + + } else if (cli.hasOption("generate")) { + if (primeField == binaryField) { + System.err.print("Need to specify field with -fp or -f2m. (not both)"); + return false; + } + if (anyKeypart) { + System.err.println("Keys should not be specified when generating keys."); + return false; + } + if (output == null) { + System.err.println("You have to specify an output file for the key generation process."); + return false; + } + if (all) { + System.err.println("You have to specify curve bit-size with -b"); + return false; + } + + generateAmount = Integer.parseInt(cli.getOptionValue("generate", "0")); + if (generateAmount < 0) { + System.err.println("Amount of keys generated cant be negative."); + return false; + } + } else if (cli.hasOption("test")) { + if (!(binaryField || primeField)) { + binaryField = true; + primeField = true; + } + + testSuite = cli.getOptionValue("test", "default").toLowerCase(); + String[] tests = new String[]{"default", "nonprime", "invalid", "test-vectors", "wrong"}; + List<String> testsList = Arrays.asList(tests); + if (!testsList.contains(testSuite)) { + System.err.println("Unknown test case. Should be one of: " + Arrays.toString(tests)); + return false; + } + + } else if (cli.hasOption("ecdh") || cli.hasOption("ecdhc")) { + if (primeField == binaryField) { + System.err.print("Need to specify field with -fp or -f2m. (not both)"); + return false; + } + if (all) { + System.err.println("You have to specify curve bit-size with -b"); + return false; + } + + if (cli.hasOption("ecdh")) { + ECDHCount = Integer.parseInt(cli.getOptionValue("ecdh", "1")); + ECDHKA = EC_Consts.KA_ECDH; + } else if (cli.hasOption("ecdhc")) { + ECDHCount = Integer.parseInt(cli.getOptionValue("ecdhc", "1")); + ECDHKA = EC_Consts.KA_ECDHC; + } + if (ECDHCount <= 0) { + System.err.println("ECDH count cannot be <= 0."); + return false; + } + + } else if (cli.hasOption("ecdsa")) { + if (primeField == binaryField) { + System.err.print("Need to specify field with -fp or -f2m. (but not both)"); + return false; + } + if (all) { + System.err.println("You have to specify curve bit-size with -b"); + return false; + } + + if ((anyPublicKey) != (anyPrivateKey) && !anyKey) { + System.err.println("You cannot only specify a part of a keypair."); + return false; + } + + ECDSACount = Integer.parseInt(cli.getOptionValue("ecdsa", "1")); + if (ECDSACount <= 0) { + System.err.println("ECDSA count cannot be <= 0."); + return false; + } + } + return true; + } + } +} diff --git a/src/cz/crcs/ectester/reader/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/Response.java b/src/cz/crcs/ectester/reader/Response.java new file mode 100644 index 0000000..d74724c --- /dev/null +++ b/src/cz/crcs/ectester/reader/Response.java @@ -0,0 +1,574 @@ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.reader.ec.EC_Curve; +import javacard.framework.ISO7816; +import javacard.security.KeyPair; + +import javax.smartcardio.ResponseAPDU; +import java.util.List; + +/** + * @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; + //TODO replace params with EC_Data? + private boolean success = true; + + protected Response(ResponseAPDU response, long time) { + this.resp = response; + this.time = time; + } + + protected void 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 = Util.getShort(data, offset); + offset += 2; + sws[i] = sw; + if (sw != ISO7816.SW_NO_ERROR) { + success = false; + } + } else { + success = false; + } + } + + if ((short) resp.getSW() != ISO7816.SW_NO_ERROR) + success = false; + + //try to parse numParams.. + params = new byte[numParams][]; + for (int i = 0; i < numParams; i++) { + if (data.length - offset < 2) { + success = false; + break; + } + short paramLength = Util.getShort(data, offset); + offset += 2; + if (data.length < offset + paramLength) { + success = false; + break; + } + params[i] = new byte[paramLength]; + System.arraycopy(data, offset, params[i], 0, paramLength); + offset += paramLength; + } + } + + public ResponseAPDU getAPDU() { + return resp; + } + + public long getDuration() { + return time; + } + + public short getNaturalSW() { + return (short) resp.getSW(); + } + + 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; + } + + @Override + public abstract String toString(); + + public String toString(String inner) { + StringBuilder suffix = new StringBuilder(); + for (int j = 0; j < getNumSW(); ++j) { + short sw = getSW(j); + if (sw != 0) { + suffix.append(" ").append(Util.getSWString(sw)); + } + } + if (suffix.length() == 0) { + suffix.append(" [").append(Util.getSW(getNaturalSW())).append("]"); + } + return String.format("%-62s:%4d ms : %s", inner, time / 1000000, suffix); + } + + public static String toString(List<Response> responses) { + return toString(responses, null); + } + + public static String toString(List<Response> responses, String prefix) { + if (prefix != null) + prefix += " | "; + StringBuilder out = new StringBuilder(); + for (int i = 0; i < responses.size(); ++i) { + Response r = responses.get(i); + + if (prefix != null) + out.append(prefix); + + String message = r.toString(); + out.append(message); + if (i < responses.size() - 1) { + out.append("\n"); + } + } + return out.toString(); + } + + /** + * + */ + public static class Allocate extends Response { + + private byte keyPair; + private short keyLength; + private byte keyClass; + + protected Allocate(ResponseAPDU response, long time, byte keyPair, short keyLength, byte keyClass) { + super(response, time); + this.keyPair = keyPair; + this.keyLength = keyLength; + this.keyClass = keyClass; + + int pairs = 0; + if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) pairs++; + if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) pairs++; + parse(pairs, 0); + } + + @Override + public String toString() { + String field = keyClass == KeyPair.ALG_EC_FP ? "ALG_EC_FP" : "ALG_EC_F2M"; + String key; + if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { + key = "both keypairs"; + } else { + key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return super.toString(String.format("Allocated %s %db %s", key, keyLength, field)); + } + } + + /** + * + */ + public static class Clear extends Response { + + private byte keyPair; + + protected Clear(ResponseAPDU response, long time, byte keyPair) { + super(response, time); + this.keyPair = keyPair; + + int pairs = 0; + if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) pairs++; + if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) pairs++; + parse(pairs, 0); + } + + @Override + public String toString() { + String key; + if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { + key = "both keypairs"; + } else { + key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return super.toString(String.format("Cleared %s", key)); + } + } + + /** + * + */ + public static class Set extends Response { + + private byte keyPair; + private byte curve; + private short parameters; + + protected Set(ResponseAPDU response, long time, byte keyPair, byte curve, short parameters) { + super(response, time); + this.keyPair = keyPair; + this.curve = curve; + this.parameters = parameters; + + int pairs = 0; + if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) pairs++; + if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) pairs++; + + parse(pairs, 0); + } + + @Override + public String toString() { + String name; + switch (curve) { + case EC_Consts.CURVE_default: + name = "default"; + break; + case EC_Consts.CURVE_external: + name = "external"; + break; + default: + name = "custom"; + break; + } + String what = ""; + if (parameters == EC_Consts.PARAMETERS_DOMAIN_F2M || parameters == EC_Consts.PARAMETERS_DOMAIN_FP) { + what = "curve"; + } else if (parameters == EC_Consts.PARAMETER_W) { + what = "pubkey"; + } else if (parameters == EC_Consts.PARAMETER_S) { + what = "privkey"; + } else if (parameters == EC_Consts.PARAMETERS_KEYPAIR) { + what = "keypair"; + } + + String pair; + if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { + pair = "both keypairs"; + } else { + pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return super.toString(String.format("Set %s %s parameters on %s", name, what, pair)); + } + + } + + /** + * + */ + public static class Corrupt extends Response { + + private byte keyPair; + private byte key; + private short params; + private byte corruption; + + protected Corrupt(ResponseAPDU response, long time, byte keyPair, byte key, short params, byte corruption) { + super(response, time); + this.keyPair = keyPair; + this.key = key; + this.params = params; + this.corruption = corruption; + + int pairs = 0; + if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) pairs++; + if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) pairs++; + + parse(pairs, 0); + } + + @Override + public String toString() { + String corrupt = Util.getCorruption(corruption); + + String pair; + if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { + pair = "both keypairs"; + } else { + pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return super.toString(String.format("Corrupted params of %s, %s", pair, corrupt)); + } + } + + /** + * + */ + public static class Generate extends Response { + + private byte keyPair; + + protected Generate(ResponseAPDU response, long time, byte keyPair) { + super(response, time); + this.keyPair = keyPair; + + int generated = 0; + if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) generated++; + if ((keyPair & ECTesterApplet.KEYPAIR_REMOTE) != 0) generated++; + parse(generated, 0); + } + + @Override + public String toString() { + String key; + if (keyPair == ECTesterApplet.KEYPAIR_BOTH) { + key = "both keypairs"; + } else { + key = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return super.toString(String.format("Generated %s", key)); + } + + } + + /** + * + */ + public static class Export extends Response { + + private byte keyPair; + private byte key; + private short parameters; + + protected Export(ResponseAPDU response, long time, byte keyPair, byte key, short parameters) { + super(response, time); + this.keyPair = keyPair; + this.key = key; + this.parameters = parameters; + + int exported = 0; + if ((keyPair & ECTesterApplet.KEYPAIR_LOCAL) != 0) exported++; + if ((keyPair & ECTesterApplet.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 = ECTesterApplet.KEYPAIR_LOCAL; + int index = 0; + while (pair <= ECTesterApplet.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 & 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)); + } + + @Override + public String toString() { + 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 == ECTesterApplet.KEYPAIR_BOTH) { + pair = "both keypairs"; + } else { + pair = ((keyPair == ECTesterApplet.KEYPAIR_LOCAL) ? "local" : "remote") + " keypair"; + } + return super.toString(String.format("Exported params from %s of %s", source, pair)); + } + } + + /** + * + */ + public static class ECDH extends Response { + + private byte pubkey; + private byte privkey; + private byte export; + private short corruption; + private byte type; + + protected ECDH(ResponseAPDU response, long time, byte pubkey, byte privkey, byte export, short corruption, byte type) { + super(response, time); + this.pubkey = pubkey; + this.privkey = privkey; + this.export = export; + this.corruption = corruption; + this.type = type; + + parse(1, (export == ECTesterApplet.EXPORT_TRUE) ? 1 : 0); + } + + public boolean hasSecret() { + return hasParam(0); + } + + public byte[] getSecret() { + return getParam(0); + } + + public int secretLength() { + return getParamLength(0); + } + + @Override + public String toString() { + String algo = Util.getKA(type); + + String pub = pubkey == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote"; + String priv = privkey == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote"; + + String validity; + if (corruption == EC_Consts.CORRUPTION_NONE) { + validity = "unchanged"; + } else { + validity = Util.getCorruption(corruption); + } + return super.toString(String.format("%s of %s pubkey and %s privkey(%s point)", algo, pub, priv, validity)); + } + } + + /** + * + */ + public static class ECDSA extends Response { + + private byte keyPair; + private byte export; + private byte[] raw; + + protected ECDSA(ResponseAPDU response, long time, byte keyPair, byte export, byte[] raw) { + super(response, time); + this.keyPair = keyPair; + this.export = export; + this.raw = raw; + + parse(1, (export == ECTesterApplet.EXPORT_TRUE) ? 1 : 0); + } + + public boolean hasSignature() { + return hasParam(0); + } + + public byte[] getSignature() { + return getParam(0); + } + + @Override + public String toString() { + String key = keyPair == ECTesterApplet.KEYPAIR_LOCAL ? "local" : "remote"; + String data = raw == null ? "random" : "provided"; + return super.toString(String.format("ECDSA with %s keypair(%s data)", key, data)); + } + } + + /** + * + */ + public static class Cleanup extends Response { + + protected Cleanup(ResponseAPDU response, long time) { + super(response, time); + + parse(1, 0); + } + + @Override + public String toString() { + return super.toString("Requested JCSystem object deletion"); + } + + } + + /** + * + */ + public static class Support extends Response { + + protected Support(ResponseAPDU response, long time) { + super(response, time); + + parse(3, 0); + } + + @Override + public String toString() { + return super.toString("Support of ECDH, ECDHC, ECDSA"); + } + } +} diff --git a/src/cz/crcs/ectester/reader/Test.java b/src/cz/crcs/ectester/reader/Test.java new file mode 100644 index 0000000..157e360 --- /dev/null +++ b/src/cz/crcs/ectester/reader/Test.java @@ -0,0 +1,82 @@ +package cz.crcs.ectester.reader; + +import javax.smartcardio.CardException; +import java.util.function.BiFunction; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class Test { + private boolean hasRun = false; + private BiFunction<Command, Response, Result> callback; + private Result result; + private Result expected; + private Command command; + private Response response; + + public Test(Command command, Result expected) { + this.command = command; + this.expected = expected; + } + + public Test(Command command, Result expected, BiFunction<Command, Response, Result> callback) { + this(command, expected); + this.callback = callback; + } + + public Command getCommand() { + return command; + } + + public Response getResponse() { + return response; + } + + public Result getResult() { + if (!hasRun) { + return null; + } + return result; + } + + public Result getExpected() { + return expected; + } + + public boolean ok() { + return result == expected || expected == Result.ANY; + } + + public void run() throws CardException { + response = command.send(); + if (callback != null) { + result = callback.apply(command, response); + } else { + if (response.successful()) { + result = Result.SUCCESS; + } else { + result = Result.FAILURE; + } + } + hasRun = true; + } + + public boolean hasRun() { + return hasRun; + } + + @Override + public String toString() { + if (hasRun) { + return (ok() ? "OK " : "NOK") + " " + response.toString(); + } else { + return ""; + } + } + + public enum Result { + SUCCESS, + FAILURE, + ANY + } +} diff --git a/src/cz/crcs/ectester/reader/TestSuite.java b/src/cz/crcs/ectester/reader/TestSuite.java new file mode 100644 index 0000000..7118dd8 --- /dev/null +++ b/src/cz/crcs/ectester/reader/TestSuite.java @@ -0,0 +1,314 @@ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.data.EC_Store; +import cz.crcs.ectester.reader.ec.*; +import javacard.security.KeyPair; + +import javax.smartcardio.CardException; +import java.io.IOException; +import java.util.*; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class TestSuite { + + EC_Store dataStore; + ECTester.Config cfg; + DirtyLogger systemOut; + String name; + List<Test> tests = new LinkedList<>(); + + TestSuite(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut, String name) { + this.dataStore = dataStore; + this.cfg = cfg; + this.systemOut = systemOut; + this.name = name; + } + + public List<Test> run(CardMngr cardManager) throws CardException, IOException { + for (Test t : tests) { + if (!t.hasRun()) { + t.run(); + systemOut.println(t.toString()); + } + } + return tests; + } + + public List<Test> getTests() { + return Collections.unmodifiableList(tests); + } + + public String getName() { + return name; + } + + /** + * @param cardManager cardManager to send APDU through + * @param generateExpected expected result of the Generate command + * @param ecdhExpected expected result of the ordinary ECDH command + * @param ecdsaExpected expected result of the ordinary ECDSA command + * @return tests to run + */ + List<Test> testCurve(CardMngr cardManager, Test.Result generateExpected, Test.Result ecdhExpected, Test.Result ecdsaExpected) { + List<Test> tests = new LinkedList<>(); + + tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH), generateExpected)); + tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH), ecdhExpected)); + tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_COMPRESS, EC_Consts.KA_ECDH), ecdhExpected)); + tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_ONE, EC_Consts.KA_ECDH), Test.Result.FAILURE)); + tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_ZERO, EC_Consts.KA_ECDH), Test.Result.FAILURE)); + tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_MAX, EC_Consts.KA_ECDH), Test.Result.FAILURE)); + tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_FULLRANDOM, EC_Consts.KA_ECDH), Test.Result.FAILURE)); + tests.add(new Test(new Command.ECDSA(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, null), ecdsaExpected)); + + return tests; + } + + /** + * @param cardManager cardManager to send APDU through + * @param category category to test + * @param field field to test (KeyPair.ALG_EC_FP || KeyPair.ALG_EC_F2M) + * @param setExpected expected result of the Set (curve) command + * @param generateExpected expected result of the Generate command + * @param ecdhExpected expected result of the ordinary ECDH command + * @param ecdsaExpected expected result of the ordinary ECDSA command + * @return tests to run + */ + List<Test> testCategory(CardMngr cardManager, String category, byte field, Test.Result setExpected, Test.Result generateExpected, Test.Result ecdhExpected, Test.Result ecdsaExpected) { + List<Test> tests = new LinkedList<>(); + Map<String, EC_Curve> curves = dataStore.getObjects(EC_Curve.class, category); + if (curves == null) + return tests; + for (Map.Entry<String, EC_Curve> entry : curves.entrySet()) { + EC_Curve curve = entry.getValue(); + if (curve.getField() == field && (curve.getBits() == cfg.bits || cfg.all)) { + tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), field), Test.Result.SUCCESS)); + tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), setExpected)); + tests.addAll(testCurve(cardManager, generateExpected, ecdhExpected, ecdsaExpected)); + tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); + } + } + + return tests; + } + + public static class Default extends TestSuite { + + public Default(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { + super(dataStore, cfg, systemOut, "default"); + } + + @Override + public List<Test> run(CardMngr cardManager) throws IOException, CardException { + tests.add(new Test(new Command.Support(cardManager), Test.Result.ANY)); + if (cfg.namedCurve != null) { + if (cfg.primeField) { + tests.addAll(testCategory(cardManager, cfg.namedCurve, KeyPair.ALG_EC_FP, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS)); + } + if (cfg.binaryField) { + tests.addAll(testCategory(cardManager, cfg.namedCurve, KeyPair.ALG_EC_F2M, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS)); + } + } else { + if (cfg.all) { + if (cfg.primeField) { + //iterate over prime curve sizes used: EC_Consts.FP_SIZES + for (short keyLength : EC_Consts.FP_SIZES) { + defaultTests(cardManager, keyLength, KeyPair.ALG_EC_FP); + } + } + if (cfg.binaryField) { + //iterate over binary curve sizes used: EC_Consts.F2M_SIZES + for (short keyLength : EC_Consts.F2M_SIZES) { + defaultTests(cardManager, keyLength, KeyPair.ALG_EC_F2M); + } + } + } else { + if (cfg.primeField) { + defaultTests(cardManager, (short) cfg.bits, KeyPair.ALG_EC_FP); + } + + if (cfg.binaryField) { + defaultTests(cardManager, (short) cfg.bits, KeyPair.ALG_EC_F2M); + } + } + } + return super.run(cardManager); + } + + private void defaultTests(CardMngr cardManager, short keyLength, byte keyType) throws IOException { + tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, keyLength, keyType), Test.Result.SUCCESS)); + Command curve = Command.prepareCurve(cardManager, dataStore, cfg, ECTesterApplet.KEYPAIR_BOTH, keyLength, keyType); + if (curve != null) + tests.add(new Test(curve, Test.Result.SUCCESS)); + tests.addAll(testCurve(cardManager, Test.Result.SUCCESS, Test.Result.SUCCESS, Test.Result.SUCCESS)); + tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); + } + } + + public static class TestVectors extends TestSuite { + + public TestVectors(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { + super(dataStore, cfg, systemOut, "test"); + } + + @Override + public List<Test> run(CardMngr cardManager) throws IOException, CardException { + /* 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 = dataStore.getObjects(EC_KAResult.class, "test"); + for (EC_KAResult result : results.values()) { + EC_Curve curve = dataStore.getObject(EC_Curve.class, result.getCurve()); + if (cfg.namedCurve != null && !(result.getCurve().startsWith(cfg.namedCurve) || result.getCurve().equals(cfg.namedCurve))) { + continue; + } + if (curve.getBits() != cfg.bits && !cfg.all) { + continue; + } + if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { + continue; + } + EC_Params onekey = dataStore.getObject(EC_Keypair.class, result.getOneKey()); + if (onekey == null) { + onekey = dataStore.getObject(EC_Key.Private.class, result.getOneKey()); + } + EC_Params otherkey = dataStore.getObject(EC_Keypair.class, result.getOtherKey()); + if (otherkey == null) { + otherkey = dataStore.getObject(EC_Key.Public.class, result.getOtherKey()); + } + if (onekey == null || otherkey == null) { + throw new IOException("Test vector keys couldn't be located."); + } + + tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS)); + tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.SUCCESS)); + //tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_BOTH), Test.Result.SUCCESS)); + tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_LOCAL, EC_Consts.CURVE_external, EC_Consts.PARAMETER_S, onekey.flatten(EC_Consts.PARAMETER_S)), Test.Result.SUCCESS)); + tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, EC_Consts.PARAMETER_W, otherkey.flatten(EC_Consts.PARAMETER_W)), Test.Result.SUCCESS)); + tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_TRUE, EC_Consts.CORRUPTION_NONE, result.getKA()), Test.Result.SUCCESS, (command, response) -> { + Response.ECDH dh = (Response.ECDH) response; + if (!dh.successful() || !dh.hasSecret()) + return Test.Result.FAILURE; + if (!Util.compareBytes(dh.getSecret(), 0, result.getParam(0), 0, dh.secretLength())) { + return Test.Result.FAILURE; + } + return Test.Result.SUCCESS; + })); + tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); + + } + return super.run(cardManager); + } + } + + public static class NonPrime extends TestSuite { + + public NonPrime(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { + super(dataStore, cfg, systemOut, "nonprime"); + } + + @Override + public List<Test> run(CardMngr cardManager) throws IOException, CardException { + /* Do the default tests with the public keys set to provided smallorder keys + * over non-prime order curves. Essentially small subgroup attacks. + * These should fail, the curves aren't safe so that if the computation with + * a small order public key succeeds the private key modulo the public key order + * is revealed. + */ + Map<String, EC_Key> keys = dataStore.getObjects(EC_Key.class, "nonprime"); + for (EC_Key key : keys.values()) { + EC_Curve curve = dataStore.getObject(EC_Curve.class, key.getCurve()); + if (cfg.namedCurve != null && !(key.getCurve().startsWith(cfg.namedCurve) || key.getCurve().equals(cfg.namedCurve))) { + continue; + } + if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { + continue; + } + if ((curve.getBits() == cfg.bits || cfg.all)) { + tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS)); + tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.ANY)); + tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL), Test.Result.ANY)); + + //tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, key.getParams(), key.flatten()), Test.Result.ANY)); + //tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH), Test.Result.FAILURE)); + tests.add(new Test(new Command.ECDH_direct(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ECDH, key.flatten()), Test.Result.FAILURE)); + + tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); + } + } + return super.run(cardManager); + } + } + + public static class Invalid extends TestSuite { + + public Invalid(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { + super(dataStore, cfg, systemOut, "invalid"); + } + + @Override + public List<Test> run(CardMngr cardManager) throws CardException, IOException { + /* Set original curves (secg/nist/brainpool). Generate local. + * Try ECDH with invalid public keys of increasing (or decreasing) order. + */ + Map<String, EC_Key.Public> pubkeys = dataStore.getObjects(EC_Key.Public.class, "invalid"); + Map<EC_Curve, List<EC_Key.Public>> curves = new HashMap<>(); + for (EC_Key.Public key : pubkeys.values()) { + EC_Curve curve = dataStore.getObject(EC_Curve.class, key.getCurve()); + if (cfg.namedCurve != null && !(key.getCurve().startsWith(cfg.namedCurve) || key.getCurve().equals(cfg.namedCurve))) { + continue; + } + if (curve.getBits() != cfg.bits && !cfg.all) { + continue; + } + if (curve.getField() == KeyPair.ALG_EC_FP && !cfg.primeField || curve.getField() == KeyPair.ALG_EC_F2M && !cfg.binaryField) { + continue; + } + List<EC_Key.Public> keys = curves.getOrDefault(curve, new LinkedList<>()); + keys.add(key); + curves.putIfAbsent(curve, keys); + } + for (Map.Entry<EC_Curve, List<EC_Key.Public>> e : curves.entrySet()) { + EC_Curve curve = e.getKey(); + List<EC_Key.Public> keys = e.getValue(); + + tests.add(new Test(new Command.Allocate(cardManager, ECTesterApplet.KEYPAIR_BOTH, curve.getBits(), curve.getField()), Test.Result.SUCCESS)); + tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_BOTH, EC_Consts.CURVE_external, curve.getParams(), curve.flatten()), Test.Result.SUCCESS)); + tests.add(new Test(new Command.Generate(cardManager, ECTesterApplet.KEYPAIR_LOCAL), Test.Result.SUCCESS)); + for (EC_Key.Public pub : keys) { + // tests.add(new Test(new Command.Set(cardManager, ECTesterApplet.KEYPAIR_REMOTE, EC_Consts.CURVE_external, pub.getParams(), pub.flatten()), Test.Result.ANY)); + // tests.add(new Test(new Command.ECDH(cardManager, ECTesterApplet.KEYPAIR_REMOTE, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ANY), Test.Result.FAILURE)); + tests.add(new Test(new Command.ECDH_direct(cardManager, ECTesterApplet.KEYPAIR_LOCAL, ECTesterApplet.EXPORT_FALSE, EC_Consts.CORRUPTION_NONE, EC_Consts.KA_ANY, pub.flatten()), Test.Result.FAILURE)); + } + tests.add(new Test(new Command.Cleanup(cardManager), Test.Result.ANY)); + } + + return super.run(cardManager); + } + } + + public static class Wrong extends TestSuite { + + public Wrong(EC_Store dataStore, ECTester.Config cfg, DirtyLogger systemOut) { + super(dataStore, cfg, systemOut, "wrong"); + } + + @Override + public List<Test> run(CardMngr cardManager) throws CardException, IOException { + /* Just do the default tests on the wrong curves. + * These should generally fail, the curves aren't curves. + */ + if (cfg.primeField) { + tests.addAll(testCategory(cardManager, cfg.testSuite, KeyPair.ALG_EC_FP, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE)); + } + if (cfg.binaryField) { + tests.addAll(testCategory(cardManager, cfg.testSuite, KeyPair.ALG_EC_F2M, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE, Test.Result.FAILURE)); + } + return super.run(cardManager); + } + } +} diff --git a/src/cz/crcs/ectester/reader/Util.java b/src/cz/crcs/ectester/reader/Util.java new file mode 100644 index 0000000..e7b7338 --- /dev/null +++ b/src/cz/crcs/ectester/reader/Util.java @@ -0,0 +1,342 @@ +package cz.crcs.ectester.reader; + +import cz.crcs.ectester.applet.ECTesterApplet; +import cz.crcs.ectester.applet.EC_Consts; +import javacard.framework.ISO7816; +import javacard.security.CryptoException; + +/** + * Utility class, some byte/hex manipulation, convenient byte[] methods. + * + * @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 boolean compareBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) { + for (int i = 0; i < length; ++i) { + byte a = one[i + oneOffset]; + byte b = other[i + otherOffset]; + if (a != b) { + return false; + } + } + return true; + } + + public static boolean allValue(byte[] array, byte value) { + for (byte a : array) { + if (a != value) + return false; + } + return true; + } + + public static byte[] hexToBytes(String hex) { + return hexToBytes(hex, true); + } + + public static byte[] hexToBytes(String hex, boolean bigEndian) { + hex = hex.replace(" ", ""); + int len = hex.length(); + StringBuilder sb = new StringBuilder(); + + if (len % 2 == 1) { + sb.append("0"); + ++len; + } + + if (bigEndian) { + sb.append(hex); + } else { + for (int i = 0; i < len / 2; ++i) { + if (sb.length() >= 2) { + sb.insert(sb.length() - 2, hex.substring(2 * i, 2 * i + 2)); + } else { + sb.append(hex.substring(2 * i, 2 * i + 2)); + } + + } + } + + String data = sb.toString(); + byte[] result = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + result[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4) + + (Character.digit(data.charAt(i + 1), 16))); + } + return result; + } + + public static String byteToHex(byte data) { + return String.format("%02x", data); + } + + public static String bytesToHex(byte[] data) { + return bytesToHex(data, true); + } + + public static String bytesToHex(byte[] data, boolean addSpace) { + return bytesToHex(data, 0, data.length, addSpace); + } + + public static String bytesToHex(byte[] data, int offset, int len) { + return bytesToHex(data, offset, len, true); + } + + public static String bytesToHex(byte[] data, int offset, int len, boolean addSpace) { + StringBuilder buf = new StringBuilder(); + for (int i = offset; i < (offset + len); i++) { + buf.append(byteToHex(data[i])); + if (addSpace && i != (offset + len - 1)) { + buf.append(" "); + } + } + return (buf.toString()); + } + + public static byte[] concatenate(byte[]... arrays) { + int len = 0; + for (byte[] array : arrays) { + if (array == null) + continue; + len += array.length; + } + byte[] out = new byte[len]; + int offset = 0; + for (byte[] array : arrays) { + if (array == null || array.length == 0) + continue; + System.arraycopy(array, 0, out, offset, array.length); + offset += array.length; + } + return out; + } + + public static String getSWSource(short sw) { + switch (sw) { + case ISO7816.SW_NO_ERROR: + case ISO7816.SW_APPLET_SELECT_FAILED: + case ISO7816.SW_BYTES_REMAINING_00: + case ISO7816.SW_CLA_NOT_SUPPORTED: + case ISO7816.SW_COMMAND_NOT_ALLOWED: + case ISO7816.SW_CONDITIONS_NOT_SATISFIED: + case ISO7816.SW_CORRECT_LENGTH_00: + case ISO7816.SW_DATA_INVALID: + case ISO7816.SW_FILE_FULL: + case ISO7816.SW_FILE_INVALID: + case ISO7816.SW_FILE_NOT_FOUND: + case ISO7816.SW_FUNC_NOT_SUPPORTED: + case ISO7816.SW_INCORRECT_P1P2: + case ISO7816.SW_INS_NOT_SUPPORTED: + case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED: + case ISO7816.SW_RECORD_NOT_FOUND: + case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED: + case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED: + case ISO7816.SW_UNKNOWN: + case ISO7816.SW_WARNING_STATE_UNCHANGED: + case ISO7816.SW_WRONG_DATA: + case ISO7816.SW_WRONG_LENGTH: + case ISO7816.SW_WRONG_P1P2: + return "ISO"; + case CryptoException.ILLEGAL_VALUE: + case CryptoException.UNINITIALIZED_KEY: + case CryptoException.NO_SUCH_ALGORITHM: + case CryptoException.INVALID_INIT: + case CryptoException.ILLEGAL_USE: + return "CryptoException"; + case ECTesterApplet.SW_SIG_VERIFY_FAIL: + case ECTesterApplet.SW_DH_DHC_MISMATCH: + case ECTesterApplet.SW_KEYPAIR_NULL: + case ECTesterApplet.SW_KA_NULL: + case ECTesterApplet.SW_SIGNATURE_NULL: + case ECTesterApplet.SW_OBJECT_NULL: + return "ECTesterApplet"; + default: + return "?"; + } + } + + public static String getSW(short sw) { + String str; + switch (sw) { + case ISO7816.SW_APPLET_SELECT_FAILED: + str = "APPLET_SELECT_FAILED"; + break; + case ISO7816.SW_BYTES_REMAINING_00: + str = "BYTES_REMAINING"; + break; + case ISO7816.SW_CLA_NOT_SUPPORTED: + str = "CLA_NOT_SUPPORTED"; + break; + case ISO7816.SW_COMMAND_NOT_ALLOWED: + str = "COMMAND_NOT_ALLOWED"; + break; + case ISO7816.SW_CONDITIONS_NOT_SATISFIED: + str = "CONDITIONS_NOT_SATISFIED"; + break; + case ISO7816.SW_CORRECT_LENGTH_00: + str = "CORRECT_LENGTH"; + break; + case ISO7816.SW_DATA_INVALID: + str = "DATA_INVALID"; + break; + case ISO7816.SW_FILE_FULL: + str = "FILE_FULL"; + break; + case ISO7816.SW_FILE_INVALID: + str = "FILE_INVALID"; + break; + case ISO7816.SW_FILE_NOT_FOUND: + str = "FILE_NOT_FOUND"; + break; + case ISO7816.SW_FUNC_NOT_SUPPORTED: + str = "FUNC_NOT_SUPPORTED"; + break; + case ISO7816.SW_INCORRECT_P1P2: + str = "INCORRECT_P1P2"; + break; + case ISO7816.SW_INS_NOT_SUPPORTED: + str = "INS_NOT_SUPPORTED"; + break; + case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED: + str = "LOGICAL_CHANNEL_NOT_SUPPORTED"; + break; + case ISO7816.SW_RECORD_NOT_FOUND: + str = "RECORD_NOT_FOUND"; + break; + case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED: + str = "SECURE_MESSAGING_NOT_SUPPORTED"; + break; + case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED: + str = "SECURITY_STATUS_NOT_SATISFIED"; + break; + case ISO7816.SW_UNKNOWN: + str = "UNKNOWN"; + break; + case ISO7816.SW_WARNING_STATE_UNCHANGED: + str = "WARNING_STATE_UNCHANGED"; + break; + case ISO7816.SW_WRONG_DATA: + str = "WRONG_DATA"; + break; + case ISO7816.SW_WRONG_LENGTH: + str = "WRONG_LENGTH"; + break; + case ISO7816.SW_WRONG_P1P2: + str = "WRONG_P1P2"; + break; + case CryptoException.ILLEGAL_VALUE: + str = "ILLEGAL_VALUE"; + break; + case CryptoException.UNINITIALIZED_KEY: + str = "UNINITIALIZED_KEY"; + break; + case CryptoException.NO_SUCH_ALGORITHM: + str = "NO_SUCH_ALG"; + break; + case CryptoException.INVALID_INIT: + str = "INVALID_INIT"; + break; + case CryptoException.ILLEGAL_USE: + str = "ILLEGAL_USE"; + break; + case ECTesterApplet.SW_SIG_VERIFY_FAIL: + str = "SIG_VERIFY_FAIL"; + break; + case ECTesterApplet.SW_DH_DHC_MISMATCH: + str = "DH_DHC_MISMATCH"; + break; + case ECTesterApplet.SW_KEYPAIR_NULL: + str = "KEYPAIR_NULL"; + break; + case ECTesterApplet.SW_KA_NULL: + str = "KA_NULL"; + break; + case ECTesterApplet.SW_SIGNATURE_NULL: + str = "SIGNATURE_NULL"; + break; + case ECTesterApplet.SW_OBJECT_NULL: + str = "OBJECT_NULL"; + break; + default: + str = "unknown"; + break; + } + return str; + } + + public static String getSWString(short sw) { + if (sw == ISO7816.SW_NO_ERROR) { + return "OK\t(0x9000)"; + } else { + String str = getSW(sw); + return String.format("fail\t(%s, 0x%04x)", str, sw); + } + } + + public static String getCorruption(short corruptionType) { + String corrupt; + switch (corruptionType) { + case EC_Consts.CORRUPTION_NONE: + corrupt = "NONE"; + break; + case EC_Consts.CORRUPTION_FIXED: + corrupt = "FIXED"; + break; + case EC_Consts.CORRUPTION_ONE: + corrupt = "ONE"; + break; + case EC_Consts.CORRUPTION_ZERO: + corrupt = "ZERO"; + break; + case EC_Consts.CORRUPTION_ONEBYTERANDOM: + corrupt = "ONE_BYTE_RANDOM"; + break; + case EC_Consts.CORRUPTION_FULLRANDOM: + corrupt = "FULL_RANDOM"; + break; + case EC_Consts.CORRUPTION_INCREMENT: + corrupt = "INCREMENT"; + break; + case EC_Consts.CORRUPTION_INFINITY: + corrupt = "INFINITY"; + break; + case EC_Consts.CORRUPTION_COMPRESS: + corrupt = "COMPRESSED"; + break; + case EC_Consts.CORRUPTION_MAX: + corrupt = "MAX"; + break; + default: + corrupt = "unknown"; + break; + } + return corrupt; + } + + public static String getKA(byte ka) { + String algo = ""; + if ((ka & EC_Consts.KA_ECDH) != 0 || ka == EC_Consts.KA_ANY) { + algo += "ECDH"; + } + if (ka == EC_Consts.KA_BOTH) { + algo += "+"; + } else if (ka == EC_Consts.KA_ANY) { + algo += "/"; + } + if ((ka & EC_Consts.KA_ECDHC) != 0 || ka == EC_Consts.KA_ANY) { + algo += "ECDHC"; + } + return algo; + } +} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Category.java b/src/cz/crcs/ectester/reader/ec/EC_Category.java new file mode 100644 index 0000000..97dd1b4 --- /dev/null +++ b/src/cz/crcs/ectester/reader/ec/EC_Category.java @@ -0,0 +1,140 @@ +package cz.crcs.ectester.reader.ec; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Category { + + private String name; + private String directory; + private String desc; + + private Map<String, EC_Data> objects; + + + public EC_Category(String name, String directory) { + this.name = name; + this.directory = directory; + } + + public EC_Category(String name, String directory, String desc) { + this(name, directory); + this.desc = desc; + } + + public EC_Category(String name, String directory, String desc, Map<String, EC_Data> objects) { + this(name, directory, desc); + this.objects = objects; + } + + public String getName() { + return name; + } + + public String getDirectory() { + return directory; + } + + public String getDesc() { + return desc; + } + + public Map<String, EC_Data> getObjects() { + return Collections.unmodifiableMap(objects); + } + + public <T extends EC_Data> Map<String, T> getObjects(Class<T> cls) { + Map<String, T> objs = new TreeMap<>(); + for (Map.Entry<String, EC_Data> entry : objects.entrySet()) { + if (cls.isInstance(entry.getValue())) { + objs.put(entry.getKey(), cls.cast(entry.getValue())); + } + } + return Collections.unmodifiableMap(objs); + } + + public <T extends EC_Data> T getObject(Class<T> cls, String id) { + EC_Data obj = objects.get(id); + if (cls.isInstance(obj)) { + return cls.cast(obj); + } else { + return null; + } + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("\t- ").append(name).append((desc == null || desc.equals("")) ? "" : ": " + desc); + out.append(System.lineSeparator()); + + Map<String, EC_Curve> curves = getObjects(EC_Curve.class); + int size = curves.size(); + if (size > 0) { + out.append("\t\tCurves: "); + for (Map.Entry<String, EC_Curve> curve : curves.entrySet()) { + out.append(curve.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map<String, EC_Key> keys = getObjects(EC_Key.class); + size = keys.size(); + if (size > 0) { + out.append("\t\tKeys: "); + for (Map.Entry<String, EC_Key> key : keys.entrySet()) { + out.append(key.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map<String, EC_Keypair> keypairs = getObjects(EC_Keypair.class); + size = keypairs.size(); + if (size > 0) { + out.append("\t\tKeypairs: "); + for (Map.Entry<String, EC_Keypair> key : keypairs.entrySet()) { + out.append(key.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + + Map<String, EC_KAResult> results = getObjects(EC_KAResult.class); + size = results.size(); + if (size > 0) { + out.append("\t\tResults: "); + for (Map.Entry<String, EC_KAResult> result : results.entrySet()) { + out.append(result.getKey()); + size--; + if (size > 0) + out.append(", "); + } + out.append(System.lineSeparator()); + } + return out.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof EC_Category && Objects.equals(this.name, ((EC_Category) obj).name); + } + + @Override + public int hashCode() { + return this.name.hashCode() ^ this.directory.hashCode(); + } + +} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Curve.java b/src/cz/crcs/ectester/reader/ec/EC_Curve.java new file mode 100644 index 0000000..45080fb --- /dev/null +++ b/src/cz/crcs/ectester/reader/ec/EC_Curve.java @@ -0,0 +1,50 @@ +package cz.crcs.ectester.reader.ec; + +import cz.crcs.ectester.applet.EC_Consts; +import javacard.security.KeyPair; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Curve extends EC_Params { + private short bits; + private byte field; + private String desc; + + /** + * @param bits + * @param field KeyPair.ALG_EC_FP or KeyPair.ALG_EC_F2M + */ + public EC_Curve(short bits, byte field) { + super(field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M); + this.bits = bits; + this.field = field; + } + + public EC_Curve(String id, short bits, byte field) { + this(bits, field); + this.id = id; + } + + public EC_Curve(String id, short bits, byte field, String desc) { + this(id, bits, field); + this.desc = desc; + } + + public short getBits() { + return bits; + } + + public byte getField() { + return field; + } + + public String getDesc() { + return desc; + } + + @Override + public String toString() { + return "<" + getId() + "> " + (field == KeyPair.ALG_EC_FP ? "Prime" : "Binary") + " field Elliptic curve (" + String.valueOf(bits) + "b)" + (desc == null ? "" : ": " + desc); + } +} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Data.java b/src/cz/crcs/ectester/reader/ec/EC_Data.java new file mode 100644 index 0000000..9dcbbe0 --- /dev/null +++ b/src/cz/crcs/ectester/reader/ec/EC_Data.java @@ -0,0 +1,196 @@ +package cz.crcs.ectester.reader.ec; + +import cz.crcs.ectester.reader.Util; + +import java.io.*; +import java.util.*; +import java.util.regex.Pattern; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public abstract class EC_Data { + String id; + int count; + byte[][] data; + + private static final Pattern HEX = Pattern.compile("(0x|0X)?[a-fA-F\\d]+"); + + EC_Data() { + } + + EC_Data(int count) { + this.count = count; + this.data = new byte[count][]; + } + + EC_Data(byte[][] data) { + this.count = data.length; + this.data = data; + } + + EC_Data(String id, int count) { + this(count); + this.id = id; + } + + EC_Data(String id, byte[][] data) { + this(data); + this.id = id; + } + + public String getId() { + return id; + } + + public int getCount() { + return count; + } + + public byte[][] getData() { + return data; + } + + public boolean hasData() { + return data != null; + } + + public byte[] getParam(int index) { + return data[index]; + } + + public byte[] flatten() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (byte[] param : data) { + byte[] length = new byte[2]; + Util.setShort(length, 0, (short) param.length); + + out.write(length, 0, 2); + out.write(param, 0, param.length); + } + + return out.toByteArray(); + } + + public String[] expand() { + List<String> out = new ArrayList<>(count); + for (byte[] param : data) { + out.add(Util.bytesToHex(param, false)); + } + + return out.toArray(new String[out.size()]); + } + + private static byte[] pad(byte[] data) { + if (data.length == 1) { + return new byte[]{(byte) 0, data[0]}; + } else if (data.length == 0 || data.length > 2) { + return data; + } + return null; + } + + private static byte[] parse(String param) { + byte[] data; + if (param.startsWith("0x") || param.startsWith("0X")) { + data = Util.hexToBytes(param.substring(2)); + } else { + data = Util.hexToBytes(param); + } + if (data == null) + return new byte[0]; + if (data.length < 2) + return pad(data); + return data; + } + + private boolean readHex(String[] hex) { + if (hex.length != count) { + return false; + } + + for (int i = 0; i < count; ++i) { + this.data[i] = parse(hex[i]); + } + return true; + } + + public boolean readCSV(InputStream in) { + Scanner s = new Scanner(in); + + s.useDelimiter(",|;"); + List<String> data = new LinkedList<>(); + while (s.hasNext()) { + String field = s.next(); + data.add(field.replaceAll("\\s+", "")); + } + + if (data.isEmpty()) { + return false; + } + for (String param : data) { + if (!HEX.matcher(param).matches()) { + return false; + } + } + return readHex(data.toArray(new String[data.size()])); + } + + public boolean readBytes(byte[] bytes) { + int offset = 0; + for (int i = 0; i < count; i++) { + if (bytes.length - offset < 2) { + return false; + } + short paramLength = Util.getShort(bytes, offset); + offset += 2; + if (bytes.length < offset + paramLength) { + return false; + } + data[i] = new byte[paramLength]; + System.arraycopy(bytes, offset, data[i], 0, paramLength); + offset += paramLength; + } + return true; + } + + public void writeCSV(OutputStream out) throws IOException { + Writer w = new OutputStreamWriter(out); + w.write(String.join(",", expand())); + w.flush(); + } + + @Override + public String toString() { + return String.join(",", expand()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof EC_Data) { + EC_Data other = (EC_Data) obj; + if (this.id != null || other.id != null) { + return Objects.equals(this.id, other.id); + } + + if (this.count != other.count) + return false; + for (int i = 0; i < this.count; ++i) { + if (!Arrays.equals(this.data[i], other.data[i])) { + return false; + } + } + return true; + } else { + return false; + } + } + + @Override + public int hashCode() { + if (this.id != null) { + return this.id.hashCode(); + } + return Arrays.deepHashCode(this.data); + } +} diff --git a/src/cz/crcs/ectester/reader/ec/EC_KAResult.java b/src/cz/crcs/ectester/reader/ec/EC_KAResult.java new file mode 100644 index 0000000..28115f7 --- /dev/null +++ b/src/cz/crcs/ectester/reader/ec/EC_KAResult.java @@ -0,0 +1,61 @@ +package cz.crcs.ectester.reader.ec; + +import cz.crcs.ectester.reader.Util; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_KAResult extends EC_Data { + + private byte ka; + private String curve; + private String oneKey; + private String otherKey; + + private String desc; + + public EC_KAResult(byte ka, String curve, String oneKey, String otherKey) { + super(1); + this.ka = ka; + this.curve = curve; + this.oneKey = oneKey; + this.otherKey = otherKey; + } + + public EC_KAResult(String id, byte ka, String curve, String oneKey, String otherKey) { + this(ka, curve, oneKey, otherKey); + this.id = id; + } + + public EC_KAResult(String id, byte ka, String curve, String oneKey, String otherKey, String desc) { + this(id, ka, curve, oneKey, otherKey); + this.desc = desc; + } + + public byte getKA() { + return ka; + } + + public String getCurve() { + return curve; + } + + public String getOneKey() { + return oneKey; + } + + public String getOtherKey() { + return otherKey; + } + + public String getDesc() { + return desc; + } + + @Override + public String toString() { + String algo = Util.getKA(ka); + return "<" + getId() + "> " + algo + " result over " + curve + ", " + oneKey + " + " + otherKey + (desc == null ? "" : ": " + desc); + } + +} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Key.java b/src/cz/crcs/ectester/reader/ec/EC_Key.java new file mode 100644 index 0000000..cecd228 --- /dev/null +++ b/src/cz/crcs/ectester/reader/ec/EC_Key.java @@ -0,0 +1,72 @@ +package cz.crcs.ectester.reader.ec; + +import cz.crcs.ectester.applet.EC_Consts; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Key extends EC_Params { + + private String curve; + private String desc; + + private EC_Key(short mask, String curve) { + super(mask); + this.curve = curve; + } + + private EC_Key(short mask, String curve, String desc) { + this(mask, curve); + this.desc = desc; + } + + private EC_Key(String id, short mask, String curve) { + this(mask, curve); + this.id = id; + } + + private EC_Key(String id, short mask, String curve, String desc) { + this(mask, curve, desc); + this.id = id; + } + + public String getCurve() { + return curve; + } + + public String getDesc() { + return desc; + } + + public static class Public extends EC_Key { + + public Public(String curve) { + super(EC_Consts.PARAMETER_W, curve); + } + + public Public(String curve, String desc) { + super(EC_Consts.PARAMETER_W, curve, desc); + } + + @Override + public String toString() { + return "EC Public key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc()); + } + } + + public static class Private extends EC_Key { + + public Private(String curve) { + super(EC_Consts.PARAMETER_S, curve); + } + + public Private(String curve, String desc) { + super(EC_Consts.PARAMETER_S, curve, desc); + } + + @Override + public String toString() { + return "<" + getId() + "> EC Private key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc()); + } + } +} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Keypair.java b/src/cz/crcs/ectester/reader/ec/EC_Keypair.java new file mode 100644 index 0000000..924906e --- /dev/null +++ b/src/cz/crcs/ectester/reader/ec/EC_Keypair.java @@ -0,0 +1,34 @@ +package cz.crcs.ectester.reader.ec; + +import cz.crcs.ectester.applet.EC_Consts; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Keypair extends EC_Params { + private String curve; + private String desc; + + public EC_Keypair(String curve) { + super(EC_Consts.PARAMETERS_KEYPAIR); + this.curve = curve; + } + + public EC_Keypair(String curve, String desc) { + this(curve); + this.desc = desc; + } + + public String getCurve() { + return curve; + } + + public String getDesc() { + return desc; + } + + @Override + public String toString() { + return "<" + getId() + "> EC Keypair, over " + curve + (desc == null ? "" : ": " + desc); + } +} diff --git a/src/cz/crcs/ectester/reader/ec/EC_Params.java b/src/cz/crcs/ectester/reader/ec/EC_Params.java new file mode 100644 index 0000000..ea2e633 --- /dev/null +++ b/src/cz/crcs/ectester/reader/ec/EC_Params.java @@ -0,0 +1,146 @@ +package cz.crcs.ectester.reader.ec; + +import cz.crcs.ectester.applet.EC_Consts; +import cz.crcs.ectester.reader.Util; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Jan Jancar johny@neuromancer.sk + */ +public class EC_Params extends EC_Data { + private short params; + + public EC_Params(short params) { + this.params = params; + this.count = numParams(); + this.data = new byte[this.count][]; + } + + public EC_Params(short params, byte[][] data) { + this.params = params; + this.count = data.length; + this.data = data; + } + + public EC_Params(String id, short params) { + this(params); + this.id = id; + } + + public EC_Params(String id, short params, byte[][] data) { + this(params, data); + this.id = id; + } + + public short getParams() { + return params; + } + + public boolean hasParam(short param) { + return (params & param) != 0; + } + + public int numParams() { + short paramMask = EC_Consts.PARAMETER_FP; + int num = 0; + while (paramMask <= EC_Consts.PARAMETER_S) { + if ((paramMask & params) != 0) { + if (paramMask == EC_Consts.PARAMETER_F2M) { + num += 3; + } + if (paramMask == EC_Consts.PARAMETER_W || paramMask == EC_Consts.PARAMETER_G) { + num += 1; + } + ++num; + } + paramMask = (short) (paramMask << 1); + } + return num; + } + + @Override + public byte[] flatten() { + return flatten(params); + } + + public byte[] flatten(short params) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + short paramMask = EC_Consts.PARAMETER_FP; + int i = 0; + while (paramMask <= EC_Consts.PARAMETER_S) { + short masked = (short) (this.params & params & paramMask); + short shallow = (short) (this.params & paramMask); + if (masked != 0) { + byte[] param = data[i]; + if (masked == EC_Consts.PARAMETER_F2M) { + //add m, e_1, e_2, e_3 + param = Util.concatenate(param, data[i + 1]); + if (!Util.allValue(data[i + 2], (byte) 0)) { + param = Util.concatenate(param, data[i + 2]); + } + if (!Util.allValue(data[i + 3], (byte) 0)) { + param = Util.concatenate(param, data[i + 3]); + } + if (!(param.length == 4 || param.length == 8)) + throw new RuntimeException("PARAMETER_F2M length is not 8.(should be)"); + } + if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) { + //read another param (the y coord) and put into X962 format. + byte[] y = data[i + 1]; + param = Util.concatenate(new byte[]{4}, param, y); //<- ugly but works! + } + if (param.length == 0) + throw new RuntimeException("Empty parameter read?"); + + //write length + byte[] length = new byte[2]; + Util.setShort(length, 0, (short) param.length); + out.write(length, 0, 2); + //write data + out.write(param, 0, param.length); + } + if (shallow == EC_Consts.PARAMETER_F2M) { + i += 4; + } else if (shallow == EC_Consts.PARAMETER_G || shallow == EC_Consts.PARAMETER_W) { + i += 2; + } else if (shallow != 0) { + i++; + } + paramMask = (short) (paramMask << 1); + } + + return (out.size() == 0) ? null : out.toByteArray(); + } + + @Override + public String[] expand() { + List<String> out = new ArrayList<>(); + + short paramMask = EC_Consts.PARAMETER_FP; + int index = 0; + while (paramMask <= EC_Consts.PARAMETER_S) { + short masked = (short) (params & paramMask); + if (masked != 0) { + byte[] param = data[index]; + if (masked == EC_Consts.PARAMETER_F2M) { + for (int i = 0; i < 4; ++i) { + out.add(Util.bytesToHex(data[index + i], false)); + } + index += 4; + } else if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) { + out.add(Util.bytesToHex(param, false)); + out.add(Util.bytesToHex(data[index + 1], false)); + index += 2; + } else { + out.add(Util.bytesToHex(param, false)); + index++; + } + } + paramMask = (short) (paramMask << 1); + } + return out.toArray(new String[out.size()]); + } +} |
