Compare commits

..
6 Commits
5 changed files with 157 additions and 7 deletions
@@ -121,6 +121,21 @@ public class Crypto {
}
}
public static byte[] ecb3des(byte[] key, byte[] data) {
try {
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding", "BC");
SecretKeySpec keyDes = new SecretKeySpec(resizeKey24(key), "DES");
cipher.init(Cipher.ENCRYPT_MODE, keyDes);
return cipher.doFinal(data);
} catch (GeneralSecurityException e) {
throw new RuntimeException("Could not encrypt data", e);
}
}
public static byte[] kcv3des(byte[] key) {
return Arrays.copyOf(ecb3des(key, NullBytes8), 3);
}
/**
* Generates a 3DES MAC for SCP02 communication
*
@@ -24,6 +24,7 @@ public class GlobalPlatformCommandSet {
static final byte INS_DELETE = (byte) 0xE4;
static final byte INS_INSTALL = (byte) 0xE6;
static final byte INS_LOAD = (byte) 0xE8;
static final byte INS_PUT_KEY = (byte) 0xD8;
static final byte SELECT_P1_BY_NAME = (byte) 0x04;
static final byte EXTERNAL_AUTHENTICATE_P1 = (byte) 0x01;
@@ -37,7 +38,10 @@ public class GlobalPlatformCommandSet {
private SCP02Keys cardKeys;
private Session session;
private final byte[] testKey = Hex.decode("404142434445464748494a4b4c4d4e4f");
private final byte[] gpDefaultKey = Hex.decode("404142434445464748494a4b4c4d4e4f");
private final SCP02Keys gpDefaultKeys = new SCP02Keys(gpDefaultKey, gpDefaultKey, gpDefaultKey);
private final byte[] developmentKey = Hex.decode("c212e073ff8b4bbfaff4de8ab655221f");
/**
* Constructs a new command set with the given CardChannel.
@@ -46,7 +50,26 @@ public class GlobalPlatformCommandSet {
*/
public GlobalPlatformCommandSet(CardChannel apduChannel) {
this.apduChannel = apduChannel;
this.cardKeys = new SCP02Keys(testKey, testKey);
setCardKeys(developmentKey);
}
/**
* Sets the given key as all of ENC, MAC and DEK static keys, used to derive session keys.
* @param key the key
*/
public void setCardKeys(byte[] key) {
setCardKeys(key, key, key);
}
/**
* Sets the the ENC, MAC and DEK static keys, used to derive session keys.
*
* @param encKey the ENC key
* @param macKey the MAC key
* @param dekKey the DEK key
*/
public void setCardKeys(byte[] encKey, byte[] macKey, byte[] dekKey) {
this.cardKeys = new SCP02Keys(encKey, macKey, dekKey);;
}
/**
@@ -74,7 +97,12 @@ public class GlobalPlatformCommandSet {
APDUCommand cmd = new APDUCommand(0x80, INS_INITIALIZE_UPDATE, 0, 0, hostChallenge, true);
APDUResponse resp = apduChannel.send(cmd);
if (resp.isOK()) {
this.session = SecureChannel.verifyChallenge(hostChallenge, this.cardKeys, resp);
try {
this.session = SecureChannel.verifyChallenge(hostChallenge, this.cardKeys, resp);
} catch(APDUException e) {
this.session = SecureChannel.verifyChallenge(hostChallenge, gpDefaultKeys, resp);
this.session.markAsUsingFallbackKeys();
}
this.secureChannel = new SecureChannel(this.apduChannel, this.session.getKeys());
}
@@ -103,18 +131,93 @@ public class GlobalPlatformCommandSet {
return this.secureChannel.send(cmd);
}
/**
* Opens an SCP02 secure channel with default keys.
* Convenience method for openSecureChannel with auto key ugprade.
*
* @throws APDUException the card didn't respond 0x9000 to either INITIALIZE UPDATE or EXTERNAL AUTHENTICATE
* @throws IOException communication error
*/
public void openSecureChannel() throws APDUException, IOException {
openSecureChannel(true);
}
/**
* Opens an SCP02 secure channel. If with the current keys the card cryptogram cannot be verified, an attempt is made
* to use the default GlobalPlatform keys instead. This does not require additional commands to the card. In case
* the autoUpgradeKeys is set to true and the default GlobalPlatform keys were used, a PUT KEY command is sent to
* change the keys to the current ones.
*
* @param autoUpgradeKeys upgrade keys if default GP keys are loaded
* @throws APDUException the card didn't respond 0x9000 to either INITIALIZE UPDATE or EXTERNAL AUTHENTICATE
* @throws IOException communication error
*/
public void openSecureChannel(boolean autoUpgradeKeys) throws APDUException, IOException {
SecureRandom random = new SecureRandom();
byte[] hostChallenge = new byte[8];
random.nextBytes(hostChallenge);
initializeUpdate(hostChallenge).checkOK();
externalAuthenticate(hostChallenge).checkOK();
if (this.session.usesFallbackKeys() && autoUpgradeKeys) {
this.putSCP02Keys(this.cardKeys.getEncKeyData(), this.cardKeys.getMacKeyData(), this.cardKeys.getDekKeyData(), 0, 1).checkOK();
}
}
/**
* Sends a PUT KEY APDU to load or replace SCP02 keys. The key is used for all 3 of ENC, MAC and DEK.
*
* @param key the key to load
* @param oldKvn the KVN to replace, 0 to put a new key without replacing
* @param newKvn the KVN of the new keyset
* @return
* @throws IOException
*/
public APDUResponse putSCP02Keys(byte[] key, int oldKvn, int newKvn) throws IOException {
return putSCP02Keys(key, key, key, oldKvn, newKvn);
}
/**
* Sends a PUT KEY APDU to load or replace SCP02 keys. The keys are assumed to be 3DES keys
*
* @param encKey the ENC key to load
* @param macKey the MAC key to load
* @param dekKey the DEK key to load
* @param oldKvn the KVN to replace, 0 to put a new key without replacing
* @param newKvn the KVN of the new keyset
* @return
* @throws IOException
*/
public APDUResponse putSCP02Keys(byte[] encKey, byte[] macKey, byte[] dekKey, int oldKvn, int newKvn) throws IOException {
if (encKey.length != 16 || macKey.length != 16 || dekKey.length != 16){
throw new IllegalArgumentException("All keys must be 16-byte 3DES keys");
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(newKvn);
writeSCP02Key(bos, encKey);
writeSCP02Key(bos, macKey);
writeSCP02Key(bos, dekKey);
APDUCommand cmd = new APDUCommand(0x84, INS_PUT_KEY, oldKvn, 0x81, bos.toByteArray());
return this.secureChannel.send(cmd);
}
/**
* writes an encrypted key for the PUT KEY command
* @param bos the output stream to write to
* @param key the key to encrypt and write
* @throws IOException if the ByteArrayOutputStream throws it (never)
*/
private void writeSCP02Key(ByteArrayOutputStream bos, byte[] key) throws IOException {
byte[] encrypted = Crypto.ecb3des(session.getKeys().getDekKeyData(), key);
byte[] kcv = Crypto.kcv3des(key);
bos.write(0x80);
bos.write(encrypted.length);
bos.write(encrypted);
bos.write(kcv.length);
bos.write(kcv);
}
/**
@@ -6,16 +6,19 @@ package im.status.keycard.globalplatform;
public class SCP02Keys {
public byte[] encKeyData;
public byte[] macKeyData;
public byte[] dekKeyData;
/**
* Constructor. Takes the ENC and MAC keys.
*
* @param encKeyData encryption key
* @param macKeyData mac key
* @param dekKeyData data encryption key
*/
public SCP02Keys(byte[] encKeyData, byte[] macKeyData) {
public SCP02Keys(byte[] encKeyData, byte[] macKeyData, byte[] dekKeyData) {
this.encKeyData = encKeyData;
this.macKeyData = macKeyData;
this.dekKeyData = dekKeyData;
}
/**
@@ -34,4 +37,13 @@ public class SCP02Keys {
public byte[] getMacKeyData() {
return macKeyData;
}
/**
* The DEK key
*
* @return the DEK key
*/
public byte[] getDekKeyData() {
return dekKeyData;
}
}
@@ -8,7 +8,7 @@ import im.status.keycard.io.APDUResponse;
import im.status.keycard.io.CardChannel;
/**
* An SCP02 Secure Channel. Wraps a CardChannel to allow transparent handling of the scure channel.
* An SCP02 Secure Channel. Wraps a CardChannel to allow transparent handling of the secure channel.
*/
public class SecureChannel {
private CardChannel channel;
@@ -77,8 +77,9 @@ public class SecureChannel {
byte[] sessionEncKey = Crypto.deriveSCP02SessionKey(cardKeys.getEncKeyData(), seq, DERIVATION_PURPOSE_ENC);
byte[] sessionMacKey = Crypto.deriveSCP02SessionKey(cardKeys.getMacKeyData(), seq, DERIVATION_PURPOSE_MAC);
byte[] sessionDekKey = Crypto.deriveSCP02SessionKey(cardKeys.getDekKeyData(), seq, DERIVATION_PURPOSE_DEK);
SCP02Keys sessionKeys = new SCP02Keys(sessionEncKey, sessionMacKey);
SCP02Keys sessionKeys = new SCP02Keys(sessionEncKey, sessionMacKey, sessionDekKey);
boolean verified = Crypto.verifyCryptogram(sessionKeys.getEncKeyData(), hostChallenge, cardChallenge, cardCryptogram);
if (!verified) {
@@ -6,6 +6,7 @@ package im.status.keycard.globalplatform;
public class Session {
private SCP02Keys keys;
private byte[] cardChallenge;
private boolean fallbackKeys;
/**
* Constructs the SCP02 session.
@@ -16,6 +17,7 @@ public class Session {
public Session(SCP02Keys keys, byte[] cardChallenge) {
this.keys = keys;
this.cardChallenge = cardChallenge;
this.fallbackKeys = false;
}
/**
@@ -33,4 +35,21 @@ public class Session {
public byte[] getCardChallenge() {
return cardChallenge;
}
/**
* Marks this session as using a fallback keyset.
*/
public void markAsUsingFallbackKeys() {
fallbackKeys = true;
}
/**
* True if a fallback keyset is being used.
*
* @return true or false
*/
public boolean usesFallbackKeys() {
return fallbackKeys;
}
}