Skip to content

Commit 0014bda

Browse files
authored
Major refactoring
- 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
1 parent c8dad37 commit 0014bda

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1861
-1883
lines changed

globalplatform.pro

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-libraryjars <java.home>/lib/rt.jar
22
-libraryjars <java.home>/lib/jce.jar
33

4-
-injars target/gp.jar
4+
-injars tool/target/gp.jar
55
-keep public class pro.javacard.gp.GPTool {
66
public static void main(java.lang.String[]);
77
}

library/pom.xml

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.github.martinpaljak</groupId>
9+
<artifactId>gppro</artifactId>
10+
<version>19.05.16</version>
11+
</parent>
12+
13+
<artifactId>globalplatformpro</artifactId>
14+
<name>GlobalPlatformPro library</name>
15+
16+
<dependencies>
17+
<!-- For APDU construction and parsing -->
18+
<dependency>
19+
<groupId>com.github.martinpaljak</groupId>
20+
<artifactId>apdu4j-core</artifactId>
21+
<version>19.05.08</version>
22+
</dependency>
23+
<!-- For CAP file parsing -->
24+
<dependency>
25+
<groupId>com.github.martinpaljak</groupId>
26+
<artifactId>capfile</artifactId>
27+
<version>19.03.04</version>
28+
</dependency>
29+
<!-- For logging -->
30+
<dependency>
31+
<groupId>org.slf4j</groupId>
32+
<artifactId>slf4j-api</artifactId>
33+
<version>1.7.25</version>
34+
</dependency>
35+
<!-- For JSON handling -->
36+
<dependency>
37+
<groupId>com.google.code.gson</groupId>
38+
<artifactId>gson</artifactId>
39+
<version>2.8.4</version>
40+
</dependency>
41+
<!-- For crypto in SCP03 -->
42+
<dependency>
43+
<groupId>org.bouncycastle</groupId>
44+
<artifactId>bcpkix-jdk15on</artifactId>
45+
<version>1.61</version>
46+
</dependency>
47+
<!-- For TLV handling -->
48+
<dependency>
49+
<groupId>com.payneteasy</groupId>
50+
<artifactId>ber-tlv</artifactId>
51+
<version>1.0-9</version>
52+
</dependency>
53+
<!-- For tests -->
54+
<dependency>
55+
<groupId>org.testng</groupId>
56+
<artifactId>testng</artifactId>
57+
<version>6.14.3</version>
58+
<scope>test</scope>
59+
</dependency>
60+
</dependencies>
61+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package pro.javacard.gp;
2+
3+
import apdu4j.CommandAPDU;
4+
import apdu4j.HexUtils;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import java.io.ByteArrayOutputStream;
9+
import java.io.IOException;
10+
import java.security.GeneralSecurityException;
11+
import java.security.PrivateKey;
12+
import java.security.Signature;
13+
14+
import static pro.javacard.gp.GPSession.INS_DELETE;
15+
16+
public class DMTokenGenerator {
17+
private static final Logger logger = LoggerFactory.getLogger(DMTokenGenerator.class);
18+
19+
private static final String defaultAlgorithm = "SHA1withRSA";
20+
private final String algorithm;
21+
22+
private PrivateKey key;
23+
private byte[] token; // Token to use
24+
25+
public DMTokenGenerator(PrivateKey key, String algorithm) {
26+
this.key = key;
27+
this.algorithm = algorithm;
28+
}
29+
30+
public DMTokenGenerator(PrivateKey key) {
31+
this(key, defaultAlgorithm);
32+
}
33+
34+
CommandAPDU applyToken(CommandAPDU apdu) throws GeneralSecurityException {
35+
ByteArrayOutputStream newData = new ByteArrayOutputStream();
36+
try {
37+
newData.write(apdu.getData());
38+
39+
if (key == null) {
40+
logger.trace("No private key for token generation provided");
41+
if (apdu.getINS() != (INS_DELETE & 0xFF))
42+
newData.write(0); // No token
43+
} else {
44+
if (apdu.getINS() == (INS_DELETE & 0xFF)) {
45+
// See GP 2.3.1 Table 11-23
46+
logger.trace("Adding tag 0x9E before Delete Token");
47+
newData.write(0x9E);
48+
}
49+
logger.trace("Using private key for token generation (" + algorithm + ")");
50+
byte[] token = calculateToken(apdu, key);
51+
newData.write(token.length);
52+
newData.write(token);
53+
}
54+
} catch (IOException e) {
55+
throw new RuntimeException("Could not apply DM token", e);
56+
}
57+
return new CommandAPDU(apdu.getCLA(), apdu.getINS(), apdu.getP1(), apdu.getP2(), newData.toByteArray()); // FIXME: Le handling
58+
}
59+
60+
private byte[] calculateToken(CommandAPDU apdu, PrivateKey key) throws GeneralSecurityException {
61+
return signData(key, getTokenData(apdu));
62+
}
63+
64+
private static byte[] getTokenData(CommandAPDU apdu) {
65+
try {
66+
ByteArrayOutputStream bo = new ByteArrayOutputStream();
67+
bo.write(apdu.getP1());
68+
bo.write(apdu.getP2());
69+
bo.write(apdu.getData().length); // FIXME: length handling for > 255 bytes
70+
bo.write(apdu.getData());
71+
return bo.toByteArray();
72+
} catch (IOException e) {
73+
throw new RuntimeException("Could not get P1/P2 or data for token calculation", e);
74+
}
75+
}
76+
77+
private byte[] signData(PrivateKey privateKey, byte[] apduData) throws GeneralSecurityException {
78+
Signature signer = Signature.getInstance(algorithm);
79+
signer.initSign(privateKey);
80+
signer.update(apduData);
81+
byte[] signature = signer.sign();
82+
logger.info("Generated DM token: {}" + HexUtils.bin2hex(signature));
83+
return signature;
84+
}
85+
86+
public boolean hasKey() {
87+
return key != null;
88+
}
89+
}

