From 22ac6660dfbbdc9363b76ac8acdedca1d6fef18a Mon Sep 17 00:00:00 2001 From: "Gregor.Johannson" Date: Thu, 7 Feb 2019 22:57:03 +0200 Subject: [PATCH] #153: Add initial functionality of including DM token calculation for INSTALL [for load]. Minor refactoring for more clarity. --- .../javacard/gp/GPCommandLineInterface.java | 6 +- src/main/java/pro/javacard/gp/GPCrypto.java | 65 +++++++++++++++-- src/main/java/pro/javacard/gp/GPTool.java | 11 ++- .../java/pro/javacard/gp/GlobalPlatform.java | 70 ++++++++++++------- 4 files changed, 122 insertions(+), 30 deletions(-) diff --git a/src/main/java/pro/javacard/gp/GPCommandLineInterface.java b/src/main/java/pro/javacard/gp/GPCommandLineInterface.java index 64f97fbc..7423e456 100644 --- a/src/main/java/pro/javacard/gp/GPCommandLineInterface.java +++ b/src/main/java/pro/javacard/gp/GPCommandLineInterface.java @@ -104,6 +104,8 @@ abstract class GPCommandLineInterface { protected final static String OPT_ACR_DELETE = "acr-delete"; protected final static String OPT_ACR_RULE = "acr-rule"; protected final static String OPT_ACR_CERT_HASH = "acr-hash"; + protected final static String OPT_TOKEN = "token-key"; + protected final static String OPT_TOKEN_KEY = "token-key"; protected static OptionSet parseArguments(String[] argv) throws IOException { OptionSet args = null; @@ -158,8 +160,10 @@ protected static OptionSet parseArguments(String[] argv) throws IOException { parser.accepts(OPT_TODAY, "Set date to today when updating CPLC"); parser.accepts(OPT_STORE_DATA_BLOB, "STORE DATA blob").withRequiredArg().describedAs("data"); - parser.accepts(OPT_STORE_DATA, "send STORE DATA commands").withRequiredArg().describedAs("data"); + parser.accepts(OPT_STORE_DATA, "Send STORE DATA commands").withRequiredArg().describedAs("data"); + parser.accepts(OPT_TOKEN, "Generate Delegated Management tokens on the fly"); + parser.accepts(OPT_TOKEN_KEY, "Path to private key used in token generation").withRequiredArg().describedAs("path"); parser.accepts(OPT_MAKE_DEFAULT, "Make AID the default").withRequiredArg().describedAs("AID"); parser.accepts(OPT_RENAME_ISD, "Rename ISD").withRequiredArg().describedAs("new AID"); diff --git a/src/main/java/pro/javacard/gp/GPCrypto.java b/src/main/java/pro/javacard/gp/GPCrypto.java index b803c41a..b2a9cfe9 100644 --- a/src/main/java/pro/javacard/gp/GPCrypto.java +++ b/src/main/java/pro/javacard/gp/GPCrypto.java @@ -29,6 +29,7 @@ import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import pro.javacard.AID; import pro.javacard.gp.GPKey.Type; import javax.crypto.BadPaddingException; @@ -36,12 +37,12 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; // Various cryptographic primitives used for secure channel or plaintext keys @@ -265,4 +266,60 @@ public static PublicKey pem2pubkey(InputStream in) throws IOException { } else throw new IllegalArgumentException("Can not read PEM"); } } + + public static byte[] calculateLoadToken(int followingLength, AID loadFile, AID securityDomain, byte[] hash, byte[] loadParams, String keyPath) { + PrivateKey privateKey; + + if (keyPath != null && !keyPath.isEmpty()) { + privateKey = get(keyPath); + } else { + //Will probably load from HSM in the future, throw for now + throw new RuntimeException("Token calculation (currently) expects a path for RSA key"); + } + + try { + ByteArrayOutputStream signatureData = new ByteArrayOutputStream(); + signatureData.write(0x02); + signatureData.write(0x00); + signatureData.write(followingLength); + signatureData.write(loadFile.getLength()); + signatureData.write(loadFile.getBytes()); + signatureData.write(securityDomain.getLength()); + signatureData.write(securityDomain.getBytes()); + signatureData.write(hash.length); + signatureData.write(hash); + signatureData.write(loadParams.length); + signatureData.write(loadParams); + + return signSignatureData(privateKey, signatureData.toByteArray(), "SHA1withRSA"); + } catch (Exception e) { + throw new RuntimeException("Could not calculate load token", e); + } + } + + public static byte[] signSignatureData(PrivateKey privateKey, byte[] signatureData, String signatureAlgorithm) { + try { + Signature signature = Signature.getInstance(signatureAlgorithm); + signature.initSign(privateKey); + signature.update(signatureData); + return signature.sign(); + } catch (Exception e) { + throw new RuntimeException("Could not create signature with instance " + signatureAlgorithm, e); + } + + } + + public static PrivateKey get(String path) { + try { + byte[] keyBytes = Files.readAllBytes(Paths.get(path)); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(spec); + } catch (Exception e) { + throw new RuntimeException("Could not obtain private key from '" + path + "'", e); + } + + } + + } diff --git a/src/main/java/pro/javacard/gp/GPTool.java b/src/main/java/pro/javacard/gp/GPTool.java index 60968d8f..463cebc0 100644 --- a/src/main/java/pro/javacard/gp/GPTool.java +++ b/src/main/java/pro/javacard/gp/GPTool.java @@ -406,6 +406,8 @@ public static void main(String[] argv) throws Exception { AID target = null; AID dapdomain = null; boolean dapRequired = false; + boolean calculateToken = false; + String keyPath = null; // Override target and check for DAP if (args.has(OPT_TO)) { @@ -429,10 +431,17 @@ public static void main(String[] argv) throws Exception { } } + if (args.has(OPT_TOKEN)) { + calculateToken = true; + if (args.has(OPT_TOKEN_KEY)) { + keyPath = (String) args.valueOf(OPT_TOKEN_KEY); + } + } + // XXX: figure out right signature type in a better way if (dapRequired) { byte[] dap = args.has(OPT_SHA256) ? loadcap.getMetaInfEntry(CAPFile.DAP_RSA_V1_SHA256_FILE) : loadcap.getMetaInfEntry(CAPFile.DAP_RSA_V1_SHA1_FILE); - gp.loadCapFile(loadcap, target, dapdomain == null ? target : dapdomain, dap, args.has(OPT_SHA256) ? "SHA-256" : "SHA1"); + gp.loadCapFile(loadcap, target, dapdomain == null ? target : dapdomain, dap, args.has(OPT_SHA256) ? "SHA-256" : "SHA1", calculateToken, keyPath); } else { gp.loadCapFile(loadcap, target); } diff --git a/src/main/java/pro/javacard/gp/GlobalPlatform.java b/src/main/java/pro/javacard/gp/GlobalPlatform.java index f7a642fc..b535f89b 100644 --- a/src/main/java/pro/javacard/gp/GlobalPlatform.java +++ b/src/main/java/pro/javacard/gp/GlobalPlatform.java @@ -85,6 +85,8 @@ public class GlobalPlatform extends CardChannel implements AutoCloseable { public static final byte P1_INSTALL_AND_MAKE_SELECTABLE = (byte) 0x0C; public static final byte P1_INSTALL_FOR_INSTALL = (byte) 0x04; + public static final byte P1_MORE_BLOCKS = (byte) 0x00; + public static final byte P1_LAST_BLOCK = (byte) 0x80; protected boolean strict = true; GPSpec spec = GPSpec.GP211; @@ -207,6 +209,12 @@ public static String getVersion() { } } + public static byte[] getLoadParams(boolean loadParam, byte[] code) { + return loadParam + ? new byte[]{(byte) 0xEF, 0x04, (byte) 0xC6, 0x02, (byte) ((code.length & 0xFF00) >> 8), (byte) (code.length & 0xFF)} + : new byte[0]; + } + @Override public void close() { // TODO explicitly closes SecureChannel, if connected. @@ -468,7 +476,10 @@ public void openSecureChannel(GPSessionKeyProvider keys, byte[] host_challenge, // FIXME: this should be possible from GPTool System.err.println("Read more from https://github.com/martinpaljak/GlobalPlatformPro/wiki/Keys"); } - giveStrictWarning("Card cryptogram invalid!\nCard: " + HexUtils.bin2hex(card_cryptogram) + "\nHost: " + HexUtils.bin2hex(my_card_cryptogram) + "\n!!! DO NOT RE-TRY THE SAME COMMAND/KEYS OR YOU MAY BRICK YOUR CARD !!!"); + giveStrictWarning("Card cryptogram invalid!" + + "\nCard: " + HexUtils.bin2hex(card_cryptogram) + + "\nHost: " + HexUtils.bin2hex(my_card_cryptogram) + + "\n!!! DO NOT RE-TRY THE SAME COMMAND/KEYS OR YOU MAY BRICK YOUR CARD !!!"); } else { logger.debug("Verified card cryptogram: " + HexUtils.bin2hex(my_card_cryptogram)); } @@ -542,22 +553,22 @@ public int getSCPVersion() { public void loadCapFile(CAPFile cap, AID target) throws CardException, GPException { if (target == null) target = sdAID; - loadCapFile(cap, target, false, false, null, null, LFDBH_SHA1); + loadCapFile(cap, target, false, false, null, null, LFDBH_SHA1, false, null); } public void loadCapFile(CAPFile cap, AID target, byte[] dap, String hash) throws CardException, GPException { if (target == null) target = sdAID; - loadCapFile(cap, target, false, false, target, dap, hash); + loadCapFile(cap, target, false, false, target, dap, hash, false, null); } - public void loadCapFile(CAPFile cap, AID target, AID dapdomain, byte[] dap, String hash) throws CardException, GPException { + public void loadCapFile(CAPFile cap, AID target, AID dapdomain, byte[] dap, String hashFunction, boolean calculateToken, String keyPath) throws CardException, GPException { if (target == null) target = sdAID; - loadCapFile(cap, target, false, false, dapdomain, dap, hash); + loadCapFile(cap, target, false, false, dapdomain, dap, hashFunction, calculateToken, keyPath); } - private void loadCapFile(CAPFile cap, AID sdaid, boolean includeDebug, boolean loadParam, AID dapdomain, byte[] dap, String lfdbh) + private void loadCapFile(CAPFile cap, AID sdaid, boolean includeDebug, boolean loadParam, AID dapdomain, byte[] dap, String hashFunction, boolean calculateToken, String keyPath) throws GPException, CardException { if (getRegistry().allAIDs().contains(cap.getPackageAID())) { @@ -565,11 +576,10 @@ private void loadCapFile(CAPFile cap, AID sdaid, boolean includeDebug, boolean l } // FIXME: hash type handling needs to be sensible. - byte[] hash = dap != null ? cap.getLoadFileDataHash(lfdbh, includeDebug) : new byte[0]; + byte[] hash = dap != null ? cap.getLoadFileDataHash(hashFunction, includeDebug) : new byte[0]; byte[] code = cap.getCode(includeDebug); // FIXME: parameters are optional for load - byte[] loadParams = loadParam ? new byte[]{(byte) 0xEF, 0x04, (byte) 0xC6, 0x02, (byte) ((code.length & 0xFF00) >> 8), - (byte) (code.length & 0xFF)} : new byte[0]; + byte[] loadParams = getLoadParams(loadParam, code); ByteArrayOutputStream bo = new ByteArrayOutputStream(); @@ -585,7 +595,18 @@ private void loadCapFile(CAPFile cap, AID sdaid, boolean includeDebug, boolean l bo.write(loadParams.length); bo.write(loadParams); - bo.write(0); // Load token + + if (calculateToken) { + try { + byte[] loadToken = GPCrypto.calculateLoadToken(bo.size(), cap.getPackageAID(), sdaid, hash, loadParams, keyPath); + bo.write(loadToken.length); + bo.write(loadToken); + } catch (RuntimeException e) { + bo.write(0); + } + } else { + bo.write(0); + } } catch (IOException ioe) { throw new RuntimeException(ioe); } @@ -596,32 +617,33 @@ private void loadCapFile(CAPFile cap, AID sdaid, boolean includeDebug, boolean l // Construct load block - ByteArrayOutputStream loadblock = new ByteArrayOutputStream(); + ByteArrayOutputStream loadBlock = new ByteArrayOutputStream(); try { // Add DAP block, if signature present if (dap != null) { - loadblock.write(0xE2); - loadblock.write(GPUtils.encodeLength(dapdomain.getLength() + dap.length + GPUtils.encodeLength(dap.length).length + 3)); // two tags, two lengths FIXME: proper size - loadblock.write(0x4F); - loadblock.write(dapdomain.getLength()); - loadblock.write(dapdomain.getBytes()); - loadblock.write(0xC3); - loadblock.write(GPUtils.encodeLength(dap.length)); - loadblock.write(dap); + loadBlock.write(0xE2); + loadBlock.write(GPUtils.encodeLength(dapdomain.getLength() + dap.length + GPUtils.encodeLength(dap.length).length + 3)); // two tags, two lengths FIXME: proper size + loadBlock.write(0x4F); + loadBlock.write(dapdomain.getLength()); + loadBlock.write(dapdomain.getBytes()); + loadBlock.write(0xC3); + loadBlock.write(GPUtils.encodeLength(dap.length)); + loadBlock.write(dap); } // See GP 2.1.1 Table 9-40, GP 2.2.1 11.6.2.3 / Table 11-58 - loadblock.write(0xC4); - loadblock.write(GPUtils.encodeLength(code.length)); - loadblock.write(code); + loadBlock.write(0xC4); + loadBlock.write(GPUtils.encodeLength(code.length)); + loadBlock.write(code); } catch (IOException e) { throw new RuntimeException(e); } // Split according to available block size - List blocks = GPUtils.splitArray(loadblock.toByteArray(), wrapper.getBlockSize()); + List blocks = GPUtils.splitArray(loadBlock.toByteArray(), wrapper.getBlockSize()); for (int i = 0; i < blocks.size(); i++) { - CommandAPDU load = new CommandAPDU(CLA_GP, INS_LOAD, (i == (blocks.size() - 1)) ? 0x80 : 0x00, (byte) i, blocks.get(i)); + byte p1 = (i == (blocks.size() - 1)) ? P1_LAST_BLOCK : P1_MORE_BLOCKS; + CommandAPDU load = new CommandAPDU(CLA_GP, INS_LOAD, p1, (byte) i, blocks.get(i)); response = transmit(load); GPException.check(response, "LOAD failed"); }