package de.persosim.simulator.protocols.ca; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import java.security.KeyPair; import java.security.SecureRandom; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.util.ArrayList; import java.util.Collection; import javax.crypto.spec.SecretKeySpec; import org.junit.Before; import org.junit.Test; import de.persosim.simulator.apdu.CommandApdu; import de.persosim.simulator.apdu.CommandApduFactory; import de.persosim.simulator.apdu.InterindustryCommandApduImpl; import de.persosim.simulator.cardobjects.CardObject; import de.persosim.simulator.cardobjects.KeyIdentifier; import de.persosim.simulator.cardobjects.KeyPairObject; import de.persosim.simulator.cardobjects.MasterFile; import de.persosim.simulator.cardobjects.OidIdentifier; import de.persosim.simulator.cardobjects.PasswordAuthObject; import de.persosim.simulator.crypto.CryptoUtil; import de.persosim.simulator.crypto.DomainParameterSetEcdh; import de.persosim.simulator.crypto.StandardizedDomainParameters; import de.persosim.simulator.exception.ProcessingException; import de.persosim.simulator.platform.CardStateAccessor; import de.persosim.simulator.platform.Iso7816; import de.persosim.simulator.platform.PlatformUtil; import de.persosim.simulator.processing.ProcessingData; import de.persosim.simulator.protocols.Tr03110Utils; import de.persosim.simulator.protocols.ta.TerminalAuthenticationMechanism; import de.persosim.simulator.secstatus.SecStatus; import de.persosim.simulator.secstatus.SecStatus.SecContext; import de.persosim.simulator.test.PersoSimTestCase; import de.persosim.simulator.tlv.ConstructedTlvDataObject; import de.persosim.simulator.tlv.TlvDataObject; import de.persosim.simulator.tlv.TlvDataObjectContainer; import de.persosim.simulator.utils.HexString; import de.persosim.simulator.utils.Utils; import mockit.Deencapsulation; import mockit.Delegate; import mockit.Expectations; import mockit.Mocked; import mockit.NonStrictExpectations; /** * @author slutters * */ public class AbstractCaProtocolTest extends PersoSimTestCase { protected static byte[] COMMAND_DATA_IMPL_KEY_SELECTION = HexString.toByteArray("80 0A 04 00 7F 00 07 02 02 03 02 02"); protected static byte[] COMMAND_DATA_EXPL_KEY_SELECTION = HexString.toByteArray("80 0A 04 00 7F 00 07 02 02 03 02 02 84 01 02"); protected static TlvDataObjectContainer TLV_COMMAND_DATA_IMPL_KEY_SELECTION = new TlvDataObjectContainer(COMMAND_DATA_IMPL_KEY_SELECTION); protected static TlvDataObjectContainer TLV_COMMAND_DATA_EXPL_KEY_SELECTION = new TlvDataObjectContainer(COMMAND_DATA_EXPL_KEY_SELECTION); private DefaultCaProtocol caProtocol; @Mocked CardStateAccessor mockedCardStateAccessor; @Mocked SecStatus mockedSecurityStatus; @Mocked SecureRandom secRandom; @Mocked MasterFile mockedMf; PasswordAuthObject passwordAuthObject; ConstructedTlvDataObject cvcaIsTlv; TlvDataObject cvcaIsCarTlv; byte[] ecdhPublicKeyDataPicc, ecdhPrivateKeyDataPicc, ecdhPublicKeyDataPcd; byte[] ecdhSharedSecretK, ecdhRPiccNonce, ecdhTPiccToken, ecdhKeySpecMac, ecdhKeySpecEnc; DomainParameterSetEcdh domainParametersEcdh; ECPublicKey ecdhPublicKeyPicc, ecdhPublicKeyPcd; ECPrivateKey ecdhPrivateKeyPicc; KeyPair ecdhKeyPairPicc; Collection<CardObject> ecdhKeys, emptyKeySet; KeyPairObject ecdhKeyObject; @Mocked TerminalAuthenticationMechanism taMechanism; Collection<TerminalAuthenticationMechanism> taMechanismCollection; byte[] ecdhPublicKeyPcdCompressed; /** * Create the test environment. * * @throws ReflectiveOperationException */ @Before public void setUp() throws ReflectiveOperationException { // create and init the object under test caProtocol = new DefaultCaProtocol(); caProtocol.setCardStateAccessor(mockedCardStateAccessor); caProtocol.init(); // --> ECDH <-- domainParametersEcdh = (DomainParameterSetEcdh) StandardizedDomainParameters.getDomainParameterSetById(13); ecdhPublicKeyDataPicc = HexString.toByteArray("04 A4 4E BE 54 51 DF 7A AD B0 1E 45 9B 8C 92 8A 87 74 6A 57 92 7C 8C 28 A6 77 5C 97 A7 E1 FE 8D 9A 46 FF 4A 1C C7 E4 D1 38 9A EA 19 75 8E 4F 75 C2 8C 59 8F D7 34 AE BE B1 35 33 7C F9 5B E1 2E 94"); ecdhPrivateKeyDataPicc = HexString.toByteArray("79 84 67 4C F3 B3 A5 24 BF 92 9C E8 A6 7F CF 22 17 3D A0 BA D5 95 EE D6 DE B7 2D 22 C5 42 FA 9D"); ecdhPublicKeyDataPcd = HexString.toByteArray("04 A9 80 49 3A 83 03 16 4B 04 6C BB 00 21 2D 52 EC 84 30 1C E8 93 3D 02 88 75 E7 63 73 0B 66 80 1A 1B 65 DB F0 15 D0 8F A4 DC 70 F7 9D 8A 92 CE 5A 4D 6A 2F 0C 30 71 3B D5 B8 44 E2 D0 5C C1 54 7D"); ecdhPublicKeyPcdCompressed = HexString.toByteArray("A980493A8303164B046CBB00212D52EC84301CE8933D028875E763730B66801A"); ecdhSharedSecretK = HexString.toByteArray("82 23 73 DD 9F 0E F5 82 1D E2 C3 96 99 6C 79 39 F6 7F 09 97 E4 0D 62 77 BE 2D 37 38 5E 6A 4B 73 CC BA 54 8D B5 92 FF CB"); ecdhRPiccNonce = HexString.toByteArray("CC BA 54 8D B5 92 FF CB"); ecdhTPiccToken = HexString.toByteArray("84 65 BE 39 7E 18 BF BB"); ecdhKeySpecMac = HexString.toByteArray("14 F5 EC 36 8F 19 9D 03 EA 69 10 CC 7E FF AA 64"); ecdhKeySpecEnc = HexString.toByteArray("E0 1F 0C 20 87 DD DD F7 8C F0 69 40 3B 97 B1 A7"); ecdhPublicKeyPicc = domainParametersEcdh.reconstructPublicKey(ecdhPublicKeyDataPicc); ecdhPrivateKeyPicc = domainParametersEcdh.reconstructPrivateKey(ecdhPrivateKeyDataPicc); ecdhKeyPairPicc = new KeyPair(ecdhPublicKeyPicc, ecdhPrivateKeyPicc); ecdhPublicKeyPcd = domainParametersEcdh.reconstructPublicKey(ecdhPublicKeyDataPcd); ecdhKeys = new ArrayList<CardObject>(); KeyIdentifier KeyIdentifier = new KeyIdentifier(2); OidIdentifier oidIdentifier = new OidIdentifier(Ca.OID_id_CA_ECDH_AES_CBC_CMAC_128); ecdhKeyObject = new KeyPairObject(ecdhKeyPairPicc, KeyIdentifier); ecdhKeyObject.addOidIdentifier(oidIdentifier); ecdhKeys.add(ecdhKeyObject); emptyKeySet = new ArrayList<CardObject>(); taMechanismCollection = new ArrayList<TerminalAuthenticationMechanism>(); taMechanismCollection.add(taMechanism); } /** * Positive test case: extract key identifier from command data, implicit domain parameter set reference (Tag 84 is missing). */ @Test public void testExtractKeyIdentifierFromCommandData_ImplicitDomainParameterReference(){ CaProtocol caProtocol = new CaProtocol(); KeyIdentifier keyIdentifierReceived = caProtocol.extractKeyIdentifierFromCommandData(TLV_COMMAND_DATA_IMPL_KEY_SELECTION); KeyIdentifier keyIdentifierExpected = new KeyIdentifier(); assertEquals(keyIdentifierExpected.getInteger(), keyIdentifierReceived.getInteger()); } /** * Positive test case: extract key identifier from command data, explicit domain parameter set reference (Tag 84 is present). */ @Test public void testExtractKeyIdentifierFromCommandData_ExplicitDomainParameterReference(){ CaProtocol caProtocol = new CaProtocol(); KeyIdentifier keyIdentifierReceived = caProtocol.extractKeyIdentifierFromCommandData(TLV_COMMAND_DATA_EXPL_KEY_SELECTION); KeyIdentifier keyIdentifierExpected = new KeyIdentifier(2); assertEquals(keyIdentifierExpected.getInteger(), keyIdentifierReceived.getInteger()); } /** * Positive test case: extract CA OID from command data. */ @Test public void testExtractCaOidFromCommandData(){ CaProtocol caProtocol = new CaProtocol(); CaOid caOidReceived = caProtocol.extractCaOidFromCommandData(TLV_COMMAND_DATA_IMPL_KEY_SELECTION); byte[] caOidData = HexString.toByteArray("04 00 7F 00 07 02 02 03 02 02"); CaOid caOidExpected = new CaOid(caOidData); assertEquals(caOidExpected, caOidReceived); } /** * Negative test case: extract CA OID from command data containing an invalid CA OID. */ @Test(expected = ProcessingException.class) public void testExtractCaOidFromCommandData_illegalCaOid(){ CaProtocol caProtocol = new CaProtocol(); byte[] commandDataBytes = HexString.toByteArray("80 0A 04 00 7F 00 07 02 02 03 FF FF"); TlvDataObjectContainer commandData = new TlvDataObjectContainer(commandDataBytes); caProtocol.extractCaOidFromCommandData(commandData); } /** * Positive test case: perform Set AT command with data from valid CA test run, explicit key reference (Tag 84 is present). */ @Test public void testSetAt_ExplicitKeyReference(){ // prepare the mock new NonStrictExpectations() { { mockedCardStateAccessor.getMasterFile(); result = mockedMf; mockedMf.findChildren( withInstanceOf(KeyIdentifier.class)); result = ecdhKeys; } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("00 22 41 A4 0F 80 0A 04 00 7F 00 07 02 02 03 02 02 84 01 02"); processingData.updateCommandApdu(this, "setAT APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut caProtocol.process(processingData); // check results assertEquals("Statusword is not 9000", Iso7816.SW_9000_NO_ERROR, processingData.getResponseApdu().getStatusWord()); } /** * Negative test case: perform Set AT command with data from valid CA test run, explicit key reference (Tag 84 is present) but key is not found. */ @Test public void testSetAt_ExplicitKeyReferenceKeyNotFound(){ // prepare the mock new NonStrictExpectations() { { mockedCardStateAccessor.getMasterFile(); result = mockedMf; mockedMf.findChildren( withInstanceOf(OidIdentifier.class), withInstanceOf(KeyIdentifier.class)); result = ecdhKeys; // mockedCardStateAccessor.getObject( // withInstanceOf(KeyIdentifier.class), // withInstanceOf(Scope.class)); // result = new NullCardObject(); } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("00 22 41 A4 0F 80 0A 04 00 7F 00 07 02 02 03 02 02 84 01 02"); processingData.updateCommandApdu(this, "setAT APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut caProtocol.process(processingData); // check results assertEquals("Statusword is not 4A88", PlatformUtil.SW_4A88_REFERENCE_DATA_NOT_FOUND, processingData.getResponseApdu().getStatusWord()); } /** * Positive test case: perform Set AT command with data from valid CA test run, implicit key reference (Tag 84 is missing). */ @Test public void testSetAt_ImplicitKeyReference(){ // prepare the mock new NonStrictExpectations() { { mockedCardStateAccessor.getMasterFile(); result = mockedMf; mockedMf.findChildren( withInstanceOf(KeyIdentifier.class)); result = ecdhKeys; // mockedCardStateAccessor.getObject( // withInstanceOf(KeyIdentifier.class), // withInstanceOf(Scope.class)); // result = ecdhKeyObject; } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("00 22 41 A4 0C 80 0A 04 00 7F 00 07 02 02 03 02 02"); processingData.updateCommandApdu(this, "setAT APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut caProtocol.process(processingData); // check results assertEquals("Statusword is not 9000", Iso7816.SW_9000_NO_ERROR, processingData.getResponseApdu().getStatusWord()); } /** * Negative test case: perform Set AT command with data from valid CA test run, implicit key reference (Tag 84 is missing) fails (no key found). */ @Test public void testSetAt_ImplicitKeyReferenceFail(){ // prepare the mock new Expectations() { { mockedCardStateAccessor.getMasterFile(); result = mockedMf; } }; new Expectations() { { mockedMf.findChildren( withInstanceOf(KeyIdentifier.class)); result = emptyKeySet; } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("00 22 41 A4 0C 80 0A 04 00 7F 00 07 02 02 03 02 02"); processingData.updateCommandApdu(this, "setAT APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut caProtocol.process(processingData); // check results assertEquals("Statusword is not 4A88", PlatformUtil.SW_4A88_REFERENCE_DATA_NOT_FOUND, processingData.getResponseApdu().getStatusWord()); } /** * Positive test case: perform General Authenticate command for EC keys with data from valid CA test run. */ @SuppressWarnings("unchecked") //jmockit @Test public void testGeneralAuthenticateEcdh(){ new NonStrictExpectations(CryptoUtil.class) { { secRandom.nextBytes( withInstanceOf(byte[].class)); // eliminate true randomness by providing fixed "random" values result = new Delegate<Object>() { @SuppressWarnings("unused") // JMockit void nextBytes(byte[] bytes) { System.arraycopy(ecdhRPiccNonce, 0, bytes, 0, ecdhRPiccNonce.length); return; } }; } }; new NonStrictExpectations() { { mockedCardStateAccessor.getCurrentMechanisms( SecContext.APPLICATION, withInstanceOf(Collection.class)); result = taMechanismCollection; } }; new NonStrictExpectations() { { taMechanism.getCompressedTerminalEphemeralPublicKey(); result = ecdhPublicKeyPcdCompressed; } }; caProtocol.caOid = Ca.OID_id_CA_ECDH_AES_CBC_CMAC_128; Deencapsulation.setField(caProtocol, "staticKeyPairPicc", ecdhKeyPairPicc); caProtocol.caDomainParameters = Tr03110Utils.getDomainParameterSetFromKey(caProtocol.staticKeyPairPicc.getPublic()); caProtocol.cryptoSupport = caProtocol.caOid.getCryptoSupport(); ProcessingData processingData = new ProcessingData(); byte[] apduFront = HexString.toByteArray("00 86 00 00 45 7C 43 80 41"); byte[] apduBack = HexString.toByteArray("00"); byte[] apduBytes = Utils.concatByteArrays(apduFront, ecdhPublicKeyDataPcd, apduBack); CommandApdu cApdu = CommandApduFactory.createCommandApdu(apduBytes); processingData.updateCommandApdu(this, "General Authenticate APDU", cApdu); Deencapsulation.setField(caProtocol, processingData); // call mut caProtocol.processCommandGeneralAuthenticate(); SecretKeySpec secretKeySpecMAC = Deencapsulation.getField(caProtocol, "secretKeySpecMAC"); SecretKeySpec secretKeySpecENC = Deencapsulation.getField(caProtocol, "secretKeySpecENC"); byte[] secretKeySpecMacKeyMaterial = secretKeySpecMAC.getEncoded(); byte[] secretKeySpecEncKeyMaterial = secretKeySpecENC.getEncoded(); System.out.println("key spec mac: " + HexString.encode(secretKeySpecMacKeyMaterial)); System.out.println("key spec enc: " + HexString.encode(secretKeySpecEncKeyMaterial)); // check results assertEquals("Statusword is not 9000", Iso7816.SW_9000_NO_ERROR, processingData.getResponseApdu().getStatusWord()); assertArrayEquals("key spec ENC mismatch", ecdhKeySpecEnc, secretKeySpecEncKeyMaterial); assertArrayEquals("key spec MAC mismatch", ecdhKeySpecMac, secretKeySpecMacKeyMaterial); } /** * Positive test case: construct a ChipAuthenticationInfo object */ @Test public void testConstructChipAuthenticationInfoObject(){ byte[] oidBytes = HexString.toByteArray("010203040506070809"); byte version = (byte) 0x01; byte keyId = (byte) 0x42; ConstructedTlvDataObject caioReceivedTlv = AbstractCaProtocol.constructChipAuthenticationInfoObject(oidBytes, version, keyId); byte[] caioReceived = caioReceivedTlv.toByteArray(); byte[] caioExpected = HexString.toByteArray("30110609010203040506070809020101020142"); assertArrayEquals(caioExpected, caioReceived); } /** * Positive test case: perform Set AT command with attached Tag for storing session context */ @Test public void testSetAt_ExplicitStoreSession(){ // prepare the mock new NonStrictExpectations() { { mockedCardStateAccessor.getMasterFile(); result = mockedMf; mockedMf.findChildren( withInstanceOf(KeyIdentifier.class)); result = ecdhKeys; } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("00 22 41 A4 14 80 0A 04 00 7F 00 07 02 02 03 02 02 84 01 02 E0 03 81 01 01"); processingData.updateCommandApdu(this, "setAT APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut caProtocol.process(processingData); assertEquals(caProtocol.sessionContextIdentifier, 1); // check results assertEquals("Statusword is not 9000", Iso7816.SW_9000_NO_ERROR, processingData.getResponseApdu().getStatusWord()); } /** * Positive test case: perform Set AT command without attached Tag for storing session context */ @Test public void testSetAt_WihtoutStore(){ // prepare the mock new NonStrictExpectations() { { mockedCardStateAccessor.getMasterFile(); result = mockedMf; mockedMf.findChildren( withInstanceOf(KeyIdentifier.class)); result = ecdhKeys; } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("00 22 41 A4 0F 80 0A 04 00 7F 00 07 02 02 03 02 02 84 01 02"); processingData.updateCommandApdu(this, "setAT APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut caProtocol.process(processingData); assertEquals(caProtocol.sessionContextIdentifier, -1); // check results assertEquals("Statusword is not 9000", Iso7816.SW_9000_NO_ERROR, processingData.getResponseApdu().getStatusWord()); } /** * Positive test case: perform 2 Set AT commands. The first with attached Tag for storing session context and the second without. */ @Test public void testSetAt_ExplicitStore_WithoutStore(){ // prepare the mock new NonStrictExpectations() { { mockedCardStateAccessor.getMasterFile(); result = mockedMf; mockedMf.findChildren( withInstanceOf(KeyIdentifier.class)); result = ecdhKeys; } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytesStore = HexString.toByteArray("00 22 41 A4 14 80 0A 04 00 7F 00 07 02 02 03 02 02 84 01 02 E0 03 81 01 01"); InterindustryCommandApduImpl cApduStore = (InterindustryCommandApduImpl) CommandApduFactory.createCommandApdu(apduBytesStore); processingData.updateCommandApdu(this, "setAT APDU", cApduStore); caProtocol.process(processingData); assertEquals(caProtocol.sessionContextIdentifier, 1); caProtocol = new DefaultCaProtocol(); caProtocol.setCardStateAccessor(mockedCardStateAccessor); caProtocol.init(); byte[] apduBytesNoStore = HexString.toByteArray("00 22 41 A4 0F 80 0A 04 00 7F 00 07 02 02 03 02 02 84 01 02"); InterindustryCommandApduImpl cApduNoStore = (InterindustryCommandApduImpl) CommandApduFactory.createCommandApdu(apduBytesNoStore, cApduStore); processingData.updateCommandApdu(this, "setAT APDU", cApduNoStore); // call mut caProtocol.process(processingData); assertEquals(-1, caProtocol.sessionContextIdentifier); // check results assertEquals("Statusword is not 9000", Iso7816.SW_9000_NO_ERROR, processingData.getResponseApdu().getStatusWord()); } }