Skip to content

Commit

Permalink
Major refactoring
Browse files Browse the repository at this point in the history
- Sanitize key handling, splitting card keys and session keys
- Merge registry elements into single class
- Rename things to be more uniform
- Remove a lot of rot
- Release a snapshot with updated dependencies

closes #118 #165 #153 #9
  • Loading branch information
martinpaljak authored May 16, 2019
1 parent c8dad37 commit 0014bda
Show file tree
Hide file tree
Showing 44 changed files with 1,861 additions and 1,883 deletions.
2 changes: 1 addition & 1 deletion globalplatform.pro
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-libraryjars <java.home>/lib/rt.jar
-libraryjars <java.home>/lib/jce.jar

-injars target/gp.jar
-injars tool/target/gp.jar
-keep public class pro.javacard.gp.GPTool {
public static void main(java.lang.String[]);
}
Expand Down
61 changes: 61 additions & 0 deletions library/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.github.martinpaljak</groupId>
<artifactId>gppro</artifactId>
<version>19.05.16</version>
</parent>

<artifactId>globalplatformpro</artifactId>
<name>GlobalPlatformPro library</name>

<dependencies>
<!-- For APDU construction and parsing -->
<dependency>
<groupId>com.github.martinpaljak</groupId>
<artifactId>apdu4j-core</artifactId>
<version>19.05.08</version>
</dependency>
<!-- For CAP file parsing -->
<dependency>
<groupId>com.github.martinpaljak</groupId>
<artifactId>capfile</artifactId>
<version>19.03.04</version>
</dependency>
<!-- For logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- For JSON handling -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.4</version>
</dependency>
<!-- For crypto in SCP03 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.61</version>
</dependency>
<!-- For TLV handling -->
<dependency>
<groupId>com.payneteasy</groupId>
<artifactId>ber-tlv</artifactId>
<version>1.0-9</version>
</dependency>
<!-- For tests -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
89 changes: 89 additions & 0 deletions library/src/main/java/pro/javacard/gp/DMTokenGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package pro.javacard.gp;

import apdu4j.CommandAPDU;
import apdu4j.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.Signature;

import static pro.javacard.gp.GPSession.INS_DELETE;

