From 3aae8daf74fdc71abc4352000efb5f6c6a96246a Mon Sep 17 00:00:00 2001 From: clemenso Date: Fri, 7 Jan 2011 09:47:39 +0000 Subject: Retail CBC MAC git-svn-id: https://joinup.ec.europa.eu/svn/mocca/trunk@875 8a26b1a7-26f0-462f-b9ef-d0e30c41f5a4 --- .../at/gv/egiz/smcc/activation/Activation.java | 492 +++++++++++++++++++++ .../at/gv/egiz/smcc/activation/ActivationTest.java | 343 +++++++++++--- .../at/gv/egiz/smcc/activation/RetailCBCMac.java | 186 ++++++++ 3 files changed, 969 insertions(+), 52 deletions(-) create mode 100644 smccTest/src/main/java/at/gv/egiz/smcc/activation/Activation.java create mode 100644 smccTest/src/main/java/at/gv/egiz/smcc/activation/RetailCBCMac.java (limited to 'smccTest/src/main/java/at/gv/egiz') 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 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. + *

+ * The retail CBC Mac is calculated according to the following + * algorithm: + *

+ * + * @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 off + * @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; + } + + + + + +} -- cgit v1.2.3