package de.persosim.simulator.securemessaging; import static mockit.Deencapsulation.setField; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import java.security.GeneralSecurityException; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import mockit.Mocked; import mockit.NonStrictExpectations; import org.globaltester.cryptoprovider.Crypto; import org.junit.Test; import de.persosim.simulator.apdu.CommandApdu; import de.persosim.simulator.apdu.CommandApduFactory; import de.persosim.simulator.apdu.ResponseApdu; import de.persosim.simulator.crypto.CryptoUtil; import de.persosim.simulator.processing.ProcessingData; import de.persosim.simulator.test.PersoSimTestCase; import de.persosim.simulator.tlv.TlvDataObjectContainer; import de.persosim.simulator.tlv.TlvTag; import de.persosim.simulator.tlv.TlvValuePlain; import de.persosim.simulator.utils.HexString; public class SecureMessagingTest extends PersoSimTestCase { // test data from ICAO Doc 9303 Part 3 Vol 2, Worked Example Appendix 6 to section IV private static final String ICAO_SK_ENC = "979EC13B1CBFE9DCD01AB0FED307EAE5"; private static final String ICAO_SK_MAC = "F1CB1F1FB5ADF208806B89DC579DC1F8"; // private static final String ICAO_SSC_INITIAL = "887022120C06C226"; private static final String ICAO_SSC_PLUS1 = "887022120C06C227"; // private static final String ICAO_SSC_PLUS2 = "887022120C06C228"; private static final String ICAO_PLAIN_APDU = "00A4020C02011E"; private static final String ICAO_SM_APDU = "0CA4020C158709016375432908C044F68E08BF8B92D635FF24F800"; // private static final String ICAO_PLAIN_RESPONSE = "9000"; // private static final String ICAO_SM_RESPONSE = "990290008E08FA855A5D4C50A8ED9000"; // testdata from correct GlobalTester run private static final String AES256_SK_ENC = "4DD037AB00B6B0D7FC80DA1D567AEF8098F8D8AC417E212660CCD6BDD7002067"; private static final String AES256_IV_ENC = "73E69C3F671B42F6A9C07F4A5CD821DC"; private static final String AES256_MAC = "aescmac"; private static final String AES256_SK_MAC = "8BC9DD9D33C62926101D212B04C0C79A5BF7032F4DDCA237552D665DCB560B68"; private static final String AES256_SK_MAC_AUX_DATA = "00000000000000000000000000000003"; private static final String AES256_CASE2_SM_APDU = "0C8200000D9701FF8E083D0A111D900F25A500"; private static final String AES256_CASE2_PLAIN_APDU = "00820000FF"; @Mocked SmDataProvider dataProviderMock; @Test public void processAscending_plainApduUntouched() { SecureMessaging secureMessaging = new SecureMessaging(); // provide plain APDU ProcessingData pData = new ProcessingData(); byte[] apduBytes = new byte[] { 0x00, (byte) 0x84, 0x00, 0x00, 0x08 }; pData.updateCommandApdu(this, "test command APDU", CommandApduFactory.createCommandApdu( apduBytes)); // call mut secureMessaging.processAscending(pData); // extract/check CommandApdu CommandApdu commandApdu = pData.getCommandApdu(); assertNotNull("commandApdu is empty", commandApdu); assertArrayEquals("plain commandApdu was modified", apduBytes, commandApdu.toByteArray()); } /** * SM after BAC, example form ICAO doc 9303 * @throws GeneralSecurityException */ @Test public void processAscending_isoCase3() throws GeneralSecurityException { //prepare configuration SecureMessaging secureMessaging = new SecureMessaging(); //provide the mocked SmDataProvider setField(secureMessaging, "dataProvider", dataProviderMock); new NonStrictExpectations() {{ dataProviderMock.getCipherIv(); result = new IvParameterSpec(new byte[8]); dataProviderMock.getCipher(); result = Cipher.getInstance("DESede/CBC/NoPadding", Crypto.getCryptoProvider()); dataProviderMock.getKeyEnc(); result = new SecretKeySpec(HexString.toByteArray(ICAO_SK_ENC), "DESede"); dataProviderMock.getMac(); result = Mac.getInstance("ISO9797ALG3", Crypto.getCryptoProvider()); dataProviderMock.getKeyMac(); result = new SecretKeySpec(HexString.toByteArray(ICAO_SK_MAC), "DESede"); dataProviderMock.getMacAuxiliaryData(); result = HexString.toByteArray(ICAO_SSC_PLUS1); dataProviderMock.getMacLength(); result = 8; }}; // provide sample APDU ProcessingData pData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray(ICAO_SM_APDU); pData.updateCommandApdu(this, "test command APDU", CommandApduFactory.createCommandApdu( apduBytes)); // call mut secureMessaging.processAscending(pData); // extract/check CommandApdu CommandApdu commandApdu = pData.getCommandApdu(); assertNotNull("commandApdu is empty", commandApdu); assertArrayEquals("unwrapped APDU incorrect", HexString.toByteArray(ICAO_PLAIN_APDU), commandApdu.toByteArray()); } /** * SM after PACE, example from previous GlobalTester run * @throws Exception */ @Test public void processAscending_isoCase2_AES265() throws Exception { //prepare configuration SecureMessaging secureMessaging = new SecureMessaging(); //provide the mocked SmDataProvider setField(secureMessaging, "dataProvider", dataProviderMock); new NonStrictExpectations() {{ dataProviderMock.getCipherIv(); result = new IvParameterSpec(HexString.toByteArray(AES256_IV_ENC)); dataProviderMock.getCipher(); result = Cipher.getInstance("AES/CBC/NoPadding", Crypto.getCryptoProvider()); dataProviderMock.getKeyEnc(); result = new SecretKeySpec(HexString.toByteArray(AES256_SK_ENC), "AES"); dataProviderMock.getMac(); result = Mac.getInstance(AES256_MAC); dataProviderMock.getKeyMac(); result = new SecretKeySpec(HexString.toByteArray(AES256_SK_MAC), "AES"); dataProviderMock.getMacAuxiliaryData(); result = HexString.toByteArray(AES256_SK_MAC_AUX_DATA); dataProviderMock.getMacLength(); result = 8; }}; // provide sample APDU ProcessingData pData = new ProcessingData(); byte[] apduBytes = HexString.toByteArray(AES256_CASE2_SM_APDU); pData.updateCommandApdu(this, "test command APDU", CommandApduFactory.createCommandApdu( apduBytes)); // call mut secureMessaging.processAscending(pData); // extract/check CommandApdu CommandApdu commandApdu = pData.getCommandApdu(); assertNotNull("commandApdu is empty", commandApdu); assertArrayEquals("unwrapped APDU incorrect", HexString.toByteArray(AES256_CASE2_PLAIN_APDU), commandApdu.toByteArray()); } /** * Check the correct update of SM data provider otherwise similar to * {@link #processAscending_isoCase3()} * * @throws GeneralSecurityException */ @Test public void processDescending_handleUpdatePropagation() throws GeneralSecurityException { //prepare dataProvider SmDataProviderContainerProxy dataProvider = new SmDataProviderContainerProxy(); dataProvider.setKeyEnc(new SecretKeySpec(HexString.toByteArray(ICAO_SK_ENC), "DESede")); dataProvider.setKeySpecMAC(new SecretKeySpec(HexString.toByteArray(ICAO_SK_MAC), "DESede")); dataProvider.setEncIv(new IvParameterSpec(new byte[8])); dataProvider.setCipher(Cipher.getInstance("DESede/CBC/NoPadding", Crypto.getCryptoProvider())); dataProvider.setKeyEnc(new SecretKeySpec(HexString.toByteArray(ICAO_SK_ENC), "DESede")); dataProvider.setMac(Mac.getInstance("ISO9797ALG3", Crypto.getCryptoProvider())); dataProvider.setMacAuxiliaryData(HexString.toByteArray(ICAO_SSC_PLUS1)); dataProvider.setMacLength(8); // mut, propagate SmDataProvider SecureMessaging secureMessaging = new SecureMessaging(); ProcessingData pData = new ProcessingData(); pData.updateResponseAPDU(this, "test response APDU", new ResponseApdu(SW_9000_NO_ERROR)); pData.addUpdatePropagation(this, "testing handling of SmDataProvider", dataProvider); secureMessaging.processDescending(pData); // check correct decoding of sample APDU ProcessingData pData2 = new ProcessingData(); byte[] apduBytes = HexString.toByteArray(ICAO_SM_APDU); pData2.updateCommandApdu(this, "test command APDU", CommandApduFactory.createCommandApdu( apduBytes)); secureMessaging.processAscending(pData2); CommandApdu commandApdu = pData2.getCommandApdu(); assertNotNull("commandApdu is empty", commandApdu); assertArrayEquals("unwrapped APDU incorrect", HexString.toByteArray(ICAO_PLAIN_APDU), commandApdu.toByteArray()); } /** * Encoding of SM Response APDU must not contain DO87 if data field of plain response is absent * @throws Exception */ @Test public void processOutgoingSmApdu_responseDataAbsent() throws Exception { //prepare configuration SecureMessaging secureMessaging = new SecureMessaging(); //provide the mocked SmDataProvider setField(secureMessaging, "dataProvider", dataProviderMock); new NonStrictExpectations() {{ dataProviderMock.getCipherIv(); result = new IvParameterSpec(HexString.toByteArray(AES256_IV_ENC)); dataProviderMock.getCipher(); result = Cipher.getInstance("AES/CBC/NoPadding", Crypto.getCryptoProvider()); dataProviderMock.getKeyEnc(); result = new SecretKeySpec(HexString.toByteArray(AES256_SK_ENC), "AES"); dataProviderMock.getMac(); result = Mac.getInstance(AES256_MAC); dataProviderMock.getKeyMac(); result = new SecretKeySpec(HexString.toByteArray(AES256_SK_MAC), "AES"); dataProviderMock.getMacAuxiliaryData(); result = HexString.toByteArray(AES256_SK_MAC_AUX_DATA); dataProviderMock.getMacLength(); result = 8; }}; // provide sample APDU ProcessingData pData = new ProcessingData(); pData.updateResponseAPDU(this, "test response w/o response data", new ResponseApdu(SW_9000_NO_ERROR)); setField(secureMessaging, "processingData", pData); // call mut secureMessaging.processOutgoingSmApdu(); // extract/check ResponseApdu ResponseApdu respApdu = pData.getResponseApdu(); assertNotNull("responseApdu is empty", respApdu); assertFalse("DO 87 present", ((TlvDataObjectContainer) respApdu.getData()).containsTlvDataObject(new TlvTag((byte)0x87))); } /** * Encoding of SM Response APDU must not contain DO87 if data field of plain response is present but empty * @throws Exception */ @Test public void processOutgoingSmApdu_responseDataLengthZero() throws Exception { //prepare configuration SecureMessaging secureMessaging = new SecureMessaging(); //provide the mocked SmDataProvider setField(secureMessaging, "dataProvider", dataProviderMock); new NonStrictExpectations() {{ dataProviderMock.getCipherIv(); result = new IvParameterSpec(HexString.toByteArray(AES256_IV_ENC)); dataProviderMock.getCipher(); result = Cipher.getInstance("AES/CBC/NoPadding", Crypto.getCryptoProvider()); dataProviderMock.getKeyEnc(); result = new SecretKeySpec(HexString.toByteArray(AES256_SK_ENC), "AES"); dataProviderMock.getMac(); result = Mac.getInstance(AES256_MAC); dataProviderMock.getKeyMac(); result = new SecretKeySpec(HexString.toByteArray(AES256_SK_MAC), "AES"); dataProviderMock.getMacAuxiliaryData(); result = HexString.toByteArray(AES256_SK_MAC_AUX_DATA); dataProviderMock.getMacLength(); result = 8; }}; // provide sample APDU ProcessingData pData = new ProcessingData(); pData.updateResponseAPDU(this, "test response w/o response data", new ResponseApdu(new TlvValuePlain(new byte[0]), SW_9000_NO_ERROR)); setField(secureMessaging, "processingData", pData); // call mut secureMessaging.processOutgoingSmApdu(); // extract/check ResponseApdu ResponseApdu respApdu = pData.getResponseApdu(); assertNotNull("responseApdu is empty", respApdu); assertFalse("DO 87 present", ((TlvDataObjectContainer) respApdu.getData()).containsTlvDataObject(new TlvTag((byte)0x87))); } /** * Positive test: input far below blocksize */ @Test public void testPaddData() { byte[] input = HexString.toByteArray("0011223344"); int blockSize = 8; byte[] exp = HexString.toByteArray("0011223344800000"); assertArrayEquals(exp, CryptoUtil.padData(input, blockSize)); } /** * Positive test: input larger than blocksize, but not near any cornercase */ @Test public void testPaddData_largInput() { byte[] input = HexString.toByteArray("001122334455667700112233"); int blockSize = 8; byte[] exp = HexString.toByteArray("00112233445566770011223380000000"); assertArrayEquals(exp, CryptoUtil.padData(input, blockSize)); } /** * Positive test: input exactly one byte shorter than blocksize */ @Test public void testPaddData_inputOneShorterThanBlocksize() { byte[] input = HexString.toByteArray("00112233445566"); int blockSize = 8; byte[] exp = HexString.toByteArray("0011223344556680"); assertArrayEquals(exp, CryptoUtil.padData(input, blockSize)); } /** * Positive test: input length matches blocksize */ @Test public void testPaddData_Input_Matches_Blocksize() { byte[] input = HexString.toByteArray("0011223344556677"); int blockSize = 8; byte[] exp = HexString.toByteArray("00112233445566778000000000000000"); assertArrayEquals(exp, CryptoUtil.padData(input, blockSize)); } /** * Negative test: unpadData gets a bytearray, which is null; */ @Test(expected=NullPointerException.class) public void testUnpadDate_Input_Array_Is_Null() { byte[] bytearray = null; SecureMessaging.unpadData(bytearray, 4); } /** * Negative test: unpadData gets a blockSize which is less 1. */ @Test(expected=IllegalArgumentException.class) public void testUnpadData_Blocksize_Is_Less_One() { byte[] bytearray = new byte[]{}; SecureMessaging.unpadData(bytearray, 0); } /** * Negative test: method unpadData gets bytearray with length less 1. */ @Test(expected=IllegalArgumentException.class) public void testUnpadData_Bytearray_Is_Less_One() { byte[] bytearray = new byte[]{}; SecureMessaging.unpadData(bytearray, 1); } /** * Negative test: In the unpadData method the current byte variable is not equal 0x80 */ @Test(expected=IllegalArgumentException.class) public void testUnpadData_Currentbyte_Is_Not_Equal_0x80() { byte[] bytearray = new byte[]{(byte) 0x81}; SecureMessaging.unpadData(bytearray, 1); } /** * Positive test: the method powerOn runs properly */ @Test public void testpPowerOn_ObjectsecureMessaging_Calls_PowerOn_Method() { SecureMessaging secureMessaging = new SecureMessaging(); secureMessaging.powerOn() ;} //TODO SMTest not yet tested functionality // extendedLength in both directions // incoming case4 short data extended le etc. // check discard key on powerOn and on incoming plain APDU // processDescending_withoutResonseData // processDescending_withResonseData // update SM keys after wrapping outgoing APDU // plainApduResetKeys() // processAscending_isoCase1() // processAscending_isoCase4() }