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 f99b873
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);

This comment has been minimized.

Copy link
@martinpaljak

martinpaljak Feb 8, 2019

The same structure is constructed in loadCapFile, everything omitting INS of the APDU to the place where the token should be inserted.

Same applies to other operations that need to be authorized with a token. Thus is makes sense to have something around the lines:

DelegatedManagementHandler dm = new DelegatedManagementHandler(Key dmKey)
CommandAPDU apdu = new CommandAPDU(CLA_GP, INS_INSTALL, P1, P2 ... )
CommandAPDU dm_applied_apdu = dm.apply(CommandAPDU apdu)

If DM has not been configured with a key, would return an APDU where 0x00 is used for the token, otherwise it would sign the APDU with the valid token.

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 f99b873

Please sign in to comment.