package de.persosim.simulator.protocols.ri; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.security.Key; import java.security.KeyPair; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.util.Collection; import java.util.HashSet; import javax.crypto.KeyAgreement; import mockit.Deencapsulation; import mockit.Delegate; import mockit.Expectations; import mockit.Mocked; import mockit.NonStrictExpectations; import org.junit.Before; import org.junit.Test; import de.persosim.simulator.apdu.CommandApduFactory; import de.persosim.simulator.cardobjects.KeyPairObject; import de.persosim.simulator.platform.CardStateAccessor; import de.persosim.simulator.platform.Iso7816; import de.persosim.simulator.processing.ProcessingData; import de.persosim.simulator.protocols.ta.TerminalAuthenticationMechanism; import de.persosim.simulator.protocols.ta.TerminalType; import de.persosim.simulator.secstatus.EffectiveAuthorizationMechanism; import de.persosim.simulator.secstatus.SecMechanism; import de.persosim.simulator.secstatus.SecStatus.SecContext; import de.persosim.simulator.test.PersoSimTestCase; import de.persosim.simulator.tlv.ConstructedTlvDataObject; import de.persosim.simulator.tlv.PrimitiveTlvDataObject; import de.persosim.simulator.tlv.TlvConstants; import de.persosim.simulator.utils.HexString; import de.persosim.simulator.utils.Utils; public class RiProtocolTest extends PersoSimTestCase { @Mocked KeyAgreement keyAgreement; @Mocked MessageDigest messageDigest; @Mocked PublicKey publicKey; @Mocked PrivateKey privateKey; @Mocked CardStateAccessor cardStateAccessor; @Mocked KeyPair keypair; @Mocked KeyPairObject keyObject; @Mocked TerminalAuthenticationMechanism taMechanism; @Mocked EffectiveAuthorizationMechanism authMechanism; @Mocked RiOid oid; RiProtocol protocol; @Before public void setUp(){ protocol = new RiProtocol(); protocol.setCardStateAccessor(cardStateAccessor); keyObject = new KeyPairObject(); } /** * Positive test using a mocked key agreement with fixed output and a mocked * hash algorithm, that adds 1 to each byte in the given array. * * @throws Exception */ @Test public void testCalculateSectorIdentifier() throws Exception{ //prepare the mock final byte [] keyAgreementResult = new byte [] {1,2,3,4}; new Expectations() {{ keyAgreement.init((Key) any); keyAgreement.doPhase((Key) any, true); keyAgreement.generateSecret(); result = keyAgreementResult; messageDigest.digest((byte[]) any); result = new Delegate<Object>() { @SuppressWarnings("unused") byte [] digest(byte [] data){ byte [] result = new byte [data.length]; for (int i = 0; i < data.length; i++){ result[i] = (byte) ((data[i] + 1) % 256); } return result; } }; } }; byte [] expectedResult = new byte [] {2,3,4,5}; byte [] result = Deencapsulation.invoke(protocol, "calculateSectorIdentifier", privateKey, publicKey, keyAgreement, messageDigest); assertArrayEquals(expectedResult, result); } /** * Positive test using a mocked hash algorithm (identity). */ @Test public void testCheckSectorPublicKeyHash(){ PrimitiveTlvDataObject data1 = new PrimitiveTlvDataObject(TlvConstants.TAG_06, new byte [] {1,2,3,4}); PrimitiveTlvDataObject data2 = new PrimitiveTlvDataObject(TlvConstants.TAG_80, new byte [] {5,6,7,8}); ConstructedTlvDataObject sectorPublicKey = new ConstructedTlvDataObject(RiOid.RI_FIRST_SECTOR_KEY_TAG); sectorPublicKey.addTlvDataObject(data1); sectorPublicKey.addTlvDataObject(data2); ConstructedTlvDataObject expectedResult = new ConstructedTlvDataObject(TlvConstants.TAG_7F49); expectedResult.addTlvDataObject(data1); expectedResult.addTlvDataObject(data2); new Expectations() { { messageDigest.digest((byte[]) any); result = new Delegate<Object>() { @SuppressWarnings("unused") byte [] digest(byte [] data){ return data; } }; } }; assertTrue((boolean)Deencapsulation.invoke(protocol, "checkSectorPublicKeyHash", sectorPublicKey, messageDigest, expectedResult.toByteArray())); } @SuppressWarnings("unchecked") // jMockit @Test public void testGeneralAuthenticate() throws Exception{ // prepare the mock final HashSet<SecMechanism> mechanisms = new HashSet<>(); mechanisms.add(taMechanism); mechanisms.add(authMechanism); new Expectations() { { MessageDigest.getInstance((String) any, (Provider) any); result = messageDigest; } }; final byte [] keyAgreementResult = new byte [] {1,2,3,4}; new NonStrictExpectations() { { taMechanism.getTerminalType(); result = TerminalType.AT; cardStateAccessor.getCurrentMechanisms((SecContext)any, (Collection<Class<? extends SecMechanism>>) any); result = mechanisms; keyObject.getKeyPair(); result=keypair; keypair.getPrivate(); result = privateKey; messageDigest.digest((byte[]) any); result = new Delegate<Object>() { @SuppressWarnings("unused") byte [] digest(byte [] data){ return data; } }; keyAgreement.init((Key) any); keyAgreement.doPhase((Key) any, true); keyAgreement.generateSecret(); result = keyAgreementResult; taMechanism.getFirstSectorPublicKeyHash(); result = HexString.toByteArray("7F4982011D060A04007F000702020502038120A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E537782207D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9832026DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B68441048BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F0469978520A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A78641045D3B49C8EE250295F7C0EF6A1AE810C4B9E1F5F80D316DC9ADAD16080C1784CF881EE0A375BCA1B53C98F3AC39FD0CA90CE31D2D8276D3CFB32B316BA0221023870101"); oid.getKeyAgreement(); result = keyAgreement; oid.getHash(); result = messageDigest; } }; Deencapsulation.setField(protocol, keyObject); Deencapsulation.setField(keyObject, keypair); ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("00 86 00 00 00 01 25 7C 82 01 21 A0 82 01 1D 06 0A 04 00 7F 00 07 02 02 05 02 03 81 20 A9 FB 57 DB A1 EE A9 BC 3E 66 0A 90 9D 83 8D 72 6E 3B F6 23 D5 26 20 28 20 13 48 1D 1F 6E 53 77 82 20 7D 5A 09 75 FC 2C 30 57 EE F6 75 30 41 7A FF E7 FB 80 55 C1 26 DC 5C 6C E9 4A 4B 44 F3 30 B5 D9 83 20 26 DC 5C 6C E9 4A 4B 44 F3 30 B5 D9 BB D7 7C BF 95 84 16 29 5C F7 E1 CE 6B CC DC 18 FF 8C 07 B6 84 41 04 8B D2 AE B9 CB 7E 57 CB 2C 4B 48 2F FC 81 B7 AF B9 DE 27 E1 E3 BD 23 C2 3A 44 53 BD 9A CE 32 62 54 7E F8 35 C3 DA C4 FD 97 F8 46 1A 14 61 1D C9 C2 77 45 13 2D ED 8E 54 5C 1D 54 C7 2F 04 69 97 85 20 A9 FB 57 DB A1 EE A9 BC 3E 66 0A 90 9D 83 8D 71 8C 39 7A A3 B5 61 A6 F7 90 1E 0E 82 97 48 56 A7 86 41 04 5D 3B 49 C8 EE 25 02 95 F7 C0 EF 6A 1A E8 10 C4 B9 E1 F5 F8 0D 31 6D C9 AD AD 16 08 0C 17 84 CF 88 1E E0 A3 75 BC A1 B5 3C 98 F3 AC 39 FD 0C A9 0C E3 1D 2D 82 76 D3 CF B3 2B 31 6B A0 22 10 23 87 01 01 00 00"); processingData.updateCommandApdu(this, "general authenticate APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut protocol.process(processingData); // check results assertEquals("Statusword is not 9000", Iso7816.SW_9000_NO_ERROR, processingData.getResponseApdu().getStatusWord()); assertArrayEquals(HexString.toByteArray("7C06810401020304"), processingData.getResponseApdu().getData().toByteArray()); } /** * Negative test: process General Authenticate command when preceding TA was performed for non-AT terminal type. */ @Test public void testGeneralAuthenticate_NonAtTerminalType() throws Exception{ // prepare the mock final HashSet<SecMechanism> mechanisms = new HashSet<>(); mechanisms.add(taMechanism); final HashSet<Class<? extends SecMechanism>> requestedMechanisms = new HashSet<>(); requestedMechanisms.add(TerminalAuthenticationMechanism.class); new NonStrictExpectations() { { taMechanism.getTerminalType(); result = TerminalType.IS; cardStateAccessor.getCurrentMechanisms((SecContext)any, requestedMechanisms); result = mechanisms; } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("00 86 00 00 00 01 25 7C 82 01 21 A0 82 01 1D 06 0A 04 00 7F 00 07 02 02 05 02 03 81 20 A9 FB 57 DB A1 EE A9 BC 3E 66 0A 90 9D 83 8D 72 6E 3B F6 23 D5 26 20 28 20 13 48 1D 1F 6E 53 77 82 20 7D 5A 09 75 FC 2C 30 57 EE F6 75 30 41 7A FF E7 FB 80 55 C1 26 DC 5C 6C E9 4A 4B 44 F3 30 B5 D9 83 20 26 DC 5C 6C E9 4A 4B 44 F3 30 B5 D9 BB D7 7C BF 95 84 16 29 5C F7 E1 CE 6B CC DC 18 FF 8C 07 B6 84 41 04 8B D2 AE B9 CB 7E 57 CB 2C 4B 48 2F FC 81 B7 AF B9 DE 27 E1 E3 BD 23 C2 3A 44 53 BD 9A CE 32 62 54 7E F8 35 C3 DA C4 FD 97 F8 46 1A 14 61 1D C9 C2 77 45 13 2D ED 8E 54 5C 1D 54 C7 2F 04 69 97 85 20 A9 FB 57 DB A1 EE A9 BC 3E 66 0A 90 9D 83 8D 71 8C 39 7A A3 B5 61 A6 F7 90 1E 0E 82 97 48 56 A7 86 41 04 5D 3B 49 C8 EE 25 02 95 F7 C0 EF 6A 1A E8 10 C4 B9 E1 F5 F8 0D 31 6D C9 AD AD 16 08 0C 17 84 CF 88 1E E0 A3 75 BC A1 B5 3C 98 F3 AC 39 FD 0C A9 0C E3 1D 2D 82 76 D3 CF B3 2B 31 6B A0 22 10 23 87 01 01 00 00"); processingData.updateCommandApdu(this, "general authenticate APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut protocol.process(processingData); // check results assertArrayEquals("Statusword is not 6985", Utils.toUnsignedByteArray(Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED), processingData.getResponseApdu().toByteArray()); } /** * Negative test: process Manage Security Environment for command APDU missing tag 80 (cryptographic mechanism reference data). */ @Test public void testManageSecurityEnvironment_MissingCryptoMechRefData() throws Exception{ // prepare the mock final HashSet<SecMechanism> mechanisms = new HashSet<>(); mechanisms.add(taMechanism); final HashSet<Class<? extends SecMechanism>> requestedMechanisms = new HashSet<>(); requestedMechanisms.add(TerminalAuthenticationMechanism.class); new NonStrictExpectations() { { taMechanism.getTerminalType(); result = TerminalType.AT; cardStateAccessor.getCurrentMechanisms((SecContext)any, requestedMechanisms); result = mechanisms; } }; ProcessingData processingData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray("002241A403840129"); processingData.updateCommandApdu(this, "general authenticate APDU", CommandApduFactory.createCommandApdu(apduBytes)); // call mut protocol.process(processingData); // check results assertArrayEquals("Statusword is not 6A88", Utils.toUnsignedByteArray(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND), processingData.getResponseApdu().toByteArray()); } }