summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--smccTest/src/main/java/at/gv/egiz/smcc/activation/Activation.java492
-rw-r--r--smccTest/src/main/java/at/gv/egiz/smcc/activation/ActivationTest.java343
-rw-r--r--smccTest/src/main/java/at/gv/egiz/smcc/activation/RetailCBCMac.java186
-rw-r--r--smccTest/src/test/java/at/gv/egiz/smcc/activation/TestRetailCBCMac.java358
4 files changed, 1327 insertions, 52 deletions
diff --git a/smccTest/src/main/java/at/gv/egiz/smcc/activation/Activation.java b/smccTest/src/main/java/at/gv/egiz/smcc/activation/Activation.java
new file mode 100644
index 00000000..1f201d0d
--- /dev/null
+++ b/smccTest/src/main/java/at/gv/egiz/smcc/activation/Activation.java
@@ -0,0 +1,492 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package at.gv.egiz.smcc.activation;
+
+import at.gv.egiz.smcc.SignatureCardException;
+import at.gv.egiz.smcc.util.TLVSequence;
+import iaik.security.provider.IAIK;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+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 javax.smartcardio.Card;
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+import javax.smartcardio.TerminalFactory;
+
+/**
+ *
+ * @author clemens
+ */
+public class Activation {
+
+ /**
+ * cf. kp_mk_ss_test.xml
+ */
+ static SecretKeySpec MK_QSig = new SecretKeySpec(new byte[]{
+ (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
+ (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0A,
+ (byte) 0x0B, (byte) 0x0C, (byte) 0x0D, (byte) 0x0E, (byte) 0x0F,
+ (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14,
+ (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18}, "3DES");
+
+ /**
+ * DES_CBC[MK_Appl](SHA-256(CIN|KeyNum|KeyVer))
+ *
+ * @param masterKey
+ * @param cin
+ * @param keyNum
+ * @param keyVer
+ * @return
+ */
+ static SecretKeySpec deriveApplicationKey(SecretKeySpec masterKey, byte[] cin, byte keyNum, byte keyVer) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
+
+ System.out.println("derive application key for \n CIN "
+ + toString(cin)
+ + "\n key number 0x0"
+ + Byte.toString(keyNum)
+ + "\n key version 0x0"
+ + Byte.toString(keyVer));
+
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+
+ sha256.update(cin);
+ sha256.update(keyNum);
+ sha256.update(keyVer);
+ byte[] derivationParam = sha256.digest();
+
+ return deriveKey(masterKey, Arrays.copyOf(derivationParam, 24));
+ }
+
+ static SecretKeySpec deriveKENC(SecretKeySpec k_appl) throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
+
+ System.out.println("derive k_enc");
+
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ byte[] derivationParam = sha256.digest("K_ENC".getBytes("ASCII"));
+
+ return deriveKey(k_appl, Arrays.copyOf(derivationParam, 24));
+ }
+
+ static SecretKeySpec deriveKMAC(SecretKeySpec k_appl) throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
+ System.out.println("derive k_mac");
+
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ byte[] derivationParam = sha256.digest("K_MAC".getBytes("ASCII"));
+
+ return deriveKey(k_appl, Arrays.copyOf(derivationParam, 24));
+ }
+
+ /**
+ * DES_CBC[MK](derivationParam)
+ * 3DES/CBC/NoPadding
+ *
+ * @param masterKey
+ * @param derivationParam
+ * @return
+ */
+ static SecretKeySpec deriveKey(SecretKeySpec masterKey, byte[] derivationParam) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
+
+ if (derivationParam.length != 24) {
+ throw new RuntimeException("invalid 3TDES derivation parameter: " + toString(derivationParam));
+ }
+
+ Cipher tdes = Cipher.getInstance("3DES/CBC/NoPadding");
+ tdes.init(Cipher.ENCRYPT_MODE, masterKey, new IvParameterSpec(ZEROS));
+
+ System.out.println(tdes.getAlgorithm());
+ System.out.println("master key : " + toString(masterKey.getEncoded()));
+
+ System.out.println("derivation parameter ("
+ + derivationParam.length * 8 + "bit): "
+ + toString(derivationParam));
+ System.out.println("derivation key ("
+ + masterKey.getAlgorithm() + ") :"
+ + toString(masterKey.getEncoded()));
+
+ byte[] x = tdes.doFinal(derivationParam);
+
+ System.out.println("x (" + x.length * 8 + "bit): " + toString(x));
+
+ if (x.length != 24) {
+ throw new RuntimeException("invalid derived key: " + toString(x));
+ }
+
+ for (int offset = 0; offset < x.length; offset += 8) {
+ adjustParityBit(x, offset);
+ }
+
+ SecretKeySpec derivedKey = new SecretKeySpec(x, masterKey.getAlgorithm());
+
+ System.out.println("derived key ("
+ + derivedKey.getAlgorithm() + ") :"
+ + toString(derivedKey.getEncoded()));
+
+ return derivedKey;
+
+ }
+
+ public final static byte[] AID_QSig = new byte[] {
+ (byte) 0xd0, (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0x17,
+ (byte) 0x00, (byte) 0x12, (byte) 0x01};
+
+ private final static byte[] ZEROS = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ /** Bit mask for counting the ones. */
+ private final static byte[] BIT_MASK = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (byte) 0x80
+ };
+
+ private static void adjustParityBit(byte[] x, int offset) {
+
+ for (int i = 0; i < 8; i++) {
+ int ones = 0;
+ for (int j = 1; j < BIT_MASK.length; j++) {
+ if ((x[i + offset] & BIT_MASK[j]) == BIT_MASK[j]) {
+ ones++;
+ }
+ }
+
+ if ((ones & 0x1) > 0) {
+ x[i + offset] &= (byte) 0xfe; // odd
+ } else {
+ x[i + offset] |= 0x1; // even
+ }
+ }
+ }
+
+
+ public static String toString(byte[] b) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ if (b != null && b.length > 0) {
+ sb.append(Integer.toHexString((b[0] & 240) >> 4));
+ sb.append(Integer.toHexString(b[0] & 15));
+ for (int i = 1; i < b.length; i++) {
+ sb.append((i % 32 == 0) ? '\n' : ':');
+ sb.append(Integer.toHexString((b[i] & 240) >> 4));
+ sb.append(Integer.toHexString(b[i] & 15));
+ }
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+
+ CardTerminal ct;
+ Card icc;
+ CardChannel channel;
+
+ public void setUp() throws NoSuchAlgorithmException, CardException {
+
+ IAIK.addAsJDK14Provider();
+
+ System.out.println("create terminalFactory...\n");
+ TerminalFactory terminalFactory = TerminalFactory.getInstance("PC/SC", null);
+
+ System.out.println("get supported terminals...\n");
+ List<CardTerminal> terminals = terminalFactory.terminals().list();
+
+ if (terminals.size() < 1) {
+ throw new CardException("no terminals");
+ }
+
+ ct = terminals.get(0);
+ System.out.println("found " + terminals.size() + " terminals, using " + ct.getName() + "\n");
+
+ System.out.println("connecting " + ct.getName() + "\n");
+ icc = ct.connect("*");
+ byte[] atr = icc.getATR().getBytes();
+ byte[] historicalBytes = icc.getATR().getHistoricalBytes();
+ System.out.println("found card " + toString(atr) + " " + new String(historicalBytes, Charset.forName("ASCII")) + "\n\n");
+
+ channel = icc.getBasicChannel();
+ }
+
+ public void activate() throws CardException, SignatureCardException {
+
+ System.out.println("SELECT MF");
+ CommandAPDU cmdAPDU = new CommandAPDU(0x00, 0xA4, 0x00, 0x0c, new byte[]{(byte) 0x3F, (byte) 0x00});
+ System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+ ResponseAPDU resp = channel.transmit(cmdAPDU);
+ System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ byte[] cin = getCIN();
+
+ byte[] k_qsigNumVer = getKApplNumberVersion(AID_QSig);
+
+ SecretKeySpec k_qsig;
+ try {
+ k_qsig = deriveApplicationKey(MK_QSig, cin, k_qsigNumVer[0], k_qsigNumVer[1]);
+ System.out.println("K_QS (" + k_qsig.getAlgorithm() + ")"
+ + toString(k_qsig.getEncoded()));
+ } catch (Exception ex) {
+ throw new SignatureCardException("failed to derive k_qs", ex);
+ }
+
+ System.out.println("SELECT EF.PuK_QS");
+ // P1=0x00 -> 67:00 (wrong length)
+ cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x02, 0x04, new byte[]{(byte) 0x0e, (byte) 0x01}, 256);
+ System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+ resp = channel.transmit(cmdAPDU);
+ System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ openSecureChannel(k_qsig, cin);
+ }
+
+
+ /**
+ * @precondition MF
+ *
+ * @return
+ * @throws CardException
+ * @throws SignatureCardException
+ */
+ byte[] getCIN() throws CardException, SignatureCardException {
+ CommandAPDU cmdAPDU;
+ ResponseAPDU resp;
+
+ System.out.println("READ EF.GDO (SFI=02)");
+ cmdAPDU = new CommandAPDU(0x00, 0xb0, 0x82, 0x00, 256);
+ System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+ resp = channel.transmit(cmdAPDU);
+ System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ if (resp.getSW() == 0x9000) {
+ byte[] cin = new TLVSequence(resp.getData()).getValue(0x5a);
+ System.out.println("CIN: " + toString(cin));
+ return cin;
+ } else {
+ throw new SignatureCardException("Failed to read EF.GDO: 0x" + Integer.toHexString(resp.getSW()));
+ }
+ }
+
+ /**
+ * @precondition MF
+ * @postcondition AID
+ *
+ * @param aid
+ * @return
+ * @throws CardException
+ * @throws SignatureCardException
+ */
+ byte[] getKApplNumberVersion(byte[] aid) throws CardException, SignatureCardException {
+
+ System.out.println("SELECT AID " + toString(aid));
+ // P1=0x00 -> 67:00 (wrong length)
+ CommandAPDU cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x04, 0x00, aid, 256);
+ System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+ ResponseAPDU resp = channel.transmit(cmdAPDU);
+ System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ byte[] keyNumVer = null;
+
+ if (resp.getSW() == 0x9000) {
+ byte[] fciBytes = new TLVSequence(resp.getData()).getValue(0x6f);
+
+ TLVSequence fci = new TLVSequence(fciBytes);
+ System.out.println("FCI AID " + toString(aid));
+ System.out.println(fci);
+
+ TLVSequence proprietary = new TLVSequence(fci.getValue(0xa5));
+ System.out.println("proprietary information");
+ System.out.println(proprietary);
+
+ keyNumVer = proprietary.getValue(0x54);
+ if (keyNumVer == null || keyNumVer.length != 2) {
+ throw new SignatureCardException("invalid key number/version: "
+ + toString(keyNumVer));
+ }
+ return keyNumVer;
+
+ } else {
+ throw new SignatureCardException("Failed to read AID: 0x"
+ + Integer.toHexString(resp.getSW()));
+ }
+ }
+
+ void openSecureChannel(SecretKeySpec k_appl, byte[] cin) throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, CardException {
+// function openSecureChannelG3(card, crypto, iccsn, kappl, kid, algo) {
+
+// if (typeof(kid) == "undefined")
+// kid = 0x81;
+//
+// if (typeof(algo) == "undefined")
+// algo = 0x54;
+//
+// // Perform mutual authentication procedure
+// GPSystem.trace("Performing mutual authentication");
+//
+// var kenc = deriveKENC(crypto, kappl);
+// var kmac = deriveKMAC(crypto, kappl);
+
+ SecretKeySpec k_enc = deriveKENC(k_appl);
+ SecretKeySpec k_mac = deriveKMAC(k_appl);
+
+//
+// // Manage SE: Set K_Appl for Mutual Authentication with Session Key establishment
+// var bb = new ByteBuffer("8301", HEX);
+// bb.append(kid);
+// bb.append(0x80);
+// bb.append(0x01);
+// bb.append(algo);
+//
+// card.sendApdu(0x00, 0x22, 0x81, 0xA4, bb.toByteString(), [0x9000])
+//
+// var rndicc = card.sendApdu(0x00, 0x84, 0x00, 0x00, 0x08, [0x9000]);
+
+ int dfSpecificKeyRef = 1;
+ byte[] crt_at = new byte[]{
+ (byte) 0x83, (byte) 0x01, (byte) (0x80 | (0x7f & dfSpecificKeyRef)),
+ // algorithm id 0x54???
+ (byte) 0x80, (byte) 0x01, (byte) 0x54
+ };
+
+ System.out.println("MSE SET AT for key agreement");
+ CommandAPDU cmdAPDU = new CommandAPDU(0x00, 0x22, 0x81, 0xa4, crt_at);
+ System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+ ResponseAPDU resp = channel.transmit(cmdAPDU);
+ System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ System.out.println("GET CHALLENGE");
+ cmdAPDU = new CommandAPDU(0x00, 0x84, 0x00, 0x00, 0x08);
+ System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+ resp = channel.transmit(cmdAPDU);
+ System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ byte[] rnd_icc = resp.getData();
+
+ if (rnd_icc.length != 8) {
+ throw new RuntimeException("invalid RND.ICC: " + toString(rnd_icc));
+ }
+
+ if (cin.length != 10) {
+ throw new RuntimeException("invalid CIN: " + toString(cin));
+ }
+
+// var rndifd = crypto.generateRandom(8);
+// var snifd = crypto.generateRandom(8);
+// var kifd = crypto.generateRandom(64); // 32 -> 64
+// var snicc = iccsn.bytes(2, 8);
+//
+
+ Random rand = new Random(System.currentTimeMillis());
+
+ byte[] rnd_ifd = new byte[8];
+ rand.nextBytes(rnd_ifd);
+ byte[] icc_id = Arrays.copyOfRange(cin, 2, 8);
+ byte[] ifd_id = new byte[8];
+ rand.nextBytes(ifd_id);
+ byte[] kd_ifd = new byte[64];
+ rand.nextBytes(kd_ifd);
+
+// var plain = rndifd.concat(snifd).concat(rndicc).concat(snicc).concat(kifd);
+// GPSystem.trace("Plain Block : " + plain);
+// print("Plain Block : " + plain);
+//
+// print("K_enc : " + kenc.getComponent(Key.DES));
+//
+// var cryptogram = crypto.encrypt(kenc, Crypto.DES_CBC, plain, new ByteString("0000000000000000", HEX));
+// GPSystem.trace("Cryptogram : " + cryptogram);
+// print("Cryptogram : " + cryptogram);
+//
+// print("K_mac : " + kmac.getComponent(Key.DES));
+//
+// var mac = crypto.sign(kmac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2));
+// GPSystem.trace("MAC : " + mac);
+// print("MAC : " + mac);
+//
+//
+// var autresp = card.sendApdu(0x00, 0x82, 0x00, 0x00, cryptogram.concat(mac), 0); // 81 -> 00
+//
+// if (card.SW != 0x9000) {
+// GPSystem.trace("Mutual authenticate failed with " + card.SW.toString(16) + " \"" + card.SWMSG + "\"");
+// throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card did not accept MAC");
+// }
+//
+// cryptogram = autresp.bytes(0, 96); // 64 -> 96
+// mac = autresp.bytes(96, 8); // 64 -> 96
+//
+// if (!crypto.verify(kmac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2), mac)) {
+// throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card MAC did not verify correctly");
+// }
+//
+// plain = crypto.decrypt(kenc, Crypto.DES_CBC, cryptogram, new ByteString("0000000000000000", HEX));
+// GPSystem.trace("Plain Block : " + plain);
+//
+// if (!plain.bytes(0, 8).equals(rndicc)) {
+// throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card response does not contain matching RND.ICC");
+// }
+//
+// if (!plain.bytes(16, 8).equals(rndifd)) {
+// throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card response does not contain matching RND.IFD");
+// }
+//
+// var kicc = plain.bytes(32, 64); // 32 -> 64
+// keyinp = kicc.xor(kifd);
+//
+// var hashin = keyinp.concat(new ByteString("00000001", HEX));
+// var hashres = crypto.digest(Crypto.SHA_256, hashin);
+// var kencval = hashres.bytes(0, 24);
+// var kencssc = hashres.bytes(24, 8);
+//
+// GPSystem.trace("Kenc : " + kencval);
+// GPSystem.trace("Kenc SSC : " + kencssc);
+// var kenc = new Key();
+// kenc.setComponent(Key.DES, kencval);
+//
+// var hashin = keyinp.concat(new ByteString("00000002", HEX));
+// var hashres = crypto.digest(Crypto.SHA_256, hashin);
+// var kmacval = hashres.bytes(0, 24);
+// var kmacssc = hashres.bytes(24, 8);
+//
+// GPSystem.trace("Kmac : " + kmacval);
+// GPSystem.trace("Kmac SSC : " + kmacssc);
+// var kmac = new Key();
+// kmac.setComponent(Key.DES, kmacval);
+//
+// var sc = new IsoSecureChannel(crypto);
+// sc.setEncKey(kenc);
+// sc.setMacKey(kmac);
+//
+// sc.setMACSendSequenceCounter(kmacssc);
+// sc.setEncryptionSendSequenceCounter(kencssc);
+// return sc;
+//}
+
+
+ }
+
+ public static void main(String[] args) {
+
+ try {
+ Activation test = new Activation();
+ test.setUp();
+ test.activate();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/smccTest/src/main/java/at/gv/egiz/smcc/activation/ActivationTest.java b/smccTest/src/main/java/at/gv/egiz/smcc/activation/ActivationTest.java
index d032d45e..a9d147db 100644
--- a/smccTest/src/main/java/at/gv/egiz/smcc/activation/ActivationTest.java
+++ b/smccTest/src/main/java/at/gv/egiz/smcc/activation/ActivationTest.java
@@ -12,12 +12,24 @@ import iaik.asn1.ASN1Object;
import iaik.asn1.CodingException;
import iaik.asn1.DerCoder;
import iaik.security.provider.IAIK;
+import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.charset.Charset;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.security.spec.RSAPublicKeySpec;
+import java.security.NoSuchProviderException;
import java.util.Arrays;
import java.util.List;
+import java.util.Random;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
@@ -120,18 +132,24 @@ public class ActivationTest {
resp = channel.transmit(cmdAPDU);
System.out.println(" -> " + toString(resp.getBytes()) + "\n");
- System.out.println("SELECT EF.GDO");
- // alternative: read with SFI=02: 00 b0 82 00 fa
- // P1=0x00 -> 6a:80 (incorrect cmd data)
- // no Le -> 67:00 (wrong length)
- cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x02, 0x00, new byte[] {(byte) 0x2f, (byte) 0x02}, 256);
- System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
- resp = channel.transmit(cmdAPDU);
- System.out.println(" -> " + toString(resp.getBytes()) + "\n");
-
- System.out.println("READ EF.GDO");
- // 7.2.2 (case2), offset=0
- cmdAPDU = new CommandAPDU(0x00, 0xb0, 0x00, 0x00, 256);
+// System.out.println("SELECT EF.GDO");
+// // alternative: read with SFI=02: 00 b0 82 00 fa
+// // P1=0x00 -> 6a:80 (incorrect cmd data)
+// // no Le -> 67:00 (wrong length)
+// cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x02, 0x00, new byte[] {(byte) 0x2f, (byte) 0x02}, 256);
+// System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+// resp = channel.transmit(cmdAPDU);
+// System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+//
+// System.out.println("READ EF.GDO");
+// // 7.2.2 (case2), offset=0
+// cmdAPDU = new CommandAPDU(0x00, 0xb0, 0x00, 0x00, 256);
+// System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+// resp = channel.transmit(cmdAPDU);
+// System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ System.out.println("READ EF.GDO (SFI=02)");
+ cmdAPDU = new CommandAPDU(0x00, 0xb0, 0x82, 0x00, 256);
System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
resp = channel.transmit(cmdAPDU);
System.out.println(" -> " + toString(resp.getBytes()) + "\n");
@@ -145,23 +163,7 @@ public class ActivationTest {
}
}
- public static String toString(byte[] b) {
- StringBuffer sb = new StringBuffer();
- sb.append('[');
- if (b != null && b.length > 0) {
- sb.append(Integer.toHexString((b[0] & 240) >> 4));
- sb.append(Integer.toHexString(b[0] & 15));
- for (int i = 1; i < b.length; i++) {
- sb.append((i % 32 == 0) ? '\n' : ':');
- sb.append(Integer.toHexString((b[i] & 240) >> 4));
- sb.append(Integer.toHexString(b[i] & 15));
- }
- }
- sb.append(']');
- return sb.toString();
- }
-
- public void getPuK_GewSig() throws CardException, SignatureCardException, CodingException {
+ public ASN1Object getCIO_PrK_SS() throws CardException, CodingException {
CommandAPDU cmdAPDU;
ResponseAPDU resp;
@@ -173,14 +175,14 @@ public class ActivationTest {
System.out.println("SELECT DF.QualifizierteSignatur");
// P1=0x00 -> 67:00 (wrong length)
- cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x04, 0x0c, new byte[] {(byte) 0xd0, (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0x17, (byte) 0x00, (byte) 0x12, (byte) 0x01});
+ cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x04, 0x00, new byte[]{(byte) 0xd0, (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0x17, (byte) 0x00, (byte) 0x12, (byte) 0x01}, 256);
System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
resp = channel.transmit(cmdAPDU);
System.out.println(" -> " + toString(resp.getBytes()) + "\n");
System.out.println("SELECT EF.PrKD");
// P1=0x00 -> 67:00 (wrong length)
- cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x02, 0x04, new byte[] {(byte) 0x50, (byte) 0x35}, 256);
+ cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x02, 0x04, new byte[]{(byte) 0x50, (byte) 0x35}, 256);
System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
resp = channel.transmit(cmdAPDU);
System.out.println(" -> " + toString(resp.getBytes()) + "\n");
@@ -203,8 +205,56 @@ public class ActivationTest {
BigInteger keyRef = (BigInteger) efPrK_QS.getComponentAt(1).getComponentAt(4).getValue();
System.out.println("PrK_QS keyRef: 0x" + Integer.toHexString(keyRef.intValue()) + "\n");
+ return efPrK_QS;
+ }
+
+ public void getPuK_SicSig(byte[] cin) throws CardException, SignatureCardException, CodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ CommandAPDU cmdAPDU;
+ ResponseAPDU resp;
+
+ System.out.println("SELECT MF");
+ cmdAPDU = new CommandAPDU(0x00, 0xA4, 0x00, 0x0c, new byte[]{(byte) 0x3f, (byte) 0x00});
+ System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+ resp = channel.transmit(cmdAPDU);
+ System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ System.out.println("SELECT DF.QualifizierteSignatur");
+ // P1=0x00 -> 67:00 (wrong length)
+ cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x04, 0x00, new byte[]{(byte) 0xd0, (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0x17, (byte) 0x00, (byte) 0x12, (byte) 0x01}, 256);
+ System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
+ resp = channel.transmit(cmdAPDU);
+ System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+
+ byte[] keyNumVer = null;
+
+ if (resp.getSW() == 0x9000) {
+ byte[] fciBytes = new TLVSequence(resp.getData()).getValue(ISO7816Utils.TAG_FCI);
+
+ TLVSequence fci = new TLVSequence(fciBytes);
+ System.out.println("FCI DF.QualifizierteSignatur");
+ System.out.println(fci);
+
+ TLVSequence proprietary = new TLVSequence(fci.getValue(0xa5));
+ System.out.println("proprietary information");
+ System.out.println(proprietary);
+
+ keyNumVer = proprietary.getValue(0x54);
+ if (keyNumVer == null || keyNumVer.length != 2) {
+ throw new SignatureCardException("invalid key number/version: " + toString(keyNumVer));
+ }
+
+ System.out.println("key number: 0x" + Byte.toString(keyNumVer[0]));
+ System.out.println("key version: 0x" + Byte.toString(keyNumVer[1]));
+ } else {
+ throw new SignatureCardException("Failed to read DF.QualifizierteSignatur: 0x" + Integer.toHexString(resp.getSW()));
+ }
+
+ SecretKeySpec kp_mk_ss = getKP_MK_SS();
+
+ SecretKeySpec kp_ss = deriveApplicationKey(kp_mk_ss, cin, keyNumVer);
+
int dfSpecificKeyRef = 1;
- byte[] crt_at = new byte[] {
+ byte[] crt_at = new byte[]{
(byte) 0x83, (byte) 0x01, (byte) (0x80 | (0x7f & dfSpecificKeyRef)),
// algorithm id 0x54???
(byte) 0x80, (byte) 0x01, (byte) 0x54
@@ -217,44 +267,233 @@ public class ActivationTest {
System.out.println(" -> " + toString(resp.getBytes()) + "\n");
System.out.println("GET CHALLENGE");
- // eg. RNDICC = [ed:9b:a3:78:83:2f:d3:6c:90:00]
cmdAPDU = new CommandAPDU(0x00, 0x84, 0x00, 0x00, 0x08);
System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
resp = channel.transmit(cmdAPDU);
System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+ byte[] rnd_icc = resp.getData();
+
+ SecretKeySpec k_enc = deriveKey(kp_ss, "3DES/CBC/NoPadding", iv, K_ENC);
+ SecretKeySpec k_mac = deriveKey(kp_ss, "3DES/CBC/NoPadding", iv, K_MAC);
+ mutualAuth(cin, rnd_icc, k_enc, k_mac);
- // ICCSN + RNDICC => Kryptogramm_CRS (STARCOS31, p357 authentication according to e-SignK)
- // MUTUALAUTH -> Kryptogramm_Karte -> prüfung -> session key
- System.out.println("MUTUAL AUTHENTICATE TODO...");
-
+ }
- System.out.println("SELECT EF.PuK_QS");
- // P1=0x00 -> 67:00 (wrong length)
- cmdAPDU = new CommandAPDU(0x00, 0xa4, 0x02, 0x04, new byte[] {(byte) 0x0e, (byte) 0x01}, 256);
- System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
- resp = channel.transmit(cmdAPDU);
- System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+ private SecretKeySpec getKP_MK_SS() {
+ byte[] kp_mk_ss_Bytes = new byte[]{(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0A, (byte) 0x0B, (byte) 0x0C, (byte) 0x0D, (byte) 0x0E, (byte) 0x0F, (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18};
+ SecretKeySpec kp_mk_ss = new SecretKeySpec(kp_mk_ss_Bytes, "3DES");
+ return kp_mk_ss;
+ }
- System.out.println("READ EF.PuK_QS");
- // 7.2.2 (case2), offset=0
- cmdAPDU = new CommandAPDU(0x00, 0xb0, 0x00, 0x00, 256);
+ protected SecretKeySpec deriveApplicationKey(SecretKeySpec keySpec, byte[] cin, byte[] keyNumVer) throws NoSuchProviderException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
+
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+
+ sha256.update(cin);
+ sha256.update(keyNumVer);
+ byte[] derivationParam = sha256.digest();
+ System.out.println("derivationParam = SHA-256(CIN|kNum|kVer) (" + derivationParam.length * 8 + "bit) = " + toString(derivationParam));
+ derivationParam = Arrays.copyOf(derivationParam, 24);
+ System.out.println("derivationParam (" + derivationParam.length * 8 + "bit) = " + toString(derivationParam));
+
+ // DESedeKeySpec kp_mk_ssSpec = new DESedeKeySpec(kp_mk_ss);
+ System.out.println("Application Master Key KP_MK_SS (DES-EDE): " + toString(keySpec.getEncoded()));
+ System.out.println("Derive application key KP_SS");
+ Cipher tripleDES = Cipher.getInstance("3DES/CBC/NoPadding", "IAIK");
+ tripleDES.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
+ System.out.println("derivationParam (" + derivationParam.length * 8 + "bit) = " + toString(derivationParam));
+ byte[] x = tripleDES.doFinal(derivationParam);
+ System.out.println("kp_ss (" + x.length * 8 + "bit): " + toString(x));
+ for (int key_i = 0; key_i < x.length; key_i += 8) {
+ for (int i = 0; i < 8; i++) {
+ int ones = 0;
+ for (int j = 1; j < BIT_MASK.length; j++) {
+ if ((x[i + key_i] & BIT_MASK[j]) == BIT_MASK[j]) {
+ ones++;
+ }
+ }
+ if ((ones & 0x1) > 0) {
+ x[i + key_i] &= (byte) 0xfe; // odd
+ } else {
+ x[i + key_i] |= 0x1; // even
+ }
+ }
+ }
+ System.out.println("kp_ss (parity adjusted): " + toString(x));
+ SecretKeySpec kp_ss = new SecretKeySpec(x, "3DES");
+ return kp_ss;
+ }
+
+ private void mutualAuth(byte[] cin, byte[] rnd_icc, SecretKeySpec kenc, SecretKeySpec kmac) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, CardException {
+ if (rnd_icc.length != 8) {
+ throw new RuntimeException("invalid RND.ICC: " + toString(rnd_icc));
+ }
+
+ if (cin.length != 10) {
+ throw new RuntimeException("invalid CIN: " + toString(cin));
+ }
+
+ Random rand = new Random(System.currentTimeMillis());
+
+ byte[] rnd_ifd = new byte[8];
+ rand.nextBytes(rnd_ifd);
+ byte[] icc_id = Arrays.copyOfRange(cin, cin.length - 8, cin.length);
+ byte[] ifd_id = new byte[8];
+ rand.nextBytes(ifd_id);
+ byte[] kd_ifd = new byte[64];
+ rand.nextBytes(kd_ifd);
+
+ Cipher tDES = Cipher.getInstance("3DES/CBC/NoPadding");
+ tDES.init(Cipher.ENCRYPT_MODE, kenc, new IvParameterSpec(iv));
+
+ byte[] sendData = new byte[4*8+64];
+ System.arraycopy(rnd_ifd, 0, sendData, 0, 8);
+ System.arraycopy(ifd_id, 0, sendData, 8, 8);
+ System.arraycopy(rnd_icc, 0, sendData, 16, 8);
+ System.arraycopy(icc_id, 0, sendData, 24, 8);
+ System.arraycopy(kd_ifd, 0, sendData, 32, 64);
+
+ System.out.println("cryptogram input (" + sendData.length + "byte): "
+ + toString(sendData));
+
+// tDES.update(rnd_ifd); // 8 byte
+// tDES.update(ifd_id); // 8 byte
+// tDES.update(rnd_icc); // 8 byte
+// tDES.update(icc_id); // 8 byte
+// tDES.update(kd_ifd); // 64 byte
+
+ byte[] cryptogram = tDES.doFinal(sendData);
+ System.out.println("cryptogram (" + cryptogram.length + "byte): "
+ + toString(cryptogram));
+
+ Mac retailMac = Mac.getInstance("CMacDESede");
+ retailMac.init(kmac);
+ byte[] mac = retailMac.doFinal(cryptogram);
+ System.out.println("MAC: " + toString(mac));
+
+ byte[] c = new byte[cryptogram.length + mac.length];
+ System.arraycopy(cryptogram, 0, c, 0, cryptogram.length);
+ System.arraycopy(mac, 0, c, cryptogram.length, mac.length);
+ System.out.println(c.length + "bytes :" + toString(c));
+ CommandAPDU cmdAPDU = new CommandAPDU(0x00, 0x82, 0x00, 0x81, c, 256); // 81->00
System.out.println(" cmd apdu " + toString(cmdAPDU.getBytes()));
- resp = channel.transmit(cmdAPDU);
+ ResponseAPDU resp = channel.transmit(cmdAPDU);
System.out.println(" -> " + toString(resp.getBytes()) + "\n");
+ }
+
+
+ private SecretKeySpec deriveKey(SecretKeySpec masterkey, String cipherAlias, byte[] iv, byte[] derivationParam) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+
+ if (derivationParam.length != 24) {
+ throw new RuntimeException("invalid 3TDES derivation parameter: " + toString(derivationParam));
+ }
+
+ //3DES/CBC/NoPadding
+ Cipher cipher = Cipher.getInstance(cipherAlias);
+ cipher.init(Cipher.ENCRYPT_MODE, masterkey, new IvParameterSpec(iv));
- System.out.println("PuK_QS:\n" + toString(resp.getData()));
+ System.out.println("derivation parameter ("
+ + derivationParam.length * 8 + "bit): "
+ + toString(derivationParam));
+ System.out.println("derivation key ("
+ + masterkey.getAlgorithm() + ") :"
+ + toString(masterkey.getEncoded()));
+ byte[] x = cipher.doFinal(derivationParam);
+ System.out.println("x (" + x.length * 8 + "bit): " + toString(x));
+
+ if (x.length != 24) {
+ throw new RuntimeException("invalid derived key: " + toString(x));
+ }
+
+ for (int offset = 0; offset < x.length; offset += 8) {
+ adjustParityBit(x, offset);
+ }
+
+ SecretKeySpec derivedKey = new SecretKeySpec(x, masterkey.getAlgorithm());
+
+ System.out.println("derived key ("
+ + derivedKey.getAlgorithm() + ") :"
+ + toString(derivedKey.getEncoded()));
+
+ return derivedKey;
}
+
+ private final static byte[] K_ENC;
+ private final static byte[] K_MAC;
+
+ static {
+ byte[] encBytes;
+ byte[] macBytes;
+ try {
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ encBytes = sha256.digest("K_ENC".getBytes("ASCII"));
+ macBytes = sha256.digest("K_MAC".getBytes("ASCII"));
+ } catch (NoSuchAlgorithmException ex) {
+ encBytes = new byte[] {(byte)0xe0};
+ macBytes = new byte[] {(byte)0xe0};
+ } catch (UnsupportedEncodingException ex) {
+ encBytes = new byte[] {(byte)0xe1};
+ macBytes = new byte[] {(byte)0xe1};
+ }
+ K_ENC = Arrays.copyOf(encBytes, 24);
+ K_MAC = Arrays.copyOf(macBytes, 24);
+ }
+
+ private final static byte[] iv = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ /** Bit mask for counting the ones. */
+ private final static byte[] BIT_MASK = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (byte) 0x80
+ };
+
+ private void adjustParityBit(byte[] x, int offset) {
+
+ for (int i = 0; i < 8; i++) {
+ int ones = 0;
+ for (int j = 1; j < BIT_MASK.length; j++) {
+ if ((x[i + offset] & BIT_MASK[j]) == BIT_MASK[j]) {
+ ones++;
+ }
+ }
- public static void main(String[] args) throws NoSuchAlgorithmException, CardException, SignatureCardException, CodingException {
+ if ((ones & 0x1) > 0) {
+ x[i + offset] &= (byte) 0xfe; // odd
+ } else {
+ x[i + offset] |= 0x1; // even
+ }
+ }
+ }
+
+ public static String toString(byte[] b) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ if (b != null && b.length > 0) {
+ sb.append(Integer.toHexString((b[0] & 240) >> 4));
+ sb.append(Integer.toHexString(b[0] & 15));
+ for (int i = 1; i < b.length; i++) {
+ sb.append((i % 32 == 0) ? '\n' : ':');
+ sb.append(Integer.toHexString((b[i] & 240) >> 4));
+ sb.append(Integer.toHexString(b[i] & 15));
+ }
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ public static void main(String[] args) throws NoSuchAlgorithmException, CardException, SignatureCardException, CodingException, InvalidKeyException, NoSuchProviderException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
ActivationTest test = new ActivationTest();
test.setUp();
-// test.getCIN();
+ byte[] cin = test.getCIN();
+
+ test.getCIO_PrK_SS();
+
// test.getSVNr();
- test.getPuK_GewSig();
+ test.getPuK_SicSig(cin);
}
}
diff --git a/smccTest/src/main/java/at/gv/egiz/smcc/activation/RetailCBCMac.java b/smccTest/src/main/java/at/gv/egiz/smcc/activation/RetailCBCMac.java
new file mode 100644
index 00000000..79f7be62
--- /dev/null
+++ b/smccTest/src/main/java/at/gv/egiz/smcc/activation/RetailCBCMac.java
@@ -0,0 +1,186 @@
+package at.gv.egiz.smcc.activation;
+
+import iaik.utils.CryptoUtils;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class RetailCBCMac {
+
+ /**
+ * Calculates a Retail CBC Mac from the given message.
+ * <p>
+ * The retail CBC Mac is calculated according to the following
+ * algorithm:
+ * <ul>
+ * <li>
+ * Pad the message to a multiple n of the CBC cipher block size with
+ * a leading one bit followed by as many zero bits as necessary.
+ * </li>
+ * <li>
+ * Create a CBC cipher key from the first <code>cbcCipherKeyLen</code>
+ * bytes of the <code>csk</code> key and use it to calculate a
+ * CBC Mac value from the first n-1 blocks of the padded message.
+ * For CBC Mac calculation initialize the CBC Cipher with an
+ * IV of all zero bytes.
+ * </li>
+ * <li>
+ * XOR the last block of the padded message with the CBC mac value
+ * and calculate the final retail MAC by encrypting the XOR result
+ * with the given Cipher algorithm in ECB mode using no padding.
+ * </li>
+ * </ul>
+ *
+ * @param msg the message
+ * @param cbcCipherAlg the name of the CBC Cipher algorithm to be used
+ * @param cipherAlg the name of the final Cipher algorithm to be used
+ * @param csk the secret key to be used
+ * @param cbcCipherKeyLen the length of the CBC cipher key to be used
+ * @param blockSize the block size of the CBC Cipher
+ *
+ * @return the retail CBC Mac value
+ *
+ * @throws NoSuchAlgorithmException if any of the requested Cipher algorithms
+ * is not available
+ * @throws NoSuchProviderException if the IAIK provider is not installed
+ * @throws InvalidKeyException if the key cannot be used with the Ciphers
+ * @throws GeneralSecurityException if the Cipher operation(s) fails
+ */
+ static byte[] retailMac(byte[] msg,
+ String cbcCipherAlg,
+ String cipherAlg,
+ SecretKey csk,
+ int cbcCipherKeyLen,
+ int blockSize)
+ throws NoSuchAlgorithmException,
+ NoSuchProviderException,
+ InvalidKeyException,
+ GeneralSecurityException {
+
+ if (msg == null) {
+ throw new NullPointerException("Message m must not be null!");
+ }
+ if (csk == null) {
+ throw new NullPointerException("Key csk must not be null!");
+ }
+
+ // calculate key for CBC cipher
+ byte[] rawCsk = csk.getEncoded();
+ int cskLen = rawCsk.length;
+ SecretKey cbcCipherKey;
+ if (cskLen == cbcCipherKeyLen) {
+ cbcCipherKey = csk;
+ } else if (cskLen < cbcCipherKeyLen) {
+ throw new InvalidKeyException("Key too short!");
+ } else {
+ byte[] rawCbcCipherKey = new byte[blockSize];
+ System.arraycopy(rawCsk, 0, rawCbcCipherKey, 0, blockSize);
+ cbcCipherKey = new SecretKeySpec(rawCbcCipherKey, cbcCipherAlg);
+ }
+ // if necessary pad message with zeros
+ byte[] paddedMsg = pad(msg, blockSize);
+
+ // calculate CBC Mac for the first n-1 blocks
+ int n = paddedMsg.length;
+ int n_1 = n - blockSize;
+ byte[] cbcMac = cbcMac(paddedMsg, 0, n_1, cbcCipherKey, cbcCipherAlg, blockSize);
+
+ // calculate retail mac
+ byte[] xor = new byte[blockSize];
+ CryptoUtils.xorBlock(paddedMsg, n_1, cbcMac, 0, xor, 0, blockSize);
+ Cipher cipher = Cipher.getInstance(cipherAlg+"/ECB/NoPadding", "IAIK");
+ cipher.init(Cipher.ENCRYPT_MODE, csk);
+ byte[] retailMac = cipher.doFinal(xor);
+ return retailMac;
+ }
+
+ /**
+ * Calculates a simple CBC Mac from the given (already) padded message.
+ *
+ * @param paddedMsg the (zero) padded message
+ * @param off the start offset in the paddedMsg array
+ * @param len the number of bytes to be processed, starting at <code>off</code>
+ * @param key the Cipher key
+ * @param cipherAlg the name of the CBC Cipher algorithm to be used
+ * @param blockSize the block size of the CBC Cipher
+ *
+ * @return the CBC Mac value
+ *
+ * @throws NoSuchAlgorithmException if the requested Cipher algorithm
+ * is not available
+ * @throws NoSuchProviderException if the IAIK provider is not installed
+ * @throws InvalidKeyException if the key cannot be used with the Ciphers
+ * @throws GeneralSecurityException if the Cipher operation fails
+ */
+ static byte[] cbcMac(byte[] paddedMsg,
+ int off,
+ int len,
+ SecretKey key,
+ String cipherAlg,
+ int blockSize)
+ throws NoSuchAlgorithmException,
+ NoSuchProviderException,
+ InvalidKeyException,
+ GeneralSecurityException {
+
+ if (paddedMsg == null) {
+ throw new NullPointerException("Message must not be null!");
+ }
+ if (key == null) {
+ throw new NullPointerException("Key csk must not be null!");
+ }
+
+
+ Cipher cbcCipher = Cipher.getInstance(cipherAlg+"/CBC/NoPadding", "IAIK");
+ byte[] iv = new byte[blockSize];
+ cbcCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
+ int finOff = off;
+ if (len > blockSize) {
+ finOff = len - blockSize;
+ cbcCipher.update(paddedMsg, 0, finOff);
+ }
+ byte[] mac = cbcCipher.doFinal(paddedMsg, finOff, blockSize);
+ return mac;
+ }
+
+ /**
+ * Pads the given message to a multiple of the given blocksize with
+ * a leading one bit followed by as many zero bits as necessary
+ *
+ * @param msg the message to be padded
+ * @param blockSize the block size
+ *
+ * @return the padded message
+ */
+ static byte[] pad(byte[] msg, int blockSize) {
+ int paddingLen;
+ byte[] paddedMsg;
+
+ int msgLen = msg.length;
+ if (msgLen == 0) {
+ paddingLen = blockSize;
+ } else {
+ paddingLen = blockSize - msgLen % blockSize;
+ }
+ if (paddingLen > 0) {
+ paddedMsg = new byte[msgLen + paddingLen];
+ System.arraycopy(msg, 0, paddedMsg, 0, msgLen);
+ paddedMsg[msgLen] = (byte)0x80;
+ } else {
+ paddedMsg = msg;
+ }
+ return paddedMsg;
+ }
+
+
+
+
+
+}
diff --git a/smccTest/src/test/java/at/gv/egiz/smcc/activation/TestRetailCBCMac.java b/smccTest/src/test/java/at/gv/egiz/smcc/activation/TestRetailCBCMac.java
new file mode 100644
index 00000000..495728c9
--- /dev/null
+++ b/smccTest/src/test/java/at/gv/egiz/smcc/activation/TestRetailCBCMac.java
@@ -0,0 +1,358 @@
+package at.gv.egiz.smcc.activation;
+
+import iaik.security.provider.IAIK;
+import iaik.utils.CryptoUtils;
+import iaik.utils.Util;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.util.Arrays;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import junit.framework.TestCase;
+
+public class TestRetailCBCMac extends TestCase {
+
+
+ final static String[] PLAIN_DATA = {
+ "5A 10 D3 0E FC F6 5D 7F 89 FF B6 69 E4 8C 1C 88 C9 D3 AB "+
+ "74 3D B0 2C DB 00 00 00 23 00 78 71 11 6C 3C FC 5F 53 6C 49 A9 F3 42 E0 "+
+ "E1 32 EE AE 2F 21 79 06 C0 6E EE 69 1B B0 10 B7 54 53 FA 31 EB 92 D1 51 "+
+ "0D 90 E3 FC E2 F6 1C 1E 15 68 0D A5 AB CA 5B E8 45 23 8D 87 7C 5F 7B F5 "+
+ "A1 D8 89 ED 30",
+
+ "5C AB 2E 55 2C 1D BD F1 9C F9 F6 B9 28 73 5B EC 11 2A 2B "+
+ "FC E5 C8 C6 86 00 00 00 23 00 78 71 11 59 D0 0C 8B 7B 45 A2 1F 02 62 5D "+
+ "F3 18 F8 F2 46 A5 14 99 60 77 55 25 2C 3D AF 19 C9 CC C1 C8 9B EA 39 A3 "+
+ "32 7C 45 19 87 B1 8A 98 CA D7 E5 90 A7 3D 70 BE 74 36 E3 09 C5 F9 00 57 "+
+ "82 AF 64 B9 7D",
+
+ "E4 A9 1F F1 5A AB BF 2F E8 3D C5 AA 70 BF A4 48 EE 32 88 "+
+ "4B 5A 0A B2 E7 00 00 00 23 00 78 71 11 8A B1 B3 1F D0 40 33 D8 0A 9C 21 "+
+ "5C CB 47 63 69 2C EC D4 46 92 C7 07 1D EF B5 6D 28 89 68 36 BA E1 7D A4 "+
+ "60 03 5A 2C 0D 53 EF 43 2D BA 09 30 CD F7 4A 8C 70 18 ED 57 EA C9 63 E6 "+
+ "43 B5 6C 97 94",
+
+ "FD A8 CA 31 D7 41 EA 1D 62 47 71 A8 C9 2F D8 3E 3C 3A F1 "+
+ "D2 60 41 8F 2A 00 00 00 23 00 78 71 11 45 0D B9 4F AF A1 4D 0C 9B 62 FE "+
+ "B9 7C 3F C0 B0 B0 C2 67 44 3F 5F 2B B5 EC 8A 0D D9 D6 33 9B A0 EC 97 C2 "+
+ "40 FA A4 56 67 46 02 58 B1 0A AB 27 FE 25 38 23 21 74 F6 34 3C 81 D4 1A "+
+ "17 DB C2 A2 E9",
+
+ "FB 5E 02 2A 0C A0 F0 09 7F FF 93 05 D1 3D 14 B4 14 3A DC "+
+ "21 A3 AB DE C6 00 00 00 23 00 78 71 11 84 21 A6 CC 84 8B 0A 07 64 2F B2 "+
+ "A1 05 7B 08 9A FB CE 3B B6 A1 36 A8 03 0D 7B EA 2D 1E 7A D7 E2 C1 2D E8 "+
+ "DF 82 CE 3F 43 D5 F6 21 DB D2 F7 31 5B A8 F2 65 E2 F4 E4 7E 1E 94 F4 E6 "+
+ "28 14 01 CA 18",
+ };
+
+ final static String[] CRYPTO_GRAM = {
+ "E6 5E D9 34 14 4D 4B 86 B5 40 FB 2A 06 29 44 2D 5F 41 14 "+
+ "E5 95 A3 6D 07 B0 C4 6A 14 35 8D FE 72 C6 18 37 96 15 20 4B CE E7 A9 A1 "+
+ "FD C6 85 3D F5 AE EA E4 92 95 0B EB 95 74 CA ED 38 E8 4B E0 FC 4C 55 1B "+
+ "DE DE AD B3 13 7E ED AB EC B3 C2 FC A2 BE 72 A6 BE 50 D4 79 89 D1 70 A4 "+
+ "4A 15 EC A0 D7",
+
+ "D8 91 7C 06 60 DC 9B 74 28 2A 44 68 96 AF EE 93 AC D2 CF "+
+ "DB CE 19 F6 73 73 F5 61 B0 AB 20 DF 63 F3 C7 4C 47 86 BD E0 7A 9B 04 64 "+
+ "F9 87 2D F3 A6 FB 3F E5 B8 80 C5 F1 29 A9 0A 56 4E 7F 96 BE 30 88 FD 81 "+
+ "86 7D 13 56 6E 17 4B 2A 31 36 D3 DA 24 FD 66 7D 21 B2 9E C9 2D 63 46 EF "+
+ "97 06 E1 DA 15",
+
+ "5A 4E F8 99 EE ED 02 1A A2 C8 A9 6B DD FB D6 CE 2A F0 5A "+
+ "05 57 6A 79 66 3C B3 B7 CA 00 98 52 EE 35 72 AE 65 17 D4 0F A7 B3 20 F5 "+
+ "25 A7 A5 7A 47 79 EB 65 FD DE 63 68 A5 C0 04 EF 3D DA 21 5E DB B7 83 FD "+
+ "EF FD 52 28 91 D3 67 F4 9E 69 57 1C 19 08 5F 67 98 86 6E 99 2F 19 A7 54 "+
+ "B2 CE E8 F1 C0",
+
+ "AB D2 A6 ED C1 AA 2C ED 12 67 7E 38 B2 CB 5C 4E 06 A0 E0 "+
+ "5A FC 59 59 11 25 8B 92 07 12 81 D3 FE 7E B8 4B 35 CD F6 A2 CD 98 C7 EF "+
+ "FC EA 75 94 2C 55 6F 35 B5 4E 83 F8 82 7B B0 85 DD B5 8E B1 04 B2 F0 71 "+
+ "79 42 FA A4 81 68 64 83 FB F8 5E 75 B3 C6 C8 CA 17 9C 94 45 EC A9 8A ED "+
+ "73 58 F9 9B 97",
+
+ "AE E7 4C D8 AA 2E EA C4 6C D9 19 48 3C 19 12 D0 EA E8 70 "+
+ "00 39 2F AF FD 77 36 3B 87 AF ED 11 9E 54 74 F6 4B DA 68 32 12 D7 B0 76 "+
+ "73 87 BE C3 74 08 0E DB 33 1E 66 AE 9E 23 56 DD DC 0D 61 FF 8B 15 4A 36 "+
+ "4E BD E7 9F E5 3C 15 43 4F CC B2 7F FC 4B DD D6 39 17 EB FD 3D D6 11 45 "+
+ "AA DD F0 2E 61",
+
+ };
+
+ final static String[] RETAIL_MAC = {
+ "27 D0 4D E8 17 20 46 E8",
+
+ "C2 31 EF 5A 99 AB FA 5F",
+
+ "09 6C 40 EE A6 12 FA 1C",
+
+ "55 FA 62 93 86 56 D0 38",
+
+ "9D 86 EB 57 A2 58 F8 C8",
+
+ };
+
+
+ final static String[] CSK = {
+ "54 3D 49 BF 31 51 25 94 67 8A 64 4F 1C B9 54 3D 31 46 43 79 E5 B9 A7 A7",
+ "76 23 EC 10 91 61 7C 75 EF EF BF 9D 7F 7F 9E FD 07 1A 0E 6E 51 0B D3 D0",
+ "76 23 EC 10 91 61 7C 75 EF EF BF 9D 7F 7F 9E FD 07 1A 0E 6E 51 0B D3 D0",
+ "54 3D 49 BF 31 51 25 94 67 8A 64 4F 1C B9 54 3D 31 46 43 79 E5 B9 A7 A72",
+ "10 57 9E F4 9D 34 4F B3 43 D0 62 92 45 D9 EF 45 16 2F CD 4F 4A D5 38 08"
+
+ };
+
+
+
+ public TestRetailCBCMac() {
+ super();
+ }
+
+ public TestRetailCBCMac(String name) {
+ super(name);
+ }
+
+ /* (non-Javadoc)
+ * @see iaik.AbstractTest#setUp()
+ */
+ public void setUp() throws Exception {
+ super.setUp();
+ IAIK.addAsProvider();
+ }
+
+
+
+ public void testRetailMac() {
+ String cbcCipherAlg = "DES";
+ String cipherAlg = "DESede";
+ int cbcCipherKeyLen = 8;
+ int blockSize = 8;
+
+
+ for (int i = 0; i < PLAIN_DATA.length; i++) {
+ byte[] plainData = Util.toByteArray(PLAIN_DATA[i].trim());
+ byte[] cryptoGram = Util.toByteArray(CRYPTO_GRAM[i].trim());
+ byte[] retailMac = Util.toByteArray(RETAIL_MAC[i].trim());
+ byte[] rawCsk = Util.toByteArray(CSK[i].trim());
+ byte[] calculatedMac = null;
+ try {
+ SecretKeySpec csk = new SecretKeySpec(rawCsk, cipherAlg);
+ calculatedMac = retailMac(cryptoGram, cbcCipherAlg, cipherAlg, csk, cbcCipherKeyLen, blockSize);
+ System.out.println("Expected Mac : " + Util.toString(retailMac));
+ System.out.println("Calculated Mac: " + Util.toString(calculatedMac));
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Unexpetced error: " + e.toString());
+ }
+ assertTrue("Different Mac values!", Arrays.equals(retailMac, calculatedMac));
+ }
+ }
+
+// public void testRetailMacDESTripleDES() {
+// testRetailMac("DES", "DESede", 8, 8);
+// }
+
+ public void testRetailMac(String cbcCipherAlg, String cipherAlg, int cbcCipherKeyLen, int blockSize) {
+ try {
+
+ Random rand = new Random();
+ KeyGenerator kg = KeyGenerator.getInstance(cipherAlg, "IAIK");
+ SecretKey key = kg.generateKey();
+ int[] dataLen = { 0, 3, 8, 15, 16, 20, 24, 31, 32, 39, 40, 48, 510, 512, 1111, 1024, 2000, 2048, 4003, 4096 };
+ for (int i = 0; i < dataLen.length; i++) {
+ int len = dataLen[i];
+ byte[] data = new byte[len];
+ rand.nextBytes(data);
+ System.out.println("\n Data (" + len + " bytes):" + Util.toString(data));
+
+ byte[] mac = retailMac(data, cbcCipherAlg, "DESede", key, cbcCipherKeyLen, blockSize);
+
+ System.out.println("MAC: " + Util.toString(mac));
+ }
+
+
+
+
+ } catch (Exception e) {
+ fail("Unexpetced error: " + e.toString());
+ }
+ }
+
+ /**
+ * Calculates a Retail CBC Mac from the given message.
+ * <p>
+ * The retail CBC Mac is calculated according to the following
+ * algorithm:
+ * <ul>
+ * <li>
+ * Pad the message to a multiple n of the CBC cipher block size with
+ * a leading one bit followed by as many zero bits as necessary.
+ * </li>
+ * <li>
+ * Create a CBC cipher key from the first <code>cbcCipherKeyLen</code>
+ * bytes of the <code>csk</code> key and use it to calculate a
+ * CBC Mac value from the first n-1 blocks of the padded message.
+ * For CBC Mac calculation initialize the CBC Cipher with an
+ * IV of all zero bytes.
+ * </li>
+ * <li>
+ * XOR the last block of the padded message with the CBC mac value
+ * and calculate the final retail MAC by encrypting the XOR result
+ * with the given Cipher algorithm in ECB mode using no padding.
+ * </li>
+ * </ul>
+ *
+ * @param msg the message
+ * @param cbcCipherAlg the name of the CBC Cipher algorithm to be used
+ * @param cipherAlg the name of the final Cipher algorithm to be used
+ * @param csk the secret key to be used
+ * @param cbcCipherKeyLen the length of the CBC cipher key to be used
+ * @param blockSize the block size of the CBC Cipher
+ *
+ * @return the retail CBC Mac value
+ *
+ * @throws NoSuchAlgorithmException if any of the requested Cipher algorithms
+ * is not available
+ * @throws NoSuchProviderException if the IAIK provider is not installed
+ * @throws InvalidKeyException if the key cannot be used with the Ciphers
+ * @throws GeneralSecurityException if the Cipher operation(s) fails
+ */
+ static byte[] retailMac(byte[] msg,
+ String cbcCipherAlg,
+ String cipherAlg,
+ SecretKey csk,
+ int cbcCipherKeyLen,
+ int blockSize)
+ throws NoSuchAlgorithmException,
+ NoSuchProviderException,
+ InvalidKeyException,
+ GeneralSecurityException {
+
+ if (msg == null) {
+ throw new NullPointerException("Message m must not be null!");
+ }
+ if (csk == null) {
+ throw new NullPointerException("Key csk must not be null!");
+ }
+
+ // calculate key for CBC cipher
+ byte[] rawCsk = csk.getEncoded();
+ int cskLen = rawCsk.length;
+ SecretKey cbcCipherKey;
+ if (cskLen == cbcCipherKeyLen) {
+ cbcCipherKey = csk;
+ } else if (cskLen < cbcCipherKeyLen) {
+ throw new InvalidKeyException("Key too short!");
+ } else {
+ byte[] rawCbcCipherKey = new byte[blockSize];
+ System.arraycopy(rawCsk, 0, rawCbcCipherKey, 0, blockSize);
+ cbcCipherKey = new SecretKeySpec(rawCbcCipherKey, cbcCipherAlg);
+ }
+ // if necessary pad message with zeros
+ byte[] paddedMsg = pad(msg, blockSize);
+
+ // calculate CBC Mac for the first n-1 blocks
+ int n = paddedMsg.length;
+ int n_1 = n - blockSize;
+ byte[] cbcMac = cbcMac(paddedMsg, 0, n_1, cbcCipherKey, cbcCipherAlg, blockSize);
+
+ // calculate retail mac
+ byte[] xor = new byte[blockSize];
+ CryptoUtils.xorBlock(paddedMsg, n_1, cbcMac, 0, xor, 0, blockSize);
+ Cipher cipher = Cipher.getInstance(cipherAlg+"/ECB/NoPadding", "IAIK");
+ cipher.init(Cipher.ENCRYPT_MODE, csk);
+ byte[] retailMac = cipher.doFinal(xor);
+ return retailMac;
+ }
+
+ /**
+ * Calculates a simple CBC Mac from the given (already) padded message.
+ *
+ * @param paddedMsg the (zero) padded message
+ * @param off the start offset in the paddedMsg array
+ * @param len the number of bytes to be processed, starting at <code>off</code>
+ * @param key the Cipher key
+ * @param cipherAlg the name of the CBC Cipher algorithm to be used
+ * @param blockSize the block size of the CBC Cipher
+ *
+ * @return the CBC Mac value
+ *
+ * @throws NoSuchAlgorithmException if the requested Cipher algorithm
+ * is not available
+ * @throws NoSuchProviderException if the IAIK provider is not installed
+ * @throws InvalidKeyException if the key cannot be used with the Ciphers
+ * @throws GeneralSecurityException if the Cipher operation fails
+ */
+ static byte[] cbcMac(byte[] paddedMsg,
+ int off,
+ int len,
+ SecretKey key,
+ String cipherAlg,
+ int blockSize)
+ throws NoSuchAlgorithmException,
+ NoSuchProviderException,
+ InvalidKeyException,
+ GeneralSecurityException {
+
+ if (paddedMsg == null) {
+ throw new NullPointerException("Message must not be null!");
+ }
+ if (key == null) {
+ throw new NullPointerException("Key csk must not be null!");
+ }
+
+
+ Cipher cbcCipher = Cipher.getInstance(cipherAlg+"/CBC/NoPadding", "IAIK");
+ byte[] iv = new byte[blockSize];
+ cbcCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
+ int finOff = off;
+ if (len > blockSize) {
+ finOff = len - blockSize;
+ cbcCipher.update(paddedMsg, 0, finOff);
+ }
+ byte[] mac = cbcCipher.doFinal(paddedMsg, finOff, blockSize);
+ return mac;
+ }
+
+ /**
+ * Pads the given message to a multiple of the given blocksize with
+ * a leading one bit followed by as many zero bits as necessary
+ *
+ * @param msg the message to be padded
+ * @param blockSize the block size
+ *
+ * @return the padded message
+ */
+ static byte[] pad(byte[] msg, int blockSize) {
+ int paddingLen;
+ byte[] paddedMsg;
+
+ int msgLen = msg.length;
+ if (msgLen == 0) {
+ paddingLen = blockSize;
+ } else {
+ paddingLen = blockSize - msgLen % blockSize;
+ }
+ if (paddingLen > 0) {
+ paddedMsg = new byte[msgLen + paddingLen];
+ System.arraycopy(msg, 0, paddedMsg, 0, msgLen);
+ paddedMsg[msgLen] = (byte)0x80;
+ } else {
+ paddedMsg = msg;
+ }
+ return paddedMsg;
+ }
+
+
+
+
+
+}