src/main/java/pro/javacard/gp/GPSessionKeyProvider.java library/src/main/java/pro/javacard/gp/GPCardKeys.java

+34-7
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@
2424
// Providers are free to derive session keys based on hardware backed master keys
2525
// PlaintextKeys provides card keys, that are ... plaintext (not backed by hardware)
2626

27-
public abstract class GPSessionKeyProvider {
27+
import apdu4j.HexUtils;
2828

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

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

35-
public abstract GPKey getKeyFor(KeyPurpose p);
35+
protected GPSecureChannel scp;
3636

3737
public abstract int getID();
3838

3939
public abstract int getVersion();
4040

41-
// Session keys are used for various purposes
41+
// Keys are used for various purposes
4242
public enum KeyPurpose {
4343
// ID is as used in diversification/derivation
4444
// That is - one based.
@@ -53,6 +53,33 @@ public enum KeyPurpose {
5353
public byte getValue() {
5454
return (byte) (value & 0xFF);
5555
}
56+
57+
// RMAC is derived, but not loaded to the card
58+
public static List<KeyPurpose> cardKeys() {
59+
return Arrays.asList(ENC, MAC, DEK);
60+
}
5661
}
5762

63+
// Encrypt data with static card DEK
64+
public abstract byte[] encrypt(byte[] data) throws GeneralSecurityException;
65+
66+
// Encrypt a key with card (or session) DEK
67+
public abstract byte[] encryptKey(GPCardKeys key, KeyPurpose p) throws GeneralSecurityException;
68+
69+
// Get session keys for given session data
70+
public abstract GPSessionKeys getSessionKeys(byte[] kdd);
71+
72+
// Get KCV of a card key
73+
public abstract byte[] kcv(KeyPurpose p);
74+
75+
// Diversify card keys automatically, based on INITIALIZE UPDATE response
76+
public GPCardKeys diversify(GPSecureChannel scp, byte[] kdd) {
77+
this.scp = scp;
78+
return this;
79+
}
80+
81+
@Override
82+
public String toString() {
83+
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);
84+
}
5885
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package pro.javacard.gp;
2+
3+
public class GPCardProfile {
4+
}

src/main/java/pro/javacard/gp/GPCommands.java library/src/main/java/pro/javacard/gp/GPCommands.java

+13-15
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,32 @@
1919
*/
2020
package pro.javacard.gp;
2121

22+
import apdu4j.CommandAPDU;
2223
import apdu4j.HexUtils;
24+
import apdu4j.ResponseAPDU;
2325
import pro.javacard.AID;
2426

25-
import javax.smartcardio.CardException;
26-
import javax.smartcardio.CommandAPDU;
27-
import javax.smartcardio.ResponseAPDU;
27+
import java.io.IOException;
2828
import java.io.PrintStream;
2929

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

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

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

47-
public static void setPerso(GlobalPlatform gp, byte[] data) throws GPException, CardException {
47+
public static void setPerso(GPSession gp, byte[] data) throws GPException, IOException {
4848
if (data == null || data.length != 8)
4949
throw new IllegalArgumentException("Perso data must be 8 bytes");
5050
byte[] payload = GPUtils.concatenate(new byte[]{(byte) 0x9f, 0x66, (byte) data.length}, data);
@@ -66,11 +66,10 @@ public static void listRegistry(GPRegistry reg, PrintStream out, boolean verbose
6666
out.println(tab + "Parent: " + e.getDomain());
6767
}
6868
if (e.getType() == GPRegistryEntry.Kind.ExecutableLoadFile) {
69-
GPRegistryEntryPkg pkg = (GPRegistryEntryPkg) e;
70-
if (pkg.getVersion() != null) {
71-
out.println(tab + "Version: " + pkg.getVersionString());
69+
if (e.getVersion() != null) {
70+
out.println(tab + "Version: " + e.getVersionString());
7271
}
73-
for (AID a : pkg.getModules()) {
72+
for (AID a : e.getModules()) {
7473
out.print(tab + "Applet: " + HexUtils.bin2hex(a.getBytes()));
7574
if (verbose) {
7675
out.println(" (" + GPUtils.byteArrayToReadableString(a.getBytes()) + ")");
@@ -79,12 +78,11 @@ public static void listRegistry(GPRegistry reg, PrintStream out, boolean verbose
7978
}
8079
}
8180
} else {
82-
GPRegistryEntryApp app = (GPRegistryEntryApp) e;
83-
if (app.getLoadFile() != null) {
84-
out.println(tab + "From: " + app.getLoadFile());
81+
if (e.getLoadFile() != null) {
82+
out.println(tab + "From: " + e.getLoadFile());
8583
}
8684
//if (!app.getPrivileges().isEmpty()) {
87-
out.println(tab + "Privs: " + app.getPrivileges());
85+
out.println(tab + "Privs: " + e.getPrivileges());
8886
//}
8987
}
9088
out.println();

0 commit comments

Comments
 (0)