Skip to content

Commit

Permalink
martinpaljak#153: Add initial functionality of including DM token cal…
Browse files Browse the repository at this point in the history
…culation for INSTALL [for load]. Minor refactoring for more clarity.
  • Loading branch information
gregorjohannson committed Feb 7, 2019
1 parent f94d7f5 commit 22ac666
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 30 deletions.
6 changes: 5 additions & 1 deletion src/main/java/pro/javacard/gp/GPCommandLineInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
65 changes: 61 additions & 4 deletions src/main/java/pro/javacard/gp/GPCrypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,20 @@
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;
import javax.crypto.Cipher;
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
Expand Down Expand Up @@ -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);
}

}


}
11 changes: 10 additions & 1 deletion src/main/java/pro/javacard/gp/GPTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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);
}
Expand Down
70 changes: 46 additions & 24 deletions src/main/java/pro/javacard/gp/GlobalPlatform.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -542,34 +553,33 @@ 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())) {
giveStrictWarning("Package with AID " + cap.getPackageAID() + " is already present on card");
}

// 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();

Expand All @@ -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);
}
Expand All @@ -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<byte[]> blocks = GPUtils.splitArray(loadblock.toByteArray(), wrapper.getBlockSize());
List<byte[]> 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");
}
Expand Down

0 comments on commit 22ac666

Please sign in to comment.