add key derivation code and test

This commit is contained in:
Andrea Franz 2018-08-27 12:35:56 +02:00
parent 9e2596f835
commit c9c9c97b1b
No known key found for this signature in database
GPG Key ID: 4F0D2F2D9DE7F29D
5 changed files with 212 additions and 1 deletions

View File

@ -36,4 +36,24 @@ public class APDUCommand {
return out.toByteArray();
}
public int getCla() {
return cla;
}
public int getIns() {
return ins;
}
public int getP1() {
return p1;
}
public int getP2() {
return p2;
}
public byte[] getData() {
return data;
}
}

View File

@ -0,0 +1,10 @@
package im.status.applet_installer_test.appletinstaller;
public class APDUException extends Exception {
public final int sw;
public APDUException(int sw, String message) {
super(message + ", 0x" + String.format("0x%04X", sw));
this.sw = sw;
}
}

View File

@ -14,7 +14,6 @@ public class APDUResponse {
public APDUResponse(byte[] apdu) {
if (apdu.length < 2) {
throw new IllegalArgumentException("APDU response must be at least 2 bytes");
}
this.apdu = apdu;
this.parse();

View File

@ -0,0 +1,111 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import im.status.applet_installer_test.appletinstaller.APDUCommand;
import im.status.applet_installer_test.appletinstaller.APDUException;
import im.status.applet_installer_test.appletinstaller.APDUResponse;
import im.status.applet_installer_test.appletinstaller.HexUtils;
public class InitializeUpdate {
public static int CLA = 0x80;
public static int INS = 0x50;
public static int P1 = 0;
public static int P2 = 0;
public static byte[] DERIVATION_PURPOSE_ENC = new byte[]{(byte) 0x01, (byte) 0x82};
public static byte[] DERIVATION_PURPOSE_MAC = new byte[]{(byte) 0x01, (byte) 0x01};
public static byte[] DERIVATION_PURPOSE_DEK = new byte[]{(byte) 0x01, (byte) 0x81};
private byte[] challenge;
public InitializeUpdate(byte[] challenge) {
this.challenge = challenge;
}
public APDUCommand getCommand() {
return new APDUCommand(CLA, INS, P1, P2, this.challenge);
}
public static byte[] generateChallenge() {
SecureRandom random = new SecureRandom();
byte challenge[] = new byte[8];
random.nextBytes(challenge);
return challenge;
}
public void validateResponse(APDUResponse resp) throws APDUException {
if (resp.getSw() == APDUResponse.SW_SECURITY_CONDITION_NOT_SATISFIED) {
throw new APDUException(resp.getSw(), "security confition not satisfied");
}
if (resp.getSw() == APDUResponse.SW_AUTHENTICATION_METHOD_BLOCKED) {
throw new APDUException(resp.getSw(), "authentication method blocked");
}
byte[] data = resp.getData();
if (data.length != 28) {
throw new APDUException(resp.getSw(), String.format("bad data length, expected 28, got %d", data.length));
}
byte[] diversificationdData = new byte[10];
System.arraycopy(data, 0, diversificationdData, 0, 10);
byte[] cardChallenge = new byte[8];
System.arraycopy(data, 12, cardChallenge, 0, 8);
byte[] ssc = new byte[2];
System.arraycopy(data, 12, ssc, 0, 2);
byte[] cardCryptogram = new byte[8];
System.arraycopy(data, 20, cardCryptogram, 0, 8);
System.out.printf("diversification: %s, %n", HexUtils.byteArrayToHexString(diversificationdData));
System.out.printf("cardChallege: %s, %n", HexUtils.byteArrayToHexString(cardChallenge));
System.out.printf("ssc: %s, %n", HexUtils.byteArrayToHexString(ssc));
System.out.printf("cardCryptogram: %s, %n", HexUtils.byteArrayToHexString(cardCryptogram));
//System.out.printf("key data: %s, %n", HexUtils.byteArrayToHexString(keyData));
}
public byte[] deriveKey(byte[] cardKey, byte[] seq, byte[] purposeData) {
byte[] key24 = new byte[24];
System.arraycopy(cardKey, 0, key24, 0, 16);
System.arraycopy(cardKey, 0, key24, 16, 8);
try {
byte[] derivationData = new byte[16];
// 2 bytes constant
System.arraycopy(purposeData, 0, derivationData, 0, 2);
// 2 bytes sequence counter + 12 bytes 0x00
System.arraycopy(seq, 0, derivationData, 2, 2);
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
IvParameterSpec iv = new IvParameterSpec(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
SecretKeySpec tmpKey = new SecretKeySpec(key24, "DESede");
cipher.init(Cipher.ENCRYPT_MODE, tmpKey, iv);
byte[] keyData = cipher.doFinal(derivationData);
return keyData;
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException("error generating session keys.", e);
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
throw new RuntimeException("error generating session keys.", e);
}
}
}

View File

@ -0,0 +1,71 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import im.status.applet_installer_test.appletinstaller.APDUCommand;
import im.status.applet_installer_test.appletinstaller.APDUException;
import im.status.applet_installer_test.appletinstaller.APDUResponse;
import im.status.applet_installer_test.appletinstaller.HexUtils;
import static org.junit.Assert.*;
public class InitializeUpdateTest {
@Test
public void getCommand() {
byte[] challenge = InitializeUpdate.generateChallenge();
InitializeUpdate init = new InitializeUpdate(challenge);
APDUCommand cmd = init.getCommand();
assertEquals(0x80, cmd.getCla());
assertEquals(0x50, cmd.getIns());
assertEquals(0, cmd.getP1());
assertEquals(0, cmd.getP2());
assertEquals(challenge, cmd.getData());
}
@Test
public void deriveKey() {
InitializeUpdate init = new InitializeUpdate(null);
byte[] cardKey = HexUtils.hexStringToByteArray("404142434445464748494a4b4c4d4e4f");
byte[] seq = HexUtils.hexStringToByteArray("0065");
byte[] encKey = init.deriveKey(cardKey, seq, InitializeUpdate.DERIVATION_PURPOSE_ENC);
String expectedEncKey = "85E72AAF47874218A202BF5EF891DD21";
assertEquals(expectedEncKey, HexUtils.byteArrayToHexString(encKey));
byte[] macKey = init.deriveKey(cardKey, seq, InitializeUpdate.DERIVATION_PURPOSE_MAC);
String expectedMacKey = "309CF99E164F3A97F3E5017FF540A79F";
assertEquals(expectedMacKey, HexUtils.byteArrayToHexString(macKey));
byte[] dekKey = init.deriveKey(cardKey, seq, InitializeUpdate.DERIVATION_PURPOSE_DEK);
String expectedDekKey = "93D08F8025242C4D775D69B9F16C939B";
assertEquals(expectedDekKey, HexUtils.byteArrayToHexString(dekKey));
}
@Test
public void validateResponse_BadResponse() throws APDUException {
byte[] apdu = HexUtils.hexStringToByteArray("000002650183039536622002003b5e508f751c0af3016e3fbc23d3a66982");
APDUResponse resp = new APDUResponse(apdu);
byte[] challenge = InitializeUpdate.generateChallenge();
InitializeUpdate init = new InitializeUpdate(challenge);
try {
init.validateResponse(resp);
fail("expected APDUException to be thrown");
} catch (APDUException e) {
assertEquals(0x6982, e.sw);
}
}
@Test
public void validateResponse_GoodResponse() throws APDUException {
byte[] challenge = HexUtils.hexStringToByteArray("54676ea0043a2f49");
InitializeUpdate init = new InitializeUpdate(challenge);
byte[] apdu = HexUtils.hexStringToByteArray("000002650183039536622002003d2310f3cc9e6cca2551458b8bdb6e9000");
APDUResponse resp = new APDUResponse(apdu);
init.validateResponse(resp);
}
}