public class DMTokenGenerator {
private static final Logger logger = LoggerFactory.getLogger(DMTokenGenerator.class);

private static final String defaultAlgorithm = "SHA1withRSA";
private final String algorithm;

private PrivateKey key;
private byte[] token; // Token to use

public DMTokenGenerator(PrivateKey key, String algorithm) {
this.key = key;
this.algorithm = algorithm;
}

public DMTokenGenerator(PrivateKey key) {
this(key, defaultAlgorithm);
}

CommandAPDU applyToken(CommandAPDU apdu) throws GeneralSecurityException {
ByteArrayOutputStream newData = new ByteArrayOutputStream();
try {
newData.write(apdu.getData());

if (key == null) {
logger.trace("No private key for token generation provided");
if (apdu.getINS() != (INS_DELETE & 0xFF))
newData.write(0); // No token
} else {
if (apdu.getINS() == (INS_DELETE & 0xFF)) {
// See GP 2.3.1 Table 11-23
logger.trace("Adding tag 0x9E before Delete Token");
newData.write(0x9E);
}
logger.trace("Using private key for token generation (" + algorithm + ")");
byte[] token = calculateToken(apdu, key);
newData.write(token.length);
newData.write(token);
}
} catch (IOException e) {
throw new RuntimeException("Could not apply DM token", e);
}
return new CommandAPDU(apdu.getCLA(), apdu.getINS(), apdu.getP1(), apdu.getP2(), newData.toByteArray()); // FIXME: Le handling
}

private byte[] calculateToken(CommandAPDU apdu, PrivateKey key) throws GeneralSecurityException {
return signData(key, getTokenData(apdu));
}

private static byte[] getTokenData(CommandAPDU apdu) {
try {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
bo.write(apdu.getP1());
bo.write(apdu.getP2());
bo.write(apdu.getData().length); // FIXME: length handling for > 255 bytes
bo.write(apdu.getData());
return bo.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Could not get P1/P2 or data for token calculation", e);
}
}

private byte[] signData(PrivateKey privateKey, byte[] apduData) throws GeneralSecurityException {
Signature signer = Signature.getInstance(algorithm);
signer.initSign(privateKey);
signer.update(apduData);
byte[] signature = signer.sign();
logger.info("Generated DM token: {}" + HexUtils.bin2hex(signature));
return signature;
}

public boolean hasKey() {
return key != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@
// Providers are free to derive session keys based on hardware backed master keys
// PlaintextKeys provides card keys, that are ... plaintext (not backed by hardware)

public abstract class GPSessionKeyProvider {
import apdu4j.HexUtils;

// returns true if keys can probably be made
public abstract boolean init(byte[] atr, byte[] cplc, byte[] kinfo);
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;

// Any can be null, if N/A for SCP version
public abstract void calculate(int scp, byte[] kdd, byte[] host_challenge, byte[] card_challenge, byte[] ssc) throws GPException;
public abstract class GPCardKeys {

public abstract GPKey getKeyFor(KeyPurpose p);
protected GPSecureChannel scp;

public abstract int getID();

public abstract int getVersion();

// Session keys are used for various purposes
// Keys are used for various purposes
public enum KeyPurpose {
// ID is as used in diversification/derivation
// That is - one based.
Expand All @@ -53,6 +53,33 @@ public enum KeyPurpose {
public byte getValue() {
return (byte) (value & 0xFF);
}

// RMAC is derived, but not loaded to the card
public static List<KeyPurpose> cardKeys() {
return Arrays.asList(ENC, MAC, DEK);
}
}

// Encrypt data with static card DEK
public abstract byte[] encrypt(byte[] data) throws GeneralSecurityException;

// Encrypt a key with card (or session) DEK
public abstract byte[] encryptKey(GPCardKeys key, KeyPurpose p) throws GeneralSecurityException;

// Get session keys for given session data
public abstract GPSessionKeys getSessionKeys(byte[] kdd);

// Get KCV of a card key
public abstract byte[] kcv(KeyPurpose p);

// Diversify card keys automatically, based on INITIALIZE UPDATE response
public GPCardKeys diversify(GPSecureChannel scp, byte[] kdd) {
this.scp = scp;
return this;
}

@Override
public String toString() {
return String.format("KCV-s ENC=%s MAC=%s DEK=%s for %s", HexUtils.bin2hex(kcv(KeyPurpose.ENC)), HexUtils.bin2hex(kcv(KeyPurpose.MAC)), HexUtils.bin2hex(kcv(KeyPurpose.DEK)), scp);
}
}
4 changes: 4 additions & 0 deletions library/src/main/java/pro/javacard/gp/GPCardProfile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package pro.javacard.gp;

public class GPCardProfile {
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,32 @@
*/
package pro.javacard.gp;

import apdu4j.CommandAPDU;
import apdu4j.HexUtils;
import apdu4j.ResponseAPDU;
import pro.javacard.AID;

import javax.smartcardio.CardException;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import java.io.IOException;
import java.io.PrintStream;

// Middle layer between GPTool (CLI) and GlobalPlatform (session)
public class GPCommands {

private static void storeDGI(GlobalPlatform gp, byte[] payload) throws GPException, CardException {
private static void storeDGI(GPSession gp, byte[] payload) throws GPException, IOException {
// Single DGI. 0x90 should work as well but 0x80 is actually respected by cards.
CommandAPDU cmd = new CommandAPDU(GlobalPlatform.CLA_GP, GlobalPlatform.INS_STORE_DATA, 0x80, 0x00, payload);
CommandAPDU cmd = new CommandAPDU(GPSession.CLA_GP, GPSession.INS_STORE_DATA, 0x80, 0x00, payload);
ResponseAPDU response = gp.transmit(cmd);
GPException.check(response, "STORE DATA failed");
}

public static void setPrePerso(GlobalPlatform gp, byte[] data) throws GPException, CardException {
public static void setPrePerso(GPSession gp, byte[] data) throws GPException, IOException {
if (data == null || data.length != 8)
throw new IllegalArgumentException("PrePerso data must be 8 bytes");
byte[] payload = GPUtils.concatenate(new byte[]{(byte) 0x9f, 0x67, (byte) data.length}, data);
storeDGI(gp, payload);
}

public static void setPerso(GlobalPlatform gp, byte[] data) throws GPException, CardException {
public static void setPerso(GPSession gp, byte[] data) throws GPException, IOException {
if (data == null || data.length != 8)
throw new IllegalArgumentException("Perso data must be 8 bytes");
byte[] payload = GPUtils.concatenate(new byte[]{(byte) 0x9f, 0x66, (byte) data.length}, data);
Expand All @@ -66,11 +66,10 @@ public static void listRegistry(GPRegistry reg, PrintStream out, boolean verbose
out.println(tab + "Parent: " + e.getDomain());
}
if (e.getType() == GPRegistryEntry.Kind.ExecutableLoadFile) {
GPRegistryEntryPkg pkg = (GPRegistryEntryPkg) e;
if (pkg.getVersion() != null) {
out.println(tab + "Version: " + pkg.getVersionString());
if (e.getVersion() != null) {
out.println(tab + "Version: " + e.getVersionString());
}
for (AID a : pkg.getModules()) {
for (AID a : e.getModules()) {
out.print(tab + "Applet: " + HexUtils.bin2hex(a.getBytes()));
if (verbose) {
out.println(" (" + GPUtils.byteArrayToReadableString(a.getBytes()) + ")");
Expand All @@ -79,12 +78,11 @@ public static void listRegistry(GPRegistry reg, PrintStream out, boolean verbose
}
}
} else {
GPRegistryEntryApp app = (GPRegistryEntryApp) e;
if (app.getLoadFile() != null) {
out.println(tab + "From: " + app.getLoadFile());
if (e.getLoadFile() != null) {
out.println(tab + "From: " + e.getLoadFile());
}
//if (!app.getPrivileges().isEmpty()) {
out.println(tab + "Privs: " + app.getPrivileges());
out.println(tab + "Privs: " + e.getPrivileges());
//}
}
out.println();
Expand Down
Loading

0 comments on commit 0014bda

Please sign in to comment.