From 6347f0f5dc7eff1a48c22d498dc8007c8a0ff995 Mon Sep 17 00:00:00 2001 From: Martin Paljak Date: Fri, 21 Feb 2020 09:38:24 +0200 Subject: [PATCH] Clean up for apdu4j (#205) * Clean up for apdu4j * remove exit --- .project | 17 - build.xml | 66 - ext/README.md | 4 - library/pom.xml | 16 +- pom.xml | 9 +- tool/pom.xml | 40 +- tool/src/main/java/pro/javacard/gp/GP.java | 41 + .../javacard/gp/GPCommandLineInterface.java | 9 +- .../src/main/java/pro/javacard/gp/GPTool.java | 1251 ++++++++--------- 9 files changed, 649 insertions(+), 804 deletions(-) delete mode 100644 .project delete mode 100644 build.xml delete mode 100644 ext/README.md create mode 100644 tool/src/main/java/pro/javacard/gp/GP.java diff --git a/.project b/.project deleted file mode 100644 index 2da04efa..00000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - GlobalPlatformPro - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/build.xml b/build.xml deleted file mode 100644 index fa553c0e..00000000 --- a/build.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - ANT build file for GlobalPlatformPro (javacard.pro) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Not releasing ${release.version}. - - - - - - diff --git a/ext/README.md b/ext/README.md deleted file mode 100644 index b104695c..00000000 --- a/ext/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Windows binary -To make the Windows .exe, download launch4j to this folder, for example: - -https://downloads.sourceforge.net/project/launch4j/launch4j-3/3.11/launch4j-3.11-linux-x64.tgz diff --git a/library/pom.xml b/library/pom.xml index d0e046cb..72e5504e 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -18,13 +18,13 @@ com.github.martinpaljak apdu4j-core - 19.05.08 + 2020b1 com.github.martinpaljak capfile - 19.03.04 + 20.02.19 @@ -32,12 +32,6 @@ slf4j-api 1.7.30 - - - com.google.code.gson - gson - 2.8.6 - org.bouncycastle @@ -48,13 +42,13 @@ com.payneteasy ber-tlv - 1.0-9 + 1.0-10 - + org.testng testng - 6.14.3 + 7.1.0 test diff --git a/pom.xml b/pom.xml index f7635aee..23547ad9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.github.martinpaljak metacard - 20.01.14 + 20.02.19 20.01.23 gppro @@ -25,11 +25,16 @@ https://github.com/martinpaljak/GlobalPlatformPro scm:git:git@github.com:martinpaljak/globalplatformpro.git + + + javacard-pro + https://javacard.pro/maven/ + + tool library - diff --git a/tool/pom.xml b/tool/pom.xml index 92c24af3..47b5c877 100644 --- a/tool/pom.xml +++ b/tool/pom.xml @@ -11,32 +11,21 @@ gptool + GlobalPlatformPro CLI tool com.github.martinpaljak apdu4j-pcsc - 19.05.08 + 20.01.01 - + com.github.martinpaljak globalplatformpro 20.01.23 - - - com.google.code.gson - gson - 2.8.5 - - - - org.apache.httpcomponents - httpclient - 4.5.10 - org.slf4j @@ -44,23 +33,24 @@ 1.7.30 true - - - org.slf4j - slf4j-api - 1.7.30 - net.sf.jopt-simple jopt-simple 5.0.4 + + + com.google.auto.service + auto-service + 1.0-rc6 + true + org.testng testng - 6.14.3 + 7.1.0 test @@ -122,11 +112,15 @@ 1.8.0 - ${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.${parsedVersion.buildNumber} + + ${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.${parsedVersion.buildNumber} + ${project.version} GlobalPlatformPro (C) 2015 - 2019 Martin Paljak and contributors (LGPL+MIT) - ${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.${parsedVersion.buildNumber} + + ${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.${parsedVersion.buildNumber} + ${project.version} GlobalPlatformPro gppro diff --git a/tool/src/main/java/pro/javacard/gp/GP.java b/tool/src/main/java/pro/javacard/gp/GP.java new file mode 100644 index 00000000..9365fa0d --- /dev/null +++ b/tool/src/main/java/pro/javacard/gp/GP.java @@ -0,0 +1,41 @@ +/* + * GlobalPlatformPro - GlobalPlatform tool + * + * Copyright (C) 2015-2020 Martin Paljak, martin@martinpaljak.net + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +package pro.javacard.gp; + +import apdu4j.BIBO; +import apdu4j.i.SmartCardApp; +import com.google.auto.service.AutoService; + +/** + * Provides an apdu4j compatible SmartCardApp interface + */ +@AutoService(SmartCardApp.class) +public final class GP implements SmartCardApp { + + // Public constructor for service initialization + public GP() { + } + + @Override + public int run(BIBO bibo, String[] args) { + return new GPTool().run(bibo, args); + } +} diff --git a/tool/src/main/java/pro/javacard/gp/GPCommandLineInterface.java b/tool/src/main/java/pro/javacard/gp/GPCommandLineInterface.java index 37378259..1578db9c 100644 --- a/tool/src/main/java/pro/javacard/gp/GPCommandLineInterface.java +++ b/tool/src/main/java/pro/javacard/gp/GPCommandLineInterface.java @@ -43,7 +43,6 @@ abstract class GPCommandLineInterface { protected final static String OPT_DELETE_KEY = "delete-key"; protected final static String OPT_DOMAIN = "domain"; protected final static String OPT_MOVE = "move"; - protected final static String OPT_DUMP = "dump"; protected final static String OPT_EMV = "emv"; protected final static String OPT_FORCE = "force"; protected final static String OPT_INFO = "info"; @@ -79,7 +78,6 @@ abstract class GPCommandLineInterface { protected final static String OPT_PUT_KEY = "put-key"; protected final static String OPT_READER = "reader"; protected final static String OPT_RENAME_ISD = "rename-isd"; - protected final static String OPT_REPLAY = "replay"; protected final static String OPT_SC_MODE = "mode"; protected final static String OPT_SDAID = "sdaid"; protected final static String OPT_SECURE_APDU = "secure-apdu"; @@ -89,7 +87,6 @@ abstract class GPCommandLineInterface { protected final static String OPT_SHA256 = "sha256"; protected final static String OPT_STORE_DATA = "store-data"; protected final static String OPT_STORE_DATA_CHUNK = "store-data-chunk"; - protected final static String OPT_TERMINALS = "terminals"; protected final static String OPT_TERMINATE = "terminate"; protected final static String OPT_TODAY = "today"; protected final static String OPT_TO = "to"; @@ -100,6 +97,7 @@ abstract class GPCommandLineInterface { protected final static String OPT_VERBOSE = "verbose"; protected final static String OPT_VERSION = "version"; protected final static String OPT_VISA2 = "visa2"; + // TODO: extract SEAC to a separate utility protected final static String OPT_ACR_LIST = "acr-list"; protected final static String OPT_ACR_LIST_ARAM = "acr-list-aram"; protected final static String OPT_ACR_ADD = "acr-add"; @@ -125,11 +123,6 @@ protected static OptionSet parseArguments(String[] argv) throws IOException { parser.acceptsAll(Arrays.asList("a", OPT_APDU), "Send raw APDU (hex)").withRequiredArg().describedAs("APDU"); parser.acceptsAll(Arrays.asList("s", OPT_SECURE_APDU), "Send raw APDU (hex) via SCP").withRequiredArg().describedAs("APDU"); parser.acceptsAll(Arrays.asList("f", OPT_FORCE), "Force operation"); - parser.accepts(OPT_DUMP, "Dump APDU communication to ").withRequiredArg().ofType(File.class); - parser.accepts(OPT_REPLAY, "Replay APDU responses from ").withRequiredArg().ofType(File.class); - - // Special options - parser.accepts(OPT_TERMINALS, "Use PC/SC provider from ").withRequiredArg(); // Applet operation options parser.accepts(OPT_CAP, "Use a CAP file as source").withRequiredArg().ofType(File.class); diff --git a/tool/src/main/java/pro/javacard/gp/GPTool.java b/tool/src/main/java/pro/javacard/gp/GPTool.java index 2e992bad..1733e767 100644 --- a/tool/src/main/java/pro/javacard/gp/GPTool.java +++ b/tool/src/main/java/pro/javacard/gp/GPTool.java @@ -20,9 +20,7 @@ */ package pro.javacard.gp; -import apdu4j.CommandAPDU; import apdu4j.*; -import apdu4j.providers.APDUReplayProvider; import apdu4j.terminals.LoggingCardTerminal; import joptsimple.OptionSet; import pro.javacard.AID; @@ -34,9 +32,12 @@ import pro.javacard.gp.PlaintextKeys.Diversification; import javax.crypto.Cipher; -import javax.smartcardio.*; -import javax.smartcardio.CardTerminals.State; -import java.io.*; +import javax.smartcardio.CardTerminal; +import javax.smartcardio.TerminalFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.RSAPrivateKey; @@ -44,785 +45,689 @@ import java.util.*; import java.util.stream.Collectors; +// Does the CLI parameter parsing and associated execution public final class GPTool extends GPCommandLineInterface { - private static boolean isVerbose = false; + private boolean isVerbose = false; - public static void main(String[] argv) throws Exception { - OptionSet args = parseArguments(argv); - - // Set up slf4j simple in a way that pleases us - System.setProperty("org.slf4j.simpleLogger.showThreadName", "false"); - System.setProperty("org.slf4j.simpleLogger.levelInBrackets", "true"); - System.setProperty("org.slf4j.simpleLogger.showShortLogName", "true"); - - if (args.has(OPT_VERBOSE)) { - System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug"); - isVerbose = true; - } else { - System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "warn"); + // To keep basic gp.jar together with apdu4j app, this is just a minimalist wrapper + public static void main(String[] argv) { + try { + OptionSet args = parseArguments(argv); + TerminalFactory tf = TerminalManager.getTerminalFactory(); + String reader = (String) args.valueOf(OPT_READER); + if (reader == null) + reader = System.getenv("GP_READER"); + Optional t = TerminalManager.getInstance(tf.terminals()).dwim(reader, System.getenv("GP_READER_IGNORE"), Collections.emptyList()); + if (!t.isPresent()) { + System.err.println("Specify reader with -r/$GP_READER"); + System.exit(1); + } + t = t.map(e -> args.has(OPT_DEBUG) ? LoggingCardTerminal.getInstance(e) : e); + int ret = new GPTool().run(CardBIBO.wrap(t.get().connect("*")), argv); + System.exit(ret); + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + System.exit(1); } + } - if (args.has(OPT_DEBUG)) { - System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "trace"); - } + public int run(BIBO bibo, String[] argv) { + try { + OptionSet args = parseArguments(argv); - if (args.has(OPT_VERSION) || args.has(OPT_VERBOSE) || args.has(OPT_DEBUG) || args.has(OPT_INFO)) { - String version = GPSession.getVersion(); - // Append host information - version += "\nRunning on " + System.getProperty("os.name"); - version += " " + System.getProperty("os.version"); - version += " " + System.getProperty("os.arch"); - version += ", Java " + System.getProperty("java.version"); - version += " by " + System.getProperty("java.vendor"); - System.out.println("GlobalPlatformPro " + version); - - // Test for unlimited crypto - if (Cipher.getMaxAllowedKeyLength("AES") == 128) { - System.out.println("Unlimited crypto policy is NOT installed!"); + // Set up slf4j simple in a way that pleases us + System.setProperty("org.slf4j.simpleLogger.showThreadName", "false"); + System.setProperty("org.slf4j.simpleLogger.levelInBrackets", "true"); + System.setProperty("org.slf4j.simpleLogger.showShortLogName", "true"); + + if (args.has(OPT_VERBOSE)) { + System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug"); + isVerbose = true; + } else { + System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "warn"); } - } - // Load a CAP file, if specified - CAPFile cap = null; - if (args.has(OPT_CAP)) { - File capfile = (File) args.valueOf(OPT_CAP); - try (FileInputStream fin = new FileInputStream(capfile)) { - cap = CAPFile.fromStream(fin); + if (args.has(OPT_DEBUG)) { + System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "trace"); } - if (args.has(OPT_INFO)) { - System.out.println("**** CAP info of " + capfile.getName()); - cap.dump(System.out); - if (args.specs().size() == 2) { - // Exit after --cap --info - System.exit(0); + + if (args.has(OPT_VERSION) || args.has(OPT_VERBOSE) || args.has(OPT_DEBUG) || args.has(OPT_INFO)) { + String version = GPSession.getVersion(); + // Append host information + version += "\nRunning on " + System.getProperty("os.name"); + version += " " + System.getProperty("os.version"); + version += " " + System.getProperty("os.arch"); + version += ", Java " + System.getProperty("java.version"); + version += " by " + System.getProperty("java.vendor"); + System.out.println("GlobalPlatformPro " + version); + + // Test for unlimited crypto + if (Cipher.getMaxAllowedKeyLength("AES") == 128) { + System.out.println("Unlimited crypto policy is NOT installed!"); } } - } - if (args.has(OPT_LIST_PRIVS)) { - System.out.println("# Known privileges:"); - System.out.println(Arrays.asList(Privilege.values()).stream().map(i -> i.toString()).collect(Collectors.joining("\n"))); - } + // Load a CAP file, if specified + CAPFile cap = null; + if (args.has(OPT_CAP)) { + File capfile = (File) args.valueOf(OPT_CAP); + try (FileInputStream fin = new FileInputStream(capfile)) { + cap = CAPFile.fromStream(fin); + } + if (args.has(OPT_INFO)) { + System.out.println("**** CAP info of " + capfile.getName()); + cap.dump(System.out); + if (args.specs().size() == 2) { + // Exit after --cap --info + return 0; + } + } + } - // Now actually talk to possible terminals - try { - final TerminalFactory tf; + if (args.has(OPT_LIST_PRIVS)) { + System.out.println("# Known privileges:"); + System.out.println(Arrays.asList(Privilege.values()).stream().map(i -> i.toString()).collect(Collectors.joining("\n"))); + } - if (args.has(OPT_REPLAY)) { - // Replay responses from a file - // FIXME: use the generic provider interface and drop command line options - File f = (File) args.valueOf(OPT_REPLAY); - try (FileInputStream fin = new FileInputStream(f)) { - tf = TerminalFactory.getInstance("PC/SC", fin, new APDUReplayProvider()); + // Now actually talk to possible terminals + APDUBIBO channel = new APDUBIBO(bibo); + // Send all raw APDU-s to the default-selected application of the card + if (args.has(OPT_APDU)) { + // Select the application, if present + AID target = null; + if (args.has(OPT_APPLET)) { + target = AID.fromString(args.valueOf(OPT_APPLET)); + } else if (cap != null) { + target = cap.getAppletAIDs().get(0); // FIXME: generalize and only work if one + } + if (target != null) { + verbose("Selecting " + target); + channel.transmit(new CommandAPDU(0x00, ISO7816.INS_SELECT, 0x04, 0x00, target.getBytes())); } + for (Object s : args.valuesOf(OPT_APDU)) { + CommandAPDU c = new CommandAPDU(HexUtils.stringToBin((String) s)); + channel.transmit(c); + } + } + + Map env = System.getenv(); + + // GlobalPlatform specific + final GPSession gp; + if (args.has(OPT_SDAID)) { + gp = GPSession.connect(channel, AID.fromString(args.valueOf(OPT_SDAID))); + } else if (env.containsKey("GP_AID")) { + gp = GPSession.connect(channel, AID.fromString(env.get("GP_AID"))); } else { - tf = TerminalManager.getTerminalFactory((String) args.valueOf(OPT_TERMINALS)); + gp = GPSession.discover(channel); } - CardTerminals terminals = tf.terminals(); + if (args.has(OPT_TOKEN_KEY)) { + gp.setDMTokenGenerator(new DMTokenGenerator(getRSAPrivateKey(args.valueOf(OPT_TOKEN_KEY).toString()))); + } else { + gp.setDMTokenGenerator(new DMTokenGenerator(null)); + } - // List terminals if needed - if (args.has(OPT_DEBUG)) { - System.out.println("# Detected readers from " + tf.getProvider().getName()); - for (CardTerminal term : terminals.list()) { - String c = " "; - if (term.isCardPresent()) { - c = "*"; - if (ignoreReader(term.getName())) { - c = "I"; - } - } - System.out.println("[" + c + "] " + term.getName()); - } + // Don't do sanity checks, just run asked commands + if (args.has(OPT_FORCE)) + gp.setStrict(false); + + // Extract information + if (args.has(OPT_INFO)) { + GPData.dump(channel); } - // Select terminal(s) to work on - List do_readers; - if (args.has(OPT_READER) || System.getenv().containsKey("GP_READER")) { - String reader = System.getenv("GP_READER"); - if (args.has(OPT_READER)) - reader = (String) args.valueOf(OPT_READER); - CardTerminal t = terminals.getTerminal(reader); - if (t == null) { - fail("Reader \"" + reader + "\" not found."); - } - do_readers = Arrays.asList(t); + // Normally assume a single master key + final GPCardKeys keys; + + if (args.has(OPT_KEYS)) { + // keys come from custom provider + fail("Not yet implemented"); + keys = PlaintextKeys.defaultKey(); } else { - List tmp = terminals.list(State.CARD_PRESENT); - do_readers = new ArrayList<>(); - for (CardTerminal t : tmp) { - if (!ignoreReader(t.getName())) { - do_readers.add(t); + PlaintextKeys keyz; + if (args.has(OPT_KEY)) { + byte[] k = HexUtils.stringToBin((String) args.valueOf(OPT_KEY)); + byte[] kcv = null; + + if (args.has(OPT_KCV)) { + kcv = HexUtils.stringToBin((String) args.valueOf(OPT_KCV)); + } + + keyz = PlaintextKeys.fromMasterKey(k, kcv); + } else { + Optional params = SecureChannelParameters.fromEnvironment(); + // XXX: better checks for exclusive key options + if (args.has(OPT_KEY_MAC) && args.has(OPT_KEY_ENC) && args.has(OPT_KEY_DEK)) { + byte[] enc = HexUtils.stringToBin((String) args.valueOf(OPT_KEY_ENC)); + byte[] mac = HexUtils.stringToBin((String) args.valueOf(OPT_KEY_MAC)); + byte[] dek = HexUtils.stringToBin((String) args.valueOf(OPT_KEY_DEK)); + keyz = PlaintextKeys.fromKeys(enc, mac, dek); + } else if (params.isPresent()) { + keyz = (PlaintextKeys) params.get().getCardKeys(); } else { - if (args.has(OPT_VERBOSE)) { - System.out.println("# Ignoring " + t.getName()); + if (needsAuthentication(args)) { + System.out.println("Warning: no keys given, using default test key " + HexUtils.bin2hex(PlaintextKeys.defaultKeyBytes)); } + keyz = PlaintextKeys.defaultKey(); } } - } - if (do_readers.size() == 0) { - fail("No smart card readers with a card found"); + // "gp -l -emv" should still work + if (args.has(OPT_VISA2)) { + keyz.setDiversifier(Diversification.VISA2); + } else if (args.has(OPT_EMV)) { + keyz.setDiversifier(Diversification.EMV); + } else if (args.has(OPT_KDF3)) { + keyz.setDiversifier(Diversification.KDF3); + } else if (args.has(OPT_KDF)) { + keyz.setDiversifier(getDiversificationOrFail(args, OPT_KDF)); + } + + if (args.has(OPT_KEY_VERSION)) { + keyz.setVersion(GPUtils.intValue((String) args.valueOf(OPT_KEY_VERSION))); + } + keys = keyz; } - // Work all readers - for (CardTerminal reader : do_readers) { - if (do_readers.size() > 1) { - System.out.println("# " + reader.getName()); - } - // Wrap with logging if requested - if (args.has(OPT_DEBUG)) { - // And with APDU dumping - OutputStream o = null; - if (args.has(OPT_DUMP)) { - File f = (File) args.valueOf(OPT_DUMP); - o = new FileOutputStream(f); - } - reader = LoggingCardTerminal.getInstance(reader, o); - } - - Card card = null; - APDUBIBO channel = null; - try { - // Establish connection - try { - card = reader.connect("*"); - // We use apdu4j which by default uses jnasmartcardio - // which uses real SCardBeginTransaction - card.beginExclusive(); - channel = CardChannelBIBO.getBIBO(card.getBasicChannel()); - } catch (CardException e) { - System.err.println("Could not connect to " + reader.getName() + ": " + TerminalManager.getExceptionMessage(e)); - continue; - } - - if (args.has(OPT_INFO) || args.has(OPT_VERBOSE)) { - System.out.println("Reader: " + reader.getName()); - System.out.println("ATR: " + HexUtils.bin2hex(card.getATR().getBytes())); - System.out.println("More information about your card:"); - System.out.println(" http://smartcard-atr.appspot.com/parse?ATR=" + HexUtils.bin2hex(card.getATR().getBytes())); - System.out.println(); - } - - // Send all raw APDU-s to the default-selected application of the card - if (args.has(OPT_APDU)) { - // Select the application, if present - AID target = null; - if (args.has(OPT_APPLET)) { - target = AID.fromString(args.valueOf(OPT_APPLET)); - } else if (cap != null) { - target = cap.getAppletAIDs().get(0); // FIXME: generalize and only work if one - } - if (target != null) { - verbose("Selecting " + target); - channel.transmit(new CommandAPDU(0x00, ISO7816.INS_SELECT, 0x04, 0x00, target.getBytes())); - } - for (Object s : args.valuesOf(OPT_APDU)) { - CommandAPDU c = new CommandAPDU(HexUtils.stringToBin((String) s)); - channel.transmit(c); - } - } + // XXX: leftover + if (args.has(OPT_OP201)) { + gp.setSpec(GPSpec.OP201); + } - Map env = System.getenv(); + // Override block size for stupidly broken readers. + // See https://github.com/martinpaljak/GlobalPlatformPro/issues/32 + // The name of the option comes from a common abbreviation as well as dd utility + if (args.has(OPT_BS)) { + gp.setBlockSize((int) args.valueOf(OPT_BS)); + } - // GlobalPlatform specific - final GPSession gp; - if (args.has(OPT_SDAID)) { - gp = GPSession.connect(channel, AID.fromString(args.valueOf(OPT_SDAID))); - } else if (env.containsKey("GP_AID")) { - gp = GPSession.connect(channel, AID.fromString(env.get("GP_AID"))); - } else { - // Oracle only applies if no other arguments given - gp = GPSession.discover(channel); - // FIXME: would like to get AID from oracle as well. - } + // list access rules from ARA-M TODO: move to separate util + if (args.has(OPT_ACR_LIST_ARAM)) { + SEAccessControlUtility.acrList(gp); + } - if (args.has(OPT_TOKEN_KEY)) { - gp.setDMTokenGenerator(new DMTokenGenerator(getRSAPrivateKey(args.valueOf(OPT_TOKEN_KEY).toString()))); - } else { - gp.setDMTokenGenerator(new DMTokenGenerator(null)); + // Authenticate, only if needed + if (needsAuthentication(args)) { + EnumSet mode = GPSession.defaultMode.clone(); + // Override default mode if needed. + if (args.has(OPT_SC_MODE)) { + mode.clear(); + for (Object s : args.valuesOf(OPT_SC_MODE)) { + mode.add(APDUMode.fromString((String) s)); } + } - // Don't do sanity checks, just run asked commands - if (args.has(OPT_FORCE)) - gp.setStrict(false); + // IMPORTANT PLACE. Possibly brick the card now, if keys don't match. + gp.openSecureChannel(keys, null, null, mode); - // Extract information - if (args.has(OPT_INFO)) { - GPData.dump(channel); + // --secure-apdu or -s + if (args.has(OPT_SECURE_APDU)) { + for (Object s : args.valuesOf(OPT_SECURE_APDU)) { + CommandAPDU c = new CommandAPDU(HexUtils.stringToBin((String) s)); + gp.transmit(c); } + } - // Normally assume a single master key - final GPCardKeys keys; + // list access rules from ARA-* via STORE DATA + if (args.has(OPT_ACR_LIST)) { + SEAccessControl.AcrListFetcher fetcher = new SEAccessControl.AcrListFetcher(gp); + byte[] r = fetcher.get(args.has(OPT_ACR_AID) ? AID.fromString(args.valueOf(OPT_ACR_AID)) : null); + SEAccessControl.AcrListResponse resp = SEAccessControl.AcrListResponse.fromBytes(r); + SEAccessControl.printList(resp.acrList); + } - if (args.has(OPT_KEYS)) { - // keys come from custom provider - fail("Not yet implemented"); - keys = PlaintextKeys.defaultKey(); - } else { - PlaintextKeys keyz; - if (args.has(OPT_KEY)) { - byte[] k = HexUtils.stringToBin((String) args.valueOf(OPT_KEY)); - byte[] kcv = null; + // --delete or --delete --default + if (args.has(OPT_DELETE)) { + GPRegistry reg = gp.getRegistry(); - if (args.has(OPT_KCV)) { - kcv = HexUtils.stringToBin((String) args.valueOf(OPT_KCV)); - } + // DWIM: assume that default selected is the one to be deleted + if (args.has(OPT_DEFAULT)) { + Optional def = reg.getDefaultSelectedAID(); - keyz = PlaintextKeys.fromMasterKey(k, kcv); + if (def.isPresent()) { + gp.deleteAID(def.get(), false); } else { - Optional params = SecureChannelParameters.fromEnvironment(); - // XXX: better checks for exclusive key options - if (args.has(OPT_KEY_MAC) && args.has(OPT_KEY_ENC) && args.has(OPT_KEY_DEK)) { - byte[] enc = HexUtils.stringToBin((String) args.valueOf(OPT_KEY_ENC)); - byte[] mac = HexUtils.stringToBin((String) args.valueOf(OPT_KEY_MAC)); - byte[] dek = HexUtils.stringToBin((String) args.valueOf(OPT_KEY_DEK)); - keyz = PlaintextKeys.fromKeys(enc, mac, dek); - } else if (params.isPresent()) { - keyz = (PlaintextKeys) params.get().getCardKeys(); + System.err.println("Could not identify default selected application!"); + } + } + List aids = args.valuesOf(OPT_DELETE).stream().map(a -> AID.fromString(a)).collect(Collectors.toList()); + for (AID aid : aids) { + try { + // If the AID represents a package or otherwise force is enabled. + boolean deleteDeps = reg.allPackageAIDs().contains(aid) || args.has(OPT_FORCE); + gp.deleteAID(aid, deleteDeps); + } catch (GPException e) { + if (!gp.getRegistry().allAIDs().contains(aid)) { + System.err.println("Could not delete AID (not present on card): " + aid); } else { - if (needsAuthentication(args)) { - System.out.println("Warning: no keys given, using default test key " + HexUtils.bin2hex(PlaintextKeys.defaultKeyBytes)); + System.err.println("Could not delete AID: " + aid); + if (e.sw == 0x6985) { + System.err.println("Deletion not allowed. Some app still active?"); + } else { + throw e; } - keyz = PlaintextKeys.defaultKey(); } } + } + } - // "gp -l -emv" should still work - if (args.has(OPT_VISA2)) { - keyz.setDiversifier(Diversification.VISA2); - } else if (args.has(OPT_EMV)) { - keyz.setDiversifier(Diversification.EMV); - } else if (args.has(OPT_KDF3)) { - keyz.setDiversifier(Diversification.KDF3); - } else if (args.has(OPT_KDF)) { - keyz.setDiversifier(getDiversificationOrFail(args, OPT_KDF)); - } - - if (args.has(OPT_KEY_VERSION)) { - keyz.setVersion(GPUtils.intValue((String) args.valueOf(OPT_KEY_VERSION))); + // --uninstall + if (args.has(OPT_UNINSTALL)) { + List caps = getCapFileList(args, OPT_UNINSTALL); + for (CAPFile instcap : caps) { + AID aid = instcap.getPackageAID(); + if (!gp.getRegistry().allAIDs().contains(aid)) { + System.out.println(aid + " is not present on card!"); + } else { + gp.deleteAID(aid, true); + System.out.println(aid + " deleted."); } - keys = keyz; } + } - // XXX: leftover - if (args.has(OPT_OP201)) { - gp.setSpec(GPSpec.OP201); - } + // --load + if (args.has(OPT_LOAD)) { + List caps = getCapFileList(args, OPT_LOAD); + for (CAPFile loadcap : caps) { + if (isVerbose) { + loadcap.dump(System.out); + } - // Override block size for stupidly broken readers. - // See https://github.com/martinpaljak/GlobalPlatformPro/issues/32 - // The name of the option comes from a common abbreviation as well as dd utility - if (args.has(OPT_BS)) { - gp.setBlockSize((int) args.valueOf(OPT_BS)); + calculateDapPropertiesAndLoadCap(args, gp, loadcap); } + } - // list access rules from ARA-M - if (args.has(OPT_ACR_LIST_ARAM)) { - SEAccessControlUtility.acrList(gp); + // --put-key + // Load a RSA public key (for DAP purposes) + if (args.has(OPT_PUT_KEY)) { + int keyVersion = 0x73; // Default DAP version + if (args.has(OPT_NEW_KEY_VERSION)) { + keyVersion = GPUtils.intValue(args.valueOf(OPT_NEW_KEY_VERSION).toString()); } - // Authenticate, only if needed - if (needsAuthentication(args)) { - EnumSet mode = GPSession.defaultMode.clone(); - // Override default mode if needed. - if (args.has(OPT_SC_MODE)) { - mode.clear(); - for (Object s : args.valuesOf(OPT_SC_MODE)) { - mode.add(APDUMode.fromString((String) s)); - } + try (FileInputStream fin = new FileInputStream(new File(args.valueOf(OPT_PUT_KEY).toString()))) { + // Get public key + PublicKey key = GPCrypto.pem2PublicKey(fin); + if (key instanceof RSAPublicKey) { + gp.putKey((RSAPublicKey) key, keyVersion); } + } + } - // IMPORTANT PLACE. Possibly brick the card now, if keys don't match. - gp.openSecureChannel(keys, null, null, mode); + // --install (--applet --create --privs --params ) + if (args.has(OPT_INSTALL)) { + final File capfile; + capfile = (File) args.valueOf(OPT_INSTALL); - // --secure-apdu or -s - if (args.has(OPT_SECURE_APDU)) { - for (Object s : args.valuesOf(OPT_SECURE_APDU)) { - CommandAPDU c = new CommandAPDU(HexUtils.stringToBin((String) s)); - gp.transmit(c); - } - } + final CAPFile instcap; + try (FileInputStream fin = new FileInputStream(capfile)) { + instcap = CAPFile.fromStream(fin); + } - // list access rules from ARA-* via STORE DATA - if (args.has(OPT_ACR_LIST)) { - SEAccessControl.AcrListFetcher fetcher = new SEAccessControl.AcrListFetcher(gp); - byte[] r = fetcher.get(args.has(OPT_ACR_AID) ? AID.fromString(args.valueOf(OPT_ACR_AID)) : null); - SEAccessControl.AcrListResponse resp = SEAccessControl.AcrListResponse.fromBytes(r); - SEAccessControl.printList(resp.acrList); - } + if (args.has(OPT_VERBOSE)) { + instcap.dump(System.out); + } - // --delete or --delete --default - if (args.has(OPT_DELETE)) { - GPRegistry reg = gp.getRegistry(); + GPRegistry reg = gp.getRegistry(); - // DWIM: assume that default selected is the one to be deleted - if (args.has(OPT_DEFAULT)) { - Optional def = reg.getDefaultSelectedAID(); + // Remove existing load file + if (args.has(OPT_FORCE) && reg.allPackageAIDs().contains(instcap.getPackageAID())) { + gp.deleteAID(instcap.getPackageAID(), true); + } - if (def.isPresent()) { - gp.deleteAID(def.get(), false); - } else { - System.err.println("Could not identify default selected application!"); - } - } - List aids = args.valuesOf(OPT_DELETE).stream().map(a -> AID.fromString(a)).collect(Collectors.toList()); - for (AID aid : aids) { - try { - // If the AID represents a package or otherwise force is enabled. - boolean deleteDeps = reg.allPackageAIDs().contains(aid) || args.has(OPT_FORCE); - gp.deleteAID(aid, deleteDeps); - } catch (GPException e) { - if (!gp.getRegistry().allAIDs().contains(aid)) { - System.err.println("Could not delete AID (not present on card): " + aid); - } else { - System.err.println("Could not delete AID: " + aid); - if (e.sw == 0x6985) { - System.err.println("Deletion not allowed. Some app still active?"); - } else { - throw e; - } - } - } - } - } + // Load + if (instcap.getAppletAIDs().size() <= 1) { + calculateDapPropertiesAndLoadCap(args, gp, instcap); + } - // --uninstall - if (args.has(OPT_UNINSTALL)) { - List caps = getCapFileList(args, OPT_UNINSTALL); - for (CAPFile instcap : caps) { - AID aid = instcap.getPackageAID(); - if (!gp.getRegistry().allAIDs().contains(aid)) { - System.out.println(aid + " is not present on card!"); - } else { - gp.deleteAID(aid, true); - System.out.println(aid + " deleted."); - } - } + // Install + final AID appaid; + final AID instanceaid; + if (instcap.getAppletAIDs().size() == 0) { + return 1; + } else if (instcap.getAppletAIDs().size() > 1) { + if (args.has(OPT_APPLET)) { + appaid = AID.fromString(args.valueOf(OPT_APPLET)); + } else { + fail("CAP contains more than one applet, specify the right one with --" + OPT_APPLET); + return 1; } + } else { + appaid = instcap.getAppletAIDs().get(0); + } - // --load - if (args.has(OPT_LOAD)) { - List caps = getCapFileList(args, OPT_LOAD); - for (CAPFile loadcap : caps) { - if (isVerbose) { - loadcap.dump(System.out); - } + // override + if (args.has(OPT_CREATE)) { + instanceaid = AID.fromString(args.valueOf(OPT_CREATE)); + } else { + instanceaid = appaid; + } - calculateDapPropertiesAndLoadCap(args, gp, loadcap); - } - } + Privileges privs = getInstPrivs(args); - // --put-key - // Load a RSA public key (for DAP purposes) - if (args.has(OPT_PUT_KEY)) { - int keyVersion = 0x73; // Default DAP version - if (args.has(OPT_NEW_KEY_VERSION)) { - keyVersion = GPUtils.intValue(args.valueOf(OPT_NEW_KEY_VERSION).toString()); - } + // Remove existing default app + if (args.has(OPT_FORCE) && (reg.getDefaultSelectedAID().isPresent() && privs.has(Privilege.CardReset))) { + gp.deleteAID(reg.getDefaultSelectedAID().get(), false); + } - try (FileInputStream fin = new FileInputStream(new File(args.valueOf(OPT_PUT_KEY).toString()))) { - // Get public key - PublicKey key = GPCrypto.pem2PublicKey(fin); - if (key instanceof RSAPublicKey) { - gp.putKey((RSAPublicKey) key, keyVersion); - } - } - } + // warn + if (gp.getRegistry().allAppletAIDs().contains(instanceaid)) { + System.err.println("WARNING: Applet " + instanceaid + " already present on card"); + } - // --install (--applet --create --privs --params ) - if (args.has(OPT_INSTALL)) { - final File capfile; - capfile = (File) args.valueOf(OPT_INSTALL); + // shoot + gp.installAndMakeSelectable(instcap.getPackageAID(), appaid, instanceaid, privs, getInstParams(args)); + } - final CAPFile instcap; - try (FileInputStream fin = new FileInputStream(capfile)) { - instcap = CAPFile.fromStream(fin); - } + // --create (--applet --package or --cap ) + if (args.has(OPT_CREATE) && !args.has(OPT_INSTALL)) { + AID packageAID = null; + AID appletAID = null; - if (args.has(OPT_VERBOSE)) { - instcap.dump(System.out); - } + // Load AID-s from cap if present + if (cap != null) { + packageAID = cap.getPackageAID(); + if (cap.getAppletAIDs().size() != 1) { + throw new IllegalArgumentException("There should be only one applet in CAP. Use --" + OPT_APPLET + " instead."); + } + appletAID = cap.getAppletAIDs().get(0); + } - GPRegistry reg = gp.getRegistry(); + // override + if (args.has(OPT_PACKAGE)) { + packageAID = AID.fromString(args.valueOf(OPT_PACKAGE)); + } + if (args.has(OPT_APPLET)) { + appletAID = AID.fromString(args.valueOf(OPT_APPLET)); + } - // Remove existing load file - if (args.has(OPT_FORCE) && reg.allPackageAIDs().contains(instcap.getPackageAID())) { - gp.deleteAID(instcap.getPackageAID(), true); - } + // check + if (packageAID == null || appletAID == null) + throw new IllegalArgumentException("Need --" + OPT_PACKAGE + " and --" + OPT_APPLET + " or --" + OPT_CAP); - // Load - if (instcap.getAppletAIDs().size() <= 1) { - calculateDapPropertiesAndLoadCap(args, gp, instcap); - } + // warn + if (gp.getRegistry().allAIDs().contains(appletAID)) { + System.err.println("WARNING: Applet " + appletAID + " already present on card"); + } - // Install - final AID appaid; - final AID instanceaid; - if (instcap.getAppletAIDs().size() == 0) { - return; - } else if (instcap.getAppletAIDs().size() > 1) { - if (args.has(OPT_APPLET)) { - appaid = AID.fromString(args.valueOf(OPT_APPLET)); - } else { - fail("CAP contains more than one applet, specify the right one with --" + OPT_APPLET); - return; - } - } else { - appaid = instcap.getAppletAIDs().get(0); - } + // shoot + AID instanceAID = AID.fromString(args.valueOf(OPT_CREATE)); + gp.installAndMakeSelectable(packageAID, appletAID, instanceAID, getInstPrivs(args), getInstParams(args)); + } - // override - if (args.has(OPT_CREATE)) { - instanceaid = AID.fromString(args.valueOf(OPT_CREATE)); - } else { - instanceaid = appaid; - } + // --domain + if (args.has(OPT_DOMAIN)) { + // Arguments check + if ((args.has(OPT_ALLOW_FROM) || args.has(OPT_ALLOW_TO)) && args.has(OPT_PARAMS)) { + fail("SSD extradition options can't be used with SSD installation parameters"); + } - Privileges privs = getInstPrivs(args); + // Default AID-s + AID packageAID = new AID("A0000001515350"); + AID appletAID = new AID("A000000151535041"); - // Remove existing default app - if (args.has(OPT_FORCE) && (reg.getDefaultSelectedAID().isPresent() && privs.has(Privilege.CardReset))) { - gp.deleteAID(reg.getDefaultSelectedAID().get(), false); - } + // Override if necessary + if (args.has(OPT_PACKAGE) && args.has(OPT_APPLET)) { + packageAID = AID.fromString(args.valueOf(OPT_PACKAGE)); + appletAID = AID.fromString(args.valueOf(OPT_APPLET)); + } else { + System.out.println("Note: using default AID-s for SSD instantiation: " + appletAID + " from " + packageAID); + } + AID instanceAID = AID.fromString(args.valueOf(OPT_DOMAIN)); - // warn - if (gp.getRegistry().allAppletAIDs().contains(instanceaid)) { - System.err.println("WARNING: Applet " + instanceaid + " already present on card"); - } + // Extra privileges + Privileges privs = getInstPrivs(args); + privs.add(Privilege.SecurityDomain); - // shoot - gp.installAndMakeSelectable(instcap.getPackageAID(), appaid, instanceaid, privs, getInstParams(args)); + // Extradition rules + byte[] params = new byte[0]; + if (args.has(OPT_PARAMS)) { + params = getInstParams(args); + } else { + if (args.has(OPT_ALLOW_TO)) { + params = GPUtils.concatenate(params, new byte[]{(byte) 0x82, 0x01, 0x20}); } - - // --create (--applet --package or --cap ) - if (args.has(OPT_CREATE) && !args.has(OPT_INSTALL)) { - AID packageAID = null; - AID appletAID = null; - - // Load AID-s from cap if present - if (cap != null) { - packageAID = cap.getPackageAID(); - if (cap.getAppletAIDs().size() != 1) { - throw new IllegalArgumentException("There should be only one applet in CAP. Use --" + OPT_APPLET + " instead."); - } - appletAID = cap.getAppletAIDs().get(0); - } - - // override - if (args.has(OPT_PACKAGE)) { - packageAID = AID.fromString(args.valueOf(OPT_PACKAGE)); - } - if (args.has(OPT_APPLET)) { - appletAID = AID.fromString(args.valueOf(OPT_APPLET)); - } - - // check - if (packageAID == null || appletAID == null) - throw new IllegalArgumentException("Need --" + OPT_PACKAGE + " and --" + OPT_APPLET + " or --" + OPT_CAP); - - // warn - if (gp.getRegistry().allAIDs().contains(appletAID)) { - System.err.println("WARNING: Applet " + appletAID + " already present on card"); - } - - // shoot - AID instanceAID = AID.fromString(args.valueOf(OPT_CREATE)); - gp.installAndMakeSelectable(packageAID, appletAID, instanceAID, getInstPrivs(args), getInstParams(args)); + if (args.has(OPT_ALLOW_FROM)) { + params = GPUtils.concatenate(params, new byte[]{(byte) 0x87, 0x01, 0x20}); } + } - // --domain - if (args.has(OPT_DOMAIN)) { - // Arguments check - if ((args.has(OPT_ALLOW_FROM) || args.has(OPT_ALLOW_TO)) && args.has(OPT_PARAMS)) { - fail("SSD extradition options can't be used with SSD installation parameters"); - } - - // Default AID-s - AID packageAID = new AID("A0000001515350"); - AID appletAID = new AID("A000000151535041"); - - // Override if necessary - if (args.has(OPT_PACKAGE) && args.has(OPT_APPLET)) { - packageAID = AID.fromString(args.valueOf(OPT_PACKAGE)); - appletAID = AID.fromString(args.valueOf(OPT_APPLET)); - } else { - System.out.println("Note: using default AID-s for SSD instantiation: " + appletAID + " from " + packageAID); - } - AID instanceAID = AID.fromString(args.valueOf(OPT_DOMAIN)); - - // Extra privileges - Privileges privs = getInstPrivs(args); - privs.add(Privilege.SecurityDomain); - - // Extradition rules - byte[] params = new byte[0]; - if (args.has(OPT_PARAMS)) { - params = getInstParams(args); - } else { - if (args.has(OPT_ALLOW_TO)) { - params = GPUtils.concatenate(params, new byte[]{(byte) 0x82, 0x01, 0x20}); - } - if (args.has(OPT_ALLOW_FROM)) { - params = GPUtils.concatenate(params, new byte[]{(byte) 0x87, 0x01, 0x20}); - } - } - - // shoot - gp.installAndMakeSelectable(packageAID, appletAID, instanceAID, privs, params); - } + // shoot + gp.installAndMakeSelectable(packageAID, appletAID, instanceAID, privs, params); + } - // --move - if (args.has(OPT_MOVE)) { - if (!args.has(OPT_TO)) { - fail("Specify extradition target with --" + OPT_TO); - } - AID what = AID.fromString(args.valueOf(OPT_MOVE)); - AID to = AID.fromString(args.valueOf(OPT_TO)); - gp.extradite(what, to); - } + // --move + if (args.has(OPT_MOVE)) { + if (!args.has(OPT_TO)) { + fail("Specify extradition target with --" + OPT_TO); + } + AID what = AID.fromString(args.valueOf(OPT_MOVE)); + AID to = AID.fromString(args.valueOf(OPT_TO)); + gp.extradite(what, to); + } - // --store-data - // This will split the data, if necessary - if (args.has(OPT_STORE_DATA)) { - List blobs = args.valuesOf(OPT_STORE_DATA).stream().map(e -> HexUtils.stringToBin((String) e)).collect(Collectors.toList()); - for (byte[] blob : blobs) { - if (args.has(OPT_APPLET)) { - gp.personalize(AID.fromString(args.valueOf(OPT_APPLET)), blob, 0x01); - } else { - gp.storeData(blob, 0x1); - } - } + // --store-data + // This will split the data, if necessary + if (args.has(OPT_STORE_DATA)) { + List blobs = args.valuesOf(OPT_STORE_DATA).stream().map(e -> HexUtils.stringToBin((String) e)).collect(Collectors.toList()); + for (byte[] blob : blobs) { + if (args.has(OPT_APPLET)) { + gp.personalize(AID.fromString(args.valueOf(OPT_APPLET)), blob, 0x01); + } else { + gp.storeData(blob, 0x1); } + } + } - // --store-data-chunk - // This will collect the chunks and send them one by one - if (args.has(OPT_STORE_DATA_CHUNK)) { - List blobs = args.valuesOf(OPT_STORE_DATA_CHUNK).stream().map(e -> HexUtils.stringToBin((String) e)).collect(Collectors.toList()); - if (args.has(OPT_APPLET)) { - gp.personalize(AID.fromString(args.valueOf(OPT_APPLET)), blobs, 0x01); - } else { - gp.storeData(blobs, 0x1); - } - } + // --store-data-chunk + // This will collect the chunks and send them one by one + if (args.has(OPT_STORE_DATA_CHUNK)) { + List blobs = args.valuesOf(OPT_STORE_DATA_CHUNK).stream().map(e -> HexUtils.stringToBin((String) e)).collect(Collectors.toList()); + if (args.has(OPT_APPLET)) { + gp.personalize(AID.fromString(args.valueOf(OPT_APPLET)), blobs, 0x01); + } else { + gp.storeData(blobs, 0x1); + } + } - if (args.has(OPT_ACR_ADD)) { - AID aid = null; - byte[] hash = null; - AID araAid = SEAccessControl.ACR_AID; - if (args.has(OPT_APPLET)) - aid = AID.fromString(args.valueOf(OPT_APPLET)); - if (args.has(OPT_ACR_CERT_HASH)) - hash = HexUtils.stringToBin((String) args.valueOf(OPT_ACR_CERT_HASH)); - if (args.has(OPT_ACR_AID)) - araAid = AID.fromString((String) args.valueOf(OPT_ACR_AID)); - if (!args.has(OPT_ACR_RULE)) { - System.err.println("Must specify an access rule with -" + OPT_ACR_RULE + " (00, 01 or an apdu filter)"); - } - if (hash != null && hash.length != 20) { - fail("certificate hash must be 20 bytes"); - } - SEAccessControlUtility.acrAdd(gp, araAid, aid, hash, HexUtils.stringToBin((String) args.valueOf(OPT_ACR_RULE))); - } + if (args.has(OPT_ACR_ADD)) { + AID aid = null; + byte[] hash = null; + AID araAid = SEAccessControl.ACR_AID; + if (args.has(OPT_APPLET)) + aid = AID.fromString(args.valueOf(OPT_APPLET)); + if (args.has(OPT_ACR_CERT_HASH)) + hash = HexUtils.stringToBin((String) args.valueOf(OPT_ACR_CERT_HASH)); + if (args.has(OPT_ACR_AID)) + araAid = AID.fromString((String) args.valueOf(OPT_ACR_AID)); + if (!args.has(OPT_ACR_RULE)) { + System.err.println("Must specify an access rule with -" + OPT_ACR_RULE + " (00, 01 or an apdu filter)"); + } + if (hash != null && hash.length != 20) { + fail("certificate hash must be 20 bytes"); + } + SEAccessControlUtility.acrAdd(gp, araAid, aid, hash, HexUtils.stringToBin((String) args.valueOf(OPT_ACR_RULE))); + } - // --acr-delete - if (args.has(OPT_ACR_DELETE)) { - AID araAid = SEAccessControl.ACR_AID; - if (args.has(OPT_ACR_AID)) - araAid = AID.fromString(args.valueOf(OPT_ACR_AID)); + // --acr-delete + if (args.has(OPT_ACR_DELETE)) { + AID araAid = SEAccessControl.ACR_AID; + if (args.has(OPT_ACR_AID)) + araAid = AID.fromString(args.valueOf(OPT_ACR_AID)); - AID aid = null; - if (args.has(OPT_APPLET)) { - aid = AID.fromString(OPT_APPLET); - } + AID aid = null; + if (args.has(OPT_APPLET)) { + aid = AID.fromString(OPT_APPLET); + } - byte[] hash = null; - if (args.has(OPT_ACR_CERT_HASH)) { - hash = HexUtils.stringToBin((String) args.valueOf(OPT_ACR_CERT_HASH)); - if (hash.length != 20) - fail("certificate hash must be 20 bytes"); - } + byte[] hash = null; + if (args.has(OPT_ACR_CERT_HASH)) { + hash = HexUtils.stringToBin((String) args.valueOf(OPT_ACR_CERT_HASH)); + if (hash.length != 20) + fail("certificate hash must be 20 bytes"); + } - SEAccessControlUtility.acrDelete(gp, araAid, aid, hash); - } + SEAccessControlUtility.acrDelete(gp, araAid, aid, hash); + } - // --lock-card - if (args.has(OPT_LOCK_CARD)) { - gp.setCardStatus(GPData.lockedStatus); - } - // --unlock-card - if (args.has(OPT_UNLOCK_CARD)) { - gp.setCardStatus(GPData.securedStatus); - } - // --initialize-card - if (args.has(OPT_INITIALIZE_CARD)) { + // --lock-card + if (args.has(OPT_LOCK_CARD)) { + gp.setCardStatus(GPData.lockedStatus); + } + // --unlock-card + if (args.has(OPT_UNLOCK_CARD)) { + gp.setCardStatus(GPData.securedStatus); + } + // --initialize-card + if (args.has(OPT_INITIALIZE_CARD)) { + gp.setCardStatus(GPData.initializedStatus); + } + // --secure-card + if (args.has(OPT_SECURE_CARD)) { + // Skip INITIALIZED + GPRegistryEntry isd = gp.getRegistry().getISD().orElseThrow(() -> new GPException("ISD is null")); + if (isd.getLifeCycle() != GPData.initializedStatus) { + if (args.has(OPT_FORCE)) { + System.out.println("Note: forcing status to INITIALIZED"); gp.setCardStatus(GPData.initializedStatus); } - // --secure-card - if (args.has(OPT_SECURE_CARD)) { - // Skip INITIALIZED - GPRegistryEntry isd = gp.getRegistry().getISD().orElseThrow(() -> new GPException("ISD is null")); - if (isd.getLifeCycle() != GPData.initializedStatus) { - if (args.has(OPT_FORCE)) { - System.out.println("Note: forcing status to INITIALIZED"); - gp.setCardStatus(GPData.initializedStatus); - } - } - gp.setCardStatus(GPData.securedStatus); - } + } + gp.setCardStatus(GPData.securedStatus); + } - // --lock-applet - if (args.has(OPT_LOCK_APPLET)) { - gp.lockUnlockApplet(AID.fromString(args.valueOf(OPT_LOCK_APPLET)), true); - } + // --lock-applet + if (args.has(OPT_LOCK_APPLET)) { + gp.lockUnlockApplet(AID.fromString(args.valueOf(OPT_LOCK_APPLET)), true); + } - // --unlock-applet - if (args.has(OPT_UNLOCK_APPLET)) { - gp.lockUnlockApplet(AID.fromString(args.valueOf(OPT_UNLOCK_APPLET)), false); - } + // --unlock-applet + if (args.has(OPT_UNLOCK_APPLET)) { + gp.lockUnlockApplet(AID.fromString(args.valueOf(OPT_UNLOCK_APPLET)), false); + } - // --list - if (args.has(OPT_LIST)) { - GPCommands.listRegistry(gp.getRegistry(), System.out, args.has(OPT_VERBOSE)); - } + // --list + if (args.has(OPT_LIST)) { + GPCommands.listRegistry(gp.getRegistry(), System.out, args.has(OPT_VERBOSE)); + } - // --delete-key - // TODO: make --delete smart enough - if (args.has(OPT_DELETE_KEY)) { - int keyver = GPUtils.intValue((String) args.valueOf(OPT_DELETE_KEY)); - System.out.println("Deleting key " + keyver); - gp.deleteKey(keyver); - } + // --delete-key + // TODO: make --delete smart enough + if (args.has(OPT_DELETE_KEY)) { + int keyver = GPUtils.intValue((String) args.valueOf(OPT_DELETE_KEY)); + System.out.println("Deleting key " + keyver); + gp.deleteKey(keyver); + } - // TODO: Move to GPCommands - // --unlock - if (args.has(OPT_UNLOCK)) { - // Write default keys - final boolean replace; - final int kv; - // Factory keys - if (gp.getScpKeyVersion() == 255) { - replace = false; - kv = 1; - } else { - // Replace current key - kv = gp.getScpKeyVersion(); - replace = true; - } + // TODO: Move to GPCommands + // --unlock + if (args.has(OPT_UNLOCK)) { + // Write default keys + final boolean replace; + final int kv; + // Factory keys + if (gp.getScpKeyVersion() == 255) { + replace = false; + kv = 1; + } else { + // Replace current key + kv = gp.getScpKeyVersion(); + replace = true; + } - PlaintextKeys new_key = PlaintextKeys.defaultKey(); - new_key.setVersion(kv); - gp.putKeys(new_key, replace); - System.out.println("Default " + HexUtils.bin2hex(PlaintextKeys.defaultKeyBytes) + " set as master key for " + gp.getAID()); - } + PlaintextKeys new_key = PlaintextKeys.defaultKey(); + new_key.setVersion(kv); + gp.putKeys(new_key, replace); + System.out.println("Default " + HexUtils.bin2hex(PlaintextKeys.defaultKeyBytes) + " set as master key for " + gp.getAID()); + } - // --lock - if (args.has(OPT_LOCK) || (args.has(OPT_LOCK_ENC) && args.has(OPT_LOCK_MAC) && args.has(OPT_LOCK_DEK))) { - // By default we try to change an existing key - boolean replace = true; - List current = gp.getKeyInfoTemplate(); - - // By default use key version 1 - int new_version = 1; - // If there are keys present, check the existing version - if (current.size() > 0) { - if (current.get(0).getVersion() == 255) { - // Factory keys, add keyset with version one. - replace = false; - } else { - // Existing keys, change the present version - new_version = current.get(0).getVersion(); - } - } + // --lock + if (args.has(OPT_LOCK) || (args.has(OPT_LOCK_ENC) && args.has(OPT_LOCK_MAC) && args.has(OPT_LOCK_DEK))) { + // By default we try to change an existing key + boolean replace = true; + List current = gp.getKeyInfoTemplate(); + + // By default use key version 1 + int new_version = 1; + // If there are keys present, check the existing version + if (current.size() > 0) { + if (current.get(0).getVersion() == 255) { + // Factory keys, add keyset with version one. + replace = false; + } else { + // Existing keys, change the present version + new_version = current.get(0).getVersion(); + } + } - // Get key value or values - PlaintextKeys newKeys; - if (args.has(OPT_LOCK_ENC) && args.has(OPT_LOCK_MAC) && args.has(OPT_LOCK_DEK)) { - byte[] enc = HexUtils.stringToBin((String) args.valueOf(OPT_LOCK_ENC)); - byte[] mac = HexUtils.stringToBin((String) args.valueOf(OPT_LOCK_MAC)); - byte[] dek = HexUtils.stringToBin((String) args.valueOf(OPT_LOCK_DEK)); - newKeys = PlaintextKeys.fromKeys(enc, mac, dek); - } else { - newKeys = PlaintextKeys.fromMasterKey(HexUtils.stringToBin((String) args.valueOf(OPT_LOCK))); - if (args.has(OPT_LOCK_KDF)) { - // XXX: should do diversification here explicitly. Expose card keys and kdd - newKeys.setDiversifier(getDiversificationOrFail(args, OPT_LOCK_KDF)); - } - } + // Get key value or values + PlaintextKeys newKeys; + if (args.has(OPT_LOCK_ENC) && args.has(OPT_LOCK_MAC) && args.has(OPT_LOCK_DEK)) { + byte[] enc = HexUtils.stringToBin((String) args.valueOf(OPT_LOCK_ENC)); + byte[] mac = HexUtils.stringToBin((String) args.valueOf(OPT_LOCK_MAC)); + byte[] dek = HexUtils.stringToBin((String) args.valueOf(OPT_LOCK_DEK)); + newKeys = PlaintextKeys.fromKeys(enc, mac, dek); + } else { + newKeys = PlaintextKeys.fromMasterKey(HexUtils.stringToBin((String) args.valueOf(OPT_LOCK))); + if (args.has(OPT_LOCK_KDF)) { + // XXX: should do diversification here explicitly. Expose card keys and kdd + newKeys.setDiversifier(getDiversificationOrFail(args, OPT_LOCK_KDF)); + } + } - // If a specific new key version is specified, use that instead. - if (args.has(OPT_NEW_KEY_VERSION)) { - new_version = GPUtils.intValue((String) args.valueOf(OPT_NEW_KEY_VERSION)); - replace = false; - System.out.println("New version: " + new_version); - } - newKeys.setVersion(new_version); + // If a specific new key version is specified, use that instead. + if (args.has(OPT_NEW_KEY_VERSION)) { + new_version = GPUtils.intValue((String) args.valueOf(OPT_NEW_KEY_VERSION)); + replace = false; + System.out.println("New version: " + new_version); + } + newKeys.setVersion(new_version); - gp.putKeys(newKeys, replace); + gp.putKeys(newKeys, replace); - if (args.has(OPT_LOCK)) { - System.out.println("Card locked with: " + HexUtils.bin2hex(HexUtils.stringToBin((String) args.valueOf(OPT_LOCK)))); - System.out.println("Write this down, DO NOT FORGET/LOSE IT!"); - } else { - System.out.println("Card locked with new keys."); - System.out.println("Write them down, DO NOT FORGET/LOSE THEM!"); - } - } + if (args.has(OPT_LOCK)) { + System.out.println("Card locked with: " + HexUtils.bin2hex(HexUtils.stringToBin((String) args.valueOf(OPT_LOCK)))); + System.out.println("Write this down, DO NOT FORGET/LOSE IT!"); + } else { + System.out.println("Card locked with new keys."); + System.out.println("Write them down, DO NOT FORGET/LOSE THEM!"); + } + } - // --make-default - if (args.has(OPT_MAKE_DEFAULT)) { - gp.makeDefaultSelected(AID.fromString(args.valueOf(OPT_MAKE_DEFAULT))); - } + // --make-default + if (args.has(OPT_MAKE_DEFAULT)) { + gp.makeDefaultSelected(AID.fromString(args.valueOf(OPT_MAKE_DEFAULT))); + } - // --rename-isd - if (args.has(OPT_RENAME_ISD)) { - gp.renameISD(AID.fromString(args.valueOf(OPT_RENAME_ISD))); - } - // --set-pre-perso - if (args.has(OPT_SET_PRE_PERSO)) { - byte[] payload = HexUtils.stringToBin((String) args.valueOf(OPT_SET_PRE_PERSO)); - if (args.has(OPT_TODAY)) { - System.arraycopy(GPData.CPLC.today(), 0, payload, 2, 2); - } - GPCommands.setPrePerso(gp, payload); - } - // --set-perso - if (args.has(OPT_SET_PERSO)) { - byte[] payload = HexUtils.stringToBin((String) args.valueOf(OPT_SET_PERSO)); - if (args.has(OPT_TODAY)) { - System.arraycopy(GPData.CPLC.today(), 0, payload, 2, 2); - } - GPCommands.setPerso(gp, payload); - } - } - } catch (GPException e) { - // All unhandled GP exceptions halt the program unless it is run with -force - if (!args.has(OPT_FORCE)) { - fail(e.getMessage()); + // --rename-isd + if (args.has(OPT_RENAME_ISD)) { + gp.renameISD(AID.fromString(args.valueOf(OPT_RENAME_ISD))); + } + // --set-pre-perso + if (args.has(OPT_SET_PRE_PERSO)) { + byte[] payload = HexUtils.stringToBin((String) args.valueOf(OPT_SET_PRE_PERSO)); + if (args.has(OPT_TODAY)) { + System.arraycopy(GPData.CPLC.today(), 0, payload, 2, 2); } - } catch (IOException e) { - System.out.println("Failed to communicate with card in " + reader + ": " + e.getMessage()); - // Card exceptions skip to the next reader, if available and allowed FIXME broken logic - continue; - } finally { - if (card != null) { - card.endExclusive(); - card.disconnect(true); + GPCommands.setPrePerso(gp, payload); + } + // --set-perso + if (args.has(OPT_SET_PERSO)) { + byte[] payload = HexUtils.stringToBin((String) args.valueOf(OPT_SET_PERSO)); + if (args.has(OPT_TODAY)) { + System.arraycopy(GPData.CPLC.today(), 0, payload, 2, 2); } + GPCommands.setPerso(gp, payload); } } - } catch (CardException e) { - // Sensible wrapper for the different PC/SC exceptions - if (TerminalManager.getExceptionMessage(e) != null) { - System.out.println("PC/SC failure: " + TerminalManager.getExceptionMessage(e)); - } else { - e.printStackTrace(); // TODO: remove - fail("CardException, terminating"); - } + // Other exceptions escape. fin. + return 0; + } catch (NoSuchAlgorithmException | IOException e) { + // FIXME: deal with it + e.printStackTrace(); } - // Other exceptions escape. fin. - System.exit(0); + return 1; } private static void calculateDapPropertiesAndLoadCap(OptionSet args, GPSession gp, CAPFile capFile) throws GPException, IOException { @@ -954,7 +859,7 @@ public static void fail(String msg) { System.exit(1); } - private static void verbose(String s) { + private void verbose(String s) { if (isVerbose) { System.out.println("# " + s); }