diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:19:18 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:19:18 +0000 |
commit | db74d7937f3a9503f0e2590ec24c3bcfbf259ce0 (patch) | |
tree | b39c3e794945072d38b97c963c5a6f10753514e1 /tests/iketests/src/java/com/android/internal/net | |
parent | 7ffef5cae6d3c93c93b4055fd22517d44f77acda (diff) | |
parent | eb4c77d7228f956f928c7d3500a220339ee78388 (diff) | |
download | ike-db74d7937f3a9503f0e2590ec24c3bcfbf259ce0.tar.gz |
Snap for 6001391 from eb4c77d7228f956f928c7d3500a220339ee78388 to qt-aml-tzdata-release
Change-Id: Ic6fc3b152696e0096d00036f460262a8b0319394
Diffstat (limited to 'tests/iketests/src/java/com/android/internal/net')
114 files changed, 24037 insertions, 0 deletions
diff --git a/tests/iketests/src/java/com/android/internal/net/TestUtils.java b/tests/iketests/src/java/com/android/internal/net/TestUtils.java new file mode 100644 index 00000000..6dd5ce5e --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/TestUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; + +import com.android.internal.net.utils.Log; + +import java.nio.ByteBuffer; + +/** TestUtils provides utility methods for parsing Hex String and constructing testing Logger. */ +public class TestUtils { + public static byte[] hexStringToByteArray(String hexString) { + int len = hexString.length(); + if (len % 2 != 0) { + throw new IllegalArgumentException("Invalid Hex String"); + } + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = + (byte) + ((Character.digit(hexString.charAt(i), 16) << 4) + + Character.digit(hexString.charAt(i + 1), 16)); + } + return data; + } + + public static int hexStringToInt(String hexString) { + if (hexString.length() > 8) { + throw new IllegalArgumentException("Invalid hex string length for integer type"); + } + + for (int i = hexString.length(); i < 8; i++) { + hexString = "0" + hexString; + } + + return ByteBuffer.wrap(hexStringToByteArray(hexString)).getInt(); + } + + public static String stringToHexString(String s) { + // two hex characters for each char in s + StringBuilder sb = new StringBuilder(s.length() * 2); + char[] chars = s.toCharArray(); + for (char c : chars) { + sb.append(Integer.toHexString(c)); + } + return sb.toString(); + } + + public static Log makeSpyLogThrowExceptionForWtf(String tag) { + Log spyLog = spy(new Log(tag, true /*logSensitive*/)); + + doAnswer( + (invocation) -> { + throw new IllegalStateException((String) invocation.getArguments()[1]); + }) + .when(spyLog) + .wtf(anyString(), anyString()); + + doAnswer( + (invocation) -> { + throw (Throwable) invocation.getArguments()[2]; + }) + .when(spyLog) + .wtf(anyString(), anyString(), anyObject()); + + return spyLog; + } + + public static Log makeSpyLogDoLogErrorForWtf(String tag) { + Log spyLog = spy(new Log(tag, true /*logSensitive*/)); + + doAnswer( + (invocation) -> { + spyLog.e( + "Mock logging WTF: " + invocation.getArguments()[0], + (String) invocation.getArguments()[1]); + return null; + }) + .when(spyLog) + .wtf(anyString(), anyString()); + + doAnswer( + (invocation) -> { + spyLog.e( + "Mock logging WTF: " + invocation.getArguments()[0], + (String) invocation.getArguments()[1], + (Throwable) invocation.getArguments()[2]); + return null; + }) + .when(spyLog) + .wtf(anyString(), anyString(), anyObject()); + + return spyLog; + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapAkaPrimeTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapAkaPrimeTest.java new file mode 100644 index 00000000..0872b67f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapAkaPrimeTest.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_SIM_START_PACKET; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.eap.EapSessionConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.statemachine.EapStateMachine; + +import org.junit.Before; +import org.junit.Test; + +public class EapAkaPrimeTest extends EapMethodEndToEndTest { + private static final long AUTHENTICATOR_TIMEOUT_MILLIS = 250L; + + private static final int SUB_ID = 1; + private static final String UNFORMATTED_IDENTITY = "123456789ABCDEF"; // IMSI + + // EAP_IDENTITY = hex("test@android.net") + private static final byte[] EAP_IDENTITY = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + private static final boolean ALLOW_MISMATCHED_NETWORK_NAMES = false; + private static final String PEER_NETWORK_NAME_1 = "foo:bar"; + private static final String PEER_NETWORK_NAME_2 = "bar"; + + // hex("foo:bar:buzz") + private static final String SERVER_NETWORK_NAME = "666F6F3A6261723A62757A7A"; + + // TODO(b/142667016): replace with externally generated test values + + // IK: 7320EE404E055EF2B5AB0F86E96C48BE + // CK: E9D1707652E13BF3E05975F601678E5C + // Server Network Name: 666F6F3A6261723A62757A7A + // SQN ^ AK: 35A9143ED9E1 + // IK': 79DC30692F3D2303D148549E5D50D0AA + // CK': BBD0A7AD3F14757BA604C4CBE70F9090 + // K_encr: 4c22c289bcf40367cf2bdb6a6e3fe56b + // K_aut: c64abd508ab628f842e9fb40a14fea769d2ccc67a8412794fe3b4c2556431e78 + // K_re: 5454ccf7ecc227f25c6cd1023e09394fa5cedc14a2f155e9d96a70dc404b4dca + private static final String RAND_1 = "D6A296F030A305601B311D38A004505C"; + private static final String RAND_2 = "000102030405060708090A0B0C0D0E0F"; + private static final String AUTN = "35A9143ED9E100011795E785DAFAAD9B"; + private static final String RES = "E5167A255FDCDE9248AF6B50ADA0D944"; + private static final String AUTS = "0102030405060708090A0B0C0D0E"; + private static final byte[] MSK = + hexStringToByteArray( + "695788d8f33af56b5b2fea065a0e8656" + + "7dc48120d6070d96056f9668614ec3e7" + + "feb4933a3aaab3587980a624998c8b5e" + + "a69d7295b824ef4a2201720be89d04df"); + private static final byte[] EMSK = + hexStringToByteArray( + "2db1f574d6e92cec294779defef5a7f0" + + "49319cc75367102815d0244087f23660" + + "0986b47a862c1aeeca418c84a2f9581b" + + "0738fdefd229a5f7a4ca76709379bf00"); + + // IK: 7320EE404E055EF2B5AB0F86E96C48BE + // CK: E9D1707652E13BF3E05975F601678E5C + // Server Network Name: 666F6F3A6261723A62757A7A + // SQN ^ AK: 35A9143ED9E1 + // IK': 6C45FB0B12FF8172223B6D0E599EAE20 + // CK': A01C894696BEB759ABE0340F71A20D7B + // K_encr: c039213c78fcf78a34bef30219a77822 + // K_aut: 95b014e569144eba71a387f91fb6b72e06781df12d61bfe88e5149477cd232aa + // K_re: 1000c2e2f01766a4d2581ac454e41fce1ee17bcccbc32dfad78815075d884c5e + private static final byte[] MSK_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "ad75a86586773134dcd9e78e3f75b282" + + "7a42435cb1be7235be58cddc60a0ba19" + + "dd5c30accfdb0db5ef065f46c3c25d7b" + + "9f8703d9493a2dc6fb6563dbdc854658"); + private static final byte[] EMSK_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "31a3f2bb0e3e831d991dc8666438297f" + + "4a5bc157fc1e31537e5a4927206d7b4b" + + "db830761eea3441d9b90da48aebb9734" + + "d3cbdec96072230a64043f54932a8841"); + + // Base 64 of: [Length][RAND_1][Length][AUTN] + private static final String BASE64_CHALLENGE_1 = + "ENailvAwowVgGzEdOKAEUFwQNakUPtnhAAEXleeF2vqtmw=="; + + // Base 64 of: ['DB'][Length][RES][Length][IK][Length][CK] + private static final String BASE_64_RESPONSE_SUCCESS = + "2xDlFnolX9zekkiva1CtoNlEEHMg7kBOBV7ytasPhulsSL4Q6dFwdlLhO/PgWXX2AWeOXA=="; + + // Base 64 of: [Length][RAND_2][Length][AUTN] + private static final String BASE64_CHALLENGE_2 = + "EAABAgMEBQYHCAkKCwwNDg8QNakUPtnhAAEXleeF2vqtmw=="; + + // Base 64 of: ['DC'][Length][AUTS] + private static final String BASE_64_RESPONSE_SYNC_FAIL = "3A4BAgMEBQYHCAkKCwwNDg=="; + + private static final String REQUEST_MAC = "9089f89b2f99bb85f2f2b529779f98db"; + private static final String RESPONSE_MAC = "48d7d6a80e1e2ff26a1e4148e0a2303e"; + private static final String REQUEST_MAC_WITHOUT_IDENTITY_REQ = + "59f680ede020a3d0156eef56affb6997"; + private static final String RESPONSE_MAC_WITHOUT_IDENTITY_REQ = + "e15322ff4abe51479c0fa92d00e343d7"; + + private static final byte[] EAP_AKA_PRIME_IDENTITY_REQUEST = + hexStringToByteArray( + "01CD000C" // EAP-Request | ID | length in bytes + + "32050000" // EAP-AKA' | Identity | 2B padding + + "0D010000"); // AT_ANY_ID_REQ attribute + private static final byte[] EAP_AKA_PRIME_IDENTITY_RESPONSE = + hexStringToByteArray( + "02CD001C" // EAP-Response | ID | length in bytes + + "32050000" // EAP-AKA' | Identity | 2B padding + + "0E05001036313233343536373839414243444546"); // AT_IDENTITY attribute + + private static final byte[] EAP_AKA_PRIME_CHALLENGE_REQUEST = + hexStringToByteArray( + "01CE0044" // EAP-Request | ID | length in bytes + + "32010000" // EAP-AKA' | Challenge | 2B padding + + "01050000" + RAND_1 // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "1704000C" + SERVER_NETWORK_NAME // AT_KDF_INPUT attribute + + "18010001" // AT_KDF attribute + + "0B050000" + REQUEST_MAC); // AT_MAC attribute + private static final byte[] EAP_AKA_PRIME_CHALLENGE_RESPONSE = + hexStringToByteArray( + "02CE0030" // EAP-Response | ID | length in bytes + + "32010000" // EAP-AKA' | Challenge | 2B padding + + "03050080" + RES // AT_RES attribute + + "0B050000" + RESPONSE_MAC); // AT_MAC attribute + + private static final byte[] EAP_AKA_PRIME_CHALLENGE_REQUEST_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "01CE0044" // EAP-Request | ID | length in bytes + + "32010000" // EAP-AKA' | Challenge | 2B padding + + "01050000" + RAND_1 // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "1704000C" + SERVER_NETWORK_NAME // AT_KDF_INPUT attribute + + "18010001" // AT_KDF attribute + + "0B050000" + REQUEST_MAC_WITHOUT_IDENTITY_REQ); // AT_MAC attribute + private static final byte[] EAP_AKA_PRIME_CHALLENGE_RESPONSE_WITHOUT_IDENTITY_REQUEST = + hexStringToByteArray( + "02CE0030" // EAP-Response | ID | length in bytes + + "32010000" // EAP-AKA' | Challenge | 2B padding + + "03050080" + RES // AT_RES attribute + + "0B050000" + RESPONSE_MAC_WITHOUT_IDENTITY_REQ); // AT_MAC attribute + + private static final byte[] EAP_AKA_PRIME_CHALLENGE_REQUEST_SYNC_FAIL = + hexStringToByteArray( + "01CE0044" // EAP-Request | ID | length in bytes + + "32010000" // EAP-AKA' | Challenge | 2B padding + + "01050000" + RAND_2 // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "1704000C" + SERVER_NETWORK_NAME // AT_KDF_INPUT attribute + + "18010001" // AT_KDF attribute + + "0B050000" + REQUEST_MAC); // AT_MAC attribute + private static final byte[] EAP_AKA_PRIME_SYNC_FAIL_RESPONSE = + hexStringToByteArray( + "02CE0018" // EAP-Response | ID | length in bytes + + "32040000" // EAP-AKA' | Synchronization-Failure | 2B padding + + "0404" + AUTS); // AT_AUTS attribute + + private static final byte[] EAP_AKA_PRIME_AUTHENTICATION_REJECT = + hexStringToByteArray( + "02CE0008" // EAP-Response | ID | length in bytes + + "32020000"); // EAP-AKA' | Authentication-Reject | 2B padding + + private static final byte[] EAP_RESPONSE_NAK_PACKET = + hexStringToByteArray("021000060332"); // NAK with EAP-AKA' listed + + private TelephonyManager mMockTelephonyManager; + + @Before + @Override + public void setUp() { + super.setUp(); + + setUp(ALLOW_MISMATCHED_NETWORK_NAMES, PEER_NETWORK_NAME_1); + } + + private void setUp(boolean allowMismatchedNetworkNames, String peerNetworkName) { + mMockTelephonyManager = mock(TelephonyManager.class); + + mEapSessionConfig = + new EapSessionConfig.Builder() + .setEapIdentity(EAP_IDENTITY) + .setEapAkaPrimeConfig( + SUB_ID, APPTYPE_USIM, peerNetworkName, allowMismatchedNetworkNames) + .build(); + mEapAuthenticator = + new EapAuthenticator( + mTestLooper.getLooper(), + mMockCallback, + new EapStateMachine(mMockContext, mEapSessionConfig, mMockSecureRandom), + (runnable) -> runnable.run(), + AUTHENTICATOR_TIMEOUT_MILLIS); + + when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE)) + .thenReturn(mMockTelephonyManager); + when(mMockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mMockTelephonyManager); + } + + @Test + public void testEapAkaPrimeEndToEnd() { + verifyEapPrimeAkaIdentity(); + verifyEapAkaPrimeChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_PRIME_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapAkaPrimeEndToEndWithoutIdentityRequest() { + verifyEapAkaPrimeChallengeWithoutIdentityReq(); + verifyEapSuccess(MSK_WITHOUT_IDENTITY_REQ, EMSK_WITHOUT_IDENTITY_REQ); + } + + @Test + public void testEapAkaPrimeWithEapNotifications() { + verifyEapNotification(1); + verifyEapPrimeAkaIdentity(); + + verifyEapNotification(2); + verifyEapAkaPrimeChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_PRIME_CHALLENGE_RESPONSE); + + verifyEapNotification(3); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapAkaPrimeUnsupportedType() { + verifyUnsupportedType(EAP_REQUEST_SIM_START_PACKET, EAP_RESPONSE_NAK_PACKET); + + verifyEapPrimeAkaIdentity(); + verifyEapAkaPrimeChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_PRIME_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapAkaPrimeSynchronizationFailure() { + verifyEapPrimeAkaIdentity(); + verifyEapAkaPrimeSynchronizationFailure(); + verifyEapAkaPrimeChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_PRIME_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapAkaPrimeAuthenticationReject() { + verifyEapPrimeAkaIdentity(); + + // return null from TelephonyManager to simluate rejection of AUTN + verifyEapAkaPrimeChallenge(null, EAP_AKA_PRIME_AUTHENTICATION_REJECT); + verifyEapFailure(); + } + + @Test + public void testEapAkaPrimeMismatchedNetworkNamesNotAllowed() { + // use mismatched peer network name + setUp(false, PEER_NETWORK_NAME_2); + verifyEapPrimeAkaIdentity(); + verifyEapAkaPrimeChallengeMismatchedNetworkNames(); + verifyEapFailure(); + } + + @Test + public void testEapAkaPrimeMismatchedNetworkNamesAllowed() { + setUp(true, PEER_NETWORK_NAME_2); + verifyEapPrimeAkaIdentity(); + verifyEapAkaPrimeChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_PRIME_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + private void verifyEapPrimeAkaIdentity() { + // EAP-AKA'/Identity request + when(mMockTelephonyManager.getSubscriberId()).thenReturn(UNFORMATTED_IDENTITY); + + mEapAuthenticator.processEapMessage(EAP_AKA_PRIME_IDENTITY_REQUEST); + mTestLooper.dispatchAll(); + + // verify EAP-AKA'/Identity response + verify(mMockContext).getSystemService(eq(Context.TELEPHONY_SERVICE)); + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + verify(mMockTelephonyManager).getSubscriberId(); + verify(mMockCallback).onResponse(eq(EAP_AKA_PRIME_IDENTITY_RESPONSE)); + verifyNoMoreInteractions( + mMockContext, mMockTelephonyManager, mMockSecureRandom, mMockCallback); + } + + private void verifyEapAkaPrimeChallenge( + String challengeBase64, + String responseBase64, + byte[] incomingEapPacket, + byte[] outgoingEapPacket) { + // EAP-AKA'/Challenge request + when(mMockTelephonyManager.getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + challengeBase64)) + .thenReturn(responseBase64); + + mEapAuthenticator.processEapMessage(incomingEapPacket); + mTestLooper.dispatchAll(); + + // verify EAP-AKA'/Challenge response + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + challengeBase64); + verify(mMockCallback).onResponse(eq(outgoingEapPacket)); + } + + private void verifyEapAkaPrimeChallenge(String responseBase64, byte[] outgoingPacket) { + verifyEapAkaPrimeChallenge( + BASE64_CHALLENGE_1, + responseBase64, + EAP_AKA_PRIME_CHALLENGE_REQUEST, + outgoingPacket); + verifyNoMoreInteractions( + mMockContext, mMockTelephonyManager, mMockSecureRandom, mMockCallback); + } + + private void verifyEapAkaPrimeChallengeWithoutIdentityReq() { + verifyEapAkaPrimeChallenge( + BASE64_CHALLENGE_1, + BASE_64_RESPONSE_SUCCESS, + EAP_AKA_PRIME_CHALLENGE_REQUEST_WITHOUT_IDENTITY_REQ, + EAP_AKA_PRIME_CHALLENGE_RESPONSE_WITHOUT_IDENTITY_REQUEST); + + // also need to verify interactions with Context and TelephonyManager + verify(mMockContext).getSystemService(eq(Context.TELEPHONY_SERVICE)); + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + verifyNoMoreInteractions( + mMockContext, mMockTelephonyManager, mMockSecureRandom, mMockCallback); + } + + private void verifyEapAkaPrimeSynchronizationFailure() { + verifyEapAkaPrimeChallenge( + BASE64_CHALLENGE_2, + BASE_64_RESPONSE_SYNC_FAIL, + EAP_AKA_PRIME_CHALLENGE_REQUEST_SYNC_FAIL, + EAP_AKA_PRIME_SYNC_FAIL_RESPONSE); + verifyNoMoreInteractions( + mMockContext, mMockTelephonyManager, mMockSecureRandom, mMockCallback); + } + + private void verifyEapAkaPrimeChallengeMismatchedNetworkNames() { + // EAP-AKA'/Challenge request + mEapAuthenticator.processEapMessage(EAP_AKA_PRIME_CHALLENGE_REQUEST); + mTestLooper.dispatchAll(); + verify(mMockCallback).onResponse(eq(EAP_AKA_PRIME_AUTHENTICATION_REJECT)); + } + + @Override + protected void verifyEapSuccess(byte[] msk, byte[] emsk) { + super.verifyEapSuccess(msk, emsk); + + verifyNoMoreInteractions(mMockTelephonyManager); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapAkaTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapAkaTest.java new file mode 100644 index 00000000..e982a85d --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapAkaTest.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_SIM_START_PACKET; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.eap.EapSessionConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.statemachine.EapStateMachine; + +import org.junit.Before; +import org.junit.Test; + +/** + * This test verifies that EAP-AKA is functional for an end-to-end implementation + */ +public class EapAkaTest extends EapMethodEndToEndTest { + private static final long AUTHENTICATOR_TIMEOUT_MILLIS = 250L; + + private static final int SUB_ID = 1; + private static final String UNFORMATTED_IDENTITY = "123456789ABCDEF"; // IMSI + + // EAP_IDENTITY = hex("test@android.net") + private static final byte[] EAP_IDENTITY = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + + // TODO(b/140797965): find valid AUTN/RAND values for the CTS test sim + // IK: 7320EE404E055EF2B5AB0F86E96C48BE + // CK: E9D1707652E13BF3E05975F601678E5C + // MK: 2AE8AD50432246E6ACED9AA0FC794A22CE9CE4BB + // K_encr: DB6F06910D5D19CC9DA5F2687F5C5737 + // K_aut: B20A586592796E08E7408FB53356E9B1 + private static final String RAND_1 = "D6A296F030A305601B311D38A004505C"; + private static final String RAND_2 = "000102030405060708090A0B0C0D0E0F"; + private static final String AUTN = "35A9143ED9E100011795E785DAFAAD9B"; + private static final String RES = "E5167A255FDCDE9248AF6B50ADA0D944"; + private static final String AUTS = "0102030405060708090A0B0C0D0E"; + private static final byte[] MSK = + hexStringToByteArray( + "EFC4FB9F54D99A3F4A04B756993CA813" + + "E463CA0ADBF3CB2A296519ED4C600FF5" + + "81898B1C425C20FE7471FC43A4BB3C00" + + "DDF80A7083972B660BC7153CBF2C9AA1"); + private static final byte[] EMSK = + hexStringToByteArray( + "5C95F3E2476ED4D6588CE6DE2618D808" + + "9ECA12A4636C8A1B0C678562CBFC31D3" + + "94B578DE0A3686E17F96F14D5341FE75" + + "2012944CA394E5288BA1B2C70CB65063"); + + // IK: 7320EE404E055EF2B5AB0F86E96C48BE + // CK: E9D1707652E13BF3E05975F601678E5C + // MK: 8183017CD8ADDB4617F4A2274DD5BCEA99354FB7 + // K_encr: 891D5DB8CACAF657D68BE72371F927A2 + // K_aut: E042A1CC5672358685EC012881EA02DE + private static final byte[] MSK_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "629DE03704E15EF1B8BADFF7FA5D84D5" + + "8574B6A3A46F274796346A86AE3455AC" + + "711E2D4D3F96EE71E664B1B947D7E9E7" + + "D227CBB6199A68BD7D43E6E4863D08D6"); + private static final byte[] EMSK_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "30A6638AE3AB5C5D29554D8256C3A287" + + "FDF6255E4D726C0622DDF89609C16A8D" + + "563768166A8111A083547DE4C8E280D6" + + "113A608DE9227FC7C02679A1E04DB3CF"); + + // Base 64 of: [Length][RAND_1][Length][AUTN] + private static final String BASE64_CHALLENGE_1 = + "ENailvAwowVgGzEdOKAEUFwQNakUPtnhAAEXleeF2vqtmw=="; + + // Base 64 of: ['DB'][Length][RES][Length][IK][Length][CK] + private static final String BASE_64_RESPONSE_SUCCESS = + "2xDlFnolX9zekkiva1CtoNlEEHMg7kBOBV7ytasPhulsSL4Q6dFwdlLhO/PgWXX2AWeOXA=="; + + // Base 64 of: [Length][RAND_2][Length][AUTN] + private static final String BASE64_CHALLENGE_2 = + "EAABAgMEBQYHCAkKCwwNDg8QNakUPtnhAAEXleeF2vqtmw=="; + + // Base 64 of: ['DC'][Length][AUTS] + private static final String BASE_64_RESPONSE_SYNC_FAIL = "3A4BAgMEBQYHCAkKCwwNDg=="; + + private static final String REQUEST_MAC = "90C3554783D49A18F9EAA231F3C261EC"; + private static final String RESPONSE_MAC = "D085987D3D15FA50A80D0CECFA2412EB"; + private static final String REQUEST_MAC_WITHOUT_IDENTITY_REQ = + "6AD7E3F43ED99384E751F55AB8EA48B4"; + private static final String RESPONSE_MAC_WITHOUT_IDENTITY_REQ = + "83E9F5B8B44BDE39B50538BF49864209"; + + private static final byte[] EAP_AKA_IDENTITY_REQUEST = + hexStringToByteArray( + "01CD000C" // EAP-Request | ID | length in bytes + + "17050000" // EAP-AKA | Identity | 2B padding + + "0D010000"); // AT_ANY_ID_REQ attribute + private static final byte[] EAP_AKA_IDENTITY_RESPONSE = + hexStringToByteArray( + "02CD001C" // EAP-Response | ID | length in bytes + + "17050000" // EAP-AKA | Identity | 2B padding + + "0E05001030313233343536373839414243444546"); // AT_IDENTITY attribute + + private static final byte[] EAP_AKA_CHALLENGE_REQUEST = + hexStringToByteArray( + "01CE0044" // EAP-Request | ID | length in bytes + + "17010000" // EAP-AKA | Challenge | 2B padding + + "01050000" + RAND_1 // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "0B050000" + REQUEST_MAC); // AT_MAC attribute + private static final byte[] EAP_AKA_CHALLENGE_RESPONSE = + hexStringToByteArray( + "02CE0030" // EAP-Response | ID | length in bytes + + "17010000" // EAP-AKA | Challenge | 2B padding + + "03050080" + RES // AT_RES attribute + + "0B050000" + RESPONSE_MAC); // AT_MAC attribute + + private static final byte[] EAP_AKA_CHALLENGE_REQUEST_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "01CE0044" // EAP-Request | ID | length in bytes + + "17010000" // EAP-AKA | Challenge | 2B padding + + "01050000" + RAND_1 // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "0B050000" + REQUEST_MAC_WITHOUT_IDENTITY_REQ); // AT_MAC attribute + private static final byte[] EAP_AKA_CHALLENGE_RESPONSE_WITHOUT_IDENTITY_REQUEST = + hexStringToByteArray( + "02CE0030" // EAP-Response | ID | length in bytes + + "17010000" // EAP-AKA | Challenge | 2B padding + + "03050080" + RES // AT_RES attribute + + "0B050000" + RESPONSE_MAC_WITHOUT_IDENTITY_REQ); // AT_MAC attribute + + private static final byte[] EAP_AKA_CHALLENGE_REQUEST_SYNC_FAIL = + hexStringToByteArray( + "01CE0044" // EAP-Request | ID | length in bytes + + "17010000" // EAP-AKA | Challenge | 2B padding + + "01050000" + RAND_2 // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "0B050000" + REQUEST_MAC); // AT_MAC attribute + private static final byte[] EAP_AKA_SYNC_FAIL_RESPONSE = + hexStringToByteArray( + "02CE0018" // EAP-Response | ID | length in bytes + + "17040000" // EAP-AKA | Synchronization-Failure | 2B padding + + "0404" + AUTS); // AT_AUTS attribute + + private static final byte[] EAP_AKA_AUTHENTICATION_REJECT = + hexStringToByteArray( + "02CE0008" // EAP-Response | ID | length in bytes + + "17020000"); // EAP-AKA | Authentication-Reject | 2B padding + + private static final byte[] EAP_RESPONSE_NAK_PACKET = + hexStringToByteArray("021000060317"); // NAK with EAP-AKA listed + + private TelephonyManager mMockTelephonyManager; + + @Before + @Override + public void setUp() { + super.setUp(); + + mMockTelephonyManager = mock(TelephonyManager.class); + + mEapSessionConfig = + new EapSessionConfig.Builder() + .setEapIdentity(EAP_IDENTITY) + .setEapAkaConfig(SUB_ID, APPTYPE_USIM) + .build(); + mEapAuthenticator = + new EapAuthenticator( + mTestLooper.getLooper(), + mMockCallback, + new EapStateMachine(mMockContext, mEapSessionConfig, mMockSecureRandom), + (runnable) -> runnable.run(), + AUTHENTICATOR_TIMEOUT_MILLIS); + + when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE)) + .thenReturn(mMockTelephonyManager); + when(mMockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mMockTelephonyManager); + } + + @Test + public void testEapAkaEndToEnd() { + verifyEapAkaIdentity(); + verifyEapAkaChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapAkaEndToEndWithoutIdentityRequest() { + verifyEapAkaChallengeWithoutIdentityReq(); + verifyEapSuccess(MSK_WITHOUT_IDENTITY_REQ, EMSK_WITHOUT_IDENTITY_REQ); + } + + @Test + public void testEapAkaWithEapNotifications() { + verifyEapNotification(1); + verifyEapAkaIdentity(); + + verifyEapNotification(2); + verifyEapAkaChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_CHALLENGE_RESPONSE); + + verifyEapNotification(3); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapAkaUnsupportedType() { + verifyUnsupportedType(EAP_REQUEST_SIM_START_PACKET, EAP_RESPONSE_NAK_PACKET); + + verifyEapAkaIdentity(); + verifyEapAkaChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapAkaSynchronizationFailure() { + verifyEapAkaIdentity(); + verifyEapAkaSynchronizationFailure(); + verifyEapAkaChallenge(BASE_64_RESPONSE_SUCCESS, EAP_AKA_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapAkaAuthenticationReject() { + verifyEapAkaIdentity(); + + // return null from TelephonyManager to simluate rejection of AUTN + verifyEapAkaChallenge(null, EAP_AKA_AUTHENTICATION_REJECT); + verifyEapFailure(); + } + + private void verifyEapAkaIdentity() { + // EAP-AKA/Identity request + when(mMockTelephonyManager.getSubscriberId()).thenReturn(UNFORMATTED_IDENTITY); + + mEapAuthenticator.processEapMessage(EAP_AKA_IDENTITY_REQUEST); + mTestLooper.dispatchAll(); + + // verify EAP-AKA/Identity response + verify(mMockContext).getSystemService(eq(Context.TELEPHONY_SERVICE)); + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + verify(mMockTelephonyManager).getSubscriberId(); + verify(mMockCallback).onResponse(eq(EAP_AKA_IDENTITY_RESPONSE)); + verifyNoMoreInteractions( + mMockContext, mMockTelephonyManager, mMockSecureRandom, mMockCallback); + } + + private void verifyEapAkaChallenge( + String challengeBase64, + String responseBase64, + byte[] incomingEapPacket, + byte[] outgoingEapPacket) { + // EAP-AKA/Challenge request + when(mMockTelephonyManager.getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + challengeBase64)) + .thenReturn(responseBase64); + + mEapAuthenticator.processEapMessage(incomingEapPacket); + mTestLooper.dispatchAll(); + + // verify EAP-AKA/Challenge response + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + challengeBase64); + verify(mMockCallback).onResponse(eq(outgoingEapPacket)); + } + + private void verifyEapAkaChallenge(String responseBase64, byte[] outgoingPacket) { + verifyEapAkaChallenge( + BASE64_CHALLENGE_1, responseBase64, EAP_AKA_CHALLENGE_REQUEST, outgoingPacket); + verifyNoMoreInteractions( + mMockContext, mMockTelephonyManager, mMockSecureRandom, mMockCallback); + } + + private void verifyEapAkaChallengeWithoutIdentityReq() { + verifyEapAkaChallenge( + BASE64_CHALLENGE_1, + BASE_64_RESPONSE_SUCCESS, + EAP_AKA_CHALLENGE_REQUEST_WITHOUT_IDENTITY_REQ, + EAP_AKA_CHALLENGE_RESPONSE_WITHOUT_IDENTITY_REQUEST); + + // also need to verify interactions with Context and TelephonyManager + verify(mMockContext).getSystemService(eq(Context.TELEPHONY_SERVICE)); + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + verifyNoMoreInteractions( + mMockContext, mMockTelephonyManager, mMockSecureRandom, mMockCallback); + } + + private void verifyEapAkaSynchronizationFailure() { + verifyEapAkaChallenge( + BASE64_CHALLENGE_2, + BASE_64_RESPONSE_SYNC_FAIL, + EAP_AKA_CHALLENGE_REQUEST_SYNC_FAIL, + EAP_AKA_SYNC_FAIL_RESPONSE); + verifyNoMoreInteractions( + mMockContext, mMockTelephonyManager, mMockSecureRandom, mMockCallback); + } + + @Override + protected void verifyEapSuccess(byte[] msk, byte[] emsk) { + super.verifyEapSuccess(msk, emsk); + + verifyNoMoreInteractions(mMockTelephonyManager); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapAuthenticatorTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapAuthenticatorTest.java new file mode 100644 index 00000000..4c868a03 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapAuthenticatorTest.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_FAILURE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_SIM_START_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_RESPONSE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SUCCESS_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.REQUEST_UNSUPPORTED_TYPE_PACKET; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.os.Looper; +import android.os.test.TestLooper; + +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.EapResult.EapSuccess; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.statemachine.EapStateMachine; + +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.TimeoutException; + +public class EapAuthenticatorTest { + private static final long AUTHENTICATOR_TIMEOUT_MILLIS = 250L; + private static final long TEST_TIMEOUT_MILLIS = 2 * AUTHENTICATOR_TIMEOUT_MILLIS; + private static final byte[] MSK = hexStringToByteArray( + "00112233445566778899AABBCCDDEEFF" + + "00112233445566778899AABBCCDDEEFF" + + "00112233445566778899AABBCCDDEEFF" + + "00112233445566778899AABBCCDDEEFF"); + private static final byte[] EMSK = hexStringToByteArray( + "FFEEDDCCBBAA99887766554433221100" + + "FFEEDDCCBBAA99887766554433221100" + + "FFEEDDCCBBAA99887766554433221100" + + "FFEEDDCCBBAA99887766554433221100"); + + private EapStateMachine mMockEapStateMachine; + + private TestLooper mTestLooper; + private boolean mCallbackFired; + + @Before + public void setUp() { + if (Looper.myLooper() == null) Looper.prepare(); + + mMockEapStateMachine = mock(EapStateMachine.class); + + mTestLooper = new TestLooper(); + mCallbackFired = false; + } + + @Test + public void testProcessEapMessageResponse() { + EapCallback eapCallback = new EapCallback() { + @Override + public void onResponse(byte[] eapMsg) { + assertArrayEquals(EAP_SIM_RESPONSE_PACKET, eapMsg); + assertFalse("Callback has already been fired", mCallbackFired); + mCallbackFired = true; + } + }; + + EapResponse eapResponse = new EapResponse(EAP_SIM_RESPONSE_PACKET); + when(mMockEapStateMachine.process(eq(EAP_REQUEST_SIM_START_PACKET))) + .thenReturn(eapResponse); + + getEapAuthenticatorWithCallback(eapCallback) + .processEapMessage(EAP_REQUEST_SIM_START_PACKET); + mTestLooper.dispatchAll(); + + assertTrue("Callback didn't fire", mCallbackFired); + verify(mMockEapStateMachine).process(eq(EAP_REQUEST_SIM_START_PACKET)); + verifyNoMoreInteractions(mMockEapStateMachine); + } + + @Test + public void testProcessEapMessageError() { + EapCallback eapCallback = new EapCallback() { + @Override + public void onError(Throwable cause) { + assertTrue(cause instanceof EapInvalidRequestException); + assertFalse("Callback has already been fired", mCallbackFired); + mCallbackFired = true; + } + }; + Exception cause = new EapInvalidRequestException("Error"); + EapError eapError = new EapError(cause); + when(mMockEapStateMachine.process(eq(REQUEST_UNSUPPORTED_TYPE_PACKET))) + .thenReturn(eapError); + + getEapAuthenticatorWithCallback(eapCallback) + .processEapMessage(REQUEST_UNSUPPORTED_TYPE_PACKET); + mTestLooper.dispatchAll(); + + assertTrue("Callback didn't fire", mCallbackFired); + verify(mMockEapStateMachine).process(eq(REQUEST_UNSUPPORTED_TYPE_PACKET)); + verifyNoMoreInteractions(mMockEapStateMachine); + } + + @Test + public void testProcessEapMessageSuccess() { + EapCallback eapCallback = new EapCallback() { + @Override + public void onSuccess(byte[] msk, byte[] emsk) { + assertArrayEquals(MSK, msk); + assertArrayEquals(EMSK, emsk); + assertFalse("Callback has already been fired", mCallbackFired); + mCallbackFired = true; + } + }; + EapSuccess eapSuccess = new EapSuccess(MSK, EMSK); + when(mMockEapStateMachine.process(eq(EAP_SUCCESS_PACKET))) + .thenReturn(eapSuccess); + + getEapAuthenticatorWithCallback(eapCallback) + .processEapMessage(EAP_SUCCESS_PACKET); + mTestLooper.dispatchAll(); + + assertTrue("Callback didn't fire", mCallbackFired); + verify(mMockEapStateMachine).process(eq(EAP_SUCCESS_PACKET)); + verifyNoMoreInteractions(mMockEapStateMachine); + } + + @Test + public void testProcessEapMessageFailure() { + EapCallback eapCallback = new EapCallback() { + @Override + public void onFail() { + // nothing to check here + assertFalse("Callback has already been fired", mCallbackFired); + mCallbackFired = true; + } + }; + when(mMockEapStateMachine.process(eq(EAP_FAILURE_PACKET))) + .thenReturn(new EapFailure()); + + getEapAuthenticatorWithCallback(eapCallback) + .processEapMessage(EAP_FAILURE_PACKET); + mTestLooper.dispatchAll(); + + assertTrue("Callback didn't fire", mCallbackFired); + verify(mMockEapStateMachine).process(eq(EAP_FAILURE_PACKET)); + verifyNoMoreInteractions(mMockEapStateMachine); + } + + @Test + public void testProcessEapMessageExceptionThrown() { + EapCallback eapCallback = new EapCallback() { + @Override + public void onError(Throwable cause) { + assertTrue(cause instanceof NullPointerException); + assertFalse("Callback has already been fired", mCallbackFired); + mCallbackFired = true; + } + }; + when(mMockEapStateMachine.process(EAP_REQUEST_SIM_START_PACKET)) + .thenThrow(new NullPointerException()); + + getEapAuthenticatorWithCallback(eapCallback) + .processEapMessage(EAP_REQUEST_SIM_START_PACKET); + mTestLooper.dispatchAll(); + + assertTrue("Callback didn't fire", mCallbackFired); + verify(mMockEapStateMachine).process(eq(EAP_REQUEST_SIM_START_PACKET)); + verifyNoMoreInteractions(mMockEapStateMachine); + } + + @Test + public void testProcessEapMessageStateMachineTimeout() { + EapCallback eapCallback = new EapCallback() { + @Override + public void onError(Throwable cause) { + assertTrue(cause instanceof TimeoutException); + assertFalse("Callback has already been fired", mCallbackFired); + mCallbackFired = true; + } + }; + EapResponse eapResponse = new EapResponse(EAP_SIM_RESPONSE_PACKET); + when(mMockEapStateMachine.process(eq(EAP_REQUEST_SIM_START_PACKET))) + .then((invocation) -> { + // move time forward to trigger the timeout + mTestLooper.moveTimeForward(TEST_TIMEOUT_MILLIS); + return eapResponse; + }); + + getEapAuthenticatorWithCallback(eapCallback) + .processEapMessage(EAP_REQUEST_SIM_START_PACKET); + mTestLooper.dispatchAll(); + + assertTrue("Callback didn't fire", mCallbackFired); + verify(mMockEapStateMachine).process(eq(EAP_REQUEST_SIM_START_PACKET)); + verifyNoMoreInteractions(mMockEapStateMachine); + } + + private EapAuthenticator getEapAuthenticatorWithCallback(EapCallback eapCallback) { + return new EapAuthenticator( + mTestLooper.getLooper(), + eapCallback, + mMockEapStateMachine, + (runnable) -> runnable.run(), + AUTHENTICATOR_TIMEOUT_MILLIS); + } + + /** + * Default {@link IEapCallback} implementation that throws {@link UnsupportedOperationException} + * for all calls. + */ + private abstract static class EapCallback implements IEapCallback { + @Override + public void onSuccess(byte[] msk, byte[] emsk) { + throw new UnsupportedOperationException(); + } + + @Override + public void onFail() { + throw new UnsupportedOperationException(); + } + + @Override + public void onResponse(byte[] eapMsg) { + throw new UnsupportedOperationException(); + } + + @Override + public void onError(Throwable cause) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapErrorTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapErrorTest.java new file mode 100644 index 00000000..ce497635 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapErrorTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static org.junit.Assert.assertEquals; + +import com.android.internal.net.eap.EapResult.EapError; + +import org.junit.Test; + +public class EapErrorTest { + private static final RuntimeException EXPECTED_EXCEPTION = new RuntimeException("expected"); + + @Test + public void testEapErrorConstructor() { + EapError eapError = new EapError(EXPECTED_EXCEPTION); + assertEquals(EXPECTED_EXCEPTION, eapError.cause); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapMethodEndToEndTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapMethodEndToEndTest.java new file mode 100644 index 00000000..412a4cf1 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapMethodEndToEndTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_FAILURE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SUCCESS; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.content.Context; +import android.net.eap.EapSessionConfig; +import android.os.test.TestLooper; + +import org.junit.Before; + +import java.security.SecureRandom; + +public class EapMethodEndToEndTest { + protected Context mMockContext; + protected SecureRandom mMockSecureRandom; + protected IEapCallback mMockCallback; + + protected TestLooper mTestLooper; + protected EapSessionConfig mEapSessionConfig; + protected EapAuthenticator mEapAuthenticator; + + @Before + public void setUp() { + mMockContext = mock(Context.class); + mMockSecureRandom = mock(SecureRandom.class); + mMockCallback = mock(IEapCallback.class); + + mTestLooper = new TestLooper(); + } + + protected void verifyUnsupportedType(byte[] invalidMessageType, byte[] nakResponse) { + mEapAuthenticator.processEapMessage(invalidMessageType); + mTestLooper.dispatchAll(); + + // verify EAP-Response/Nak returned + verify(mMockCallback).onResponse(eq(nakResponse)); + verifyNoMoreInteractions(mMockCallback); + } + + protected void verifyEapNotification(int callsToVerify) { + mEapAuthenticator.processEapMessage(EAP_REQUEST_NOTIFICATION_PACKET); + mTestLooper.dispatchAll(); + + verify(mMockCallback, times(callsToVerify)) + .onResponse(eq(EAP_RESPONSE_NOTIFICATION_PACKET)); + verifyNoMoreInteractions(mMockCallback); + } + + protected void verifyEapSuccess(byte[] msk, byte[] emsk) { + // EAP-Success + mEapAuthenticator.processEapMessage(EAP_SUCCESS); + mTestLooper.dispatchAll(); + + // verify that onSuccess callback made + verify(mMockCallback).onSuccess(eq(msk), eq(emsk)); + verifyNoMoreInteractions(mMockContext, mMockSecureRandom, mMockCallback); + } + + protected void verifyEapFailure() { + mEapAuthenticator.processEapMessage(EAP_FAILURE_PACKET); + mTestLooper.dispatchAll(); + + verify(mMockCallback).onFail(); + verifyNoMoreInteractions(mMockCallback); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapMsChapV2Test.java b/tests/iketests/src/java/com/android/internal/net/eap/EapMsChapV2Test.java new file mode 100644 index 00000000..01264f9d --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapMsChapV2Test.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_AKA_IDENTITY_PACKET; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.net.eap.EapSessionConfig; + +import com.android.internal.net.eap.statemachine.EapStateMachine; + +import org.junit.Before; +import org.junit.Test; + +public class EapMsChapV2Test extends EapMethodEndToEndTest { + private static final long AUTHENTICATOR_TIMEOUT_MILLIS = 250L; + + private static final String USERNAME = "User"; + private static final String PASSWORD = "clientPass"; + + private static final byte[] PEER_CHALLENGE = + hexStringToByteArray("21402324255E262A28295F2B3A337C7E"); + private static final byte[] MSK = + hexStringToByteArray( + "D5F0E9521E3EA9589645E86051C822268B7CDC149B993A1BA118CB153F56DCCB"); + + // Server-Name = hex("authenticator@android.net") + private static final byte[] EAP_MSCHAP_V2_CHALLENGE_REQUEST = + hexStringToByteArray("01110033" // EAP-Request | ID | length in bytes + + "1A0142" // EAP-MSCHAPv2 | Request | MSCHAPv2 ID + + "002E10" // MS length | Value Size (0x10) + + "5B5D7C7D7B3F2F3E3C2C602132262628" // Authenticator-Challenge + + "61757468656E74696361746F7240616E64726F69642E6E6574"); // Server-Name + private static final byte[] EAP_MSCHAP_V2_CHALLENGE_RESPONSE = + hexStringToByteArray("0211003F" // EAP-Response | ID | length in bytes + + "1A0242" // EAP-MSCHAPv2 | Response | MSCHAPv2 ID + + "003A31" // MS length | Value Size (0x31) + + "21402324255E262A28295F2B3A337C7E" // Peer-Challenge + + "0000000000000000" // 8B (reserved) + + "82309ECD8D708B5EA08FAA3981CD83544233114A3D85D6DF" // NT-Response + + "00" // Flags + + "55736572"); // hex(USERNAME) + private static final byte[] EAP_MSCHAP_V2_SUCCESS_REQUEST = + hexStringToByteArray("01120047" // EAP-Request | ID | length in bytes + + "1A03420042" // EAP-MSCHAPv2 | Success | MSCHAPv2 ID | MS length + + "533D" // hex("S=") + + "3430374135353839313135464430443632303946" + + "3531304645394330343536363933324344413536" // hex("<auth_string>") + + "204D3D" // hex(" M=") + + "7465737420416E64726F69642031323334"); // hex("test Android 1234") + private static final byte[] EAP_MSCHAP_V2_SUCCESS_RESPONSE = + hexStringToByteArray("02120006" // EAP-Response | ID | length in bytes + + "1A03"); // EAP-MSCHAPv2 | Success + private static final byte[] EAP_MSCHAP_V2_FAILURE_REQUEST = + hexStringToByteArray("01130049" // EAP-Request | ID | length in bytes + + "1A04420044" // EAP-MSCHAPv2 | Failure | MSCHAPv2 ID | MS length + + "453D363437" // hex("E=647") + + "20523D31" // hex(" R=1") + + "20433D" // hex(" C=") + + "30303031303230333034303530363037" + + "30383039304130423043304430453046" // hex("<authenticator challenge>") + + "20563D33" // hex(" V=3") + + "204D3D" // hex(" M=") + + "7465737420416E64726F69642031323334"); // hex("test Android 1234") + private static final byte[] EAP_MSCHAP_V2_FAILURE_RESPONSE = + hexStringToByteArray("02130006" // EAP-Response | ID | length in bytes + + "1A04"); // EAP-MSCHAPv2 | Failure + + private static final byte[] EAP_RESPONSE_NAK_PACKET = hexStringToByteArray("02100006031A"); + + @Before + @Override + public void setUp() { + super.setUp(); + + mEapSessionConfig = + new EapSessionConfig.Builder().setEapMsChapV2Config(USERNAME, PASSWORD).build(); + mEapAuthenticator = + new EapAuthenticator( + mTestLooper.getLooper(), + mMockCallback, + new EapStateMachine(mMockContext, mEapSessionConfig, mMockSecureRandom), + (runnable) -> runnable.run(), + AUTHENTICATOR_TIMEOUT_MILLIS); + } + + @Test + public void testEapMsChapV2EndToEndSuccess() { + verifyEapMsChapV2Challenge(); + verifyEapMsChapV2SuccessRequest(); + verifyEapSuccess(MSK, new byte[0]); + } + + @Test + public void testEapMsChapV2EndToEndFailure() { + verifyEapMsChapV2Challenge(); + verifyEapMsChapV2FailureRequest(); + verifyEapFailure(); + } + + @Test + public void testEapMsChapV2UnsupportedType() { + verifyUnsupportedType(EAP_REQUEST_AKA_IDENTITY_PACKET, EAP_RESPONSE_NAK_PACKET); + + verifyEapMsChapV2Challenge(); + verifyEapMsChapV2SuccessRequest(); + verifyEapSuccess(MSK, new byte[0]); + } + + @Test + public void verifyEapMsChapV2WithEapNotifications() { + verifyEapNotification(1); + + verifyEapMsChapV2Challenge(); + verifyEapNotification(2); + + verifyEapMsChapV2SuccessRequest(); + verifyEapNotification(3); + + verifyEapSuccess(MSK, new byte[0]); + } + + private void verifyEapMsChapV2Challenge() { + doAnswer(invocation -> { + byte[] dst = invocation.getArgument(0); + System.arraycopy(PEER_CHALLENGE, 0, dst, 0, PEER_CHALLENGE.length); + return null; + }).when(mMockSecureRandom).nextBytes(eq(new byte[PEER_CHALLENGE.length])); + + mEapAuthenticator.processEapMessage(EAP_MSCHAP_V2_CHALLENGE_REQUEST); + mTestLooper.dispatchAll(); + + verify(mMockCallback).onResponse(eq(EAP_MSCHAP_V2_CHALLENGE_RESPONSE)); + verify(mMockSecureRandom).nextBytes(any(byte[].class)); + verifyNoMoreInteractions(mMockCallback); + } + + private void verifyEapMsChapV2SuccessRequest() { + mEapAuthenticator.processEapMessage(EAP_MSCHAP_V2_SUCCESS_REQUEST); + mTestLooper.dispatchAll(); + + verify(mMockCallback).onResponse(eq(EAP_MSCHAP_V2_SUCCESS_RESPONSE)); + verifyNoMoreInteractions(mMockCallback); + } + + private void verifyEapMsChapV2FailureRequest() { + mEapAuthenticator.processEapMessage(EAP_MSCHAP_V2_FAILURE_REQUEST); + mTestLooper.dispatchAll(); + + verify(mMockCallback).onResponse(eq(EAP_MSCHAP_V2_FAILURE_RESPONSE)); + verifyNoMoreInteractions(mMockCallback); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapResponseTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapResponseTest.java new file mode 100644 index 00000000..0c6dd52d --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapResponseTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NAK_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SUCCESS_PACKET; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.InvalidEapResponseException; +import com.android.internal.net.eap.message.EapMessage; + +import org.junit.Before; +import org.junit.Test; + +public class EapResponseTest { + private EapMessage mEapResponse; + private EapMessage mEapSuccess; + + @Before + public void setUp() throws Exception { + mEapResponse = EapMessage.decode(EAP_RESPONSE_NAK_PACKET); + mEapSuccess = EapMessage.decode(EAP_SUCCESS_PACKET); + } + + @Test + public void testGetEapResponse() { + EapResult eapResult = EapResponse.getEapResponse(mEapResponse); + assertTrue(eapResult instanceof EapResponse); + + EapResponse eapResponse = (EapResponse) eapResult; + assertArrayEquals(EAP_RESPONSE_NAK_PACKET, eapResponse.packet); + } + + @Test + public void testGetEapResponseNullMessage() { + try { + EapResponse.getEapResponse(null); + fail("Expected IllegalArgumentException for null EapMessage"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testGetEapResponseNonRequestMessage() { + EapResult eapResult = EapResponse.getEapResponse(mEapSuccess); + assertTrue(eapResult instanceof EapError); + + EapError eapError = (EapError) eapResult; + assertTrue(eapError.cause instanceof InvalidEapResponseException); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapSimTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapSimTest.java new file mode 100644 index 00000000..8b11d278 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapSimTest.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_AKA_IDENTITY_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NAK_PACKET; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.eap.EapSessionConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.statemachine.EapStateMachine; + +import org.junit.Before; +import org.junit.Test; + +/** + * This test verifies that EAP-SIM is functional for an end-to-end implementation + */ +public class EapSimTest extends EapMethodEndToEndTest { + private static final long AUTHENTICATOR_TIMEOUT_MILLIS = 250L; + + private static final byte[] NONCE = hexStringToByteArray("37f3ddd3954c4831a5ee08c574844398"); + private static final String UNFORMATTED_IDENTITY = "123456789ABCDEF"; // IMSI + + // EAP_IDENTITY = hex("test@android.net") + private static final byte[] EAP_IDENTITY = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + + private static final int SUB_ID = 1; + + // Base 64 of: RAND + private static final String BASE64_RAND_1 = "EAEjRWeJq83vESNFZ4mrze8="; + private static final String BASE64_RAND_2 = "EBEjRWeJq83vESNFZ4mrze8="; + private static final String BASE64_RAND_3 = "ECEjRWeJq83vESNFZ4mrze8="; + + // BASE 64 of: "04" + SRES + "08" + KC + // SRES 1: 0ABCDEF0 KC 1: FEDCBA9876543210 + // SRES 2: 1ABCDEF1 KC 2: FEDCBA9876543211 + // SRES 3: 2ABCDEF2 KC 3: FEDCBA9876543212 + private static final String BASE64_RESP_1 = "BAq83vAI/ty6mHZUMhA="; + private static final String BASE64_RESP_2 = "BBq83vEI/ty6mHZUMhE="; + private static final String BASE64_RESP_3 = "BCq83vII/ty6mHZUMhI="; + + // MK: 202FC68A3335E8A939A33BC0A0EA8C435DC10060 + // K_encr: F63E152461391FF655C2632E35D076ED + // K_aut: 48E001C8DBA37120FD0465153A56F712 + private static final byte[] MSK = + hexStringToByteArray( + "9B1E2B6892BC113F6B6D0B5789DD8ADD" + + "B83BE2A84AA50FCAECD0003F92D8DA16" + + "4BF983C923695C309F1D7D68DB6992B0" + + "76EA8CE7129647A6F198F3A6AA8ADED9"); + private static final byte[] EMSK = hexStringToByteArray( + "88210b6724400313539c740f417076b0" + + "41da7e64658ec365bd2901a7cd7c2763" + + "dad1a0508b92a42fdf85ac53c6f7e756" + + "7f99b62bcaf467441b567f19b58d86ae"); + + // MK: ED275A588A4C1AEC15C55261DCCD851189E5C5FD + // K_encr: FED573CFA6FC81267C08E264F50A0BB9 + // K_aut: 277B5D6A68FE5156A387996510AC5D61 + private static final byte[] MSK_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "8023A49840433464DA1A4F2457FAB3D6" + + "B1A3CA6E5E1DB212FA1AEA17F0A5C933" + + "5541DE7448FE448AC3F09DC25BBAE1EE" + + "17DCE3D32099519CC75840F0E3FB612B"); + private static final byte[] EMSK_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "F7E213F0E8F14A21C87F9B5DFADA9A75" + + "A8EAF4AD718BF8C3ED6557BDB60E4671" + + "E6AE109448B2F32F9B984667AE6C2B3F" + + "2FDFE67F97AF4D4727A2EA37F06B7785"); + + private static final byte[] EAP_SIM_START_REQUEST = hexStringToByteArray( + "01850014120a0000" // EAP header + + "0f02000200010000" // AT_VERSION_LIST attribute + + "0d010000"); // AT_ANY_ID_REQ attribute + private static final byte[] EAP_SIM_START_RESPONSE = hexStringToByteArray( + "02850034120a0000" // EAP header + + "0705000037f3ddd3954c4831a5ee08c574844398" // AT_NONCE_MT attribute + + "10010001" // AT_SELECTED_VERSION attribute + + "0e05001031313233343536373839414243444546"); // AT_IDENTITY attribute + private static final byte[] EAP_SIM_CHALLENGE_REQUEST = hexStringToByteArray( + "01860050120b0000" // EAP header + + "010d0000" // AT_RAND attribute + + "0123456789abcdef1123456789abcdef" // Rand 1 + + "1123456789abcdef1123456789abcdef" // Rand 2 + + "2123456789abcdef1123456789abcdef" // Rand 3 + + "0b050000e4675b17fa7ba4d93db48d1af9ecbb01"); // AT_MAC attribute + private static final byte[] EAP_SIM_CHALLENGE_RESPONSE = + hexStringToByteArray( + "0286001c120b0000" // EAP header + + "0b050000e5df9cb1d935ea5f54d449a038bed061"); // AT_MAC attribute + + private static final byte[] EAP_SIM_START_REQUEST_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "01850010" // EAP-Request | ID | length in bytes + + "120a0000" // EAP-SIM | Start| 2B padding + + "0f02000200010000"); // AT_VERSION_LIST attribute + private static final byte[] EAP_SIM_START_RESPONSE_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "02850020" // EAP-Response | ID | length in bytes + + "120a0000" // EAP-SIM | Start | 2B padding + + "0705000037f3ddd3954c4831a5ee08c574844398" // AT_NONCE_MT attribute + + "10010001"); // AT_SELECTED_VERSION attribute + private static final byte[] EAP_SIM_CHALLENGE_REQUEST_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "01860050" // EAP-Request | ID | length in bytes + + "120b0000" // EAP-SIM | Challenge | 2B padding + + "010d0000" // AT_RAND attribute + + "0123456789abcdef1123456789abcdef" // Rand 1 + + "1123456789abcdef1123456789abcdef" // Rand 2 + + "2123456789abcdef1123456789abcdef" // Rand 3 + + "0b050000F2F8C10FCA946AAFE9555E2BD3693DF6"); // AT_MAC attribute + private static final byte[] EAP_SIM_CHALLENGE_RESPONSE_WITHOUT_IDENTITY_REQ = + hexStringToByteArray( + "0286001c" // EAP-Response | ID | length in bytes + + "120b0000" // EAP-SIM | Challenge | 2B padding + + "0b050000DAC3C1B7D9DBFBC923464A94F186E410"); // AT_MAC attribute + + private TelephonyManager mMockTelephonyManager; + + @Before + @Override + public void setUp() { + super.setUp(); + + mMockTelephonyManager = mock(TelephonyManager.class); + + mEapSessionConfig = + new EapSessionConfig.Builder() + .setEapIdentity(EAP_IDENTITY) + .setEapSimConfig(SUB_ID, APPTYPE_USIM) + .build(); + mEapAuthenticator = + new EapAuthenticator( + mTestLooper.getLooper(), + mMockCallback, + new EapStateMachine(mMockContext, mEapSessionConfig, mMockSecureRandom), + (runnable) -> runnable.run(), + AUTHENTICATOR_TIMEOUT_MILLIS); + } + + @Test + public void testEapSimEndToEnd() { + verifyEapSimStart(EAP_SIM_START_REQUEST, EAP_SIM_START_RESPONSE, true); + verifyEapSimChallenge(EAP_SIM_CHALLENGE_REQUEST, EAP_SIM_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void testEapSimEndToEndWithoutIdentityRequest() { + verifyEapSimStart( + EAP_SIM_START_REQUEST_WITHOUT_IDENTITY_REQ, + EAP_SIM_START_RESPONSE_WITHOUT_IDENTITY_REQ, + false); + verifyEapSimChallenge( + EAP_SIM_CHALLENGE_REQUEST_WITHOUT_IDENTITY_REQ, + EAP_SIM_CHALLENGE_RESPONSE_WITHOUT_IDENTITY_REQ); + verifyEapSuccess(MSK_WITHOUT_IDENTITY_REQ, EMSK_WITHOUT_IDENTITY_REQ); + } + + @Test + public void testEapSimUnsupportedType() { + verifyUnsupportedType(EAP_REQUEST_AKA_IDENTITY_PACKET, EAP_RESPONSE_NAK_PACKET); + + verifyEapSimStart(EAP_SIM_START_REQUEST, EAP_SIM_START_RESPONSE, true); + verifyEapSimChallenge(EAP_SIM_CHALLENGE_REQUEST, EAP_SIM_CHALLENGE_RESPONSE); + verifyEapSuccess(MSK, EMSK); + } + + @Test + public void verifyEapSimWithEapNotifications() { + verifyEapNotification(1); + verifyEapSimStart(EAP_SIM_START_REQUEST, EAP_SIM_START_RESPONSE, true); + + verifyEapNotification(2); + verifyEapSimChallenge(EAP_SIM_CHALLENGE_REQUEST, EAP_SIM_CHALLENGE_RESPONSE); + verifyEapNotification(3); + verifyEapSuccess(MSK, EMSK); + } + + private void verifyEapSimStart( + byte[] incomingEapPacket, byte[] outgoingEapPacket, boolean expectIdentityRequest) { + // EAP-SIM/Start request + when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE)) + .thenReturn(mMockTelephonyManager); + when(mMockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mMockTelephonyManager); + when(mMockTelephonyManager.getSubscriberId()).thenReturn(UNFORMATTED_IDENTITY); + doAnswer(invocation -> { + byte[] dst = invocation.getArgument(0); + System.arraycopy(NONCE, 0, dst, 0, NONCE.length); + return null; + }).when(mMockSecureRandom).nextBytes(eq(new byte[NONCE.length])); + + mEapAuthenticator.processEapMessage(incomingEapPacket); + mTestLooper.dispatchAll(); + verify(mMockContext).getSystemService(eq(Context.TELEPHONY_SERVICE)); + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + + if (expectIdentityRequest) { + verify(mMockTelephonyManager).getSubscriberId(); + } + + verify(mMockSecureRandom).nextBytes(any(byte[].class)); + + // verify EAP-SIM/Start response + verify(mMockCallback).onResponse(eq(outgoingEapPacket)); + verifyNoMoreInteractions( + mMockContext, + mMockTelephonyManager, + mMockSecureRandom, + mMockCallback); + } + + private void verifyEapSimChallenge(byte[] incomingEapPacket, byte[] outgoingEapPacket) { + // EAP-SIM/Challenge request + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE64_RAND_1)) + .thenReturn(BASE64_RESP_1); + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE64_RAND_2)) + .thenReturn(BASE64_RESP_2); + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE64_RAND_3)) + .thenReturn(BASE64_RESP_3); + + mEapAuthenticator.processEapMessage(incomingEapPacket); + mTestLooper.dispatchAll(); + + // verify EAP-SIM/Challenge response + verify(mMockTelephonyManager) + .getIccAuthentication( + eq(TelephonyManager.APPTYPE_USIM), + eq(TelephonyManager.AUTHTYPE_EAP_SIM), + eq(BASE64_RAND_1)); + verify(mMockTelephonyManager) + .getIccAuthentication( + eq(TelephonyManager.APPTYPE_USIM), + eq(TelephonyManager.AUTHTYPE_EAP_SIM), + eq(BASE64_RAND_2)); + verify(mMockTelephonyManager) + .getIccAuthentication( + eq(TelephonyManager.APPTYPE_USIM), + eq(TelephonyManager.AUTHTYPE_EAP_SIM), + eq(BASE64_RAND_3)); + verify(mMockCallback).onResponse(eq(outgoingEapPacket)); + verifyNoMoreInteractions( + mMockContext, + mMockTelephonyManager, + mMockSecureRandom, + mMockCallback); + } + + @Override + protected void verifyEapSuccess(byte[] msk, byte[] emsk) { + super.verifyEapSuccess(msk, emsk); + + verifyNoMoreInteractions(mMockTelephonyManager); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapSuccessTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapSuccessTest.java new file mode 100644 index 00000000..d7410540 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapSuccessTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.EapResult.EapSuccess; + +import org.junit.Test; + +public class EapSuccessTest { + public static final byte[] MSK = new byte[] {(byte) 1, (byte) 2, (byte) 3}; + public static final byte[] EMSK = new byte[] {(byte) 4, (byte) 5, (byte) 6}; + + @Test + public void testEapSuccessConstructor() { + EapSuccess eapSuccess = new EapSuccess(MSK, EMSK); + assertArrayEquals(MSK, eapSuccess.msk); + assertArrayEquals(EMSK, eapSuccess.emsk); + } + + @Test + public void testEapSuccessConstructorNullMsk() { + try { + new EapSuccess(null, EMSK); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testEapSuccessConstructorNullEmsk() { + try { + new EapSuccess(MSK, null); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapTestUtils.java b/tests/iketests/src/java/com/android/internal/net/eap/EapTestUtils.java new file mode 100644 index 00000000..de058d15 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/EapTestUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import android.net.eap.EapSessionConfig; + +import java.util.HashMap; + +/** + * EapTestUtils is a util class for providing test-values of EAP-related objects. + */ +public class EapTestUtils { + /** + * Creates and returns a dummy EapSessionConfig instance. + * + * @return a new, empty EapSessionConfig instance + */ + public static EapSessionConfig getDummyEapSessionConfig() { + return new EapSessionConfig(new HashMap<>(), new byte[0]); + } + + /** + * Creates and returns a dummy EapSessionConfig instance with the given EAP-Identity. + * + * @param eapIdentity byte-array representing the EAP-Identity of the client + * @return a new, empty EapSessionConfig instance with the given EAP-Identity + */ + public static EapSessionConfig getDummyEapSessionConfig(byte[] eapIdentity) { + return new EapSessionConfig(new HashMap<>(), eapIdentity); + } + + /** + * Creates and returns a dummy EapSessionConfig instance with EAP-SIM configured. + * + * @return a new EapSessionConfig with EAP-SIM configs set + */ + public static EapSessionConfig getDummyEapSimSessionConfig() { + return new EapSessionConfig.Builder().setEapSimConfig(0, APPTYPE_USIM).build(); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/crypto/Fips186_2PrfTest.java b/tests/iketests/src/java/com/android/internal/net/eap/crypto/Fips186_2PrfTest.java new file mode 100644 index 00000000..978f070f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/crypto/Fips186_2PrfTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.crypto; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; + +import com.android.internal.net.TestUtils; + +import org.junit.Before; +import org.junit.Test; + +/** + * The test vectors in this file come directly from NIST: + * + * <p>The original link to the test vectors was here: + * http://csrc.nist.gov/publications/fips/fips186-2/fips186-2-change1.pdf + * + * <p>But has since been removed. A cached copy was found here: + * https://web.archive.org/web/20041031202951/http://www.csrc.nist.gov/CryptoToolkit/dss/Examples- + * 1024bit.pdf + */ +public final class Fips186_2PrfTest { + private static final String SEED = "bd029bbe7f51960bcf9edb2b61f06f0feb5a38b6"; + private static final String EXPECTED_RESULT = + "2070b3223dba372fde1c0ffc7b2e3b498b2606143c6c18bacb0f6c55babb13788e20d737a3275116"; + + private Fips186_2Prf mFipsPrf; + + @Before + public void setUp() { + mFipsPrf = new Fips186_2Prf(); + } + + @Test + public void testFips186_2Prf_Invalid_Seed() throws Exception { + try { + mFipsPrf.getRandom(new byte[0], 40); + fail("Expected exception for invalid length seed"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testFips186_2Prf() throws Exception { + byte[] seed = TestUtils.hexStringToByteArray(SEED); + byte[] actual = mFipsPrf.getRandom(seed, 40); + + assertArrayEquals(TestUtils.hexStringToByteArray(EXPECTED_RESULT), actual); + } + + // TODO: (b/136177143) Add more test vectors +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/crypto/HmacSha256ByteSignerTest.java b/tests/iketests/src/java/com/android/internal/net/eap/crypto/HmacSha256ByteSignerTest.java new file mode 100644 index 00000000..355c1db1 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/crypto/HmacSha256ByteSignerTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.crypto; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; + +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Before; +import org.junit.Test; + +/** + * HmacSha256ByteSignerTest tests that {@link HmacSha256ByteSigner} correctly signs data using the + * HMAC-SHA-256 algorithm. + * + * <p>These test vectors are defined in RFC 4231#4. + * + * @see <a href="https://tools.ietf.org/html/rfc4231#section-4">Test Vectors</a> + */ +public class HmacSha256ByteSignerTest { + private static final String[] KEYS = { + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "4a656665", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "0102030405060708090a0b0c0d0e0f10111213141516171819", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaa" + }; + private static final String[] DATA = { + "4869205468657265", + "7768617420646f2079612077616e7420666f72206e6f7468696e673f", + "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + + "dddddddddddddddddddddddddddddddddddd", + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a" + + "65204b6579202d2048617368204b6579204669727374", + "5468697320697320612074657374207573696e672061206c6172676572207468" + + "616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074" + + "68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565" + + "647320746f20626520686173686564206265666f7265206265696e6720757365" + + "642062792074686520484d414320616c676f726974686d2e" + }; + private static final String[] MACS = { + "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", + "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", + "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", + "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", + "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", + "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2" + }; + + private HmacSha256ByteSigner mMacByteSigner; + + @Before + public void setUp() { + mMacByteSigner = HmacSha256ByteSigner.getInstance(); + } + + @Test + public void testSignBytes() { + for (int i = 0; i < KEYS.length; i++) { + byte[] key = hexStringToByteArray(KEYS[i]); + byte[] data = hexStringToByteArray(DATA[i]); + + byte[] expected = hexStringToByteArray(MACS[i]); + + assertArrayEquals(expected, mMacByteSigner.signBytes(key, data)); + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/crypto/ParityBitUtilTest.java b/tests/iketests/src/java/com/android/internal/net/eap/crypto/ParityBitUtilTest.java new file mode 100644 index 00000000..7ba024e5 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/crypto/ParityBitUtilTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.crypto; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +/** + * Inputs taken from RFC 2759 Section 9.3. + * + * @see <a href="https://tools.ietf.org/html/rfc2759#section-9.3">RFC 2759 Section 9.3, Examples of + * DES Key Generation</a> + */ +public class ParityBitUtilTest { + private static final byte[] INPUT_BYTES = {(byte) 0xFC, (byte) 0x0E, (byte) 0x7E, (byte) 0x37}; + private static final byte[] OUTPUT_BYTES = {(byte) 0xFD, (byte) 0x0E, (byte) 0x7F, (byte) 0x37}; + + private static final String[] RAW_KEYS = {"FC156AF7EDCD6C", "0EDDE3337D427F"}; + private static final long[] RAW_KEY_LONGS = {0xFC156AF7EDCD6CL, 0xEDDE3337D427FL}; + private static final String[] PARITY_CORRECTED_KEYS = {"FD0B5B5E7F6E34D9", "0E6E796737EA08FE"}; + + @Test + public void testGetByteWithParityBit() { + for (int i = 0; i < INPUT_BYTES.length; i++) { + assertEquals(OUTPUT_BYTES[i], ParityBitUtil.getByteWithParityBit(INPUT_BYTES[i])); + } + } + + @Test + public void testByteArrayToLong() { + for (int i = 0; i < RAW_KEYS.length; i++) { + byte[] rawKey = hexStringToByteArray(RAW_KEYS[i]); + + assertEquals(RAW_KEY_LONGS[i], ParityBitUtil.byteArrayToLong(rawKey)); + } + } + + @Test + public void testAddParityBits() { + for (int i = 0; i < RAW_KEYS.length; i++) { + byte[] rawKey = hexStringToByteArray(RAW_KEYS[i]); + byte[] parityCorrectedKey = hexStringToByteArray(PARITY_CORRECTED_KEYS[i]); + + assertArrayEquals(parityCorrectedKey, ParityBitUtil.addParityBits(rawKey)); + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/EapDataTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/EapDataTest.java new file mode 100644 index 00000000..9d4cc7ca --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/EapDataTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_SIM; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class EapDataTest { + private static final byte[] EXPECTED_EAP_DATA_BYTES = new byte[] { + EAP_TYPE_SIM, + (byte) 1, + (byte) 2, + (byte) 3 + }; + private static final byte[] EAP_TYPE_DATA = new byte[] {(byte) 1, (byte) 2, (byte) 3}; + private static final int UNSUPPORTED_EAP_TYPE = -1; + + @Test + public void testEapDataConstructor() { + new EapData(EAP_TYPE_SIM, EAP_TYPE_DATA); + } + + @Test + public void testEapDataConstructorNullEapData() { + try { + new EapData(EAP_TYPE_SIM, null); + fail("IllegalArgumentException expected for null eapTypeData"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testEapDataConstructorUnsupportedType() { + try { + new EapData(UNSUPPORTED_EAP_TYPE, EAP_TYPE_DATA); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testGetLength() { + EapData eapData = new EapData(EAP_TYPE_SIM, EAP_TYPE_DATA); + assertEquals(EAP_TYPE_DATA.length + 1, eapData.getLength()); + } + + @Test + public void testEquals() throws Exception { + EapData eapData = new EapData(EAP_TYPE_SIM, EAP_TYPE_DATA); + EapData eapDataCopy = new EapData(EAP_TYPE_SIM, EAP_TYPE_DATA); + assertEquals(eapData, eapDataCopy); + + EapData eapDataDifferent = new EapData(EAP_TYPE_SIM, new byte[0]); + assertNotEquals(eapData, eapDataDifferent); + } + + @Test + public void testHashCode() throws Exception { + EapData eapData = new EapData(EAP_TYPE_SIM, EAP_TYPE_DATA); + EapData eapDataCopy = new EapData(EAP_TYPE_SIM, EAP_TYPE_DATA); + assertNotEquals(0, eapData.hashCode()); + assertEquals(eapData.hashCode(), eapDataCopy.hashCode()); + } + + @Test + public void testEncodeToByteBuffer() { + EapData eapData = new EapData(EAP_TYPE_SIM, EAP_TYPE_DATA); + + ByteBuffer b = ByteBuffer.allocate(eapData.getLength()); + eapData.encodeToByteBuffer(b); + assertArrayEquals(EXPECTED_EAP_DATA_BYTES, b.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/EapMessageTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/EapMessageTest.java new file mode 100644 index 00000000..813a30f7 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/EapMessageTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_NAK; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_SIM; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_RESPONSE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_SIM_START_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_SIM_TYPE_DATA; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NAK_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SUCCESS_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.INCOMPLETE_HEADER_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.INVALID_CODE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.LONG_SUCCESS_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.REQUEST_MISSING_TYPE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.REQUEST_UNSUPPORTED_TYPE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SHORT_PACKET; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.EapInvalidPacketLengthException; +import com.android.internal.net.eap.exceptions.InvalidEapCodeException; +import com.android.internal.net.eap.exceptions.UnsupportedEapTypeException; + +import org.junit.Test; + +import java.util.Arrays; + +public class EapMessageTest { + @Test + public void testConstructorRequestWithoutType() throws Exception { + try { + new EapMessage(EAP_CODE_REQUEST, ID_INT, null); + fail("Expected EapInvalidPacketLengthException for an EAP-Request without Type value"); + } catch (EapInvalidPacketLengthException expected) { + } + } + + @Test + public void testDecode() throws Exception { + EapMessage result = EapMessage.decode(EAP_SUCCESS_PACKET); + assertEquals(EAP_CODE_SUCCESS, result.eapCode); + assertEquals(ID_INT, result.eapIdentifier); + assertEquals(EAP_SUCCESS_PACKET.length, result.eapLength); + assertNull(result.eapData); + + EapData expectedEapData = new EapData(EAP_TYPE_SIM, + hexStringToByteArray(EAP_REQUEST_SIM_TYPE_DATA)); + result = EapMessage.decode(EAP_REQUEST_SIM_START_PACKET); + assertEquals(EAP_CODE_REQUEST, result.eapCode); + assertEquals(ID_INT, result.eapIdentifier); + assertEquals(EAP_REQUEST_SIM_START_PACKET.length, result.eapLength); + assertEquals(expectedEapData, result.eapData); + } + + @Test + public void testDecodeInvalidCode() throws Exception { + try { + EapMessage.decode(INVALID_CODE_PACKET); + fail("Expected InvalidEapCodeException"); + } catch (InvalidEapCodeException expected) { + } + } + + @Test + public void testDecodeIncompleteHeader() throws Exception { + try { + EapMessage.decode(INCOMPLETE_HEADER_PACKET); + fail("Expected EapInvalidPacketLengthException"); + } catch (EapInvalidPacketLengthException expected) { + } + } + + @Test + public void testDecodeShortPacket() throws Exception { + try { + EapMessage.decode(SHORT_PACKET); + fail("Expected EapInvalidPacketLengthException"); + } catch (EapInvalidPacketLengthException expected) { + } + } + + @Test + public void testDecodeSuccessIncorrectLength() throws Exception { + try { + EapMessage.decode(LONG_SUCCESS_PACKET); + fail("Expected EapInvalidPacketLengthException"); + } catch (EapInvalidPacketLengthException expected) { + } + } + + @Test + public void testDecodeMissingTypeData() throws Exception { + try { + EapMessage.decode(REQUEST_MISSING_TYPE_PACKET); + fail("Expected EapInvalidPacketLengthException"); + } catch (EapInvalidPacketLengthException expected) { + } + } + + @Test + public void testDecodeUnsupportedEapType() throws Exception { + try { + EapMessage.decode(REQUEST_UNSUPPORTED_TYPE_PACKET); + fail("Expected UnsupportedEapDataTypeException"); + } catch (UnsupportedEapTypeException expected) { + assertEquals(ID_INT, expected.eapIdentifier); + } + } + + @Test + public void testEncode() throws Exception { + EapMessage eapMessage = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null); + byte[] actualPacket = eapMessage.encode(); + assertArrayEquals(EAP_SUCCESS_PACKET, actualPacket); + + EapData nakData = new EapData(EAP_NAK, new byte[] {EAP_TYPE_SIM}); + eapMessage = new EapMessage(EAP_CODE_RESPONSE, ID_INT, nakData); + actualPacket = eapMessage.encode(); + assertArrayEquals(EAP_RESPONSE_NAK_PACKET, actualPacket); + } + + @Test + public void testEncodeDecode() throws Exception { + EapMessage eapMessage = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null); + EapMessage result = EapMessage.decode(eapMessage.encode()); + + assertEquals(eapMessage.eapCode, result.eapCode); + assertEquals(eapMessage.eapIdentifier, result.eapIdentifier); + assertEquals(eapMessage.eapLength, result.eapLength); + assertEquals(eapMessage.eapData, result.eapData); + } + + @Test + public void testDecodeEncode() throws Exception { + byte[] result = EapMessage.decode(EAP_REQUEST_SIM_START_PACKET).encode(); + assertArrayEquals(EAP_REQUEST_SIM_START_PACKET, result); + } + + @Test + public void testGetNakResponse() { + EapResult nakResponse = EapMessage.getNakResponse(ID_INT, Arrays.asList(EAP_TYPE_SIM)); + + assertTrue(nakResponse instanceof EapResponse); + EapResponse eapResponse = (EapResponse) nakResponse; + assertArrayEquals(EAP_RESPONSE_NAK_PACKET, eapResponse.packet); + } + + @Test + public void testGetNotificationResponse() { + EapResult notificationResponse = EapMessage.getNotificationResponse(ID_INT); + + assertTrue(notificationResponse instanceof EapResponse); + EapResponse eapResponse = (EapResponse) notificationResponse; + assertArrayEquals(EAP_RESPONSE_NOTIFICATION_PACKET, eapResponse.packet); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/EapTestMessageDefinitions.java b/tests/iketests/src/java/com/android/internal/net/eap/message/EapTestMessageDefinitions.java new file mode 100644 index 00000000..20517583 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/EapTestMessageDefinitions.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_VERSION_LIST_DATA; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.IDENTITY_STRING; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_MT_STRING; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_2; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RES; + +/** + * EapTestMessageDefinitions provides byte[] encodings of commonly used EAP Messages. + * + * @see <a href="https://tools.ietf.org/html/rfc3748#section-4">RFC 3748, Extensible Authentication + * Protocol (EAP)</a> + */ +public class EapTestMessageDefinitions { + public static final String ID = "10"; + public static final int ID_INT = Integer.parseInt(ID, 16 /* radix */); + + // EAP-AKA Identity request + public static final String EAP_REQUEST_TYPE_DATA = "0500000D010000"; + public static final byte[] EAP_AKA_IDENTITY_REQUEST = + hexStringToByteArray(EAP_REQUEST_TYPE_DATA); + + // EAP-AKA/Identity request with no attributes + public static final byte[] EAP_REQUEST_AKA = hexStringToByteArray("01" + ID + "000817050000"); + public static final byte[] EAP_REQUEST_AKA_IDENTITY_PACKET = + hexStringToByteArray("01" + ID + "000A17" + EAP_REQUEST_TYPE_DATA); + public static final byte[] EAP_REQUEST_IDENTITY_PACKET = + hexStringToByteArray("01" + ID + "000501"); + + // EAP-Identity: hex for ASCII in "test@android.net" + public static final String EAP_IDENTITY_STRING = "7465737440616E64726F69642E6E6574"; + public static final byte[] EAP_IDENTITY = hexStringToByteArray(EAP_IDENTITY_STRING); + public static final byte[] EAP_RESPONSE_IDENTITY_PACKET = + hexStringToByteArray("02" + ID + "001501" + EAP_IDENTITY_STRING); + public static final byte[] EAP_RESPONSE_IDENTITY_DEFAULT_PACKET = + hexStringToByteArray("02" + ID + "000501"); + public static final byte[] EAP_REQUEST_NOTIFICATION_PACKET = + hexStringToByteArray("01" + ID + "000802AABBCC"); + public static final byte[] EAP_SUCCESS_PACKET = hexStringToByteArray("03" + ID + "0004"); + public static final byte[] EAP_FAILURE_PACKET = hexStringToByteArray("04" + ID + "0004"); + public static final byte[] EAP_SIM_CLIENT_ERROR_RESPONSE = + hexStringToByteArray("02" + ID + "000C120E000016010001"); + public static final byte[] EAP_SIM_CLIENT_ERROR_INSUFFICIENT_CHALLENGES = + hexStringToByteArray("02" + ID + "000C120E000016010002"); + public static final byte[] EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS = + hexStringToByteArray("02" + ID + "000C120E000016010000"); + public static final byte[] EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS = + hexStringToByteArray("02" + ID + "000C170E000016010000"); + + // EAP-SIM response containing SELECTED_VERSION (1) and IDENTITY attributes + public static final byte[] EAP_SIM_RESPONSE_PACKET = hexStringToByteArray( + "02" + ID + "0024120A0000100100010E060011" + IDENTITY_STRING + "000000"); + public static final byte[] EAP_SIM_RESPONSE_WITHOUT_IDENTITY = + hexStringToByteArray("02" + ID + "0020120A000007050000" + NONCE_MT_STRING + "10010001"); + public static final byte[] EAP_SIM_NOTIFICATION_RESPONSE = hexStringToByteArray( + "02" + ID + "0008120C0000"); + public static final byte[] EAP_AKA_NOTIFICATION_RESPONSE = + hexStringToByteArray("02" + ID + "0008170C0000"); + + // Body of EapData is the list of supported methods + public static final byte[] EAP_RESPONSE_NAK_PACKET = + hexStringToByteArray("02" + ID + "00060312"); + public static final byte[] EAP_RESPONSE_NOTIFICATION_PACKET = + hexStringToByteArray("02" + ID + "000502"); + public static final byte[] EAP_REQUEST_MD5_CHALLENGE = + hexStringToByteArray("01" + ID + "000504"); + public static final byte[] EAP_REQUEST_NAK_PACKET = + hexStringToByteArray("01" + ID + "000503"); + public static final String EAP_REQUEST_SIM_TYPE_DATA = "0A00000F02000200010000"; + public static final byte[] EAP_REQUEST_SIM_START_PACKET = + hexStringToByteArray("01" + ID + "001012" + EAP_REQUEST_SIM_TYPE_DATA); + + public static final byte[] REQUEST_UNSUPPORTED_TYPE_PACKET = + hexStringToByteArray("01" + ID + "0005FF"); + public static final byte[] REQUEST_MISSING_TYPE_PACKET = + hexStringToByteArray("01" + ID + "0004"); + public static final byte[] LONG_SUCCESS_PACKET = hexStringToByteArray("03" + ID + "000500"); + public static final byte[] SHORT_PACKET = hexStringToByteArray("01" + ID + "0005"); + public static final byte[] INCOMPLETE_HEADER_PACKET = hexStringToByteArray("03" + ID); + public static final byte[] INVALID_CODE_PACKET = hexStringToByteArray("F0" + ID + "0004"); + + // Attributes + public static final String SKIPPABLE_DATA = "112233445566"; + public static final byte[] SKIPPABLE_DATA_BYTES = hexStringToByteArray(SKIPPABLE_DATA); + public static final byte[] SKIPPABLE_INVALID_ATTRIBUTE = + hexStringToByteArray("FF02" + SKIPPABLE_DATA); + public static final byte[] NON_SKIPPABLE_INVALID_ATTRIBUTE = + hexStringToByteArray("7F010000"); + + // Type-Data + public static final byte[] EAP_SIM_START_SUBTYPE = + hexStringToByteArray("0A00000F02" + AT_VERSION_LIST_DATA + "0A010000"); + public static final byte[] INVALID_SUBTYPE = hexStringToByteArray("FF"); + public static final byte[] TYPE_DATA_INVALID_AT_RAND = + hexStringToByteArray("0A000001050000" + RAND_1); + public static final byte[] SHORT_TYPE_DATA = hexStringToByteArray("0A"); + public static final byte[] TYPE_DATA_INVALID_ATTRIBUTE = + hexStringToByteArray("0A00007F01"); + public static final byte[] EAP_SIM_START_DUPLICATE_ATTRIBUTES = + hexStringToByteArray("0A00000F02" + "0A010000" + "0A010000"); + + // RAND Challenge Results + public static final String SRES_1 = "11223344"; + public static final byte[] SRES_1_BYTES = hexStringToByteArray(SRES_1); + public static final String SRES_2 = "44332211"; + public static final byte[] SRES_2_BYTES = hexStringToByteArray(SRES_2); + public static final byte[] SRES_BYTES = hexStringToByteArray(SRES_1 + SRES_2); + public static final String KC_1 = "0102030405060708"; + public static final byte[] KC_1_BYTES = hexStringToByteArray(KC_1); + public static final String KC_2 = "0807060504030201"; + public static final byte[] KC_2_BYTES = hexStringToByteArray(KC_2); + public static final byte[] VALID_CHALLENGE_RESPONSE = + hexStringToByteArray("04" + SRES_1 + "08" + KC_1); + public static final byte[] CHALLENGE_RESPONSE_INVALID_SRES = hexStringToByteArray("03"); + public static final byte[] CHALLENGE_RESPONSE_INVALID_KC = + hexStringToByteArray("04" + SRES_1 + "04"); + + public static final String IMSI = "123456789012345"; + public static final String EAP_SIM_IDENTITY = "1" + IMSI; + public static final byte[] EAP_SIM_IDENTITY_BYTES = hexStringToByteArray(EAP_SIM_IDENTITY); + + // ASCII hex for "0" + IMSI (EAP-AKA identity format) + public static final String EAP_AKA_IDENTITY_BYTES = "30313233343536373839303132333435"; + + // Master Key generation + public static final String MK_STRING = "0123456789ABCDEF0123456789ABCDEF01234567"; + public static final byte[] MK = hexStringToByteArray(MK_STRING); + public static final String K_ENCR_STRING = "000102030405060708090A0B0C0D0E0F"; + public static final byte[] K_ENCR = hexStringToByteArray(K_ENCR_STRING); + public static final String K_AUT_STRING = "0F0E0D0C0B0A09080706050403020100"; + public static final byte[] K_AUT = hexStringToByteArray(K_AUT_STRING); + public static final String MSK_STRING = + "00112233445566778899AABBCCDDEEFF" + + "00112233445566778899AABBCCDDEEFF" + + "00112233445566778899AABBCCDDEEFF" + + "00112233445566778899AABBCCDDEEFF"; + public static final byte[] MSK = hexStringToByteArray(MSK_STRING); + public static final String EMSK_STRING = + "FFEEDDCCBBAA99887766554433221100" + + "FFEEDDCCBBAA99887766554433221100" + + "FFEEDDCCBBAA99887766554433221100" + + "FFEEDDCCBBAA99887766554433221100"; + public static final byte[] EMSK = hexStringToByteArray(EMSK_STRING); + + // MAC computation + public static final String ORIGINAL_MAC_STRING = "112233445566778899AABBCCDDEEFF11"; + public static final byte[] ORIGINAL_MAC = hexStringToByteArray(ORIGINAL_MAC_STRING); + public static final String COMPUTED_MAC_STRING = "FFEEDDCCBBAA998877665544332211FF"; + public static final byte[] COMPUTED_MAC = hexStringToByteArray(COMPUTED_MAC_STRING); + public static final String EAP_SIM_CHALLENGE_REQUEST_STRING = + "01" + ID + "0040" // EAP-Request | ID | length in bytes + + "120b0000" // EAP-SIM | Challenge | 2B padding + + "01090000" + RAND_1 + RAND_2 // EAP-SIM AT_RAND attribute + + "0B05000000000000000000000000000000000000"; // AT_MAC attribute with no MAC + public static final byte[] MAC_INPUT = + hexStringToByteArray(EAP_SIM_CHALLENGE_REQUEST_STRING + NONCE_MT_STRING); + + // Response Message with MAC + public static final String EAP_SIM_CHALLENGE_RESPONSE_EMPTY_MAC = + "02" + ID + "001C" // EAP-Response | ID | length in bytes + + "120b0000" // EAP-SIM | Challenge | 2B padding + + "0B05000000000000000000000000000000000000"; // AT_MAC attribute with no MAC + public static final byte[] EAP_SIM_CHALLENGE_RESPONSE_MAC_INPUT = + hexStringToByteArray(EAP_SIM_CHALLENGE_RESPONSE_EMPTY_MAC + SRES_1 + SRES_2); + public static final byte[] EAP_SIM_CHALLENGE_RESPONSE_WITH_MAC = hexStringToByteArray( + "02" + ID + "001C" // EAP-Response | ID | length in bytes + + "120b0000" // EAP-SIM | Challenge | 2B padding + + "0B050000" + COMPUTED_MAC_STRING); // AT_MAC attribute + public static final byte[] EAP_SIM_NOTIFICATION_REQUEST_WITH_EMPTY_MAC = hexStringToByteArray( + "01" + ID + "0020" // EAP-Request | ID | length in bytes + + "120C0000" // EAP-SIM | Notification | 2B padding + + "0C010000" // AT_NOTIFICATION attribute + + "0B05000000000000000000000000000000000000"); // empty AT_MAC attribute + public static final byte[] EAP_SIM_NOTIFICATION_RESPONSE_WITH_EMPTY_MAC = hexStringToByteArray( + "02" + ID + "001C" // EAP-Response | ID | length in bytes + + "120C0000" // EAP-SIM | Notification | 2B padding + + "0B05000000000000000000000000000000000000"); // empty AT_MAC attribute + public static final byte[] EAP_SIM_NOTIFICATION_RESPONSE_WITH_MAC = hexStringToByteArray( + "02" + ID + "001C" // EAP-Response | ID | length in bytes + + "120C0000" // EAP-SIM | Notification | 2B padding + + "0B050000" + COMPUTED_MAC_STRING); // AT_MAC attribute + + public static final byte[] EAP_AKA_IDENTITY_RESPONSE = + hexStringToByteArray("02" + ID + "001C" // EAP-Response | ID | length in bytes + + "17050000" // EAP-AKA | Identity | 2B padding + + "0E050010" + EAP_AKA_IDENTITY_BYTES); // AT_IDENTITY ("0" + IMSI) + + // Base64 of: FF0111 + public static final String EAP_AKA_UICC_RESP_INVALID_TAG = "/wER"; + + // Base64 of: DC0E112233445566778899AABBCCDDEE + public static final String EAP_AKA_UICC_RESP_SYNCHRONIZE_BASE_64 = "3A4RIjNEVWZ3iJmqu8zd7g=="; + + public static final byte[] EAP_AKA_SYNCHRONIZATION_FAILURE = + hexStringToByteArray("02" + ID + "0018" // EAP-Response | ID | length in bytes + + "17040000" // EAP-SIM | Synchronization-Failure | 2B padding + + "0404112233445566778899AABBCCDDEE"); // AT_AUTS attribute + + public static final String IK = "00112233445566778899AABBCCDDEEFF"; + public static final byte[] IK_BYTES = hexStringToByteArray(IK); + public static final String CK = "FFEEDDCCBBAA99887766554433221100"; + public static final byte[] CK_BYTES = hexStringToByteArray(CK); + + // Base-64 of: 'DB05' + RES_BYTES + '10' + IK + '10' + CK + // 'DB0511223344551000112233445566778899AABBCCDDEEFF10FFEEDDCCBBAA99887766554433221100' + public static final String EAP_AKA_UICC_RESP_SUCCESS_BASE_64 = + "2wURIjNEVRAAESIzRFVmd4iZqrvM3e7/EP/u3cy7qpmId2ZVRDMiEQA="; + + public static final byte[] EAP_AKA_AUTHENTICATION_REJECT = + hexStringToByteArray("02" + ID + "000817020000"); + public static final String EAP_AKA_CHALLENGE_RESPONSE_MAC = "C70366512D9C5EBA8E3484509A25DCE4"; + public static final byte[] EAP_AKA_CHALLENGE_RESPONSE_MAC_BYTES = + hexStringToByteArray(EAP_AKA_CHALLENGE_RESPONSE_MAC); + public static final byte[] EAP_AKA_CHALLENGE_RESPONSE_TYPE_DATA = + hexStringToByteArray( + "01000003030028" + RES + "0000000B050000" + EAP_AKA_CHALLENGE_RESPONSE_MAC); + public static final byte[] EAP_AKA_CHALLENGE_RESPONSE = + hexStringToByteArray( + "02100028" // EAP-Response | ID | length in bytes + + "17010000" // EAP-AKA | Challenge | 2B padding + + "03030028" + RES + "000000" // AT_RES attribute + + "0B050000" + EAP_AKA_CHALLENGE_RESPONSE_MAC); // AT_MAC attribute + + public static final byte[] EAP_SUCCESS = hexStringToByteArray("03860004"); + + public static final byte[] EAP_REQUEST_MSCHAP_V2 = + hexStringToByteArray("01" + ID + "00061A01"); + + // MSCHAPv2 Test vectors taken from RFC 2759#9.2 and RFC 3079#3.5.3 + public static final String MSCHAP_V2_USERNAME = "User"; + public static final String MSCHAP_V2_USERNAME_HEX = "55736572"; + public static final byte[] MSCHAP_V2_USERNAME_ASCII_BYTES = + hexStringToByteArray(MSCHAP_V2_USERNAME_HEX); + public static final String MSCHAP_V2_PASSWORD = "clientPass"; + public static final byte[] MSCHAP_V2_PASSWORD_UTF_BYTES = + hexStringToByteArray("63006C00690065006E0074005000610073007300"); + public static final String MSCHAP_V2_AUTHENTICATOR_CHALLENGE_STRING = + "5B5D7C7D7B3F2F3E3C2C602132262628"; + public static final byte[] MSCHAP_V2_AUTHENTICATOR_CHALLENGE = + hexStringToByteArray(MSCHAP_V2_AUTHENTICATOR_CHALLENGE_STRING); + public static final String MSCHAP_V2_PEER_CHALLENGE_STRING = "21402324255E262A28295F2B3A337C7E"; + public static final byte[] MSCHAP_V2_PEER_CHALLENGE = + hexStringToByteArray(MSCHAP_V2_PEER_CHALLENGE_STRING); + public static final byte[] MSCHAP_V2_CHALLENGE = hexStringToByteArray("D02E4386BCE91226"); + public static final byte[] MSCHAP_V2_PASSWORD_HASH = + hexStringToByteArray("44EBBA8D5312B8D611474411F56989AE"); + public static final byte[] MSCHAP_V2_PASSWORD_HASH_HASH = + hexStringToByteArray("41C00C584BD2D91C4017A2A12FA59F3F"); + public static final String MSCHAP_V2_NT_RESPONSE_STRING = + "82309ECD8D708B5EA08FAA3981CD83544233114A3D85D6DF"; + public static final byte[] MSCHAP_V2_NT_RESPONSE = + hexStringToByteArray(MSCHAP_V2_NT_RESPONSE_STRING); + public static final byte[] MSCHAP_V2_AUTHENTICATOR_RESPONSE = + hexStringToByteArray("407A5589115FD0D6209F510FE9C04566932CDA56"); + public static final byte[] MSCHAP_V2_MASTER_KEY = + hexStringToByteArray("FDECE3717A8C838CB388E527AE3CDD31"); + + // generated based on RFC 3079#3.5.3 params + public static final String SEND_KEY = "D5F0E9521E3EA9589645E86051C82226"; + public static final byte[] MSCHAP_V2_SEND_START_KEY = hexStringToByteArray(SEND_KEY); + + // This value is labeled 'send key' in RFC 3079#3.5.3. However, it's used as 'receive key' here, + // because send and receive keys are swapped for peers relative to authenticators. + public static final String RECEIVE_KEY = "8B7CDC149B993A1BA118CB153F56DCCB"; + public static final byte[] MSCHAP_V2_RECEIVE_START_KEY = hexStringToByteArray(RECEIVE_KEY); + + // MSK: MSCHAP_V2_SEND_START_KEY + MSCHAP_V2_RECEIVE_START_KEY + public static final byte[] MSCHAP_V2_MSK = hexStringToByteArray(SEND_KEY + RECEIVE_KEY); + + public static final String MSCHAP_V2_ID = "42"; + public static final int MSCHAP_V2_ID_INT = Integer.parseInt(MSCHAP_V2_ID, 16 /* radix */); + public static final byte[] EAP_MSCHAP_V2_CHALLENGE_RESPONSE = + hexStringToByteArray("02" + ID + "003F" // EAP-Response | ID | length in bytes + + "1A02" + MSCHAP_V2_ID // EAP-MSCHAPv2 | Response | MSCHAPv2 ID + + "003A31" // MS length | Value Size (0x31) + + MSCHAP_V2_PEER_CHALLENGE_STRING + + "0000000000000000" // 8B (reserved) + + MSCHAP_V2_NT_RESPONSE_STRING + + "00" // Flags (always 0) + + MSCHAP_V2_USERNAME_HEX); + + public static final byte[] EAP_MSCHAP_V2_SUCCESS_RESPONSE = + hexStringToByteArray("02" + ID + "0006" // EAP-Response | ID | length in bytes + + "1A03"); // EAP-MSCHAPv2 | Success + + public static final byte[] INVALID_AUTHENTICATOR_RESPONSE = new byte[20]; + + public static final byte[] EAP_MSCHAP_V2_FAILURE_RESPONSE = + hexStringToByteArray("02" + ID + "0006" // EAP-Response | ID | length in bytes + + "1A04"); // EAP-MSCHAPv2 | Failure + + public static final byte[] EAP_AKA_PRIME_REQUEST = + hexStringToByteArray("01" + ID + "000832050000"); + public static final byte[] EAP_AKA_PRIME_CLIENT_ERROR_UNABLE_TO_PROCESS = + hexStringToByteArray("02" + ID + "000C320E000016010000"); + public static final String EAP_AKA_PRIME_IDENTITY = "36313233343536373839303132333435"; + public static final byte[] EAP_AKA_PRIME_IDENTITY_BYTES = + hexStringToByteArray(EAP_AKA_PRIME_IDENTITY); + public static final byte[] EAP_AKA_PRIME_IDENTITY_RESPONSE = + hexStringToByteArray( + "02" + ID + "001C" // EAP-Response | ID | length in bytes + + "32050000" // EAP-AKA' | Identity | 2B padding + + "0E050010" + EAP_AKA_PRIME_IDENTITY); // AT_IDENTITY ("6" + IMSI) + public static final byte[] EAP_AKA_PRIME_AUTHENTICATION_REJECT = + hexStringToByteArray( + "02" + ID + "0008" // EAP-Response | ID | length in bytes + + "32020000"); // EAP-AKA' | Authentication Reject | 2B padding +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2ChallengeRequestTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2ChallengeRequestTest.java new file mode 100644 index 00000000..45a86735 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2ChallengeRequestTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.mschapv2; + +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.CHALLENGE_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.CHALLENGE_REQUEST_LONG_MS_LENGTH; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.CHALLENGE_REQUEST_SHORT_CHALLENGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.CHALLENGE_REQUEST_SHORT_MS_LENGTH; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.CHALLENGE_REQUEST_WRONG_OP_CODE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_CHALLENGE_REQUEST; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.ID_INT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SERVER_NAME_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_CHALLENGE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2ChallengeRequest; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder.DecodeResult; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.BufferUnderflowException; + +public class EapMsChapV2ChallengeRequestTest { + private static final String TAG = EapMsChapV2ChallengeRequestTest.class.getSimpleName(); + + private EapMsChapV2TypeDataDecoder mTypeDataDecoder; + + @Before + public void setUp() { + mTypeDataDecoder = new EapMsChapV2TypeDataDecoder(); + } + + @Test + public void testDecodeChallengeRequest() { + DecodeResult<EapMsChapV2ChallengeRequest> result = + mTypeDataDecoder.decodeChallengeRequest(TAG, EAP_MSCHAP_V2_CHALLENGE_REQUEST); + assertTrue(result.isSuccessfulDecode()); + EapMsChapV2ChallengeRequest challengeRequest = result.eapTypeData; + + assertEquals(EAP_MSCHAP_V2_CHALLENGE, challengeRequest.opCode); + assertEquals(ID_INT, challengeRequest.msChapV2Id); + assertEquals(EAP_MSCHAP_V2_CHALLENGE_REQUEST.length, challengeRequest.msLength); + assertArrayEquals(CHALLENGE_BYTES, challengeRequest.challenge); + assertArrayEquals(SERVER_NAME_BYTES, challengeRequest.name); + } + + @Test + public void testDecodeChallengeRequestWrongOpCode() { + DecodeResult<EapMsChapV2ChallengeRequest> result = + mTypeDataDecoder.decodeChallengeRequest(TAG, CHALLENGE_REQUEST_WRONG_OP_CODE); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testDecodeChallengeRequestShortChallenge() { + DecodeResult<EapMsChapV2ChallengeRequest> result = + mTypeDataDecoder.decodeChallengeRequest(TAG, CHALLENGE_REQUEST_SHORT_CHALLENGE); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testDecodeChallengeRequestShortMsLength() { + DecodeResult<EapMsChapV2ChallengeRequest> result = + mTypeDataDecoder.decodeChallengeRequest(TAG, CHALLENGE_REQUEST_SHORT_MS_LENGTH); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testDecodeChallengeRequestLongMsLength() { + DecodeResult<EapMsChapV2ChallengeRequest> result = + mTypeDataDecoder.decodeChallengeRequest(TAG, CHALLENGE_REQUEST_LONG_MS_LENGTH); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof BufferUnderflowException); + } + + @Test + public void testEncodeChallengeRequestFails() throws Exception { + EapMsChapV2ChallengeRequest challengeRequest = + new EapMsChapV2ChallengeRequest( + ID_INT, + EAP_MSCHAP_V2_CHALLENGE_REQUEST.length, + CHALLENGE_BYTES, + SERVER_NAME_BYTES); + try { + challengeRequest.encode(); + fail("Expected UnsupportedOperationException for encoding a Challenge Request"); + } catch (UnsupportedOperationException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2ChallengeResponseTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2ChallengeResponseTest.java new file mode 100644 index 00000000..3e243a98 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2ChallengeResponseTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.mschapv2; + +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_CHALLENGE_RESPONSE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.ID_INT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.NT_RESPONSE_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.PEER_CHALLENGE_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.PEER_NAME_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SHORT_CHALLENGE_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SHORT_NT_RESPONSE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_RESPONSE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2ChallengeResponse; + +import org.junit.Test; + +public class EapMsChapV2ChallengeResponseTest { + private static final int FLAGS = 0; + private static final int INVALID_FLAGS = 0xFF; + + @Test + public void testConstructor() throws Exception { + EapMsChapV2ChallengeResponse challengeResponse = + new EapMsChapV2ChallengeResponse( + ID_INT, PEER_CHALLENGE_BYTES, NT_RESPONSE_BYTES, FLAGS, PEER_NAME_BYTES); + assertEquals(EAP_MSCHAP_V2_RESPONSE, challengeResponse.opCode); + assertEquals(ID_INT, challengeResponse.msChapV2Id); + assertEquals(EAP_MSCHAP_V2_CHALLENGE_RESPONSE.length, challengeResponse.msLength); + assertArrayEquals(PEER_CHALLENGE_BYTES, challengeResponse.peerChallenge); + assertArrayEquals(NT_RESPONSE_BYTES, challengeResponse.ntResponse); + assertEquals(FLAGS, challengeResponse.flags); + assertArrayEquals(PEER_NAME_BYTES, challengeResponse.name); + } + + @Test + public void testConstructorInvalidChallenge() { + try { + EapMsChapV2ChallengeResponse challengeResponse = + new EapMsChapV2ChallengeResponse( + ID_INT, + SHORT_CHALLENGE_BYTES, + NT_RESPONSE_BYTES, + FLAGS, + PEER_NAME_BYTES); + fail("Expected EapMsChapV2ParsingException for invalid Peer Challenge length"); + } catch (EapMsChapV2ParsingException expected) { + } + } + + @Test + public void testConstructorInvalidNtResponse() { + try { + EapMsChapV2ChallengeResponse challengeResponse = + new EapMsChapV2ChallengeResponse( + ID_INT, + PEER_CHALLENGE_BYTES, + SHORT_NT_RESPONSE, + FLAGS, + PEER_NAME_BYTES); + fail("Expected EapMsChapV2ParsingException for invalid NT-Response length"); + } catch (EapMsChapV2ParsingException expected) { + } + } + + @Test + public void testConstructorInvalidFlags() { + try { + EapMsChapV2ChallengeResponse challengeResponse = + new EapMsChapV2ChallengeResponse( + ID_INT, + PEER_CHALLENGE_BYTES, + NT_RESPONSE_BYTES, + INVALID_FLAGS, + PEER_NAME_BYTES); + fail("Expected EapMsChapV2ParsingException for non-zero Flags value"); + } catch (EapMsChapV2ParsingException expected) { + } + } + + @Test + public void testEncode() throws Exception { + EapMsChapV2ChallengeResponse challengeResponse = + new EapMsChapV2ChallengeResponse( + ID_INT, PEER_CHALLENGE_BYTES, NT_RESPONSE_BYTES, FLAGS, PEER_NAME_BYTES); + byte[] encodedChallengeResponse = challengeResponse.encode(); + + assertArrayEquals(EAP_MSCHAP_V2_CHALLENGE_RESPONSE, encodedChallengeResponse); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2FailureRequestTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2FailureRequestTest.java new file mode 100644 index 00000000..ead8022a --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2FailureRequestTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.mschapv2; + +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.CHALLENGE_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_FAILURE_REQUEST; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_FAILURE_REQUEST_MISSING_MESSAGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_FAILURE_REQUEST_MISSING_MESSAGE_WITH_SPACE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.ERROR_CODE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.FAILURE_REQUEST_EXTRA_ATTRIBUTE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.FAILURE_REQUEST_INVALID_CHALLENGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.FAILURE_REQUEST_INVALID_ERROR_CODE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.FAILURE_REQUEST_INVALID_PASSWORD_CHANGE_PROTOCOL; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.FAILURE_REQUEST_SHORT_CHALLENGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.ID_INT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.MESSAGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.MESSAGE_MISSING_TEXT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.PASSWORD_CHANGE_PROTOCOL; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.RETRY_BIT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_FAILURE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2FailureRequest; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder.DecodeResult; + +import org.junit.Before; +import org.junit.Test; + +public class EapMsChapV2FailureRequestTest { + private static final String TAG = EapMsChapV2FailureRequestTest.class.getSimpleName(); + + private EapMsChapV2TypeDataDecoder mTypeDataDecoder; + + @Before + public void setUp() { + mTypeDataDecoder = new EapMsChapV2TypeDataDecoder(); + } + + @Test + public void testDecodeFailureRequest() { + DecodeResult<EapMsChapV2FailureRequest> result = + mTypeDataDecoder.decodeFailureRequest(TAG, EAP_MSCHAP_V2_FAILURE_REQUEST); + assertTrue(result.isSuccessfulDecode()); + + EapMsChapV2FailureRequest failureRequest = result.eapTypeData; + assertEquals(EAP_MSCHAP_V2_FAILURE, failureRequest.opCode); + assertEquals(ID_INT, failureRequest.msChapV2Id); + assertEquals(EAP_MSCHAP_V2_FAILURE_REQUEST.length, failureRequest.msLength); + assertEquals(ERROR_CODE, failureRequest.errorCode); + assertEquals(RETRY_BIT, failureRequest.isRetryable); + assertArrayEquals(CHALLENGE_BYTES, failureRequest.challenge); + assertEquals(PASSWORD_CHANGE_PROTOCOL, failureRequest.passwordChangeProtocol); + assertEquals(MESSAGE, failureRequest.message); + } + + @Test + public void testDecodeFailureRequestMissingMessage() { + DecodeResult<EapMsChapV2FailureRequest> result = + mTypeDataDecoder.decodeFailureRequest( + TAG, EAP_MSCHAP_V2_FAILURE_REQUEST_MISSING_MESSAGE); + assertTrue(result.isSuccessfulDecode()); + + EapMsChapV2FailureRequest failureRequest = result.eapTypeData; + assertEquals(EAP_MSCHAP_V2_FAILURE, failureRequest.opCode); + assertEquals(ID_INT, failureRequest.msChapV2Id); + assertEquals(EAP_MSCHAP_V2_FAILURE_REQUEST_MISSING_MESSAGE.length, failureRequest.msLength); + assertEquals(ERROR_CODE, failureRequest.errorCode); + assertEquals(RETRY_BIT, failureRequest.isRetryable); + assertArrayEquals(CHALLENGE_BYTES, failureRequest.challenge); + assertEquals(PASSWORD_CHANGE_PROTOCOL, failureRequest.passwordChangeProtocol); + assertEquals(MESSAGE_MISSING_TEXT, failureRequest.message); + } + + @Test + public void testDecodeFailureRequestMissingMessageWithSpace() { + DecodeResult<EapMsChapV2FailureRequest> result = + mTypeDataDecoder.decodeFailureRequest( + TAG, EAP_MSCHAP_V2_FAILURE_REQUEST_MISSING_MESSAGE_WITH_SPACE); + assertTrue(result.isSuccessfulDecode()); + + EapMsChapV2FailureRequest failureRequest = result.eapTypeData; + assertEquals(EAP_MSCHAP_V2_FAILURE, failureRequest.opCode); + assertEquals(ID_INT, failureRequest.msChapV2Id); + assertEquals( + EAP_MSCHAP_V2_FAILURE_REQUEST_MISSING_MESSAGE_WITH_SPACE.length, + failureRequest.msLength); + assertEquals(ERROR_CODE, failureRequest.errorCode); + assertEquals(RETRY_BIT, failureRequest.isRetryable); + assertArrayEquals(CHALLENGE_BYTES, failureRequest.challenge); + assertEquals(PASSWORD_CHANGE_PROTOCOL, failureRequest.passwordChangeProtocol); + assertEquals(MESSAGE_MISSING_TEXT, failureRequest.message); + } + + @Test + public void testDecodeFailureRequestInvalidErrorCode() { + DecodeResult<EapMsChapV2FailureRequest> result = + mTypeDataDecoder.decodeFailureRequest(TAG, FAILURE_REQUEST_INVALID_ERROR_CODE); + assertTrue(!result.isSuccessfulDecode()); + assertTrue(result.eapError.cause instanceof NumberFormatException); + } + + @Test + public void testDecodeFailureRequestInvalidChallenge() { + DecodeResult<EapMsChapV2FailureRequest> result = + mTypeDataDecoder.decodeFailureRequest(TAG, FAILURE_REQUEST_INVALID_CHALLENGE); + assertTrue(!result.isSuccessfulDecode()); + assertTrue(result.eapError.cause instanceof NumberFormatException); + } + + @Test + public void testDecodeFailureRequestShortChallenge() { + DecodeResult<EapMsChapV2FailureRequest> result = + mTypeDataDecoder.decodeFailureRequest(TAG, FAILURE_REQUEST_SHORT_CHALLENGE); + assertTrue(!result.isSuccessfulDecode()); + assertTrue(result.eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testDecodeFailureRequestInvalidPasswordChangeProtocol() { + DecodeResult<EapMsChapV2FailureRequest> result = + mTypeDataDecoder.decodeFailureRequest( + TAG, FAILURE_REQUEST_INVALID_PASSWORD_CHANGE_PROTOCOL); + assertTrue(!result.isSuccessfulDecode()); + assertTrue(result.eapError.cause instanceof NumberFormatException); + } + + @Test + public void testDecodeFailureExtraAttribute() { + DecodeResult<EapMsChapV2FailureRequest> result = + mTypeDataDecoder.decodeFailureRequest(TAG, FAILURE_REQUEST_EXTRA_ATTRIBUTE); + assertTrue(!result.isSuccessfulDecode()); + assertTrue(result.eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testEncodeFails() throws Exception { + EapMsChapV2FailureRequest failureRequest = + new EapMsChapV2FailureRequest( + ID_INT, + EAP_MSCHAP_V2_FAILURE_REQUEST.length, + ERROR_CODE, + RETRY_BIT, + CHALLENGE_BYTES, + PASSWORD_CHANGE_PROTOCOL, + MESSAGE); + try { + failureRequest.encode(); + fail("Expected UnsupportedOperationException for encoding a request"); + } catch (UnsupportedOperationException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2FailureResponseTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2FailureResponseTest.java new file mode 100644 index 00000000..261ddb28 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2FailureResponseTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.mschapv2; + +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_FAILURE_RESPONSE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_FAILURE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2FailureResponse; + +import org.junit.Test; + +public class EapMsChapV2FailureResponseTest { + @Test + public void testGetEapMsChapV2FailureResponse() { + EapMsChapV2FailureResponse failureResponse = + EapMsChapV2FailureResponse.getEapMsChapV2FailureResponse(); + assertEquals(EAP_MSCHAP_V2_FAILURE, failureResponse.opCode); + } + + @Test + public void testEncode() { + EapMsChapV2FailureResponse failureResponse = + EapMsChapV2FailureResponse.getEapMsChapV2FailureResponse(); + assertArrayEquals(EAP_MSCHAP_V2_FAILURE_RESPONSE, failureResponse.encode()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2PacketDefinitions.java b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2PacketDefinitions.java new file mode 100644 index 00000000..e70c2a2f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2PacketDefinitions.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.mschapv2; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; + +public class EapMsChapV2PacketDefinitions { + public static final String ID = "1F"; + public static final int ID_INT = Integer.parseInt(ID, 16 /* radix */); + + public static final String CHALLENGE = "000102030405060708090A0B0C0D0E0F"; + public static final byte[] CHALLENGE_BYTES = hexStringToByteArray(CHALLENGE); + + // server name is the ASCII hex for "authenticator@android.net" + public static final String SERVER_NAME = "61757468656E74696361746F7240616E64726F69642E6E6574"; + public static final byte[] SERVER_NAME_BYTES = hexStringToByteArray(SERVER_NAME); + public static final byte[] EAP_MSCHAP_V2_CHALLENGE_REQUEST = + hexStringToByteArray("01" + ID + "002E10" + CHALLENGE + SERVER_NAME); + + public static final byte[] CHALLENGE_REQUEST_WRONG_OP_CODE = hexStringToByteArray("02"); + public static final String SHORT_CHALLENGE = "001122334455"; + public static final byte[] SHORT_CHALLENGE_BYTES = hexStringToByteArray(SHORT_CHALLENGE); + public static final byte[] CHALLENGE_REQUEST_SHORT_CHALLENGE = + hexStringToByteArray("01" + ID + "002406" + SHORT_CHALLENGE + SERVER_NAME); + public static final byte[] CHALLENGE_REQUEST_SHORT_MS_LENGTH = + hexStringToByteArray("01" + ID + "000110" + CHALLENGE + SERVER_NAME); + public static final byte[] CHALLENGE_REQUEST_LONG_MS_LENGTH = + hexStringToByteArray("01" + ID + "00FF10" + CHALLENGE + SERVER_NAME); + + public static final String PEER_CHALLENGE = "00112233445566778899AABBCCDDEEFF"; + public static final byte[] PEER_CHALLENGE_BYTES = hexStringToByteArray(PEER_CHALLENGE); + public static final String NT_RESPONSE = "FFEEDDCCBBAA998877665544332211000011223344556677"; + public static final byte[] NT_RESPONSE_BYTES = hexStringToByteArray(NT_RESPONSE); + + // peer name is the ASCII hex for "peer@android.net" + public static final String PEER_NAME = "7065657240616E64726F69642E6E6574"; + public static final byte[] PEER_NAME_BYTES = hexStringToByteArray(PEER_NAME); + public static final byte[] EAP_MSCHAP_V2_CHALLENGE_RESPONSE = + hexStringToByteArray( + "02" + + ID + + "004631" + + PEER_CHALLENGE + + "0000000000000000" + + NT_RESPONSE + + "00" + + PEER_NAME); + + public static final byte[] SHORT_NT_RESPONSE = hexStringToByteArray("0011223344"); + + public static final String AUTH_STRING = "00112233445566778899AABBCCDDEEFF00112233"; + + // ASCII hex for AUTH_STRING + public static final String AUTH_STRING_HEX = + "30303131323233333434353536363737383839394141424243434444454546463030313132323333"; + public static final byte[] AUTH_BYTES = hexStringToByteArray(AUTH_STRING); + + // hex("S=") + AUTH_STRING_HEX + public static final String FORMATTED_AUTH_STRING = "533D" + AUTH_STRING_HEX; + + public static final String SPACE_HEX = "20"; + + // ASCII hex for: "test Android 1234" + public static final String MESSAGE = "test Android 1234"; + public static final String MESSAGE_HEX = "7465737420416E64726F69642031323334"; + + // hex("M=") + MESSAGE_HEX + public static final String FORMATTED_MESSAGE = "4D3D" + MESSAGE_HEX; + + public static final byte[] EAP_MSCHAP_V2_SUCCESS_REQUEST = + hexStringToByteArray( + "03" + ID + "0042" + FORMATTED_AUTH_STRING + SPACE_HEX + FORMATTED_MESSAGE); + public static final byte[] EAP_MSCHAP_V2_SUCCESS_REQUEST_EMPTY_MESSAGE = + hexStringToByteArray("03" + ID + "0031" + FORMATTED_AUTH_STRING + SPACE_HEX + "4D3D"); + public static final byte[] EAP_MSCHAP_V2_SUCCESS_REQUEST_MISSING_MESSAGE = + hexStringToByteArray("03" + ID + "002E" + FORMATTED_AUTH_STRING); + public static final byte[] EAP_MSCHAP_V2_SUCCESS_REQUEST_MISSING_MESSAGE_WITH_SPACE = + hexStringToByteArray("03" + ID + "002F" + FORMATTED_AUTH_STRING + SPACE_HEX); + public static final String MESSAGE_MISSING_TEXT = "<omitted by authenticator>"; + + public static final String SHORT_AUTH_STRING = "001122334455"; + + public static final byte[] SUCCESS_REQUEST_WRONG_OP_CODE = hexStringToByteArray("02"); + + // message format: hex("M=") + AUTH_STRING_HEX + hex("M=") + MESSAGE_HEX + public static final byte[] SUCCESS_REQUEST_WRONG_PREFIX = + hexStringToByteArray("03" + ID + "00314D3D" + AUTH_STRING_HEX + SPACE_HEX + "4D3D"); + + // message format: hex("S=") + SHORT_AUTH_STRING + hex("M=") + MESSAGE_HEX + public static final byte[] SUCCESS_REQUEST_SHORT_AUTH_STRING = + hexStringToByteArray("03" + ID + "0031533D" + SHORT_AUTH_STRING + SPACE_HEX + "4D3D"); + + public static final String INVALID_AUTH_HEX = + "3030313132323333343435353636373738383939414142424343444445454646303031317A7A7979"; + public static final byte[] SUCCESS_REQUEST_INVALID_AUTH_STRING = + hexStringToByteArray("03" + ID + "0031533D" + INVALID_AUTH_HEX + SPACE_HEX + "4D3D"); + + // extra key-value: hex("N=12") + public static final String EXTRA_KEY = "4E3D3132"; + public static final byte[] SUCCESS_REQUEST_EXTRA_ATTRIBUTE = + hexStringToByteArray( + "03" + + ID + + "0042" + + FORMATTED_AUTH_STRING + + SPACE_HEX + + EXTRA_KEY + + SPACE_HEX + + FORMATTED_MESSAGE); + + public static final String SUCCESS_REQUEST = "S=" + AUTH_STRING + " M=" + MESSAGE; + public static final String EXTRA_M_MESSAGE = "M=" + MESSAGE; + public static final String SUCCESS_REQUEST_EXTRA_M = + "S=" + AUTH_STRING + " M=" + EXTRA_M_MESSAGE; + public static final String SUCCESS_REQUEST_MISSING_M = "S=" + AUTH_STRING; + public static final String SUCCESS_REQUEST_INVALID_FORMAT = + "S==" + AUTH_STRING + "M=" + MESSAGE; + public static final String SUCCESS_REQUEST_DUPLICATE_KEY = + "S=" + AUTH_STRING + " S=" + AUTH_STRING + " M=" + MESSAGE; + + public static final byte[] EAP_MSCHAP_V2_SUCCESS_RESPONSE = hexStringToByteArray("03"); + + public static final int ERROR_CODE = 647; // account disabled + + // formatted error code: hex("E=" + ERROR_CODE) + public static final String FORMATTED_ERROR_CODE = "453D363437"; + public static final boolean RETRY_BIT = true; + + // formatted retry bit: hex("R=1") + public static final String FORMATTED_RETRY_BIT = "523D31"; + + // challenge hex: hex(CHALLENGE) + public static final String CHALLENGE_HEX = + "3030303130323033303430353036303730383039304130423043304430453046"; + + // formatted challenge: hex("C=") + CHALLENGE_HEX + public static final String FORMATTED_CHALLENGE = "433D" + CHALLENGE_HEX; + + public static final int PASSWORD_CHANGE_PROTOCOL = 3; + + // formatted password change protocol: hex("V=3") + public static final String FORMATTED_PASSWORD_CHANGE_PROTOCOL = "563D33"; + + public static final byte[] EAP_MSCHAP_V2_FAILURE_REQUEST = + hexStringToByteArray( + "04" + + ID + + "0048" + + FORMATTED_ERROR_CODE + + SPACE_HEX + + FORMATTED_RETRY_BIT + + SPACE_HEX + + FORMATTED_CHALLENGE + + SPACE_HEX + + FORMATTED_PASSWORD_CHANGE_PROTOCOL + + SPACE_HEX + + FORMATTED_MESSAGE); + public static final byte[] EAP_MSCHAP_V2_FAILURE_REQUEST_MISSING_MESSAGE = + hexStringToByteArray( + "04" + + ID + + "0034" + + FORMATTED_ERROR_CODE + + SPACE_HEX + + FORMATTED_RETRY_BIT + + SPACE_HEX + + FORMATTED_CHALLENGE + + SPACE_HEX + + FORMATTED_PASSWORD_CHANGE_PROTOCOL); + public static final byte[] EAP_MSCHAP_V2_FAILURE_REQUEST_MISSING_MESSAGE_WITH_SPACE = + hexStringToByteArray( + "04" + + ID + + "0035" + + FORMATTED_ERROR_CODE + + SPACE_HEX + + FORMATTED_RETRY_BIT + + SPACE_HEX + + FORMATTED_CHALLENGE + + SPACE_HEX + + FORMATTED_PASSWORD_CHANGE_PROTOCOL + + SPACE_HEX); + + // invalid error code: hex("E=abc") + public static final String INVALID_ERROR_CODE = "453D616263"; + public static final byte[] FAILURE_REQUEST_INVALID_ERROR_CODE = + hexStringToByteArray( + "04" + + ID + + "0048" + + INVALID_ERROR_CODE + + SPACE_HEX + + FORMATTED_RETRY_BIT + + SPACE_HEX + + FORMATTED_CHALLENGE + + SPACE_HEX + + FORMATTED_PASSWORD_CHANGE_PROTOCOL + + SPACE_HEX + + FORMATTED_MESSAGE); + + // invalid challenge: hex("C=zyxd") + public static final String INVALID_CHALLENGE = "433D7A797864"; + public static final byte[] FAILURE_REQUEST_INVALID_CHALLENGE = + hexStringToByteArray( + "04" + + ID + + "0032" + + FORMATTED_ERROR_CODE + + SPACE_HEX + + FORMATTED_RETRY_BIT + + SPACE_HEX + + INVALID_CHALLENGE + + SPACE_HEX + + FORMATTED_PASSWORD_CHANGE_PROTOCOL + + SPACE_HEX + + FORMATTED_MESSAGE); + + // short challenge: hex("C=" + SHORT_CHALLENGE) + public static final String FORMATTED_SHORT_CHALLENGE = "433D303031313232333334343535"; + public static final byte[] FAILURE_REQUEST_SHORT_CHALLENGE = + hexStringToByteArray( + "04" + + ID + + "0034" + + FORMATTED_ERROR_CODE + + SPACE_HEX + + FORMATTED_RETRY_BIT + + SPACE_HEX + + FORMATTED_SHORT_CHALLENGE + + SPACE_HEX + + FORMATTED_PASSWORD_CHANGE_PROTOCOL + + SPACE_HEX + + FORMATTED_MESSAGE); + + // invalid password change protocol: hex("V=d") + public static final String INVALID_PASSWORD_CHANGE_PROTOCOL = "563D64"; + public static final byte[] FAILURE_REQUEST_INVALID_PASSWORD_CHANGE_PROTOCOL = + hexStringToByteArray( + "04" + + ID + + "0048" + + FORMATTED_ERROR_CODE + + SPACE_HEX + + FORMATTED_RETRY_BIT + + SPACE_HEX + + FORMATTED_CHALLENGE + + SPACE_HEX + + INVALID_PASSWORD_CHANGE_PROTOCOL + + SPACE_HEX + + FORMATTED_MESSAGE); + + public static final byte[] FAILURE_REQUEST_EXTRA_ATTRIBUTE = + hexStringToByteArray( + "04" + + ID + + "0048" + + FORMATTED_ERROR_CODE + + SPACE_HEX + + FORMATTED_RETRY_BIT + + SPACE_HEX + + FORMATTED_CHALLENGE + + SPACE_HEX + + FORMATTED_PASSWORD_CHANGE_PROTOCOL + + SPACE_HEX + + EXTRA_KEY + + SPACE_HEX + + FORMATTED_MESSAGE); + + public static final byte[] EAP_MSCHAP_V2_FAILURE_RESPONSE = hexStringToByteArray("04"); +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2SuccessRequestTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2SuccessRequestTest.java new file mode 100644 index 00000000..1e0909ed --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2SuccessRequestTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.mschapv2; + +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.AUTH_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_SUCCESS_REQUEST; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_SUCCESS_REQUEST_EMPTY_MESSAGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_SUCCESS_REQUEST_MISSING_MESSAGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_SUCCESS_REQUEST_MISSING_MESSAGE_WITH_SPACE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.ID_INT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.MESSAGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.MESSAGE_MISSING_TEXT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_EXTRA_ATTRIBUTE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_INVALID_AUTH_STRING; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_SHORT_AUTH_STRING; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_WRONG_OP_CODE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_WRONG_PREFIX; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_SUCCESS; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2SuccessRequest; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder.DecodeResult; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.BufferUnderflowException; + +public class EapMsChapV2SuccessRequestTest { + private static final String TAG = EapMsChapV2SuccessRequestTest.class.getSimpleName(); + + private EapMsChapV2TypeDataDecoder mTypeDataDecoder; + + @Before + public void setUp() { + mTypeDataDecoder = new EapMsChapV2TypeDataDecoder(); + } + + @Test + public void testDecodeSuccessRequest() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest(TAG, EAP_MSCHAP_V2_SUCCESS_REQUEST); + assertTrue(result.isSuccessfulDecode()); + + EapMsChapV2SuccessRequest successRequest = result.eapTypeData; + assertEquals(EAP_MSCHAP_V2_SUCCESS, successRequest.opCode); + assertEquals(ID_INT, successRequest.msChapV2Id); + assertEquals(EAP_MSCHAP_V2_SUCCESS_REQUEST.length, successRequest.msLength); + assertArrayEquals(AUTH_BYTES, successRequest.authBytes); + assertEquals(MESSAGE, successRequest.message); + } + + @Test + public void testDecodeSuccessRequestEmptyMessage() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest( + TAG, EAP_MSCHAP_V2_SUCCESS_REQUEST_EMPTY_MESSAGE); + assertTrue(result.isSuccessfulDecode()); + + EapMsChapV2SuccessRequest successRequest = result.eapTypeData; + assertEquals(EAP_MSCHAP_V2_SUCCESS, successRequest.opCode); + assertEquals(ID_INT, successRequest.msChapV2Id); + assertEquals(EAP_MSCHAP_V2_SUCCESS_REQUEST_EMPTY_MESSAGE.length, successRequest.msLength); + assertArrayEquals(AUTH_BYTES, successRequest.authBytes); + assertTrue(successRequest.message.isEmpty()); + } + + @Test + public void testDecodeSuccessRequestMissingMessage() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest( + TAG, EAP_MSCHAP_V2_SUCCESS_REQUEST_MISSING_MESSAGE); + assertTrue(result.isSuccessfulDecode()); + + EapMsChapV2SuccessRequest successRequest = result.eapTypeData; + assertEquals(EAP_MSCHAP_V2_SUCCESS, successRequest.opCode); + assertEquals(ID_INT, successRequest.msChapV2Id); + assertEquals(EAP_MSCHAP_V2_SUCCESS_REQUEST_MISSING_MESSAGE.length, successRequest.msLength); + assertArrayEquals(AUTH_BYTES, successRequest.authBytes); + assertEquals(MESSAGE_MISSING_TEXT, successRequest.message); + } + + @Test + public void testDecodeSuccessRequestMissingMessageWithSpace() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest( + TAG, EAP_MSCHAP_V2_SUCCESS_REQUEST_MISSING_MESSAGE_WITH_SPACE); + assertTrue(result.isSuccessfulDecode()); + + EapMsChapV2SuccessRequest successRequest = result.eapTypeData; + assertEquals(EAP_MSCHAP_V2_SUCCESS, successRequest.opCode); + assertEquals(ID_INT, successRequest.msChapV2Id); + assertEquals( + EAP_MSCHAP_V2_SUCCESS_REQUEST_MISSING_MESSAGE_WITH_SPACE.length, + successRequest.msLength); + assertArrayEquals(AUTH_BYTES, successRequest.authBytes); + assertEquals(MESSAGE_MISSING_TEXT, successRequest.message); + } + + @Test + public void testDecodeSuccessRequestWrongOpCode() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest(TAG, SUCCESS_REQUEST_WRONG_OP_CODE); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testDecodeSuccessRequestShortMessage() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest(TAG, new byte[0]); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof BufferUnderflowException); + } + + @Test + public void testDecodeSuccessRequestInvalidPrefix() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest(TAG, SUCCESS_REQUEST_WRONG_PREFIX); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testDecodeSuccessRequestShortAuthString() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest(TAG, SUCCESS_REQUEST_SHORT_AUTH_STRING); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testDecodeSuccessRequestInvalidAuthString() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest(TAG, SUCCESS_REQUEST_INVALID_AUTH_STRING); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof NumberFormatException); + } + + @Test + public void testDecodeSuccessRequestExtraAttribute() { + DecodeResult<EapMsChapV2SuccessRequest> result = + mTypeDataDecoder.decodeSuccessRequest(TAG, SUCCESS_REQUEST_EXTRA_ATTRIBUTE); + assertFalse(result.isSuccessfulDecode()); + EapError eapError = result.eapError; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + } + + @Test + public void testEncodeFails() throws Exception { + EapMsChapV2SuccessRequest successRequest = + new EapMsChapV2SuccessRequest( + ID_INT, EAP_MSCHAP_V2_SUCCESS_REQUEST.length, AUTH_BYTES, MESSAGE); + try { + successRequest.encode(); + fail("Expected UnsupportedOperationException for encoding a request"); + } catch (UnsupportedOperationException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2SuccessResponseTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2SuccessResponseTest.java new file mode 100644 index 00000000..524b3ecf --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2SuccessResponseTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.mschapv2; + +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EAP_MSCHAP_V2_SUCCESS_RESPONSE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_SUCCESS; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2SuccessResponse; + +import org.junit.Test; + +public class EapMsChapV2SuccessResponseTest { + @Test + public void testGetEapMsChapV2SuccessResponse() { + EapMsChapV2SuccessResponse successResponse = + EapMsChapV2SuccessResponse.getEapMsChapV2SuccessResponse(); + assertEquals(EAP_MSCHAP_V2_SUCCESS, successResponse.opCode); + } + + @Test + public void testEncode() { + EapMsChapV2SuccessResponse successResponse = + EapMsChapV2SuccessResponse.getEapMsChapV2SuccessResponse(); + assertArrayEquals(EAP_MSCHAP_V2_SUCCESS_RESPONSE, successResponse.encode()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2TypeDataTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2TypeDataTest.java new file mode 100644 index 00000000..45f1c641 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2TypeDataTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.mschapv2; + +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.AUTH_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.AUTH_STRING; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.EXTRA_M_MESSAGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.MESSAGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.MESSAGE_MISSING_TEXT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_DUPLICATE_KEY; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_EXTRA_M; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_INVALID_FORMAT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SUCCESS_REQUEST_MISSING_M; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_CHALLENGE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder.DecodeResult; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2VariableTypeData; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class EapMsChapV2TypeDataTest { + private static final int INVALID_OPCODE = -1; + private static final int MSCHAP_V2_ID = 1; + private static final int MS_LENGTH = 32; + private static final String HEX_STRING_INVALID_LENGTH = "00112"; + private static final String HEX_STRING_INVALID_CHARS = "001122z-+x"; + + @Test + public void testEapMsChapV2TypeDataConstructor() throws Exception { + EapMsChapV2TypeData typeData = new EapMsChapV2TypeData(EAP_MSCHAP_V2_CHALLENGE) {}; + assertEquals(EAP_MSCHAP_V2_CHALLENGE, typeData.opCode); + + try { + new EapMsChapV2TypeData(INVALID_OPCODE) {}; + fail("ExpectedEapMsChapV2ParsingException for invalid OpCode"); + } catch (EapMsChapV2ParsingException expected) { + } + } + + @Test + public void testEapMsChapV2VariableTypeDataConstructor() throws Exception { + EapMsChapV2VariableTypeData typeData = + new EapMsChapV2VariableTypeData( + EAP_MSCHAP_V2_CHALLENGE, MSCHAP_V2_ID, MS_LENGTH) {}; + assertEquals(EAP_MSCHAP_V2_CHALLENGE, typeData.opCode); + assertEquals(MSCHAP_V2_ID, typeData.msChapV2Id); + assertEquals(MS_LENGTH, typeData.msLength); + + try { + new EapMsChapV2VariableTypeData(INVALID_OPCODE, MSCHAP_V2_ID, MS_LENGTH) {}; + fail("ExpectedEapMsChapV2ParsingException for invalid OpCode"); + } catch (EapMsChapV2ParsingException expected) { + } + } + + @Test + public void testDecodeResultIsSuccessfulDecode() throws Exception { + DecodeResult<EapMsChapV2TypeData> result = + new DecodeResult(new EapMsChapV2TypeData(EAP_MSCHAP_V2_CHALLENGE) {}); + assertTrue(result.isSuccessfulDecode()); + + result = new DecodeResult(new EapError(new Exception())); + assertFalse(result.isSuccessfulDecode()); + } + + @Test + public void testGetMessageMappings() throws Exception { + Map<String, String> expectedMappings = new HashMap<>(); + expectedMappings.put("S", AUTH_STRING); + expectedMappings.put("M", MESSAGE); + assertEquals(expectedMappings, EapMsChapV2TypeData.getMessageMappings(SUCCESS_REQUEST)); + + expectedMappings = new HashMap<>(); + expectedMappings.put("S", AUTH_STRING); + expectedMappings.put("M", EXTRA_M_MESSAGE); + assertEquals( + expectedMappings, EapMsChapV2TypeData.getMessageMappings(SUCCESS_REQUEST_EXTRA_M)); + + expectedMappings = new HashMap<>(); + expectedMappings.put("S", AUTH_STRING); + expectedMappings.put("M", MESSAGE_MISSING_TEXT); + assertEquals( + expectedMappings, + EapMsChapV2TypeData.getMessageMappings(SUCCESS_REQUEST_MISSING_M)); + } + + @Test + public void testGetMessageMappingsInvalidFormat() { + try { + EapMsChapV2TypeData.getMessageMappings(SUCCESS_REQUEST_INVALID_FORMAT); + fail("Expected EapMsChapV2ParsingException for extra '='s in message"); + } catch (EapMsChapV2ParsingException expected) { + } + } + + @Test + public void testGetMessageMappingDuplicateKey() { + try { + EapMsChapV2TypeData.getMessageMappings(SUCCESS_REQUEST_DUPLICATE_KEY); + fail("Expected EapMsChapV2ParsingException for duplicate key in message"); + } catch (EapMsChapV2ParsingException expected) { + } + } + + @Test + public void testHexStringToByteArray() throws Exception { + byte[] result = EapMsChapV2TypeData.hexStringToByteArray(AUTH_STRING); + assertArrayEquals(AUTH_BYTES, result); + } + + @Test + public void testHexStringToByteArrayInvalidLength() { + try { + EapMsChapV2TypeData.hexStringToByteArray(HEX_STRING_INVALID_LENGTH); + fail("Expected EapMsChapV2ParsingException for invalid hex string length"); + } catch (EapMsChapV2ParsingException expected) { + } + } + + @Test + public void testHexStringToByteArrayInvalidChars() throws Exception { + try { + EapMsChapV2TypeData.hexStringToByteArray(HEX_STRING_INVALID_CHARS); + fail("Expected NumberFormatException for invalid hex chars"); + } catch (NumberFormatException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapAkaPrimeTypeDataTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapAkaPrimeTypeDataTest.java new file mode 100644 index 00000000..6a9e517f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapAkaPrimeTypeDataTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTN; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF_INPUT; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RAND; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_KDF_INPUT; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.KDF_VERSION; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NETWORK_NAME_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NETWORK_NAME_HEX; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData.EapAkaPrimeTypeDataDecoder; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdf; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdfInput; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandAka; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +public class EapAkaPrimeTypeDataTest { + private static final String RAND = "7A1FCDC0034BA1227E7B9FCEAFD47D53"; + private static final byte[] RAND_BYTES = hexStringToByteArray(RAND); + private static final String AUTN = "000102030405060708090A0B0C0D0E0F"; + private static final byte[] AUTN_BYTES = hexStringToByteArray(AUTN); + private static final String MAC = "95FEB9E70427F34B4FAC8F2C7A65A302"; + private static final byte[] MAC_BYTES = hexStringToByteArray(MAC); + private static final byte[] EAP_AKA_PRIME_CHALLENGE_REQUEST = + hexStringToByteArray( + "010000" // Challenge | 2B padding + + "01050000" + RAND // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "1704000B" + NETWORK_NAME_HEX + "00" // AT_KDF_INPUT + + "18010001" // AT_KDF + + "0B050000" + MAC); // AT_MAC attribute + private static final byte[] EAP_AKA_PRIME_MULTIPLE_AT_KDF = + hexStringToByteArray( + "010000" // Challenge | 2B padding + + "01050000" + RAND // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "1704000B" + NETWORK_NAME_HEX + "00" // AT_KDF_INPUT + + "18010001" // AT_KDF + + "18010002" // AT_KDF + + "0B050000" + MAC); // AT_MAC attribute + + private EapAkaPrimeTypeDataDecoder mTypeDataDecoder; + + @Before + public void setUp() { + mTypeDataDecoder = EapAkaPrimeTypeData.getEapAkaPrimeTypeDataDecoder(); + } + + @Test + public void testDecode() { + DecodeResult<EapAkaTypeData> result = + mTypeDataDecoder.decode(EAP_AKA_PRIME_CHALLENGE_REQUEST); + + assertTrue(result.isSuccessfulDecode()); + EapAkaPrimeTypeData eapAkaPrimeTypeData = (EapAkaPrimeTypeData) result.eapTypeData; + assertEquals(EAP_AKA_CHALLENGE, eapAkaPrimeTypeData.eapSubtype); + + // also check Map entries (needs to match input order) + Iterator<Entry<Integer, EapSimAkaAttribute>> itr = + eapAkaPrimeTypeData.attributeMap.entrySet().iterator(); + Entry<Integer, EapSimAkaAttribute> entry = itr.next(); + assertEquals(EAP_AT_RAND, (int) entry.getKey()); + assertArrayEquals(RAND_BYTES, ((AtRandAka) entry.getValue()).rand); + + entry = itr.next(); + assertEquals(EAP_AT_AUTN, (int) entry.getKey()); + assertArrayEquals(AUTN_BYTES, ((AtAutn) entry.getValue()).autn); + + entry = itr.next(); + assertEquals(EAP_AT_KDF_INPUT, (int) entry.getKey()); + assertArrayEquals(NETWORK_NAME_BYTES, ((AtKdfInput) entry.getValue()).networkName); + + entry = itr.next(); + assertEquals(EAP_AT_KDF, (int) entry.getKey()); + assertEquals(KDF_VERSION, ((AtKdf) entry.getValue()).kdf); + + entry = itr.next(); + assertEquals(EAP_AT_MAC, (int) entry.getKey()); + assertArrayEquals(MAC_BYTES, ((AtMac) entry.getValue()).mac); + + assertFalse(itr.hasNext()); + } + + @Test + public void testDecodeMultipleAtKdfAttributes() { + DecodeResult<EapAkaTypeData> result = + mTypeDataDecoder.decode(EAP_AKA_PRIME_MULTIPLE_AT_KDF); + + assertFalse(result.isSuccessfulDecode()); + assertEquals(AtClientErrorCode.UNABLE_TO_PROCESS, result.atClientErrorCode); + } + + @Test + public void testEncode() throws Exception { + LinkedHashMap<Integer, EapSimAkaAttribute> attributes = new LinkedHashMap<>(); + attributes.put(EAP_AT_RAND, new AtRandAka(RAND_BYTES)); + attributes.put(EAP_AT_AUTN, new AtAutn(AUTN_BYTES)); + attributes.put(EAP_AT_KDF_INPUT, new AtKdfInput(AT_KDF_INPUT.length, NETWORK_NAME_BYTES)); + attributes.put(EAP_AT_KDF, new AtKdf(KDF_VERSION)); + attributes.put(EAP_AT_MAC, new AtMac(MAC_BYTES)); + EapAkaPrimeTypeData eapAkaPrimeTypeData = + new EapAkaPrimeTypeData(EAP_AKA_CHALLENGE, attributes); + + byte[] result = eapAkaPrimeTypeData.encode(); + assertArrayEquals(EAP_AKA_PRIME_CHALLENGE_REQUEST, result); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapAkaTypeDataTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapAkaTypeDataTest.java new file mode 100644 index 00000000..b2d89d16 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapAkaTypeDataTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_CHALLENGE_RESPONSE_MAC_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_CHALLENGE_RESPONSE_TYPE_DATA; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_IDENTITY_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.INVALID_SUBTYPE; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ANY_ID_REQ; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTN; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_CHECKCODE; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RAND; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RES_BYTES; + +import static junit.framework.TestCase.fail; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.internal.net.eap.message.simaka.EapAkaTypeData.EapAkaTypeDataDecoder; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAnyIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandAka; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRes; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EapSimAkaUnsupportedAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +public class EapAkaTypeDataTest { + private static final int UNABLE_TO_PROCESS_CODE = 0; + private static final int INVALID_SUBTYPE_INT = -1; + + private static final int EAP_AT_TRUST_IND = 139; + private static final String RAND = "7A1FCDC0034BA1227E7B9FCEAFD47D53"; + private static final byte[] RAND_BYTES = hexStringToByteArray(RAND); + private static final String AUTN = "000102030405060708090A0B0C0D0E0F"; + private static final byte[] AUTN_BYTES = hexStringToByteArray(AUTN); + private static final String MAC = "95FEB9E70427F34B4FAC8F2C7A65A302"; + private static final byte[] MAC_BYTES = hexStringToByteArray(MAC); + private static final byte[] EAP_AKA_REQUEST = + hexStringToByteArray( + "010000" // Challenge | 2B padding + + "01050000" + RAND // AT_RAND attribute + + "02050000" + AUTN // AT_AUTN attribute + + "8B010002" // AT_RESULT_IND attribute (TS 124 302#8.2.3.1) + + "0B050000" + MAC // AT_MAC attribute + + "86010000"); // AT_CHECKCODE attribute + + private EapAkaTypeDataDecoder mEapAkaTypeDataDecoder; + + @Before + public void setUp() { + mEapAkaTypeDataDecoder = EapAkaTypeData.getEapAkaTypeDataDecoder(); + } + + @Test + public void testDecode() { + DecodeResult<EapAkaTypeData> result = + mEapAkaTypeDataDecoder.decode(EAP_AKA_CHALLENGE_RESPONSE_TYPE_DATA); + + assertTrue(result.isSuccessfulDecode()); + EapAkaTypeData eapAkaTypeData = result.eapTypeData; + assertEquals(EAP_AKA_CHALLENGE, eapAkaTypeData.eapSubtype); + + // also check Map entries (needs to match input order) + Iterator<Entry<Integer, EapSimAkaAttribute>> itr = + eapAkaTypeData.attributeMap.entrySet().iterator(); + Entry<Integer, EapSimAkaAttribute> entry = itr.next(); + assertEquals(EAP_AT_RES, (int) entry.getKey()); + assertArrayEquals(RES_BYTES, ((AtRes) entry.getValue()).res); + + entry = itr.next(); + assertEquals(EAP_AT_MAC, (int) entry.getKey()); + assertArrayEquals(EAP_AKA_CHALLENGE_RESPONSE_MAC_BYTES, ((AtMac) entry.getValue()).mac); + + assertFalse(itr.hasNext()); + } + + @Test + public void testDecodeWithOptionalAttributes() { + DecodeResult<EapAkaTypeData> result = mEapAkaTypeDataDecoder.decode(EAP_AKA_REQUEST); + + assertTrue(result.isSuccessfulDecode()); + EapAkaTypeData eapAkaTypeData = result.eapTypeData; + assertEquals(EAP_AKA_CHALLENGE, eapAkaTypeData.eapSubtype); + + // also check Map entries (needs to match input order) + Iterator<Entry<Integer, EapSimAkaAttribute>> itr = + eapAkaTypeData.attributeMap.entrySet().iterator(); + Entry<Integer, EapSimAkaAttribute> entry = itr.next(); + assertEquals(EAP_AT_RAND, (int) entry.getKey()); + assertArrayEquals(RAND_BYTES, ((AtRandAka) entry.getValue()).rand); + + entry = itr.next(); + assertEquals(EAP_AT_AUTN, (int) entry.getKey()); + assertArrayEquals(AUTN_BYTES, ((AtAutn) entry.getValue()).autn); + + entry = itr.next(); + assertEquals(EAP_AT_TRUST_IND, (int) entry.getKey()); + assertTrue(entry.getValue() instanceof EapSimAkaUnsupportedAttribute); + + entry = itr.next(); + assertEquals(EAP_AT_MAC, (int) entry.getKey()); + assertArrayEquals(MAC_BYTES, ((AtMac) entry.getValue()).mac); + + entry = itr.next(); + assertEquals(EAP_AT_CHECKCODE, (int) entry.getKey()); + assertTrue(entry.getValue() instanceof EapSimAkaUnsupportedAttribute); + + assertFalse(itr.hasNext()); + } + + @Test + public void testDecodeInvalidSubtype() { + DecodeResult<EapAkaTypeData> result = mEapAkaTypeDataDecoder.decode(INVALID_SUBTYPE); + assertFalse(result.isSuccessfulDecode()); + assertEquals(UNABLE_TO_PROCESS_CODE, result.atClientErrorCode.errorCode); + } + + @Test + public void testEncode() throws Exception { + LinkedHashMap<Integer, EapSimAkaAttribute> attributes = new LinkedHashMap<>(); + attributes.put(EAP_AT_ANY_ID_REQ, new AtAnyIdReq()); + EapAkaTypeData eapAkaTypeData = new EapAkaTypeData(EAP_AKA_IDENTITY, attributes); + + byte[] result = eapAkaTypeData.encode(); + assertArrayEquals(EAP_AKA_IDENTITY_REQUEST, result); + } + + @Test + public void testConstructorInvalidSubtype() throws Exception { + try { + new EapAkaTypeData(INVALID_SUBTYPE_INT, Arrays.asList(new AtAnyIdReq())); + fail("Expected IllegalArgumentException for invalid subtype"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testConstructorDuplicateAttributes() throws Exception { + try { + new EapAkaTypeData(EAP_AKA_IDENTITY, Arrays.asList(new AtAnyIdReq(), new AtAnyIdReq())); + fail("Expected IllegalArgumentException for duplicate attributes"); + } catch (IllegalArgumentException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapSimAkaAttributeFactoryTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapSimAkaAttributeFactoryTest.java new file mode 100644 index 00000000..5b6f5d60 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapSimAkaAttributeFactoryTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SKIPPABLE_DATA; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SKIPPABLE_DATA_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SKIPPABLE_INVALID_ATTRIBUTE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaUnsupportedAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EapSimAkaUnsupportedAttribute; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class EapSimAkaAttributeFactoryTest { + private static final int SKIPPABLE_ATTRIBUTE_TYPE = 0xFF; + private static final int SKIPPABLE_EXPECTED_LENGTH = 8; + + private static final int NON_SKIPPABLE_ATTRIBUTE_TYPE = 0x7F; + private static final int NON_SKIPPABLE_ATTRIBUTE_LENGTH = 4; + + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testDecodeInvalidSkippable() throws Exception { + ByteBuffer byteBuffer = ByteBuffer.wrap(SKIPPABLE_DATA_BYTES); + + EapSimAkaAttribute result = mAttributeFactory.getAttribute( + SKIPPABLE_ATTRIBUTE_TYPE, + SKIPPABLE_EXPECTED_LENGTH, + byteBuffer); + assertTrue(result instanceof EapSimAkaUnsupportedAttribute); + EapSimAkaUnsupportedAttribute unsupportedAttribute = (EapSimAkaUnsupportedAttribute) result; + assertEquals(SKIPPABLE_ATTRIBUTE_TYPE, unsupportedAttribute.attributeType); + assertEquals(SKIPPABLE_EXPECTED_LENGTH, unsupportedAttribute.lengthInBytes); + assertArrayEquals(hexStringToByteArray(SKIPPABLE_DATA), unsupportedAttribute.data); + } + + @Test + public void testEncodeInvalidSkippable() throws Exception { + EapSimAkaUnsupportedAttribute unsupportedAttribute = new EapSimAkaUnsupportedAttribute( + SKIPPABLE_ATTRIBUTE_TYPE, + SKIPPABLE_EXPECTED_LENGTH, + hexStringToByteArray(SKIPPABLE_DATA)); + + ByteBuffer result = ByteBuffer.allocate(SKIPPABLE_EXPECTED_LENGTH); + unsupportedAttribute.encode(result); + assertArrayEquals(SKIPPABLE_INVALID_ATTRIBUTE, result.array()); + } + + @Test + public void testDecodeInvalidNonSkippable() throws Exception { + // Unskippable type + length + byte[] represent shortest legitimate attribute: "7F040000" + ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[2]); + + try { + mAttributeFactory.getAttribute( + NON_SKIPPABLE_ATTRIBUTE_TYPE, + NON_SKIPPABLE_ATTRIBUTE_LENGTH, + byteBuffer); + fail("Expected EapSimAkaUnsupportedAttributeException for decoding invalid" + + " non-skippable Attribute"); + } catch (EapSimAkaUnsupportedAttributeException expected) { + } + } + +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapSimTypeDataTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapSimTypeDataTest.java new file mode 100644 index 00000000..678a812b --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapSimTypeDataTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka; + +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_START_DUPLICATE_ATTRIBUTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_START_SUBTYPE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.INVALID_SUBTYPE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SHORT_TYPE_DATA; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.TYPE_DATA_INVALID_ATTRIBUTE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.TYPE_DATA_INVALID_AT_RAND; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_PERMANENT_ID_REQ; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_VERSION_LIST; + +import static junit.framework.TestCase.fail; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtPermanentIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtVersionList; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.message.simaka.EapSimTypeData.EapSimTypeDataDecoder; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map.Entry; + +public class EapSimTypeDataTest { + private static final int UNABLE_TO_PROCESS_CODE = 0; + private static final int INSUFFICIENT_CHALLENGES_CODE = 2; + private static final int EAP_SIM_START = 10; + private static final int INVALID_SUBTYPE_INT = -1; + + private EapSimTypeDataDecoder mEapSimTypeDataDecoder; + + @Before + public void setUp() { + mEapSimTypeDataDecoder = EapSimTypeData.getEapSimTypeDataDecoder(); + } + + @Test + public void testConstructor() throws Exception { + List<EapSimAkaAttribute> attributes = Arrays.asList( + new AtVersionList(8, 1), new AtPermanentIdReq()); + + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_START, attributes); + assertEquals(EAP_SIM_START, eapSimTypeData.eapSubtype); + + // check order of entries in EapSimTypeData.attributeMap + Iterator<Entry<Integer, EapSimAkaAttribute>> itr = + eapSimTypeData.attributeMap.entrySet().iterator(); + Entry<Integer, EapSimAkaAttribute> pair = itr.next(); + assertEquals(EAP_AT_VERSION_LIST, (int) pair.getKey()); + assertEquals(Arrays.asList(1), ((AtVersionList) pair.getValue()).versions); + + pair = itr.next(); + assertEquals(EAP_AT_PERMANENT_ID_REQ, (int) pair.getKey()); + assertTrue(pair.getValue() instanceof AtPermanentIdReq); + } + + @Test + public void testDecode() { + DecodeResult<EapSimTypeData> result = mEapSimTypeDataDecoder.decode(EAP_SIM_START_SUBTYPE); + + assertTrue(result.isSuccessfulDecode()); + EapSimTypeData eapSimTypeData = result.eapTypeData; + assertEquals(EAP_SIM_START, eapSimTypeData.eapSubtype); + assertTrue(eapSimTypeData.attributeMap.containsKey(EAP_AT_VERSION_LIST)); + AtVersionList atVersionList = (AtVersionList) + eapSimTypeData.attributeMap.get(EAP_AT_VERSION_LIST); + assertEquals(Arrays.asList(1), atVersionList.versions); + assertTrue(eapSimTypeData.attributeMap.containsKey(EAP_AT_PERMANENT_ID_REQ)); + + // also check order of Map entries (needs to match input order) + Iterator<Integer> itr = eapSimTypeData.attributeMap.keySet().iterator(); + assertEquals(EAP_AT_VERSION_LIST, (int) itr.next()); + assertEquals(EAP_AT_PERMANENT_ID_REQ, (int) itr.next()); + assertFalse(itr.hasNext()); + } + + @Test + public void testDecodeNullTypeData() { + DecodeResult<EapSimTypeData> result = mEapSimTypeDataDecoder.decode(null); + assertFalse(result.isSuccessfulDecode()); + assertEquals(UNABLE_TO_PROCESS_CODE, result.atClientErrorCode.errorCode); + } + + @Test + public void testDecodeInvalidSubtype() { + DecodeResult<EapSimTypeData> result = mEapSimTypeDataDecoder.decode(INVALID_SUBTYPE); + assertFalse(result.isSuccessfulDecode()); + assertEquals(UNABLE_TO_PROCESS_CODE, result.atClientErrorCode.errorCode); + } + + @Test + public void testDecodeInvalidAtRand() { + DecodeResult<EapSimTypeData> result = + mEapSimTypeDataDecoder.decode(TYPE_DATA_INVALID_AT_RAND); + assertFalse(result.isSuccessfulDecode()); + assertEquals(INSUFFICIENT_CHALLENGES_CODE, result.atClientErrorCode.errorCode); + } + + @Test + public void testDecodeShortPacket() { + DecodeResult<EapSimTypeData> result = mEapSimTypeDataDecoder.decode(SHORT_TYPE_DATA); + assertFalse(result.isSuccessfulDecode()); + assertEquals(UNABLE_TO_PROCESS_CODE, result.atClientErrorCode.errorCode); + } + + @Test + public void testDecodeInvalidEapAttribute() { + DecodeResult<EapSimTypeData> result = + mEapSimTypeDataDecoder.decode(TYPE_DATA_INVALID_ATTRIBUTE); + assertFalse(result.isSuccessfulDecode()); + assertEquals(UNABLE_TO_PROCESS_CODE, result.atClientErrorCode.errorCode); + } + + @Test + public void testEncode() throws Exception { + LinkedHashMap<Integer, EapSimAkaAttribute> attributes = new LinkedHashMap<>(); + attributes.put(EAP_AT_VERSION_LIST, new AtVersionList(8, 1)); + attributes.put(EAP_AT_PERMANENT_ID_REQ, new AtPermanentIdReq()); + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_START, attributes); + + byte[] result = eapSimTypeData.encode(); + assertArrayEquals(EAP_SIM_START_SUBTYPE, result); + } + + @Test + public void testDecodeDuplicateAttributes() { + DecodeResult<EapSimTypeData> result = + mEapSimTypeDataDecoder.decode(EAP_SIM_START_DUPLICATE_ATTRIBUTES); + assertFalse(result.isSuccessfulDecode()); + assertEquals(UNABLE_TO_PROCESS_CODE, result.atClientErrorCode.errorCode); + } + + @Test + public void testConstructorInvalidSubtype() throws Exception { + try { + new EapSimTypeData(INVALID_SUBTYPE_INT, Arrays.asList(new AtPermanentIdReq())); + fail("Expected IllegalArgumentException for invalid subtype"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testConstructorDuplicateAttributes() throws Exception { + try { + new EapSimTypeData( + EAP_SIM_START, Arrays.asList(new AtPermanentIdReq(), new AtPermanentIdReq())); + fail("Expected IllegalArgumentException for duplicate attributes"); + } catch (IllegalArgumentException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtAutnTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtAutnTest.java new file mode 100644 index 00000000..ebf22e4f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtAutnTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTN; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_AUTN; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_AUTN_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AUTN_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapAkaAttributeFactory; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtAutnTest { + private EapAkaAttributeFactory mEapAkaAttributeFactory; + + @Before + public void setUp() { + mEapAkaAttributeFactory = EapAkaAttributeFactory.getInstance(); + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_AUTN); + EapSimAkaAttribute result = mEapAkaAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtAutn atAutn = (AtAutn) result; + assertEquals(EAP_AT_AUTN, atAutn.attributeType); + assertEquals(AT_AUTN.length, atAutn.lengthInBytes); + assertArrayEquals(AUTN_BYTES, atAutn.autn); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_AUTN_INVALID_LENGTH); + try { + mEapAkaAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtAutn atAutn = new AtAutn(AUTN_BYTES); + + ByteBuffer result = ByteBuffer.allocate(AT_AUTN.length); + atAutn.encode(result); + assertArrayEquals(AT_AUTN, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtAutsTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtAutsTest.java new file mode 100644 index 00000000..d65a735f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtAutsTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTS; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_AUTS; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_AUTS_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AUTS_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapAkaAttributeFactory; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAuts; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtAutsTest { + private EapAkaAttributeFactory mEapAkaAttributeFactory; + + @Before + public void setUp() { + mEapAkaAttributeFactory = EapAkaAttributeFactory.getInstance(); + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_AUTS); + EapSimAkaAttribute result = mEapAkaAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtAuts atAuts = (AtAuts) result; + assertEquals(EAP_AT_AUTS, atAuts.attributeType); + assertEquals(AT_AUTS.length, atAuts.lengthInBytes); + assertArrayEquals(AUTS_BYTES, atAuts.auts); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_AUTS_INVALID_LENGTH); + try { + mEapAkaAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtAuts atAuts = new AtAuts(AUTS_BYTES); + + ByteBuffer result = ByteBuffer.allocate(AT_AUTS.length); + atAuts.encode(result); + assertArrayEquals(AT_AUTS, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtBiddingTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtBiddingTest.java new file mode 100644 index 00000000..efdfcc2e --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtBiddingTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_BIDDING; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_BIDDING_DOES_NOT_SUPPORT_AKA_PRIME; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_BIDDING_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_BIDDING_SUPPORTS_AKA_PRIME; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapAkaAttributeFactory; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtBidding; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtBiddingTest { + private EapAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = EapAkaAttributeFactory.getInstance(); + } + + @Test + public void testDecodeServerSupportsAkaPrime() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_BIDDING_SUPPORTS_AKA_PRIME); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtBidding atBidding = (AtBidding) result; + assertEquals(EAP_AT_BIDDING, atBidding.attributeType); + assertEquals(AT_BIDDING_SUPPORTS_AKA_PRIME.length, atBidding.lengthInBytes); + assertTrue(atBidding.doesServerSupportEapAkaPrime); + } + + @Test + public void testDecodeDoesNotSupportAkaPrime() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_BIDDING_DOES_NOT_SUPPORT_AKA_PRIME); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtBidding atBidding = (AtBidding) result; + assertEquals(EAP_AT_BIDDING, atBidding.attributeType); + assertEquals(AT_BIDDING_DOES_NOT_SUPPORT_AKA_PRIME.length, atBidding.lengthInBytes); + assertFalse(atBidding.doesServerSupportEapAkaPrime); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_BIDDING_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncodeServerSupportsAkaPrime() throws Exception { + AtBidding atBidding = new AtBidding(true); + + ByteBuffer result = ByteBuffer.allocate(AT_BIDDING_SUPPORTS_AKA_PRIME.length); + atBidding.encode(result); + assertArrayEquals(AT_BIDDING_SUPPORTS_AKA_PRIME, result.array()); + } + + @Test + public void testEncodeDoesNotSupportAkaPrime() throws Exception { + AtBidding atBidding = new AtBidding(false); + + ByteBuffer result = ByteBuffer.allocate(AT_BIDDING_DOES_NOT_SUPPORT_AKA_PRIME.length); + atBidding.encode(result); + assertArrayEquals(AT_BIDDING_DOES_NOT_SUPPORT_AKA_PRIME, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtClientErrorCodeTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtClientErrorCodeTest.java new file mode 100644 index 00000000..2051414c --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtClientErrorCodeTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.TestUtils.hexStringToInt; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_CLIENT_ERROR_CODE; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_CLIENT_ERROR_CODE; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_CLIENT_ERROR_CODE_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.ERROR_CODE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; + +public class AtClientErrorCodeTest { + private static final int EXPECTED_LENGTH = 4; + + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_CLIENT_ERROR_CODE); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtClientErrorCode); + AtClientErrorCode atClientErrorCode = (AtClientErrorCode) result; + assertEquals(EAP_AT_CLIENT_ERROR_CODE, atClientErrorCode.attributeType); + assertEquals(EXPECTED_LENGTH, atClientErrorCode.lengthInBytes); + assertEquals(hexStringToInt(ERROR_CODE), atClientErrorCode.errorCode); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_CLIENT_ERROR_CODE_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected BufferUnderflowException for invalid attribute length"); + } catch (BufferUnderflowException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtClientErrorCode atNotification = new AtClientErrorCode( + EXPECTED_LENGTH, hexStringToInt(ERROR_CODE)); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + + atNotification.encode(result); + assertArrayEquals(AT_CLIENT_ERROR_CODE, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtCounterTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtCounterTest.java new file mode 100644 index 00000000..eb1086d5 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtCounterTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_COUNTER; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_COUNTER_TOO_SMALL; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_COUNTER; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_COUNTER_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_COUNTER_TOO_SMALL; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_COUNTER_TOO_SMALL_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.COUNTER_INT; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtCounter; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtCounterTooSmall; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtCounterTest { + private static final int EXPECTED_LENGTH = 4; + + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testDecodeAtCounter() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_COUNTER); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtCounter); + AtCounter atCounter = (AtCounter) result; + assertEquals(EAP_AT_COUNTER, atCounter.attributeType); + assertEquals(EXPECTED_LENGTH, atCounter.lengthInBytes); + assertEquals(COUNTER_INT, atCounter.counter); + } + + @Test + public void testDecodeAtCounterInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_COUNTER_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncodeAtCounter() throws Exception { + AtCounter atCounter = new AtCounter(COUNTER_INT); + + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + atCounter.encode(result); + assertArrayEquals(AT_COUNTER, result.array()); + } + + @Test + public void testAtCounterTooSmallConstructor() throws Exception { + AtCounterTooSmall atCounterTooSmall = new AtCounterTooSmall(); + assertEquals(EAP_AT_COUNTER_TOO_SMALL, atCounterTooSmall.attributeType); + assertEquals(EXPECTED_LENGTH, atCounterTooSmall.lengthInBytes); + } + + @Test + public void testDecodeAtCounterTooSmall() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_COUNTER_TOO_SMALL); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtCounterTooSmall); + AtCounterTooSmall atCounterTooSmall = (AtCounterTooSmall) result; + assertEquals(EAP_AT_COUNTER_TOO_SMALL, atCounterTooSmall.attributeType); + assertEquals(EXPECTED_LENGTH, atCounterTooSmall.lengthInBytes); + } + + @Test + public void testDecodeAtCounterTooSmallInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_COUNTER_TOO_SMALL_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncodeAtCounterTooSmall() throws Exception { + AtCounterTooSmall atCounterTooSmall = new AtCounterTooSmall(); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + atCounterTooSmall.encode(result); + assertArrayEquals(AT_COUNTER_TOO_SMALL, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtIdReqTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtIdReqTest.java new file mode 100644 index 00000000..d006053e --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtIdReqTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ANY_ID_REQ; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_FULLAUTH_ID_REQ; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_PERMANENT_ID_REQ; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.ANY_ID_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_ANY_ID_REQ; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_FULL_AUTH_ID_REQ; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_PERMANENT_ID_REQ; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.FULL_AUTH_ID_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.PERMANENT_ID_INVALID_LENGTH; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAnyIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtFullauthIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtPermanentIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtIdReqTest { + private static final int EXPECTED_LENGTH = 4; + + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testDecodeAtPermanentIdReq() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_PERMANENT_ID_REQ); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtPermanentIdReq); + AtPermanentIdReq atPermanentIdReq = (AtPermanentIdReq) result; + assertEquals(EAP_AT_PERMANENT_ID_REQ, atPermanentIdReq.attributeType); + assertEquals(EXPECTED_LENGTH, atPermanentIdReq.lengthInBytes); + } + + @Test + public void testDecodeAtPermanentIdReqInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(PERMANENT_ID_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid attribute length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncodeAtPermanentIdReq() throws Exception { + AtPermanentIdReq atPermanentIdReq = new AtPermanentIdReq(); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + + atPermanentIdReq.encode(result); + assertArrayEquals(AT_PERMANENT_ID_REQ, result.array()); + } + + @Test + public void testDecodeAtAnyIdReq() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_ANY_ID_REQ); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtAnyIdReq); + AtAnyIdReq atAnyIdReq = (AtAnyIdReq) result; + assertEquals(EAP_AT_ANY_ID_REQ, atAnyIdReq.attributeType); + assertEquals(EXPECTED_LENGTH, atAnyIdReq.lengthInBytes); + } + + @Test + public void testDecodeAtAnyIdReqInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(ANY_ID_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid attribute length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncodeAtAnyIdReq() throws Exception { + AtAnyIdReq atPermanentIdReq = new AtAnyIdReq(); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + + atPermanentIdReq.encode(result); + assertArrayEquals(AT_ANY_ID_REQ, result.array()); + } + + @Test + public void testDecodeAtFullauthIdReq() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_FULL_AUTH_ID_REQ); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtFullauthIdReq); + AtFullauthIdReq atFullauthIdReq = (AtFullauthIdReq) result; + assertEquals(EAP_AT_FULLAUTH_ID_REQ, atFullauthIdReq.attributeType); + assertEquals(EXPECTED_LENGTH, atFullauthIdReq.lengthInBytes); + } + + @Test + public void testDecodeAtFullauthIdReqInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(FULL_AUTH_ID_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid attribute length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncodeAtFullauthIdReq() throws Exception { + AtFullauthIdReq atPermanentIdReq = new AtFullauthIdReq(); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + + atPermanentIdReq.encode(result); + assertArrayEquals(AT_FULL_AUTH_ID_REQ, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtIdentityTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtIdentityTest.java new file mode 100644 index 00000000..cf8e8803 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtIdentityTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_IDENTITY; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_IDENTITY; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.IDENTITY; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtIdentity; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttributeFactory; +import com.android.internal.net.eap.message.simaka.EapSimAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtIdentityTest { + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_IDENTITY); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtIdentity); + AtIdentity atIdentity = (AtIdentity) result; + assertEquals(EAP_AT_IDENTITY, atIdentity.attributeType); + assertEquals(AT_IDENTITY.length, atIdentity.lengthInBytes); + assertArrayEquals(IDENTITY, atIdentity.identity); + } + + @Test + public void testEncode() throws Exception { + AtIdentity atIdentity = new AtIdentity(AT_IDENTITY.length, IDENTITY); + ByteBuffer result = ByteBuffer.allocate(AT_IDENTITY.length); + atIdentity.encode(result); + + assertArrayEquals(AT_IDENTITY, result.array()); + } + + @Test + public void testGetAtIdentity() throws Exception { + AtIdentity atIdentity = AtIdentity.getAtIdentity(IDENTITY); + + assertArrayEquals(IDENTITY, atIdentity.identity); + + ByteBuffer buffer = ByteBuffer.allocate(atIdentity.lengthInBytes); + atIdentity.encode(buffer); + buffer.rewind(); + + EapSimAkaAttribute eapSimAkaAttribute = + EapSimAttributeFactory.getInstance().getAttribute(buffer); + assertTrue(eapSimAkaAttribute instanceof AtIdentity); + AtIdentity newAtIdentity = (AtIdentity) eapSimAkaAttribute; + assertEquals(atIdentity.lengthInBytes, newAtIdentity.lengthInBytes); + assertArrayEquals(atIdentity.identity, newAtIdentity.identity); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtKdfInputTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtKdfInputTest.java new file mode 100644 index 00000000..51ea3f14 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtKdfInputTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF_INPUT; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_KDF_INPUT; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_KDF_INPUT_EMPTY_NETWORK_NAME; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NETWORK_NAME_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import com.android.internal.net.eap.message.simaka.EapAkaPrimeAttributeFactory; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdfInput; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtKdfInputTest { + private EapAkaPrimeAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = EapAkaPrimeAttributeFactory.getInstance(); + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_KDF_INPUT); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtKdfInput atKdfInput = (AtKdfInput) result; + assertEquals(EAP_AT_KDF_INPUT, atKdfInput.attributeType); + assertEquals(AT_KDF_INPUT.length, atKdfInput.lengthInBytes); + assertArrayEquals(NETWORK_NAME_BYTES, atKdfInput.networkName); + } + + @Test + public void testDecodeEmptyNetworkName() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_KDF_INPUT_EMPTY_NETWORK_NAME); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtKdfInput atKdfInput = (AtKdfInput) result; + assertEquals(EAP_AT_KDF_INPUT, atKdfInput.attributeType); + assertEquals(AT_KDF_INPUT_EMPTY_NETWORK_NAME.length, atKdfInput.lengthInBytes); + assertArrayEquals(new byte[0], atKdfInput.networkName); + } + + @Test + public void testEncode() throws Exception { + AtKdfInput atKdfInput = new AtKdfInput(AT_KDF_INPUT.length, NETWORK_NAME_BYTES); + ByteBuffer result = ByteBuffer.allocate(AT_KDF_INPUT.length); + + atKdfInput.encode(result); + assertArrayEquals(AT_KDF_INPUT, result.array()); + assertFalse(result.hasRemaining()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtKdfTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtKdfTest.java new file mode 100644 index 00000000..0bb07326 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtKdfTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_KDF; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_KDF_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.KDF_VERSION; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapAkaPrimeAttributeFactory; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdf; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtKdfTest { + private EapAkaPrimeAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = EapAkaPrimeAttributeFactory.getInstance(); + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_KDF); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtKdf atKdf = (AtKdf) result; + assertEquals(EAP_AT_KDF, atKdf.attributeType); + assertEquals(AT_KDF.length, atKdf.lengthInBytes); + assertEquals(KDF_VERSION, atKdf.kdf); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_KDF_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtKdf atKdf = new AtKdf(KDF_VERSION); + ByteBuffer result = ByteBuffer.allocate(AT_KDF.length); + + atKdf.encode(result); + assertArrayEquals(AT_KDF, result.array()); + assertFalse(result.hasRemaining()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtMacTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtMacTest.java new file mode 100644 index 00000000..82b066d5 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtMacTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_MAC; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_MAC_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.MAC; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtMacTest { + private static final int EXPECTED_LENGTH = 20; + private static final int MAC_LENGTH = 16; + private static final byte[] MAC_BYTES = hexStringToByteArray(MAC); + private static final byte[] INVALID_MAC = {(byte) 1, (byte) 2, (byte) 3}; + + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testConstructor() throws Exception { + AtMac atMac = new AtMac(); + assertEquals(EAP_AT_MAC, atMac.attributeType); + assertEquals(EXPECTED_LENGTH, atMac.lengthInBytes); + assertArrayEquals(new byte[MAC_LENGTH], atMac.mac); + } + + @Test + public void testParameterizedConstructor() throws Exception { + AtMac atMac = new AtMac(MAC_BYTES); + assertEquals(EAP_AT_MAC, atMac.attributeType); + assertEquals(EXPECTED_LENGTH, atMac.lengthInBytes); + assertArrayEquals(MAC_BYTES, atMac.mac); + } + + @Test + public void testParameterizedConstructorInvalidMac() { + try { + AtMac atMac = new AtMac(INVALID_MAC); + fail("Expected EapSimAkaInvalidAttributeException for invalid MAC length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_MAC); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtMac); + AtMac atMac = (AtMac) result; + assertEquals(EAP_AT_MAC, atMac.attributeType); + assertEquals(EXPECTED_LENGTH, atMac.lengthInBytes); + assertArrayEquals(MAC_BYTES, atMac.mac); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_MAC_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtMac atMac = new AtMac(MAC_BYTES); + + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + atMac.encode(result); + assertArrayEquals(AT_MAC, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNonceMtTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNonceMtTest.java new file mode 100644 index 00000000..751908a2 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNonceMtTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_NONCE_MT; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_NONCE_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_NONCE_MT; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_MT; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNonceMt; +import com.android.internal.net.eap.message.simaka.EapSimAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtNonceMtTest { + private static final byte[] INVALID_NONCE = new byte[10]; + private static final int EXPECTED_LENGTH = 20; + + private EapSimAttributeFactory mEapSimAttributeFactory; + + @Before + public void setUp() { + mEapSimAttributeFactory = EapSimAttributeFactory.getInstance(); + } + + @Test + public void testConstructorInvalidNonceLength() { + try { + new AtNonceMt(INVALID_NONCE); + fail("Expected EapSimAkaInvalidAttributeException for invalid NonceMt length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_NONCE_MT); + EapSimAkaAttribute result = mEapSimAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtNonceMt); + AtNonceMt atNonceMt = (AtNonceMt) result; + assertEquals(EAP_AT_NONCE_MT, atNonceMt.attributeType); + assertEquals(EXPECTED_LENGTH, atNonceMt.lengthInBytes); + assertArrayEquals(NONCE_MT, atNonceMt.nonceMt); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_NONCE_INVALID_LENGTH); + try { + mEapSimAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid attribute length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws EapSimAkaInvalidAttributeException { + AtNonceMt atNonceMt = new AtNonceMt(NONCE_MT); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + + atNonceMt.encode(result); + assertArrayEquals(AT_NONCE_MT, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNonceSTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNonceSTest.java new file mode 100644 index 00000000..1ad64669 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNonceSTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_NONCE_S; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_NONCE_S; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_NONCE_S_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_S; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNonceS; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtNonceSTest { + private static final byte[] INVALID_NONCE = new byte[10]; + private static final int EXPECTED_LENGTH = 20; + + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testConstructorInvalidNonceLength() { + try { + new AtNonceS(INVALID_NONCE); + fail("Expected EapSimAkaInvalidAttributeException for invalid NonceMt length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_NONCE_S); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtNonceS); + AtNonceS atNonceS = (AtNonceS) result; + assertEquals(EAP_AT_NONCE_S, atNonceS.attributeType); + assertEquals(EXPECTED_LENGTH, atNonceS.lengthInBytes); + assertArrayEquals(hexStringToByteArray(NONCE_S), atNonceS.nonceS); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_NONCE_S_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtNonceS atNonceS = new AtNonceS(hexStringToByteArray(NONCE_S)); + + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + atNonceS.encode(result); + assertArrayEquals(AT_NONCE_S, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNotificationTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNotificationTest.java new file mode 100644 index 00000000..2db3cbb2 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNotificationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.TestUtils.hexStringToInt; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_POST_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_NOTIFICATION; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_NOTIFICATION; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_NOTIFICATION_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_NOTIFICATION_INVALID_STATE; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NOTIFICATION_CODE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtNotificationTest { + private static final int EXPECTED_LENGTH = 4; + private static final int UNKNOWN_CODE = 0xA0FF; + + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_NOTIFICATION); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtNotification); + AtNotification atNotification = (AtNotification) result; + assertEquals(EAP_AT_NOTIFICATION, atNotification.attributeType); + assertEquals(EXPECTED_LENGTH, atNotification.lengthInBytes); + assertTrue(atNotification.isSuccessCode); + assertFalse(atNotification.isPreSuccessfulChallenge); + assertEquals(hexStringToInt(NOTIFICATION_CODE), atNotification.notificationCode); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_NOTIFICATION_INVALID_LENGTH); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid attribute length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testDecodeInvalidState() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_NOTIFICATION_INVALID_STATE); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid state"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtNotification atNotification = new AtNotification(hexStringToInt(NOTIFICATION_CODE)); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + + atNotification.encode(result); + assertArrayEquals(AT_NOTIFICATION, result.array()); + } + + @Test + public void testToString() throws Exception { + AtNotification knownCode = new AtNotification(GENERAL_FAILURE_POST_CHALLENGE); + AtNotification unknownCode = new AtNotification(UNKNOWN_CODE); + + assertNotNull(knownCode.toString()); + assertNotNull(unknownCode.toString()); + assertNotEquals(knownCode.toString(), unknownCode.toString()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtPaddingTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtPaddingTest.java new file mode 100644 index 00000000..d310d504 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtPaddingTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_PADDING; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_PADDING; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_PADDING_INVALID_PADDING; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAtPaddingException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtPadding; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtPaddingTest { + private static final int EXPECTED_LENGTH = 8; + + private EapSimAkaAttributeFactory mAttributeFactory; + + @Before + public void setUp() { + mAttributeFactory = new EapSimAkaAttributeFactory() {}; + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_PADDING); + EapSimAkaAttribute result = mAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtPadding); + AtPadding atPadding = (AtPadding) result; + assertEquals(EAP_AT_PADDING, atPadding.attributeType); + assertEquals(EXPECTED_LENGTH, atPadding.lengthInBytes); + } + + @Test + public void testDecodeInvalidPadding() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_PADDING_INVALID_PADDING); + try { + mAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAtPaddingException for nonzero padding bytes"); + } catch (EapSimAkaInvalidAtPaddingException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtPadding atPadding = new AtPadding(EXPECTED_LENGTH); + + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + atPadding.encode(result); + + assertFalse(result.hasRemaining()); + assertArrayEquals(AT_PADDING, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtRandAkaTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtRandAkaTest.java new file mode 100644 index 00000000..bdffdda9 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtRandAkaTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RAND; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RAND_AKA; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RAND_AKA_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapAkaAttributeFactory; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandAka; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtRandAkaTest { + private EapAkaAttributeFactory mEapAkaAttributeFactory; + + @Before + public void setUp() { + mEapAkaAttributeFactory = EapAkaAttributeFactory.getInstance(); + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RAND_AKA); + EapSimAkaAttribute result = mEapAkaAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtRandAka atRandAka = (AtRandAka) result; + assertEquals(EAP_AT_RAND, atRandAka.attributeType); + assertEquals(AT_RAND_AKA.length, atRandAka.lengthInBytes); + assertArrayEquals(RAND_1_BYTES, atRandAka.rand); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RAND_AKA_INVALID_LENGTH); + try { + mEapAkaAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + + ByteBuffer result = ByteBuffer.allocate(AT_RAND_AKA.length); + atRandAka.encode(result); + assertArrayEquals(AT_RAND_AKA, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtRandSimTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtRandSimTest.java new file mode 100644 index 00000000..7456be6c --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtRandSimTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RAND; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RAND_SIM; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RAND_SIM_DUPLICATE_RANDS; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RAND_SIM_INVALID_NUM_RANDS; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_2; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.exceptions.simaka.EapSimInvalidAtRandException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandSim; +import com.android.internal.net.eap.message.simaka.EapSimAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtRandSimTest { + private static final int EXPECTED_NUM_RANDS = 2; + + private EapSimAttributeFactory mEapSimAttributeFactory; + + @Before + public void setUp() { + mEapSimAttributeFactory = EapSimAttributeFactory.getInstance(); + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RAND_SIM); + EapSimAkaAttribute result = mEapSimAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtRandSim); + AtRandSim atRandSim = (AtRandSim) result; + assertEquals(EAP_AT_RAND, atRandSim.attributeType); + assertEquals(AT_RAND_SIM.length, atRandSim.lengthInBytes); + assertEquals(EXPECTED_NUM_RANDS, atRandSim.rands.size()); + assertArrayEquals(hexStringToByteArray(RAND_1), atRandSim.rands.get(0)); + assertArrayEquals(hexStringToByteArray(RAND_2), atRandSim.rands.get(1)); + } + + @Test + public void testDecodeInvalidNumRands() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RAND_SIM_INVALID_NUM_RANDS); + try { + mEapSimAttributeFactory.getAttribute(input); + fail("Expected EapSimInvalidAtRandException for invalid number of RANDs"); + } catch (EapSimInvalidAtRandException expected) { + } + } + + @Test + public void testDecodeDuplicateRands() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RAND_SIM_DUPLICATE_RANDS); + try { + mEapSimAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for duplicate RANDs"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + byte[][] expectedRands = new byte[][] { + hexStringToByteArray(RAND_1), + hexStringToByteArray(RAND_2) + }; + AtRandSim atRandSim = new AtRandSim(AT_RAND_SIM.length, expectedRands); + + ByteBuffer result = ByteBuffer.allocate(AT_RAND_SIM.length); + atRandSim.encode(result); + assertArrayEquals(AT_RAND_SIM, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtResTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtResTest.java new file mode 100644 index 00000000..34c2ff39 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtResTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RES_INVALID_RES_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RES_LONG_RES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_RES_SHORT_RES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RES_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapAkaAttributeFactory; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRes; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtResTest { + private EapAkaAttributeFactory mEapAkaAttributeFactory; + + @Before + public void setUp() { + mEapAkaAttributeFactory = EapAkaAttributeFactory.getInstance(); + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RES); + EapSimAkaAttribute result = mEapAkaAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + AtRes atRes = (AtRes) result; + assertEquals(EAP_AT_RES, atRes.attributeType); + assertEquals(AT_RES.length, atRes.lengthInBytes); + assertArrayEquals(RES_BYTES, atRes.res); + } + + @Test + public void testDecodeInvalidResLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RES_INVALID_RES_LENGTH); + try { + mEapAkaAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid RES length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testDecodeShortResLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RES_SHORT_RES); + try { + mEapAkaAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for too short RES"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testDecodeLongResLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_RES_LONG_RES); + try { + mEapAkaAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for too long RES"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtRes atRes = new AtRes(AT_RES.length, RES_BYTES); + + ByteBuffer result = ByteBuffer.allocate(AT_RES.length); + atRes.encode(result); + assertArrayEquals(AT_RES, result.array()); + } + + @Test + public void testGetAtRes() throws Exception { + AtRes atRes = AtRes.getAtRes(RES_BYTES); + + ByteBuffer result = ByteBuffer.allocate(AT_RES.length); + atRes.encode(result); + assertArrayEquals(AT_RES, result.array()); + } + + @Test + public void testIsValidResLen() { + // valid RES length: 4 <= RES length <= 16 + assertTrue(AtRes.isValidResLen(5)); + assertFalse(AtRes.isValidResLen(0)); + assertFalse(AtRes.isValidResLen(20)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtSelectedVersionTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtSelectedVersionTest.java new file mode 100644 index 00000000..659fe9a8 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtSelectedVersionTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_SELECTED_VERSION; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_SELECTED_VERSION; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_SELECTED_VERSION_INVALID_LENGTH; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtSelectedVersion; +import com.android.internal.net.eap.message.simaka.EapSimAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class AtSelectedVersionTest { + private static final int EXPECTED_LENGTH = 4; + private static final int EXPECTED_VERSION = 1; + + private EapSimAttributeFactory mEapSimAttributeFactory; + + @Before + public void setUp() { + mEapSimAttributeFactory = EapSimAttributeFactory.getInstance(); + } + + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_SELECTED_VERSION); + EapSimAkaAttribute result = mEapSimAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtSelectedVersion); + AtSelectedVersion atSelectedVersion = (AtSelectedVersion) result; + assertEquals(EAP_AT_SELECTED_VERSION, atSelectedVersion.attributeType); + assertEquals(EXPECTED_LENGTH, atSelectedVersion.lengthInBytes); + assertEquals(EXPECTED_VERSION, atSelectedVersion.selectedVersion); + } + + @Test + public void testDecodeInvalidLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_SELECTED_VERSION_INVALID_LENGTH); + try { + mEapSimAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid actual list length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtSelectedVersion atSelectedVersion = new AtSelectedVersion( + EXPECTED_LENGTH, EXPECTED_VERSION); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + + atSelectedVersion.encode(result); + assertArrayEquals(AT_SELECTED_VERSION, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtVersionListTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtVersionListTest.java new file mode 100644 index 00000000..96bb7ca3 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtVersionListTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.TestUtils.hexStringToInt; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_VERSION_LIST; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_VERSION_LIST; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_VERSION_LIST_INVALID_LENGTH; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.VERSION; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtVersionList; +import com.android.internal.net.eap.message.simaka.EapSimAttributeFactory; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +public class AtVersionListTest { + private static final int EXPECTED_LENGTH = 8; + private static final List<Integer> EXPECTED_VERSIONS = Arrays.asList(1); + + private EapSimAttributeFactory mEapSimAttributeFactory; + + @Before + public void setUp() { + mEapSimAttributeFactory = EapSimAttributeFactory.getInstance(); + } + + @Test + public void testDecode() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_VERSION_LIST); + EapSimAkaAttribute result = mEapSimAttributeFactory.getAttribute(input); + + assertFalse(input.hasRemaining()); + assertTrue(result instanceof AtVersionList); + AtVersionList atVersionList = (AtVersionList) result; + assertEquals(EAP_AT_VERSION_LIST, atVersionList.attributeType); + assertEquals(EXPECTED_LENGTH, atVersionList.lengthInBytes); + assertEquals(EXPECTED_VERSIONS, atVersionList.versions); + } + + @Test + public void testDecodeInvalidActualLength() throws Exception { + ByteBuffer input = ByteBuffer.wrap(AT_VERSION_LIST_INVALID_LENGTH); + try { + mEapSimAttributeFactory.getAttribute(input); + fail("Expected EapSimAkaInvalidAttributeException for invalid actual list length"); + } catch (EapSimAkaInvalidAttributeException expected) { + } + } + + @Test + public void testEncode() throws Exception { + AtVersionList atVersionList = new AtVersionList(EXPECTED_LENGTH, hexStringToInt(VERSION)); + ByteBuffer result = ByteBuffer.allocate(EXPECTED_LENGTH); + + atVersionList.encode(result); + assertArrayEquals(AT_VERSION_LIST, result.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/EapSimAkaAttributeTest.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/EapSimAkaAttributeTest.java new file mode 100644 index 00000000..98ea222c --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/EapSimAkaAttributeTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; + +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; + +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class EapSimAkaAttributeTest { + private static final int EXPECTED_ATTRIBUTE_TYPE = 1; + private static final int EXPECTED_LENGTH_IN_BYTES = 4; + private static final int BUFFER_LENGTH = 2; + private static final int EXPECTED_LENGTH_ENCODED = 1; + private static final byte[] EXPECTED_ENCODING = { + (byte) EXPECTED_ATTRIBUTE_TYPE, + (byte) EXPECTED_LENGTH_ENCODED + }; + + @Test + public void testEncode() throws Exception { + EapSimAkaAttribute eapSimAkaAttribute = new EapSimAkaAttribute( + EXPECTED_ATTRIBUTE_TYPE, + EXPECTED_LENGTH_IN_BYTES) { + public void encode(ByteBuffer byteBuffer) { + encodeAttributeHeader(byteBuffer); + } + }; + + ByteBuffer result = ByteBuffer.allocate(BUFFER_LENGTH); + eapSimAkaAttribute.encode(result); + assertArrayEquals(EXPECTED_ENCODING, result.array()); + assertFalse(result.hasRemaining()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/EapTestAttributeDefinitions.java b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/EapTestAttributeDefinitions.java new file mode 100644 index 00000000..60397e1f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/EapTestAttributeDefinitions.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.message.simaka.attributes; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; + +/** + * EapTestAttributeDefinitions provides byte[] encodings of commonly used EAP Messages. + * + * @ee <a href="https://tools.ietf.org/html/rfc4186#section-10">RFC 4186, EAP-SIM Authentication, + * Section 10</a> + * @see <a href="https://tools.ietf.org/html/rfc4187#section-10">RFC 4187, EAP-AKA Authentication, + * Section 10</a> + */ +public class EapTestAttributeDefinitions { + public static final String VERSION = "0001"; + public static final String AT_VERSION_LIST_DATA = "0002" + VERSION + "0000"; + public static final byte[] AT_VERSION_LIST = + hexStringToByteArray("0F02" + AT_VERSION_LIST_DATA); + public static final byte[] AT_SELECTED_VERSION = hexStringToByteArray("10010001"); + public static final String NONCE_MT_STRING = "0123456789ABCDEFFEDCBA9876543210"; + public static final byte[] NONCE_MT = hexStringToByteArray(NONCE_MT_STRING); + public static final byte[] AT_NONCE_MT = hexStringToByteArray("07050000" + NONCE_MT_STRING); + public static final byte[] AT_PERMANENT_ID_REQ = hexStringToByteArray("0A010000"); + public static final byte[] AT_ANY_ID_REQ = hexStringToByteArray("0D010000"); + public static final byte[] AT_FULL_AUTH_ID_REQ = hexStringToByteArray("11010000"); + + // Identity = "test1@android.net" + public static final String IDENTITY_STRING = "746573743140616E64726F69642E6E6574"; + public static final byte[] IDENTITY = hexStringToByteArray(IDENTITY_STRING); + public static final byte[] AT_IDENTITY = + hexStringToByteArray("0E060011" + IDENTITY_STRING + "000000"); + public static final String RAND_1 = "00112233445566778899AABBCCDDEEFF"; + public static final byte[] RAND_1_BYTES = hexStringToByteArray(RAND_1); + public static final String RAND_2 = "FFEEDDCCBBAA99887766554433221100"; + public static final byte[] RAND_2_BYTES = hexStringToByteArray(RAND_2); + public static final byte[] AT_RAND_SIM = hexStringToByteArray("01090000" + RAND_1 + RAND_2); + public static final byte[] AT_RAND_AKA = hexStringToByteArray("01050000" + RAND_1); + public static final byte[] AT_PADDING = hexStringToByteArray("0602000000000000"); + public static final String MAC = "112233445566778899AABBCCDDEEFF11"; + public static final byte[] MAC_BYTES = hexStringToByteArray(MAC); + public static final byte[] AT_MAC = hexStringToByteArray("0B050000" + MAC); + public static final String COUNTER = "000A"; + public static final int COUNTER_INT = Integer.parseInt(COUNTER, 16 /* radix */); + public static final byte[] AT_COUNTER = hexStringToByteArray("1301" + COUNTER); + public static final byte[] AT_COUNTER_TOO_SMALL = hexStringToByteArray("14010000"); + public static final String NONCE_S = "0123456789ABCDEFFEDCBA9876543210"; + public static final byte[] AT_NONCE_S = hexStringToByteArray("15050000" + NONCE_S); + public static final String NOTIFICATION_CODE = "8000"; + public static final byte[] AT_NOTIFICATION = hexStringToByteArray("0C01" + NOTIFICATION_CODE); + public static final String ERROR_CODE = "0001"; + public static final byte[] AT_CLIENT_ERROR_CODE = hexStringToByteArray("1601" + ERROR_CODE); + public static final String AUTN = "0123456789ABCDEFFEDCBA9876543210"; + public static final byte[] AUTN_BYTES = hexStringToByteArray(AUTN); + public static final byte[] AT_AUTN = hexStringToByteArray("02050000" + AUTN); + public static final String RES = "1122334455"; + public static final byte[] RES_BYTES = hexStringToByteArray(RES); + public static final byte[] AT_RES = hexStringToByteArray("03030028" + RES + "000000"); + public static final String AUTS = "112233445566778899AABBCCDDEE"; + public static final byte[] AUTS_BYTES = hexStringToByteArray(AUTS); + public static final byte[] AT_AUTS = hexStringToByteArray("0404" + AUTS); + public static final byte[] AT_BIDDING_SUPPORTS_AKA_PRIME = hexStringToByteArray("88018000"); + public static final byte[] AT_BIDDING_DOES_NOT_SUPPORT_AKA_PRIME = + hexStringToByteArray("88010000"); + + // Network Name = "android.net" + public static final String NETWORK_NAME_HEX = "616E64726F69642E6E6574"; + public static final byte[] NETWORK_NAME_BYTES = hexStringToByteArray(NETWORK_NAME_HEX); + public static final byte[] AT_KDF_INPUT = + hexStringToByteArray("1704000B" + NETWORK_NAME_HEX + "00"); + public static final byte[] AT_KDF_INPUT_EMPTY_NETWORK_NAME = hexStringToByteArray("17010000"); + public static final int KDF_VERSION = 1; + public static final byte[] AT_KDF = hexStringToByteArray("18010001"); + + public static final byte[] AT_VERSION_LIST_INVALID_LENGTH = hexStringToByteArray("0F020003"); + public static final byte[] AT_SELECTED_VERSION_INVALID_LENGTH = + hexStringToByteArray("10020001"); + public static final byte[] AT_NONCE_INVALID_LENGTH = + hexStringToByteArray("07060000" + NONCE_MT_STRING); + public static final byte[] PERMANENT_ID_INVALID_LENGTH = hexStringToByteArray("0A020000"); + public static final byte[] ANY_ID_INVALID_LENGTH = hexStringToByteArray("0D020000"); + public static final byte[] FULL_AUTH_ID_INVALID_LENGTH = hexStringToByteArray("11020000"); + public static final byte[] AT_RAND_SIM_INVALID_NUM_RANDS = + hexStringToByteArray("01050000" + RAND_1); + public static final byte[] AT_RAND_SIM_DUPLICATE_RANDS = + hexStringToByteArray("01090000" + RAND_1 + RAND_1); + public static final byte[] AT_RAND_AKA_INVALID_LENGTH = hexStringToByteArray("01010000"); + public static final byte[] AT_PADDING_INVALID_PADDING = hexStringToByteArray("0601FFFF"); + public static final byte[] AT_MAC_INVALID_LENGTH = hexStringToByteArray("0B06"); + public static final byte[] AT_COUNTER_INVALID_LENGTH = hexStringToByteArray("1302"); + public static final byte[] AT_COUNTER_TOO_SMALL_INVALID_LENGTH = hexStringToByteArray("1402"); + public static final byte[] AT_NONCE_S_INVALID_LENGTH = hexStringToByteArray("1506"); + public static final byte[] AT_NOTIFICATION_INVALID_LENGTH = hexStringToByteArray("0C02"); + public static final byte[] AT_NOTIFICATION_INVALID_STATE = hexStringToByteArray("0C01C000"); + public static final byte[] AT_CLIENT_ERROR_CODE_INVALID_LENGTH = hexStringToByteArray("1602"); + public static final byte[] AT_AUTN_INVALID_LENGTH = hexStringToByteArray("02010000"); + public static final byte[] AT_RES_INVALID_RES_LENGTH = + hexStringToByteArray("030300241122334450000000"); + public static final byte[] AT_RES_SHORT_RES = + hexStringToByteArray("0302000811000000"); + public static final byte[] AT_RES_LONG_RES = + hexStringToByteArray("0306008800112233445566778899AABBCCDDEEFF11000000"); + public static final byte[] AT_AUTS_INVALID_LENGTH = hexStringToByteArray("03010000"); + public static final byte[] AT_KDF_INVALID_LENGTH = hexStringToByteArray("18020001"); + public static final byte[] AT_BIDDING_INVALID_LENGTH = hexStringToByteArray("88020000"); +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/CreatedStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/CreatedStateTest.java new file mode 100644 index 00000000..a452fa6f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/CreatedStateTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_IDENTITY_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_SIM_START_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.statemachine.EapStateMachine.IdentityState; +import com.android.internal.net.eap.statemachine.EapStateMachine.MethodState; + +import org.junit.Before; +import org.junit.Test; + +public class CreatedStateTest extends EapStateTest { + private EapStateMachine mEapStateMachineSpy; + + @Before + @Override + public void setUp() { + super.setUp(); + + mEapStateMachineSpy = spy(mEapStateMachine); + mEapState = mEapStateMachineSpy.new CreatedState(); + } + + @Test + public void testProcessIdentityRequest() { + mEapState.process(EAP_REQUEST_IDENTITY_PACKET); + + verify(mEapStateMachineSpy).transitionAndProcess( + any(IdentityState.class), eq(EAP_REQUEST_IDENTITY_PACKET)); + } + + @Test + public void testProcessNotificationRequest() { + EapResult eapResult = mEapState.process(EAP_REQUEST_NOTIFICATION_PACKET); + + // state shouldn't change after Notification request + assertTrue(eapResult instanceof EapResponse); + EapResponse eapResponse = (EapResponse) eapResult; + assertArrayEquals(EAP_RESPONSE_NOTIFICATION_PACKET, eapResponse.packet); + verify(mEapStateMachineSpy, never()).transitionAndProcess(any(), any()); + } + + @Test + public void testProcessSimStart() { + mEapState.process(EAP_REQUEST_SIM_START_PACKET); + + // EapStateMachine should change to MethodState for method-type packet + verify(mEapStateMachineSpy).transitionAndProcess( + any(MethodState.class), eq(EAP_REQUEST_SIM_START_PACKET)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaChallengeStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaChallengeStateTest.java new file mode 100644 index 00000000..b07d1ff3 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaChallengeStateTest.java @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_IDENTITY; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.CK_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_AUTHENTICATION_REJECT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_CHALLENGE_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_SYNCHRONIZATION_FAILURE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_UICC_RESP_INVALID_TAG; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_UICC_RESP_SUCCESS_BASE_64; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_UICC_RESP_SYNCHRONIZE_BASE_64; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EMSK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.IK_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSK; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AUTN_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AUTS_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.IDENTITY; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RES_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.EapResult.EapSuccess; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.exceptions.simaka.EapAkaInvalidAuthenticationResponse; +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidLengthException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtBidding; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandAka; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.ChallengeState; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.ChallengeState.RandChallengeResult; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +public class EapAkaChallengeStateTest extends EapAkaStateTest { + private ChallengeState mChallengeState; + + // '10' + RAND_1_BYTES + '10' + AUTN_BYTES + private static final String BASE_64_CHALLENGE = + "EAARIjNEVWZ3iJmqu8zd7v8QASNFZ4mrze/+3LqYdlQyEA=="; + + /** + * Process to generate MAC: + * + * message = 01100044 | EAP-Request, ID, length in bytes + * 17010000 | EAP-AKA, AKA-Challenge, padding + * 0105000000112233445566778899AABBCCDDEEFF | AT_RAND + * 020500000123456789ABCDEFFEDCBA9876543210 | AT_AUTN + * 0B05000000000000000000000000000000000000 | AT_MAC (zeroed out) + * + * MK = SHA-1(Identity | IK | CK) + * K_encr, K_aut, MSK, EMSK = PRF(MK) + * MAC = HMAC-SHA-1(K_aut, message) + */ + private static final byte[] REQUEST_MAC_BYTES = + hexStringToByteArray("3EB97A1D0E62894FD0DA384D24D8983C"); + + /** + * message = 01100048 | EAP-Request, ID, length in bytes + * 17010000 | EAP-AKA, AKA-Challenge, padding + * 0105000000112233445566778899AABBCCDDEEFF | AT_RAND + * 020500000123456789ABCDEFFEDCBA9876543210 | AT_AUTN + * 88018000 | AT_BIDDING + * 0B05000000000000000000000000000000000000 | AT_MAC (zeroed out) + */ + private static final byte[] BIDDING_DOWN_MAC = + hexStringToByteArray("9CB543894A5EFDC32DF6A6CE1AB0E01A"); + + @Before + public void setUp() { + super.setUp(); + + mChallengeState = mEapAkaMethodStateMachine.new ChallengeState(IDENTITY); + mEapAkaMethodStateMachine.transitionTo(mChallengeState); + } + + @Test + public void testProcessIncorrectEapMethodType() throws Exception { + EapData eapData = new EapData(EAP_IDENTITY, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + EapResult result = mChallengeState.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testProcessSuccess() throws Exception { + System.arraycopy(MSK, 0, mEapAkaMethodStateMachine.mMsk, 0, MSK.length); + System.arraycopy(EMSK, 0, mEapAkaMethodStateMachine.mEmsk, 0, EMSK.length); + + mChallengeState.mHadSuccessfulChallenge = true; + EapMessage input = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null); + + EapSuccess eapSuccess = (EapSuccess) mEapAkaMethodStateMachine.process(input); + assertArrayEquals(MSK, eapSuccess.msk); + assertArrayEquals(EMSK, eapSuccess.emsk); + assertTrue(mEapAkaMethodStateMachine.getState() instanceof FinalState); + } + + @Test + public void testProcessInvalidSuccess() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null); + + EapError eapError = (EapError) mEapAkaMethodStateMachine.process(input); + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testProcessFailure() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_FAILURE, ID_INT, null); + EapResult result = mEapAkaMethodStateMachine.process(input); + assertTrue(mEapAkaMethodStateMachine.getState() instanceof FinalState); + + assertTrue(result instanceof EapFailure); + } + + @Test + public void testProcessMissingAtRand() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(REQUEST_MAC_BYTES); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData(EAP_AKA_CHALLENGE, Arrays.asList(atAutn, atMac))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessMissingAtAutn() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtMac atMac = new AtMac(REQUEST_MAC_BYTES); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData(EAP_AKA_CHALLENGE, Arrays.asList(atRandAka, atMac))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessMissingAtMac() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData(EAP_AKA_CHALLENGE, Arrays.asList(atRandAka, atAutn))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testRandChallengeResultConstructor() throws Exception { + RandChallengeResult result = + mChallengeState.new RandChallengeResult(RES_BYTES, IK_BYTES, CK_BYTES); + assertArrayEquals(RES_BYTES, result.res); + assertArrayEquals(IK_BYTES, result.ik); + assertArrayEquals(CK_BYTES, result.ck); + assertNull(result.auts); + + result = mChallengeState.new RandChallengeResult(AUTS_BYTES); + assertArrayEquals(AUTS_BYTES, result.auts); + assertNull(result.res); + assertNull(result.ik); + assertNull(result.ck); + + try { + mChallengeState.new RandChallengeResult(new byte[0], IK_BYTES, CK_BYTES); + fail("Expected EapSimAkaInvalidLengthException for invalid RES length"); + } catch (EapSimAkaInvalidLengthException ex) { + } + + try { + mChallengeState.new RandChallengeResult(RES_BYTES, new byte[0], CK_BYTES); + fail("Expected EapSimAkaInvalidLengthException for invalid IK length"); + } catch (EapSimAkaInvalidLengthException ex) { + } + + try { + mChallengeState.new RandChallengeResult(RES_BYTES, IK_BYTES, new byte[0]); + fail("Expected EapSimAkaInvalidLengthException for invalid CK length"); + } catch (EapSimAkaInvalidLengthException ex) { + } + + try { + mChallengeState.new RandChallengeResult(new byte[0]); + fail("Expected EapSimAkaInvalidLengthException for invalid AUTS length"); + } catch (EapSimAkaInvalidLengthException ex) { + } + } + + @Test + public void testProcessIccAuthenticationNullResponse() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(REQUEST_MAC_BYTES); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atMac))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE)) + .thenReturn(null); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_AUTHENTICATION_REJECT, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessIccAuthenticationInvalidTag() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(REQUEST_MAC_BYTES); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atMac))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE)) + .thenReturn(EAP_AKA_UICC_RESP_INVALID_TAG); + + EapError eapError = (EapError) mEapAkaMethodStateMachine.process(eapMessage); + assertTrue(eapError.cause instanceof EapAkaInvalidAuthenticationResponse); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessIccAuthenticationSynchronizeTag() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(REQUEST_MAC_BYTES); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atMac))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE)) + .thenReturn(EAP_AKA_UICC_RESP_SYNCHRONIZE_BASE_64); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_SYNCHRONIZATION_FAILURE, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessValidChallenge() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(REQUEST_MAC_BYTES); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData( + EAP_AKA_CHALLENGE, Arrays.asList(atRandAka, atAutn, atMac))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager.getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE)) + .thenReturn(EAP_AKA_UICC_RESP_SUCCESS_BASE_64); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_CHALLENGE_RESPONSE, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessBiddingDownAttack() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtBidding atBidding = new AtBidding(true); + AtMac atMac = new AtMac(BIDDING_DOWN_MAC); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atBidding, atMac))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager.getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE)) + .thenReturn(EAP_AKA_UICC_RESP_SUCCESS_BASE_64); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_AUTHENTICATION_REJECT, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaCreatedStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaCreatedStateTest.java new file mode 100644 index 00000000..6e100dbf --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaCreatedStateTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.ChallengeState; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.IdentityState; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; + +import org.junit.Test; + +import java.util.LinkedHashMap; + +public class EapAkaCreatedStateTest extends EapAkaStateTest { + @Test + public void testProcessTransitionToIdentityState() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + // Don't actually need any attributes in the attributeMap, since we only care about the + // state transition here. + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>(new EapAkaTypeData(EAP_AKA_IDENTITY, new LinkedHashMap<>())); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mEapAkaMethodStateMachine.process(eapMessage); + + assertTrue(mEapAkaMethodStateMachine.getState() instanceof IdentityState); + + // decoded in CreatedState and IdentityState + verify(mMockEapAkaTypeDataDecoder, times(2)).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + } + + @Test + public void testProcessTransitionToChallengeState() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + // Don't actually need any attributes in the attributeMap, since we only care about the + // state transition here. + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>(new EapAkaTypeData(EAP_AKA_CHALLENGE, new LinkedHashMap<>())); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mEapAkaMethodStateMachine.process(eapMessage); + + ChallengeState challengeState = (ChallengeState) mEapAkaMethodStateMachine.getState(); + assertArrayEquals(EAP_IDENTITY_BYTES, challengeState.mIdentity); + + // decoded in CreatedState and ChallengeState + verify(mMockEapAkaTypeDataDecoder, times(2)).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + } + + @Test + public void testProcessSuccess() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null); + EapResult result = mEapAkaMethodStateMachine.process(input); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testProcessFailure() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_FAILURE, ID_INT, null); + EapResult result = mEapAkaMethodStateMachine.process(input); + assertTrue(mEapAkaMethodStateMachine.getState() instanceof FinalState); + + assertTrue(result instanceof EapFailure); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaIdentityStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaIdentityStateTest.java new file mode 100644 index 00000000..20175f95 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaIdentityStateTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_IDENTITY_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.IMSI; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaIdentityUnavailableException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAnyIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtPermanentIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.ChallengeState; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.IdentityState; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.LinkedHashMap; + +public class EapAkaIdentityStateTest extends EapAkaStateTest { + private IdentityState mIdentityState; + + @Before + public void setUp() { + super.setUp(); + + mIdentityState = mEapAkaMethodStateMachine.new IdentityState(); + mEapAkaMethodStateMachine.transitionTo(mIdentityState); + } + + @Test + public void testProcessIdentityRequest() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData(EAP_AKA_IDENTITY, Arrays.asList(new AtAnyIdReq()))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager.getSubscriberId()).thenReturn(IMSI); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_IDENTITY_RESPONSE, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager).getSubscriberId(); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessWithoutIdentityRequestAttributes() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>(new EapAkaTypeData(EAP_AKA_IDENTITY, new LinkedHashMap<>())); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessMultipleIdentityRequestAttributes() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData( + EAP_AKA_IDENTITY, + Arrays.asList(new AtAnyIdReq(), new AtPermanentIdReq()))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessImsiUnavailable() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaTypeData(EAP_AKA_IDENTITY, Arrays.asList(new AtAnyIdReq()))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager.getSubscriberId()).thenReturn(null); + + EapError eapError = (EapError) mEapAkaMethodStateMachine.process(eapMessage); + assertTrue(eapError.cause instanceof EapSimAkaIdentityUnavailableException); + + verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager).getSubscriberId(); + verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessTransitionToChallengeState() throws Exception { + // transition to IdentityState so we can verify the transition to ChallengeState + IdentityState identityState = mEapAkaMethodStateMachine.new IdentityState(); + mEapAkaMethodStateMachine.transitionTo(identityState); + + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + // Don't actually need any attributes in the attributeMap, since we only care about the + // state transition here. + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>(new EapAkaTypeData(EAP_AKA_CHALLENGE, new LinkedHashMap<>())); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mEapAkaMethodStateMachine.process(eapMessage); + + assertTrue(mEapAkaMethodStateMachine.getState() instanceof ChallengeState); + + // decoded in IdentityState and ChallengeState + verify(mMockEapAkaTypeDataDecoder, times(2)).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachineTest.java new file mode 100644 index 00000000..db2899b6 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachineTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_NOTIFICATION_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.IMSI; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_NOTIFICATION; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_PRE_CHALLENGE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.eap.EapSessionConfig.EapAkaConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData.EapAkaTypeDataDecoder; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAnyIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.CreatedState; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +public class EapAkaMethodStateMachineTest { + private static final int SUB_ID = 1; + private static final byte[] DUMMY_EAP_TYPE_DATA = hexStringToByteArray("112233445566"); + + // EAP-Identity = hex("test@android.net") + protected static final byte[] EAP_IDENTITY_BYTES = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + + protected TelephonyManager mMockTelephonyManager; + private EapAkaTypeDataDecoder mMockEapAkaTypeDataDecoder; + + private EapAkaConfig mEapAkaConfig = new EapAkaConfig(SUB_ID, APPTYPE_USIM); + private EapAkaMethodStateMachine mEapAkaMethodStateMachine; + + @Before + public void setUp() { + mMockTelephonyManager = mock(TelephonyManager.class); + mMockEapAkaTypeDataDecoder = mock(EapAkaTypeDataDecoder.class); + + when(mMockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mMockTelephonyManager); + + mEapAkaMethodStateMachine = + new EapAkaMethodStateMachine( + mMockTelephonyManager, + EAP_IDENTITY_BYTES, + mEapAkaConfig, + mMockEapAkaTypeDataDecoder, + false); + + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + } + + @Test + public void testEapAkaMethodStateMachineStartState() { + assertTrue(mEapAkaMethodStateMachine.getState() instanceof CreatedState); + } + + @Test + public void testGetEapMethod() { + assertEquals(EAP_TYPE_AKA, mEapAkaMethodStateMachine.getEapMethod()); + } + + @Test + public void testEapAkaFailsOnMultipleAkaNotifications() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + // First EAP-AKA/Notification + EapAkaTypeData notificationTypeData = + new EapAkaTypeData( + EAP_AKA_NOTIFICATION, + Arrays.asList(new AtNotification(GENERAL_FAILURE_PRE_CHALLENGE))); + DecodeResult<EapAkaTypeData> decodeResult = new DecodeResult<>(notificationTypeData); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_NOTIFICATION_RESPONSE, eapResponse.packet); + verify(mMockEapAkaTypeDataDecoder).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + + // Transition to IdentityState + decodeResult = + new DecodeResult<>( + new EapAkaTypeData(EAP_AKA_IDENTITY, Arrays.asList(new AtAnyIdReq()))); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager.getSubscriberId()).thenReturn(IMSI); + + eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertFalse( + "EAP-Request/AKA-Identity returned a Client-Error response", + Arrays.equals(EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet)); + + // decoded in: previous 1 time + in CreatedState and IdentityState + verify(mMockEapAkaTypeDataDecoder, times(3)).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager).getSubscriberId(); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + + // Second EAP-AKA/Notification + decodeResult = new DecodeResult<>(notificationTypeData); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapError eapError = (EapError) mEapAkaMethodStateMachine.process(eapMessage); + assertTrue(eapError.cause instanceof EapInvalidRequestException); + + // decoded previous 3 times + 1 + verify(mMockEapAkaTypeDataDecoder, times(4)).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeChallengeStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeChallengeStateTest.java new file mode 100644 index 00000000..731eb6bd --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeChallengeStateTest.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA_PRIME; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.CK_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_AUTHENTICATION_REJECT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_IDENTITY_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_IDENTITY_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.IK_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.IMSI; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AUTN_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.MAC_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RES_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.eap.EapSessionConfig; + +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAnyIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdf; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdfInput; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandAka; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.ChallengeState.RandChallengeResult; +import com.android.internal.net.eap.statemachine.EapAkaPrimeMethodStateMachine.ChallengeState; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; + +public class EapAkaPrimeChallengeStateTest extends EapAkaPrimeStateTest { + private static final String SERVER_NETWORK_NAME_STRING = "foo:bar:buzz"; + private static final byte[] SERVER_NETWORK_NAME = + SERVER_NETWORK_NAME_STRING.getBytes(StandardCharsets.UTF_8); + private static final String INCORRECT_NETWORK_NAME = "foo:buzz"; + private static final byte[] INCORRECT_SERVER_NETWORK_NAME = + INCORRECT_NETWORK_NAME.getBytes(StandardCharsets.UTF_8); + private static final int VALID_KDF = 1; + private static final int INVALID_KDF = 10; + + private static final byte[] EXPECTED_CK_IK_PRIME = + hexStringToByteArray( + "A0B37E7C7E9CC4F37A5C0AAA55DC87BE51FDA70A9D8F37E62E23B15F1B3941E6"); + private static final byte[] K_ENCR = hexStringToByteArray("15a5bb098528210cde9e8d4a1bd63850"); + private static final byte[] K_AUT = + hexStringToByteArray( + "957b3d518ac9ff028f2cc5177fedad841f5f812cb06e2b88aceaa98129680f35"); + private static final byte[] K_RE = + hexStringToByteArray( + "3c15cf7112935a8170d0904622ecbb67c49dcba5d50814bdd81958e045e42f9c"); + private static final byte[] MSK = + hexStringToByteArray( + "1dcca0351a58d2b858e6cf2380551470d67cc8749d1915409793171abd360118" + + "e3ae271bf088ca5a41bb1b9b8f7028bcba888298bfbf64d7b8a4f53a6c2cdf18"); + private static final byte[] EMSK = + hexStringToByteArray( + "a5e6b66a9cb2daa9fe3867d41145848e7bf50d749bfd1bb0d090257402e6a555" + + "da6d538e76b71e9f80afe60709965a63a355bdccc4e3a8b358e098e41545fa67"); + + private ChallengeState mState; + + @Before + public void setUp() { + super.setUp(); + + mState = mStateMachine.new ChallengeState(); + mStateMachine.transitionTo(mState); + } + + @Test + public void testTransitionWithEapIdentity() throws Exception { + mStateMachine.transitionTo(mStateMachine.new CreatedState()); + + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>(new EapAkaPrimeTypeData(EAP_AKA_CHALLENGE, new ArrayList<>())); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mStateMachine.process(eapMessage); + + ChallengeState challengeState = (ChallengeState) mStateMachine.getState(); + assertArrayEquals(EAP_IDENTITY_BYTES, challengeState.mIdentity); + + // decode() is called in CreatedState and ChallengeState + verify(mMockTypeDataDecoder, times(2)).decode(eq(DUMMY_EAP_TYPE_DATA)); + } + + @Test + public void testTransitionWithEapAkaPrimeIdentity() throws Exception { + mStateMachine.transitionTo(mStateMachine.new CreatedState()); + + // Process AKA' Identity Request + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaPrimeTypeData(EAP_AKA_IDENTITY, Arrays.asList(new AtAnyIdReq()))); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager.getSubscriberId()).thenReturn(IMSI); + + EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_PRIME_IDENTITY_RESPONSE, eapResponse.packet); + + // decode() is called in CreatedState and IdentityState + verify(mMockTypeDataDecoder, times(2)).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager).getSubscriberId(); + + // Process AKA' Challenge Request + decodeResult = + new DecodeResult<>(new EapAkaPrimeTypeData(EAP_AKA_CHALLENGE, new ArrayList<>())); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mStateMachine.process(eapMessage); + + ChallengeState challengeState = (ChallengeState) mStateMachine.getState(); + assertArrayEquals(EAP_AKA_PRIME_IDENTITY_BYTES, challengeState.mIdentity); + + // decode() called again in IdentityState and ChallengeState + verify(mMockTypeDataDecoder, times(4)).decode(eq(DUMMY_EAP_TYPE_DATA)); + } + + @Test + public void testProcessMissingAtKdf() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(MAC_BYTES); + AtKdfInput atKdfInput = new AtKdfInput(0, SERVER_NETWORK_NAME); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaPrimeTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atMac, atKdfInput))); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_PRIME_AUTHENTICATION_REJECT, eapResponse.packet); + verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + } + + @Test + public void testProcessMissingAtKdfInput() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(MAC_BYTES); + AtKdf atKdf = new AtKdf(VALID_KDF); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaPrimeTypeData( + EAP_AKA_CHALLENGE, Arrays.asList(atRandAka, atAutn, atMac, atKdf))); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_PRIME_AUTHENTICATION_REJECT, eapResponse.packet); + verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + } + + @Test + public void testProcessUnsupportedKdf() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(MAC_BYTES); + AtKdfInput atKdfInput = new AtKdfInput(0, SERVER_NETWORK_NAME); + AtKdf atKdf = new AtKdf(INVALID_KDF); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaPrimeTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atMac, atKdfInput, atKdf))); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_PRIME_AUTHENTICATION_REJECT, eapResponse.packet); + verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + } + + @Test + public void testProcessIncorrectNetworkName() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(MAC_BYTES); + AtKdfInput atKdfInput = new AtKdfInput(0, INCORRECT_SERVER_NETWORK_NAME); + AtKdf atKdf = new AtKdf(VALID_KDF); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaPrimeTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atMac, atKdfInput, atKdf))); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_PRIME_AUTHENTICATION_REJECT, eapResponse.packet); + verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + } + + @Test + public void testProcessIncorrectNetworkNameIsIgnored() throws Exception { + // Create state machine with configs allowing invalid network name to be ignored + mStateMachine = + new EapAkaPrimeMethodStateMachine( + mMockContext, + EAP_IDENTITY_BYTES, + new EapSessionConfig.EapAkaPrimeConfig( + SUB_ID, APPTYPE_USIM, PEER_NETWORK_NAME, true), + mMockTypeDataDecoder); + mState = mStateMachine.new ChallengeState(); + mStateMachine.transitionTo(mState); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(MAC_BYTES); + AtKdfInput atKdfInput = new AtKdfInput(0, INCORRECT_SERVER_NETWORK_NAME); + AtKdf atKdf = new AtKdf(VALID_KDF); + + EapAkaPrimeTypeData eapAkaPrimeTypeData = + new EapAkaPrimeTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atMac, atKdfInput, atKdf)); + assertTrue( + "Incorrect network names should be ignored", + mState.isValidChallengeAttributes(eapAkaPrimeTypeData)); + } + + @Test + public void testHasMatchingNetworkNames() { + // "" should match anything + assertTrue(mState.hasMatchingNetworkNames("", SERVER_NETWORK_NAME_STRING)); + assertTrue(mState.hasMatchingNetworkNames(SERVER_NETWORK_NAME_STRING, "")); + + // "foo:bar" should match "foo:bar:buzz" + assertTrue(mState.hasMatchingNetworkNames(PEER_NETWORK_NAME, SERVER_NETWORK_NAME_STRING)); + assertTrue(mState.hasMatchingNetworkNames(SERVER_NETWORK_NAME_STRING, PEER_NETWORK_NAME)); + + // "foo:buzz" shouldn't match "foo:bar:buzz" + assertFalse( + mState.hasMatchingNetworkNames(SERVER_NETWORK_NAME_STRING, INCORRECT_NETWORK_NAME)); + assertFalse( + mState.hasMatchingNetworkNames(INCORRECT_NETWORK_NAME, SERVER_NETWORK_NAME_STRING)); + } + + @Test + public void testDeriveCkIkPrime() throws Exception { + RandChallengeResult randChallengeResult = + mState.new RandChallengeResult(RES_BYTES, IK_BYTES, CK_BYTES); + AtKdfInput atKdfInput = + new AtKdfInput(0, PEER_NETWORK_NAME.getBytes(StandardCharsets.UTF_8)); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + + // S = FC | Network Name | len(Network Name) | SQN ^ AK | len(SQN ^ AK) + // = 20666F6F3A62617200070123456789AB0006 + // K = CK | IK + // = FFEEDDCCBBAA9988776655443322110000112233445566778899AABBCCDDEEFF + // CK' | IK' = HMAC-SHA256(K, S) + // = A0B37E7C7E9CC4F37A5C0AAA55DC87BE51FDA70A9D8F37E62E23B15F1B3941E6 + byte[] result = mState.deriveCkIkPrime(randChallengeResult, atKdfInput, atAutn); + assertArrayEquals(EXPECTED_CK_IK_PRIME, result); + } + + @Test + public void testGenerateAndPersistEapAkaKeys() throws Exception { + RandChallengeResult randChallengeResult = + mState.new RandChallengeResult(RES_BYTES, IK_BYTES, CK_BYTES); + + AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES); + AtAutn atAutn = new AtAutn(AUTN_BYTES); + AtMac atMac = new AtMac(MAC_BYTES); + AtKdfInput atKdfInput = + new AtKdfInput(0, PEER_NETWORK_NAME.getBytes(StandardCharsets.UTF_8)); + AtKdf atKdf = new AtKdf(VALID_KDF); + + EapAkaPrimeTypeData eapAkaPrimeTypeData = + new EapAkaPrimeTypeData( + EAP_AKA_CHALLENGE, + Arrays.asList(atRandAka, atAutn, atMac, atKdfInput, atKdf)); + + // CK' | IK' = A0B37E7C7E9CC4F37A5C0AAA55DC87BE51FDA70A9D8F37E62E23B15F1B3941E6 + // data = "EAP-AKA'" | Identity + // = 4541502D414B41277465737440616E64726F69642E6E6574 + // prf+(CK' | IK', data) = T1 | T2 | T3 | T4 | T5 | T6 | T7 + // T1 = 15a5bb098528210cde9e8d4a1bd63850957b3d518ac9ff028f2cc5177fedad84 + // T2 = 1f5f812cb06e2b88aceaa98129680f353c15cf7112935a8170d0904622ecbb67 + // T3 = c49dcba5d50814bdd81958e045e42f9c1dcca0351a58d2b858e6cf2380551470 + // T4 = d67cc8749d1915409793171abd360118e3ae271bf088ca5a41bb1b9b8f7028bc + // T5 = ba888298bfbf64d7b8a4f53a6c2cdf18a5e6b66a9cb2daa9fe3867d41145848e + // T6 = 7bf50d749bfd1bb0d090257402e6a555da6d538e76b71e9f80afe60709965a63 + // T7 = a355bdccc4e3a8b358e098e41545fa677897d8341c4a107a2343f393ec966181 + // K_encr | K_aut | K_re | MSK | EMSK = prf+(CK' | IK', data) + assertNull( + mState.generateAndPersistEapAkaKeys(randChallengeResult, 0, eapAkaPrimeTypeData)); + assertArrayEquals(K_ENCR, mStateMachine.mKEncr); + assertArrayEquals(K_AUT, mStateMachine.mKAut); + assertArrayEquals(K_RE, mStateMachine.mKRe); + assertArrayEquals(MSK, mStateMachine.mMsk); + assertArrayEquals(EMSK, mStateMachine.mEmsk); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeCreatedStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeCreatedStateTest.java new file mode 100644 index 00000000..90d0bf66 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeCreatedStateTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA_PRIME; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapAkaPrimeMethodStateMachine.ChallengeState; + +import org.junit.Test; + +import java.util.ArrayList; + +public class EapAkaPrimeCreatedStateTest extends EapAkaPrimeStateTest { + @Test + public void testProcessTransitionToIdentityState() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + // Don't actually need any attributes in the attributeMap, since we only care about the + // state transition here. + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>(new EapAkaPrimeTypeData(EAP_AKA_IDENTITY, new ArrayList<>())); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mStateMachine.process(eapMessage); + + assertTrue(mStateMachine.getState() instanceof EapAkaMethodStateMachine.IdentityState); + + // decoded in CreatedState and IdentityState + verify(mMockTypeDataDecoder, times(2)).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockTelephonyManager, mMockTypeDataDecoder); + } + + @Test + public void testProcessTransitionToChallengeState() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + // Don't actually need any attributes in the attributeMap, since we only care about the + // state transition here. + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>(new EapAkaPrimeTypeData(EAP_AKA_CHALLENGE, new ArrayList<>())); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mStateMachine.process(eapMessage); + + ChallengeState challengeState = (ChallengeState) mStateMachine.getState(); + assertArrayEquals(EAP_IDENTITY_BYTES, challengeState.mIdentity); + + // decoded in CreatedState and ChallengeState + verify(mMockTypeDataDecoder, times(2)).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockTypeDataDecoder); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeIdentityStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeIdentityStateTest.java new file mode 100644 index 00000000..73191fb0 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeIdentityStateTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA_PRIME; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_IDENTITY_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.IMSI; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAnyIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapAkaPrimeMethodStateMachine.ChallengeState; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +public class EapAkaPrimeIdentityStateTest extends EapAkaPrimeStateTest { + @Before + public void setUp() { + super.setUp(); + + mStateMachine.transitionTo(mStateMachine.new IdentityState()); + } + + @Test + public void testProcessIdentityRequest() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>( + new EapAkaPrimeTypeData(EAP_AKA_IDENTITY, Arrays.asList(new AtAnyIdReq()))); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager.getSubscriberId()).thenReturn(IMSI); + + EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_PRIME_IDENTITY_RESPONSE, eapResponse.packet); + + verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager).getSubscriberId(); + verifyNoMoreInteractions(mMockTypeDataDecoder, mMockTelephonyManager); + } + + @Test + public void testProcessTransitionToChallengeState() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + // Don't actually need any attributes in the attributeMap, since we only care about the + // state transition here. + DecodeResult<EapAkaTypeData> decodeResult = + new DecodeResult<>(new EapAkaPrimeTypeData(EAP_AKA_CHALLENGE, new ArrayList<>())); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mStateMachine.process(eapMessage); + + assertTrue(mStateMachine.getState() instanceof ChallengeState); + + // decoded in IdentityState and ChallengeState + verify(mMockTypeDataDecoder, times(2)).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockTypeDataDecoder); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeMethodStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeMethodStateMachineTest.java new file mode 100644 index 00000000..125b3235 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeMethodStateMachineTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA_PRIME; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE; +import static com.android.internal.net.eap.statemachine.EapAkaPrimeMethodStateMachine.K_AUT_LEN; +import static com.android.internal.net.eap.statemachine.EapAkaPrimeMethodStateMachine.K_RE_LEN; +import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.KEY_LEN; +import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.SESSION_KEY_LENGTH; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.statemachine.EapAkaMethodStateMachine.CreatedState; + +import org.junit.Test; + +import java.util.Arrays; + +public class EapAkaPrimeMethodStateMachineTest extends EapAkaPrimeTest { + private static final String TAG = EapAkaPrimeMethodStateMachineTest.class.getSimpleName(); + private static final byte[] K_AUT = + hexStringToByteArray( + "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"); + private static final byte[] MAC = hexStringToByteArray("0322b08b59cae2df8f766162ac76f30b"); + + @Test + public void testEapAkaPrimeMethodStateMachineStartState() { + assertTrue(mStateMachine.getState() instanceof CreatedState); + } + + @Test + public void testKeyLengths() { + assertEquals(KEY_LEN, mStateMachine.getKEncrLength()); + assertEquals(K_AUT_LEN, mStateMachine.getKAutLength()); + assertEquals(K_RE_LEN, mStateMachine.getKReLen()); + assertEquals(SESSION_KEY_LENGTH, mStateMachine.getMskLength()); + assertEquals(SESSION_KEY_LENGTH, mStateMachine.getEmskLength()); + } + + @Test + public void testIsValidMacUsesHmacSha256() throws Exception { + System.arraycopy(K_AUT, 0, mStateMachine.mKAut, 0, K_AUT.length); + + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, new byte[0]); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapAkaPrimeTypeData eapAkaPrimeTypeData = + new EapAkaPrimeTypeData(EAP_AKA_CHALLENGE, Arrays.asList(new AtMac(MAC))); + + assertTrue(mStateMachine.isValidMac(TAG, eapMessage, eapAkaPrimeTypeData, new byte[0])); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeStateTest.java new file mode 100644 index 00000000..18fce265 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeStateTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_NOTIFICATION; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA_PRIME; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_CLIENT_ERROR_UNABLE_TO_PROCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.EapMethodState; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; + +import org.junit.Test; + +public class EapAkaPrimeStateTest extends EapAkaPrimeTest { + protected static final String NOTIFICATION_MESSAGE = "test"; + + @Test + public void testProcessNotification() throws Exception { + EapData eapData = new EapData(EAP_NOTIFICATION, NOTIFICATION_MESSAGE.getBytes()); + EapMessage notification = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preNotification = (EapMethodState) mStateMachine.getState(); + + EapResult result = mStateMachine.process(notification); + assertEquals(preNotification, mStateMachine.getState()); + verifyNoMoreInteractions(mMockTelephonyManager, mMockTypeDataDecoder); + + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_RESPONSE_NOTIFICATION_PACKET, eapResponse.packet); + } + + @Test + public void testProcessInvalidDecodeResult() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preProcess = (EapMethodState) mStateMachine.getState(); + + AtClientErrorCode atClientErrorCode = AtClientErrorCode.UNABLE_TO_PROCESS; + DecodeResult<EapAkaTypeData> decodeResult = new DecodeResult<>(atClientErrorCode); + when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResult result = mStateMachine.process(eapMessage); + assertEquals(preProcess, mStateMachine.getState()); + verify(mMockTypeDataDecoder).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockTypeDataDecoder); + + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_AKA_PRIME_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + } + + @Test + public void testHandleEapFailure() throws Exception { + EapResult result = mStateMachine.process(new EapMessage(EAP_CODE_FAILURE, ID_INT, null)); + assertTrue(result instanceof EapFailure); + assertTrue(mStateMachine.getState() instanceof FinalState); + } + + @Test + public void testHandleEapSuccess() throws Exception { + EapResult result = mStateMachine.process(new EapMessage(EAP_CODE_SUCCESS, ID_INT, null)); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeTest.java new file mode 100644 index 00000000..48371bbd --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.eap.EapSessionConfig.EapAkaPrimeConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData; + +import org.junit.Before; + +public class EapAkaPrimeTest { + + // newtork name example in RFC 5448#3.1 + protected static final int SUB_ID = 1; + protected static final String PEER_NETWORK_NAME = "foo:bar"; + protected static final boolean ALLOW_MISMATCHED_NETWORK_NAMES = false; + protected static final EapAkaPrimeConfig EAP_AKA_PRIME_CONFIG = + new EapAkaPrimeConfig( + SUB_ID, APPTYPE_USIM, PEER_NETWORK_NAME, ALLOW_MISMATCHED_NETWORK_NAMES); + protected static final byte[] DUMMY_EAP_TYPE_DATA = hexStringToByteArray("112233445566"); + + // EAP-Identity = hex("test@android.net") + protected static final byte[] EAP_IDENTITY_BYTES = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + + protected Context mMockContext; + protected TelephonyManager mMockTelephonyManager; + protected EapAkaPrimeTypeData.EapAkaPrimeTypeDataDecoder mMockTypeDataDecoder; + + protected EapAkaPrimeMethodStateMachine mStateMachine; + + @Before + public void setUp() { + mMockContext = mock(Context.class); + mMockTelephonyManager = mock(TelephonyManager.class); + mMockTypeDataDecoder = mock(EapAkaPrimeTypeData.EapAkaPrimeTypeDataDecoder.class); + + when(mMockContext.getSystemService(eq(Context.TELEPHONY_SERVICE))) + .thenReturn(mMockTelephonyManager); + when(mMockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mMockTelephonyManager); + + mStateMachine = + new EapAkaPrimeMethodStateMachine( + mMockContext, + EAP_IDENTITY_BYTES, + EAP_AKA_PRIME_CONFIG, + mMockTypeDataDecoder); + + verify(mMockContext).getSystemService(eq(Context.TELEPHONY_SERVICE)); + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaStateTest.java new file mode 100644 index 00000000..23b240b9 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaStateTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_NOTIFICATION; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_AKA; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_NOTIFICATION_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_NOTIFICATION; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_PRE_CHALLENGE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.eap.EapSessionConfig.EapAkaConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapAkaTypeData.EapAkaTypeDataDecoder; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.EapMethodState; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +public class EapAkaStateTest { + protected static final int SUB_ID = 1; + protected static final String NOTIFICATION_MESSAGE = "test"; + protected static final byte[] DUMMY_EAP_TYPE_DATA = hexStringToByteArray("112233445566"); + + // EAP-Identity = hex("test@android.net") + protected static final byte[] EAP_IDENTITY_BYTES = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + + protected TelephonyManager mMockTelephonyManager; + protected EapAkaTypeDataDecoder mMockEapAkaTypeDataDecoder; + + protected EapAkaConfig mEapAkaConfig = new EapAkaConfig(SUB_ID, APPTYPE_USIM); + protected EapAkaMethodStateMachine mEapAkaMethodStateMachine; + + @Before + public void setUp() { + mMockTelephonyManager = mock(TelephonyManager.class); + mMockEapAkaTypeDataDecoder = mock(EapAkaTypeDataDecoder.class); + + when(mMockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mMockTelephonyManager); + + mEapAkaMethodStateMachine = + new EapAkaMethodStateMachine( + mMockTelephonyManager, + EAP_IDENTITY_BYTES, + mEapAkaConfig, + mMockEapAkaTypeDataDecoder, + true); + + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + } + + @Test + public void testProcessNotification() throws Exception { + EapData eapData = new EapData(EAP_NOTIFICATION, NOTIFICATION_MESSAGE.getBytes()); + EapMessage notification = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preNotification = (EapMethodState) mEapAkaMethodStateMachine.getState(); + + EapResult result = mEapAkaMethodStateMachine.process(notification); + assertEquals(preNotification, mEapAkaMethodStateMachine.getState()); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + + assertTrue(result instanceof EapResult.EapResponse); + EapResult.EapResponse eapResponse = (EapResult.EapResponse) result; + assertArrayEquals(EAP_RESPONSE_NOTIFICATION_PACKET, eapResponse.packet); + } + + @Test + public void testProcessEapAkaNotification() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preProcess = (EapMethodState) mEapAkaMethodStateMachine.getState(); + EapAkaTypeData typeData = + new EapAkaTypeData( + EAP_AKA_NOTIFICATION, + Arrays.asList(new AtNotification(GENERAL_FAILURE_PRE_CHALLENGE))); + + DecodeResult<EapAkaTypeData> decodeResult = new DecodeResult<>(typeData); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_AKA_NOTIFICATION_RESPONSE, eapResponse.packet); + assertEquals(preProcess, mEapAkaMethodStateMachine.getState()); + verify(mMockEapAkaTypeDataDecoder).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + } + + @Test + public void testProcessInvalidDecodeResult() throws Exception { + EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preProcess = (EapMethodState) mEapAkaMethodStateMachine.getState(); + + AtClientErrorCode atClientErrorCode = AtClientErrorCode.UNABLE_TO_PROCESS; + DecodeResult<EapAkaTypeData> decodeResult = new DecodeResult<>(atClientErrorCode); + when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResult result = mEapAkaMethodStateMachine.process(eapMessage); + assertEquals(preProcess, mEapAkaMethodStateMachine.getState()); + verify(mMockEapAkaTypeDataDecoder).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapAkaTypeDataDecoder); + + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_AKA_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2AwaitingEapFailureStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2AwaitingEapFailureStateTest.java new file mode 100644 index 00000000..1dd60c0c --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2AwaitingEapFailureStateTest.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import org.junit.Before; + +public class EapMsChapV2AwaitingEapFailureStateTest extends EapMsChapV2StateTest { + @Before + @Override + public void setUp() { + super.setUp(); + + mStateMachine.transitionTo(mStateMachine.new AwaitingEapFailureState()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2AwaitingEapSuccessStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2AwaitingEapSuccessStateTest.java new file mode 100644 index 00000000..d4377cb6 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2AwaitingEapSuccessStateTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_MSK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_NT_RESPONSE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapSuccess; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; + +import org.junit.Before; +import org.junit.Test; + +public class EapMsChapV2AwaitingEapSuccessStateTest extends EapMsChapV2StateTest { + @Before + @Override + public void setUp() { + super.setUp(); + + mStateMachine.transitionTo( + mStateMachine.new AwaitingEapSuccessState(MSCHAP_V2_NT_RESPONSE)); + } + + @Test + @Override + public void testHandleEapSuccess() throws Exception { + EapResult result = mStateMachine.process(new EapMessage(EAP_CODE_SUCCESS, ID_INT, null)); + EapSuccess eapSuccess = (EapSuccess) result; + assertArrayEquals(MSCHAP_V2_MSK, eapSuccess.msk); + assertArrayEquals(new byte[0], eapSuccess.emsk); + assertTrue(mStateMachine.getState() instanceof FinalState); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2ChallengeStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2ChallengeStateTest.java new file mode 100644 index 00000000..ec442dab --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2ChallengeStateTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_MSCHAP_V2; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_MSCHAP_V2_CHALLENGE_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_AUTHENTICATOR_CHALLENGE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PEER_CHALLENGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SERVER_NAME_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2ChallengeRequest.TYPE_DATA_HEADER_SIZE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2ChallengeRequest.VALUE_SIZE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2ChallengeRequest; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder.DecodeResult; +import com.android.internal.net.eap.statemachine.EapMsChapV2MethodStateMachine.ValidateAuthenticatorState; + +import org.junit.Before; +import org.junit.Test; + +public class EapMsChapV2ChallengeStateTest extends EapMsChapV2StateTest { + @Before + @Override + public void setUp() { + super.setUp(); + + mStateMachine.transitionTo(mStateMachine.new ChallengeState()); + } + + @Test + public void testProcessChallenge() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, DUMMY_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + EapMsChapV2ChallengeRequest challengeRequest = + new EapMsChapV2ChallengeRequest( + MSCHAP_V2_ID_INT, + TYPE_DATA_HEADER_SIZE + VALUE_SIZE + SERVER_NAME_BYTES.length, + MSCHAP_V2_AUTHENTICATOR_CHALLENGE, + SERVER_NAME_BYTES); + when(mMockTypeDataDecoder.decodeChallengeRequest(any(String.class), eq(DUMMY_TYPE_DATA))) + .thenReturn(new DecodeResult<>(challengeRequest)); + + doAnswer(invocation -> { + byte[] dst = invocation.getArgument(0); + System.arraycopy(MSCHAP_V2_PEER_CHALLENGE, 0, dst, 0, MSCHAP_V2_PEER_CHALLENGE.length); + return null; + }).when(mMockSecureRandom).nextBytes(eq(new byte[MSCHAP_V2_PEER_CHALLENGE.length])); + + EapResult result = mStateMachine.process(eapMessage); + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_MSCHAP_V2_CHALLENGE_RESPONSE, eapResponse.packet); + assertTrue(mStateMachine.getState() instanceof ValidateAuthenticatorState); + verify(mMockSecureRandom).nextBytes(any(byte[].class)); + verify(mMockTypeDataDecoder).decodeChallengeRequest(any(String.class), eq(DUMMY_TYPE_DATA)); + } + + @Test + public void testIncorrectTypeData() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, DUMMY_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + when(mMockTypeDataDecoder.decodeChallengeRequest(any(String.class), eq(DUMMY_TYPE_DATA))) + .thenReturn( + new DecodeResult<>( + new EapError( + new EapMsChapV2ParsingException("incorrect type data")))); + + EapResult result = mStateMachine.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + verify(mMockTypeDataDecoder).decodeChallengeRequest(any(String.class), eq(DUMMY_TYPE_DATA)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2CreatedStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2CreatedStateTest.java new file mode 100644 index 00000000..633e0ebd --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2CreatedStateTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_MSCHAP_V2; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.CHALLENGE_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.SERVER_NAME_BYTES; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2ChallengeRequest; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder.DecodeResult; +import com.android.internal.net.eap.statemachine.EapMsChapV2MethodStateMachine.ValidateAuthenticatorState; + +import org.junit.Test; + +public class EapMsChapV2CreatedStateTest extends EapMsChapV2StateTest { + @Test + public void testProcessChallengeRequest() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, DUMMY_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + when(mMockTypeDataDecoder.decodeChallengeRequest(any(String.class), eq(DUMMY_TYPE_DATA))) + .thenReturn( + new DecodeResult<>( + new EapMsChapV2ChallengeRequest( + 0, 0, CHALLENGE_BYTES, SERVER_NAME_BYTES))); + + mStateMachine.process(eapMessage); + + assertTrue(mStateMachine.getState() instanceof ValidateAuthenticatorState); + verify(mMockTypeDataDecoder, times(2)) + .decodeChallengeRequest(any(String.class), eq(DUMMY_TYPE_DATA)); + } + + @Test + public void testIncorrectTypeData() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, DUMMY_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + when(mMockTypeDataDecoder.decodeChallengeRequest( + eq(CREATED_STATE_TAG), eq(DUMMY_TYPE_DATA))) + .thenReturn( + new DecodeResult<>( + new EapError( + new EapMsChapV2ParsingException("incorrect type data")))); + + EapResult result = mStateMachine.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapMsChapV2ParsingException); + verify(mMockTypeDataDecoder) + .decodeChallengeRequest(eq(CREATED_STATE_TAG), eq(DUMMY_TYPE_DATA)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2MethodStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2MethodStateMachineTest.java new file mode 100644 index 00000000..b24c3f82 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2MethodStateMachineTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_MSCHAP_V2; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_AUTHENTICATOR_CHALLENGE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_AUTHENTICATOR_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_CHALLENGE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_MASTER_KEY; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_MSK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_NT_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD_HASH; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD_HASH_HASH; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD_UTF_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PEER_CHALLENGE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_RECEIVE_START_KEY; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_SEND_START_KEY; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_USERNAME; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_USERNAME_ASCII_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.net.eap.EapSessionConfig.EapMsChapV2Config; + +import com.android.internal.net.eap.statemachine.EapMsChapV2MethodStateMachine.CreatedState; +import com.android.internal.net.utils.Log; + +import org.junit.Before; +import org.junit.Test; + +import java.security.SecureRandom; + +public class EapMsChapV2MethodStateMachineTest { + private EapMsChapV2Config mEapMsChapV2Config; + private EapMsChapV2MethodStateMachine mStateMachine; + + @Before + public void setUp() { + mEapMsChapV2Config = new EapMsChapV2Config(MSCHAP_V2_USERNAME, MSCHAP_V2_PASSWORD); + mStateMachine = new EapMsChapV2MethodStateMachine(mEapMsChapV2Config, new SecureRandom()); + } + + @Test + public void testGetEapMethod() { + assertEquals(EAP_TYPE_MSCHAP_V2, mStateMachine.getEapMethod()); + } + + @Test + public void testStartsOnCreatedState() { + assertTrue(mStateMachine.getState() instanceof CreatedState); + } + + // Tests for MS CHAPv2 authentication utils. Test vectors from RFC 2759#9.2. + + @Test + public void testUsernameToBytes() { + assertArrayEquals( + MSCHAP_V2_USERNAME_ASCII_BYTES, + EapMsChapV2MethodStateMachine.usernameToBytes(MSCHAP_V2_USERNAME)); + } + + @Test + public void testPasswordToBytes() { + assertArrayEquals( + MSCHAP_V2_PASSWORD_UTF_BYTES, + EapMsChapV2MethodStateMachine.passwordToBytes(MSCHAP_V2_PASSWORD)); + } + + @Test + public void testGenerateNtResponse() throws Exception { + byte[] ntResponse = + EapMsChapV2MethodStateMachine.generateNtResponse( + MSCHAP_V2_AUTHENTICATOR_CHALLENGE, + MSCHAP_V2_PEER_CHALLENGE, + MSCHAP_V2_USERNAME, + MSCHAP_V2_PASSWORD); + assertArrayEquals(MSCHAP_V2_NT_RESPONSE, ntResponse); + } + + @Test + public void testChallengeHash() throws Exception { + byte[] challenge = + EapMsChapV2MethodStateMachine.challengeHash( + MSCHAP_V2_PEER_CHALLENGE, + MSCHAP_V2_AUTHENTICATOR_CHALLENGE, + MSCHAP_V2_USERNAME); + assertArrayEquals(MSCHAP_V2_CHALLENGE, challenge); + } + + @Test + public void testNtPasswordHash() { + byte[] passwordHash = EapMsChapV2MethodStateMachine.ntPasswordHash(MSCHAP_V2_PASSWORD); + assertArrayEquals(MSCHAP_V2_PASSWORD_HASH, passwordHash); + } + + @Test + public void testHashNtPasswordHash() { + byte[] passwordHashHash = + EapMsChapV2MethodStateMachine.hashNtPasswordHash(MSCHAP_V2_PASSWORD_HASH); + assertArrayEquals(MSCHAP_V2_PASSWORD_HASH_HASH, passwordHashHash); + } + + @Test + public void testChallengeResponse() throws Exception { + byte[] challengeResponse = + EapMsChapV2MethodStateMachine.challengeResponse( + MSCHAP_V2_CHALLENGE, MSCHAP_V2_PASSWORD_HASH); + assertArrayEquals(MSCHAP_V2_NT_RESPONSE, challengeResponse); + } + + @Test + public void testGenerateAuthenticatorResponse() throws Exception { + byte[] authenticatorResponse = + EapMsChapV2MethodStateMachine.generateAuthenticatorResponse( + MSCHAP_V2_PASSWORD, + MSCHAP_V2_NT_RESPONSE, + MSCHAP_V2_PEER_CHALLENGE, + MSCHAP_V2_AUTHENTICATOR_CHALLENGE, + MSCHAP_V2_USERNAME); + assertArrayEquals(MSCHAP_V2_AUTHENTICATOR_RESPONSE, authenticatorResponse); + } + + @Test + public void testCheckAuthenticatorResponse() throws Exception { + assertTrue( + "AuthenticatorResponse didn't match computed response", + EapMsChapV2MethodStateMachine.checkAuthenticatorResponse( + MSCHAP_V2_PASSWORD, + MSCHAP_V2_NT_RESPONSE, + MSCHAP_V2_PEER_CHALLENGE, + MSCHAP_V2_AUTHENTICATOR_CHALLENGE, + MSCHAP_V2_USERNAME, + MSCHAP_V2_AUTHENTICATOR_RESPONSE)); + } + + @Test + public void testGetMasterKey() throws Exception { + byte[] masterKey = + EapMsChapV2MethodStateMachine.getMasterKey( + MSCHAP_V2_PASSWORD_HASH_HASH, MSCHAP_V2_NT_RESPONSE); + assertArrayEquals(MSCHAP_V2_MASTER_KEY, masterKey); + } + + @Test + public void testGetAsymmetricStartKeySendKey() throws Exception { + byte[] startKey = + EapMsChapV2MethodStateMachine.getAsymmetricStartKey(MSCHAP_V2_MASTER_KEY, true); + assertArrayEquals(Log.byteArrayToHexString(startKey), MSCHAP_V2_SEND_START_KEY, startKey); + } + + @Test + public void testGetAsymmetricStartKeyReceiveKey() throws Exception { + byte[] receiveKey = + EapMsChapV2MethodStateMachine.getAsymmetricStartKey(MSCHAP_V2_MASTER_KEY, false); + assertArrayEquals(MSCHAP_V2_RECEIVE_START_KEY, receiveKey); + } + + @Test + public void testGenerateMsk() throws Exception { + byte[] msk = + EapMsChapV2MethodStateMachine.generateMsk( + MSCHAP_V2_PASSWORD, MSCHAP_V2_NT_RESPONSE); + assertArrayEquals(MSCHAP_V2_MSK, msk); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2StateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2StateTest.java new file mode 100644 index 00000000..1e4a0c58 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2StateTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_NOTIFICATION; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_USERNAME; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import android.net.eap.EapSessionConfig.EapMsChapV2Config; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.EapMethodState; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; + +import org.junit.Before; +import org.junit.Test; + +import java.security.SecureRandom; + +public class EapMsChapV2StateTest { + protected static final String CREATED_STATE_TAG = "CreatedState"; + protected static final String NOTIFICATION_MESSAGE = "test"; + protected static final byte[] DUMMY_TYPE_DATA = hexStringToByteArray("00112233"); + + protected SecureRandom mMockSecureRandom; + protected EapMsChapV2TypeDataDecoder mMockTypeDataDecoder; + + protected EapMsChapV2Config mEapMsChapV2Config; + protected EapMsChapV2MethodStateMachine mStateMachine; + + @Before + public void setUp() { + mMockSecureRandom = mock(SecureRandom.class); + mMockTypeDataDecoder = mock(EapMsChapV2TypeDataDecoder.class); + + mEapMsChapV2Config = new EapMsChapV2Config(MSCHAP_V2_USERNAME, MSCHAP_V2_PASSWORD); + mStateMachine = + new EapMsChapV2MethodStateMachine( + mEapMsChapV2Config, mMockSecureRandom, mMockTypeDataDecoder); + } + + @Test + public void testHandleEapFailure() throws Exception { + EapResult result = mStateMachine.process(new EapMessage(EAP_CODE_FAILURE, ID_INT, null)); + assertTrue(result instanceof EapFailure); + assertTrue(mStateMachine.getState() instanceof FinalState); + } + + @Test + public void testHandleEapSuccess() throws Exception { + EapResult result = mStateMachine.process(new EapMessage(EAP_CODE_SUCCESS, ID_INT, null)); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testHandleEapNotification() throws Exception { + EapData eapData = new EapData(EAP_NOTIFICATION, NOTIFICATION_MESSAGE.getBytes()); + EapMessage notification = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preNotification = (EapMethodState) mStateMachine.getState(); + + EapResult result = mStateMachine.process(notification); + assertEquals(preNotification, mStateMachine.getState()); + + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_RESPONSE_NOTIFICATION_PACKET, eapResponse.packet); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2ValidateAuthenticatorStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2ValidateAuthenticatorStateTest.java new file mode 100644 index 00000000..dda7d5bc --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2ValidateAuthenticatorStateTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_MSCHAP_V2; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_MSCHAP_V2_FAILURE_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_MSCHAP_V2_SUCCESS_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.INVALID_AUTHENTICATOR_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_AUTHENTICATOR_CHALLENGE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_AUTHENTICATOR_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_NT_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PEER_CHALLENGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.CHALLENGE_BYTES; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.ERROR_CODE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.MESSAGE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.PASSWORD_CHANGE_PROTOCOL; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2PacketDefinitions.RETRY_BIT; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_FAILURE; +import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_SUCCESS; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2FailureRequest; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2SuccessRequest; +import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder.DecodeResult; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; +import com.android.internal.net.eap.statemachine.EapMsChapV2MethodStateMachine.AwaitingEapFailureState; +import com.android.internal.net.eap.statemachine.EapMsChapV2MethodStateMachine.AwaitingEapSuccessState; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.BufferUnderflowException; + +public class EapMsChapV2ValidateAuthenticatorStateTest extends EapMsChapV2StateTest { + private static final int INVALID_OP_CODE = -1; + + @Before + @Override + public void setUp() { + super.setUp(); + + mStateMachine.transitionTo( + mStateMachine.new ValidateAuthenticatorState( + MSCHAP_V2_AUTHENTICATOR_CHALLENGE, + MSCHAP_V2_PEER_CHALLENGE, + MSCHAP_V2_NT_RESPONSE)); + } + + @Test + public void testProcessSuccessRequest() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, DUMMY_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + EapMsChapV2SuccessRequest successRequest = + new EapMsChapV2SuccessRequest( + MSCHAP_V2_ID_INT, 0, MSCHAP_V2_AUTHENTICATOR_RESPONSE, MESSAGE); + + when(mMockTypeDataDecoder.getOpCode(eq(DUMMY_TYPE_DATA))).thenReturn(EAP_MSCHAP_V2_SUCCESS); + when(mMockTypeDataDecoder.decodeSuccessRequest(any(String.class), eq(DUMMY_TYPE_DATA))) + .thenReturn(new DecodeResult<>(successRequest)); + + EapResult result = mStateMachine.process(eapMessage); + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_MSCHAP_V2_SUCCESS_RESPONSE, eapResponse.packet); + assertTrue(mStateMachine.getState() instanceof AwaitingEapSuccessState); + verify(mMockTypeDataDecoder).getOpCode(eq(DUMMY_TYPE_DATA)); + verify(mMockTypeDataDecoder).decodeSuccessRequest(any(String.class), eq(DUMMY_TYPE_DATA)); + } + + @Test + public void testProcessInvalidSuccessRequest() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, DUMMY_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + EapMsChapV2SuccessRequest successRequest = + new EapMsChapV2SuccessRequest( + MSCHAP_V2_ID_INT, 0, INVALID_AUTHENTICATOR_RESPONSE, MESSAGE); + + when(mMockTypeDataDecoder.getOpCode(eq(DUMMY_TYPE_DATA))).thenReturn(EAP_MSCHAP_V2_SUCCESS); + when(mMockTypeDataDecoder.decodeSuccessRequest(any(String.class), eq(DUMMY_TYPE_DATA))) + .thenReturn(new DecodeResult<>(successRequest)); + + EapResult result = mStateMachine.process(eapMessage); + assertTrue(result instanceof EapFailure); + assertTrue(mStateMachine.getState() instanceof FinalState); + verify(mMockTypeDataDecoder).getOpCode(eq(DUMMY_TYPE_DATA)); + verify(mMockTypeDataDecoder).decodeSuccessRequest(any(String.class), eq(DUMMY_TYPE_DATA)); + } + + @Test + public void testProcessFailureRequest() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, DUMMY_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + EapMsChapV2FailureRequest failureRequest = + new EapMsChapV2FailureRequest( + MSCHAP_V2_ID_INT, + 0, + ERROR_CODE, + RETRY_BIT, + CHALLENGE_BYTES, + PASSWORD_CHANGE_PROTOCOL, + MESSAGE); + + when(mMockTypeDataDecoder.getOpCode(eq(DUMMY_TYPE_DATA))).thenReturn(EAP_MSCHAP_V2_FAILURE); + when(mMockTypeDataDecoder.decodeFailureRequest(any(String.class), eq(DUMMY_TYPE_DATA))) + .thenReturn(new DecodeResult<>(failureRequest)); + + EapResult result = mStateMachine.process(eapMessage); + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_MSCHAP_V2_FAILURE_RESPONSE, eapResponse.packet); + assertTrue(mStateMachine.getState() instanceof AwaitingEapFailureState); + verify(mMockTypeDataDecoder).getOpCode(eq(DUMMY_TYPE_DATA)); + verify(mMockTypeDataDecoder).decodeFailureRequest(any(String.class), eq(DUMMY_TYPE_DATA)); + } + + @Test + public void testProcessEmptyTypeData() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, new byte[0]); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + when(mMockTypeDataDecoder.getOpCode(eq(new byte[0]))) + .thenThrow(new BufferUnderflowException()); + + EapResult result = mStateMachine.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof BufferUnderflowException); + verify(mMockTypeDataDecoder).getOpCode(eq(new byte[0])); + } + + @Test + public void testProcessInvalidPacket() throws Exception { + EapData eapData = new EapData(EAP_TYPE_MSCHAP_V2, DUMMY_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + when(mMockTypeDataDecoder.getOpCode(eq(DUMMY_TYPE_DATA))).thenReturn(INVALID_OP_CODE); + + EapResult result = mStateMachine.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + verify(mMockTypeDataDecoder).getOpCode(eq(DUMMY_TYPE_DATA)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimAkaMethodStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimAkaMethodStateMachineTest.java new file mode 100644 index 00000000..7adc9f75 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimAkaMethodStateMachineTest.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_SIM; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.COMPUTED_MAC; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_CHALLENGE_RESPONSE_MAC_INPUT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_CHALLENGE_RESPONSE_WITH_MAC; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_CLIENT_ERROR_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_IDENTITY; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_REQUEST_WITH_EMPTY_MAC; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_RESPONSE_WITH_EMPTY_MAC; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_RESPONSE_WITH_MAC; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_RESPONSE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EMSK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EMSK_STRING; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.KC_1; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.KC_2; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.K_AUT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.K_AUT_STRING; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.K_ENCR; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.K_ENCR_STRING; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MAC_INPUT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSK_STRING; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ORIGINAL_MAC; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SRES_1; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SRES_BYTES; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_POST_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_PRE_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_CLIENT_ERROR; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_NOTIFICATION; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_START; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_IDENTITY; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.IDENTITY; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_MT; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_MT_STRING; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_2_BYTES; +import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.KEY_LEN; +import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.MAC_ALGORITHM_STRING; +import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.MASTER_KEY_GENERATION_ALG; +import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.SESSION_KEY_LENGTH; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.eap.EapSessionConfig.EapSimConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.crypto.Fips186_2Prf; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaAuthenticationFailureException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtIdentity; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandSim; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtSelectedVersion; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimTypeData; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.EapMethodState; + +import org.junit.Before; +import org.junit.Test; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class EapSimAkaMethodStateMachineTest { + private static final String TAG = EapSimAkaMethodStateMachineTest.class.getSimpleName(); + private static final int SUB_ID = 1; + private static final int AT_RAND_LEN = 36; + private static final String VERSIONS_STRING = "0001"; + private static final String SELECTED_VERSION = "0001"; + private static final byte[] SHA_1_INPUT = hexStringToByteArray("0123456789ABCDEF"); + private static final byte[] FORMATTED_UICC_CHALLENGE = + hexStringToByteArray("1000112233445566778899AABBCCDDEEFF"); + private static final String BASE_64_CHALLENGE = "EAARIjNEVWZ3iJmqu8zd7v8="; + private static final String BASE_64_RESPONSE = "BBEiM0QIAQIDBAUGBwg="; + private static final byte[] UICC_RESPONSE = + hexStringToByteArray("04" + SRES_1 + "08" + KC_1); + + // EAP-Identity = hex("test@android.net") + protected static final byte[] EAP_IDENTITY_BYTES = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + + // K_encr + K_aut + MSK + EMSK + private static final int PRF_OUTPUT_BYTES = (2 * KEY_LEN) + (2 * SESSION_KEY_LENGTH); + + private TelephonyManager mMockTelephonyManager; + private EapSimConfig mEapSimConfig; + private EapSimAkaMethodStateMachine mStateMachine; + + @Before + public void setUp() { + mMockTelephonyManager = mock(TelephonyManager.class); + mEapSimConfig = new EapSimConfig(SUB_ID, TelephonyManager.APPTYPE_USIM); + + mStateMachine = + new EapSimAkaMethodStateMachine( + mMockTelephonyManager, EAP_IDENTITY_BYTES, mEapSimConfig) { + @Override + EapSimAkaTypeData getEapSimAkaTypeData(AtClientErrorCode clientErrorCode) { + return new EapSimTypeData( + EAP_SIM_CLIENT_ERROR, Arrays.asList(clientErrorCode)); + } + + @Override + EapSimAkaTypeData getEapSimAkaTypeData( + int eapSubtype, List<EapSimAkaAttribute> attributes) { + return new EapSimTypeData(eapSubtype, attributes); + } + + @Override + int getEapMethod() { + return EAP_TYPE_SIM; + } + }; + mStateMachine = spy(mStateMachine); + } + + @Test + public void testBuildClientErrorResponse() { + AtClientErrorCode errorCode = AtClientErrorCode.UNSUPPORTED_VERSION; + + EapResult result = + mStateMachine.buildClientErrorResponse(ID_INT, EAP_TYPE_SIM, errorCode); + assertTrue(result instanceof EapResult.EapResponse); + EapResult.EapResponse eapResponse = (EapResult.EapResponse) result; + assertArrayEquals(EAP_SIM_CLIENT_ERROR_RESPONSE, eapResponse.packet); + } + + @Test + public void testBuildResponseMessage() throws Exception { + List<EapSimAkaAttribute> attributes = new ArrayList<>(); + attributes.add(new AtSelectedVersion(1)); + attributes.add(new AtIdentity(AT_IDENTITY.length, IDENTITY)); + int identifier = ID_INT; + + EapResult result = + mStateMachine.buildResponseMessage( + EAP_TYPE_SIM, + EAP_SIM_START, + identifier, + attributes); + assertTrue(result instanceof EapResult); + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_SIM_RESPONSE_PACKET, eapResponse.packet); + } + + @Test + public void testGenerateAndPersistKeys() { + byte[] mkInput = hexStringToByteArray( + EAP_SIM_IDENTITY + + KC_1 + + KC_2 + + NONCE_MT_STRING + + VERSIONS_STRING + + SELECTED_VERSION); + MessageDigest mockSha1 = mock(MessageDigest.class); + when(mockSha1.digest(eq(mkInput))).thenReturn(MK); + + byte[] keys = hexStringToByteArray(K_ENCR_STRING + K_AUT_STRING + MSK_STRING + EMSK_STRING); + Fips186_2Prf mockFips186_2Prf = mock(Fips186_2Prf.class); + when(mockFips186_2Prf.getRandom(eq(MK), eq(PRF_OUTPUT_BYTES))).thenReturn(keys); + + mStateMachine.generateAndPersistKeys(TAG, mockSha1, mockFips186_2Prf, mkInput); + assertArrayEquals(K_ENCR, mStateMachine.mKEncr); + assertArrayEquals(K_AUT, mStateMachine.mKAut); + assertArrayEquals(MSK, mStateMachine.mMsk); + assertArrayEquals(EMSK, mStateMachine.mEmsk); + + verify(mockSha1).digest(eq(mkInput)); + verify(mockFips186_2Prf).getRandom(eq(MK), eq(PRF_OUTPUT_BYTES)); + verifyNoMoreInteractions(mockSha1, mockFips186_2Prf); + } + + /** + * Test that we can actually instantiate and use the SHA-1algorithm. + */ + @Test + public void testCreateSha1() throws Exception { + MessageDigest sha1 = MessageDigest.getInstance(MASTER_KEY_GENERATION_ALG); + byte[] sha1Result = sha1.digest(SHA_1_INPUT); + assertFalse(Arrays.equals(SHA_1_INPUT, sha1Result)); + } + + /** + * Test that we can actually instantiate and use the HMAC-SHA-1 algorithm. + */ + @Test + public void testCreateHmacSha1() throws Exception { + Mac macAlgorithm = Mac.getInstance(MAC_ALGORITHM_STRING); + macAlgorithm.init(new SecretKeySpec(K_AUT, MAC_ALGORITHM_STRING)); + byte[] mac = macAlgorithm.doFinal(MAC_INPUT); + assertFalse(Arrays.equals(MAC_INPUT, mac)); + } + + @Test + public void testProcessUiccAuthentication() throws Exception { + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE)).thenReturn(BASE_64_RESPONSE); + + byte[] result = + mStateMachine.processUiccAuthentication( + TAG, + TelephonyManager.AUTHTYPE_EAP_AKA, + FORMATTED_UICC_CHALLENGE); + + assertArrayEquals(UICC_RESPONSE, result); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE); + verifyNoMoreInteractions(mMockTelephonyManager); + } + + @Test + public void testProcessUiccAuthenticationNullResponse() throws Exception { + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE)).thenReturn(null); + + try { + mStateMachine.processUiccAuthentication( + TAG, + TelephonyManager.AUTHTYPE_EAP_AKA, + FORMATTED_UICC_CHALLENGE); + fail("EapSimAkaAuthenticationFailureException expected for null TelMan response"); + } catch (EapSimAkaAuthenticationFailureException expected) { + } + + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_AKA, + BASE_64_CHALLENGE); + verifyNoMoreInteractions(mMockTelephonyManager); + } + + @Test + public void testGetMac() throws Exception { + AtMac atMac = new AtMac(ORIGINAL_MAC); + AtRandSim atRandSim = new AtRandSim(AT_RAND_LEN, RAND_1_BYTES, RAND_2_BYTES); + EapSimTypeData eapSimTypeData = + new EapSimTypeData(EAP_SIM_CHALLENGE, Arrays.asList(atRandSim, atMac)); + + Mac mockMac = mock(Mac.class); + when(mockMac.doFinal(eq(MAC_INPUT))).thenReturn(COMPUTED_MAC); + mStateMachine.mMacAlgorithm = mockMac; + + byte[] mac = mStateMachine.getMac(EAP_CODE_REQUEST, ID_INT, eapSimTypeData, NONCE_MT); + assertArrayEquals(COMPUTED_MAC, mac); + AtMac postCalculationAtMac = (AtMac) eapSimTypeData.attributeMap.get(EAP_AT_MAC); + assertArrayEquals(ORIGINAL_MAC, postCalculationAtMac.mac); + + verify(mockMac).doFinal(eq(MAC_INPUT)); + verifyNoMoreInteractions(mockMac); + } + + @Test + public void testGetMacNoMacAlgorithm() throws Exception { + try { + mStateMachine.getMac(EAP_CODE_REQUEST, ID_INT, null, null); + fail("Expected IllegalStateException if Mac not set"); + } catch (IllegalStateException expected) { + } + } + + @Test + public void testReceivedValidMac() throws Exception { + AtMac atMac = new AtMac(ORIGINAL_MAC); + AtRandSim atRandSim = new AtRandSim(AT_RAND_LEN, RAND_1_BYTES, RAND_2_BYTES); + EapSimTypeData eapSimTypeData = + new EapSimTypeData(EAP_SIM_CHALLENGE, Arrays.asList(atRandSim, atMac)); + EapData eapData = new EapData(EAP_TYPE_SIM, new byte[0]); + EapMessage message = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + doReturn(ORIGINAL_MAC) + .when(mStateMachine) + .getMac(eq(EAP_CODE_REQUEST), eq(ID_INT), eq(eapSimTypeData), eq(NONCE_MT)); + + assertTrue(mStateMachine.isValidMac(TAG, message, eapSimTypeData, NONCE_MT)); + + doReturn(new byte[0]) + .when(mStateMachine) + .getMac(eq(EAP_CODE_REQUEST), eq(ID_INT), eq(eapSimTypeData), eq(NONCE_MT)); + + assertFalse(mStateMachine.isValidMac(TAG, message, eapSimTypeData, NONCE_MT)); + + verify(mStateMachine, times(2)) + .getMac(eq(EAP_CODE_REQUEST), eq(ID_INT), eq(eapSimTypeData), eq(NONCE_MT)); + } + + @Test + public void testBuildResponseMessageWithMac() { + Mac mockMac = mock(Mac.class); + when(mockMac.doFinal(eq(EAP_SIM_CHALLENGE_RESPONSE_MAC_INPUT))).thenReturn(COMPUTED_MAC); + mStateMachine.mMacAlgorithm = mockMac; + + EapResult result = + mStateMachine.buildResponseMessageWithMac(ID_INT, EAP_SIM_CHALLENGE, SRES_BYTES); + + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_SIM_CHALLENGE_RESPONSE_WITH_MAC, eapResponse.packet); + verify(mockMac).doFinal(eq(EAP_SIM_CHALLENGE_RESPONSE_MAC_INPUT)); + verifyNoMoreInteractions(mockMac); + } + + @Test + public void testHandleEapSimNotificationPreChallenge() throws Exception { + EapSimTypeData typeData = + new EapSimTypeData( + EAP_SIM_NOTIFICATION, + Arrays.asList(new AtNotification(GENERAL_FAILURE_PRE_CHALLENGE))); + + EapResponse eapResponse = + (EapResponse) + mStateMachine.handleEapSimAkaNotification(TAG, true, ID_INT, typeData); + assertArrayEquals(EAP_SIM_NOTIFICATION_RESPONSE, eapResponse.packet); + assertTrue(mStateMachine.mHasReceivedSimAkaNotification); + verify(mStateMachine, never()).transitionTo(any(EapMethodState.class)); + } + + @Test + public void testHandleEapSimNotificationPreChallengeInvalidPBit() throws Exception { + EapSimTypeData typeData = + new EapSimTypeData( + EAP_SIM_NOTIFICATION, + Arrays.asList(new AtNotification(GENERAL_FAILURE_POST_CHALLENGE))); + + EapResponse eapResponse = + (EapResponse) + mStateMachine.handleEapSimAkaNotification(TAG, true, ID_INT, typeData); + assertArrayEquals(EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + verify(mStateMachine, never()) + .transitionTo(any(EapMethodStateMachine.EapMethodState.class)); + } + + @Test + public void testHandleEapSimNotificationMultipleNotifications() throws Exception { + EapSimTypeData typeData = + new EapSimTypeData( + EAP_SIM_NOTIFICATION, + Arrays.asList(new AtNotification(GENERAL_FAILURE_PRE_CHALLENGE))); + + mStateMachine.handleEapSimAkaNotification(TAG, true, ID_INT, typeData); + + EapError eapError = + (EapError) mStateMachine.handleEapSimAkaNotification(TAG, true, ID_INT, typeData); + assertTrue(eapError.cause instanceof EapInvalidRequestException); + assertTrue(mStateMachine.mHasReceivedSimAkaNotification); + verify(mStateMachine, never()) + .transitionTo(any(EapMethodStateMachine.EapMethodState.class)); + } + + @Test + public void testHandleEapSimNotificationInvalidAtMac() throws Exception { + EapSimTypeData typeData = + new EapSimTypeData( + EAP_SIM_NOTIFICATION, + Arrays.asList( + new AtNotification(GENERAL_FAILURE_PRE_CHALLENGE), new AtMac())); + + EapResponse eapResponse = + (EapResponse) + mStateMachine.handleEapSimAkaNotification(TAG, true, ID_INT, typeData); + assertArrayEquals(EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + verify(mStateMachine, never()) + .transitionTo(any(EapMethodStateMachine.EapMethodState.class)); + } + + @Test + public void testHandleEapSimNotificationPostChallenge() throws Exception { + EapSimTypeData typeData = + new EapSimTypeData( + EAP_SIM_NOTIFICATION, + Arrays.asList( + new AtNotification(GENERAL_FAILURE_POST_CHALLENGE), + new AtMac(ORIGINAL_MAC))); + + Mac mockMac = mock(Mac.class); + when(mockMac.doFinal(eq(EAP_SIM_NOTIFICATION_REQUEST_WITH_EMPTY_MAC))) + .thenReturn(ORIGINAL_MAC); + when(mockMac.doFinal(eq(EAP_SIM_NOTIFICATION_RESPONSE_WITH_EMPTY_MAC))) + .thenReturn(COMPUTED_MAC); + mStateMachine.mMacAlgorithm = mockMac; + + EapResponse eapResponse = + (EapResponse) + mStateMachine.handleEapSimAkaNotification(TAG, false, ID_INT, typeData); + assertArrayEquals(EAP_SIM_NOTIFICATION_RESPONSE_WITH_MAC, eapResponse.packet); + assertTrue(mStateMachine.mHasReceivedSimAkaNotification); + verify(mStateMachine, never()).transitionTo(any(EapMethodState.class)); + + verify(mockMac).doFinal(eq(EAP_SIM_NOTIFICATION_REQUEST_WITH_EMPTY_MAC)); + verify(mockMac).doFinal(eq(EAP_SIM_NOTIFICATION_RESPONSE_WITH_EMPTY_MAC)); + verifyNoMoreInteractions(mockMac); + } + + @Test + public void testHandleEapSimNotificationPostChallengeInvalidAtMac() throws Exception { + EapSimTypeData typeData = + new EapSimTypeData( + EAP_SIM_NOTIFICATION, + Arrays.asList(new AtNotification(GENERAL_FAILURE_POST_CHALLENGE))); + + EapResponse eapResponse = + (EapResponse) + mStateMachine.handleEapSimAkaNotification(TAG, false, ID_INT, typeData); + assertArrayEquals(EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet); + verify(mStateMachine, never()).transitionTo(any(EapMethodState.class)); + } + + @Test + public void testKeyLengths() { + assertEquals(KEY_LEN, mStateMachine.getKEncrLength()); + assertEquals(KEY_LEN, mStateMachine.getKAutLength()); + assertEquals(SESSION_KEY_LENGTH, mStateMachine.getMskLength()); + assertEquals(SESSION_KEY_LENGTH, mStateMachine.getEmskLength()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimChallengeStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimChallengeStateTest.java new file mode 100644 index 00000000..bffc03ab --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimChallengeStateTest.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_IDENTITY; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_SIM; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.CHALLENGE_RESPONSE_INVALID_KC; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.CHALLENGE_RESPONSE_INVALID_SRES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_IDENTITY_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EMSK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.KC_1_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.KC_2_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SRES_1_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SRES_2_BYTES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.VALID_CHALLENGE_RESPONSE; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RAND; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_MT; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1_BYTES; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_2; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_2_BYTES; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.EapResult.EapSuccess; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaAuthenticationFailureException; +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidLengthException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNonceMt; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandSim; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.message.simaka.EapSimTypeData; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; +import com.android.internal.net.eap.statemachine.EapSimMethodStateMachine.ChallengeState; +import com.android.internal.net.eap.statemachine.EapSimMethodStateMachine.ChallengeState.RandChallengeResult; + +import org.junit.Test; + +import java.nio.BufferUnderflowException; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; + +public class EapSimChallengeStateTest extends EapSimStateTest { + private static final int VALID_SRES_LENGTH = 4; + private static final int INVALID_SRES_LENGTH = 5; + private static final int VALID_KC_LENGTH = 8; + private static final int INVALID_KC_LENGTH = 9; + private static final int AT_RAND_LENGTH = 36; + private static final List<Integer> VERSIONS = Arrays.asList(1); + + // Base64 of {@link EapTestAttributeDefinitions#RAND_1} + private static final String BASE_64_RAND_1 = "EAARIjNEVWZ3iJmqu8zd7v8="; + + // Base64 of {@link EapTestAttributeDefinitions#RAND_2} + private static final String BASE_64_RAND_2 = "EP/u3cy7qpmId2ZVRDMiEQA="; + + // Base64 of "04" + SRES_1 + "08" + KC_1 + private static final String BASE_64_RESP_1 = "BBEiM0QIAQIDBAUGBwg="; + + // Base64 of "04" + SRES_2 + "08" + KC_2 + private static final String BASE_64_RESP_2 = "BEQzIhEICAcGBQQDAgE="; + + // Base64 of "04" + SRES_1 + '081122" + private static final String BASE_64_INVALID_RESP = "BBEiM0QIESI="; + + private AtNonceMt mAtNonceMt; + private ChallengeState mChallengeState; + + @Override + public void setUp() { + super.setUp(); + + try { + mAtNonceMt = new AtNonceMt(NONCE_MT); + } catch (EapSimAkaInvalidAttributeException ex) { + // this will never happen + } + mChallengeState = mEapSimMethodStateMachine + .new ChallengeState(VERSIONS, mAtNonceMt, EAP_SIM_IDENTITY_BYTES); + mEapSimMethodStateMachine.transitionTo(mChallengeState); + } + + @Test + public void testProcessIncorrectEapMethodType() throws Exception { + EapData eapData = new EapData(EAP_IDENTITY, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + EapResult result = mChallengeState.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testProcessSuccess() throws Exception { + System.arraycopy(MSK, 0, mEapSimMethodStateMachine.mMsk, 0, MSK.length); + System.arraycopy(EMSK, 0, mEapSimMethodStateMachine.mEmsk, 0, EMSK.length); + + EapMessage input = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null); + EapResult result = mEapSimMethodStateMachine.process(input); + assertTrue(mEapSimMethodStateMachine.getState() instanceof FinalState); + + EapSuccess eapSuccess = (EapSuccess) result; + assertArrayEquals(MSK, eapSuccess.msk); + assertArrayEquals(EMSK, eapSuccess.emsk); + } + + @Test + public void testProcessFailure() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_FAILURE, ID_INT, null); + EapResult result = mEapSimMethodStateMachine.process(input); + assertTrue(mEapSimMethodStateMachine.getState() instanceof FinalState); + + assertTrue(result instanceof EapFailure); + } + + @Test + public void testIsValidChallengeAttributes() { + LinkedHashMap<Integer, EapSimAkaAttribute> attributeMap = new LinkedHashMap<>(); + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_CHALLENGE, attributeMap); + assertFalse(mChallengeState.isValidChallengeAttributes(eapSimTypeData)); + + attributeMap.put(EAP_AT_RAND, null); // value doesn't matter, just need key + eapSimTypeData = new EapSimTypeData(EAP_SIM_CHALLENGE, attributeMap); + assertFalse(mChallengeState.isValidChallengeAttributes(eapSimTypeData)); + + attributeMap.put(EAP_AT_MAC, null); // value doesn't matter, just need key + eapSimTypeData = new EapSimTypeData(EAP_SIM_CHALLENGE, attributeMap); + assertTrue(mChallengeState.isValidChallengeAttributes(eapSimTypeData)); + } + + @Test + public void testRandChallengeResultConstructor() { + try { + mChallengeState.new RandChallengeResult( + new byte[VALID_SRES_LENGTH], new byte[INVALID_KC_LENGTH]); + fail("EapSimAkaInvalidLengthException expected for invalid SRES lengths"); + } catch (EapSimAkaInvalidLengthException expected) { + } + + try { + mChallengeState.new RandChallengeResult( + new byte[INVALID_SRES_LENGTH], new byte[VALID_KC_LENGTH]); + fail("EapSimAkaInvalidLengthException expected for invalid Kc lengths"); + } catch (EapSimAkaInvalidLengthException expected) { + } + } + + @Test + public void testRandChallengeResultEquals() throws Exception { + RandChallengeResult resultA = + mChallengeState.new RandChallengeResult(SRES_1_BYTES, KC_1_BYTES); + RandChallengeResult resultB = + mChallengeState.new RandChallengeResult(SRES_1_BYTES, KC_1_BYTES); + RandChallengeResult resultC = + mChallengeState.new RandChallengeResult(SRES_2_BYTES, KC_2_BYTES); + + assertEquals(resultA, resultB); + assertNotEquals(resultA, resultC); + } + + @Test + public void testRandChallengeResultHashCode() throws Exception { + RandChallengeResult resultA = + mChallengeState.new RandChallengeResult(SRES_1_BYTES, KC_1_BYTES); + RandChallengeResult resultB = + mChallengeState.new RandChallengeResult(SRES_1_BYTES, KC_1_BYTES); + RandChallengeResult resultC = + mChallengeState.new RandChallengeResult(SRES_2_BYTES, KC_2_BYTES); + + assertEquals(resultA.hashCode(), resultB.hashCode()); + assertNotEquals(resultA.hashCode(), resultC.hashCode()); + } + + @Test + public void testGetRandChallengeResultFromResponse() throws Exception { + RandChallengeResult result = + mChallengeState.getRandChallengeResultFromResponse(VALID_CHALLENGE_RESPONSE); + + assertArrayEquals(SRES_1_BYTES, result.sres); + assertArrayEquals(KC_1_BYTES, result.kc); + } + + @Test + public void testGetRandChallengeResultFromResponseInvalidSres() { + try { + mChallengeState.getRandChallengeResultFromResponse(CHALLENGE_RESPONSE_INVALID_SRES); + fail("EapSimAkaInvalidLengthException expected for invalid SRES_1 length"); + } catch (EapSimAkaInvalidLengthException expected) { + } + } + + @Test + public void testGetRandChallengeResultFromResponseInvalidKc() { + try { + mChallengeState.getRandChallengeResultFromResponse(CHALLENGE_RESPONSE_INVALID_KC); + fail("EapSimAkaInvalidLengthException expected for invalid KC length"); + } catch (EapSimAkaInvalidLengthException expected) { + } + } + + @Test + public void testGetRandChallengeResults() throws Exception { + EapSimTypeData eapSimTypeData = + new EapSimTypeData(EAP_SIM_CHALLENGE, Arrays.asList( + new AtRandSim(AT_RAND_LENGTH, + hexStringToByteArray(RAND_1), + hexStringToByteArray(RAND_2)))); + + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_1)) + .thenReturn(BASE_64_RESP_1); + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_2)) + .thenReturn(BASE_64_RESP_2); + + List<RandChallengeResult> actualResult = + mChallengeState.getRandChallengeResults(eapSimTypeData); + + List<RandChallengeResult> expectedResult = Arrays.asList( + mChallengeState.new RandChallengeResult(SRES_1_BYTES, KC_1_BYTES), + mChallengeState.new RandChallengeResult(SRES_2_BYTES, KC_2_BYTES)); + assertEquals(expectedResult, actualResult); + + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_1); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_2); + verifyNoMoreInteractions(mMockTelephonyManager); + } + + @Test + public void testGetRandChallengeResultsBufferUnderflow() throws Exception { + EapSimTypeData eapSimTypeData = + new EapSimTypeData(EAP_SIM_CHALLENGE, Arrays.asList( + new AtRandSim(AT_RAND_LENGTH, + hexStringToByteArray(RAND_1), + hexStringToByteArray(RAND_2)))); + + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_1)) + .thenReturn(BASE_64_RESP_1); + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_2)) + .thenReturn(BASE_64_INVALID_RESP); + + try { + mChallengeState.getRandChallengeResults(eapSimTypeData); + fail("BufferUnderflowException expected for short Kc value"); + } catch (BufferUnderflowException ex) { + } + + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_1); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_2); + verifyNoMoreInteractions(mMockTelephonyManager); + } + + @Test + public void testProcessUiccAuthenticationNullResponse() throws Exception { + EapData eapData = new EapData(EAP_TYPE_SIM, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + AtRandSim atRandSim = new AtRandSim(AT_RAND_LENGTH, RAND_1_BYTES, RAND_2_BYTES); + + DecodeResult<EapSimTypeData> decodeResult = + new DecodeResult<>( + new EapSimTypeData( + EAP_SIM_CHALLENGE, + Arrays.asList(atRandSim, new AtMac()))); + when(mMockEapSimTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + when(mMockTelephonyManager + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_1)) + .thenReturn(null); + + EapError eapError = (EapError) mEapSimMethodStateMachine.process(eapMessage); + assertTrue(eapError.cause instanceof EapSimAkaAuthenticationFailureException); + + verify(mMockEapSimTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + verify(mMockTelephonyManager) + .getIccAuthentication( + TelephonyManager.APPTYPE_USIM, + TelephonyManager.AUTHTYPE_EAP_SIM, + BASE_64_RAND_1); + verifyNoMoreInteractions(mMockEapSimTypeDataDecoder, mMockTelephonyManager); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimCreatedStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimCreatedStateTest.java new file mode 100644 index 00000000..bdd19201 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimCreatedStateTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_IDENTITY; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_SIM; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtPermanentIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtVersionList; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.message.simaka.EapSimTypeData; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; +import com.android.internal.net.eap.statemachine.EapSimMethodStateMachine.CreatedState; +import com.android.internal.net.eap.statemachine.EapSimMethodStateMachine.StartState; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +public class EapSimCreatedStateTest extends EapSimStateTest { + private static final int EAP_SIM_START = 10; + + private CreatedState mCreatedState; + + @Before + public void setUp() { + super.setUp(); + mCreatedState = mEapSimMethodStateMachine.new CreatedState(); + } + + @Test + public void testProcessSuccess() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null); + EapResult result = mCreatedState.process(input); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testProcessFailure() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_FAILURE, ID_INT, null); + EapResult result = mCreatedState.process(input); + assertTrue(mEapSimMethodStateMachine.getState() instanceof FinalState); + + assertTrue(result instanceof EapFailure); + } + + @Test + public void testTransitionToStartState() throws Exception { + EapData eapData = new EapData(EAP_TYPE_SIM, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + List<EapSimAkaAttribute> attributes = Arrays.asList( + new AtVersionList(8, 1), new AtPermanentIdReq()); + DecodeResult<EapSimTypeData> decodeResult = + new DecodeResult<>(new EapSimTypeData(EAP_SIM_START, attributes)); + when(mMockEapSimTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + mEapSimMethodStateMachine.process(eapMessage); + assertTrue(mEapSimMethodStateMachine.getState() instanceof StartState); + + // decoded in CreatedState and StartState + verify(mMockEapSimTypeDataDecoder, times(2)).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockEapSimTypeDataDecoder); + } + + @Test + public void testProcessIncorrectEapMethodType() throws Exception { + EapData eapData = new EapData(EAP_IDENTITY, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + EapResult result = mEapSimMethodStateMachine.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimMethodStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimMethodStateMachineTest.java new file mode 100644 index 00000000..ac1d01ca --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimMethodStateMachineTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_SIM; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_PRE_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_NOTIFICATION; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_START; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.eap.EapSessionConfig.EapSimConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtVersionList; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.message.simaka.EapSimTypeData; +import com.android.internal.net.eap.message.simaka.EapSimTypeData.EapSimTypeDataDecoder; +import com.android.internal.net.eap.statemachine.EapSimMethodStateMachine.CreatedState; + +import org.junit.Before; +import org.junit.Test; + +import java.security.SecureRandom; +import java.util.Arrays; + +public class EapSimMethodStateMachineTest { + private static final int SUB_ID = 1; + private static final byte[] DUMMY_EAP_TYPE_DATA = hexStringToByteArray("112233445566"); + + // EAP-Identity = hex("test@android.net") + protected static final byte[] EAP_IDENTITY_BYTES = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + + private TelephonyManager mMockTelephonyManager; + private EapSimTypeDataDecoder mMockEapSimTypeDataDecoder; + + private EapSimConfig mEapSimConfig = new EapSimConfig(SUB_ID, APPTYPE_USIM); + private EapSimMethodStateMachine mEapSimMethodStateMachine; + + + @Before + public void setUp() { + mMockTelephonyManager = mock(TelephonyManager.class); + mMockEapSimTypeDataDecoder = mock(EapSimTypeDataDecoder.class); + + when(mMockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mMockTelephonyManager); + + mEapSimMethodStateMachine = + new EapSimMethodStateMachine( + mMockTelephonyManager, + EAP_IDENTITY_BYTES, + mEapSimConfig, + new SecureRandom(), + mMockEapSimTypeDataDecoder); + + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + } + + @Test + public void testEapSimMethodStateMachineStartState() { + assertTrue(mEapSimMethodStateMachine.getState() instanceof CreatedState); + } + + @Test + public void testGetMethod() { + assertEquals(EAP_TYPE_SIM, mEapSimMethodStateMachine.getEapMethod()); + } + + @Test + public void testEapSimFailsOnMultipleSimNotifications() throws Exception { + EapData eapData = new EapData(EAP_TYPE_SIM, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + // First EAP-SIM/Notification + EapSimTypeData notificationTypeData = + new EapSimTypeData( + EAP_SIM_NOTIFICATION, + Arrays.asList(new AtNotification(GENERAL_FAILURE_PRE_CHALLENGE))); + DecodeResult<EapSimTypeData> decodeResult = new DecodeResult<>(notificationTypeData); + when(mMockEapSimTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapSimMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_SIM_NOTIFICATION_RESPONSE, eapResponse.packet); + verify(mMockEapSimTypeDataDecoder).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapSimTypeDataDecoder); + + // Transition to StartState + decodeResult = + new DecodeResult<>( + new EapSimTypeData(EAP_SIM_START, Arrays.asList(new AtVersionList(8, 1)))); + when(mMockEapSimTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + eapResponse = (EapResponse) mEapSimMethodStateMachine.process(eapMessage); + assertFalse( + "EAP-Request/SIM-Start returned a Client-Error response", + Arrays.equals(EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet)); + + // decoded in: previous 1 time + in CreatedState and StartState + verify(mMockEapSimTypeDataDecoder, times(3)).decode(eq(DUMMY_EAP_TYPE_DATA)); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapSimTypeDataDecoder); + + // Second EAP-SIM/Notification + decodeResult = new EapSimAkaTypeData.DecodeResult<>(notificationTypeData); + when(mMockEapSimTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapError eapError = (EapError) mEapSimMethodStateMachine.process(eapMessage); + assertTrue(eapError.cause instanceof EapInvalidRequestException); + + // decoded previous 3 times + 1 + verify(mMockEapSimTypeDataDecoder, times(4)).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapSimTypeDataDecoder); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimStartStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimStartStateTest.java new file mode 100644 index 00000000..ccb746ab --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimStartStateTest.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.message.EapData.EAP_IDENTITY; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_SIM; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_IDENTITY; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_RESPONSE_WITHOUT_IDENTITY; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.IMSI; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ANY_ID_REQ; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ENCR_DATA; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_IV; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_PERMANENT_ID_REQ; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_VERSION_LIST; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_START; +import static com.android.internal.net.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_MT; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.exceptions.simaka.EapSimAkaIdentityUnavailableException; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAnyIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtIdentity; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNonceMt; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtPermanentIdReq; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtVersionList; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.message.simaka.EapSimTypeData; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.FinalState; +import com.android.internal.net.eap.statemachine.EapSimMethodStateMachine.ChallengeState; +import com.android.internal.net.eap.statemachine.EapSimMethodStateMachine.StartState; +import com.android.internal.net.utils.Log; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.LinkedHashMap; + +public class EapSimStartStateTest extends EapSimStateTest { + + private StartState mStartState; + private LinkedHashMap<Integer, EapSimAkaAttribute> mAttributes; + + @Before + public void setUp() { + super.setUp(); + + AtNonceMt atNonceMt = null; + try { + atNonceMt = new AtNonceMt(NONCE_MT); + } catch (Exception e) { + fail("Failed to create AtNonceMt attribute in setUp()"); + } + + mStartState = mEapSimMethodStateMachine.new StartState(atNonceMt); + mEapSimMethodStateMachine.transitionTo(mStartState); + + mAttributes = new LinkedHashMap<>(); + } + + @Test + public void testProcessSuccess() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null); + EapResult result = mStartState.process(input); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testProcessFailure() throws Exception { + EapMessage input = new EapMessage(EAP_CODE_FAILURE, ID_INT, null); + EapResult result = mStartState.process(input); + assertTrue(mEapSimMethodStateMachine.getState() instanceof FinalState); + + assertTrue(result instanceof EapFailure); + } + + @Test + public void testProcessIncorrectEapMethodType() throws Exception { + EapData eapData = new EapData(EAP_IDENTITY, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + + EapResult result = mStartState.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testIsValidStartAttributes() throws Exception { + mAttributes.put(EAP_AT_VERSION_LIST, new AtVersionList(8, 1)); + mAttributes.put(EAP_AT_PERMANENT_ID_REQ, new AtPermanentIdReq()); + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_START, mAttributes); + assertTrue(mStartState.isValidStartAttributes(eapSimTypeData)); + } + + @Test + public void testIsValidStartAttributesMissingVersionList() throws Exception { + mAttributes.put(EAP_AT_PERMANENT_ID_REQ, new AtPermanentIdReq()); + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_START, mAttributes); + assertFalse(mStartState.isValidStartAttributes(eapSimTypeData)); + } + + @Test + public void testIsValidStartAttributesMultipleIdRequests() throws Exception { + mAttributes.put(EAP_AT_VERSION_LIST, new AtVersionList(8, 1)); + mAttributes.put(EAP_AT_PERMANENT_ID_REQ, new AtPermanentIdReq()); + mAttributes.put(EAP_AT_ANY_ID_REQ, new AtAnyIdReq()); + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_START, mAttributes); + assertFalse(mStartState.isValidStartAttributes(eapSimTypeData)); + } + + @Test + public void testIsValidStartAttributesInvalidAttributes() throws Exception { + mAttributes.put(EAP_AT_VERSION_LIST, new AtVersionList(8, 1)); + mAttributes.put(EAP_AT_PERMANENT_ID_REQ, new AtPermanentIdReq()); + mAttributes.put(EAP_AT_MAC, new AtMac()); + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_START, mAttributes); + assertFalse(mStartState.isValidStartAttributes(eapSimTypeData)); + + mAttributes.remove(EAP_AT_MAC); + mAttributes.put(EAP_AT_IV, null); // just need <K, V> pair in the map + eapSimTypeData = new EapSimTypeData(EAP_SIM_START, mAttributes); + assertFalse(mStartState.isValidStartAttributes(eapSimTypeData)); + + mAttributes.remove(EAP_AT_IV); + mAttributes.put(EAP_AT_ENCR_DATA, null); // just need <K, V> pair in the map + eapSimTypeData = new EapSimTypeData(EAP_SIM_START, mAttributes); + assertFalse(mStartState.isValidStartAttributes(eapSimTypeData)); + } + + @Test + public void testAddIdentityAttributeToResponse() throws Exception { + EapSimTypeData eapSimTypeData = new EapSimTypeData( + EAP_SIM_START, Arrays.asList(new AtPermanentIdReq())); + + when(mMockTelephonyManager.getSubscriberId()).thenReturn(IMSI); + + AtIdentity atIdentity = mStartState.getIdentityResponse(eapSimTypeData); + assertArrayEquals(EAP_SIM_IDENTITY.getBytes(), mStartState.mIdentity); + verify(mMockTelephonyManager).getSubscriberId(); + assertArrayEquals(EAP_SIM_IDENTITY.getBytes(), atIdentity.identity); + verifyNoMoreInteractions(mMockTelephonyManager); + } + + @Test + public void testAddIdentityAttributeToResponseImsiUnavailable() throws Exception { + EapMessage eapMessage = new EapMessage( + EAP_CODE_REQUEST, + ID_INT, + new EapData(EAP_TYPE_SIM, DUMMY_EAP_TYPE_DATA)); + mAttributes.put(EAP_AT_VERSION_LIST, new AtVersionList(8, 1)); + mAttributes.put(EAP_AT_PERMANENT_ID_REQ, new AtPermanentIdReq()); + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_START, mAttributes); + DecodeResult decodeResult = new DecodeResult(eapSimTypeData); + + when(mMockEapSimTypeDataDecoder.decode(DUMMY_EAP_TYPE_DATA)).thenReturn(decodeResult); + when(mMockTelephonyManager.getSubscriberId()).thenReturn(null); + + EapResult result = mStartState.process(eapMessage); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapSimAkaIdentityUnavailableException); + + verify(mMockTelephonyManager).getSubscriberId(); + verifyNoMoreInteractions(mMockTelephonyManager); + } + + @Test + public void testAddIdentityAttributeToResponseNoIdRequest() throws Exception { + EapSimTypeData eapSimTypeData = new EapSimTypeData(EAP_SIM_START, Arrays.asList()); + + AtIdentity atIdentity = mStartState.getIdentityResponse(eapSimTypeData); + assertNull(atIdentity); + verifyNoMoreInteractions(mMockTelephonyManager); + } + + @Test + public void testProcessWithoutIdentityRequest() throws Exception { + EapMessage eapMessage = + new EapMessage( + EAP_CODE_REQUEST, ID_INT, new EapData(EAP_TYPE_SIM, DUMMY_EAP_TYPE_DATA)); + + // Send EAP-SIM/Start message without Identity request + mAttributes.put(EAP_AT_VERSION_LIST, new AtVersionList(8, 1)); + DecodeResult eapSimStartDecodeResult = + new DecodeResult(new EapSimTypeData(EAP_SIM_START, mAttributes)); + when(mMockEapSimTypeDataDecoder.decode(DUMMY_EAP_TYPE_DATA)) + .thenReturn(eapSimStartDecodeResult); + + EapResult result = mEapSimMethodStateMachine.process(eapMessage); + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals( + Log.byteArrayToHexString(eapResponse.packet), + EAP_SIM_RESPONSE_WITHOUT_IDENTITY, + eapResponse.packet); + + verify(mMockEapSimTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA)); + + // Send EAP-SIM/Challenge message + DecodeResult eapSimChallengeDecodeResult = + new DecodeResult(new EapSimTypeData(EAP_SIM_CHALLENGE, new LinkedHashMap<>())); + when(mMockEapSimTypeDataDecoder.decode(DUMMY_EAP_TYPE_DATA)) + .thenReturn(eapSimChallengeDecodeResult); + + // We only care about the transition to ChallengeState - the response doesn't matter + mEapSimMethodStateMachine.process(eapMessage); + ChallengeState challengeState = (ChallengeState) mEapSimMethodStateMachine.getState(); + assertArrayEquals(EAP_IDENTITY_BYTES, challengeState.mIdentity); + + // verify decode called 3x times: + // 1. decode in EAP-SIM/Start test above + // 2. decode in EAP-SIM/Challenge test for StartState + // 3. decode in EAP-SIM/Challenge test for ChallengeState + verify(mMockEapSimTypeDataDecoder, times(3)).decode(eq(DUMMY_EAP_TYPE_DATA)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimStateTest.java new file mode 100644 index 00000000..d8d18416 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimStateTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; +import static com.android.internal.net.eap.message.EapData.EAP_NOTIFICATION; +import static com.android.internal.net.eap.message.EapData.EAP_TYPE_SIM; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_CLIENT_ERROR_INSUFFICIENT_CHALLENGES; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_RESPONSE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_PRE_CHALLENGE; +import static com.android.internal.net.eap.message.simaka.EapSimTypeData.EAP_SIM_NOTIFICATION; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.eap.EapSessionConfig.EapSimConfig; +import android.telephony.TelephonyManager; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.message.EapData; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; +import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification; +import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; +import com.android.internal.net.eap.message.simaka.EapSimTypeData; +import com.android.internal.net.eap.message.simaka.EapSimTypeData.EapSimTypeDataDecoder; +import com.android.internal.net.eap.statemachine.EapMethodStateMachine.EapMethodState; + +import org.junit.Before; +import org.junit.Test; + +import java.security.SecureRandom; +import java.util.Arrays; + +public class EapSimStateTest { + protected static final int SUB_ID = 1; + protected static final String NOTIFICATION_MESSAGE = "test"; + protected static final byte[] DUMMY_EAP_TYPE_DATA = hexStringToByteArray("112233445566"); + + // EAP-Identity = hex("test@android.net") + protected static final byte[] EAP_IDENTITY_BYTES = + hexStringToByteArray("7465737440616E64726F69642E6E6574"); + + protected TelephonyManager mMockTelephonyManager; + protected EapSimTypeDataDecoder mMockEapSimTypeDataDecoder; + + protected EapSimConfig mEapSimConfig = new EapSimConfig(SUB_ID, APPTYPE_USIM); + protected EapSimMethodStateMachine mEapSimMethodStateMachine; + + @Before + public void setUp() { + mMockTelephonyManager = mock(TelephonyManager.class); + mMockEapSimTypeDataDecoder = mock(EapSimTypeDataDecoder.class); + + when(mMockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mMockTelephonyManager); + + mEapSimMethodStateMachine = + new EapSimMethodStateMachine( + mMockTelephonyManager, + EAP_IDENTITY_BYTES, + mEapSimConfig, + new SecureRandom(), + mMockEapSimTypeDataDecoder); + + verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID); + } + + @Test + public void testProcessNotification() throws Exception { + EapData eapData = new EapData(EAP_NOTIFICATION, NOTIFICATION_MESSAGE.getBytes()); + EapMessage notification = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preNotification = (EapMethodState) mEapSimMethodStateMachine.getState(); + + EapResult result = mEapSimMethodStateMachine.process(notification); + assertEquals(preNotification, mEapSimMethodStateMachine.getState()); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapSimTypeDataDecoder); + + assertTrue(result instanceof EapResponse); + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_RESPONSE_NOTIFICATION_PACKET, eapResponse.packet); + } + + @Test + public void testProcessEapSimNotification() throws Exception { + EapData eapData = new EapData(EAP_TYPE_SIM, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preProcess = (EapMethodState) mEapSimMethodStateMachine.getState(); + EapSimTypeData typeData = + new EapSimTypeData( + EAP_SIM_NOTIFICATION, + Arrays.asList(new AtNotification(GENERAL_FAILURE_PRE_CHALLENGE))); + + DecodeResult<EapSimTypeData> decodeResult = new DecodeResult<>(typeData); + when(mMockEapSimTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResponse eapResponse = (EapResponse) mEapSimMethodStateMachine.process(eapMessage); + assertArrayEquals(EAP_SIM_NOTIFICATION_RESPONSE, eapResponse.packet); + assertEquals(preProcess, mEapSimMethodStateMachine.getState()); + verify(mMockEapSimTypeDataDecoder).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapSimTypeDataDecoder); + } + + @Test + public void testProcessInvalidDecodeResult() throws Exception { + EapData eapData = new EapData(EAP_TYPE_SIM, DUMMY_EAP_TYPE_DATA); + EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData); + EapMethodState preProcess = (EapMethodState) mEapSimMethodStateMachine.getState(); + + AtClientErrorCode atClientErrorCode = AtClientErrorCode.INSUFFICIENT_CHALLENGES; + DecodeResult<EapSimTypeData> decodeResult = new DecodeResult<>(atClientErrorCode); + when(mMockEapSimTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult); + + EapResult result = mEapSimMethodStateMachine.process(eapMessage); + assertEquals(preProcess, mEapSimMethodStateMachine.getState()); + verify(mMockEapSimTypeDataDecoder).decode(DUMMY_EAP_TYPE_DATA); + verifyNoMoreInteractions(mMockTelephonyManager, mMockEapSimTypeDataDecoder); + + assertTrue(result instanceof EapResponse); + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_SIM_CLIENT_ERROR_INSUFFICIENT_CHALLENGES, eapResponse.packet); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapStateMachineTest.java new file mode 100644 index 00000000..288fc85f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapStateMachineTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import static com.android.internal.net.eap.EapTestUtils.getDummyEapSessionConfig; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SUCCESS_PACKET; + +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.net.eap.EapSessionConfig; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.statemachine.EapStateMachine.CreatedState; +import com.android.internal.net.eap.statemachine.EapStateMachine.FailureState; +import com.android.internal.net.eap.statemachine.EapStateMachine.SuccessState; + +import org.junit.Before; +import org.junit.Test; + +import java.security.SecureRandom; + +public class EapStateMachineTest { + private Context mContext; + private EapSessionConfig mEapSessionConfig; + + @Before + public void setUp() { + mContext = getInstrumentation().getContext(); + mEapSessionConfig = getDummyEapSessionConfig(); + } + + @Test + public void testEapStateMachineStartState() { + EapStateMachine eapStateMachine = + new EapStateMachine(mContext, mEapSessionConfig, new SecureRandom()); + assertTrue(eapStateMachine.getState() instanceof CreatedState); + } + + @Test + public void testSuccessStateProcessFails() { + SuccessState successState = + new EapStateMachine(mContext, mEapSessionConfig, new SecureRandom()) + .new SuccessState(); + EapResult result = successState.process(EAP_SUCCESS_PACKET); + assertTrue(result instanceof EapError); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testFailureStateProcessFails() { + FailureState failureState = + new EapStateMachine(mContext, mEapSessionConfig, new SecureRandom()) + .new FailureState(); + EapResult result = failureState.process(EAP_SUCCESS_PACKET); + assertTrue(result instanceof EapError); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapStateTest.java new file mode 100644 index 00000000..0193cb4e --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapStateTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import static com.android.internal.net.eap.EapTestUtils.getDummyEapSimSessionConfig; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_MD5_CHALLENGE; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_NAK_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NAK_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.REQUEST_UNSUPPORTED_TYPE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.SHORT_PACKET; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.net.eap.EapSessionConfig; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.exceptions.EapInvalidPacketLengthException; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.statemachine.EapStateMachine.EapState; + +import org.junit.Before; +import org.junit.Test; + +import java.security.SecureRandom; + +/** + * EapStateTest is a test template for testing EapState implementations. + * + * <p>Specifically, testing for CreatedState, IdentityState, and MethodState should subclass + * EapStateTest + */ +public class EapStateTest { + protected Context mContext; + protected EapSessionConfig mEapSessionConfig; + protected EapStateMachine mEapStateMachine; + protected EapState mEapState; + + @Before + public void setUp() { + mContext = getInstrumentation().getContext(); + mEapSessionConfig = getDummyEapSimSessionConfig(); + mEapStateMachine = new EapStateMachine(mContext, mEapSessionConfig, new SecureRandom()); + + // this EapState definition is used to make sure all non-Success/Failure EAP states + // produce the same results for error cases. + mEapState = mEapStateMachine.new EapState() { + @Override + public EapResult process(byte[] msg) { + return decode(msg).eapResult; + } + }; + } + + @Test + public void testProcessNullPacket() { + EapResult result = mEapState.process(null); + assertTrue(result instanceof EapError); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof IllegalArgumentException); + } + + @Test + public void testProcessUnsupportedEapDataType() { + EapResult result = mEapState.process(REQUEST_UNSUPPORTED_TYPE_PACKET); + assertTrue(result instanceof EapResponse); + + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_RESPONSE_NAK_PACKET, eapResponse.packet); + } + + @Test + public void testProcessDecodeFailure() { + EapResult result = mEapState.process(SHORT_PACKET); + assertTrue(result instanceof EapError); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidPacketLengthException); + } + + @Test + public void testProcessResponse() { + EapResult result = mEapState.process(EAP_RESPONSE_NOTIFICATION_PACKET); + assertTrue(result instanceof EapError); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testProcessNakRequest() { + EapResult result = mEapState.process(EAP_REQUEST_NAK_PACKET); + assertTrue(result instanceof EapError); + + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + } + + @Test + public void testProcessMd5Challenge() { + EapResult result = mEapState.process(EAP_REQUEST_MD5_CHALLENGE); + assertTrue(result instanceof EapResponse); + + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_RESPONSE_NAK_PACKET, eapResponse.packet); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/IdentityStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/IdentityStateTest.java new file mode 100644 index 00000000..abe4e824 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/IdentityStateTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static com.android.internal.net.eap.EapTestUtils.getDummyEapSessionConfig; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_IDENTITY; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_IDENTITY_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_SIM_START_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_IDENTITY_DEFAULT_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_IDENTITY_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.statemachine.EapStateMachine.MethodState; + +import org.junit.Before; +import org.junit.Test; + +import java.security.SecureRandom; + +public class IdentityStateTest extends EapStateTest { + private EapStateMachine mEapStateMachineSpy; + + @Before + @Override + public void setUp() { + super.setUp(); + + mEapStateMachineSpy = spy(mEapStateMachine); + mEapState = mEapStateMachineSpy.new IdentityState(); + } + + @Test + public void testProcess() { + mEapSessionConfig = getDummyEapSessionConfig(EAP_IDENTITY); + mEapStateMachineSpy = spy( + new EapStateMachine(mContext, mEapSessionConfig, new SecureRandom())); + mEapState = mEapStateMachineSpy.new IdentityState(); + + EapResult eapResult = mEapState.process(EAP_REQUEST_IDENTITY_PACKET); + + assertTrue(eapResult instanceof EapResponse); + EapResponse eapResponse = (EapResponse) eapResult; + assertArrayEquals(EAP_RESPONSE_IDENTITY_PACKET, eapResponse.packet); + verify(mEapStateMachineSpy, never()).transitionAndProcess(any(), any()); + } + + @Test + public void testProcessDefaultIdentity() { + EapResult eapResult = mEapState.process(EAP_REQUEST_IDENTITY_PACKET); + + assertTrue(eapResult instanceof EapResponse); + EapResponse eapResponse = (EapResponse) eapResult; + assertArrayEquals(EAP_RESPONSE_IDENTITY_DEFAULT_PACKET, eapResponse.packet); + verify(mEapStateMachineSpy, never()).transitionAndProcess(any(), any()); + } + + @Test + public void testProcessNotificationRequest() { + EapResult eapResult = mEapState.process(EAP_REQUEST_NOTIFICATION_PACKET); + + // state shouldn't change after Notification request + assertTrue(eapResult instanceof EapResponse); + EapResponse eapResponse = (EapResponse) eapResult; + assertArrayEquals(EAP_RESPONSE_NOTIFICATION_PACKET, eapResponse.packet); + verify(mEapStateMachineSpy, never()).transitionAndProcess(any(), any()); + } + + @Test + public void testProcessSimStart() { + mEapState.process(EAP_REQUEST_SIM_START_PACKET); + + // EapStateMachine should change to MethodState for method-type packet + verify(mEapStateMachineSpy).transitionAndProcess( + any(MethodState.class), eq(EAP_REQUEST_SIM_START_PACKET)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/MethodStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/MethodStateTest.java new file mode 100644 index 00000000..9df06e56 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/MethodStateTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.eap.statemachine; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE; +import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS; +import static com.android.internal.net.eap.message.EapMessage.EAP_HEADER_LENGTH; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_REQUEST; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_FAILURE_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_AKA; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_IDENTITY_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_MSCHAP_V2; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_SIM_START_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NAK_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_RESPONSE_NOTIFICATION_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SUCCESS_PACKET; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EMSK; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT; +import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSK; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.eap.EapSessionConfig; + +import com.android.internal.net.eap.EapResult; +import com.android.internal.net.eap.EapResult.EapError; +import com.android.internal.net.eap.EapResult.EapFailure; +import com.android.internal.net.eap.EapResult.EapResponse; +import com.android.internal.net.eap.EapResult.EapSuccess; +import com.android.internal.net.eap.exceptions.EapInvalidRequestException; +import com.android.internal.net.eap.message.EapMessage; +import com.android.internal.net.eap.statemachine.EapStateMachine.FailureState; +import com.android.internal.net.eap.statemachine.EapStateMachine.MethodState; +import com.android.internal.net.eap.statemachine.EapStateMachine.SuccessState; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; + +import java.security.SecureRandom; + +public class MethodStateTest extends EapStateTest { + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private static final String NETWORK_NAME = "android.net"; + private static final boolean ALLOW_MISMATCHED_NETWORK_NAMES = true; + + @Before + @Override + public void setUp() { + super.setUp(); + mEapState = mEapStateMachine.new MethodState(); + mEapStateMachine.transitionTo(mEapState); + } + + @Test + public void testProcessUnsupportedEapType() { + mEapState = mEapStateMachine.new MethodState(); + EapResult result = mEapState.process(EAP_REQUEST_IDENTITY_PACKET); + + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_RESPONSE_NAK_PACKET, eapResponse.packet); + } + + @Test + public void testProcessTransitionsToEapSim() { + mEapStateMachine.process(EAP_REQUEST_SIM_START_PACKET); + + assertTrue(mEapStateMachine.getState() instanceof MethodState); + MethodState methodState = (MethodState) mEapStateMachine.getState(); + assertTrue(methodState.mEapMethodStateMachine instanceof EapSimMethodStateMachine); + } + + @Test + public void testProcessTransitionToEapAka() { + // make EapStateMachine with EAP-AKA configurations + EapSessionConfig eapSessionConfig = new EapSessionConfig.Builder() + .setEapAkaConfig(0, APPTYPE_USIM).build(); + mEapStateMachine = new EapStateMachine(mContext, eapSessionConfig, new SecureRandom()); + + mEapStateMachine.process(EAP_REQUEST_AKA); + + assertTrue(mEapStateMachine.getState() instanceof MethodState); + MethodState methodState = (MethodState) mEapStateMachine.getState(); + assertTrue(methodState.mEapMethodStateMachine instanceof EapAkaMethodStateMachine); + } + + @Test + public void testProcessTransitionToEapAkaPrime() { + // make EapStateMachine with EAP-AKA' configurations + EapSessionConfig eapSessionConfig = + new EapSessionConfig.Builder() + .setEapAkaPrimeConfig( + 0, APPTYPE_USIM, NETWORK_NAME, ALLOW_MISMATCHED_NETWORK_NAMES) + .build(); + mEapStateMachine = new EapStateMachine(mContext, eapSessionConfig, new SecureRandom()); + + mEapStateMachine.process(EAP_AKA_PRIME_REQUEST); + + assertTrue(mEapStateMachine.getState() instanceof MethodState); + MethodState methodState = (MethodState) mEapStateMachine.getState(); + assertTrue(methodState.mEapMethodStateMachine instanceof EapAkaPrimeMethodStateMachine); + } + + @Test + public void testProcessTransitionToEapMsChapV2() { + // make EapStateMachine with EAP MSCHAPv2 configurations + EapSessionConfig eapSessionConfig = + new EapSessionConfig.Builder().setEapMsChapV2Config(USERNAME, PASSWORD).build(); + mEapStateMachine = new EapStateMachine(mContext, eapSessionConfig, new SecureRandom()); + + mEapStateMachine.process(EAP_REQUEST_MSCHAP_V2); + + assertTrue(mEapStateMachine.getState() instanceof MethodState); + MethodState methodState = (MethodState) mEapStateMachine.getState(); + assertTrue(methodState.mEapMethodStateMachine instanceof EapMsChapV2MethodStateMachine); + } + + @Test + public void testProcessTransitionToSuccessState() { + EapSuccess eapSuccess = new EapSuccess(MSK, EMSK); + + ArgumentMatcher<EapMessage> eapSuccessMatcher = msg -> + msg.eapCode == EAP_CODE_SUCCESS + && msg.eapIdentifier == ID_INT + && msg.eapLength == EAP_HEADER_LENGTH + && msg.eapData == null; + + EapMethodStateMachine mockEapMethodStateMachine = mock(EapMethodStateMachine.class); + when(mockEapMethodStateMachine.process(argThat(eapSuccessMatcher))).thenReturn(eapSuccess); + ((MethodState) mEapState).mEapMethodStateMachine = mockEapMethodStateMachine; + + mEapState.process(EAP_SUCCESS_PACKET); + verify(mockEapMethodStateMachine).process(argThat(eapSuccessMatcher)); + assertTrue(mEapStateMachine.getState() instanceof SuccessState); + verifyNoMoreInteractions(mockEapMethodStateMachine); + } + + @Test + public void testProcessTransitionToFailureState() { + EapFailure eapFailure = new EapFailure(); + + ArgumentMatcher<EapMessage> eapSuccessMatcher = msg -> + msg.eapCode == EAP_CODE_FAILURE + && msg.eapIdentifier == ID_INT + && msg.eapLength == EAP_HEADER_LENGTH + && msg.eapData == null; + + EapMethodStateMachine mockEapMethodStateMachine = mock(EapMethodStateMachine.class); + when(mockEapMethodStateMachine.process(argThat(eapSuccessMatcher))).thenReturn(eapFailure); + ((MethodState) mEapState).mEapMethodStateMachine = mockEapMethodStateMachine; + + mEapState.process(EAP_FAILURE_PACKET); + verify(mockEapMethodStateMachine).process(argThat(eapSuccessMatcher)); + assertTrue(mEapStateMachine.getState() instanceof FailureState); + verifyNoMoreInteractions(mockEapMethodStateMachine); + } + + @Test + public void testProcessEapFailureWithNoEapMethodState() { + EapResult result = mEapStateMachine.process(EAP_FAILURE_PACKET); + assertTrue(result instanceof EapFailure); + assertTrue(mEapStateMachine.getState() instanceof FailureState); + } + + @Test + public void testProcessEapSuccessWithNoEapMethodState() { + EapResult result = mEapStateMachine.process(EAP_SUCCESS_PACKET); + EapError eapError = (EapError) result; + assertTrue(eapError.cause instanceof EapInvalidRequestException); + assertTrue(mEapStateMachine.getState() instanceof MethodState); + } + + @Test + public void testProcessEapNotificationWithNoEapMethodState() { + EapResult result = mEapStateMachine.process(EAP_REQUEST_NOTIFICATION_PACKET); + EapResponse eapResponse = (EapResponse) result; + assertArrayEquals(EAP_RESPONSE_NOTIFICATION_PACKET, eapResponse.packet); + assertTrue(mEapStateMachine.getState() instanceof MethodState); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java new file mode 100644 index 00000000..f6c32eef --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java @@ -0,0 +1,1646 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike; + +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE; +import static android.system.OsConstants.AF_INET; + +import static com.android.internal.net.ipsec.ike.ChildSessionStateMachine.CMD_FORCE_TRANSITION; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_REKEY_CHILD; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS; +import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA; +import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL; +import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_CP; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_DELETE; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_KE; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NONCE; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_SA; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_INITIATOR; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_RESPONDER; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PROTOCOL_ID_ESP; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.IpSecManager; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.IpSecTransform; +import android.net.LinkAddress; +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.ChildSessionCallback; +import android.net.ipsec.ike.ChildSessionConfiguration; +import android.net.ipsec.ike.ChildSessionOptions; +import android.net.ipsec.ike.IkeManager; +import android.net.ipsec.ike.IkeTrafficSelector; +import android.net.ipsec.ike.SaProposal; +import android.net.ipsec.ike.TunnelModeChildSessionOptions; +import android.net.ipsec.ike.exceptions.IkeException; +import android.net.ipsec.ike.exceptions.IkeInternalException; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.CreateChildSaHelper; +import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.IChildSessionSmCallback; +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest; +import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecord; +import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecordConfig; +import com.android.internal.net.ipsec.ike.SaRecord.ISaRecordHelper; +import com.android.internal.net.ipsec.ike.SaRecord.SaRecordHelper; +import com.android.internal.net.ipsec.ike.crypto.IkeCipher; +import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; +import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf; +import com.android.internal.net.ipsec.ike.exceptions.InvalidKeException; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; +import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttribute; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Address; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Netmask; +import com.android.internal.net.ipsec.ike.message.IkeDeletePayload; +import com.android.internal.net.ipsec.ike.message.IkeKePayload; +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.internal.net.ipsec.ike.message.IkeNoncePayload; +import com.android.internal.net.ipsec.ike.message.IkeNotifyPayload; +import com.android.internal.net.ipsec.ike.message.IkePayload; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; +import com.android.internal.net.ipsec.ike.message.IkeTestUtils; +import com.android.internal.net.ipsec.ike.message.IkeTsPayload; +import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils; +import com.android.internal.net.utils.Log; +import com.android.server.IpSecService; + +import libcore.net.InetAddressUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executor; + +public final class ChildSessionStateMachineTest { + private static final String TAG = "ChildSessionStateMachineTest"; + + private static final Inet4Address LOCAL_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200")); + private static final Inet4Address REMOTE_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100")); + private static final Inet4Address INTERNAL_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("203.0.113.100")); + + private static final int IPV4_PREFIX_LEN = 32; + + private static final String IKE_AUTH_RESP_SA_PAYLOAD = + "2c00002c0000002801030403cae7019f0300000c0100000c800e0080" + + "03000008030000020000000805000000"; + private static final String REKEY_CHILD_RESP_SA_PAYLOAD = + "2800002c0000002801030403cd1736b30300000c0100000c800e0080" + + "03000008030000020000000805000000"; + private static final String REKEY_CHILD_REQ_SA_PAYLOAD = + "2800002c0000002801030403c88336490300000c0100000c800e0080" + + "03000008030000020000000805000000"; + private static final String REKEY_CHILD_UNACCEPTABLE_REQ_SA_PAYLOAD = + "2800002c0000002801030403c88336490300000c0100000c800e00c0" + + "03000008030000020000000805000000"; + + private static final int CURRENT_CHILD_SA_SPI_IN = 0x2ad4c0a2; + private static final int CURRENT_CHILD_SA_SPI_OUT = 0xcae7019f; + + private static final int LOCAL_INIT_NEW_CHILD_SA_SPI_IN = 0x57a09b0f; + private static final int LOCAL_INIT_NEW_CHILD_SA_SPI_OUT = 0xcd1736b3; + + private static final int REMOTE_INIT_NEW_CHILD_SA_SPI_IN = 0xd2d01795; + private static final int REMOTE_INIT_NEW_CHILD_SA_SPI_OUT = 0xc8833649; + + private static final String IKE_SK_D_HEX_STRING = "C86B56EFCF684DCC2877578AEF3137167FE0EBF6"; + private static final byte[] SK_D = TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING); + + private static final int KEY_LEN_IKE_SKD = 20; + + private IkeMacPrf mIkePrf; + + private Context mContext; + private IpSecService mMockIpSecService; + private IpSecManager mMockIpSecManager; + private UdpEncapsulationSocket mMockUdpEncapSocket; + + private TestLooper mLooper; + private ChildSessionStateMachine mChildSessionStateMachine; + + private List<IkePayload> mFirstSaReqPayloads = new LinkedList<>(); + private List<IkePayload> mFirstSaRespPayloads = new LinkedList<>(); + + private ChildSaRecord mSpyCurrentChildSaRecord; + private ChildSaRecord mSpyLocalInitNewChildSaRecord; + private ChildSaRecord mSpyRemoteInitNewChildSaRecord; + + private Log mSpyIkeLog; + + private ISaRecordHelper mMockSaRecordHelper; + + private ChildSessionOptions mChildSessionOptions; + private EncryptionTransform mChildEncryptionTransform; + private IntegrityTransform mChildIntegrityTransform; + private DhGroupTransform mChildDhGroupTransform; + + private ChildSaProposal mMockNegotiatedProposal; + + private Executor mSpyUserCbExecutor; + private ChildSessionCallback mMockChildSessionCallback; + private IChildSessionSmCallback mMockChildSessionSmCallback; + + private ArgumentCaptor<ChildSaRecordConfig> mChildSaRecordConfigCaptor = + ArgumentCaptor.forClass(ChildSaRecordConfig.class); + private ArgumentCaptor<List<IkePayload>> mPayloadListCaptor = + ArgumentCaptor.forClass(List.class); + private ArgumentCaptor<ChildSessionConfiguration> mChildConfigCaptor = + ArgumentCaptor.forClass(ChildSessionConfiguration.class); + + private ArgumentMatcher<ChildLocalRequest> mRekeyChildLocalReqMatcher = + (argument) -> { + return CMD_LOCAL_REQUEST_REKEY_CHILD == argument.procedureType + && mMockChildSessionCallback == argument.childSessionCallback; + }; + + public ChildSessionStateMachineTest() { + mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class); + mMockChildSessionSmCallback = mock(IChildSessionSmCallback.class); + + mChildEncryptionTransform = + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128); + mChildIntegrityTransform = + new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96); + + mChildDhGroupTransform = new DhGroupTransform(SaProposal.DH_GROUP_1024_BIT_MODP); + } + + @Before + public void setup() throws Exception { + mSpyIkeLog = TestUtils.makeSpyLogThrowExceptionForWtf(TAG); + IkeManager.setIkeLog(mSpyIkeLog); + + mIkePrf = + IkeMacPrf.create( + new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1), + IkeMessage.getSecurityProvider()); + + mContext = InstrumentationRegistry.getContext(); + mMockIpSecService = mock(IpSecService.class); + mMockIpSecManager = new IpSecManager(mContext, mMockIpSecService); + mMockUdpEncapSocket = mock(UdpEncapsulationSocket.class); + + mMockNegotiatedProposal = mock(ChildSaProposal.class); + + mSpyUserCbExecutor = + spy( + (command) -> { + command.run(); + }); + + mMockChildSessionCallback = mock(ChildSessionCallback.class); + mChildSessionOptions = buildChildSessionOptions(); + + // Setup thread and looper + mLooper = new TestLooper(); + mChildSessionStateMachine = + new ChildSessionStateMachine( + mLooper.getLooper(), + mContext, + mMockIpSecManager, + mChildSessionOptions, + mSpyUserCbExecutor, + mMockChildSessionCallback, + mMockChildSessionSmCallback); + mChildSessionStateMachine.setDbg(true); + SaRecord.setSaRecordHelper(mMockSaRecordHelper); + + setUpFirstSaNegoPayloadLists(); + setUpChildSaRecords(); + + mChildSessionStateMachine.start(); + } + + @After + public void tearDown() { + mChildSessionStateMachine.setDbg(false); + IkeManager.resetIkeLog(); + SaRecord.setSaRecordHelper(new SaRecordHelper()); + } + + private ChildSaProposal buildSaProposal() throws Exception { + return new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .build(); + } + + private ChildSessionOptions buildChildSessionOptions() throws Exception { + return new TunnelModeChildSessionOptions.Builder() + .addSaProposal(buildSaProposal()) + .addInternalAddressRequest(AF_INET, 1) + .addInternalAddressRequest(INTERNAL_ADDRESS, IPV4_PREFIX_LEN) + .build(); + } + + private void setUpChildSaRecords() { + mSpyCurrentChildSaRecord = + makeSpyChildSaRecord(CURRENT_CHILD_SA_SPI_IN, CURRENT_CHILD_SA_SPI_OUT); + mSpyLocalInitNewChildSaRecord = + makeSpyChildSaRecord( + LOCAL_INIT_NEW_CHILD_SA_SPI_IN, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT); + mSpyRemoteInitNewChildSaRecord = + makeSpyChildSaRecord( + REMOTE_INIT_NEW_CHILD_SA_SPI_IN, REMOTE_INIT_NEW_CHILD_SA_SPI_OUT); + } + + private void setUpSpiResource(InetAddress address, int spiRequested) throws Exception { + when(mMockIpSecService.allocateSecurityParameterIndex( + eq(address.getHostAddress()), anyInt(), anyObject())) + .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(spiRequested)); + } + + private void setUpFirstSaNegoPayloadLists() throws Exception { + // Build locally generated SA payload that has its SPI resource allocated. + setUpSpiResource(LOCAL_ADDRESS, CURRENT_CHILD_SA_SPI_IN); + IkeSaPayload reqSaPayload = + IkeSaPayload.createChildSaRequestPayload( + mChildSessionOptions.getSaProposals(), mMockIpSecManager, LOCAL_ADDRESS); + mFirstSaReqPayloads.add(reqSaPayload); + + // Build a remotely generated SA payload whoes SPI resource has not been allocated. + setUpSpiResource(REMOTE_ADDRESS, CURRENT_CHILD_SA_SPI_OUT); + IkeSaPayload respSaPayload = + (IkeSaPayload) + (IkeTestUtils.hexStringToIkePayload( + IkePayload.PAYLOAD_TYPE_SA, true, IKE_AUTH_RESP_SA_PAYLOAD)); + mFirstSaRespPayloads.add(respSaPayload); + + // Build TS Payloads + IkeTsPayload tsInitPayload = + new IkeTsPayload( + true /*isInitiator*/, mChildSessionOptions.getLocalTrafficSelectors()); + IkeTsPayload tsRespPayload = + new IkeTsPayload( + false /*isInitiator*/, mChildSessionOptions.getRemoteTrafficSelectors()); + + mFirstSaReqPayloads.add(tsInitPayload); + mFirstSaReqPayloads.add(tsRespPayload); + mFirstSaRespPayloads.add(tsInitPayload); + mFirstSaRespPayloads.add(tsRespPayload); + + // Build Nonce Payloads + mFirstSaReqPayloads.add(new IkeNoncePayload()); + mFirstSaRespPayloads.add(new IkeNoncePayload()); + + // Build Config Request Payload + List<ConfigAttribute> attrReqList = new LinkedList<>(); + attrReqList.add(new ConfigAttributeIpv4Address(INTERNAL_ADDRESS)); + attrReqList.add(new ConfigAttributeIpv4Netmask()); + mFirstSaReqPayloads.add(new IkeConfigPayload(false /*isReply*/, attrReqList)); + + // Build Config Reply Payload + List<ConfigAttribute> attrRespList = new LinkedList<>(); + attrRespList.add(new ConfigAttributeIpv4Address(INTERNAL_ADDRESS)); + mFirstSaRespPayloads.add(new IkeConfigPayload(true /*isReply*/, attrRespList)); + } + + private ChildSaRecord makeSpyChildSaRecord(int inboundSpi, int outboundSpi) { + ChildSaRecord child = + spy( + new ChildSaRecord( + inboundSpi, + outboundSpi, + true /*localInit*/, + null, + null, + null, + null, + null, + null, + mock(IpSecTransform.class), + mock(IpSecTransform.class), + mock(ChildLocalRequest.class))); + doNothing().when(child).close(); + return child; + } + + private void quitAndVerify() { + mChildSessionStateMachine.mCurrentChildSaRecord = null; + mChildSessionStateMachine.mLocalInitNewChildSaRecord = null; + mChildSessionStateMachine.mRemoteInitNewChildSaRecord = null; + + reset(mMockChildSessionSmCallback); + mChildSessionStateMachine.quit(); + mLooper.dispatchAll(); + + verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine); + verify(mMockChildSessionSmCallback).onChildSessionClosed(mMockChildSessionCallback); + } + + private void verifyChildSaRecordConfig( + ChildSaRecordConfig childSaRecordConfig, + int initSpi, + int respSpi, + boolean isLocalInit) { + assertEquals(mContext, childSaRecordConfig.context); + assertEquals(initSpi, childSaRecordConfig.initSpi.getSpi()); + assertEquals(respSpi, childSaRecordConfig.respSpi.getSpi()); + + if (isLocalInit) { + assertEquals(LOCAL_ADDRESS, childSaRecordConfig.initAddress); + assertEquals(REMOTE_ADDRESS, childSaRecordConfig.respAddress); + } else { + assertEquals(REMOTE_ADDRESS, childSaRecordConfig.initAddress); + assertEquals(LOCAL_ADDRESS, childSaRecordConfig.respAddress); + } + + assertEquals(mMockUdpEncapSocket, childSaRecordConfig.udpEncapSocket); + assertEquals(mIkePrf, childSaRecordConfig.ikePrf); + assertArrayEquals(SK_D, childSaRecordConfig.skD); + assertFalse(childSaRecordConfig.isTransport); + assertEquals(isLocalInit, childSaRecordConfig.isLocalInit); + assertTrue(childSaRecordConfig.hasIntegrityAlgo); + assertEquals( + CMD_LOCAL_REQUEST_REKEY_CHILD, childSaRecordConfig.futureRekeyEvent.procedureType); + assertEquals( + mMockChildSessionCallback, + childSaRecordConfig.futureRekeyEvent.childSessionCallback); + } + + private void verifyNotifyUsersCreateIpSecSa( + ChildSaRecord childSaRecord, boolean expectInbound) { + IpSecTransform transform = + expectInbound + ? childSaRecord.getInboundIpSecTransform() + : childSaRecord.getOutboundIpSecTransform(); + int direction = expectInbound ? IpSecManager.DIRECTION_IN : IpSecManager.DIRECTION_OUT; + + verify(mMockChildSessionCallback).onIpSecTransformCreated(eq(transform), eq(direction)); + } + + private void verifyInitCreateChildResp( + List<IkePayload> reqPayloads, List<IkePayload> respPayloads) throws Exception { + verify(mMockChildSessionSmCallback) + .onChildSaCreated( + mSpyCurrentChildSaRecord.getRemoteSpi(), mChildSessionStateMachine); + verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine); + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + + // Validate negotiated SA proposal. + ChildSaProposal negotiatedProposal = mChildSessionStateMachine.mSaProposal; + assertNotNull(negotiatedProposal); + assertEquals( + new EncryptionTransform[] {mChildEncryptionTransform}, + negotiatedProposal.getEncryptionTransforms()); + assertEquals( + new IntegrityTransform[] {mChildIntegrityTransform}, + negotiatedProposal.getIntegrityTransforms()); + + // Validate current ChildSaRecord + verify(mMockSaRecordHelper) + .makeChildSaRecord( + eq(reqPayloads), eq(respPayloads), mChildSaRecordConfigCaptor.capture()); + ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue(); + + verifyChildSaRecordConfig( + childSaRecordConfig, + CURRENT_CHILD_SA_SPI_IN, + CURRENT_CHILD_SA_SPI_OUT, + true /*isLocalInit*/); + + assertEquals(mSpyCurrentChildSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord); + + verify(mMockChildSessionSmCallback) + .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine)); + verify(mMockChildSessionSmCallback) + .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong()); + verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine); + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + + // Verify users have been notified + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verifyNotifyUsersCreateIpSecSa(mSpyCurrentChildSaRecord, true /*expectInbound*/); + verifyNotifyUsersCreateIpSecSa(mSpyCurrentChildSaRecord, false /*expectInbound*/); + verify(mMockChildSessionCallback).onOpened(mChildConfigCaptor.capture()); + + // Verify Child Session Configuration + ChildSessionConfiguration sessionConfig = mChildConfigCaptor.getValue(); + verifyTsList( + Arrays.asList(mChildSessionOptions.getLocalTrafficSelectors()), + sessionConfig.getInboundTrafficSelectors()); + verifyTsList( + Arrays.asList(mChildSessionOptions.getRemoteTrafficSelectors()), + sessionConfig.getOutboundTrafficSelectors()); + + List<LinkAddress> addrList = sessionConfig.getInternalAddressList(); + assertEquals(1, addrList.size()); + assertEquals(INTERNAL_ADDRESS, addrList.get(0).getAddress()); + assertEquals(IPV4_PREFIX_LEN, addrList.get(0).getPrefixLength()); + } + + private void verifyTsList( + List<IkeTrafficSelector> expectedList, List<IkeTrafficSelector> tsList) { + assertEquals(expectedList.size(), tsList.size()); + for (int i = 0; i < expectedList.size(); i++) { + assertEquals(expectedList.get(i), tsList.get(i)); + } + } + + @Test + public void testCreateFirstChild() throws Exception { + when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any())) + .thenReturn(mSpyCurrentChildSaRecord); + + mChildSessionStateMachine.handleFirstChildExchange( + mFirstSaReqPayloads, + mFirstSaRespPayloads, + LOCAL_ADDRESS, + REMOTE_ADDRESS, + mMockUdpEncapSocket, + mIkePrf, + SK_D); + mLooper.dispatchAll(); + + verifyInitCreateChildResp(mFirstSaReqPayloads, mFirstSaRespPayloads); + + quitAndVerify(); + } + + private void verifyOutboundCreatePayloadTypes( + List<IkePayload> outboundPayloads, boolean isRekey) { + assertNotNull( + IkePayload.getPayloadForTypeInProvidedList( + PAYLOAD_TYPE_SA, IkeSaPayload.class, outboundPayloads)); + assertNotNull( + IkePayload.getPayloadForTypeInProvidedList( + PAYLOAD_TYPE_TS_INITIATOR, IkeTsPayload.class, outboundPayloads)); + assertNotNull( + IkePayload.getPayloadForTypeInProvidedList( + PAYLOAD_TYPE_TS_RESPONDER, IkeTsPayload.class, outboundPayloads)); + assertNotNull( + IkePayload.getPayloadForTypeInProvidedList( + PAYLOAD_TYPE_NONCE, IkeNoncePayload.class, outboundPayloads)); + assertNull( + IkePayload.getPayloadForTypeInProvidedList( + PAYLOAD_TYPE_KE, IkeKePayload.class, outboundPayloads)); + + IkeConfigPayload configPayload = + IkePayload.getPayloadForTypeInProvidedList( + PAYLOAD_TYPE_CP, IkeConfigPayload.class, outboundPayloads); + if (isRekey) { + assertNull(configPayload); + } else { + assertNotNull(configPayload); + assertEquals(IkeConfigPayload.CONFIG_TYPE_REQUEST, configPayload.configType); + } + } + + @Test + public void testCreateChild() throws Exception { + when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any())) + .thenReturn(mSpyCurrentChildSaRecord); + + mChildSessionStateMachine.createChildSession( + LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D); + mLooper.dispatchAll(); + + // Validate outbound payload list + verify(mMockChildSessionSmCallback) + .onOutboundPayloadsReady( + eq(EXCHANGE_TYPE_CREATE_CHILD_SA), + eq(false), + mPayloadListCaptor.capture(), + eq(mChildSessionStateMachine)); + + List<IkePayload> reqPayloadList = mPayloadListCaptor.getValue(); + verifyOutboundCreatePayloadTypes(reqPayloadList, false /*isRekey*/); + assertTrue( + IkePayload.getPayloadListForTypeInProvidedList( + PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, reqPayloadList) + .isEmpty()); + + mChildSessionStateMachine.receiveResponse( + EXCHANGE_TYPE_CREATE_CHILD_SA, mFirstSaRespPayloads); + mLooper.dispatchAll(); + + verifyInitCreateChildResp(reqPayloadList, mFirstSaRespPayloads); + + quitAndVerify(); + } + + private <T extends IkeException> void verifyHandleFatalErrorAndQuit(Class<T> exceptionClass) { + assertNull(mChildSessionStateMachine.getCurrentState()); + verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine); + verify(mMockChildSessionSmCallback).onChildSessionClosed(mMockChildSessionCallback); + + verify(mMockChildSessionCallback).onClosedExceptionally(any(exceptionClass)); + } + + @Test + public void testCreateChildHandlesErrorNotifyResp() throws Exception { + // Send out Create request + mChildSessionStateMachine.createChildSession( + LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D); + mLooper.dispatchAll(); + + // Receive error notification in Create response + IkeNotifyPayload notifyPayload = new IkeNotifyPayload(ERROR_TYPE_NO_PROPOSAL_CHOSEN); + List<IkePayload> respPayloads = new LinkedList<>(); + respPayloads.add(notifyPayload); + mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads); + mLooper.dispatchAll(); + + // Verify no SPI for provisional Child was registered. + verify(mMockChildSessionSmCallback, never()) + .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine)); + + // Verify user was notified and state machine has quit. + verifyHandleFatalErrorAndQuit(NoValidProposalChosenException.class); + } + + @Test + public void testCreateChildHandlesRespWithMissingPayload() throws Exception { + // Send out Create request + mChildSessionStateMachine.createChildSession( + LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D); + mLooper.dispatchAll(); + + // Receive response with no Nonce Payload + List<IkePayload> respPayloads = new LinkedList<>(); + for (IkePayload payload : mFirstSaRespPayloads) { + if (IkePayload.PAYLOAD_TYPE_NONCE == payload.payloadType) continue; + respPayloads.add(payload); + } + mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads); + mLooper.dispatchAll(); + + // Verify SPI for provisional Child was registered and unregistered. + verify(mMockChildSessionSmCallback) + .onChildSaCreated(CURRENT_CHILD_SA_SPI_OUT, mChildSessionStateMachine); + verify(mMockChildSessionSmCallback).onChildSaDeleted(CURRENT_CHILD_SA_SPI_OUT); + + // Verify user was notified and state machine has quit. + verifyHandleFatalErrorAndQuit(InvalidSyntaxException.class); + } + + @Test + public void testCreateChildHandlesKeyCalculationFail() throws Exception { + // Throw exception when building ChildSaRecord + when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any())) + .thenThrow( + new GeneralSecurityException("testCreateChildHandlesKeyCalculationFail")); + + // Send out and receive Create Child message + mChildSessionStateMachine.createChildSession( + LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D); + mLooper.dispatchAll(); + mChildSessionStateMachine.receiveResponse( + EXCHANGE_TYPE_CREATE_CHILD_SA, mFirstSaRespPayloads); + mLooper.dispatchAll(); + + // Verify SPI for provisional Child was registered and unregistered. + verify(mMockChildSessionSmCallback) + .onChildSaCreated(CURRENT_CHILD_SA_SPI_OUT, mChildSessionStateMachine); + verify(mMockChildSessionSmCallback).onChildSaDeleted(CURRENT_CHILD_SA_SPI_OUT); + + // Verify user was notified and state machine has quit. + verifyHandleFatalErrorAndQuit(IkeInternalException.class); + } + + private void setupIdleStateMachine() throws Exception { + mChildSessionStateMachine.mLocalAddress = LOCAL_ADDRESS; + mChildSessionStateMachine.mRemoteAddress = REMOTE_ADDRESS; + mChildSessionStateMachine.mUdpEncapSocket = mMockUdpEncapSocket; + mChildSessionStateMachine.mIkePrf = mIkePrf; + mChildSessionStateMachine.mSkD = SK_D; + + mChildSessionStateMachine.mSaProposal = buildSaProposal(); + mChildSessionStateMachine.mChildCipher = mock(IkeCipher.class); + mChildSessionStateMachine.mChildIntegrity = mock(IkeMacIntegrity.class); + mChildSessionStateMachine.mLocalTs = mChildSessionOptions.getLocalTrafficSelectors(); + mChildSessionStateMachine.mRemoteTs = mChildSessionOptions.getRemoteTrafficSelectors(); + + mChildSessionStateMachine.mCurrentChildSaRecord = mSpyCurrentChildSaRecord; + + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mIdle); + mLooper.dispatchAll(); + + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + } + + private List<IkePayload> makeDeletePayloads(int spi) { + List<IkePayload> inboundPayloads = new ArrayList<>(1); + inboundPayloads.add(new IkeDeletePayload(new int[] {spi})); + return inboundPayloads; + } + + private void verifyOutboundDeletePayload(int expectedSpi, boolean isResp) { + verify(mMockChildSessionSmCallback) + .onOutboundPayloadsReady( + eq(EXCHANGE_TYPE_INFORMATIONAL), + eq(isResp), + mPayloadListCaptor.capture(), + eq(mChildSessionStateMachine)); + + List<IkePayload> outPayloadList = mPayloadListCaptor.getValue(); + assertEquals(1, outPayloadList.size()); + + List<IkeDeletePayload> deletePayloads = + IkePayload.getPayloadListForTypeInProvidedList( + PAYLOAD_TYPE_DELETE, IkeDeletePayload.class, outPayloadList); + assertEquals(1, deletePayloads.size()); + IkeDeletePayload deletePayload = deletePayloads.get(0); + assertEquals(expectedSpi, deletePayload.spisToDelete[0]); + } + + private void verifyNotifyUserDeleteChildSa(ChildSaRecord childSaRecord) { + verify(mMockChildSessionCallback) + .onIpSecTransformDeleted( + eq(childSaRecord.getInboundIpSecTransform()), + eq(IpSecManager.DIRECTION_IN)); + verify(mMockChildSessionCallback) + .onIpSecTransformDeleted( + eq(childSaRecord.getOutboundIpSecTransform()), + eq(IpSecManager.DIRECTION_OUT)); + } + + private void verifyNotifyUsersDeleteSession() { + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verify(mMockChildSessionCallback).onClosed(); + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + } + + @Test + public void testDeleteChildLocal() throws Exception { + setupIdleStateMachine(); + + // Test initiating Delete request + mChildSessionStateMachine.deleteChildSession(); + mLooper.dispatchAll(); + + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.DeleteChildLocalDelete); + verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), false /*isResp*/); + + // Test receiving Delete response + mChildSessionStateMachine.receiveResponse( + EXCHANGE_TYPE_INFORMATIONAL, + makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi())); + mLooper.dispatchAll(); + + assertNull(mChildSessionStateMachine.getCurrentState()); + + verifyNotifyUsersDeleteSession(); + } + + @Test + public void testDeleteChildLocalHandlesInvalidResp() throws Exception { + setupIdleStateMachine(); + + // Test initiating Delete request + mChildSessionStateMachine.deleteChildSession(); + mLooper.dispatchAll(); + + // Test receiving response with no Delete Payload + mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_INFORMATIONAL, new LinkedList<>()); + mLooper.dispatchAll(); + + assertNull(mChildSessionStateMachine.getCurrentState()); + verify(mMockChildSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + } + + @Test + public void testDeleteChildLocalInInitial() throws Exception { + mChildSessionStateMachine.deleteChildSession(); + mLooper.dispatchAll(); + + assertNull(mChildSessionStateMachine.getCurrentState()); + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verify(mMockChildSessionCallback).onClosed(); + } + + @Test + public void testSimultaneousDeleteChild() throws Exception { + setupIdleStateMachine(); + + mChildSessionStateMachine.deleteChildSession(); + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_DELETE_CHILD, + EXCHANGE_TYPE_INFORMATIONAL, + makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi())); + mLooper.dispatchAll(); + + verify(mMockChildSessionSmCallback) + .onOutboundPayloadsReady( + eq(EXCHANGE_TYPE_INFORMATIONAL), + eq(true), + mPayloadListCaptor.capture(), + eq(mChildSessionStateMachine)); + List<IkePayload> respPayloadList = mPayloadListCaptor.getValue(); + assertTrue(respPayloadList.isEmpty()); + + mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_INFORMATIONAL, new LinkedList<>()); + mLooper.dispatchAll(); + + assertNull(mChildSessionStateMachine.getCurrentState()); + + verifyNotifyUsersDeleteSession(); + } + + @Test + public void testReplyRekeyRequestDuringDeletion() throws Exception { + setupIdleStateMachine(); + + mChildSessionStateMachine.deleteChildSession(); + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, mock(List.class)); + mLooper.dispatchAll(); + + // Verify outbound response to Rekey Child request + verify(mMockChildSessionSmCallback) + .onOutboundPayloadsReady( + eq(EXCHANGE_TYPE_INFORMATIONAL), + eq(true), + mPayloadListCaptor.capture(), + eq(mChildSessionStateMachine)); + List<IkePayload> respPayloadList = mPayloadListCaptor.getValue(); + assertEquals(1, respPayloadList.size()); + + IkeNotifyPayload notifyPayload = (IkeNotifyPayload) respPayloadList.get(0); + assertEquals(ERROR_TYPE_TEMPORARY_FAILURE, notifyPayload.notifyType); + assertEquals(0, notifyPayload.notifyData.length); + + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.DeleteChildLocalDelete); + } + + @Test + public void testDeleteChildRemote() throws Exception { + setupIdleStateMachine(); + + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_DELETE_CHILD, + EXCHANGE_TYPE_INFORMATIONAL, + makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi())); + mLooper.dispatchAll(); + + assertNull(mChildSessionStateMachine.getCurrentState()); + // Verify response + verify(mMockChildSessionSmCallback) + .onOutboundPayloadsReady( + eq(EXCHANGE_TYPE_INFORMATIONAL), + eq(true), + mPayloadListCaptor.capture(), + eq(mChildSessionStateMachine)); + List<IkePayload> respPayloadList = mPayloadListCaptor.getValue(); + + assertEquals(1, respPayloadList.size()); + assertArrayEquals( + new int[] {mSpyCurrentChildSaRecord.getLocalSpi()}, + ((IkeDeletePayload) respPayloadList.get(0)).spisToDelete); + + verifyNotifyUsersDeleteSession(); + } + + private void verifyOutboundRekeySaPayload(List<IkePayload> outboundPayloads, boolean isResp) { + IkeSaPayload saPayload = + IkePayload.getPayloadForTypeInProvidedList( + PAYLOAD_TYPE_SA, IkeSaPayload.class, outboundPayloads); + assertEquals(isResp, saPayload.isSaResponse); + assertEquals(1, saPayload.proposalList.size()); + + IkeSaPayload.ChildProposal proposal = + (IkeSaPayload.ChildProposal) saPayload.proposalList.get(0); + assertEquals(1, proposal.number); // Must be 1-indexed + assertEquals(mChildSessionStateMachine.mSaProposal, proposal.saProposal); + } + + private void verifyOutboundRekeyNotifyPayload(List<IkePayload> outboundPayloads) { + List<IkeNotifyPayload> notifyPayloads = + IkePayload.getPayloadListForTypeInProvidedList( + PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, outboundPayloads); + assertEquals(1, notifyPayloads.size()); + IkeNotifyPayload notifyPayload = notifyPayloads.get(0); + assertEquals(NOTIFY_TYPE_REKEY_SA, notifyPayload.notifyType); + assertEquals(PROTOCOL_ID_ESP, notifyPayload.protocolId); + assertEquals(mSpyCurrentChildSaRecord.getLocalSpi(), notifyPayload.spi); + } + + @Test + public void testRekeyChildLocalCreateSendsRequest() throws Exception { + setupIdleStateMachine(); + + // Send Rekey-Create request + mChildSessionStateMachine.rekeyChildSession(); + mLooper.dispatchAll(); + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.RekeyChildLocalCreate); + verify(mMockChildSessionSmCallback) + .onOutboundPayloadsReady( + eq(EXCHANGE_TYPE_CREATE_CHILD_SA), + eq(false), + mPayloadListCaptor.capture(), + eq(mChildSessionStateMachine)); + + // Verify outbound payload list + List<IkePayload> reqPayloadList = mPayloadListCaptor.getValue(); + verifyOutboundCreatePayloadTypes(reqPayloadList, true /*isRekey*/); + + verifyOutboundRekeySaPayload(reqPayloadList, false /*isResp*/); + verifyOutboundRekeyNotifyPayload(reqPayloadList); + } + + private List<IkePayload> makeInboundRekeyChildPayloads( + int remoteSpi, String inboundSaHexString, boolean isLocalInitRekey) throws Exception { + List<IkePayload> inboundPayloads = new LinkedList<>(); + + IkeSaPayload saPayload = + (IkeSaPayload) + (IkeTestUtils.hexStringToIkePayload( + IkePayload.PAYLOAD_TYPE_SA, true, inboundSaHexString)); + inboundPayloads.add(saPayload); + + // Build TS Payloads + IkeTrafficSelector[] initTs = + isLocalInitRekey + ? mChildSessionStateMachine.mLocalTs + : mChildSessionStateMachine.mRemoteTs; + IkeTrafficSelector[] respTs = + isLocalInitRekey + ? mChildSessionStateMachine.mRemoteTs + : mChildSessionStateMachine.mLocalTs; + inboundPayloads.add(new IkeTsPayload(true /*isInitiator*/, initTs)); + inboundPayloads.add(new IkeTsPayload(false /*isInitiator*/, respTs)); + + // Build Nonce Payloads + inboundPayloads.add(new IkeNoncePayload()); + + if (isLocalInitRekey) { + // Rekey-Create response without Notify-Rekey payload is valid. + return inboundPayloads; + } + + // Build Rekey-Notification + inboundPayloads.add( + new IkeNotifyPayload( + PROTOCOL_ID_ESP, + mSpyCurrentChildSaRecord.getRemoteSpi(), + NOTIFY_TYPE_REKEY_SA, + new byte[0])); + + return inboundPayloads; + } + + @Test + public void testRekeyChildLocalCreateValidatesResponse() throws Exception { + setupIdleStateMachine(); + setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN); + setUpSpiResource(REMOTE_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT); + + // Send Rekey-Create request + mChildSessionStateMachine.rekeyChildSession(); + mLooper.dispatchAll(); + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.RekeyChildLocalCreate); + + // Prepare "rekeyed" SA and receive Rekey response + List<IkePayload> rekeyRespPayloads = + makeInboundRekeyChildPayloads( + LOCAL_INIT_NEW_CHILD_SA_SPI_OUT, + REKEY_CHILD_RESP_SA_PAYLOAD, + true /*isLocalInitRekey*/); + when(mMockSaRecordHelper.makeChildSaRecord( + any(List.class), eq(rekeyRespPayloads), any(ChildSaRecordConfig.class))) + .thenReturn(mSpyLocalInitNewChildSaRecord); + + mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyRespPayloads); + mLooper.dispatchAll(); + + // Verify state transition + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.RekeyChildLocalDelete); + + // Verify newly created ChildSaRecord + assertEquals( + mSpyLocalInitNewChildSaRecord, + mChildSessionStateMachine.mLocalInitNewChildSaRecord); + verify(mMockChildSessionSmCallback) + .onChildSaCreated( + eq(mSpyLocalInitNewChildSaRecord.getRemoteSpi()), + eq(mChildSessionStateMachine)); + verify(mMockChildSessionSmCallback) + .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong()); + + verify(mMockSaRecordHelper) + .makeChildSaRecord( + any(List.class), + eq(rekeyRespPayloads), + mChildSaRecordConfigCaptor.capture()); + ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue(); + verifyChildSaRecordConfig( + childSaRecordConfig, + LOCAL_INIT_NEW_CHILD_SA_SPI_IN, + LOCAL_INIT_NEW_CHILD_SA_SPI_OUT, + true /*isLocalInit*/); + + // Verify users have been notified + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verifyNotifyUsersCreateIpSecSa(mSpyLocalInitNewChildSaRecord, true /*expectInbound*/); + verifyNotifyUsersCreateIpSecSa(mSpyLocalInitNewChildSaRecord, false /*expectInbound*/); + } + + @Test + public void testRekeyLocalCreateHandlesErrorNotifyResp() throws Exception { + setupIdleStateMachine(); + setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN); + + // Send Rekey-Create request + mChildSessionStateMachine.rekeyChildSession(); + mLooper.dispatchAll(); + + // Receive error notification in Create response + IkeNotifyPayload notifyPayload = new IkeNotifyPayload(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE); + List<IkePayload> respPayloads = new LinkedList<>(); + respPayloads.add(notifyPayload); + mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads); + mLooper.dispatchAll(); + + // Verify rekey has been rescheduled and Child Session is alive + verify(mMockChildSessionSmCallback) + .scheduleRetryLocalRequest( + (ChildLocalRequest) mSpyCurrentChildSaRecord.getFutureRekeyEvent()); + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + + // Verify no SPI for provisional Child was registered. + verify(mMockChildSessionSmCallback, never()) + .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine)); + } + + @Test + public void testRekeyLocalCreateHandlesRespWithMissingPayload() throws Exception { + setupIdleStateMachine(); + setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN); + reset(mMockChildSessionSmCallback); + + // Send Rekey-Create request + mChildSessionStateMachine.rekeyChildSession(); + mLooper.dispatchAll(); + + // Receive response with no SA Payload + List<IkePayload> validRekeyRespPayloads = + makeInboundRekeyChildPayloads( + LOCAL_INIT_NEW_CHILD_SA_SPI_OUT, + REKEY_CHILD_RESP_SA_PAYLOAD, + true /*isLocalInitRekey*/); + List<IkePayload> respPayloads = new LinkedList<>(); + for (IkePayload payload : validRekeyRespPayloads) { + if (IkePayload.PAYLOAD_TYPE_SA == payload.payloadType) continue; + respPayloads.add(payload); + } + mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads); + mLooper.dispatchAll(); + + // Verify user was notified and state machine has quit. + verifyHandleFatalErrorAndQuit(InvalidSyntaxException.class); + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + + // Verify no SPI for provisional Child was registered. + verify(mMockChildSessionSmCallback, never()) + .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine)); + + // Verify retry was not scheduled + verify(mMockChildSessionSmCallback, never()).scheduleRetryLocalRequest(any()); + } + + @Test + public void testRekeyLocalCreateChildHandlesKeyCalculationFail() throws Exception { + // Throw exception when building ChildSaRecord + when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any())) + .thenThrow( + new GeneralSecurityException( + "testRekeyCreateChildHandlesKeyCalculationFail")); + + // Setup for rekey negotiation + setupIdleStateMachine(); + setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN); + setUpSpiResource(REMOTE_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT); + reset(mMockChildSessionSmCallback); + + // Send Rekey-Create request + mChildSessionStateMachine.rekeyChildSession(); + mLooper.dispatchAll(); + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.RekeyChildLocalCreate); + + // Receive Rekey response + List<IkePayload> rekeyRespPayloads = + makeInboundRekeyChildPayloads( + LOCAL_INIT_NEW_CHILD_SA_SPI_OUT, + REKEY_CHILD_RESP_SA_PAYLOAD, + true /*isLocalInitRekey*/); + mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyRespPayloads); + mLooper.dispatchAll(); + + // Verify user was notified and state machine has quit. + verifyHandleFatalErrorAndQuit(IkeInternalException.class); + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + + // Verify SPI for provisional Child was registered and unregistered. + verify(mMockChildSessionSmCallback) + .onChildSaCreated(LOCAL_INIT_NEW_CHILD_SA_SPI_OUT, mChildSessionStateMachine); + verify(mMockChildSessionSmCallback).onChildSaDeleted(LOCAL_INIT_NEW_CHILD_SA_SPI_OUT); + + // Verify retry was not scheduled + verify(mMockChildSessionSmCallback, never()).scheduleRetryLocalRequest(any()); + } + + @Test + public void testRekeyChildLocalDeleteSendsRequest() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildLocalDelete + mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete); + mLooper.dispatchAll(); + + // Verify outbound delete request + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.RekeyChildLocalDelete); + verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), false /*isResp*/); + + assertEquals(mSpyCurrentChildSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord); + assertEquals( + mSpyLocalInitNewChildSaRecord, mChildSessionStateMachine.mChildSaRecordSurviving); + } + + void verifyChildSaUpdated(ChildSaRecord oldSaRecord, ChildSaRecord newSaRecord) { + verify(mMockChildSessionSmCallback).onChildSaDeleted(oldSaRecord.getRemoteSpi()); + verify(oldSaRecord).close(); + + assertNull(mChildSessionStateMachine.mChildSaRecordSurviving); + assertEquals(newSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord); + } + + @Test + public void testRekeyChildLocalDeleteValidatesResponse() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildLocalDelete + mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete); + mLooper.dispatchAll(); + + // Test receiving Delete response + mChildSessionStateMachine.receiveResponse( + EXCHANGE_TYPE_INFORMATIONAL, + makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi())); + mLooper.dispatchAll(); + + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + + // First invoked in #setupIdleStateMachine + verify(mMockChildSessionSmCallback, times(2)) + .onProcedureFinished(mChildSessionStateMachine); + + verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyLocalInitNewChildSaRecord); + + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verify(mMockChildSessionCallback, never()).onClosed(); + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + } + + @Test + public void testRekeyChildLocalDeleteHandlesInvalidResp() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildLocalDelete + mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete); + mLooper.dispatchAll(); + + // Test receiving Delete response with missing Delete payload + mChildSessionStateMachine.receiveResponse( + EXCHANGE_TYPE_INFORMATIONAL, new ArrayList<IkePayload>()); + mLooper.dispatchAll(); + + // Verify rekey has finished + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyLocalInitNewChildSaRecord); + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + + // First invoked in #setupIdleStateMachine + verify(mMockChildSessionSmCallback, times(2)) + .onProcedureFinished(mChildSessionStateMachine); + } + + @Test + public void testRekeyChildRemoteCreate() throws Exception { + setupIdleStateMachine(); + + // Setup for new Child SA negotiation. + setUpSpiResource(LOCAL_ADDRESS, REMOTE_INIT_NEW_CHILD_SA_SPI_IN); + setUpSpiResource(REMOTE_ADDRESS, REMOTE_INIT_NEW_CHILD_SA_SPI_OUT); + + List<IkePayload> rekeyReqPayloads = + makeInboundRekeyChildPayloads( + REMOTE_INIT_NEW_CHILD_SA_SPI_OUT, + REKEY_CHILD_REQ_SA_PAYLOAD, + false /*isLocalInitRekey*/); + when(mMockSaRecordHelper.makeChildSaRecord( + eq(rekeyReqPayloads), any(List.class), any(ChildSaRecordConfig.class))) + .thenReturn(mSpyRemoteInitNewChildSaRecord); + + // Receive rekey Child request + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads); + mLooper.dispatchAll(); + + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.RekeyChildRemoteDelete); + + // Verify outbound rekey response + verify(mMockChildSessionSmCallback) + .onOutboundPayloadsReady( + eq(EXCHANGE_TYPE_CREATE_CHILD_SA), + eq(true), + mPayloadListCaptor.capture(), + eq(mChildSessionStateMachine)); + List<IkePayload> respPayloadList = mPayloadListCaptor.getValue(); + verifyOutboundCreatePayloadTypes(respPayloadList, true /*isRekey*/); + + verifyOutboundRekeySaPayload(respPayloadList, true /*isResp*/); + verifyOutboundRekeyNotifyPayload(respPayloadList); + + // Verify new Child SA + assertEquals( + mSpyRemoteInitNewChildSaRecord, + mChildSessionStateMachine.mRemoteInitNewChildSaRecord); + + verify(mMockChildSessionSmCallback) + .onChildSaCreated( + eq(mSpyRemoteInitNewChildSaRecord.getRemoteSpi()), + eq(mChildSessionStateMachine)); + verify(mMockChildSessionSmCallback) + .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong()); + + verify(mMockSaRecordHelper) + .makeChildSaRecord( + eq(rekeyReqPayloads), + any(List.class), + mChildSaRecordConfigCaptor.capture()); + ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue(); + verifyChildSaRecordConfig( + childSaRecordConfig, + REMOTE_INIT_NEW_CHILD_SA_SPI_OUT, + REMOTE_INIT_NEW_CHILD_SA_SPI_IN, + false /*isLocalInit*/); + + // Verify that users are notified the creation of new inbound IpSecTransform + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, true /*expectInbound*/); + } + + private void verifyOutboundErrorNotify(int exchangeType, int errorCode) { + verify(mMockChildSessionSmCallback) + .onOutboundPayloadsReady( + eq(exchangeType), + eq(true), + mPayloadListCaptor.capture(), + eq(mChildSessionStateMachine)); + List<IkePayload> respPayloadList = mPayloadListCaptor.getValue(); + + assertEquals(1, respPayloadList.size()); + IkePayload payload = respPayloadList.get(0); + assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType); + assertEquals(errorCode, ((IkeNotifyPayload) payload).notifyType); + } + + @Test + public void testRekeyChildRemoteCreateHandlesInvalidReq() throws Exception { + setupIdleStateMachine(); + + List<IkePayload> rekeyReqPayloads = + makeInboundRekeyChildPayloads( + REMOTE_INIT_NEW_CHILD_SA_SPI_OUT, + REKEY_CHILD_UNACCEPTABLE_REQ_SA_PAYLOAD, + false /*isLocalInitRekey*/); + + // Receive rekey Child request + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads); + mLooper.dispatchAll(); + + // Verify error notification was sent and state machind was back to Idle + verifyOutboundErrorNotify(EXCHANGE_TYPE_CREATE_CHILD_SA, ERROR_TYPE_NO_PROPOSAL_CHOSEN); + + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + } + + @Test + public void testRekeyChildRemoteCreateSaCreationFail() throws Exception { + // Throw exception when building ChildSaRecord + when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any())) + .thenThrow( + new GeneralSecurityException("testRekeyChildRemoteCreateSaCreationFail")); + + setupIdleStateMachine(); + + List<IkePayload> rekeyReqPayloads = + makeInboundRekeyChildPayloads( + REMOTE_INIT_NEW_CHILD_SA_SPI_OUT, + REKEY_CHILD_REQ_SA_PAYLOAD, + false /*isLocalInitRekey*/); + + // Receive rekey Child request + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads); + mLooper.dispatchAll(); + + // Verify error notification was sent and state machind was back to Idle + verifyOutboundErrorNotify(EXCHANGE_TYPE_CREATE_CHILD_SA, ERROR_TYPE_NO_PROPOSAL_CHOSEN); + + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + } + + @Test + public void testRekeyChildRemoteDelete() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildRemoteDelete + mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete); + + // Test receiving Delete request + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_DELETE_CHILD, + EXCHANGE_TYPE_INFORMATIONAL, + makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi())); + mLooper.dispatchAll(); + + // Verify outbound Delete response + verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), true /*isResp*/); + + // Verify Child SA has been updated + verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyRemoteInitNewChildSaRecord); + + // Verify procedure has been finished. #onProcedureFinished was first invoked in + // #setupIdleStateMachine + verify(mMockChildSessionSmCallback, times(2)) + .onProcedureFinished(mChildSessionStateMachine); + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + + verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class)); + + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/); + verify(mMockChildSessionCallback, never()).onClosed(); + } + + @Test + public void testRekeyChildLocalDeleteWithReqForNewSa() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildLocalDelete + mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete); + mLooper.dispatchAll(); + + // Test receiving Delete new Child SA request + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_DELETE_CHILD, + EXCHANGE_TYPE_INFORMATIONAL, + makeDeletePayloads(mSpyLocalInitNewChildSaRecord.getRemoteSpi())); + mLooper.dispatchAll(); + + // Verify outbound Delete response on new Child SA + verifyOutboundDeletePayload(mSpyLocalInitNewChildSaRecord.getLocalSpi(), true /*isResp*/); + verify(mMockChildSessionSmCallback) + .onChildSaDeleted(mSpyLocalInitNewChildSaRecord.getRemoteSpi()); + verify(mSpyLocalInitNewChildSaRecord).close(); + + assertNull(mChildSessionStateMachine.getCurrentState()); + + verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class)); + + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + verifyNotifyUserDeleteChildSa(mSpyLocalInitNewChildSaRecord); + + verify(mMockChildSessionCallback).onClosed(); + } + + @Test + public void testRekeyChildRemoteDeleteWithReqForNewSa() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildRemoteDelete + mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete); + mLooper.dispatchAll(); + + // Test receiving Delete new Child SA request + mChildSessionStateMachine.receiveRequest( + IKE_EXCHANGE_SUBTYPE_DELETE_CHILD, + EXCHANGE_TYPE_INFORMATIONAL, + makeDeletePayloads(mSpyRemoteInitNewChildSaRecord.getRemoteSpi())); + mLooper.dispatchAll(); + + // Verify outbound Delete response on new Child SA + verifyOutboundDeletePayload(mSpyRemoteInitNewChildSaRecord.getLocalSpi(), true /*isResp*/); + verify(mMockChildSessionSmCallback) + .onChildSaDeleted(mSpyRemoteInitNewChildSaRecord.getRemoteSpi()); + verify(mSpyRemoteInitNewChildSaRecord).close(); + + assertNull(mChildSessionStateMachine.getCurrentState()); + + verify(mSpyUserCbExecutor, times(3)).execute(any(Runnable.class)); + + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + verifyNotifyUserDeleteChildSa(mSpyRemoteInitNewChildSaRecord); + verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/); + + verify(mMockChildSessionCallback).onClosed(); + } + + @Test + public void testRekeyChildRemoteDeleteTimeout() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildRemoteDelete + mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete); + mLooper.dispatchAll(); + + mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS); + mLooper.dispatchAll(); + + // Verify no response sent. + verify(mMockChildSessionSmCallback, never()) + .onOutboundPayloadsReady(anyInt(), anyBoolean(), any(List.class), anyObject()); + + // Verify Child SA has been renewed + verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyRemoteInitNewChildSaRecord); + + // Verify procedure has been finished. #onProcedureFinished was first invoked in + // #setupIdleStateMachine + verify(mMockChildSessionSmCallback, times(2)) + .onProcedureFinished(mChildSessionStateMachine); + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.Idle); + + verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class)); + + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/); + + verify(mMockChildSessionCallback, never()).onClosed(); + } + + @Test + public void testRekeyChildRemoteDeleteExitAndRenter() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildRemoteDelete + mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete); + mLooper.dispatchAll(); + + // Trigger a timeout, and immediately re-enter remote-delete + mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS / 2 + 1); + mChildSessionStateMachine.sendMessage(ChildSessionStateMachine.TIMEOUT_REKEY_REMOTE_DELETE); + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete); + mLooper.dispatchAll(); + + // Shift time forward + mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS / 2 + 1); + mLooper.dispatchAll(); + + // Verify final state has not changed - timeout was not triggered. + assertTrue( + mChildSessionStateMachine.getCurrentState() + instanceof ChildSessionStateMachine.RekeyChildRemoteDelete); + + verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class)); + + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/); + + verify(mMockChildSessionCallback, never()).onClosed(); + } + + @Test + public void testCloseSessionNow() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyChildLocalDelete + mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord; + mChildSessionStateMachine.sendMessage( + CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete); + + mChildSessionStateMachine.killSession(); + mLooper.dispatchAll(); + + assertNull(mChildSessionStateMachine.getCurrentState()); + + verify(mSpyUserCbExecutor, times(3)).execute(any(Runnable.class)); + + verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord); + verifyNotifyUserDeleteChildSa(mSpyLocalInitNewChildSaRecord); + + verify(mMockChildSessionCallback).onClosed(); + } + + @Test + public void testValidateExpectKeExistCase() throws Exception { + when(mMockNegotiatedProposal.getDhGroupTransforms()) + .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform}); + List<IkePayload> payloadList = new LinkedList<>(); + payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_1024_BIT_MODP)); + + CreateChildSaHelper.validateKePayloads( + payloadList, true /*isResp*/, mMockNegotiatedProposal); + CreateChildSaHelper.validateKePayloads( + payloadList, false /*isResp*/, mMockNegotiatedProposal); + } + + @Test + public void testValidateExpectNoKeExistCase() throws Exception { + when(mMockNegotiatedProposal.getDhGroupTransforms()).thenReturn(new DhGroupTransform[0]); + List<IkePayload> payloadList = new LinkedList<>(); + + CreateChildSaHelper.validateKePayloads( + payloadList, true /*isResp*/, mMockNegotiatedProposal); + CreateChildSaHelper.validateKePayloads( + payloadList, false /*isResp*/, mMockNegotiatedProposal); + } + + @Test + public void testThrowWhenKeMissing() throws Exception { + when(mMockNegotiatedProposal.getDhGroupTransforms()) + .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform}); + List<IkePayload> payloadList = new LinkedList<>(); + + try { + CreateChildSaHelper.validateKePayloads( + payloadList, true /*isResp*/, mMockNegotiatedProposal); + fail("Expected to fail due to the absence of KE Payload"); + } catch (InvalidSyntaxException expected) { + } + + try { + CreateChildSaHelper.validateKePayloads( + payloadList, false /*isResp*/, mMockNegotiatedProposal); + fail("Expected to fail due to the absence of KE Payload"); + } catch (InvalidKeException expected) { + } + } + + @Test + public void testThrowWhenKeHasMismatchedDhGroup() throws Exception { + when(mMockNegotiatedProposal.getDhGroupTransforms()) + .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform}); + List<IkePayload> payloadList = new LinkedList<>(); + payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_2048_BIT_MODP)); + + try { + CreateChildSaHelper.validateKePayloads( + payloadList, true /*isResp*/, mMockNegotiatedProposal); + fail("Expected to fail due to mismatched DH Group"); + } catch (InvalidSyntaxException expected) { + } + + try { + CreateChildSaHelper.validateKePayloads( + payloadList, false /*isResp*/, mMockNegotiatedProposal); + fail("Expected to fail due to mismatched DH Group"); + } catch (InvalidKeException expected) { + } + } + + @Test + public void testThrowForUnexpectedKe() throws Exception { + DhGroupTransform noneGroup = new DhGroupTransform(SaProposal.DH_GROUP_NONE); + when(mMockNegotiatedProposal.getDhGroupTransforms()) + .thenReturn(new DhGroupTransform[] {noneGroup}); + List<IkePayload> payloadList = new LinkedList<>(); + payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_2048_BIT_MODP)); + + try { + CreateChildSaHelper.validateKePayloads( + payloadList, true /*isResp*/, mMockNegotiatedProposal); + fail("Expected to fail due to unexpected KE payload."); + } catch (InvalidSyntaxException expected) { + } + + CreateChildSaHelper.validateKePayloads( + payloadList, false /*isResp*/, mMockNegotiatedProposal); + } + + @Test + public void testHandleUnexpectedException() throws Exception { + Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG); + IkeManager.setIkeLog(spyIkeLog); + + mChildSessionStateMachine.createChildSession( + null /*localAddress*/, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D); + mLooper.dispatchAll(); + + verifyHandleFatalErrorAndQuit(IkeInternalException.class); + verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestSchedulerTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestSchedulerTest.java new file mode 100644 index 00000000..3406d014 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestSchedulerTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike; + +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.IProcedureConsumer; +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; + +public final class IkeLocalRequestSchedulerTest { + private IkeLocalRequestScheduler mScheduler; + + private IProcedureConsumer mMockConsumer; + private LocalRequest[] mMockRequestArray; + + private ArgumentCaptor<LocalRequest> mLocalRequestCaptor = + ArgumentCaptor.forClass(LocalRequest.class); + + @Before + public void setUp() { + mMockConsumer = mock(IProcedureConsumer.class); + mScheduler = new IkeLocalRequestScheduler(mMockConsumer); + + mMockRequestArray = new LocalRequest[10]; + for (int i = 0; i < mMockRequestArray.length; i++) { + mMockRequestArray[i] = mock(LocalRequest.class); + } + } + + @Test + public void testAddMultipleRequestProcessOnlyOne() { + for (LocalRequest r : mMockRequestArray) mScheduler.addRequest(r); + + // Verify that no procedure was preemptively pulled from the queue + verify(mMockConsumer, never()).onNewProcedureReady(any()); + + // Check that the onNewPrcedureReady called exactly once, on the first item + mScheduler.readyForNextProcedure(); + verify(mMockConsumer, times(1)).onNewProcedureReady(any()); + verify(mMockConsumer, times(1)).onNewProcedureReady(mMockRequestArray[0]); + for (int i = 1; i < mMockRequestArray.length; i++) { + verify(mMockConsumer, never()).onNewProcedureReady(mMockRequestArray[i]); + } + } + + @Test + public void testProcessOrder() { + InOrder inOrder = inOrder(mMockConsumer); + + for (LocalRequest r : mMockRequestArray) mScheduler.addRequest(r); + for (int i = 0; i < mMockRequestArray.length; i++) mScheduler.readyForNextProcedure(); + + for (LocalRequest r : mMockRequestArray) { + inOrder.verify(mMockConsumer).onNewProcedureReady(r); + } + } + + @Test + public void testAddRequestToFrontProcessOrder() { + InOrder inOrder = inOrder(mMockConsumer); + + LocalRequest[] mockHighPriorityRequestArray = new LocalRequest[10]; + for (int i = 0; i < mockHighPriorityRequestArray.length; i++) { + mockHighPriorityRequestArray[i] = mock(LocalRequest.class); + } + + for (LocalRequest r : mMockRequestArray) mScheduler.addRequest(r); + for (LocalRequest r : mockHighPriorityRequestArray) mScheduler.addRequestAtFront(r); + + for (int i = 0; i < mockHighPriorityRequestArray.length + mMockRequestArray.length; i++) { + mScheduler.readyForNextProcedure(); + } + + // Verify processing order. mockHighPriorityRequestArray is processed in reverse order + for (int i = mockHighPriorityRequestArray.length - 1; i >= 0; i--) { + inOrder.verify(mMockConsumer).onNewProcedureReady(mockHighPriorityRequestArray[i]); + } + for (LocalRequest r : mMockRequestArray) { + inOrder.verify(mMockConsumer).onNewProcedureReady(r); + } + } + + @Test + public void testDoNotProcessCanceledRequest() { + LocalRequest[] requestArray = new LocalRequest[4]; + + for (int i = 0; i < requestArray.length; i++) { + requestArray[i] = new LocalRequest(CMD_LOCAL_REQUEST_REKEY_IKE); + mScheduler.addRequest(requestArray[i]); + } + + mScheduler.readyForNextProcedure(); + verify(mMockConsumer).onNewProcedureReady(eq(requestArray[0])); + + requestArray[1].cancel(); + mScheduler.readyForNextProcedure(); + verify(mMockConsumer, never()).onNewProcedureReady(eq(requestArray[1])); + verify(mMockConsumer).onNewProcedureReady(eq(requestArray[2])); + + mScheduler.readyForNextProcedure(); + verify(mMockConsumer).onNewProcedureReady(eq(requestArray[3])); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java new file mode 100644 index 00000000..94f4d622 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java @@ -0,0 +1,4036 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike; + +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_CHILD_SA_NOT_FOUND; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_ADDITIONAL_SAS; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD; + +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_REKEY_CHILD; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.RETRY_INTERVAL_MS; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.SA_SOFT_LIFETIME_MS; +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.TEMP_FAILURE_RETRY_TIMEOUT_MS; +import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA; +import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL; +import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED; +import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP; +import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_AUTH; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_SA; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.IpSecManager; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.eap.EapSessionConfig; +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.ChildSessionCallback; +import android.net.ipsec.ike.ChildSessionOptions; +import android.net.ipsec.ike.IkeIpv4AddrIdentification; +import android.net.ipsec.ike.IkeManager; +import android.net.ipsec.ike.IkeSaProposal; +import android.net.ipsec.ike.IkeSessionCallback; +import android.net.ipsec.ike.IkeSessionOptions; +import android.net.ipsec.ike.SaProposal; +import android.net.ipsec.ike.TransportModeChildSessionOptions; +import android.net.ipsec.ike.exceptions.IkeException; +import android.net.ipsec.ike.exceptions.IkeInternalException; +import android.net.ipsec.ike.exceptions.IkeProtocolException; +import android.os.test.TestLooper; +import android.telephony.TelephonyManager; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.eap.EapAuthenticator; +import com.android.internal.net.eap.IEapCallback; +import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.IChildSessionSmCallback; +import com.android.internal.net.ipsec.ike.ChildSessionStateMachineFactory.ChildSessionFactoryHelper; +import com.android.internal.net.ipsec.ike.ChildSessionStateMachineFactory.IChildSessionFactoryHelper; +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest; +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest; +import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IkeSecurityParameterIndex; +import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.ReceivedIkePacket; +import com.android.internal.net.ipsec.ike.SaRecord.ISaRecordHelper; +import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord; +import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecordConfig; +import com.android.internal.net.ipsec.ike.SaRecord.SaRecordHelper; +import com.android.internal.net.ipsec.ike.crypto.IkeCipher; +import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; +import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf; +import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; +import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException; +import com.android.internal.net.ipsec.ike.exceptions.UnsupportedCriticalPayloadException; +import com.android.internal.net.ipsec.ike.message.IkeAuthDigitalSignPayload; +import com.android.internal.net.ipsec.ike.message.IkeAuthPayload; +import com.android.internal.net.ipsec.ike.message.IkeAuthPskPayload; +import com.android.internal.net.ipsec.ike.message.IkeCertX509CertPayload; +import com.android.internal.net.ipsec.ike.message.IkeDeletePayload; +import com.android.internal.net.ipsec.ike.message.IkeEapPayload; +import com.android.internal.net.ipsec.ike.message.IkeHeader; +import com.android.internal.net.ipsec.ike.message.IkeIdPayload; +import com.android.internal.net.ipsec.ike.message.IkeInformationalPayload; +import com.android.internal.net.ipsec.ike.message.IkeKePayload; +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResult; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultOk; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultPartial; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultProtectedError; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultUnprotectedError; +import com.android.internal.net.ipsec.ike.message.IkeMessage.IIkeMessageHelper; +import com.android.internal.net.ipsec.ike.message.IkeMessage.IkeMessageHelper; +import com.android.internal.net.ipsec.ike.message.IkeNoncePayload; +import com.android.internal.net.ipsec.ike.message.IkeNotifyPayload; +import com.android.internal.net.ipsec.ike.message.IkePayload; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; +import com.android.internal.net.ipsec.ike.message.IkeSkfPayload; +import com.android.internal.net.ipsec.ike.message.IkeTestUtils; +import com.android.internal.net.ipsec.ike.message.IkeTsPayload; +import com.android.internal.net.ipsec.ike.testutils.CertUtils; +import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils; +import com.android.internal.net.ipsec.ike.utils.Retransmitter; +import com.android.internal.net.ipsec.ike.utils.Retransmitter.IBackoffTimeoutCalculator; +import com.android.internal.net.utils.Log; +import com.android.internal.util.State; + +import libcore.net.InetAddressUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; + +import java.io.IOException; +import java.net.Inet4Address; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executor; + +public final class IkeSessionStateMachineTest { + private static final String TAG = "IkeSessionStateMachineTest"; + + private static final Inet4Address LOCAL_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200")); + private static final Inet4Address REMOTE_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("127.0.0.1")); + + private static final String IKE_INIT_RESP_HEX_STRING = + "5f54bf6d8b48e6e1909232b3d1edcb5c21202220000000000000014c220000300000" + + "002c010100040300000c0100000c800e008003000008030000020300000802000002" + + "00000008040000022800008800020000fe014fefed55a4229928bfa3dad1ea6ffaca" + + "abfb5f5bdd71790e99a192530e3f849d3a3d96dc6e0a7a10ff6f72a6162103ac573c" + + "acd41d08b7a034cad8f5eab09c14ced5a9e4af5692dff028f21c1119dd75226b6af6" + + "b2f009245369c9892cc5742e5c94a254ebff052470771fb2cb4f29a35d8953e18a1a" + + "6c6fbc56acc188a5290000249756112ca539f5c25abacc7ee92b73091942a9c06950" + + "f98848f1af1694c4ddff2900001c00004004c53f054b976a25d75fde72dbf1c7b6c8" + + "c9aa9ca12900001c00004005b16d79b21c1bc89ca7350f42de805be0227e2ed62b00" + + "00080000401400000014882fe56d6fd20dbc2251613b2ebe5beb"; + private static final String IKE_SA_PAYLOAD_HEX_STRING = + "220000300000002c010100040300000c0100000c800e00800300000803000002030" + + "00008020000020000000804000002"; + private static final String IKE_REKEY_SA_PAYLOAD_HEX_STRING = + "22000038000000340101080400000000000000FF0300000c0100000c800e0080030" + + "000080300000203000008020000020000000804000002"; + private static final String IKE_REKEY_UNACCEPTABLE_SA_PAYLOAD_HEX_STRING = + "22000038000000340101080400000000000000FF0300000c0100000c800e0080030" + + "00008030000020300000802000002000000080400000e"; + private static final int IKE_REKEY_SA_INITIATOR_SPI = 0xff; + private static final String KE_PAYLOAD_HEX_STRING = + "2800008800020000b4a2faf4bb54878ae21d638512ece55d9236fc50" + + "46ab6cef82220f421f3ce6361faf36564ecb6d28798a94aa" + + "d7b2b4b603ddeaaa5630adb9ece8ac37534036040610ebdd" + + "92f46bef84f0be7db860351843858f8acf87056e272377f7" + + "0c9f2d81e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2" + + "6bbeb08214c7071376079587"; + private static final String NONCE_INIT_PAYLOAD_HEX_STRING = + "29000024c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412"; + private static final String NONCE_RESP_PAYLOAD_HEX_STRING = + "290000249756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff"; + private static final String NONCE_INIT_HEX_STRING = + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412"; + private static final String NONCE_RESP_HEX_STRING = + "9756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff"; + private static final String NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING = + "2900001c00004004e54f73b7d83f6beb881eab2051d8663f421d10b0"; + private static final String NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING = + "2b00001c00004005d915368ca036004cb578ae3e3fb268509aeab190"; + private static final String FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING = "290000080000402e"; + private static final String DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000"; + private static final String NOTIFY_REKEY_IKE_PAYLOAD_HEX_STRING = "2100000800004009"; + private static final String ID_PAYLOAD_INITIATOR_HEX_STRING = + "290000180200000031313233343536373839414243444546"; + private static final String ID_PAYLOAD_RESPONDER_HEX_STRING = "2700000c010000007f000001"; + private static final String PSK_AUTH_RESP_PAYLOAD_HEX_STRING = + "2100001c0200000058f36412e9b7b38df817a9f7779b7a008dacdd25"; + private static final String GENERIC_DIGITAL_SIGN_AUTH_RESP_HEX_STRING = + "300000580e0000000f300d06092a864886f70d01010b05006f76af4150d653c5d413" + + "6b9f69d905849bf075c563e6d14ccda42361ec3e7d12c72e2dece5711ea1d952f7b8e" + + "12c5d982aa4efdaeac36a02b222aa96242cc424"; + private static final String CHILD_SA_PAYLOAD_HEX_STRING = + "2c00002c0000002801030403cae7019f0300000c0100000c800e008003000008030" + + "000020000000805000000"; + private static final String TS_INIT_PAYLOAD_HEX_STRING = + "2d00001801000000070000100000ffff00000000ffffffff"; + private static final String TS_RESP_PAYLOAD_HEX_STRING = + "2900001801000000070000100000ffff000000000fffffff"; + + private static final String PSK_HEX_STRING = "6A756E69706572313233"; + + private static final String PRF_KEY_INIT_HEX_STRING = + "094787780EE466E2CB049FA327B43908BC57E485"; + private static final String PRF_KEY_RESP_HEX_STRING = + "A30E6B08BE56C0E6BFF4744143C75219299E1BEB"; + + private static final byte[] EAP_DUMMY_MSG = "EAP Message".getBytes(); + + private static final int KEY_LEN_IKE_INTE = 20; + private static final int KEY_LEN_IKE_ENCR = 16; + private static final int KEY_LEN_IKE_PRF = 20; + private static final int KEY_LEN_IKE_SKD = KEY_LEN_IKE_PRF; + + private static final int CHILD_SPI_LOCAL = 0x2ad4c0a2; + private static final int CHILD_SPI_REMOTE = 0xcae7019f; + + private static final int DUMMY_UDP_ENCAP_RESOURCE_ID = 0x3234; + private static final int UDP_ENCAP_PORT = 34567; + + private static final int EAP_SIM_SUB_ID = 1; + + private static final int PAYLOAD_TYPE_UNSUPPORTED = 127; + + private static final long RETRANSMIT_BACKOFF_TIMEOUT_MS = 5000L; + + private MockIpSecTestUtils mMockIpSecTestUtils; + private Context mContext; + private IpSecManager mIpSecManager; + private UdpEncapsulationSocket mUdpEncapSocket; + + private IkeSocket mSpyIkeSocket; + + private TestLooper mLooper; + private IkeSessionStateMachine mIkeSessionStateMachine; + + private byte[] mPsk; + + private ChildSessionOptions mChildSessionOptions; + + private Executor mSpyUserCbExecutor; + private IkeSessionCallback mMockIkeSessionCallback; + private ChildSessionCallback mMockChildSessionCallback; + + private EncryptionTransform mIkeEncryptionTransform; + private IntegrityTransform mIkeIntegrityTransform; + private PrfTransform mIkePrfTransform; + private DhGroupTransform mIkeDhGroupTransform; + + private IIkeMessageHelper mMockIkeMessageHelper; + private ISaRecordHelper mMockSaRecordHelper; + private IBackoffTimeoutCalculator mMockBackoffTimeoutCalculator; + + private ChildSessionStateMachine mMockChildSessionStateMachine; + private IChildSessionFactoryHelper mMockChildSessionFactoryHelper; + private IChildSessionSmCallback mDummyChildSmCallback; + + private IkeSaRecord mSpyCurrentIkeSaRecord; + private IkeSaRecord mSpyLocalInitIkeSaRecord; + private IkeSaRecord mSpyRemoteInitIkeSaRecord; + + private Log mSpyIkeLog; + + private int mExpectedCurrentSaLocalReqMsgId; + private int mExpectedCurrentSaRemoteReqMsgId; + + private EapSessionConfig mEapSessionConfig; + private IkeEapAuthenticatorFactory mMockEapAuthenticatorFactory; + private EapAuthenticator mMockEapAuthenticator; + + private X509Certificate mRootCertificate; + private X509Certificate mServerEndCertificate; + + private ArgumentCaptor<IkeMessage> mIkeMessageCaptor = + ArgumentCaptor.forClass(IkeMessage.class); + private ArgumentCaptor<IkeSaRecordConfig> mIkeSaRecordConfigCaptor = + ArgumentCaptor.forClass(IkeSaRecordConfig.class); + private ArgumentCaptor<IChildSessionSmCallback> mChildSessionSmCbCaptor = + ArgumentCaptor.forClass(IChildSessionSmCallback.class); + private ArgumentCaptor<List<IkePayload>> mPayloadListCaptor = + ArgumentCaptor.forClass(List.class); + + private ReceivedIkePacket makeDummyReceivedIkeInitRespPacket( + long initiatorSpi, + long responderSpi, + @IkeHeader.ExchangeType int eType, + boolean isResp, + boolean fromIkeInit, + List<Integer> payloadTypeList, + List<String> payloadHexStringList) + throws Exception { + + List<IkePayload> payloadList = + hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp); + // Build a remotely generated NAT_DETECTION_SOURCE_IP payload to mock a remote node's + // network that is not behind NAT. + IkePayload sourceNatPayload = + new IkeNotifyPayload( + NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP, + IkeNotifyPayload.generateNatDetectionData( + initiatorSpi, + responderSpi, + REMOTE_ADDRESS, + IkeSocket.IKE_SERVER_PORT)); + payloadList.add(sourceNatPayload); + return makeDummyUnencryptedReceivedIkePacket( + initiatorSpi, responderSpi, eType, isResp, fromIkeInit, payloadList); + } + + private ReceivedIkePacket makeDummyUnencryptedReceivedIkePacket( + long initiatorSpi, + long responderSpi, + @IkeHeader.ExchangeType int eType, + boolean isResp, + boolean fromIkeInit, + List<IkePayload> payloadList) + throws Exception { + IkeMessage dummyIkeMessage = + makeDummyIkeMessageForTest( + initiatorSpi, + responderSpi, + eType, + isResp, + fromIkeInit, + 0, + false /*isEncrypted*/, + payloadList); + + byte[] dummyIkePacketBytes = new byte[0]; + when(mMockIkeMessageHelper.decode(0, dummyIkeMessage.ikeHeader, dummyIkePacketBytes)) + .thenReturn(new DecodeResultOk(dummyIkeMessage, dummyIkePacketBytes)); + + return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes); + } + + private ReceivedIkePacket makeDummyEncryptedReceivedIkePacket( + IkeSaRecord ikeSaRecord, + @IkeHeader.ExchangeType int eType, + boolean isResp, + List<Integer> payloadTypeList, + List<String> payloadHexStringList) + throws Exception { + List<IkePayload> payloadList = + hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp); + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + ikeSaRecord, eType, isResp, payloadList); + } + + private ReceivedIkePacket makeDummyEncryptedReceivedIkePacketWithPayloadList( + IkeSaRecord ikeSaRecord, + @IkeHeader.ExchangeType int eType, + boolean isResp, + List<IkePayload> payloadList) + throws Exception { + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + ikeSaRecord, + eType, + isResp, + isResp + ? ikeSaRecord.getLocalRequestMessageId() + : ikeSaRecord.getRemoteRequestMessageId(), + payloadList, + new byte[0] /*dummyIkePacketBytes*/); + } + + private ReceivedIkePacket makeDummyEncryptedReceivedIkePacketWithPayloadList( + IkeSaRecord ikeSaRecord, + @IkeHeader.ExchangeType int eType, + boolean isResp, + int msgId, + List<IkePayload> payloadList, + byte[] dummyIkePacketBytes) + throws Exception { + boolean fromIkeInit = !ikeSaRecord.isLocalInit; + IkeMessage dummyIkeMessage = + makeDummyIkeMessageForTest( + ikeSaRecord.getInitiatorSpi(), + ikeSaRecord.getResponderSpi(), + eType, + isResp, + fromIkeInit, + msgId, + true /*isEncyprted*/, + payloadList); + + setDecodeEncryptedPacketResult( + ikeSaRecord, + dummyIkeMessage.ikeHeader, + null /*collectedFrags*/, + new DecodeResultOk(dummyIkeMessage, dummyIkePacketBytes)); + + return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes); + } + + private ReceivedIkePacket makeDummyReceivedIkePacketWithInvalidSyntax( + IkeSaRecord ikeSaRecord, boolean isResp, int eType) { + return makeDummyReceivedIkePacketWithDecodingError( + ikeSaRecord, isResp, eType, new InvalidSyntaxException("IkeStateMachineTest")); + } + + private ReceivedIkePacket makeDummyReceivedIkePacketWithDecodingError( + IkeSaRecord ikeSaRecord, boolean isResp, int eType, IkeProtocolException exception) { + IkeHeader header = + makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SK); + byte[] dummyPacket = new byte[0]; + when(mMockIkeMessageHelper.decode( + anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any())) + .thenReturn(new DecodeResultProtectedError(exception, dummyPacket)); + + return new ReceivedIkePacket(header, dummyPacket); + } + + private ReceivedIkePacket makeDummyReceivedIkePacketWithUnprotectedError( + IkeSaRecord ikeSaRecord, boolean isResp, int eType, IkeException exception) { + IkeHeader header = + makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SK); + byte[] dummyPacket = new byte[0]; + when(mMockIkeMessageHelper.decode( + anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any())) + .thenReturn(new DecodeResultUnprotectedError(exception)); + + return new ReceivedIkePacket(header, dummyPacket); + } + + private ReceivedIkePacket makeDummyReceivedIkeFragmentPacket( + IkeSaRecord ikeSaRecord, + boolean isResp, + int eType, + IkeSkfPayload skfPayload, + int nextPayloadType, + DecodeResultPartial collectedFrags) { + IkeHeader header = + makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF); + + byte[] dummyPacket = new byte[0]; + DecodeResultPartial resultFrags = + new DecodeResultPartial( + header, dummyPacket, skfPayload, nextPayloadType, collectedFrags); + setDecodeEncryptedPacketResult(ikeSaRecord, header, collectedFrags, resultFrags); + + return new ReceivedIkePacket(header, dummyPacket); + } + + private ReceivedIkePacket makeDummyReceivedLastIkeFragmentPacketOk( + IkeSaRecord ikeSaRecord, + boolean isResp, + int eType, + DecodeResultPartial collectedFrags, + List<IkePayload> payloadList, + byte[] firstFragBytes) { + IkeHeader header = + makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF); + + IkeMessage completeMessage = new IkeMessage(header, payloadList); + + setDecodeEncryptedPacketResult( + ikeSaRecord, + header, + collectedFrags, + new DecodeResultOk(completeMessage, firstFragBytes)); + + return new ReceivedIkePacket(header, new byte[0] /*dummyIkePacketBytes*/); + } + + private ReceivedIkePacket makeDummyReceivedLastIkeFragmentPacketError( + IkeSaRecord ikeSaRecord, + boolean isResp, + int eType, + DecodeResultPartial collectedFrags, + IkeException exception) { + IkeHeader header = + makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF); + + byte[] dummyIkePacketBytes = new byte[0]; + setDecodeEncryptedPacketResult( + ikeSaRecord, + header, + collectedFrags, + new DecodeResultProtectedError(exception, dummyIkePacketBytes)); + + return new ReceivedIkePacket(header, dummyIkePacketBytes); + } + + private IkeHeader makeDummyIkeHeader( + IkeSaRecord ikeSaRecord, boolean isResp, int eType, int firstPayloadType) { + return new IkeHeader( + ikeSaRecord.getInitiatorSpi(), + ikeSaRecord.getResponderSpi(), + firstPayloadType, + eType, + isResp, + !ikeSaRecord.isLocalInit, + isResp + ? ikeSaRecord.getLocalRequestMessageId() + : ikeSaRecord.getRemoteRequestMessageId()); + } + + private void setDecodeEncryptedPacketResult( + IkeSaRecord ikeSaRecord, + IkeHeader header, + DecodeResultPartial collectedFrags, + DecodeResult result) { + when(mMockIkeMessageHelper.decode( + anyInt(), + any(), + any(), + eq(ikeSaRecord), + eq(header), + any(), + eq(collectedFrags))) + .thenReturn(result); + } + + private IkeMessage makeDummyIkeMessageForTest( + long initSpi, + long respSpi, + @IkeHeader.ExchangeType int eType, + boolean isResp, + boolean fromikeInit, + int messageId, + boolean isEncrypted, + List<IkePayload> payloadList) + throws Exception { + int firstPayloadType = + isEncrypted ? IkePayload.PAYLOAD_TYPE_SK : IkePayload.PAYLOAD_TYPE_NO_NEXT; + + IkeHeader header = + new IkeHeader( + initSpi, respSpi, firstPayloadType, eType, isResp, fromikeInit, messageId); + + return new IkeMessage(header, payloadList); + } + + private static List<IkePayload> hexStrListToIkePayloadList( + List<Integer> payloadTypeList, List<String> payloadHexStringList, boolean isResp) + throws Exception { + List<IkePayload> payloadList = new LinkedList<>(); + for (int i = 0; i < payloadTypeList.size(); i++) { + payloadList.add( + IkeTestUtils.hexStringToIkePayload( + payloadTypeList.get(i), isResp, payloadHexStringList.get(i))); + } + return payloadList; + } + + private void verifyDecodeEncryptedMessage(IkeSaRecord record, ReceivedIkePacket rcvPacket) + throws Exception { + verify(mMockIkeMessageHelper) + .decode( + anyInt(), + any(), + any(), + eq(record), + eq(rcvPacket.ikeHeader), + eq(rcvPacket.ikePacketBytes), + eq(null)); + } + + private static IkeSaRecord makeDummyIkeSaRecord(long initSpi, long respSpi, boolean isLocalInit) + throws IOException { + Inet4Address initAddress = isLocalInit ? LOCAL_ADDRESS : REMOTE_ADDRESS; + Inet4Address respAddress = isLocalInit ? REMOTE_ADDRESS : LOCAL_ADDRESS; + + return new IkeSaRecord( + IkeSecurityParameterIndex.allocateSecurityParameterIndex(initAddress, initSpi), + IkeSecurityParameterIndex.allocateSecurityParameterIndex(respAddress, respSpi), + isLocalInit, + TestUtils.hexStringToByteArray(NONCE_INIT_HEX_STRING), + TestUtils.hexStringToByteArray(NONCE_RESP_HEX_STRING), + new byte[KEY_LEN_IKE_SKD], + new byte[KEY_LEN_IKE_INTE], + new byte[KEY_LEN_IKE_INTE], + new byte[KEY_LEN_IKE_ENCR], + new byte[KEY_LEN_IKE_ENCR], + TestUtils.hexStringToByteArray(PRF_KEY_INIT_HEX_STRING), + TestUtils.hexStringToByteArray(PRF_KEY_RESP_HEX_STRING), + new LocalRequest(CMD_LOCAL_REQUEST_REKEY_IKE)); + } + + @Before + public void setUp() throws Exception { + mSpyIkeLog = TestUtils.makeSpyLogThrowExceptionForWtf(TAG); + IkeManager.setIkeLog(mSpyIkeLog); + + mMockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec(); + mIpSecManager = mMockIpSecTestUtils.getIpSecManager(); + mContext = mMockIpSecTestUtils.getContext(); + mUdpEncapSocket = mIpSecManager.openUdpEncapsulationSocket(); + mEapSessionConfig = + new EapSessionConfig.Builder() + .setEapSimConfig(EAP_SIM_SUB_ID, TelephonyManager.APPTYPE_USIM) + .build(); + + mMockEapAuthenticatorFactory = mock(IkeEapAuthenticatorFactory.class); + mMockEapAuthenticator = mock(EapAuthenticator.class); + when(mMockEapAuthenticatorFactory.newEapAuthenticator(any(), any(), any(), any())) + .thenReturn(mMockEapAuthenticator); + + mRootCertificate = CertUtils.createCertFromPemFile("self-signed-ca-a.pem"); + mServerEndCertificate = CertUtils.createCertFromPemFile("end-cert-a.pem"); + + mPsk = TestUtils.hexStringToByteArray(PSK_HEX_STRING); + + mChildSessionOptions = buildChildSessionOptions(); + + mIkeEncryptionTransform = + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128); + mIkeIntegrityTransform = + new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96); + mIkePrfTransform = new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1); + mIkeDhGroupTransform = new DhGroupTransform(SaProposal.DH_GROUP_1024_BIT_MODP); + + mSpyUserCbExecutor = + spy( + (command) -> { + command.run(); + }); + + mMockIkeSessionCallback = mock(IkeSessionCallback.class); + mMockChildSessionCallback = mock(ChildSessionCallback.class); + + mLooper = new TestLooper(); + + mMockChildSessionStateMachine = mock(ChildSessionStateMachine.class); + mMockChildSessionFactoryHelper = mock(IChildSessionFactoryHelper.class); + ChildSessionStateMachineFactory.setChildSessionFactoryHelper( + mMockChildSessionFactoryHelper); + setupChildStateMachineFactory(mMockChildSessionStateMachine); + + // Inject longer retransmission timeout + mMockBackoffTimeoutCalculator = mock(IBackoffTimeoutCalculator.class); + when(mMockBackoffTimeoutCalculator.getExponentialBackoffTimeout(anyInt())) + .thenReturn(RETRANSMIT_BACKOFF_TIMEOUT_MS); + Retransmitter.setBackoffTimeoutCalculator(mMockBackoffTimeoutCalculator); + + // Setup state machine + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsPsk(mPsk)); + + mMockIkeMessageHelper = mock(IkeMessage.IIkeMessageHelper.class); + IkeMessage.setIkeMessageHelper(mMockIkeMessageHelper); + resetMockIkeMessageHelper(); + + mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class); + SaRecord.setSaRecordHelper(mMockSaRecordHelper); + + mSpyCurrentIkeSaRecord = spy(makeDummyIkeSaRecord(11, 12, true)); + mSpyLocalInitIkeSaRecord = spy(makeDummyIkeSaRecord(21, 22, true)); + mSpyRemoteInitIkeSaRecord = spy(makeDummyIkeSaRecord(31, 32, false)); + + mExpectedCurrentSaLocalReqMsgId = 0; + mExpectedCurrentSaRemoteReqMsgId = 0; + } + + @After + public void tearDown() throws Exception { + mIkeSessionStateMachine.quit(); + mIkeSessionStateMachine.setDbg(false); + mUdpEncapSocket.close(); + + mSpyCurrentIkeSaRecord.close(); + mSpyLocalInitIkeSaRecord.close(); + mSpyRemoteInitIkeSaRecord.close(); + + IkeManager.resetIkeLog(); + Retransmitter.resetBackoffTimeoutCalculator(); + IkeMessage.setIkeMessageHelper(new IkeMessageHelper()); + SaRecord.setSaRecordHelper(new SaRecordHelper()); + ChildSessionStateMachineFactory.setChildSessionFactoryHelper( + new ChildSessionFactoryHelper()); + } + + private IkeSessionStateMachine makeAndStartIkeSession(IkeSessionOptions ikeOptions) + throws Exception { + IkeSessionStateMachine ikeSession = + new IkeSessionStateMachine( + mLooper.getLooper(), + mContext, + mIpSecManager, + ikeOptions, + mChildSessionOptions, + mSpyUserCbExecutor, + mMockIkeSessionCallback, + mMockChildSessionCallback, + mMockEapAuthenticatorFactory); + ikeSession.setDbg(true); + + mLooper.dispatchAll(); + ikeSession.mLocalAddress = LOCAL_ADDRESS; + + mSpyIkeSocket = spy(IkeSocket.getIkeSocket(mUdpEncapSocket, ikeSession)); + doNothing().when(mSpyIkeSocket).sendIkePacket(any(), any()); + ikeSession.mIkeSocket = mSpyIkeSocket; + + return ikeSession; + } + + public static IkeSaProposal buildSaProposal() throws Exception { + return new IkeSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .build(); + } + + private IkeSessionOptions.Builder buildIkeSessionOptionsCommon() throws Exception { + return new IkeSessionOptions.Builder() + .setServerAddress(REMOTE_ADDRESS) + .setUdpEncapsulationSocket(mUdpEncapSocket) + .addSaProposal(buildSaProposal()) + .setLocalIdentification(new IkeIpv4AddrIdentification((Inet4Address) LOCAL_ADDRESS)) + .setRemoteIdentification( + new IkeIpv4AddrIdentification((Inet4Address) REMOTE_ADDRESS)); + } + + private IkeSessionOptions buildIkeSessionOptionsPsk(byte[] psk) throws Exception { + return buildIkeSessionOptionsCommon().setAuthPsk(psk).build(); + } + + private IkeSessionOptions buildIkeSessionOptionsEap() throws Exception { + return buildIkeSessionOptionsCommon() + .setAuthEap(mRootCertificate, mEapSessionConfig) + .build(); + } + + private ChildSessionOptions buildChildSessionOptions() throws Exception { + ChildSaProposal saProposal = + new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .build(); + + return new TransportModeChildSessionOptions.Builder().addSaProposal(saProposal).build(); + } + + private ReceivedIkePacket makeIkeInitResponse() throws Exception { + // TODO: Build real IKE INIT response when IKE INIT response validation is implemented. + List<Integer> payloadTypeList = new LinkedList<>(); + List<String> payloadHexStringList = new LinkedList<>(); + + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY); + + payloadHexStringList.add(IKE_SA_PAYLOAD_HEX_STRING); + payloadHexStringList.add(KE_PAYLOAD_HEX_STRING); + payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING); + payloadHexStringList.add(NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING); + payloadHexStringList.add(NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING); + payloadHexStringList.add(FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING); + + // In each test assign different IKE responder SPI in IKE INIT response to avoid remote SPI + // collision during response validation. + // STOPSHIP: b/131617794 allow #mockIkeSetup to be independent in each test after we can + // support IkeSession cleanup. + return makeDummyReceivedIkeInitRespPacket( + 1L /*initiator SPI*/, + 2L /*responder SPI*/, + IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT, + true /*isResp*/, + false /*fromIkeInit*/, + payloadTypeList, + payloadHexStringList); + } + + private List<IkePayload> getIkeAuthPayloadListWithChildPayloads( + List<IkePayload> authRelatedPayloads) throws Exception { + List<Integer> payloadTypeList = new LinkedList<>(); + List<String> payloadHexStringList = new LinkedList<>(); + + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER); + + payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING); + payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING); + payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING); + + List<IkePayload> payloadList = + hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, true /*isResp*/); + payloadList.addAll(authRelatedPayloads); + + return payloadList; + } + + private ReceivedIkePacket makeIkeAuthRespWithChildPayloads(List<IkePayload> authRelatedPayloads) + throws Exception { + List<IkePayload> payloadList = getIkeAuthPayloadListWithChildPayloads(authRelatedPayloads); + + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_IKE_AUTH, + true /*isResp*/, + payloadList); + } + + private ReceivedIkePacket makeIkeAuthRespWithoutChildPayloads( + List<IkePayload> authRelatedPayloads) throws Exception { + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_IKE_AUTH, + true /*isResp*/, + authRelatedPayloads); + } + + private ReceivedIkePacket makeCreateChildCreateMessage(boolean isResp) throws Exception { + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, + isResp, + makeCreateChildPayloadList(isResp)); + } + + private ReceivedIkePacket makeRekeyChildCreateMessage(boolean isResp, int spi) + throws Exception { + IkeNotifyPayload rekeyPayload = + new IkeNotifyPayload( + IkePayload.PROTOCOL_ID_ESP, + spi, + IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA, + new byte[0]); + + List<IkePayload> payloadList = makeCreateChildPayloadList(isResp); + payloadList.add(rekeyPayload); + + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, + isResp, + payloadList); + } + + private List<IkePayload> makeCreateChildPayloadList(boolean isResp) throws Exception { + List<Integer> payloadTypeList = new LinkedList<>(); + List<String> payloadHexStringList = new LinkedList<>(); + + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER); + + payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING); + payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING); + payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING); + payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING); + + return hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp); + } + + private ReceivedIkePacket makeDeleteChildPacket(IkeDeletePayload[] payloads, boolean isResp) + throws Exception { + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, + isResp, + Arrays.asList(payloads)); + } + + private ReceivedIkePacket makeRekeyIkeResponse() throws Exception { + List<Integer> payloadTypeList = new LinkedList<>(); + List<String> payloadHexStringList = new LinkedList<>(); + + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE); + + payloadHexStringList.add(IKE_REKEY_SA_PAYLOAD_HEX_STRING); + payloadHexStringList.add(KE_PAYLOAD_HEX_STRING); + payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING); + + return makeDummyEncryptedReceivedIkePacket( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, + true /*isResp*/, + payloadTypeList, + payloadHexStringList); + } + + private ReceivedIkePacket makeDeleteIkeResponse(IkeSaRecord ikeSaRecord) throws Exception { + return makeDummyEncryptedReceivedIkePacket( + ikeSaRecord, + IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, + true /*isResp*/, + new LinkedList<>(), + new LinkedList<>()); + } + + private ReceivedIkePacket makeDpdIkeRequest(IkeSaRecord saRecord) throws Exception { + return makeDummyEncryptedReceivedIkePacket( + saRecord, + IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, + false /*isResp*/, + new LinkedList<>(), + new LinkedList<>()); + } + + private ReceivedIkePacket makeDpdIkeRequest(int msgId, byte[] dummyIkePacketBytes) + throws Exception { + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + EXCHANGE_TYPE_INFORMATIONAL, + false /*isResp*/, + msgId, + new LinkedList<>(), + dummyIkePacketBytes); + } + + private ReceivedIkePacket makeRekeyIkeRequest() throws Exception { + IkeSaPayload saPayload = + (IkeSaPayload) + IkeTestUtils.hexStringToIkePayload( + IkePayload.PAYLOAD_TYPE_SA, + false /*isResp*/, + IKE_REKEY_SA_PAYLOAD_HEX_STRING); + return makeRekeyIkeRequest(saPayload); + } + + private ReceivedIkePacket makeRekeyIkeRequestWithUnacceptableProposal() throws Exception { + IkeSaPayload saPayload = + (IkeSaPayload) + IkeTestUtils.hexStringToIkePayload( + IkePayload.PAYLOAD_TYPE_SA, + false /*isResp*/, + IKE_REKEY_UNACCEPTABLE_SA_PAYLOAD_HEX_STRING); + return makeRekeyIkeRequest(saPayload); + } + + private ReceivedIkePacket makeRekeyIkeRequest(IkeSaPayload saPayload) throws Exception { + List<Integer> payloadTypeList = new LinkedList<>(); + List<String> payloadHexStringList = new LinkedList<>(); + + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE); + + payloadHexStringList.add(KE_PAYLOAD_HEX_STRING); + payloadHexStringList.add(NONCE_INIT_PAYLOAD_HEX_STRING); + + List<IkePayload> payloadList = + hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, false /*isResp*/); + payloadList.add(saPayload); + + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, + false /*isResp*/, + payloadList); + } + + private ReceivedIkePacket makeDeleteIkeRequest(IkeSaRecord saRecord) throws Exception { + List<Integer> payloadTypeList = new LinkedList<>(); + List<String> payloadHexStringList = new LinkedList<>(); + + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_DELETE); + + payloadHexStringList.add(DELETE_IKE_PAYLOAD_HEX_STRING); + + return makeDummyEncryptedReceivedIkePacket( + saRecord, + IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, + false /*isResp*/, + payloadTypeList, + payloadHexStringList); + } + + private ReceivedIkePacket makeResponseWithErrorNotify(IkeNotifyPayload notify) + throws Exception { + List<IkePayload> payloads = new LinkedList<>(); + payloads.add(notify); + return makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, EXCHANGE_TYPE_INFORMATIONAL, true /*isResp*/, payloads); + } + + private static boolean isIkePayloadExist( + List<IkePayload> payloadList, @IkePayload.PayloadType int payloadType) { + for (IkePayload payload : payloadList) { + if (payload.payloadType == payloadType) return true; + } + return false; + } + + private static boolean isNotifyExist( + List<IkePayload> payloadList, @IkeNotifyPayload.NotifyType int notifyType) { + for (IkeNotifyPayload notify : + IkePayload.getPayloadListForTypeInProvidedList( + PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, payloadList)) { + if (notify.notifyType == notifyType) return true; + } + return false; + } + + private void verifyIncrementLocaReqMsgId() { + assertEquals( + ++mExpectedCurrentSaLocalReqMsgId, + mSpyCurrentIkeSaRecord.getLocalRequestMessageId()); + } + + private void verifyIncrementRemoteReqMsgId() { + assertEquals( + ++mExpectedCurrentSaRemoteReqMsgId, + mSpyCurrentIkeSaRecord.getRemoteRequestMessageId()); + } + + private void verifyRetransmissionStarted() { + assertTrue( + mIkeSessionStateMachine + .getHandler() + .hasMessages(IkeSessionStateMachine.CMD_RETRANSMIT)); + } + + private void verifyRetransmissionStopped() { + assertFalse( + mIkeSessionStateMachine + .getHandler() + .hasMessages(IkeSessionStateMachine.CMD_RETRANSMIT)); + } + + private IkeMessage verifyEncryptAndEncodeAndGetMessage(IkeSaRecord ikeSaRecord) { + verify(mMockIkeMessageHelper) + .encryptAndEncode( + anyObject(), + anyObject(), + eq(ikeSaRecord), + mIkeMessageCaptor.capture(), + anyBoolean(), + anyInt()); + return mIkeMessageCaptor.getValue(); + } + + private void verifyEncryptAndEncodeNeverCalled(IkeSaRecord ikeSaRecord) { + verify(mMockIkeMessageHelper, never()) + .encryptAndEncode( + anyObject(), + anyObject(), + eq(ikeSaRecord), + any(IkeMessage.class), + anyBoolean(), + anyInt()); + } + + private void verifyEncryptAndEncodeNeverCalled() { + verify(mMockIkeMessageHelper, never()) + .encryptAndEncode( + anyObject(), + anyObject(), + any(IkeSaRecord.class), + any(IkeMessage.class), + anyBoolean(), + anyInt()); + } + + private void resetMockIkeMessageHelper() { + reset(mMockIkeMessageHelper); + when(mMockIkeMessageHelper.encode(any())).thenReturn(new byte[0]); + when(mMockIkeMessageHelper.encryptAndEncode( + any(), any(), any(), any(), anyBoolean(), anyInt())) + .thenReturn(new byte[1][0]); + } + + @Test + public void testQuit() { + mIkeSessionStateMachine.quit(); + mLooper.dispatchAll(); + + verify(mSpyIkeSocket).releaseReference(eq(mIkeSessionStateMachine)); + verify(mSpyIkeSocket).close(); + } + + @Test + public void testAllocateIkeSpi() throws Exception { + // Test randomness. + IkeSecurityParameterIndex ikeSpiOne = + IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS); + IkeSecurityParameterIndex ikeSpiTwo = + IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS); + + assertNotEquals(ikeSpiOne.getSpi(), ikeSpiTwo.getSpi()); + ikeSpiTwo.close(); + + // Test duplicate SPIs. + long spiValue = ikeSpiOne.getSpi(); + try { + IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS, spiValue); + fail("Expected to fail because duplicate SPI was assigned to the same address."); + } catch (IOException expected) { + + } + + ikeSpiOne.close(); + IkeSecurityParameterIndex ikeSpiThree = + IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS, spiValue); + ikeSpiThree.close(); + } + + private void setupFirstIkeSa() throws Exception { + // Inject IkeSaRecord and release IKE SPI resource since we will lose their references + // later. + when(mMockSaRecordHelper.makeFirstIkeSaRecord(any(), any(), any())) + .thenAnswer( + (invocation) -> { + captureAndReleaseIkeSpiResource(invocation, 2); + return mSpyCurrentIkeSaRecord; + }); + } + + private void setupRekeyedIkeSa(IkeSaRecord rekeySaRecord) throws Exception { + // Inject IkeSaRecord and release IKE SPI resource since we will lose their references + // later. + when(mMockSaRecordHelper.makeRekeyedIkeSaRecord( + eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any())) + .thenAnswer( + (invocation) -> { + captureAndReleaseIkeSpiResource(invocation, 4); + return rekeySaRecord; + }); + } + + private void throwExceptionWhenMakeRekeyIkeSa(Exception exception) throws Exception { + // Inject IkeSaRecord and release IKE SPI resource since we will lose their references + // later. + when(mMockSaRecordHelper.makeRekeyedIkeSaRecord( + eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any())) + .thenAnswer( + (invocation) -> { + captureAndReleaseIkeSpiResource(invocation, 4); + throw exception; + }); + } + + private void captureAndReleaseIkeSpiResource(InvocationOnMock invocation, int ikeConfigIndex) { + IkeSaRecordConfig config = (IkeSaRecordConfig) invocation.getArguments()[ikeConfigIndex]; + config.initSpi.close(); + config.respSpi.close(); + } + + @Test + public void testCreateIkeLocalIkeInit() throws Exception { + setupFirstIkeSa(); + + // Send IKE INIT request + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + // Receive IKE INIT response + ReceivedIkePacket dummyReceivedIkePacket = makeIkeInitResponse(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket); + mLooper.dispatchAll(); + verifyIncrementLocaReqMsgId(); + + // Validate outbound IKE INIT request + verify(mMockIkeMessageHelper, times(2)).encode(mIkeMessageCaptor.capture()); + IkeMessage ikeInitReqMessage = mIkeMessageCaptor.getValue(); + + IkeHeader ikeHeader = ikeInitReqMessage.ikeHeader; + assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT, ikeHeader.exchangeType); + assertFalse(ikeHeader.isResponseMsg); + assertTrue(ikeHeader.fromIkeInitiator); + + List<IkePayload> payloadList = ikeInitReqMessage.ikePayloadList; + assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_SA)); + assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_KE)); + assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_NONCE)); + assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP)); + assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP)); + assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED)); + + verify(mSpyIkeSocket) + .registerIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine)); + + verify(mMockIkeMessageHelper) + .decode(0, dummyReceivedIkePacket.ikeHeader, dummyReceivedIkePacket.ikePacketBytes); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + + // Validate negotiated SA proposal. + IkeSaProposal negotiatedProposal = mIkeSessionStateMachine.mSaProposal; + assertNotNull(negotiatedProposal); + + assertEquals( + new EncryptionTransform[] {mIkeEncryptionTransform}, + negotiatedProposal.getEncryptionTransforms()); + assertEquals( + new IntegrityTransform[] {mIkeIntegrityTransform}, + negotiatedProposal.getIntegrityTransforms()); + assertEquals(new PrfTransform[] {mIkePrfTransform}, negotiatedProposal.getPrfTransforms()); + + // Validate current IkeSaRecord. + verify(mMockSaRecordHelper) + .makeFirstIkeSaRecord( + any(IkeMessage.class), + any(IkeMessage.class), + mIkeSaRecordConfigCaptor.capture()); + + IkeSaRecordConfig ikeSaRecordConfig = mIkeSaRecordConfigCaptor.getValue(); + assertEquals(KEY_LEN_IKE_PRF, ikeSaRecordConfig.prf.getKeyLength()); + assertEquals(KEY_LEN_IKE_INTE, ikeSaRecordConfig.integrityKeyLength); + assertEquals(KEY_LEN_IKE_ENCR, ikeSaRecordConfig.encryptionKeyLength); + assertEquals(CMD_LOCAL_REQUEST_REKEY_IKE, ikeSaRecordConfig.futureRekeyEvent.procedureType); + + // Validate NAT detection + assertTrue(mIkeSessionStateMachine.mIsLocalBehindNat); + assertFalse(mIkeSessionStateMachine.mIsRemoteBehindNat); + + // Validate fragmentation support negotiation + assertTrue(mIkeSessionStateMachine.mSupportFragment); + } + + private void setIkeInitResults() throws Exception { + mIkeSessionStateMachine.mIkeCipher = mock(IkeCipher.class); + mIkeSessionStateMachine.mIkeIntegrity = mock(IkeMacIntegrity.class); + mIkeSessionStateMachine.mIkePrf = mock(IkeMacPrf.class); + mIkeSessionStateMachine.mSaProposal = buildSaProposal(); + mIkeSessionStateMachine.mCurrentIkeSaRecord = mSpyCurrentIkeSaRecord; + mIkeSessionStateMachine.mLocalAddress = LOCAL_ADDRESS; + mIkeSessionStateMachine.mIsLocalBehindNat = true; + mIkeSessionStateMachine.mIsRemoteBehindNat = false; + mIkeSessionStateMachine.mSupportFragment = true; + mIkeSessionStateMachine.addIkeSaRecord(mSpyCurrentIkeSaRecord); + } + + /** Initializes the mIkeSessionStateMachine in the IDLE state. */ + private void setupIdleStateMachine() throws Exception { + setIkeInitResults(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle); + mLooper.dispatchAll(); + + mDummyChildSmCallback = + createChildAndGetChildSessionSmCallback( + mMockChildSessionStateMachine, CHILD_SPI_REMOTE, mMockChildSessionCallback); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + private void mockIkeInitAndTransitionToIkeAuth(State authState) throws Exception { + setIkeInitResults(); + + // Need to create a real IkeMacPrf instance for authentication because we cannot inject a + // method stub for IkeMacPrf#signBytes. IkeMacPrf#signBytes is inheritted from a package + // protected class IkePrf. We don't have the visibility to mock it. + mIkeSessionStateMachine.mIkePrf = + IkeMacPrf.create( + new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1), + IkeMessage.getSecurityProvider()); + + mIkeSessionStateMachine.mIkeInitRequestBytes = new byte[0]; + mIkeSessionStateMachine.mIkeInitResponseBytes = new byte[0]; + mIkeSessionStateMachine.mIkeInitNoncePayload = new IkeNoncePayload(); + mIkeSessionStateMachine.mIkeRespNoncePayload = new IkeNoncePayload(); + + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_FORCE_TRANSITION, authState); + mLooper.dispatchAll(); + } + + private void setupChildStateMachineFactory(ChildSessionStateMachine child) { + // After state machine start, add to the callback->statemachine map + when(mMockChildSessionFactoryHelper.makeChildSessionStateMachine( + eq(mLooper.getLooper()), + eq(mContext), + eq(mChildSessionOptions), + eq(mSpyUserCbExecutor), + any(ChildSessionCallback.class), + any(IChildSessionSmCallback.class))) + .thenReturn(child); + } + + /** + * Utility to register a new callback -> state machine mapping. + * + * <p>Must be used if IkeSessionStateMachine.openChildSession() is not called, but commands + * injected instead. + * + * @param callback The callback to be used for the mapping + * @param sm The ChildSessionStateMachine instance to be used. + */ + private void registerChildStateMachine( + ChildSessionCallback callback, ChildSessionStateMachine sm) { + setupChildStateMachineFactory(sm); + mIkeSessionStateMachine.registerChildSessionCallback( + mChildSessionOptions, callback, false /*isFirstChild*/); + } + + @Test + public void testCreateAdditionalChild() throws Exception { + setupIdleStateMachine(); + + ChildSessionCallback childCallback = mock(ChildSessionCallback.class); + ChildSessionStateMachine childStateMachine = mock(ChildSessionStateMachine.class); + registerChildStateMachine(childCallback, childStateMachine); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new ChildLocalRequest( + IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_CHILD, + childCallback, + mChildSessionOptions)); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + verify(childStateMachine) + .createChildSession( + eq(LOCAL_ADDRESS), + eq(REMOTE_ADDRESS), + any(), // udpEncapSocket + eq(mIkeSessionStateMachine.mIkePrf), + any()); // sk_d + + // Once for initial child, a second time for the additional child. + verify(mMockChildSessionFactoryHelper) + .makeChildSessionStateMachine( + eq(mLooper.getLooper()), + eq(mContext), + eq(mChildSessionOptions), + eq(mSpyUserCbExecutor), + eq(childCallback), + mChildSessionSmCbCaptor.capture()); + IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue(); + + // Mocking sending request + cb.onOutboundPayloadsReady( + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, + false /*isResp*/, + new LinkedList<>(), + childStateMachine); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + IkeMessage createChildRequest = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + IkeHeader ikeHeader = createChildRequest.ikeHeader; + assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, ikeHeader.exchangeType); + assertFalse(ikeHeader.isResponseMsg); + assertTrue(ikeHeader.fromIkeInitiator); + assertEquals(mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), ikeHeader.messageId); + assertTrue(createChildRequest.ikePayloadList.isEmpty()); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + + // Mocking receiving response + ReceivedIkePacket dummyCreateChildResp = makeCreateChildCreateMessage(true /*isResp*/); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyCreateChildResp); + mLooper.dispatchAll(); + + verifyIncrementLocaReqMsgId(); + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyCreateChildResp); + + verify(childStateMachine) + .receiveResponse( + eq(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA), mPayloadListCaptor.capture()); + + List<IkePayload> childRespList = mPayloadListCaptor.getValue(); + assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_SA)); + assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR)); + assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER)); + assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_NONCE)); + + // Mock finishing procedure + cb.onProcedureFinished(childStateMachine); + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + verifyRetransmissionStopped(); + } + + @Test + public void testTriggerDeleteChildLocal() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new ChildLocalRequest( + IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD, + mMockChildSessionCallback, + null /*childOptions*/)); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + verify(mMockChildSessionStateMachine).deleteChildSession(); + } + + @Test + public void testHandleDeleteChildBeforeCreation() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new ChildLocalRequest( + IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD, + mock(ChildSessionCallback.class), + null /*childOptions*/)); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + @Test + public void testTriggerRekeyChildLocal() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new ChildLocalRequest( + IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD, + mMockChildSessionCallback, + null /*childOptions*/)); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + verify(mMockChildSessionStateMachine).rekeyChildSession(); + } + + @Test + public void testScheduleAndTriggerRekeyChildLocal() throws Exception { + setupIdleStateMachine(); + long dummyRekeyTimeout = 10000L; + + ChildLocalRequest rekeyRequest = + new ChildLocalRequest( + IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD, + mMockChildSessionCallback, + null /*childOptions*/); + mDummyChildSmCallback.scheduleLocalRequest(rekeyRequest, dummyRekeyTimeout); + + mLooper.moveTimeForward(dummyRekeyTimeout); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + verify(mMockChildSessionStateMachine).rekeyChildSession(); + } + + private IChildSessionSmCallback createChildAndGetChildSessionSmCallback( + ChildSessionStateMachine child, int remoteSpi) throws Exception { + return createChildAndGetChildSessionSmCallback( + child, remoteSpi, mock(ChildSessionCallback.class)); + } + + private IChildSessionSmCallback createChildAndGetChildSessionSmCallback( + ChildSessionStateMachine child, int remoteSpi, ChildSessionCallback childCallback) + throws Exception { + registerChildStateMachine(childCallback, child); + + IChildSessionSmCallback cb = mIkeSessionStateMachine.new ChildSessionSmCallback(); + cb.onChildSaCreated(remoteSpi, child); + mLooper.dispatchAll(); + + return cb; + } + + private void transitionToChildProcedureOngoing() { + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mChildProcedureOngoing); + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + } + + private void verifyChildReceiveDeleteRequest( + ChildSessionStateMachine child, IkeDeletePayload[] expectedDelPayloads) { + verify(child) + .receiveRequest( + eq(IKE_EXCHANGE_SUBTYPE_DELETE_CHILD), + eq(EXCHANGE_TYPE_INFORMATIONAL), + mPayloadListCaptor.capture()); + List<IkePayload> reqPayloads = mPayloadListCaptor.getValue(); + + int numExpectedDelPayloads = expectedDelPayloads.length; + assertEquals(numExpectedDelPayloads, reqPayloads.size()); + + for (int i = 0; i < numExpectedDelPayloads; i++) { + assertEquals(expectedDelPayloads[i], (IkeDeletePayload) reqPayloads.get(i)); + } + } + + private void outboundDeleteChildPayloadsReady( + IChildSessionSmCallback childSmCb, + IkeDeletePayload delPayload, + boolean isResp, + ChildSessionStateMachine child) { + List<IkePayload> outPayloadList = new LinkedList<>(); + outPayloadList.add(delPayload); + childSmCb.onOutboundPayloadsReady( + IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, isResp, outPayloadList, child); + mLooper.dispatchAll(); + } + + private List<IkePayload> verifyOutInfoMsgHeaderAndGetPayloads(boolean isResp) { + IkeMessage deleteChildMessage = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + IkeHeader ikeHeader = deleteChildMessage.ikeHeader; + assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), ikeHeader.ikeInitiatorSpi); + assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), ikeHeader.ikeResponderSpi); + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType); + assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator); + assertEquals(isResp, ikeHeader.isResponseMsg); + + return deleteChildMessage.ikePayloadList; + } + + @Test + public void testDeferChildRequestToChildProcedureOngoing() throws Exception { + setupIdleStateMachine(); + + IkeDeletePayload[] inboundDelPayloads = + new IkeDeletePayload[] {new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE})}; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads); + } + + @Test + public void testRemoteDeleteOneChild() throws Exception { + setupIdleStateMachine(); + transitionToChildProcedureOngoing(); + + // Receive Delete Child Request + IkeDeletePayload[] inboundDelPayloads = + new IkeDeletePayload[] {new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE})}; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); + mLooper.dispatchAll(); + + // Verify received payloads + verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads); + + // Outbound payload list ready + IkeDeletePayload outDelPayload = new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL}); + outboundDeleteChildPayloadsReady( + mDummyChildSmCallback, + outDelPayload, + true /*isResp*/, + mMockChildSessionStateMachine); + + // Verify outbound response + List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, payloadList.size()); + assertEquals(outDelPayload, ((IkeDeletePayload) payloadList.get(0))); + } + + @Test + public void testRemoteDeleteMultipleChildSession() throws Exception { + ChildSessionStateMachine childOne = mock(ChildSessionStateMachine.class); + int childOneRemoteSpi = 11; + int childOneLocalSpi = 12; + + ChildSessionStateMachine childTwo = mock(ChildSessionStateMachine.class); + int childTwoRemoteSpi = 21; + int childTwoLocalSpi = 22; + + setupIdleStateMachine(); + IChildSessionSmCallback childSmCbOne = + createChildAndGetChildSessionSmCallback(childOne, childOneRemoteSpi); + IChildSessionSmCallback childSmCbTwo = + createChildAndGetChildSessionSmCallback(childTwo, childTwoRemoteSpi); + + transitionToChildProcedureOngoing(); + + // Receive Delete Child Request + IkeDeletePayload[] inboundDelPayloads = + new IkeDeletePayload[] { + new IkeDeletePayload(new int[] {childOneRemoteSpi, childTwoRemoteSpi}) + }; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); + mLooper.dispatchAll(); + + // Verify received payloads + verifyChildReceiveDeleteRequest(childOne, inboundDelPayloads); + verifyChildReceiveDeleteRequest(childTwo, inboundDelPayloads); + + // childOne outbound payload list ready + IkeDeletePayload outDelPayloadOne = new IkeDeletePayload(new int[] {childOneLocalSpi}); + outboundDeleteChildPayloadsReady(childSmCbOne, outDelPayloadOne, true /*isResp*/, childOne); + mLooper.dispatchAll(); + + // Verify that no response is sent + verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord); + + // childTwo outbound payload list ready + IkeDeletePayload outDelPayloadTwo = new IkeDeletePayload(new int[] {childTwoLocalSpi}); + outboundDeleteChildPayloadsReady(childSmCbTwo, outDelPayloadTwo, true /*isResp*/, childTwo); + mLooper.dispatchAll(); + + // Verify outbound response + List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(2, payloadList.size()); + assertEquals(outDelPayloadOne, ((IkeDeletePayload) payloadList.get(0))); + assertEquals(outDelPayloadTwo, ((IkeDeletePayload) payloadList.get(1))); + } + + @Test + public void testRemoteDeleteMultipleChildSaInSameSession() throws Exception { + int newChildRemoteSpi = 21; + int newChildLocalSpi = 22; + + setupIdleStateMachine(); + mDummyChildSmCallback.onChildSaCreated(newChildRemoteSpi, mMockChildSessionStateMachine); + + transitionToChildProcedureOngoing(); + + // Receive Delete Child Request + IkeDeletePayload[] inboundDelPayloads = + new IkeDeletePayload[] { + new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE}), + new IkeDeletePayload(new int[] {newChildRemoteSpi}) + }; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); + mLooper.dispatchAll(); + + // Verify received payloads + verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads); + + // child outbound payload list ready + IkeDeletePayload outDelPayload = + new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL, newChildLocalSpi}); + outboundDeleteChildPayloadsReady( + mDummyChildSmCallback, + outDelPayload, + true /*isResp*/, + mMockChildSessionStateMachine); + mLooper.dispatchAll(); + + // Verify outbound response + List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, payloadList.size()); + assertEquals(outDelPayload, ((IkeDeletePayload) payloadList.get(0))); + } + + @Test + public void testIgnoreUnrecognizedChildSpi() throws Exception { + int unrecognizedSpi = 2; + + setupIdleStateMachine(); + transitionToChildProcedureOngoing(); + + // Receive Delete Child Request + IkeDeletePayload[] inboundDelPayloads = + new IkeDeletePayload[] { + new IkeDeletePayload(new int[] {unrecognizedSpi, CHILD_SPI_REMOTE}) + }; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); + mLooper.dispatchAll(); + + // Verify received payloads + verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads); + + // child outbound payload list ready + IkeDeletePayload outPayload = new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL}); + outboundDeleteChildPayloadsReady( + mDummyChildSmCallback, outPayload, true /*isResp*/, mMockChildSessionStateMachine); + mLooper.dispatchAll(); + + // Verify outbound response + List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, payloadList.size()); + assertEquals(outPayload, ((IkeDeletePayload) payloadList.get(0))); + } + + @Test + public void testRemoteDeleteChildHandlesReqWithNoRecognizedSpi() throws Exception { + int unrecognizedSpi = 2; + + setupIdleStateMachine(); + + // Receive Delete Child Request without any recognized SPI + IkeDeletePayload[] inboundDelPayloads = + new IkeDeletePayload[] {new IkeDeletePayload(new int[] {unrecognizedSpi})}; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); + mLooper.dispatchAll(); + + // Verify outbound empty response was sent + List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertTrue(payloadList.isEmpty()); + + // Verify IKE Session was back to Idle + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + @Test + public void testRemoteCreateChild() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + CMD_RECEIVE_IKE_PACKET, makeCreateChildCreateMessage(false /*isResp*/)); + + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + + List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, ikePayloadList.size()); + assertEquals( + ERROR_TYPE_NO_ADDITIONAL_SAS, + ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType); + } + + @Test + public void testTriggerRemoteRekeyChild() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + CMD_RECEIVE_IKE_PACKET, + makeRekeyChildCreateMessage(false /*isResp*/, CHILD_SPI_REMOTE)); + mLooper.dispatchAll(); + + verify(mMockChildSessionStateMachine) + .receiveRequest( + eq(IKE_EXCHANGE_SUBTYPE_REKEY_CHILD), + eq(EXCHANGE_TYPE_CREATE_CHILD_SA), + any(List.class)); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + } + + @Test + public void testHandleRekeyChildReqWithUnrecognizedSpi() throws Exception { + int unrecognizedSpi = 2; + + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + CMD_RECEIVE_IKE_PACKET, + makeRekeyChildCreateMessage(false /*isResp*/, unrecognizedSpi)); + mLooper.dispatchAll(); + + verify(mMockChildSessionStateMachine, never()).receiveRequest(anyInt(), anyInt(), any()); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + + List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, ikePayloadList.size()); + IkeNotifyPayload notifyPayload = (IkeNotifyPayload) ikePayloadList.get(0); + assertEquals(ERROR_TYPE_CHILD_SA_NOT_FOUND, notifyPayload.notifyType); + assertEquals(unrecognizedSpi, notifyPayload.spi); + } + + private void verifyNotifyUserCloseSession() { + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verify(mMockIkeSessionCallback).onClosed(); + } + + @Test + public void testRcvRemoteDeleteIkeWhenChildProcedureOngoing() throws Exception { + setupIdleStateMachine(); + transitionToChildProcedureOngoing(); + + mIkeSessionStateMachine.sendMessage( + CMD_RECEIVE_IKE_PACKET, makeDeleteIkeRequest(mSpyCurrentIkeSaRecord)); + + mLooper.dispatchAll(); + + verifyNotifyUserCloseSession(); + + // Verify state machine quit properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + + List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertTrue(ikePayloadList.isEmpty()); + } + + @Test + public void testRcvRemoteRekeyIkeWhenChildProcedureOngoing() throws Exception { + setupIdleStateMachine(); + transitionToChildProcedureOngoing(); + + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, makeRekeyIkeRequest()); + + mLooper.dispatchAll(); + + // Since we have forced state machine to transition to ChildProcedureOngoing state without + // really starting any Child procedure, it should transition to Idle at this time. + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + + List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, ikePayloadList.size()); + assertEquals( + ERROR_TYPE_TEMPORARY_FAILURE, + ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType); + } + + @Test + public void testKillChildSessions() throws Exception { + setupIdleStateMachine(); + + ChildSessionStateMachine childOne = mock(ChildSessionStateMachine.class); + ChildSessionStateMachine childTwo = mock(ChildSessionStateMachine.class); + registerChildStateMachine(mock(ChildSessionCallback.class), childOne); + registerChildStateMachine(mock(ChildSessionCallback.class), childTwo); + + mIkeSessionStateMachine.mCurrentIkeSaRecord = null; + + mIkeSessionStateMachine.quitNow(); + + mLooper.dispatchAll(); + + verify(childOne).killSession(); + verify(childTwo).killSession(); + } + + private IkeMessage verifyAuthReqAndGetMsg() { + IkeMessage ikeAuthReqMessage = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + IkeHeader ikeHeader = ikeAuthReqMessage.ikeHeader; + assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType); + assertFalse(ikeHeader.isResponseMsg); + assertTrue(ikeHeader.fromIkeInitiator); + + return ikeAuthReqMessage; + } + + private IkeMessage verifyAuthReqWithChildPayloadsAndGetMsg() { + IkeMessage ikeAuthReqMessage = verifyAuthReqAndGetMsg(); + + assertNotNull( + ikeAuthReqMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_ID_INITIATOR, IkeIdPayload.class)); + assertNotNull( + ikeAuthReqMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_ID_RESPONDER, IkeIdPayload.class)); + assertNotNull( + ikeAuthReqMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class)); + assertNotNull( + ikeAuthReqMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_TS_INITIATOR, IkeTsPayload.class)); + assertNotNull( + ikeAuthReqMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_TS_RESPONDER, IkeTsPayload.class)); + + return ikeAuthReqMessage; + } + + private void verifySharedKeyAuthentication( + IkeAuthPskPayload spyAuthPayload, + IkeIdPayload respIdPayload, + List<IkePayload> authRelatedPayloads, + boolean hasChildPayloads) + throws Exception { + // Send IKE AUTH response to IKE state machine + ReceivedIkePacket authResp = makeIkeAuthRespWithChildPayloads(authRelatedPayloads); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, authResp); + mLooper.dispatchAll(); + + // Validate outbound IKE AUTH request + IkeMessage ikeAuthReqMessage; + if (hasChildPayloads) { + ikeAuthReqMessage = verifyAuthReqWithChildPayloadsAndGetMsg(); + } else { + ikeAuthReqMessage = verifyAuthReqAndGetMsg(); + } + assertNotNull( + ikeAuthReqMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_AUTH, IkeAuthPskPayload.class)); + + // Validate inbound IKE AUTH response + verifyIncrementLocaReqMsgId(); + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, authResp); + + // Validate authentication is done. Cannot use matchers because IkeAuthPskPayload is final. + verify(spyAuthPayload) + .verifyInboundSignature( + mPsk, + mIkeSessionStateMachine.mIkeInitRequestBytes, + mSpyCurrentIkeSaRecord.nonceInitiator, + respIdPayload.getEncodedPayloadBody(), + mIkeSessionStateMachine.mIkePrf, + mSpyCurrentIkeSaRecord.getSkPr()); + + // Validate that user has been notified + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verify(mMockIkeSessionCallback).onOpened(any()); + // TODO: Verify sessionConfiguration + + // Verify payload list pair for first Child negotiation + ArgumentCaptor<List<IkePayload>> mReqPayloadListCaptor = + ArgumentCaptor.forClass(List.class); + ArgumentCaptor<List<IkePayload>> mRespPayloadListCaptor = + ArgumentCaptor.forClass(List.class); + verify(mMockChildSessionStateMachine) + .handleFirstChildExchange( + mReqPayloadListCaptor.capture(), + mRespPayloadListCaptor.capture(), + eq(LOCAL_ADDRESS), + eq(REMOTE_ADDRESS), + any(), // udpEncapSocket + eq(mIkeSessionStateMachine.mIkePrf), + any()); // sk_d + List<IkePayload> childReqList = mReqPayloadListCaptor.getValue(); + List<IkePayload> childRespList = mRespPayloadListCaptor.getValue(); + + assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_SA)); + assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR)); + assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER)); + assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_NONCE)); + IkeSaPayload reqSaPayload = + IkePayload.getPayloadForTypeInProvidedList( + IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class, childReqList); + assertFalse(reqSaPayload.isSaResponse); + + assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_SA)); + assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR)); + assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER)); + assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_NONCE)); + IkeSaPayload respSaPayload = + IkePayload.getPayloadForTypeInProvidedList( + IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class, childRespList); + assertTrue(respSaPayload.isSaResponse); + + // Mock finishing first Child SA negotiation. + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + + verify(mMockChildSessionFactoryHelper) + .makeChildSessionStateMachine( + eq(mLooper.getLooper()), + eq(mContext), + eq(mChildSessionOptions), + eq(mSpyUserCbExecutor), + eq(mMockChildSessionCallback), + mChildSessionSmCbCaptor.capture()); + IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue(); + + cb.onProcedureFinished(mMockChildSessionStateMachine); + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + private IkeAuthPskPayload makeSpyRespPskPayload() throws Exception { + IkeAuthPskPayload spyAuthPayload = + spy( + (IkeAuthPskPayload) + IkeTestUtils.hexStringToIkePayload( + IkePayload.PAYLOAD_TYPE_AUTH, + true /*isResp*/, + PSK_AUTH_RESP_PAYLOAD_HEX_STRING)); + + doNothing() + .when(spyAuthPayload) + .verifyInboundSignature(any(), any(), any(), any(), any(), any()); + return spyAuthPayload; + } + + private IkeAuthDigitalSignPayload makeSpyDigitalSignAuthPayload() throws Exception { + IkeAuthDigitalSignPayload spyAuthPayload = + spy( + (IkeAuthDigitalSignPayload) + IkeTestUtils.hexStringToIkePayload( + IkePayload.PAYLOAD_TYPE_AUTH, + true /*isResp*/, + GENERIC_DIGITAL_SIGN_AUTH_RESP_HEX_STRING)); + doNothing() + .when(spyAuthPayload) + .verifyInboundSignature(any(), any(), any(), any(), any(), any()); + return spyAuthPayload; + } + + private IkeIdPayload makeRespIdPayload() throws Exception { + return (IkeIdPayload) + IkeTestUtils.hexStringToIkePayload( + IkePayload.PAYLOAD_TYPE_ID_RESPONDER, + true /*isResp*/, + ID_PAYLOAD_RESPONDER_HEX_STRING); + } + + @Test + public void testCreateIkeLocalIkeAuthPsk() throws Exception { + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + + // Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload. + List<IkePayload> authRelatedPayloads = new LinkedList<>(); + IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload(); + authRelatedPayloads.add(spyAuthPayload); + + IkeIdPayload respIdPayload = makeRespIdPayload(); + authRelatedPayloads.add(respIdPayload); + + verifySharedKeyAuthentication(spyAuthPayload, respIdPayload, authRelatedPayloads, true); + verifyRetransmissionStopped(); + } + + @Test + public void testCreateIkeLocalIkeAuthPskVerifyFail() throws Exception { + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + resetMockIkeMessageHelper(); + + // Build IKE AUTH response with invalid Auth-PSK Payload and ID-Responder Payload. + List<IkePayload> authRelatedPayloads = new LinkedList<>(); + IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload(); + doThrow(new AuthenticationFailedException("DummyAuthFailException")) + .when(spyAuthPayload) + .verifyInboundSignature(any(), any(), any(), any(), any(), any()); + authRelatedPayloads.add(spyAuthPayload); + + IkeIdPayload respIdPayload = makeRespIdPayload(); + authRelatedPayloads.add(respIdPayload); + + // Send response to IKE state machine + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeIkeAuthRespWithChildPayloads(authRelatedPayloads)); + mLooper.dispatchAll(); + + // Verify Delete request was sent + List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/); + assertEquals(1, payloads.size()); + assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType); + + // Verify IKE Session was closed properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback) + .onClosedExceptionally(any(AuthenticationFailedException.class)); + } + + @Test + public void testAuthPskHandleRespWithParsingError() throws Exception { + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + resetMockIkeMessageHelper(); + + // Mock receiving packet with syntax error + ReceivedIkePacket mockInvalidPacket = + makeDummyReceivedIkePacketWithInvalidSyntax( + mSpyCurrentIkeSaRecord, true /*isResp*/, IkeHeader.EXCHANGE_TYPE_IKE_AUTH); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket); + mLooper.dispatchAll(); + + // Verify Delete request was sent + List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/); + assertEquals(1, payloads.size()); + assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType); + + // Verify IKE Session is closed properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + } + + @Test + public void testCreateIkeLocalIkeAuthPreEap() throws Exception { + mIkeSessionStateMachine.quitNow(); + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap()); + + // Mock IKE INIT + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + + // Build IKE AUTH response with EAP. Auth, ID-Resp and Cert payloads. + List<IkePayload> authRelatedPayloads = new LinkedList<>(); + + authRelatedPayloads.add(new IkeEapPayload(EAP_DUMMY_MSG)); + authRelatedPayloads.add(makeSpyDigitalSignAuthPayload()); + authRelatedPayloads.add(makeRespIdPayload()); + + IkeCertX509CertPayload certPayload = new IkeCertX509CertPayload(mServerEndCertificate); + authRelatedPayloads.add(certPayload); + + // Send IKE AUTH response to IKE state machine + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeIkeAuthRespWithoutChildPayloads(authRelatedPayloads)); + mLooper.dispatchAll(); + + // Validate outbound IKE AUTH request + IkeMessage ikeAuthReqMessage = verifyAuthReqWithChildPayloadsAndGetMsg(); + assertNull( + ikeAuthReqMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_AUTH, IkeAuthPayload.class)); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuthInEap); + verifyRetransmissionStopped(); + assertNotNull(mIkeSessionStateMachine.mInitIdPayload); + assertNotNull(mIkeSessionStateMachine.mRespIdPayload); + } + + private IEapCallback verifyEapAuthenticatorCreatedAndGetCallback() { + ArgumentCaptor<IEapCallback> captor = ArgumentCaptor.forClass(IEapCallback.class); + + verify(mMockEapAuthenticatorFactory) + .newEapAuthenticator( + eq(mIkeSessionStateMachine.getHandler().getLooper()), + captor.capture(), + eq(mContext), + eq(mEapSessionConfig)); + + return captor.getValue(); + } + + @Test + public void testCreateIkeLocalIkeAuthInEapStartsAuthenticatorAndProxiesMessage() + throws Exception { + mIkeSessionStateMachine.quitNow(); + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap()); + + // Setup state and go to IN_EAP state + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap); + mLooper.dispatchAll(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EAP_START_EAP_AUTH, new IkeEapPayload(EAP_DUMMY_MSG)); + mLooper.dispatchAll(); + + verifyEapAuthenticatorCreatedAndGetCallback(); + + verify(mMockEapAuthenticator).processEapMessage(eq(EAP_DUMMY_MSG)); + } + + @Test + public void testCreateIkeLocalIkeAuthInEapHandlesOutboundResponse() throws Exception { + mIkeSessionStateMachine.quitNow(); + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap()); + + // Setup state and go to IN_EAP state + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap); + mLooper.dispatchAll(); + + IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(); + callback.onResponse(EAP_DUMMY_MSG); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + // Verify EAP response + IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + IkeHeader ikeHeader = resp.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType); + assertFalse(ikeHeader.isResponseMsg); + assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator); + + assertEquals(1, resp.ikePayloadList.size()); + assertArrayEquals(EAP_DUMMY_MSG, ((IkeEapPayload) resp.ikePayloadList.get(0)).eapMessage); + } + + @Test + public void testCreateIkeLocalIkeAuthInEapHandlesMissingEapPacket() throws Exception { + mIkeSessionStateMachine.quitNow(); + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap()); + + // Setup state and go to IN_EAP state + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap); + mLooper.dispatchAll(); + + // Mock sending IKE_AUTH{EAP} request + IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(); + callback.onResponse(EAP_DUMMY_MSG); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + // Send IKE AUTH response with no EAP Payload to IKE state machine + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeIkeAuthRespWithoutChildPayloads(new LinkedList<>())); + mLooper.dispatchAll(); + + // Verify state machine quit properly + verify(mMockIkeSessionCallback) + .onClosedExceptionally(any(AuthenticationFailedException.class)); + assertNull(mIkeSessionStateMachine.getCurrentState()); + } + + @Test + public void testCreateIkeLocalIkeAuthInEapHandlesSuccess() throws Exception { + mIkeSessionStateMachine.quitNow(); + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap()); + + // Setup state and go to IN_EAP state + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap); + mLooper.dispatchAll(); + + IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(); + + // Setup dummy initIdPayload for next state. + mIkeSessionStateMachine.mInitIdPayload = mock(IkeIdPayload.class); + when(mIkeSessionStateMachine.mInitIdPayload.getEncodedPayloadBody()) + .thenReturn(new byte[0]); + + callback.onSuccess(mPsk, new byte[0]); // use mPsk as MSK, eMSK does not matter + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuthPostEap); + } + + @Test + public void testCreateIkeLocalIkeAuthInEapHandlesError() throws Exception { + mIkeSessionStateMachine.quitNow(); + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap()); + + // Setup state and go to IN_EAP state + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap); + mLooper.dispatchAll(); + + IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(); + + Throwable error = new IllegalArgumentException(); + callback.onError(error); + mLooper.dispatchAll(); + + // Fires user error callbacks + verify(mMockIkeSessionCallback) + .onClosedExceptionally(argThat(err -> err.getCause() == error)); + + // Verify state machine quit properly + verify(mSpyCurrentIkeSaRecord).close(); + assertNull(mIkeSessionStateMachine.getCurrentState()); + } + + @Test + public void testCreateIkeLocalIkeAuthInEapHandlesFailure() throws Exception { + mIkeSessionStateMachine.quitNow(); + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap()); + + // Setup state and go to IN_EAP state + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap); + mLooper.dispatchAll(); + + IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(); + callback.onFail(); + mLooper.dispatchAll(); + + // Fires user error callbacks + verify(mMockIkeSessionCallback) + .onClosedExceptionally(any(AuthenticationFailedException.class)); + + // Verify state machine quit properly + verify(mSpyCurrentIkeSaRecord).close(); + assertNull(mIkeSessionStateMachine.getCurrentState()); + } + + @Test + public void testCreateIkeLocalIkeAuthPostEap() throws Exception { + mIkeSessionStateMachine.quitNow(); + reset(mMockChildSessionFactoryHelper); + setupChildStateMachineFactory(mMockChildSessionStateMachine); + mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap()); + + // Setup dummy state from IkeAuthPreEap for next state. + mIkeSessionStateMachine.mInitIdPayload = mock(IkeIdPayload.class); + when(mIkeSessionStateMachine.mInitIdPayload.getEncodedPayloadBody()) + .thenReturn(new byte[0]); + mIkeSessionStateMachine.mRespIdPayload = + (IkeIdPayload) + IkeTestUtils.hexStringToIkePayload( + IkePayload.PAYLOAD_TYPE_ID_RESPONDER, + true /*isResp*/, + ID_PAYLOAD_RESPONDER_HEX_STRING); + + List<Integer> payloadTypeList = new LinkedList<>(); + List<String> payloadHexStringList = new LinkedList<>(); + + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR); + payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER); + + payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING); + payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING); + payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING); + + mIkeSessionStateMachine.mFirstChildReqList = + hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, false /*isResp*/); + + // Setup state and go to IN_EAP state + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthPostEap); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_EAP_FINISH_EAP_AUTH, mPsk); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + // Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload. + List<IkePayload> authRelatedPayloads = new LinkedList<>(); + IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload(); + authRelatedPayloads.add(spyAuthPayload); + + IkeIdPayload respIdPayload = makeRespIdPayload(); + + verifySharedKeyAuthentication(spyAuthPayload, respIdPayload, authRelatedPayloads, false); + verifyRetransmissionStopped(); + } + + @Test + public void testCreateIkeLocalIkeAuthHandlesFirstFrag() throws Exception { + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + + // Received IKE fragment + byte[] unencryptedData = "testCreateIkeLocalIkeAuthHandlesFrag".getBytes(); + int fragNum = 1; + int totalFragments = 2; + IkeSkfPayload skfPayload = + IkeTestUtils.makeDummySkfPayload(unencryptedData, fragNum, totalFragments); + + ReceivedIkePacket packet = + makeDummyReceivedIkeFragmentPacket( + mSpyCurrentIkeSaRecord, + true /*isResp*/, + IkeHeader.EXCHANGE_TYPE_IKE_AUTH, + skfPayload, + PAYLOAD_TYPE_AUTH, + null /* collectedFrags*/); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet); + mLooper.dispatchAll(); + + // Verify state doesn't change + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth); + + // Verify the IkeSaRecord has stored the fragment. + DecodeResultPartial resultPartial = + mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/); + assertEquals(PAYLOAD_TYPE_AUTH, resultPartial.firstPayloadType); + assertEquals(totalFragments, resultPartial.collectedFragsList.length); + assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[fragNum - 1]); + assertFalse(resultPartial.isAllFragmentsReceived()); + + assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(false /*isResp*/)); + } + + @Test + public void testCreateIkeLocalIkeAuthHandlesLastFragOk() throws Exception { + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + + // Set previously collected IKE fragments + DecodeResultPartial mockCollectedFrags = mock(DecodeResultPartial.class); + mSpyCurrentIkeSaRecord.updateCollectedFragments(mockCollectedFrags, true /*isResp*/); + + // Build reassembled IKE AUTH response with Auth-PSK Payload and ID-Responder Payload. + List<IkePayload> authRelatedPayloads = new LinkedList<>(); + IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload(); + authRelatedPayloads.add(spyAuthPayload); + + IkeIdPayload respIdPayload = makeRespIdPayload(); + authRelatedPayloads.add(respIdPayload); + + List<IkePayload> authPayloadList = + getIkeAuthPayloadListWithChildPayloads(authRelatedPayloads); + + // Receive last auth response and do IKE_AUTH + ReceivedIkePacket packet = + makeDummyReceivedLastIkeFragmentPacketOk( + mSpyCurrentIkeSaRecord, + true /*isResp*/, + IkeHeader.EXCHANGE_TYPE_IKE_AUTH, + mockCollectedFrags, + authPayloadList, + "FirstFrag".getBytes()); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet); + mLooper.dispatchAll(); + + // Verify IKE AUTH is done + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + + // Verify collected response fragments are cleared. + assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/)); + verify(mSpyCurrentIkeSaRecord).resetCollectedFragments(true /*isResp*/); + } + + @Test + public void testCreateIkeLocalIkeAuthHandlesLastFragError() throws Exception { + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + resetMockIkeMessageHelper(); + + // Set previously collected IKE fragments + DecodeResultPartial mockCollectedFrags = mock(DecodeResultPartial.class); + mSpyCurrentIkeSaRecord.updateCollectedFragments(mockCollectedFrags, true /*isResp*/); + + // Receive last auth response with syntax error + ReceivedIkePacket packet = + makeDummyReceivedLastIkeFragmentPacketError( + mSpyCurrentIkeSaRecord, + true /*isResp*/, + IkeHeader.EXCHANGE_TYPE_IKE_AUTH, + mockCollectedFrags, + new InvalidSyntaxException("IkeStateMachineTest")); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet); + mLooper.dispatchAll(); + + // Verify Delete request is sent + List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/); + assertEquals(1, payloads.size()); + assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType); + + // Verify IKE Session is closed properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + + // Collected response fragments are cleared + assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/)); + verify(mSpyCurrentIkeSaRecord).resetCollectedFragments(true /*isResp*/); + } + + @Test + public void testRekeyIkeLocalCreateSendsRequest() throws Exception { + setupIdleStateMachine(); + + // Send Rekey-Create request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalCreate); + verifyRetransmissionStarted(); + + // Verify outbound message + IkeMessage rekeyMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + IkeHeader ikeHeader = rekeyMsg.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, ikeHeader.exchangeType); + assertEquals(mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), ikeHeader.messageId); + assertFalse(ikeHeader.isResponseMsg); + assertTrue(ikeHeader.fromIkeInitiator); + + // Verify SA payload & proposals + IkeSaPayload saPayload = + rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class); + assertFalse(saPayload.isSaResponse); + assertEquals(1, saPayload.proposalList.size()); + + IkeSaPayload.IkeProposal proposal = + (IkeSaPayload.IkeProposal) saPayload.proposalList.get(0); + assertEquals(1, proposal.number); // Must be 1-indexed + assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId); + assertEquals(IkePayload.SPI_LEN_IKE, proposal.spiSize); + assertEquals(mIkeSessionStateMachine.mSaProposal, proposal.saProposal); + + // Verify Nonce and KE payloads exist. + assertNotNull( + rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class)); + + IkeKePayload kePayload = + rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class); + assertNotNull(kePayload); + assertTrue(kePayload.isOutbound); + } + + @Test + public void testRekeyIkeLocalCreateHandlesResponse() throws Exception { + setupIdleStateMachine(); + + // Send Rekey-Create request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + // Prepare "rekeyed" SA + setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord); + + // Receive Rekey response + ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket); + mLooper.dispatchAll(); + verifyIncrementLocaReqMsgId(); + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRespReceivedPacket); + + // Verify in delete state, and new SA record was saved: + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalDelete); + verifyRetransmissionStarted(); + assertEquals(mSpyLocalInitIkeSaRecord, mIkeSessionStateMachine.mLocalInitNewIkeSaRecord); + verify(mSpyIkeSocket) + .registerIke( + eq(mSpyLocalInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine)); + } + + @Test + public void testRekeyIkeLocalCreateHandleRespWithParsingError() throws Exception { + setupIdleStateMachine(); + + // Send Rekey-Create request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + resetMockIkeMessageHelper(); + + // Mock receiving packet with syntax error + ReceivedIkePacket mockInvalidPacket = + makeDummyReceivedIkePacketWithInvalidSyntax( + mSpyCurrentIkeSaRecord, + true /*isResp*/, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket); + mLooper.dispatchAll(); + + // Verify Delete request was sent + List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/); + assertEquals(1, payloads.size()); + assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType); + + // Verify IKE Session is closed properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + } + + @Test + public void testRekeyIkeLocalCreateHandleRespWithNonFatalErrorNotify() throws Exception { + setupIdleStateMachine(); + + // Send Rekey-Create request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + + // Mock receiving packet with NO_PROPOSAL_CHOSEN + ReceivedIkePacket resp = + makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_NO_PROPOSAL_CHOSEN)); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp); + mLooper.dispatchAll(); + + // Verify IKE Session goes back to Idle + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + + // Move time forward to trigger retry + mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalCreate); + } + + @Test + public void testRekeyIkeLocalCreateHandleRespWithFatalErrorNotify() throws Exception { + setupIdleStateMachine(); + + // Send Rekey-Create request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + resetMockIkeMessageHelper(); + + // Mock receiving packet with NO_PROPOSAL_CHOSEN + ReceivedIkePacket resp = + makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_INVALID_SYNTAX)); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp); + mLooper.dispatchAll(); + + // Verify no message was sent because a fatal error notification was received + verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord); + + // Verify IKE Session is closed properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + } + + @Test + public void testRekeyIkeLocalCreateSaCreationFail() throws Exception { + // Throw error when building new IKE SA + throwExceptionWhenMakeRekeyIkeSa( + new GeneralSecurityException("testRekeyIkeLocalCreateSaCreationFail")); + + setupIdleStateMachine(); + + // Send Rekey-Create request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + resetMockIkeMessageHelper(); + + // Receive Rekey response + ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket); + mLooper.dispatchAll(); + + // Verify Delete request was sent + List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/); + assertEquals(1, payloads.size()); + assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType); + + // Verify IKE Session is closed properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class)); + } + + @Test + public void testRekeyIkeLocalCreateHandleReqWithNonFatalError() throws Exception { + setupIdleStateMachine(); + + // Send Rekey-Create request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + resetMockIkeMessageHelper(); + + // Build protocol exception + List<Integer> unsupportedPayloads = new LinkedList<>(); + unsupportedPayloads.add(PAYLOAD_TYPE_UNSUPPORTED); + UnsupportedCriticalPayloadException exception = + new UnsupportedCriticalPayloadException(unsupportedPayloads); + + // Mock receiving packet with unsupported critical payload + ReceivedIkePacket mockInvalidPacket = + makeDummyReceivedIkePacketWithDecodingError( + mSpyCurrentIkeSaRecord, + false /*isResp*/, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, + exception); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket); + mLooper.dispatchAll(); + + // Verify error notification was sent + List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, payloads.size()); + + IkePayload payload = payloads.get(0); + assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType); + assertEquals( + ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD, ((IkeNotifyPayload) payload).notifyType); + + // Verify IKE Session stays in the same state + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalCreate); + } + + private void mockCreateAndTransitionToRekeyDeleteLocal() { + // Seed fake rekey data and force transition to RekeyIkeLocalDelete + mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord; + mIkeSessionStateMachine.addIkeSaRecord(mSpyLocalInitIkeSaRecord); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mRekeyIkeLocalDelete); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + } + + @Test + public void testRekeyIkeLocalDeleteSendsRequest() throws Exception { + setupIdleStateMachine(); + mockCreateAndTransitionToRekeyDeleteLocal(); + + // Verify Rekey-Delete request + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalDelete); + verifyRetransmissionStarted(); + + // Verify outbound message + IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + IkeHeader ikeHeader = delMsg.ikeHeader; + assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), ikeHeader.ikeInitiatorSpi); + assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), ikeHeader.ikeResponderSpi); + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType); + assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator); + assertFalse(ikeHeader.isResponseMsg); + + List<IkeDeletePayload> deletePayloadList = + delMsg.getPayloadListForType( + IkePayload.PAYLOAD_TYPE_DELETE, IkeDeletePayload.class); + assertEquals(1, deletePayloadList.size()); + + IkeDeletePayload deletePayload = deletePayloadList.get(0); + assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId); + assertEquals(0, deletePayload.numSpi); + assertEquals(0, deletePayload.spiSize); + assertArrayEquals(new int[0], deletePayload.spisToDelete); + } + + private void verifyRekeyReplaceSa(IkeSaRecord newSaRecord) { + verify(mSpyCurrentIkeSaRecord).close(); + verify(mSpyIkeSocket).unregisterIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi())); + verify(mSpyIkeSocket, never()).unregisterIke(eq(newSaRecord.getLocalSpi())); + + assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, newSaRecord); + + verify(mMockChildSessionStateMachine).setSkD(newSaRecord.getSkD()); + } + + @Test + public void testRekeyIkeLocalDeleteHandlesResponse() throws Exception { + setupIdleStateMachine(); + mockCreateAndTransitionToRekeyDeleteLocal(); + + // Receive Delete response + ReceivedIkePacket dummyDeleteIkeRespReceivedPacket = + makeDeleteIkeResponse(mSpyCurrentIkeSaRecord); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRespReceivedPacket); + mLooper.dispatchAll(); + verifyIncrementLocaReqMsgId(); + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRespReceivedPacket); + + // Verify final state - Idle, with new SA, and old SA closed. + verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord); + verify(mMockIkeSessionCallback, never()).onClosed(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + verifyRetransmissionStopped(); + } + + @Test + public void testRekeyIkeLocalDeleteHandlesRespWithParsingError() throws Exception { + setupIdleStateMachine(); + mockCreateAndTransitionToRekeyDeleteLocal(); + resetMockIkeMessageHelper(); + + // Mock receiving packet with syntax error + ReceivedIkePacket mockInvalidPacket = + makeDummyReceivedIkePacketWithInvalidSyntax( + mSpyCurrentIkeSaRecord, + true /*isResp*/, + IkeHeader.EXCHANGE_TYPE_INFORMATIONAL); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket); + mLooper.dispatchAll(); + + // Verify no more request out + verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord); + + // Verify final state - Idle, with new SA, and old SA closed. + verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + verifyRetransmissionStopped(); + } + + @Test + public void testRekeyIkeLocalDeleteWithRequestOnNewSa() throws Exception { + setupIdleStateMachine(); + mockCreateAndTransitionToRekeyDeleteLocal(); + + // Receive an empty (DPD) request on the new IKE SA + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDpdIkeRequest(mSpyLocalInitIkeSaRecord)); + mLooper.dispatchAll(); + + // Verify final state - Idle, with new SA, and old SA closed. + verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + verifyRetransmissionStopped(); + } + + @Test + public void testRekeyIkeLocalDeleteWithRequestFragOnNewSa() throws Exception { + setupIdleStateMachine(); + mockCreateAndTransitionToRekeyDeleteLocal(); + + // Received IKE fragment + byte[] unencryptedData = "testRekeyIkeLocalDeleteWithRequestFragOnNewSa".getBytes(); + int fragNum = 1; + int totalFragments = 2; + IkeSkfPayload skfPayload = + IkeTestUtils.makeDummySkfPayload(unencryptedData, fragNum, totalFragments); + + ReceivedIkePacket packet = + makeDummyReceivedIkeFragmentPacket( + mSpyLocalInitIkeSaRecord, + false /*isResp*/, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, + skfPayload, + PAYLOAD_TYPE_SA, + null /* collectedFrags*/); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet); + mLooper.dispatchAll(); + + // Verify rekey is done. + verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord); + verifyRetransmissionStopped(); + + // Verify the IkeSaRecord has stored the new fragment. + DecodeResultPartial resultPartial = + mSpyLocalInitIkeSaRecord.getCollectedFragments(false /*isResp*/); + assertEquals(PAYLOAD_TYPE_SA, resultPartial.firstPayloadType); + assertEquals(totalFragments, resultPartial.collectedFragsList.length); + assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[fragNum - 1]); + assertFalse(resultPartial.isAllFragmentsReceived()); + } + + @Test + public void testRekeyIkeRemoteDeleteWithRequestOnNewSa() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyIkeRemoteDelete + mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord; + mIkeSessionStateMachine.addIkeSaRecord(mSpyRemoteInitIkeSaRecord); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mRekeyIkeRemoteDelete); + mLooper.dispatchAll(); + + // Receive an empty (DPD) request on the new IKE SA + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDpdIkeRequest(mSpyRemoteInitIkeSaRecord)); + mLooper.dispatchAll(); + + // Verify final state - Idle, with new SA, and old SA closed. + verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + @Test + public void testRekeyIkeRemoteCreate() throws Exception { + setupIdleStateMachine(); + + setupRekeyedIkeSa(mSpyRemoteInitIkeSaRecord); + + // Receive Rekey request + ReceivedIkePacket dummyRekeyIkeRequestReceivedPacket = makeRekeyIkeRequest(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRequestReceivedPacket); + mLooper.dispatchAll(); + verifyIncrementRemoteReqMsgId(); + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRequestReceivedPacket); + + // Verify SA created with correct parameters + ArgumentCaptor<SaRecord.IkeSaRecordConfig> recordConfigCaptor = + ArgumentCaptor.forClass(SaRecord.IkeSaRecordConfig.class); + verify(mMockSaRecordHelper) + .makeRekeyedIkeSaRecord(any(), any(), any(), any(), recordConfigCaptor.capture()); + assertEquals(IKE_REKEY_SA_INITIATOR_SPI, recordConfigCaptor.getValue().initSpi.getSpi()); + + // Verify outbound CREATE_CHILD_SA message + IkeMessage rekeyCreateResp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + IkeHeader rekeyCreateRespHeader = rekeyCreateResp.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, rekeyCreateRespHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyCreateRespHeader.exchangeType); + assertTrue(rekeyCreateRespHeader.isResponseMsg); + assertTrue(rekeyCreateRespHeader.fromIkeInitiator); + assertNotNull( + rekeyCreateResp.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class)); + assertNotNull( + rekeyCreateResp.getPayloadForType(IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class)); + assertNotNull( + rekeyCreateResp.getPayloadForType( + IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class)); + + // Verify SA, StateMachine state + assertEquals(mSpyCurrentIkeSaRecord, mIkeSessionStateMachine.mIkeSaRecordAwaitingRemoteDel); + assertEquals(mSpyRemoteInitIkeSaRecord, mIkeSessionStateMachine.mIkeSaRecordSurviving); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete); + verify(mSpyIkeSocket) + .registerIke( + eq(mSpyRemoteInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine)); + } + + @Test + public void testRekeyIkeRemoteCreateHandlesInvalidReq() throws Exception { + setupIdleStateMachine(); + + // Receive Rekey request + ReceivedIkePacket request = makeRekeyIkeRequestWithUnacceptableProposal(); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request); + mLooper.dispatchAll(); + + verifyProcessRekeyReqFailure(ERROR_TYPE_NO_PROPOSAL_CHOSEN); + } + + @Test + public void testRekeyIkeRemoteCreateSaCreationFailure() throws Exception { + // Throw error when building new IKE SA + throwExceptionWhenMakeRekeyIkeSa( + new GeneralSecurityException("testRekeyIkeRemoteCreateSaCreationFailure")); + setupIdleStateMachine(); + + // Receive Rekey request + ReceivedIkePacket request = makeRekeyIkeRequest(); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request); + mLooper.dispatchAll(); + + verifyProcessRekeyReqFailure(ERROR_TYPE_NO_PROPOSAL_CHOSEN); + } + + private void verifyProcessRekeyReqFailure(int expectedErrorCode) { + // Verify IKE Session is back to Idle + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + + // Verify error notification was sent + List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, payloads.size()); + IkeNotifyPayload notify = (IkeNotifyPayload) payloads.get(0); + assertEquals(expectedErrorCode, notify.notifyType); + } + + @Test + public void testRekeyIkeRemoteDelete() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyIkeLocalDelete + mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mRekeyIkeRemoteDelete); + mLooper.dispatchAll(); + + // Rekey Delete request + ReceivedIkePacket dummyDeleteIkeRequestReceivedPacket = + makeDeleteIkeRequest(mSpyCurrentIkeSaRecord); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRequestReceivedPacket); + mLooper.dispatchAll(); + verifyIncrementRemoteReqMsgId(); + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket); + + // Verify outbound DELETE_IKE_SA message + IkeMessage rekeyDeleteResp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + IkeHeader rekeyDeleteRespHeader = rekeyDeleteResp.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, rekeyDeleteRespHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, rekeyDeleteRespHeader.exchangeType); + assertTrue(rekeyDeleteRespHeader.isResponseMsg); + assertTrue(rekeyDeleteRespHeader.fromIkeInitiator); + assertTrue(rekeyDeleteResp.ikePayloadList.isEmpty()); + + // Verify final state - Idle, with new SA, and old SA closed. + verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord); + + verify(mMockIkeSessionCallback, never()).onClosed(); + + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + @Test + public void testRekeyIkeRemoteDeleteExitAndRenter() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyIkeLocalDelete + mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mRekeyIkeRemoteDelete); + mLooper.dispatchAll(); + + // Trigger a timeout, and immediately re-enter remote-delete + mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS / 2 + 1); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.TIMEOUT_REKEY_REMOTE_DELETE); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mRekeyIkeRemoteDelete); + mLooper.dispatchAll(); + + // Shift time forward, and assert the previous timeout was NOT fired. + mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS / 2 + 1); + mLooper.dispatchAll(); + + // Verify no request received, or response sent. + verify(mMockIkeMessageHelper, never()).decode(anyInt(), anyObject(), anyObject()); + verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord); + + // Verify final state has not changed - signal was not sent. + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete); + } + + @Test + public void testRekeyIkeRemoteDeleteTimedOut() throws Exception { + setupIdleStateMachine(); + + // Seed fake rekey data and force transition to RekeyIkeLocalDelete + mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mRekeyIkeRemoteDelete); + mLooper.dispatchAll(); + + mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS); + mLooper.dispatchAll(); + + // Verify no request received, or response sent. + verify(mMockIkeMessageHelper, never()).decode(anyInt(), anyObject(), anyObject()); + verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord); + + // Verify final state - Idle, with new SA, and old SA closed. + verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + @Test + public void testSimulRekey() throws Exception { + setupIdleStateMachine(); + + // Prepare "rekeyed" SA + setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord); + when(mSpyLocalInitIkeSaRecord.compareTo(mSpyRemoteInitIkeSaRecord)).thenReturn(1); + + // Send Rekey request on mSpyCurrentIkeSaRecord + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + + // Receive Rekey request on mSpyCurrentIkeSaRecord + ReceivedIkePacket dummyRekeyIkeRequestReceivedPacket = makeRekeyIkeRequest(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRequestReceivedPacket); + mLooper.dispatchAll(); + verifyIncrementRemoteReqMsgId(); + + // Receive Rekey response on mSpyCurrentIkeSaRecord + ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket); + mLooper.dispatchAll(); + verifyIncrementLocaReqMsgId(); + verify(mSpyIkeSocket) + .registerIke( + eq(mSpyLocalInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine)); + + // Receive Delete response on mSpyCurrentIkeSaRecord + ReceivedIkePacket dummyDeleteIkeRespReceivedPacket = + makeDeleteIkeResponse(mSpyCurrentIkeSaRecord); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRespReceivedPacket); + mLooper.dispatchAll(); + verifyIncrementLocaReqMsgId(); + + // Verify + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRequestReceivedPacket); + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRespReceivedPacket); + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRespReceivedPacket); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + + verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord); + verify(mMockIkeSessionCallback, never()).onClosed(); + } + + @Test + public void testOpenIkeSession() throws Exception { + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.Initial); + + mIkeSessionStateMachine.openSession(); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.CreateIkeLocalIkeInit); + } + + @Test + public void testIkeInitSchedulesRekey() throws Exception { + setupFirstIkeSa(); + + // Send IKE INIT request + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE); + + // Receive IKE INIT response + ReceivedIkePacket dummyReceivedIkePacket = makeIkeInitResponse(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket); + + // Mock IKE AUTH and transition to Idle + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle); + mLooper.dispatchAll(); + mIkeSessionStateMachine.mSaProposal = buildSaProposal(); + + // Move time forward to trigger rekey + mLooper.moveTimeForward(SA_SOFT_LIFETIME_MS); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalCreate); + } + + @Test + public void testRekeyCreateIkeSchedulesRekey() throws Exception { + setupIdleStateMachine(); + + // Send Rekey-Create request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + + // Prepare "rekeyed" SA + setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord); + + // Receive Rekey response + ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket); + mLooper.dispatchAll(); + + // Mock rekey delete and transition to Idle + mIkeSessionStateMachine.mCurrentIkeSaRecord = mSpyLocalInitIkeSaRecord; + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle); + mLooper.dispatchAll(); + + // Move time forward to trigger rekey + mLooper.moveTimeForward(SA_SOFT_LIFETIME_MS); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalCreate); + } + + @Test + public void testBuildEncryptedInformationalMessage() throws Exception { + IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_INVALID_SYNTAX, new byte[0]); + + boolean isResp = false; + IkeMessage generated = + mIkeSessionStateMachine.buildEncryptedInformationalMessage( + mSpyCurrentIkeSaRecord, new IkeInformationalPayload[] {payload}, isResp, 0); + + assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), generated.ikeHeader.ikeInitiatorSpi); + assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), generated.ikeHeader.ikeResponderSpi); + assertEquals( + mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), generated.ikeHeader.messageId); + assertEquals(isResp, generated.ikeHeader.isResponseMsg); + assertEquals(IkePayload.PAYLOAD_TYPE_SK, generated.ikeHeader.nextPayloadType); + + List<IkeNotifyPayload> generatedPayloads = + generated.getPayloadListForType( + IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class); + assertEquals(1, generatedPayloads.size()); + + IkeNotifyPayload generatedPayload = generatedPayloads.get(0); + assertArrayEquals(new byte[0], generatedPayload.notifyData); + assertEquals(ERROR_TYPE_INVALID_SYNTAX, generatedPayload.notifyType); + } + + private void verifyLastSentRespAllPackets(byte[][] expectedPackets, IkeSaRecord saRecord) { + if (expectedPackets == null) { + assertNull(saRecord.getLastSentRespAllPackets()); + return; + } + + assertEquals(expectedPackets.length, saRecord.getLastSentRespAllPackets().size()); + for (int i = 0; i < expectedPackets.length; i++) { + assertArrayEquals(expectedPackets[i], saRecord.getLastSentRespAllPackets().get(i)); + } + } + + @Test + public void testEncryptedRetransmitterImmediatelySendsRequest() throws Exception { + setupIdleStateMachine(); + byte[][] dummyLastRespBytes = + new byte[][] {"testRetransmitterSendsRequestLastResp".getBytes()}; + mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyLastRespBytes)); + + IkeMessage spyIkeReqMessage = + spy( + new IkeMessage( + new IkeHeader( + mSpyCurrentIkeSaRecord.getInitiatorSpi(), + mSpyCurrentIkeSaRecord.getResponderSpi(), + IkePayload.PAYLOAD_TYPE_SK, + EXCHANGE_TYPE_INFORMATIONAL, + false /*isResp*/, + mSpyCurrentIkeSaRecord.isLocalInit, + mSpyCurrentIkeSaRecord.getLocalRequestMessageId()), + new LinkedList<>())); + + // Use something unique as a sentinel value + byte[][] dummyReqBytesList = + new byte[][] { + "testRetransmitterSendsReqFrag1".getBytes(), + "testRetransmitterSendsReqFrag2".getBytes() + }; + + doReturn(dummyReqBytesList) + .when(spyIkeReqMessage) + .encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord), anyBoolean(), anyInt()); + + IkeSessionStateMachine.EncryptedRetransmitter retransmitter = + mIkeSessionStateMachine.new EncryptedRetransmitter(spyIkeReqMessage); + + // Verify message is sent out, and that request does not change cached retransmit-response + // mLastSentIkeResp. + verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytesList[0]), eq(REMOTE_ADDRESS)); + verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytesList[1]), eq(REMOTE_ADDRESS)); + verifyLastSentRespAllPackets(dummyLastRespBytes, mSpyCurrentIkeSaRecord); + } + + // TODO: b/141275871 Test retransmisstions are fired for correct times within certain time. + + @Test + public void testCacheLastRequestAndResponse() throws Exception { + setupIdleStateMachine(); + mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(null /*reqPacket*/); + mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(null /*respPacketList*/); + + byte[] dummyIkeReqFirstPacket = "testLastSentRequest".getBytes(); + byte[][] dummyIkeResp = + new byte[][] { + "testLastSentRespFrag1".getBytes(), "testLastSentRespFrag2".getBytes() + }; + + when(mMockIkeMessageHelper.encryptAndEncode( + any(), + any(), + eq(mSpyCurrentIkeSaRecord), + any(IkeMessage.class), + anyBoolean(), + anyInt())) + .thenReturn(dummyIkeResp); + + // Receive a DPD request, expect to send dummyIkeResp + ReceivedIkePacket dummyDpdRequest = + makeDpdIkeRequest( + mSpyCurrentIkeSaRecord.getRemoteRequestMessageId(), dummyIkeReqFirstPacket); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest); + mLooper.dispatchAll(); + + verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS)); + verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[1]), eq(REMOTE_ADDRESS)); + + verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord); + assertTrue(mSpyCurrentIkeSaRecord.isRetransmittedRequest(dummyIkeReqFirstPacket)); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + @Test + public void testReplyRetransmittedRequest() throws Exception { + setupIdleStateMachine(); + + // Mock last sent request and response + byte[] dummyIkeReqFirstPacket = "testRcvRetransmittedRequestReq".getBytes(); + byte[][] dummyIkeResp = new byte[][] {"testRcvRetransmittedRequestResp".getBytes()}; + + mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(dummyIkeReqFirstPacket); + mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyIkeResp)); + mSpyCurrentIkeSaRecord.incrementRemoteRequestMessageId(); + + // Build request with last validated message ID + ReceivedIkePacket request = + makeDpdIkeRequest( + mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1, + dummyIkeReqFirstPacket); + + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request); + + mLooper.dispatchAll(); + + verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord); + verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS)); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + @Test + public void testDiscardFakeRetransmittedRequest() throws Exception { + setupIdleStateMachine(); + + // Mock last sent request and response + byte[] dummyIkeReqFirstPacket = "testDiscardFakeRetransmittedRequestReq".getBytes(); + byte[][] dummyIkeResp = new byte[][] {"testDiscardFakeRetransmittedRequestResp".getBytes()}; + mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(dummyIkeReqFirstPacket); + mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyIkeResp)); + mSpyCurrentIkeSaRecord.incrementRemoteRequestMessageId(); + + // Build request with last validated message ID but different bytes + ReceivedIkePacket request = + makeDpdIkeRequest( + mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1, new byte[0]); + + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request); + + mLooper.dispatchAll(); + + verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord); + verify(mSpyIkeSocket, never()).sendIkePacket(any(), any()); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } + + @Test + public void testDiscardRetransmittedResponse() throws Exception { + mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth); + verifyRetransmissionStarted(); + + // Build and send fake response with last validated message ID to IKE state machine + ReceivedIkePacket resp = + makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT, + true /*isResp*/, + mSpyCurrentIkeSaRecord.getLocalRequestMessageId() - 1, + new LinkedList<>(), + new byte[0]); + + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp); + mLooper.dispatchAll(); + + // Verify current state does not change + verifyRetransmissionStarted(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth); + } + + @Test + public void testDeleteIkeLocalDeleteRequest() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE)); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + // Verify outbound message + IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + IkeHeader ikeHeader = delMsg.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType); + assertFalse(ikeHeader.isResponseMsg); + assertTrue(ikeHeader.fromIkeInitiator); + + List<IkeDeletePayload> deletePayloadList = + delMsg.getPayloadListForType( + IkePayload.PAYLOAD_TYPE_DELETE, IkeDeletePayload.class); + assertEquals(1, deletePayloadList.size()); + + IkeDeletePayload deletePayload = deletePayloadList.get(0); + assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId); + assertEquals(0, deletePayload.numSpi); + assertEquals(0, deletePayload.spiSize); + assertArrayEquals(new int[0], deletePayload.spisToDelete); + } + + @Test + public void testDeleteIkeLocalDeleteResponse() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE)); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + ReceivedIkePacket received = makeDeleteIkeResponse(mSpyCurrentIkeSaRecord); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received); + mLooper.dispatchAll(); + verifyIncrementLocaReqMsgId(); + + verifyNotifyUserCloseSession(); + + // Verify state machine quit properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + } + + @Test + public void testDeleteIkeLocalDeleteResponseWithParsingError() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE)); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + resetMockIkeMessageHelper(); + + // Mock receiving response with syntax error + ReceivedIkePacket mockInvalidPacket = + makeDummyReceivedIkePacketWithInvalidSyntax( + mSpyCurrentIkeSaRecord, + true /*isResp*/, + IkeHeader.EXCHANGE_TYPE_INFORMATIONAL); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket); + mLooper.dispatchAll(); + + // Verify no more request out + verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord); + + // Verify state machine quit properly + verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + assertNull(mIkeSessionStateMachine.getCurrentState()); + } + + @Test + public void testDeleteIkeLocalDeleteHandlesInvalidResp() throws Exception { + setupIdleStateMachine(); + + // Send delete request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE)); + mLooper.dispatchAll(); + + // Receive response with wrong exchange type + ReceivedIkePacket resp = + makeDummyReceivedIkePacketWithInvalidSyntax( + mSpyCurrentIkeSaRecord, + true /*isResp*/, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp); + mLooper.dispatchAll(); + + // Verify state machine quit properly + verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + assertNull(mIkeSessionStateMachine.getCurrentState()); + } + + @Test + public void testDeleteIkeLocalDeleteReceivedNonDeleteRequest() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE)); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + // Verify delete sent out. + verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + resetMockIkeMessageHelper(); // Discard value. + + ReceivedIkePacket received = makeRekeyIkeRequest(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received); + + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + verifyIncrementRemoteReqMsgId(); + + // Verify outbound response + IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + IkeHeader ikeHeader = resp.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType); + assertTrue(ikeHeader.isResponseMsg); + assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator); + + List<IkeNotifyPayload> notificationPayloadList = + resp.getPayloadListForType(IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class); + assertEquals(1, notificationPayloadList.size()); + + IkeNotifyPayload notifyPayload = notificationPayloadList.get(0); + assertEquals(IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE, notifyPayload.notifyType); + } + + @Test + public void testDeleteIkeRemoteDelete() throws Exception { + setupIdleStateMachine(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, + makeDeleteIkeRequest(mSpyCurrentIkeSaRecord)); + + mLooper.dispatchAll(); + verifyIncrementRemoteReqMsgId(); + + // Verify outbound message + IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + + IkeHeader ikeHeader = delMsg.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType); + assertTrue(ikeHeader.isResponseMsg); + assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator); + + assertTrue(delMsg.ikePayloadList.isEmpty()); + + verifyNotifyUserCloseSession(); + + // Verify state machine quit properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + } + + @Test + public void testReceiveDpd() throws Exception { + setupIdleStateMachine(); + + // Receive a DPD request, expect to stay in IDLE state + ReceivedIkePacket dummyDpdRequest = makeDpdIkeRequest(mSpyCurrentIkeSaRecord); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest); + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDpdRequest); + + // Verify outbound response + IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + IkeHeader ikeHeader = resp.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType); + assertTrue(ikeHeader.isResponseMsg); + assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator); + assertTrue(resp.ikePayloadList.isEmpty()); + } + + @Test + public void testReceiveDpdNonIdle() throws Exception { + setupIdleStateMachine(); + + // Move to a non-idle state. Use RekeyIkeRemoteDelete, as it doesn't send out any requests. + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mRekeyIkeRemoteDelete); + mLooper.dispatchAll(); + + // In a rekey state, receiving (and handling) a DPD should not result in a change of states + ReceivedIkePacket dummyDpdRequest = makeDpdIkeRequest(mSpyCurrentIkeSaRecord); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest); + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete); + + verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDpdRequest); + + // Verify outbound response + IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + IkeHeader ikeHeader = resp.ikeHeader; + assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); + assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType); + assertTrue(ikeHeader.isResponseMsg); + assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator); + assertTrue(resp.ikePayloadList.isEmpty()); + } + + @Test + public void testIdleTriggersNewRequests() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + + // Verify that the command is executed, and the state machine transitions to the right state + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalCreate); + verifyRetransmissionStarted(); + } + + @Test + public void testNonIdleStateDoesNotTriggerNewRequests() throws Exception { + setupIdleStateMachine(); + + // Force ourselves into a non-idle state + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mReceiving); + mLooper.dispatchAll(); + verifyEncryptAndEncodeNeverCalled(); + + // Queue a local request, and expect that it is not run (yet) + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE, + new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE)); + mLooper.dispatchAll(); + + // Verify that the state machine is still in the Receiving state + verifyEncryptAndEncodeNeverCalled(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.Receiving); + + // Go back to Idle, and expect to immediately transition to RekeyIkeLocalCreate from the + // queued request + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle); + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.RekeyIkeLocalCreate); + verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord); + } + + @Test + public void testOpenChildSessionValidatesArgs() throws Exception { + setupIdleStateMachine(); + + // Expect failure - no callbacks provided + try { + mIkeSessionStateMachine.openChildSession(mChildSessionOptions, null); + } catch (IllegalArgumentException expected) { + } + + // Expect failure - callbacks already registered + try { + mIkeSessionStateMachine.openChildSession( + mChildSessionOptions, mMockChildSessionCallback); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testOpenChildSession() throws Exception { + setupIdleStateMachine(); + + ChildSessionCallback cb = mock(ChildSessionCallback.class); + mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb); + + // Test that inserting the same cb returns an error, even before the state + // machine has a chance to process it. + try { + mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb); + } catch (IllegalArgumentException expected) { + } + + verify(mMockChildSessionFactoryHelper) + .makeChildSessionStateMachine( + eq(mLooper.getLooper()), + eq(mContext), + eq(mChildSessionOptions), + eq(mSpyUserCbExecutor), + eq(cb), + any()); + + // Verify state in IkeSessionStateMachine + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + + synchronized (mIkeSessionStateMachine.mChildCbToSessions) { + assertTrue(mIkeSessionStateMachine.mChildCbToSessions.containsKey(cb)); + } + } + + @Test + public void testCloseChildSessionValidatesArgs() throws Exception { + setupIdleStateMachine(); + + // Expect failure - callbacks not registered + try { + mIkeSessionStateMachine.closeChildSession(mock(ChildSessionCallback.class)); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testCloseChildSession() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.closeChildSession(mMockChildSessionCallback); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + } + + @Test + public void testCloseImmediatelyAfterOpenChildSession() throws Exception { + setupIdleStateMachine(); + + ChildSessionCallback cb = mock(ChildSessionCallback.class); + mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb); + + // Verify that closing the session immediately still picks up the child callback + // even before the looper has a chance to run. + mIkeSessionStateMachine.closeChildSession(mMockChildSessionCallback); + } + + @Test + public void testOnChildSessionClosed() throws Exception { + setupIdleStateMachine(); + + mDummyChildSmCallback.onChildSessionClosed(mMockChildSessionCallback); + + synchronized (mIkeSessionStateMachine.mChildCbToSessions) { + assertFalse( + mIkeSessionStateMachine.mChildCbToSessions.containsKey( + mMockChildSessionCallback)); + } + } + + @Test + public void testHandleUnexpectedExceptionInEnterState() throws Exception { + Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG); + IkeManager.setIkeLog(spyIkeLog); + + IkeSessionOptions mockSessionOptions = mock(IkeSessionOptions.class); + when(mockSessionOptions.getSaProposals()).thenThrow(mock(RuntimeException.class)); + + IkeSessionStateMachine ikeSession = + new IkeSessionStateMachine( + mLooper.getLooper(), + mContext, + mIpSecManager, + mockSessionOptions, + mChildSessionOptions, + mSpyUserCbExecutor, + mMockIkeSessionCallback, + mMockChildSessionCallback, + mMockEapAuthenticatorFactory); + // Send IKE INIT request + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE); + mLooper.dispatchAll(); + + assertNull(ikeSession.getCurrentState()); + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class)); + verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class)); + } + + @Test + public void testHandleUnexpectedExceptionInProcessStateMsg() throws Exception { + Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG); + IkeManager.setIkeLog(spyIkeLog); + + setupIdleStateMachine(); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, null /*receivedIkePacket*/); + mLooper.dispatchAll(); + + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mSpyUserCbExecutor).execute(any(Runnable.class)); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class)); + verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class)); + } + + @Test + public void testCreateIkeLocalIkeInitRcvErrorNotify() throws Exception { + // Send IKE INIT request + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE); + mLooper.dispatchAll(); + verifyRetransmissionStarted(); + + // Receive IKE INIT response with erro notification. + List<IkePayload> payloads = new LinkedList<>(); + payloads.add(new IkeNotifyPayload(IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN)); + ReceivedIkePacket resp = + makeDummyUnencryptedReceivedIkePacket( + 1L /*initiator SPI*/, + 2L /*respodner SPI*/, + IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT, + true /*isResp*/, + false /*fromIkeInit*/, + payloads); + + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp); + mLooper.dispatchAll(); + + // Fires user error callbacks + verify(mMockIkeSessionCallback) + .onClosedExceptionally( + argThat(err -> err instanceof NoValidProposalChosenException)); + // Verify state machine quit properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + } + + private void mockSendRekeyChildReq() throws Exception { + setupIdleStateMachine(); + + ChildLocalRequest childLocalRequest = + new ChildLocalRequest( + IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD, + mMockChildSessionCallback, + null /*childOptions*/); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, childLocalRequest); + mLooper.dispatchAll(); + + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + verify(mMockChildSessionStateMachine).rekeyChildSession(); + + // Mocking sending request + mDummyChildSmCallback.onOutboundPayloadsReady( + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, + false /*isResp*/, + new LinkedList<>(), + mMockChildSessionStateMachine); + mLooper.dispatchAll(); + } + + private void mockRcvTempFail() throws Exception { + ReceivedIkePacket resp = + makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_TEMPORARY_FAILURE)); + + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle); + mLooper.dispatchAll(); + } + + @Test + public void testTempFailureHandlerScheduleRetry() throws Exception { + mockSendRekeyChildReq(); + + // Mock sending TEMPORARY_FAILURE response + mockRcvTempFail(); + + // Move time forward to trigger retry + mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS); + mLooper.dispatchAll(); + + // Verify that rekey is triggered again + assertTrue( + mIkeSessionStateMachine.getCurrentState() + instanceof IkeSessionStateMachine.ChildProcedureOngoing); + verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession(); + } + + @Test + public void testTempFailureHandlerTimeout() throws Exception { + long currentTime = 0; + int retryCnt = 0; + + mockSendRekeyChildReq(); + + while (currentTime + RETRY_INTERVAL_MS < TEMP_FAILURE_RETRY_TIMEOUT_MS) { + mockRcvTempFail(); + + mLooper.moveTimeForward(RETRY_INTERVAL_MS); + currentTime += RETRY_INTERVAL_MS; + mLooper.dispatchAll(); + + retryCnt++; + verify(mMockChildSessionStateMachine, times(1 + retryCnt)).rekeyChildSession(); + } + + mLooper.moveTimeForward(RETRY_INTERVAL_MS); + mLooper.dispatchAll(); + + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class)); + } + + @Test + public void testTempFailureHandlerCancelTimer() throws Exception { + mockSendRekeyChildReq(); + + // Mock sending TEMPORARY_FAILURE response + mockRcvTempFail(); + + // Move time forward to trigger retry + mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS); + mLooper.dispatchAll(); + verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession(); + + // Mock sending a valid response + ReceivedIkePacket resp = + makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + EXCHANGE_TYPE_CREATE_CHILD_SA, + true /*isResp*/, + new LinkedList<>()); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp); + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle); + mLooper.dispatchAll(); + + // Move time forward + mLooper.moveTimeForward(IkeSessionStateMachine.TEMP_FAILURE_RETRY_TIMEOUT_MS); + mLooper.dispatchAll(); + + // Validate IKE Session is not closed + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + // Validate no more retry + verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession(); + } + + @Test + public void testIdleReceiveRequestWithFatalError() throws Exception { + setupIdleStateMachine(); + + // Mock receiving packet with syntax error + ReceivedIkePacket mockInvalidPacket = + makeDummyReceivedIkePacketWithInvalidSyntax( + mSpyCurrentIkeSaRecord, + false /*isResp*/, + IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA); + mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket); + mLooper.dispatchAll(); + + // Verify Delete request was sent + List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, payloads.size()); + + IkePayload payload = payloads.get(0); + assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType); + assertEquals(ERROR_TYPE_INVALID_SYNTAX, ((IkeNotifyPayload) payload).notifyType); + + // Verify IKE Session is closed properly + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + } + + @Test + public void testHandlesInvalidRequest() throws Exception { + setupIdleStateMachine(); + + mIkeSessionStateMachine.sendMessage( + IkeSessionStateMachine.CMD_FORCE_TRANSITION, + mIkeSessionStateMachine.mChildProcedureOngoing); + + // Receive an IKE AUTH request + ReceivedIkePacket request = + makeDummyEncryptedReceivedIkePacketWithPayloadList( + mSpyCurrentIkeSaRecord, + IkeHeader.EXCHANGE_TYPE_IKE_AUTH, + false /*isResp*/, + new LinkedList<IkePayload>()); + mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request); + mLooper.dispatchAll(); + + // Verify error notification was sent + List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); + assertEquals(1, ikePayloadList.size()); + assertEquals( + ERROR_TYPE_INVALID_SYNTAX, ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType); + + // Verify IKE Session has quit + assertNull(mIkeSessionStateMachine.getCurrentState()); + verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class)); + } + + @Test + public void testIdleHandlesUnprotectedPacket() throws Exception { + setupIdleStateMachine(); + + ReceivedIkePacket req = + makeDummyReceivedIkePacketWithUnprotectedError( + mSpyCurrentIkeSaRecord, + false /*isResp*/, + EXCHANGE_TYPE_INFORMATIONAL, + mock(IkeException.class)); + + mLooper.dispatchAll(); + assertTrue( + mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSocketTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSocketTest.java new file mode 100644 index 00000000..1b8761f7 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSocketTest.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.net.IpSecManager; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.os.HandlerThread; +import android.os.Looper; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.Log; +import android.util.LongSparseArray; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.IkeSocket.PacketReceiver; +import com.android.internal.net.ipsec.ike.message.IkeHeader; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public final class IkeSocketTest { + private static final int REMOTE_RECV_BUFF_SIZE = 2048; + private static final int TIMEOUT = 1000; + + private static final String NON_ESP_MARKER_HEX_STRING = "00000000"; + private static final String IKE_REQ_MESSAGE_HEX_STRING = + "5f54bf6d8b48e6e100000000000000002120220800000000" + + "00000150220000300000002c010100040300000c0100000c" + + "800e00800300000803000002030000080400000200000008" + + "020000022800008800020000b4a2faf4bb54878ae21d6385" + + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656" + + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37" + + "534036040610ebdd92f46bef84f0be7db860351843858f8a" + + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb" + + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024" + + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7" + + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb" + + "881eab2051d8663f421d10b02b00001c00004005d915368c" + + "a036004cb578ae3e3fb268509aeab1900000002069936922" + + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500"; + + private static final String LOCAL_SPI = "0000000000000000"; + private static final String REMOTE_SPI = "5f54bf6d8b48e6e1"; + + private static final String DATA_ONE = "one 1"; + private static final String DATA_TWO = "two 2"; + + private static final String IPV4_LOOPBACK = "127.0.0.1"; + + private byte[] mDataOne; + private byte[] mDataTwo; + + private long mLocalSpi; + private long mRemoteSpi; + + private LongSparseArray mSpiToIkeStateMachineMap; + private PacketReceiver mPacketReceiver; + + private UdpEncapsulationSocket mClientUdpEncapSocket; + private InetAddress mLocalAddress; + private FileDescriptor mDummyRemoteServerFd; + + private IkeSessionStateMachine mMockIkeSessionStateMachine; + + @Before + public void setUp() throws Exception { + Context context = InstrumentationRegistry.getContext(); + IpSecManager ipSecManager = (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE); + mClientUdpEncapSocket = ipSecManager.openUdpEncapsulationSocket(); + + mLocalAddress = InetAddress.getByName(IPV4_LOOPBACK); + mDummyRemoteServerFd = getBoundUdpSocket(mLocalAddress); + + mDataOne = DATA_ONE.getBytes("UTF-8"); + mDataTwo = DATA_TWO.getBytes("UTF-8"); + + ByteBuffer localSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(LOCAL_SPI)); + mLocalSpi = localSpiBuffer.getLong(); + ByteBuffer remoteSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(REMOTE_SPI)); + mRemoteSpi = remoteSpiBuffer.getLong(); + + mMockIkeSessionStateMachine = mock(IkeSessionStateMachine.class); + + mSpiToIkeStateMachineMap = new LongSparseArray<IkeSessionStateMachine>(); + mSpiToIkeStateMachineMap.put(mLocalSpi, mMockIkeSessionStateMachine); + + mPacketReceiver = new IkeSocket.PacketReceiver(); + } + + @After + public void tearDown() throws Exception { + mClientUdpEncapSocket.close(); + IkeSocket.setPacketReceiver(mPacketReceiver); + Os.close(mDummyRemoteServerFd); + } + + private static FileDescriptor getBoundUdpSocket(InetAddress address) throws Exception { + FileDescriptor sock = + Os.socket(OsConstants.AF_INET, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP); + Os.bind(sock, address, IkeSocket.IKE_SERVER_PORT); + return sock; + } + + @Test + public void testGetAndCloseIkeSocket() throws Exception { + // Must be prepared here; AndroidJUnitRunner runs tests on different threads from the + // setUp() call. Since the new Handler() call is run in getIkeSocket, the Looper must be + // prepared here. + if (Looper.myLooper() == null) Looper.prepare(); + + IkeSessionStateMachine mMockIkeSessionOne = mock(IkeSessionStateMachine.class); + IkeSessionStateMachine mMockIkeSessionTwo = mock(IkeSessionStateMachine.class); + + IkeSocket ikeSocketOne = IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionOne); + assertEquals(1, ikeSocketOne.mAliveIkeSessions.size()); + + IkeSocket ikeSocketTwo = IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionTwo); + assertEquals(ikeSocketOne, ikeSocketTwo); + assertEquals(2, ikeSocketTwo.mAliveIkeSessions.size()); + + ikeSocketOne.releaseReference(mMockIkeSessionOne); + assertEquals(1, ikeSocketOne.mAliveIkeSessions.size()); + + ikeSocketTwo.releaseReference(mMockIkeSessionTwo); + assertEquals(0, ikeSocketTwo.mAliveIkeSessions.size()); + } + + @Test + public void testSendIkePacket() throws Exception { + if (Looper.myLooper() == null) Looper.prepare(); + + // Send IKE packet + IkeSocket ikeSocket = + IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionStateMachine); + ikeSocket.sendIkePacket(mDataOne, mLocalAddress); + + byte[] receivedData = receive(mDummyRemoteServerFd); + + // Verify received data + ByteBuffer expectedBuffer = + ByteBuffer.allocate(IkeSocket.NON_ESP_MARKER_LEN + mDataOne.length); + expectedBuffer.put(IkeSocket.NON_ESP_MARKER).put(mDataOne); + + assertArrayEquals(expectedBuffer.array(), receivedData); + + ikeSocket.releaseReference(mMockIkeSessionStateMachine); + } + + @Test + public void testReceiveIkePacket() throws Exception { + // Create working thread. + HandlerThread mIkeThread = new HandlerThread("IkeSocketTest"); + mIkeThread.start(); + + // Create IkeSocket on working thread. + IkeSocketReceiver socketReceiver = new IkeSocketReceiver(); + TestCountDownLatch createLatch = new TestCountDownLatch(); + mIkeThread + .getThreadHandler() + .post( + () -> { + try { + socketReceiver.setIkeSocket( + IkeSocket.getIkeSocket( + mClientUdpEncapSocket, + mMockIkeSessionStateMachine)); + createLatch.countDown(); + Log.d("IkeSocketTest", "IkeSocket created."); + } catch (ErrnoException e) { + Log.e("IkeSocketTest", "error encountered creating IkeSocket ", e); + } + }); + createLatch.await(); + + IkeSocket ikeSocket = socketReceiver.getIkeSocket(); + assertNotNull(ikeSocket); + + // Configure IkeSocket + TestCountDownLatch receiveLatch = new TestCountDownLatch(); + DummyPacketReceiver packetReceiver = new DummyPacketReceiver(receiveLatch); + IkeSocket.setPacketReceiver(packetReceiver); + + // Send first packet. + sendToIkeSocket(mDummyRemoteServerFd, mDataOne, mLocalAddress); + receiveLatch.await(); + + assertEquals(1, ikeSocket.numPacketsReceived()); + assertArrayEquals(mDataOne, packetReceiver.mReceivedData); + + // Send second packet. + sendToIkeSocket(mDummyRemoteServerFd, mDataTwo, mLocalAddress); + receiveLatch.await(); + + assertEquals(2, ikeSocket.numPacketsReceived()); + assertArrayEquals(mDataTwo, packetReceiver.mReceivedData); + + // Close IkeSocket. + TestCountDownLatch closeLatch = new TestCountDownLatch(); + ikeSocket + .getHandler() + .post( + () -> { + ikeSocket.releaseReference(mMockIkeSessionStateMachine); + closeLatch.countDown(); + }); + closeLatch.await(); + + mIkeThread.quitSafely(); + } + + @Test + public void testHandlePacket() throws Exception { + byte[] recvBuf = + TestUtils.hexStringToByteArray( + NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING); + + mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); + + byte[] expectedIkePacketBytes = TestUtils.hexStringToByteArray(IKE_REQ_MESSAGE_HEX_STRING); + ArgumentCaptor<IkeHeader> ikeHeaderCaptor = ArgumentCaptor.forClass(IkeHeader.class); + verify(mMockIkeSessionStateMachine) + .receiveIkePacket(ikeHeaderCaptor.capture(), eq(expectedIkePacketBytes)); + + IkeHeader capturedIkeHeader = ikeHeaderCaptor.getValue(); + assertEquals(mRemoteSpi, capturedIkeHeader.ikeInitiatorSpi); + assertEquals(mLocalSpi, capturedIkeHeader.ikeResponderSpi); + } + + @Test + public void testHandleEspPacket() throws Exception { + byte[] recvBuf = + TestUtils.hexStringToByteArray( + NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING); + // Modify Non-ESP Marker + recvBuf[0] = 1; + + mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); + + verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any()); + } + + @Test + public void testHandlePacketWithMalformedHeader() throws Exception { + String malformedIkePacketHexString = "5f54bf6d8b48e6e100000000000000002120220800000000"; + byte[] recvBuf = + TestUtils.hexStringToByteArray( + NON_ESP_MARKER_HEX_STRING + malformedIkePacketHexString); + + mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); + + verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any()); + } + + private byte[] receive(FileDescriptor mfd) throws Exception { + byte[] receiveBuffer = new byte[REMOTE_RECV_BUFF_SIZE]; + AtomicInteger bytesRead = new AtomicInteger(-1); + Thread receiveThread = + new Thread( + () -> { + while (bytesRead.get() < 0) { + try { + bytesRead.set( + Os.recvfrom( + mDummyRemoteServerFd, + receiveBuffer, + 0, + REMOTE_RECV_BUFF_SIZE, + 0, + null)); + } catch (Exception e) { + Log.e( + "IkeSocketTest", + "Error encountered reading from socket", + e); + } + } + Log.d( + "IkeSocketTest", + "Packet received with size of " + bytesRead.get()); + }); + + receiveThread.start(); + receiveThread.join(TIMEOUT); + + return Arrays.copyOfRange(receiveBuffer, 0, bytesRead.get()); + } + + private void sendToIkeSocket(FileDescriptor fd, byte[] data, InetAddress destAddress) + throws Exception { + Os.sendto(fd, data, 0, data.length, 0, destAddress, mClientUdpEncapSocket.getPort()); + } + + private static class IkeSocketReceiver { + private IkeSocket mIkeSocket; + + void setIkeSocket(IkeSocket ikeSocket) { + mIkeSocket = ikeSocket; + } + + IkeSocket getIkeSocket() { + return mIkeSocket; + } + } + + private static class DummyPacketReceiver implements IkeSocket.IPacketReceiver { + byte[] mReceivedData = null; + final TestCountDownLatch mLatch; + + DummyPacketReceiver(TestCountDownLatch latch) { + mLatch = latch; + } + + public void handlePacket( + byte[] revbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession) { + mReceivedData = Arrays.copyOfRange(revbuf, 0, revbuf.length); + mLatch.countDown(); + Log.d("IkeSocketTest", "Packet received"); + } + } + + private static class TestCountDownLatch { + private CountDownLatch mLatch; + + TestCountDownLatch() { + reset(); + } + + private void reset() { + mLatch = new CountDownLatch(1); + } + + void countDown() { + mLatch.countDown(); + } + + void await() { + try { + if (!mLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { + fail("Time out"); + } + } catch (InterruptedException e) { + fail(e.toString()); + } + reset(); + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java new file mode 100644 index 00000000..646b9d9a --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.AdditionalMatchers.aryEq; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.IpSecManager; +import android.net.IpSecManager.SecurityParameterIndex; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.IpSecTransform; +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest; +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest; +import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IkeSecurityParameterIndex; +import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecord; +import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecordConfig; +import com.android.internal.net.ipsec.ike.SaRecord.IIpSecTransformHelper; +import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord; +import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecordConfig; +import com.android.internal.net.ipsec.ike.SaRecord.IpSecTransformHelper; +import com.android.internal.net.ipsec.ike.SaRecord.SaRecordHelper; +import com.android.internal.net.ipsec.ike.crypto.IkeCipher; +import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; +import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf; +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; +import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils; +import com.android.server.IpSecService; + +import libcore.net.InetAddressUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.net.Inet4Address; + +@RunWith(JUnit4.class) +public final class SaRecordTest { + private static final Inet4Address LOCAL_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200")); + private static final Inet4Address REMOTE_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100")); + + private static final String PRF_KEY_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485"; + private static final String DATA_TO_SIGN_HEX_STRING = "010000000a50500d"; + private static final String CALCULATED_MAC_HEX_STRING = + "D83B20CC6A0932B2A7CEF26E4020ABAAB64F0C6A"; + + private static final long IKE_INIT_SPI = 0x5F54BF6D8B48E6E1L; + private static final long IKE_RESP_SPI = 0x909232B3D1EDCB5CL; + + private static final String IKE_NONCE_INIT_HEX_STRING = + "C39B7F368F4681B89FA9B7BE6465ABD7C5F68B6ED5D3B4C72CB4240EB5C46412"; + private static final String IKE_NONCE_RESP_HEX_STRING = + "9756112CA539F5C25ABACC7EE92B73091942A9C06950F98848F1AF1694C4DDFF"; + + private static final String IKE_SHARED_DH_KEY_HEX_STRING = + "C14155DEA40056BD9C76FB4819687B7A397582F4CD5AFF4B" + + "8F441C56E0C08C84234147A0BA249A555835A048E3CA2980" + + "7D057A61DD26EEFAD9AF9C01497005E52858E29FB42EB849" + + "6731DF96A11CCE1F51137A9A1B900FA81AEE7898E373D4E4" + + "8B899BBECA091314ECD4B6E412EF4B0FEF798F54735F3180" + + "7424A318287F20E8"; + + private static final String IKE_SKEYSEED_HEX_STRING = + "8C42F3B1F5F81C7BAAC5F33E9A4F01987B2F9657"; + private static final String IKE_SK_D_HEX_STRING = "C86B56EFCF684DCC2877578AEF3137167FE0EBF6"; + private static final String IKE_SK_AUTH_INIT_HEX_STRING = + "554FBF5A05B7F511E05A30CE23D874DB9EF55E51"; + private static final String IKE_SK_AUTH_RESP_HEX_STRING = + "36D83420788337CA32ECAA46892C48808DCD58B1"; + private static final String IKE_SK_ENCR_INIT_HEX_STRING = "5CBFD33F75796C0188C4A3A546AEC4A1"; + private static final String IKE_SK_ENCR_RESP_HEX_STRING = "C33B35FCF29514CD9D8B4A695E1A816E"; + private static final String IKE_SK_PRF_INIT_HEX_STRING = + "094787780EE466E2CB049FA327B43908BC57E485"; + private static final String IKE_SK_PRF_RESP_HEX_STRING = + "A30E6B08BE56C0E6BFF4744143C75219299E1BEB"; + private static final String IKE_KEY_MAT = + IKE_SK_D_HEX_STRING + + IKE_SK_AUTH_INIT_HEX_STRING + + IKE_SK_AUTH_RESP_HEX_STRING + + IKE_SK_ENCR_INIT_HEX_STRING + + IKE_SK_ENCR_RESP_HEX_STRING + + IKE_SK_PRF_INIT_HEX_STRING + + IKE_SK_PRF_RESP_HEX_STRING; + + private static final int IKE_AUTH_ALGO_KEY_LEN = 20; + private static final int IKE_ENCR_ALGO_KEY_LEN = 16; + private static final int IKE_PRF_KEY_LEN = 20; + private static final int IKE_SK_D_KEY_LEN = IKE_PRF_KEY_LEN; + + private static final int FIRST_CHILD_INIT_SPI = 0x2ad4c0a2; + private static final int FIRST_CHILD_RESP_SPI = 0xcae7019f; + + private static final String FIRST_CHILD_ENCR_INIT_HEX_STRING = + "1B865CEA6E2C23973E8C5452ADC5CD7D"; + private static final String FIRST_CHILD_ENCR_RESP_HEX_STRING = + "5E82FEDACC6DCB0756DDD7553907EBD1"; + private static final String FIRST_CHILD_AUTH_INIT_HEX_STRING = + "A7A5A44F7EF4409657206C7DC52B7E692593B51E"; + private static final String FIRST_CHILD_AUTH_RESP_HEX_STRING = + "CDE612189FD46DE870FAEC04F92B40B0BFDBD9E1"; + private static final String FIRST_CHILD_KEY_MAT = + FIRST_CHILD_ENCR_INIT_HEX_STRING + + FIRST_CHILD_AUTH_INIT_HEX_STRING + + FIRST_CHILD_ENCR_RESP_HEX_STRING + + FIRST_CHILD_AUTH_RESP_HEX_STRING; + + private static final int FIRST_CHILD_AUTH_ALGO_KEY_LEN = 20; + private static final int FIRST_CHILD_ENCR_ALGO_KEY_LEN = 16; + + private IkeMacPrf mIkeHmacSha1Prf; + private IkeMacIntegrity mHmacSha1IntegrityMac; + private IkeCipher mAesCbcCipher; + + private LocalRequest mMockFutureRekeyIkeEvent; + private ChildLocalRequest mMockFutureRekeyChildEvent; + + private SaRecordHelper mSaRecordHelper = new SaRecordHelper(); + + @Before + public void setUp() throws Exception { + mIkeHmacSha1Prf = + IkeMacPrf.create( + new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1), + IkeMessage.getSecurityProvider()); + mHmacSha1IntegrityMac = + IkeMacIntegrity.create( + new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96), + IkeMessage.getSecurityProvider()); + mAesCbcCipher = + IkeCipher.create( + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, + SaProposal.KEY_LEN_AES_128), + IkeMessage.getSecurityProvider()); + + mMockFutureRekeyIkeEvent = mock(LocalRequest.class); + mMockFutureRekeyChildEvent = mock(ChildLocalRequest.class); + } + + // Test generating keying material for making IKE SA. + @Test + public void testMakeIkeSaRecord() throws Exception { + byte[] sKeySeed = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING); + byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING); + byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING); + + IkeSecurityParameterIndex ikeInitSpi = + IkeSecurityParameterIndex.allocateSecurityParameterIndex( + LOCAL_ADDRESS, IKE_INIT_SPI); + IkeSecurityParameterIndex ikeRespSpi = + IkeSecurityParameterIndex.allocateSecurityParameterIndex( + REMOTE_ADDRESS, IKE_RESP_SPI); + IkeSaRecordConfig ikeSaRecordConfig = + new IkeSaRecordConfig( + ikeInitSpi, + ikeRespSpi, + mIkeHmacSha1Prf, + IKE_AUTH_ALGO_KEY_LEN, + IKE_ENCR_ALGO_KEY_LEN, + true /*isLocalInit*/, + mMockFutureRekeyIkeEvent); + + int keyMaterialLen = + IKE_SK_D_KEY_LEN + + IKE_AUTH_ALGO_KEY_LEN * 2 + + IKE_ENCR_ALGO_KEY_LEN * 2 + + IKE_PRF_KEY_LEN * 2; + + IkeSaRecord ikeSaRecord = + mSaRecordHelper.makeIkeSaRecord(sKeySeed, nonceInit, nonceResp, ikeSaRecordConfig); + + assertTrue(ikeSaRecord.isLocalInit); + assertEquals(IKE_INIT_SPI, ikeSaRecord.getInitiatorSpi()); + assertEquals(IKE_RESP_SPI, ikeSaRecord.getResponderSpi()); + assertArrayEquals( + TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING), ikeSaRecord.getSkD()); + assertArrayEquals( + TestUtils.hexStringToByteArray(IKE_SK_AUTH_INIT_HEX_STRING), + ikeSaRecord.getOutboundIntegrityKey()); + assertArrayEquals( + TestUtils.hexStringToByteArray(IKE_SK_AUTH_RESP_HEX_STRING), + ikeSaRecord.getInboundIntegrityKey()); + assertArrayEquals( + TestUtils.hexStringToByteArray(IKE_SK_ENCR_INIT_HEX_STRING), + ikeSaRecord.getOutboundEncryptionKey()); + assertArrayEquals( + TestUtils.hexStringToByteArray(IKE_SK_ENCR_RESP_HEX_STRING), + ikeSaRecord.getInboundDecryptionKey()); + assertArrayEquals( + TestUtils.hexStringToByteArray(IKE_SK_PRF_INIT_HEX_STRING), ikeSaRecord.getSkPi()); + assertArrayEquals( + TestUtils.hexStringToByteArray(IKE_SK_PRF_RESP_HEX_STRING), ikeSaRecord.getSkPr()); + + ikeSaRecord.close(); + + verify(mMockFutureRekeyIkeEvent).cancel(); + } + + // Test generating keying material and building IpSecTransform for making Child SA. + @Test + public void testMakeChildSaRecord() throws Exception { + byte[] sharedKey = new byte[0]; + byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING); + byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING); + + MockIpSecTestUtils mockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec(); + IpSecManager ipSecManager = mockIpSecTestUtils.getIpSecManager(); + IpSecService mockIpSecService = mockIpSecTestUtils.getIpSecService(); + Context context = mockIpSecTestUtils.getContext(); + + when(mockIpSecService.allocateSecurityParameterIndex( + eq(LOCAL_ADDRESS.getHostAddress()), anyInt(), anyObject())) + .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(FIRST_CHILD_INIT_SPI)); + when(mockIpSecService.allocateSecurityParameterIndex( + eq(REMOTE_ADDRESS.getHostAddress()), anyInt(), anyObject())) + .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(FIRST_CHILD_RESP_SPI)); + + SecurityParameterIndex childInitSpi = + ipSecManager.allocateSecurityParameterIndex(LOCAL_ADDRESS); + SecurityParameterIndex childRespSpi = + ipSecManager.allocateSecurityParameterIndex(REMOTE_ADDRESS); + + byte[] initAuthKey = TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_INIT_HEX_STRING); + byte[] respAuthKey = TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_RESP_HEX_STRING); + byte[] initEncryptionKey = TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_INIT_HEX_STRING); + byte[] respEncryptionKey = TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_RESP_HEX_STRING); + + IIpSecTransformHelper mockIpSecHelper; + mockIpSecHelper = mock(IIpSecTransformHelper.class); + SaRecord.setIpSecTransformHelper(mockIpSecHelper); + + IpSecTransform mockInTransform = mock(IpSecTransform.class); + IpSecTransform mockOutTransform = mock(IpSecTransform.class); + UdpEncapsulationSocket mockUdpEncapSocket = mock(UdpEncapsulationSocket.class); + + when(mockIpSecHelper.makeIpSecTransform( + eq(context), + eq(LOCAL_ADDRESS), + eq(mockUdpEncapSocket), + eq(childRespSpi), + eq(mHmacSha1IntegrityMac), + eq(mAesCbcCipher), + aryEq(initAuthKey), + aryEq(initEncryptionKey), + eq(false))) + .thenReturn(mockOutTransform); + + when(mockIpSecHelper.makeIpSecTransform( + eq(context), + eq(REMOTE_ADDRESS), + eq(mockUdpEncapSocket), + eq(childInitSpi), + eq(mHmacSha1IntegrityMac), + eq(mAesCbcCipher), + aryEq(respAuthKey), + aryEq(respEncryptionKey), + eq(false))) + .thenReturn(mockInTransform); + + ChildSaRecordConfig childSaRecordConfig = + new ChildSaRecordConfig( + mockIpSecTestUtils.getContext(), + childInitSpi, + childRespSpi, + LOCAL_ADDRESS, + REMOTE_ADDRESS, + mockUdpEncapSocket, + mIkeHmacSha1Prf, + mHmacSha1IntegrityMac, + mAesCbcCipher, + TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING), + false /*isTransport*/, + true /*isLocalInit*/, + mMockFutureRekeyChildEvent); + + ChildSaRecord childSaRecord = + mSaRecordHelper.makeChildSaRecord( + sharedKey, nonceInit, nonceResp, childSaRecordConfig); + + assertTrue(childSaRecord.isLocalInit); + assertEquals(FIRST_CHILD_INIT_SPI, childSaRecord.getLocalSpi()); + assertEquals(FIRST_CHILD_RESP_SPI, childSaRecord.getRemoteSpi()); + assertEquals(mockInTransform, childSaRecord.getInboundIpSecTransform()); + assertEquals(mockOutTransform, childSaRecord.getOutboundIpSecTransform()); + + assertArrayEquals( + TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_INIT_HEX_STRING), + childSaRecord.getOutboundIntegrityKey()); + assertArrayEquals( + TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_RESP_HEX_STRING), + childSaRecord.getInboundIntegrityKey()); + assertArrayEquals( + TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_INIT_HEX_STRING), + childSaRecord.getOutboundEncryptionKey()); + assertArrayEquals( + TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_RESP_HEX_STRING), + childSaRecord.getInboundDecryptionKey()); + + childSaRecord.close(); + verify(mMockFutureRekeyChildEvent).cancel(); + + SaRecord.setIpSecTransformHelper(new IpSecTransformHelper()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipherTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipherTest.java new file mode 100644 index 00000000..a3b2253e --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipherTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.crypto; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.IpSecAlgorithm; +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.Random; + +import javax.crypto.AEADBadTagException; + +@RunWith(JUnit4.class) +public final class IkeCombinedModeCipherTest { + private static final String IV = "fbd69d9de2dafc5e"; + private static final String ENCRYPTED_PADDED_DATA_WITH_CHECKSUM = + "f4109834e9f3559758c05edf119917521b885f67f0d14ced43"; + private static final String UNENCRYPTED_PADDED_DATA = "000000080000400f00"; + private static final String ADDITIONAL_AUTH_DATA = + "77c708b4523e39a471dc683c1d4f21362e202508000000060000004129000025"; + private static final String KEY = + "7C04513660DEC572D896105254EF92608054F8E6EE19E79CE52AB8697B2B5F2C2AA90C29"; + + private static final int AES_GCM_IV_LEN = 8; + private static final int AES_GCM_16_CHECKSUM_LEN = 128; + + private IkeCombinedModeCipher mAesGcm16Cipher; + + private byte[] mAesGcmKey; + private byte[] mIv; + private byte[] mEncryptedPaddedDataWithChecksum; + private byte[] mUnencryptedPaddedData; + private byte[] mAdditionalAuthData; + + @Before + public void setUp() { + mAesGcm16Cipher = + (IkeCombinedModeCipher) + IkeCipher.create( + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16, + SaProposal.KEY_LEN_AES_256), + IkeMessage.getSecurityProvider()); + + mAesGcmKey = TestUtils.hexStringToByteArray(KEY); + mIv = TestUtils.hexStringToByteArray(IV); + mEncryptedPaddedDataWithChecksum = + TestUtils.hexStringToByteArray(ENCRYPTED_PADDED_DATA_WITH_CHECKSUM); + mUnencryptedPaddedData = TestUtils.hexStringToByteArray(UNENCRYPTED_PADDED_DATA); + mAdditionalAuthData = TestUtils.hexStringToByteArray(ADDITIONAL_AUTH_DATA); + } + + @Test + public void testBuild() throws Exception { + assertTrue(mAesGcm16Cipher.isAead()); + assertEquals(AES_GCM_IV_LEN, mAesGcm16Cipher.generateIv().length); + } + + @Test + public void testGenerateRandomIv() throws Exception { + assertFalse(Arrays.equals(mAesGcm16Cipher.generateIv(), mAesGcm16Cipher.generateIv())); + } + + @Test + public void testEncrypt() throws Exception { + byte[] calculatedData = + mAesGcm16Cipher.encrypt( + mUnencryptedPaddedData, mAdditionalAuthData, mAesGcmKey, mIv); + + assertArrayEquals(mEncryptedPaddedDataWithChecksum, calculatedData); + } + + @Test + public void testDecrypt() throws Exception { + byte[] calculatedData = + mAesGcm16Cipher.decrypt( + mEncryptedPaddedDataWithChecksum, mAdditionalAuthData, mAesGcmKey, mIv); + + assertArrayEquals(mUnencryptedPaddedData, calculatedData); + } + + @Test + public void testEncryptWithWrongKeyLen() throws Exception { + byte[] encryptionKey = TestUtils.hexStringToByteArray(KEY + "00"); + + try { + mAesGcm16Cipher.encrypt( + mUnencryptedPaddedData, mAdditionalAuthData, encryptionKey, mIv); + fail("Expected to fail because encryption key has wrong length."); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testDecrypWithWrongKey() throws Exception { + byte[] encryptionKey = new byte[mAesGcmKey.length]; + new Random().nextBytes(encryptionKey); + + try { + mAesGcm16Cipher.decrypt( + mEncryptedPaddedDataWithChecksum, mAdditionalAuthData, encryptionKey, mIv); + fail("Expected to fail because decryption key is wrong"); + } catch (AEADBadTagException expected) { + + } + } + + @Test + public void testBuildIpSecAlgorithm() throws Exception { + IpSecAlgorithm ipsecAlgorithm = mAesGcm16Cipher.buildIpSecAlgorithmWithKey(mAesGcmKey); + + IpSecAlgorithm expectedIpSecAlgorithm = + new IpSecAlgorithm( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, mAesGcmKey, AES_GCM_16_CHECKSUM_LEN); + + assertTrue(IpSecAlgorithm.equals(expectedIpSecAlgorithm, ipsecAlgorithm)); + } + + @Test + public void buildIpSecAlgorithmWithInvalidKey() throws Exception { + byte[] encryptionKey = TestUtils.hexStringToByteArray(KEY + "00"); + + try { + mAesGcm16Cipher.buildIpSecAlgorithmWithKey(encryptionKey); + fail("Expected to fail because encryption key has wrong length."); + } catch (IllegalArgumentException expected) { + + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacIntegrityTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacIntegrityTest.java new file mode 100644 index 00000000..ed625660 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacIntegrityTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.crypto; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.IpSecAlgorithm; +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; + +@RunWith(JUnit4.class) +public final class IkeMacIntegrityTest { + private static final String DATA_TO_AUTH_HEX_STRING = + "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec" + + "230000d0b9132b7bb9f658dfdc648e5017a6322a030c316c" + + "e55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58" + + "46de333ecd3ea2b705d18293b130395300ba92a351041345" + + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f" + + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60" + + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b" + + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423" + + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3" + + "59eb4e81"; + private static final String INTEGRITY_KEY_HEX_STRING = + "554fbf5a05b7f511e05a30ce23d874db9ef55e51"; + private static final String CHECKSUM_HEX_STRING = "ae6e0f22abdad69ba8007d50"; + + private IkeMacIntegrity mHmacSha1IntegrityMac; + private byte[] mHmacSha1IntegrityKey; + + private byte[] mDataToAuthenticate; + + @Before + public void setUp() throws Exception { + mHmacSha1IntegrityMac = + IkeMacIntegrity.create( + new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96), + IkeMessage.getSecurityProvider()); + mHmacSha1IntegrityKey = TestUtils.hexStringToByteArray(INTEGRITY_KEY_HEX_STRING); + + mDataToAuthenticate = TestUtils.hexStringToByteArray(DATA_TO_AUTH_HEX_STRING); + } + + @Test + public void testGenerateChecksum() throws Exception { + byte[] calculatedChecksum = + mHmacSha1IntegrityMac.generateChecksum(mHmacSha1IntegrityKey, mDataToAuthenticate); + + byte[] expectedChecksum = TestUtils.hexStringToByteArray(CHECKSUM_HEX_STRING); + assertArrayEquals(expectedChecksum, calculatedChecksum); + } + + @Test + public void testGenerateChecksumWithDifferentKey() throws Exception { + byte[] integrityKey = mHmacSha1IntegrityKey.clone(); + integrityKey[0]++; + + byte[] calculatedChecksum = + mHmacSha1IntegrityMac.generateChecksum(integrityKey, mDataToAuthenticate); + + byte[] expectedChecksum = TestUtils.hexStringToByteArray(CHECKSUM_HEX_STRING); + assertFalse(Arrays.equals(expectedChecksum, calculatedChecksum)); + } + + @Test + public void testGenerateChecksumWithInvalidKey() throws Exception { + byte[] integrityKey = TestUtils.hexStringToByteArray(INTEGRITY_KEY_HEX_STRING + "0000"); + + try { + byte[] calculatedChecksum = + mHmacSha1IntegrityMac.generateChecksum(integrityKey, mDataToAuthenticate); + fail("Expected to fail due to invalid authentication key."); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testBuildIpSecAlgorithm() throws Exception { + IpSecAlgorithm ipsecAlgorithm = + mHmacSha1IntegrityMac.buildIpSecAlgorithmWithKey(mHmacSha1IntegrityKey); + + IpSecAlgorithm expectedIpSecAlgorithm = + new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, mHmacSha1IntegrityKey, 96); + + assertTrue(IpSecAlgorithm.equals(expectedIpSecAlgorithm, ipsecAlgorithm)); + } + + @Test + public void buildIpSecAlgorithmWithInvalidKey() throws Exception { + byte[] encryptionKey = TestUtils.hexStringToByteArray(INTEGRITY_KEY_HEX_STRING + "00"); + + try { + mHmacSha1IntegrityMac.buildIpSecAlgorithmWithKey(encryptionKey); + + fail("Expected to fail due to integrity key with wrong length."); + } catch (IllegalArgumentException expected) { + + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacPrfTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacPrfTest.java new file mode 100644 index 00000000..717886f7 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacPrfTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.crypto; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; + +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; + +@RunWith(JUnit4.class) +public final class IkeMacPrfTest { + + private static final String PRF_KEY_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485"; + private static final String DATA_TO_SIGN_HEX_STRING = "010000000a50500d"; + private static final String CALCULATED_MAC_HEX_STRING = + "D83B20CC6A0932B2A7CEF26E4020ABAAB64F0C6A"; + + private static final String IKE_INIT_SPI = "5F54BF6D8B48E6E1"; + private static final String IKE_RESP_SPI = "909232B3D1EDCB5C"; + + private static final String IKE_NONCE_INIT_HEX_STRING = + "C39B7F368F4681B89FA9B7BE6465ABD7C5F68B6ED5D3B4C72CB4240EB5C46412"; + private static final String IKE_NONCE_RESP_HEX_STRING = + "9756112CA539F5C25ABACC7EE92B73091942A9C06950F98848F1AF1694C4DDFF"; + + private static final String IKE_SHARED_DH_KEY_HEX_STRING = + "C14155DEA40056BD9C76FB4819687B7A397582F4CD5AFF4B" + + "8F441C56E0C08C84234147A0BA249A555835A048E3CA2980" + + "7D057A61DD26EEFAD9AF9C01497005E52858E29FB42EB849" + + "6731DF96A11CCE1F51137A9A1B900FA81AEE7898E373D4E4" + + "8B899BBECA091314ECD4B6E412EF4B0FEF798F54735F3180" + + "7424A318287F20E8"; + + private static final String IKE_SKEYSEED_HEX_STRING = + "8C42F3B1F5F81C7BAAC5F33E9A4F01987B2F9657"; + private static final String IKE_SK_D_HEX_STRING = "C86B56EFCF684DCC2877578AEF3137167FE0EBF6"; + private static final String IKE_SK_AUTH_INIT_HEX_STRING = + "554FBF5A05B7F511E05A30CE23D874DB9EF55E51"; + private static final String IKE_SK_AUTH_RESP_HEX_STRING = + "36D83420788337CA32ECAA46892C48808DCD58B1"; + private static final String IKE_SK_ENCR_INIT_HEX_STRING = "5CBFD33F75796C0188C4A3A546AEC4A1"; + private static final String IKE_SK_ENCR_RESP_HEX_STRING = "C33B35FCF29514CD9D8B4A695E1A816E"; + private static final String IKE_SK_PRF_INIT_HEX_STRING = + "094787780EE466E2CB049FA327B43908BC57E485"; + private static final String IKE_SK_PRF_RESP_HEX_STRING = + "A30E6B08BE56C0E6BFF4744143C75219299E1BEB"; + private static final String IKE_KEY_MAT = + IKE_SK_D_HEX_STRING + + IKE_SK_AUTH_INIT_HEX_STRING + + IKE_SK_AUTH_RESP_HEX_STRING + + IKE_SK_ENCR_INIT_HEX_STRING + + IKE_SK_ENCR_RESP_HEX_STRING + + IKE_SK_PRF_INIT_HEX_STRING + + IKE_SK_PRF_RESP_HEX_STRING; + + private static final int IKE_AUTH_ALGO_KEY_LEN = 20; + private static final int IKE_ENCR_ALGO_KEY_LEN = 16; + private static final int IKE_PRF_KEY_LEN = 20; + private static final int IKE_SK_D_KEY_LEN = IKE_PRF_KEY_LEN; + + private static final String FIRST_CHILD_ENCR_INIT_HEX_STRING = + "1B865CEA6E2C23973E8C5452ADC5CD7D"; + private static final String FIRST_CHILD_ENCR_RESP_HEX_STRING = + "5E82FEDACC6DCB0756DDD7553907EBD1"; + private static final String FIRST_CHILD_AUTH_INIT_HEX_STRING = + "A7A5A44F7EF4409657206C7DC52B7E692593B51E"; + private static final String FIRST_CHILD_AUTH_RESP_HEX_STRING = + "CDE612189FD46DE870FAEC04F92B40B0BFDBD9E1"; + private static final String FIRST_CHILD_KEY_MAT = + FIRST_CHILD_ENCR_INIT_HEX_STRING + + FIRST_CHILD_AUTH_INIT_HEX_STRING + + FIRST_CHILD_ENCR_RESP_HEX_STRING + + FIRST_CHILD_AUTH_RESP_HEX_STRING; + + private static final int FIRST_CHILD_AUTH_ALGO_KEY_LEN = 20; + private static final int FIRST_CHILD_ENCR_ALGO_KEY_LEN = 16; + + private IkeMacPrf mIkeHmacSha1Prf; + + @Before + public void setUp() throws Exception { + mIkeHmacSha1Prf = + IkeMacPrf.create( + new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1), + IkeMessage.getSecurityProvider()); + } + + @Test + public void testsignBytes() throws Exception { + byte[] skpBytes = TestUtils.hexStringToByteArray(PRF_KEY_HEX_STRING); + byte[] dataBytes = TestUtils.hexStringToByteArray(DATA_TO_SIGN_HEX_STRING); + + byte[] calculatedBytes = mIkeHmacSha1Prf.signBytes(skpBytes, dataBytes); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(CALCULATED_MAC_HEX_STRING); + assertArrayEquals(expectedBytes, calculatedBytes); + } + + @Test + public void testGenerateSKeySeed() throws Exception { + byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING); + byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING); + byte[] sharedDhKey = TestUtils.hexStringToByteArray(IKE_SHARED_DH_KEY_HEX_STRING); + + byte[] calculatedSKeySeed = + mIkeHmacSha1Prf.generateSKeySeed(nonceInit, nonceResp, sharedDhKey); + + byte[] expectedSKeySeed = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING); + assertArrayEquals(expectedSKeySeed, calculatedSKeySeed); + } + + @Test + public void testGenerateRekeyedSKeySeed() throws Exception { + byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING); + byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING); + byte[] sharedDhKey = TestUtils.hexStringToByteArray(IKE_SHARED_DH_KEY_HEX_STRING); + byte[] old_skd = TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING); + + byte[] calculatedSKeySeed = + mIkeHmacSha1Prf.generateRekeyedSKeySeed(old_skd, nonceInit, nonceResp, sharedDhKey); + + // Verify that the new sKeySeed is different. + // TODO: Find actual test vectors to test positive case. + byte[] oldSKeySeed = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING); + assertFalse(Arrays.equals(oldSKeySeed, calculatedSKeySeed)); + } + + @Test + public void testGenerateKeyMatForIke() throws Exception { + byte[] prfKey = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING); + byte[] prfData = + TestUtils.hexStringToByteArray( + IKE_NONCE_INIT_HEX_STRING + + IKE_NONCE_RESP_HEX_STRING + + IKE_INIT_SPI + + IKE_RESP_SPI); + int keyMaterialLen = + IKE_SK_D_KEY_LEN + + IKE_AUTH_ALGO_KEY_LEN * 2 + + IKE_ENCR_ALGO_KEY_LEN * 2 + + IKE_PRF_KEY_LEN * 2; + + byte[] calculatedKeyMat = mIkeHmacSha1Prf.generateKeyMat(prfKey, prfData, keyMaterialLen); + + byte[] expectedKeyMat = TestUtils.hexStringToByteArray(IKE_KEY_MAT); + assertArrayEquals(expectedKeyMat, calculatedKeyMat); + } + + @Test + public void testGenerateKeyMatForFirstChild() throws Exception { + byte[] prfKey = TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING); + byte[] prfData = + TestUtils.hexStringToByteArray( + IKE_NONCE_INIT_HEX_STRING + IKE_NONCE_RESP_HEX_STRING); + int keyMaterialLen = FIRST_CHILD_AUTH_ALGO_KEY_LEN * 2 + FIRST_CHILD_ENCR_ALGO_KEY_LEN * 2; + + byte[] calculatedKeyMat = mIkeHmacSha1Prf.generateKeyMat(prfKey, prfData, keyMaterialLen); + + byte[] expectedKeyMat = TestUtils.hexStringToByteArray(FIRST_CHILD_KEY_MAT); + assertArrayEquals(expectedKeyMat, calculatedKeyMat); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeNormalModeCipherTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeNormalModeCipherTest.java new file mode 100644 index 00000000..3f3a0e10 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeNormalModeCipherTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.crypto; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.IpSecAlgorithm; +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; + +import javax.crypto.IllegalBlockSizeException; + +@RunWith(JUnit4.class) +public final class IkeNormalModeCipherTest { + private static final String IKE_AUTH_INIT_REQUEST_IV = "b9132b7bb9f658dfdc648e5017a6322a"; + private static final String IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA = + "030c316ce55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58" + + "46de333ecd3ea2b705d18293b130395300ba92a351041345" + + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f" + + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60" + + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b" + + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423" + + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3" + + "59eb4e81"; + private static final String IKE_AUTH_INIT_REQUEST_UNENCRYPTED_PADDED_DATA = + "2400000c010000000a50500d2700000c010000000a505050" + + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327" + + "44dfb2c12c00002c00000028010304032ad4c0a20300000c" + + "0100000c800e008003000008030000020000000805000000" + + "2d00001801000000070000100000ffff00000000ffffffff" + + "2900001801000000070000100000ffff00000000ffffffff" + + "29000008000040000000000c000040010000000100000000" + + "000000000000000b"; + + private static final String ENCR_KEY_FROM_INIT_TO_RESP = "5cbfd33f75796c0188c4a3a546aec4a1"; + + private static final int AES_BLOCK_SIZE = 16; + + private IkeNormalModeCipher mAesCbcCipher; + private byte[] mAesCbcKey; + + private byte[] mIv; + private byte[] mEncryptedPaddedData; + private byte[] mUnencryptedPaddedData; + + @Before + public void setUp() throws Exception { + mAesCbcCipher = + (IkeNormalModeCipher) + IkeCipher.create( + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, + SaProposal.KEY_LEN_AES_128), + IkeMessage.getSecurityProvider()); + mAesCbcKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP); + + mIv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV); + mEncryptedPaddedData = + TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA); + mUnencryptedPaddedData = + TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_PADDED_DATA); + } + + @Test + public void testBuild() throws Exception { + assertFalse(mAesCbcCipher.isAead()); + assertEquals(AES_BLOCK_SIZE, mAesCbcCipher.getBlockSize()); + assertEquals(AES_BLOCK_SIZE, mAesCbcCipher.generateIv().length); + } + + @Test + public void testGenerateRandomIv() throws Exception { + assertFalse(Arrays.equals(mAesCbcCipher.generateIv(), mAesCbcCipher.generateIv())); + } + + @Test + public void testEncryptWithNormalCipher() throws Exception { + byte[] calculatedData = mAesCbcCipher.encrypt(mUnencryptedPaddedData, mAesCbcKey, mIv); + + assertArrayEquals(mEncryptedPaddedData, calculatedData); + } + + @Test + public void testDecryptWithNormalCipher() throws Exception { + byte[] calculatedData = mAesCbcCipher.decrypt(mEncryptedPaddedData, mAesCbcKey, mIv); + assertArrayEquals(mUnencryptedPaddedData, calculatedData); + } + + @Test + public void testEncryptWithWrongKey() throws Exception { + byte[] encryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP + "00"); + + try { + mAesCbcCipher.encrypt(mEncryptedPaddedData, encryptionKey, mIv); + fail("Expected to fail due to encryption key with wrong length."); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testDecryptWithNormalCipherWithBadPad() throws Exception { + byte[] dataToDecrypt = + TestUtils.hexStringToByteArray( + IKE_AUTH_INIT_REQUEST_UNENCRYPTED_PADDED_DATA + "00"); + try { + mAesCbcCipher.decrypt(dataToDecrypt, mAesCbcKey, mIv); + fail("Expected to fail when try to decrypt data with bad padding"); + } catch (IllegalBlockSizeException expected) { + + } + } + + @Test + public void testBuildIpSecAlgorithm() throws Exception { + IpSecAlgorithm ipsecAlgorithm = mAesCbcCipher.buildIpSecAlgorithmWithKey(mAesCbcKey); + + IpSecAlgorithm expectedIpSecAlgorithm = + new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, mAesCbcKey); + + assertTrue(IpSecAlgorithm.equals(expectedIpSecAlgorithm, ipsecAlgorithm)); + } + + @Test + public void buildIpSecAlgorithmWithInvalidKey() throws Exception { + byte[] encryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP + "00"); + + try { + mAesCbcCipher.buildIpSecAlgorithmWithKey(encryptionKey); + + fail("Expected to fail due to encryption key with wrong length."); + } catch (IllegalArgumentException expected) { + + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthDigitalSignPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthDigitalSignPayloadTest.java new file mode 100644 index 00000000..96921c30 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthDigitalSignPayloadTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static com.android.internal.net.ipsec.ike.message.IkeAuthDigitalSignPayload.SIGNATURE_ALGO_RSA_SHA2_256; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf; +import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; +import com.android.internal.net.ipsec.ike.testutils.CertUtils; + +import org.junit.Before; +import org.junit.Test; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +public final class IkeAuthDigitalSignPayloadTest { + // TODO: Build a RSA_SHA1 signature and add tests for it. + + // RSA_SHA2_256 + private static final String AUTH_PAYLOAD_BODY_GENERIC_DIGITAL_SIGN_HEX_STRING = + "0e0000000f300d06092a864886f70d01010b05006f76af4150d653c5d4136b9f" + + "69d905849bf075c563e6d14ccda42361ec3e7d12c72e2dece5711ea1d952f7b8" + + "e12c5d982aa4efdaeac36a02b222aa96242cc424"; + private static final String SIGNATURE = + "6f76af4150d653c5d4136b9f69d905849bf075c563e6d14ccda42361ec3e7d12" + + "c72e2dece5711ea1d952f7b8e12c5d982aa4efdaeac36a02b222aa96242cc424"; + + private static final String IKE_INIT_RESP_HEX_STRING = + "02458497587b09d488d5b76480bce53d2120222000000000000001cc2200002c" + + "00000028010100040300000801000003030000080300000203000008020000020" + + "00000080400000e28000108000e000013d60e51c40922cb121e395bacbd627cdd" + + "d3240baa4fcefd29f65f8dd37329d68d4fb4854f8b8f07cfb60900e276d99a396" + + "1112ee866b5456cf588dc1092fd3bc19668fb8fa42872f51c0ee748bdb665dcbe" + + "15ac454f6ed966149954dac5187638d1ab61869d97a4873c4733c48cbe3acc8a6" + + "5cfea3ce83fd09fba174bf0ec56d73a0585859399e61c2c38e695841f8df8a511" + + "aadd438f56634165ad9b88e858c1585f1bee646943b8a96f5397721079a127b87" + + "fd286e8f869ae021ce82adf91fa360217ac32268b39b698bf06a4e89b8d0267af" + + "1c5b979b6493adb10a0e14aa707309e914b8d377903e75cb13cffbfde9c26842f" + + "b49a07a4497c9907d39515b290000244b8aed6297c09a5a0dda06c873f5573b34" + + "886dd779e90c19beca3fc54ab3cae02900001c00004004d8e7cb9d1e689ae8c84" + + "c5078355436f3347376ff2900001c0000400545bc3f2113770de91c769094f1bd" + + "614534e765ea290000080000402e290000100000402f000100020003000400000" + + "00800004014"; + private static final String NONCE_INIT_HEX_STRING = + "a5dded450b5ffd2670f37954367fce28279a085c830a03358b10b0872c0578f9"; + private static final String ID_RESP_PAYLOAD_BODY_HEX_STRING = "01000000c0a82b8a"; + private static final String SKP_RESP_HEX_STRING = "8FE8EC3153EDE924C23D6630D3C992A494E2F256"; + + private static final byte[] IKE_INIT_RESP_REQUEST = + TestUtils.hexStringToByteArray(IKE_INIT_RESP_HEX_STRING); + private static final byte[] NONCE_INIT_RESP = + TestUtils.hexStringToByteArray(NONCE_INIT_HEX_STRING); + private static final byte[] ID_RESP_PAYLOAD_BODY = + TestUtils.hexStringToByteArray(ID_RESP_PAYLOAD_BODY_HEX_STRING); + private static final byte[] PRF_RESP_KEY = TestUtils.hexStringToByteArray(SKP_RESP_HEX_STRING); + + private IkeMacPrf mIkeHmacSha1Prf; + + @Before + public void setUp() throws Exception { + mIkeHmacSha1Prf = + IkeMacPrf.create( + new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1), + IkeMessage.getSecurityProvider()); + } + + @Test + public void testDecodeGenericDigitalSignPayload() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(AUTH_PAYLOAD_BODY_GENERIC_DIGITAL_SIGN_HEX_STRING); + IkeAuthPayload payload = IkeAuthPayload.getIkeAuthPayload(false, inputPacket); + + assertTrue(payload instanceof IkeAuthDigitalSignPayload); + IkeAuthDigitalSignPayload dsPayload = (IkeAuthDigitalSignPayload) payload; + assertEquals(SIGNATURE_ALGO_RSA_SHA2_256, dsPayload.signatureAlgoAndHash); + assertArrayEquals(dsPayload.signature, TestUtils.hexStringToByteArray(SIGNATURE)); + } + + @Test + public void testVerifyInboundSignature() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(AUTH_PAYLOAD_BODY_GENERIC_DIGITAL_SIGN_HEX_STRING); + IkeAuthDigitalSignPayload payload = + (IkeAuthDigitalSignPayload) IkeAuthPayload.getIkeAuthPayload(false, inputPacket); + + X509Certificate cert = CertUtils.createCertFromPemFile("end-cert-small.pem"); + + payload.verifyInboundSignature( + cert, + IKE_INIT_RESP_REQUEST, + NONCE_INIT_RESP, + ID_RESP_PAYLOAD_BODY, + mIkeHmacSha1Prf, + PRF_RESP_KEY); + } + + @Test + public void testVerifyInboundSignatureFail() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(AUTH_PAYLOAD_BODY_GENERIC_DIGITAL_SIGN_HEX_STRING); + IkeAuthDigitalSignPayload payload = + (IkeAuthDigitalSignPayload) IkeAuthPayload.getIkeAuthPayload(false, inputPacket); + + assertArrayEquals(payload.signature, TestUtils.hexStringToByteArray(SIGNATURE)); + X509Certificate cert = CertUtils.createCertFromPemFile("end-cert-a.pem"); + + try { + payload.verifyInboundSignature( + cert, + IKE_INIT_RESP_REQUEST, + NONCE_INIT_RESP, + ID_RESP_PAYLOAD_BODY, + mIkeHmacSha1Prf, + PRF_RESP_KEY); + fail("Expected to fail due to wrong certificate."); + } catch (AuthenticationFailedException expected) { + } + } + + @Test + public void testGenerateSignature() throws Exception { + PrivateKey key = CertUtils.createRsaPrivateKeyFromKeyFile("end-cert-key-a.key"); + + IkeAuthDigitalSignPayload authPayload = + new IkeAuthDigitalSignPayload( + SIGNATURE_ALGO_RSA_SHA2_256, + key, + IKE_INIT_RESP_REQUEST, + NONCE_INIT_RESP, + ID_RESP_PAYLOAD_BODY, + mIkeHmacSha1Prf, + PRF_RESP_KEY); + + assertEquals(SIGNATURE_ALGO_RSA_SHA2_256, authPayload.signatureAlgoAndHash); + assertArrayEquals(authPayload.signature, TestUtils.hexStringToByteArray(SIGNATURE)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPayloadTest.java new file mode 100644 index 00000000..989b4689 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPayloadTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf; +import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; + +import org.junit.Before; +import org.junit.Test; + +public final class IkeAuthPayloadTest { + private static final String PSK_AUTH_PAYLOAD_HEX_STRING = + "02000000df7c038aefaaa32d3f44b228b52a332744dfb2c1"; + private static final String PSK_AUTH_PAYLOAD_SIGNATURE_HEX_STRING = + "df7c038aefaaa32d3f44b228b52a332744dfb2c1"; + private static final String PSK_ID_PAYLOAD_HEX_STRING = "010000000a50500d"; + private static final String PSK_SKP_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485"; + private static final String PSK_SIGNED_OCTETS_APPENDIX_HEX_STRING = + "D83B20CC6A0932B2A7CEF26E4020ABAAB64F0C6A"; + private static final String PSK_IKE_INIT_REQUEST_HEX_STRING = + "5f54bf6d8b48e6e1000000000000000021202208" + + "0000000000000150220000300000002c01010004" + + "0300000c0100000c800e00800300000803000002" + + "0300000804000002000000080200000228000088" + + "00020000b4a2faf4bb54878ae21d638512ece55d" + + "9236fc5046ab6cef82220f421f3ce6361faf3656" + + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9" + + "ece8ac37534036040610ebdd92f46bef84f0be7d" + + "b860351843858f8acf87056e272377f70c9f2d81" + + "e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2" + + "6bbeb08214c707137607958729000024c39b7f36" + + "8f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7" + + "2cb4240eb5c464122900001c00004004e54f73b7" + + "d83f6beb881eab2051d8663f421d10b02b00001c" + + "00004005d915368ca036004cb578ae3e3fb26850" + + "9aeab19000000020699369228741c6d4ca094c93" + + "e242c9de19e7b7c60000000500000500"; + private static final String PSK_NONCE_RESP_HEX_STRING = + "9756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff"; + private static final String PSK_INIT_SIGNED_OCTETS = + "5F54BF6D8B48E6E1000000000000000021202208" + + "0000000000000150220000300000002C01010004" + + "0300000C0100000C800E00800300000803000002" + + "0300000804000002000000080200000228000088" + + "00020000B4A2FAF4BB54878AE21D638512ECE55D" + + "9236FC5046AB6CEF82220F421F3CE6361FAF3656" + + "4ECB6D28798A94AAD7B2B4B603DDEAAA5630ADB9" + + "ECE8AC37534036040610EBDD92F46BEF84F0BE7D" + + "B860351843858F8ACF87056E272377F70C9F2D81" + + "E29C7B0CE4F291A3A72476BB0B278FD4B7B0A4C2" + + "6BBEB08214C707137607958729000024C39B7F36" + + "8F4681B89FA9B7BE6465ABD7C5F68B6ED5D3B4C7" + + "2CB4240EB5C464122900001C00004004E54F73B7" + + "D83F6BEB881EAB2051D8663F421D10B02B00001C" + + "00004005D915368CA036004CB578AE3E3FB26850" + + "9AEAB19000000020699369228741C6D4CA094C93" + + "E242C9DE19E7B7C600000005000005009756112C" + + "A539F5C25ABACC7EE92B73091942A9C06950F988" + + "48F1AF1694C4DDFFD83B20CC6A0932B2A7CEF26E" + + "4020ABAAB64F0C6A"; + + private static final int AUTH_METHOD_POSITION = 0; + + private IkeMacPrf mIkeHmacSha1Prf; + + @Before + public void setUp() throws Exception { + mIkeHmacSha1Prf = + IkeMacPrf.create( + new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1), + IkeMessage.getSecurityProvider()); + } + + @Test + public void testDecodeIkeAuthPayload() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_HEX_STRING); + IkeAuthPayload payload = IkeAuthPayload.getIkeAuthPayload(false, inputPacket); + + assertEquals(IkeAuthPayload.AUTH_METHOD_PRE_SHARED_KEY, payload.authMethod); + assertTrue(payload instanceof IkeAuthPskPayload); + + byte[] expectedSignature = + TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_SIGNATURE_HEX_STRING); + assertArrayEquals(expectedSignature, ((IkeAuthPskPayload) payload).signature); + } + + @Test + public void testDecodeIkeAuthPayloadWithUnsupportedMethod() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_HEX_STRING); + inputPacket[AUTH_METHOD_POSITION] = 0; + try { + IkeAuthPayload payload = IkeAuthPayload.getIkeAuthPayload(false, inputPacket); + fail("Expected Exception: authentication method is not supported"); + } catch (AuthenticationFailedException e) { + } + } + + @Test + public void testGetSignedOctets() throws Exception { + byte[] skpBytes = TestUtils.hexStringToByteArray(PSK_SKP_HEX_STRING); + byte[] idBytes = TestUtils.hexStringToByteArray(PSK_ID_PAYLOAD_HEX_STRING); + byte[] ikeInitRequest = TestUtils.hexStringToByteArray(PSK_IKE_INIT_REQUEST_HEX_STRING); + byte[] nonceResp = TestUtils.hexStringToByteArray(PSK_NONCE_RESP_HEX_STRING); + + byte[] calculatedBytes = + IkeAuthPayload.getSignedOctets( + ikeInitRequest, nonceResp, idBytes, mIkeHmacSha1Prf, skpBytes); + byte[] expectedBytes = TestUtils.hexStringToByteArray(PSK_INIT_SIGNED_OCTETS); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPskPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPskPayloadTest.java new file mode 100644 index 00000000..cad60522 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPskPayloadTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf; +import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public final class IkeAuthPskPayloadTest { + private static final String PSK_AUTH_PAYLOAD_HEX_STRING = + "2100001c02000000df7c038aefaaa32d3f44b228b52a332744dfb2c1"; + private static final String PSK_AUTH_PAYLOAD_BODY_HEX_STRING = + "02000000df7c038aefaaa32d3f44b228b52a332744dfb2c1"; + private static final String PSK_AUTH_PAYLOAD_SIGNATURE_HEX_STRING = + "df7c038aefaaa32d3f44b228b52a332744dfb2c1"; + + private static final String PSK_IKE_INIT_REQUEST_HEX_STRING = + "5f54bf6d8b48e6e1000000000000000021202208" + + "0000000000000150220000300000002c01010004" + + "0300000c0100000c800e00800300000803000002" + + "0300000804000002000000080200000228000088" + + "00020000b4a2faf4bb54878ae21d638512ece55d" + + "9236fc5046ab6cef82220f421f3ce6361faf3656" + + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9" + + "ece8ac37534036040610ebdd92f46bef84f0be7d" + + "b860351843858f8acf87056e272377f70c9f2d81" + + "e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2" + + "6bbeb08214c707137607958729000024c39b7f36" + + "8f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7" + + "2cb4240eb5c464122900001c00004004e54f73b7" + + "d83f6beb881eab2051d8663f421d10b02b00001c" + + "00004005d915368ca036004cb578ae3e3fb26850" + + "9aeab19000000020699369228741c6d4ca094c93" + + "e242c9de19e7b7c60000000500000500"; + private static final String PSK_NONCE_RESP_HEX_STRING = + "9756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff"; + private static final String PSK_ID_INITIATOR_PAYLOAD_HEX_STRING = "010000000a50500d"; + + private static final String PSK_HEX_STRING = "6A756E69706572313233"; + private static final String PSK_SKP_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485"; + + private static final byte[] PSK = TestUtils.hexStringToByteArray(PSK_HEX_STRING); + private static final byte[] IKE_INIT_REQUEST = + TestUtils.hexStringToByteArray(PSK_IKE_INIT_REQUEST_HEX_STRING); + private static final byte[] NONCE = TestUtils.hexStringToByteArray(PSK_NONCE_RESP_HEX_STRING); + private static final byte[] ID_PAYLOAD_BODY = + TestUtils.hexStringToByteArray(PSK_ID_INITIATOR_PAYLOAD_HEX_STRING); + private static final byte[] PRF_KEY = TestUtils.hexStringToByteArray(PSK_SKP_HEX_STRING); + private static final byte[] SIGNATURE = + TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_SIGNATURE_HEX_STRING); + + private IkeMacPrf mIkeHmacSha1Prf; + + @Before + public void setUp() throws Exception { + mIkeHmacSha1Prf = + IkeMacPrf.create( + new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1), + IkeMessage.getSecurityProvider()); + } + + @Test + public void testBuildOutboundIkeAuthPskPayload() throws Exception { + IkeAuthPskPayload payload = + new IkeAuthPskPayload( + PSK, IKE_INIT_REQUEST, NONCE, ID_PAYLOAD_BODY, mIkeHmacSha1Prf, PRF_KEY); + + assertEquals(IkeAuthPayload.AUTH_METHOD_PRE_SHARED_KEY, payload.authMethod); + assertArrayEquals(SIGNATURE, payload.signature); + + // Verify payload length + int payloadLength = payload.getPayloadLength(); + byte[] expectedPayload = TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_HEX_STRING); + assertEquals(expectedPayload.length, payloadLength); + + // Verify encoding + ByteBuffer byteBuffer = ByteBuffer.allocate(payloadLength); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_SA, byteBuffer); + assertArrayEquals(expectedPayload, byteBuffer.array()); + } + + private IkeAuthPskPayload buildPskPayload() throws Exception { + byte[] payloadBody = TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_BODY_HEX_STRING); + IkeAuthPskPayload pskPayload = + (IkeAuthPskPayload) IkeAuthPayload.getIkeAuthPayload(false, payloadBody); + return pskPayload; + } + + @Test + public void testDecodeIkeAuthPskPayload() throws Exception { + IkeAuthPskPayload pskPayload = buildPskPayload(); + + assertArrayEquals(SIGNATURE, pskPayload.signature); + } + + @Test + public void testVerifyReceivedSignature() throws Exception { + IkeAuthPskPayload pskPayload = buildPskPayload(); + + pskPayload.verifyInboundSignature( + PSK, IKE_INIT_REQUEST, NONCE, ID_PAYLOAD_BODY, mIkeHmacSha1Prf, PRF_KEY); + } + + @Test + public void testVerifyReceivedSignatureFailure() throws Exception { + IkeAuthPskPayload pskPayload = buildPskPayload(); + byte[] nonce = Arrays.copyOf(NONCE, NONCE.length); + nonce[0]++; + + try { + pskPayload.verifyInboundSignature( + PSK, IKE_INIT_REQUEST, nonce, ID_PAYLOAD_BODY, mIkeHmacSha1Prf, PRF_KEY); + fail("Expected signature verification to have failed due to mismatched signatures."); + } catch (AuthenticationFailedException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertPayloadTest.java new file mode 100644 index 00000000..2bb72e33 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertPayloadTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.fail; + +import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; +import com.android.internal.net.ipsec.ike.testutils.CertUtils; + +import org.junit.Before; +import org.junit.Test; + +import java.security.cert.TrustAnchor; +import java.security.cert.X509Certificate; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +public final class IkeCertPayloadTest { + private X509Certificate mEndCertA; + private X509Certificate mEndCertB; + private X509Certificate mEndCertSmall; + + private X509Certificate mIntermediateCertBOne; + private X509Certificate mIntermediateCertBTwo; + + private TrustAnchor mTrustAnchorA; + private TrustAnchor mTrustAnchorB; + private TrustAnchor mTrustAnchorSmall; + + @Before + public void setUp() throws Exception { + mEndCertA = CertUtils.createCertFromPemFile("end-cert-a.pem"); + mTrustAnchorA = + new TrustAnchor( + CertUtils.createCertFromPemFile("self-signed-ca-a.pem"), + null /*nameConstraints*/); + + mEndCertB = CertUtils.createCertFromPemFile("end-cert-b.pem"); + mIntermediateCertBOne = CertUtils.createCertFromPemFile("intermediate-ca-b-one.pem"); + mIntermediateCertBTwo = CertUtils.createCertFromPemFile("intermediate-ca-b-two.pem"); + mTrustAnchorB = + new TrustAnchor( + CertUtils.createCertFromPemFile("self-signed-ca-b.pem"), + null /*nameConstraints*/); + + mEndCertSmall = CertUtils.createCertFromPemFile("end-cert-small.pem"); + mTrustAnchorSmall = + new TrustAnchor( + CertUtils.createCertFromPemFile("self-signed-ca-small.pem"), + null /*nameConstraints*/); + } + + @Test + public void testValidateCertsNoIntermediateCerts() throws Exception { + List<X509Certificate> certList = new LinkedList<>(); + certList.add(mEndCertA); + + Set<TrustAnchor> trustAnchors = new HashSet<>(); + trustAnchors.add(mTrustAnchorA); + + IkeCertPayload.validateCertificates(mEndCertA, certList, null /*crlList*/, trustAnchors); + } + + @Test + public void testValidateCertsWithIntermediateCerts() throws Exception { + List<X509Certificate> certList = new LinkedList<>(); + + certList.add(mEndCertB); + certList.add(mIntermediateCertBTwo); + certList.add(mIntermediateCertBOne); + + Set<TrustAnchor> trustAnchors = new HashSet<>(); + trustAnchors.add(mTrustAnchorB); + + IkeCertPayload.validateCertificates(mEndCertB, certList, null /*crlList*/, trustAnchors); + } + + @Test + public void testValidateCertsWithMultiTrustAnchors() throws Exception { + List<X509Certificate> certList = new LinkedList<>(); + certList.add(mEndCertA); + + Set<TrustAnchor> trustAnchors = new HashSet<>(); + trustAnchors.add(mTrustAnchorA); + trustAnchors.add(mTrustAnchorB); + + IkeCertPayload.validateCertificates(mEndCertA, certList, null /*crlList*/, trustAnchors); + } + + @Test + public void testValidateCertsWithWrongTrustAnchor() throws Exception { + List<X509Certificate> certList = new LinkedList<>(); + certList.add(mEndCertA); + + Set<TrustAnchor> trustAnchors = new HashSet<>(); + trustAnchors.add(mTrustAnchorB); + + try { + IkeCertPayload.validateCertificates( + mEndCertA, certList, null /*crlList*/, trustAnchors); + fail("Expected to fail due to absence of valid trust anchor."); + } catch (AuthenticationFailedException expected) { + } + } + + @Test + public void testValidateCertsWithMissingIntermediateCerts() throws Exception { + List<X509Certificate> certList = new LinkedList<>(); + certList.add(mEndCertB); + certList.add(mIntermediateCertBOne); + + Set<TrustAnchor> trustAnchors = new HashSet<>(); + trustAnchors.add(mTrustAnchorB); + + try { + IkeCertPayload.validateCertificates( + mEndCertA, certList, null /*crlList*/, trustAnchors); + fail("Expected to fail due to absence of intermediate certificate."); + } catch (AuthenticationFailedException expected) { + } + } + + @Test + public void testValidateCertsWithSmallSizeKey() throws Exception { + List<X509Certificate> certList = new LinkedList<>(); + certList.add(mEndCertSmall); + + Set<TrustAnchor> trustAnchors = new HashSet<>(); + trustAnchors.add(mTrustAnchorSmall); + + try { + IkeCertPayload.validateCertificates( + mEndCertSmall, certList, null /*crlList*/, trustAnchors); + fail("Expected to fail because certificates use small size key"); + } catch (AuthenticationFailedException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertX509CertPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertX509CertPayloadTest.java new file mode 100644 index 00000000..ef6ec289 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertX509CertPayloadTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Base64; + +public final class IkeCertX509CertPayloadTest { + private static final String CERT_PAYLOAD_BODY_HEX_STRING = + "043082042130820209a003020102020827a2b30cdd5043ab300d06092a864886" + + "f70d01010c05003035310b3009060355040613024e5a310e300c060355040a13" + + "054576697461311630140603550403130d457669746120526f6f74204341301e" + + "170d3138313033303139303034305a170d3230313032393139303034305a3036" + + "310b3009060355040613024e5a310e300c060355040a13054576697461311730" + + "150603550403130e3139322e3136382e34332e31303330820122300d06092a86" + + "4886f70d01010105000382010f003082010a0282010100b15dd29d25ef546532" + + "2b85b2db5aefbff85b2f8e90a67c5ce48a7baee696d645602b1122501e10aec4" + + "7733a875a6f02432a4d684e67bdab36549996f3a2fc919cf769dc3a3b270791a" + + "3682c868225e5478df1d2467b1afafd608db2696c484a0134f8069f9ed0b55fc" + + "656b15fef6b9b151debfd1d96f6bba0f032432ec6497573d47f3539b4754f33a" + + "89ea4d3986ad33137a750b36073fe50188c169a851c092506ea383ceb58b55ad" + + "f55956e037118f2f747be6dc3f0f741ec1d658defcd9197b8750a75ae9c1b1c7" + + "2f2ff91f561d1b3d77d54037e8cfe3738668a3de42cf7eab21353ae322e40eb4" + + "8dcf38eb34572118315047133f7727cab46f10f9f70de30203010001a3343032" + + "301f0603551d23041830168014fbcb6762ffde82b4d63d069beeac583228ab32" + + "25300f0603551d11040830068704c0a82b67300d06092a864886f70d01010c05" + + "0003820201006f26b5973346030b7297d50e3ce4cd8ca5b1527fd9045b4bb540" + + "a7cd0193e16732346d6fb99c2058a17f0129460ba6e4832b6b4a6c51731ceea4" + + "5464e0f179b860b3f0bf10956b1f4bae8cecdfc08261a3c00e8aa0ddbbe9ca08" + + "6a954ec9e3bcb0ea907e617e98a97ec1c2b7c2190b10a8117b12e56640830c1f" + + "c6492a8f7062e92b7e2d03f22173fb333b9a45d96c9842fc16c0c6658428fbdf" + + "6351be6fe65f7e1f329d7a07dad617837208e72539774ef46a72bc8e279fca74" + + "d68fbfba91de8d62331c2df135ce234b007afc330f96d3ee80888ba102334c32" + + "2d25a78d26a2c9434fafdfa9ea35acadf4938a69623de5ee966d1d550d851df3" + + "d99b477f804a6eb2d8c16f80161a9511674bfa514f5d127c856ca6c0dff07eba" + + "87672e2154c4fd0f3b906ff888978315a041b3fdbc7cf8e2306e0b20b3a79d2b" + + "5a59f7f6b8e784af57d43be4be37f9381a6ff6c3fa477fed151d586c42634e0a" + + "88e699a9f3b38459589ea014d549b7ed7fd551bd544d464d955476ed1c051fa1" + + "a7351d5d4f13efe232bc847a245c85a4a04abf66abd7d983b254a67d0189206c" + + "8fc8989f38e63bd827552e209a2aa119d0622f0defe08cef0bf48a3459c09ad9" + + "8f729b51bb57f2518385abd790ff3d80d1cdce1218f61ea45c0c6fc9814c300d" + + "abc24a747560744e9861c9395dd2f849b4d1196fe302ac8a063afeea9bc9d637" + + "2fedb79130bb"; + + private static final String CLIENT_END_CERTIFICATE = + "MIIEITCCAgmgAwIBAgIIJ6KzDN1QQ6swDQYJKoZIhvcNAQEMBQAwNTELMAkGA1UE" + + "BhMCTloxDjAMBgNVBAoTBUV2aXRhMRYwFAYDVQQDEw1Fdml0YSBSb290IENBMB4X" + + "DTE4MTAzMDE5MDA0MFoXDTIwMTAyOTE5MDA0MFowNjELMAkGA1UEBhMCTloxDjAM" + + "BgNVBAoTBUV2aXRhMRcwFQYDVQQDEw4xOTIuMTY4LjQzLjEwMzCCASIwDQYJKoZI" + + "hvcNAQEBBQADggEPADCCAQoCggEBALFd0p0l71RlMiuFstta77/4Wy+OkKZ8XOSK" + + "e67mltZFYCsRIlAeEK7EdzOodabwJDKk1oTme9qzZUmZbzovyRnPdp3Do7JweRo2" + + "gshoIl5UeN8dJGexr6/WCNsmlsSEoBNPgGn57QtV/GVrFf72ubFR3r/R2W9rug8D" + + "JDLsZJdXPUfzU5tHVPM6iepNOYatMxN6dQs2Bz/lAYjBaahRwJJQbqODzrWLVa31" + + "WVbgNxGPL3R75tw/D3QewdZY3vzZGXuHUKda6cGxxy8v+R9WHRs9d9VAN+jP43OG" + + "aKPeQs9+qyE1OuMi5A60jc846zRXIRgxUEcTP3cnyrRvEPn3DeMCAwEAAaM0MDIw" + + "HwYDVR0jBBgwFoAU+8tnYv/egrTWPQab7qxYMiirMiUwDwYDVR0RBAgwBocEwKgr" + + "ZzANBgkqhkiG9w0BAQwFAAOCAgEAbya1lzNGAwtyl9UOPOTNjKWxUn/ZBFtLtUCn" + + "zQGT4WcyNG1vuZwgWKF/ASlGC6bkgytrSmxRcxzupFRk4PF5uGCz8L8QlWsfS66M" + + "7N/AgmGjwA6KoN276coIapVOyeO8sOqQfmF+mKl+wcK3whkLEKgRexLlZkCDDB/G" + + "SSqPcGLpK34tA/Ihc/szO5pF2WyYQvwWwMZlhCj732NRvm/mX34fMp16B9rWF4Ny" + + "COclOXdO9GpyvI4nn8p01o+/upHejWIzHC3xNc4jSwB6/DMPltPugIiLoQIzTDIt" + + "JaeNJqLJQ0+v36nqNayt9JOKaWI95e6WbR1VDYUd89mbR3+ASm6y2MFvgBYalRFn" + + "S/pRT10SfIVspsDf8H66h2cuIVTE/Q87kG/4iJeDFaBBs/28fPjiMG4LILOnnSta" + + "Wff2uOeEr1fUO+S+N/k4Gm/2w/pHf+0VHVhsQmNOCojmmanzs4RZWJ6gFNVJt+1/" + + "1VG9VE1GTZVUdu0cBR+hpzUdXU8T7+IyvIR6JFyFpKBKv2ar19mDslSmfQGJIGyP" + + "yJifOOY72CdVLiCaKqEZ0GIvDe/gjO8L9Io0WcCa2Y9ym1G7V/JRg4Wr15D/PYDR" + + "zc4SGPYepFwMb8mBTDANq8JKdHVgdE6YYck5XdL4SbTRGW/jAqyKBjr+6pvJ1jcv" + + "7beRMLs="; + private static final int CERTIFICATE_OFFSET = 1; + + @Test + public void testDecodeX509Certificate() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(CERT_PAYLOAD_BODY_HEX_STRING); + IkeCertPayload certPayload = IkeCertPayload.getIkeCertPayload(false, inputPacket); + + assertTrue(certPayload instanceof IkeCertX509CertPayload); + X509Certificate expectedCert = pemStringToCertificate(CLIENT_END_CERTIFICATE); + assertEquals(expectedCert, ((IkeCertX509CertPayload) certPayload).certificate); + } + + @Test + public void testDecodeX509CertificateWithUnexpectedTrailing() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(CERT_PAYLOAD_BODY_HEX_STRING + "ffff"); + try { + IkeCertPayload.getIkeCertPayload(false, inputPacket); + fail("Expected AuthenticationFailedException: " + "Unexpected trailing bytes."); + } catch (AuthenticationFailedException expected) { + } + } + + @Test + public void testDecodeGetNoX509Certificate() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(CERT_PAYLOAD_BODY_HEX_STRING); + inputPacket[CERTIFICATE_OFFSET] = 0; + try { + IkeCertPayload.getIkeCertPayload(false, inputPacket); + fail("Expected AuthenticationFailedException: " + "No certificate got."); + } catch (AuthenticationFailedException expected) { + } + } + + @Test + public void testDecodeInvalidX509Certificate() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(CERT_PAYLOAD_BODY_HEX_STRING); + try { + IkeCertPayload.getIkeCertPayload( + false, Arrays.copyOfRange(inputPacket, 0, inputPacket.length - 1)); + fail("Expected AuthenticationFailedException: " + "Certificate parsing exception."); + } catch (AuthenticationFailedException expected) { + } + } + + private X509Certificate pemStringToCertificate(String certPemStr) throws Exception { + CertificateFactory factory = + CertificateFactory.getInstance("X.509", IkeMessage.getSecurityProvider()); + Base64.Decoder bs64Decoder = Base64.getDecoder(); + byte[] decodedBytes = bs64Decoder.decode(certPemStr); + return (X509Certificate) + factory.generateCertificate(new ByteArrayInputStream(decodedBytes)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayloadTest.java new file mode 100644 index 00000000..9fc32229 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayloadTest.java @@ -0,0 +1,631 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_ADDRESS; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_DHCP; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_DNS; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_NETMASK; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_SUBNET; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP6_ADDRESS; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP6_DNS; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP6_SUBNET; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_TYPE_REPLY; +import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_TYPE_REQUEST; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_CP; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.net.LinkAddress; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttrIpv4AddressBase; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttrIpv6AddrRangeBase; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttribute; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Address; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Dhcp; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Dns; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Netmask; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Subnet; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv6Address; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv6Dns; +import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv6Subnet; + +import libcore.net.InetAddressUtils; + +import org.junit.Before; +import org.junit.Test; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +public final class IkeConfigPayloadTest { + private static final String CONFIG_REQ_PAYLOAD_HEX = + "2900001801000000000100000008000000030000000a0000"; + private static final String CONFIG_RESP_PAYLOAD_HEX = + "210000200200000000010004c000026400030004080808080003000408080404"; + private static final String CONFIG_RESP_PAYLOAD_INVALID_ONE_HEX = + "210000200200000000010004c000026400020004fffffffe00020004fffffffe"; + private static final String CONFIG_RESP_PAYLOAD_INVALID_TWO_HEX = + "210000100200000000020004fffffffe"; + + private static final byte[] CONFIG_REQ_PAYLOAD = + TestUtils.hexStringToByteArray(CONFIG_REQ_PAYLOAD_HEX); + private static final byte[] CONFIG_RESP_PAYLOAD = + TestUtils.hexStringToByteArray(CONFIG_RESP_PAYLOAD_HEX); + + private static final Inet4Address IPV4_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100")); + private static final Inet4Address IPV4_NETMASK = + (Inet4Address) (InetAddressUtils.parseNumericAddress("255.255.255.240")); + private static final int IP4_PREFIX_LEN = 28; + private static final LinkAddress IPV4_LINK_ADDRESS = + new LinkAddress(IPV4_ADDRESS, IP4_PREFIX_LEN); + + private static final byte[] IPV4_ADDRESS_ATTRIBUTE_WITH_VALUE = + TestUtils.hexStringToByteArray("00010004c0000264"); + private static final byte[] IPV4_ADDRESS_ATTRIBUTE_WITHOUT_VALUE = + TestUtils.hexStringToByteArray("00010000"); + + private static final byte[] IPV4_NETMASK_ATTRIBUTE_WITHOUT_VALUE = + TestUtils.hexStringToByteArray("00020000"); + + private static final Inet4Address IPV4_DNS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("8.8.8.8")); + private static final byte[] IPV4_DNS_ATTRIBUTE_VALUE = + TestUtils.hexStringToByteArray("08080808"); + private static final byte[] IPV4_DNS_ATTRIBUTE_WITHOUT_VALUE = + TestUtils.hexStringToByteArray("00030000"); + + private static final Inet4Address IPV4_DHCP = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200")); + private static final byte[] IPV4_DHCP_ATTRIBUTE_WITH_VALUE = + TestUtils.hexStringToByteArray("00060004c00002c8"); + private static final byte[] IPV4_DHCP_ATTRIBUTE_WITHOUT_VALUE = + TestUtils.hexStringToByteArray("00060000"); + + private static final byte[] IPV4_SUBNET_ATTRIBUTE_VALUE = + TestUtils.hexStringToByteArray("c0000264fffffff0"); + private static final byte[] IPV4_SUBNET_ATTRIBUTE_WITH_VALUE = + TestUtils.hexStringToByteArray("000d0008c0000264fffffff0"); + private static final byte[] IPV4_SUBNET_ATTRIBUTE_WITHOUT_VALUE = + TestUtils.hexStringToByteArray("000d0000"); + + private static final Inet6Address IPV6_ADDRESS = + (Inet6Address) (InetAddressUtils.parseNumericAddress("2001:db8::1")); + private static final int IP6_PREFIX_LEN = 64; + private static final LinkAddress IPV6_LINK_ADDRESS = + new LinkAddress(IPV6_ADDRESS, IP6_PREFIX_LEN); + + private static final byte[] IPV6_ADDRESS_ATTRIBUTE_VALUE = + TestUtils.hexStringToByteArray("20010db800000000000000000000000140"); + private static final byte[] IPV6_ADDRESS_ATTRIBUTE_WITH_VALUE = + TestUtils.hexStringToByteArray("0008001120010db800000000000000000000000140"); + private static final byte[] IPV6_ADDRESS_ATTRIBUTE_WITHOUT_VALUE = + TestUtils.hexStringToByteArray("00080000"); + + private static final byte[] IPV6_SUBNET_ATTRIBUTE_VALUE = IPV6_ADDRESS_ATTRIBUTE_VALUE; + private static final byte[] IPV6_SUBNET_ATTRIBUTE_WITH_VALUE = + TestUtils.hexStringToByteArray("000f001120010db800000000000000000000000140"); + private static final byte[] IPV6_SUBNET_ATTRIBUTE_WITHOUT_VALUE = + TestUtils.hexStringToByteArray("000f0000"); + + private static final Inet6Address IPV6_DNS = + (Inet6Address) (InetAddressUtils.parseNumericAddress("2001:db8:100::1")); + private static final byte[] IPV6_DNS_ATTRIBUTE_WITHOUT_VALUE = + TestUtils.hexStringToByteArray("000a0000"); + + private Inet4Address[] mNetMasks; + private int[] mIpv4PrefixLens; + + @Before + public void setUp() throws Exception { + mNetMasks = + new Inet4Address[] { + (Inet4Address) (InetAddressUtils.parseNumericAddress("0.0.0.0")), + (Inet4Address) (InetAddressUtils.parseNumericAddress("255.255.255.255")), + (Inet4Address) (InetAddressUtils.parseNumericAddress("255.255.255.240")) + }; + mIpv4PrefixLens = new int[] {0, 32, 28}; + } + + private IkeConfigPayload verifyDecodeHeaderAndGetPayload( + IkePayload payload, int expectedConfigType) { + assertEquals(PAYLOAD_TYPE_CP, payload.payloadType); + assertFalse(payload.isCritical); + assertTrue(payload instanceof IkeConfigPayload); + + IkeConfigPayload configPayload = (IkeConfigPayload) payload; + assertEquals(expectedConfigType, configPayload.configType); + + return configPayload; + } + + @Test + public void testDecodeConfigRequest() throws Exception { + IkePayload payload = + IkePayloadFactory.getIkePayload( + PAYLOAD_TYPE_CP, + false /*isResp*/, + ByteBuffer.wrap(CONFIG_REQ_PAYLOAD)) + .first; + + IkeConfigPayload configPayload = + verifyDecodeHeaderAndGetPayload(payload, CONFIG_TYPE_REQUEST); + + List<ConfigAttribute> recognizedAttributeList = configPayload.recognizedAttributeList; + assertEquals(4, recognizedAttributeList.size()); + + ConfigAttribute att = recognizedAttributeList.get(0); + assertEquals(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, att.attributeType); + assertNull(((ConfigAttributeIpv4Address) att).address); + + att = recognizedAttributeList.get(1); + assertEquals(CONFIG_ATTR_INTERNAL_IP6_ADDRESS, att.attributeType); + assertNull(((ConfigAttributeIpv6Address) att).linkAddress); + + att = recognizedAttributeList.get(2); + assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, att.attributeType); + assertNull(((ConfigAttributeIpv4Dns) att).address); + + att = recognizedAttributeList.get(3); + assertEquals(CONFIG_ATTR_INTERNAL_IP6_DNS, att.attributeType); + assertNull(((ConfigAttributeIpv6Dns) att).address); + } + + @Test + public void testDecodeConfigResponse() throws Exception { + IkePayload payload = + IkePayloadFactory.getIkePayload( + PAYLOAD_TYPE_CP, + true /*isResp*/, + ByteBuffer.wrap(CONFIG_RESP_PAYLOAD)) + .first; + + IkeConfigPayload configPayload = + verifyDecodeHeaderAndGetPayload(payload, CONFIG_TYPE_REPLY); + + List<ConfigAttribute> recognizedAttributeList = configPayload.recognizedAttributeList; + assertEquals(3, recognizedAttributeList.size()); + + ConfigAttribute att = recognizedAttributeList.get(0); + assertEquals(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, att.attributeType); + assertEquals(IPV4_ADDRESS, ((ConfigAttributeIpv4Address) att).address); + + att = recognizedAttributeList.get(1); + assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, att.attributeType); + assertEquals(IPV4_DNS, ((ConfigAttributeIpv4Dns) att).address); + + att = recognizedAttributeList.get(2); + InetAddress expectedDns = InetAddress.getByName("8.8.4.4"); + assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, att.attributeType); + assertEquals(expectedDns, ((ConfigAttributeIpv4Dns) att).address); + } + + @Test + public void testDecodeConfigRespWithTwoNetmask() throws Exception { + byte[] configPayloadBytes = + TestUtils.hexStringToByteArray(CONFIG_RESP_PAYLOAD_INVALID_ONE_HEX); + try { + IkePayloadFactory.getIkePayload( + PAYLOAD_TYPE_CP, true /*isResp*/, ByteBuffer.wrap(configPayloadBytes)); + fail("Expected to fail because more than on netmask found"); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testDecodeConfigRespNetmaskFoundWithoutIpv4Addr() throws Exception { + byte[] configPayloadBytes = + TestUtils.hexStringToByteArray(CONFIG_RESP_PAYLOAD_INVALID_TWO_HEX); + try { + IkePayloadFactory.getIkePayload( + PAYLOAD_TYPE_CP, true /*isResp*/, ByteBuffer.wrap(configPayloadBytes)); + fail("Expected to fail because netmask is found without a IPv4 address"); + } catch (InvalidSyntaxException expected) { + } + } + + private ConfigAttribute makeMockAttribute(byte[] encodedAttribute) { + ConfigAttribute mockAttribute = mock(ConfigAttribute.class); + + when(mockAttribute.getAttributeLen()).thenReturn(encodedAttribute.length); + + doAnswer( + (invocation) -> { + ByteBuffer buffer = (ByteBuffer) invocation.getArguments()[0]; + buffer.put(encodedAttribute); + return null; + }) + .when(mockAttribute) + .encodeAttributeToByteBuffer(any(ByteBuffer.class)); + + return mockAttribute; + } + + @Test + public void testBuildAndEncodeOutboundConfig() throws Exception { + List<ConfigAttribute> mockAttributeList = new LinkedList<>(); + mockAttributeList.add(makeMockAttribute(IPV4_ADDRESS_ATTRIBUTE_WITHOUT_VALUE)); + mockAttributeList.add(makeMockAttribute(IPV6_ADDRESS_ATTRIBUTE_WITHOUT_VALUE)); + mockAttributeList.add(makeMockAttribute(IPV4_DNS_ATTRIBUTE_WITHOUT_VALUE)); + mockAttributeList.add(makeMockAttribute(IPV6_DNS_ATTRIBUTE_WITHOUT_VALUE)); + IkeConfigPayload configPayload = new IkeConfigPayload(false /*isReply*/, mockAttributeList); + + assertEquals(PAYLOAD_TYPE_CP, configPayload.payloadType); + assertFalse(configPayload.isCritical); + assertEquals(CONFIG_TYPE_REQUEST, configPayload.configType); + assertEquals(mockAttributeList, configPayload.recognizedAttributeList); + + ByteBuffer buffer = ByteBuffer.allocate(configPayload.getPayloadLength()); + configPayload.encodeToByteBuffer(PAYLOAD_TYPE_NOTIFY, buffer); + assertArrayEquals(CONFIG_REQ_PAYLOAD, buffer.array()); + } + + private void verifyBuildAndEncodeAttributeCommon( + ConfigAttribute attribute, int expectedAttributeType, byte[] expectedEncodedAttribute) { + assertEquals(expectedAttributeType, attribute.attributeType); + + ByteBuffer buffer = ByteBuffer.allocate(attribute.getAttributeLen()); + attribute.encodeAttributeToByteBuffer(buffer); + assertArrayEquals(expectedEncodedAttribute, buffer.array()); + } + + private void verifyEncodeIpv4AddresBaseAttribute( + ConfigAttrIpv4AddressBase attribute, + int expectedAttributeType, + byte[] expectedEncodedAttribute, + Inet4Address expectedAddress) { + verifyBuildAndEncodeAttributeCommon( + attribute, expectedAttributeType, expectedEncodedAttribute); + assertEquals(expectedAddress, attribute.address); + } + + private void verifyEncodeIpv6RangeBaseAttribute( + ConfigAttrIpv6AddrRangeBase attribute, + int expectedAttributeType, + byte[] expectedEncodedAttribute, + LinkAddress expectedLinkAddress) { + verifyBuildAndEncodeAttributeCommon( + attribute, expectedAttributeType, expectedEncodedAttribute); + assertEquals(expectedLinkAddress, attribute.linkAddress); + } + + @Test + public void testDecodeIpv4AddressWithValue() throws Exception { + ConfigAttributeIpv4Address attributeIp4Address = + new ConfigAttributeIpv4Address(IPV4_ADDRESS.getAddress()); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, attributeIp4Address.attributeType); + assertEquals(IPV4_ADDRESS, attributeIp4Address.address); + } + + @Test + public void testDecodeIpv4AddressWithoutValue() throws Exception { + ConfigAttributeIpv4Address attributeIp4Address = + new ConfigAttributeIpv4Address(new byte[0]); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, attributeIp4Address.attributeType); + assertNull(attributeIp4Address.address); + } + + @Test + public void testDecodeIpv4AddressWithInvalidValue() throws Exception { + byte[] invalidValue = new byte[] {1}; + + try { + ConfigAttributeIpv4Address attributeIp4Address = + new ConfigAttributeIpv4Address(invalidValue); + fail("Expected to fail due to invalid attribute value"); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testEncodeIpv4AddressWithValue() throws Exception { + ConfigAttributeIpv4Address attributeIp4Address = + new ConfigAttributeIpv4Address(IPV4_ADDRESS); + + verifyEncodeIpv4AddresBaseAttribute( + attributeIp4Address, + CONFIG_ATTR_INTERNAL_IP4_ADDRESS, + IPV4_ADDRESS_ATTRIBUTE_WITH_VALUE, + IPV4_ADDRESS); + } + + @Test + public void testEncodeIpv4AddressWithoutValue() throws Exception { + ConfigAttributeIpv4Address attributeIp4Address = new ConfigAttributeIpv4Address(); + + verifyEncodeIpv4AddresBaseAttribute( + attributeIp4Address, + CONFIG_ATTR_INTERNAL_IP4_ADDRESS, + IPV4_ADDRESS_ATTRIBUTE_WITHOUT_VALUE, + null /*expectedAddress*/); + } + + @Test + public void testDecodeIpv4NetmaskWithValue() throws Exception { + ConfigAttributeIpv4Netmask attribute = + new ConfigAttributeIpv4Netmask(IPV4_NETMASK.getAddress()); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_NETMASK, attribute.attributeType); + assertEquals(IPV4_NETMASK, attribute.address); + } + + @Test + public void testDecodeIpv4NetmaskWithoutValue() throws Exception { + ConfigAttributeIpv4Netmask attribute = new ConfigAttributeIpv4Netmask(new byte[0]); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_NETMASK, attribute.attributeType); + assertNull(attribute.address); + } + + @Test + public void testEncodeIpv4Netmask() throws Exception { + ConfigAttributeIpv4Netmask attribute = new ConfigAttributeIpv4Netmask(); + + verifyEncodeIpv4AddresBaseAttribute( + attribute, + CONFIG_ATTR_INTERNAL_IP4_NETMASK, + IPV4_NETMASK_ATTRIBUTE_WITHOUT_VALUE, + null /*expectedAddress*/); + } + + @Test + public void testDecodeIpv4DnsWithValue() throws Exception { + ConfigAttributeIpv4Dns attribute = new ConfigAttributeIpv4Dns(IPV4_DNS.getAddress()); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, attribute.attributeType); + assertEquals(IPV4_DNS, attribute.address); + } + + @Test + public void testDecodeIpv4DnsWithoutValue() throws Exception { + ConfigAttributeIpv4Dns attribute = new ConfigAttributeIpv4Dns(new byte[0]); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, attribute.attributeType); + assertNull(attribute.address); + } + + @Test + public void testEncodeIpv4Dns() throws Exception { + ConfigAttributeIpv4Dns attribute = new ConfigAttributeIpv4Dns(); + + verifyEncodeIpv4AddresBaseAttribute( + attribute, + CONFIG_ATTR_INTERNAL_IP4_DNS, + IPV4_DNS_ATTRIBUTE_WITHOUT_VALUE, + null /*expectedAddress*/); + } + + @Test + public void testDecodeIpv4DhcpWithValue() throws Exception { + ConfigAttributeIpv4Dhcp attribute = new ConfigAttributeIpv4Dhcp(IPV4_DHCP.getAddress()); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_DHCP, attribute.attributeType); + assertEquals(IPV4_DHCP, attribute.address); + } + + @Test + public void testDecodeIpv4DhcpWithoutValue() throws Exception { + ConfigAttributeIpv4Dhcp attribute = new ConfigAttributeIpv4Dhcp(new byte[0]); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_DHCP, attribute.attributeType); + assertNull(attribute.address); + } + + @Test + public void testEncodeIpv4DhcpWithValue() throws Exception { + ConfigAttributeIpv4Dhcp attributeIp4Dhcp = new ConfigAttributeIpv4Dhcp(IPV4_DHCP); + + verifyEncodeIpv4AddresBaseAttribute( + attributeIp4Dhcp, + CONFIG_ATTR_INTERNAL_IP4_DHCP, + IPV4_DHCP_ATTRIBUTE_WITH_VALUE, + IPV4_DHCP); + } + + @Test + public void testEncodeIpv4DhcpWithoutValue() throws Exception { + ConfigAttributeIpv4Dhcp attribute = new ConfigAttributeIpv4Dhcp(); + + verifyEncodeIpv4AddresBaseAttribute( + attribute, + CONFIG_ATTR_INTERNAL_IP4_DHCP, + IPV4_DHCP_ATTRIBUTE_WITHOUT_VALUE, + null /*expectedAddress*/); + } + + @Test + public void testDecodeIpv4SubnetWithValue() throws Exception { + ConfigAttributeIpv4Subnet attributeIp4Subnet = + new ConfigAttributeIpv4Subnet(IPV4_SUBNET_ATTRIBUTE_VALUE); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_SUBNET, attributeIp4Subnet.attributeType); + assertEquals(IPV4_LINK_ADDRESS, attributeIp4Subnet.linkAddress); + } + + @Test + public void testDecodeIpv4SubnetWithoutValue() throws Exception { + ConfigAttributeIpv4Subnet attributeIp4Subnet = new ConfigAttributeIpv4Subnet(new byte[0]); + + assertEquals(CONFIG_ATTR_INTERNAL_IP4_SUBNET, attributeIp4Subnet.attributeType); + assertNull(attributeIp4Subnet.linkAddress); + } + + @Test + public void testDecodeIpv4SubnetWithInvalidValue() throws Exception { + byte[] ipAddress = IPV4_ADDRESS.getAddress(); + ByteBuffer buffer = ByteBuffer.allocate(ipAddress.length * 2); + buffer.put(ipAddress).put(ipAddress); + + try { + new ConfigAttributeIpv4Subnet(buffer.array()); + fail("Expected to fail due to invalid netmask."); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testEncodeIpv4SubnetWithoutValue() throws Exception { + ConfigAttributeIpv4Subnet attributeIp4Subnet = new ConfigAttributeIpv4Subnet(); + + verifyBuildAndEncodeAttributeCommon( + attributeIp4Subnet, + CONFIG_ATTR_INTERNAL_IP4_SUBNET, + IPV4_SUBNET_ATTRIBUTE_WITHOUT_VALUE); + assertNull(attributeIp4Subnet.linkAddress); + } + + @Test + public void testNetmaskToPrefixLen() throws Exception { + for (int i = 0; i < mNetMasks.length; i++) { + assertEquals(mIpv4PrefixLens[i], ConfigAttribute.netmaskToPrefixLen(mNetMasks[i])); + } + } + + @Test + public void testPrefixToNetmaskBytes() throws Exception { + for (int i = 0; i < mIpv4PrefixLens.length; i++) { + assertArrayEquals( + mNetMasks[i].getAddress(), + ConfigAttribute.prefixToNetmaskBytes(mIpv4PrefixLens[i])); + } + } + + @Test + public void testDecodeIpv6AddressWithValue() throws Exception { + ConfigAttributeIpv6Address attributeIp6Address = + new ConfigAttributeIpv6Address(IPV6_ADDRESS_ATTRIBUTE_VALUE); + + assertEquals(CONFIG_ATTR_INTERNAL_IP6_ADDRESS, attributeIp6Address.attributeType); + assertEquals(IPV6_LINK_ADDRESS, attributeIp6Address.linkAddress); + } + + @Test + public void testDecodeIpv6AddressWithoutValue() throws Exception { + ConfigAttributeIpv6Address attributeIp6Address = + new ConfigAttributeIpv6Address(new byte[0]); + + assertEquals(CONFIG_ATTR_INTERNAL_IP6_ADDRESS, attributeIp6Address.attributeType); + assertNull(attributeIp6Address.linkAddress); + } + + @Test + public void testDecodeIpv6AddressWithInvalidValue() throws Exception { + byte[] invalidValue = new byte[] {1}; + + try { + ConfigAttributeIpv6Address attributeIp6Address = + new ConfigAttributeIpv6Address(invalidValue); + fail("Expected to fail due to invalid attribute value"); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testEncodeIpv6AddressWithValue() throws Exception { + ConfigAttributeIpv6Address attributeIp6Address = + new ConfigAttributeIpv6Address(IPV6_LINK_ADDRESS); + + verifyEncodeIpv6RangeBaseAttribute( + attributeIp6Address, + CONFIG_ATTR_INTERNAL_IP6_ADDRESS, + IPV6_ADDRESS_ATTRIBUTE_WITH_VALUE, + IPV6_LINK_ADDRESS); + } + + @Test + public void testEncodeIpv6AddressWithoutValue() throws Exception { + ConfigAttributeIpv6Address attributeIp6Address = new ConfigAttributeIpv6Address(); + + verifyEncodeIpv6RangeBaseAttribute( + attributeIp6Address, + CONFIG_ATTR_INTERNAL_IP6_ADDRESS, + IPV6_ADDRESS_ATTRIBUTE_WITHOUT_VALUE, + null /*expectedLinkAddress*/); + } + + @Test + public void testDecodeIpv6SubnetWithValue() throws Exception { + ConfigAttributeIpv6Subnet attributeIp6Subnet = + new ConfigAttributeIpv6Subnet(IPV6_SUBNET_ATTRIBUTE_VALUE); + + assertEquals(CONFIG_ATTR_INTERNAL_IP6_SUBNET, attributeIp6Subnet.attributeType); + assertEquals(IPV6_LINK_ADDRESS, attributeIp6Subnet.linkAddress); + } + + @Test + public void testDecodeIpv6SubnetWithoutValue() throws Exception { + ConfigAttributeIpv6Subnet attributeIp6Subnet = new ConfigAttributeIpv6Subnet(new byte[0]); + + assertEquals(CONFIG_ATTR_INTERNAL_IP6_SUBNET, attributeIp6Subnet.attributeType); + assertNull(attributeIp6Subnet.linkAddress); + } + + @Test + public void testEncodeIpv6SubnetWithoutValue() throws Exception { + ConfigAttributeIpv6Subnet attributeIp6Subnet = new ConfigAttributeIpv6Subnet(); + + verifyEncodeIpv6RangeBaseAttribute( + attributeIp6Subnet, + CONFIG_ATTR_INTERNAL_IP6_SUBNET, + IPV6_SUBNET_ATTRIBUTE_WITHOUT_VALUE, + null /*expectedLinkAddress*/); + } + + @Test + public void testDecodeIpv6DnsWithValue() throws Exception { + ConfigAttributeIpv6Dns attribute = new ConfigAttributeIpv6Dns(IPV6_DNS.getAddress()); + + assertEquals(CONFIG_ATTR_INTERNAL_IP6_DNS, attribute.attributeType); + assertEquals(IPV6_DNS, attribute.address); + } + + @Test + public void testDecodeIpv6DnsWithoutValue() throws Exception { + ConfigAttributeIpv6Dns attribute = new ConfigAttributeIpv6Dns(new byte[0]); + + assertEquals(CONFIG_ATTR_INTERNAL_IP6_DNS, attribute.attributeType); + assertNull(attribute.address); + } + + @Test + public void testEncodeIpv6Dns() throws Exception { + ConfigAttributeIpv6Dns attribute = new ConfigAttributeIpv6Dns(); + + verifyBuildAndEncodeAttributeCommon( + attribute, CONFIG_ATTR_INTERNAL_IP6_DNS, IPV6_DNS_ATTRIBUTE_WITHOUT_VALUE); + assertNull(attribute.address); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeDeletePayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeDeletePayloadTest.java new file mode 100644 index 00000000..6be5336f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeDeletePayloadTest.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static com.android.internal.net.ipsec.ike.message.IkePayload.PROTOCOL_ID_ESP; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PROTOCOL_ID_IKE; +import static com.android.internal.net.ipsec.ike.message.IkePayload.SPI_LEN_IPSEC; +import static com.android.internal.net.ipsec.ike.message.IkePayload.SPI_LEN_NOT_INCLUDED; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; + +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public final class IkeDeletePayloadTest { + private static final String DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000"; + private static final String DELETE_CHILD_PAYLOAD_HEX_STRING = "0000000c030400012ad4c0a2"; + private static final String CHILD_SPI = "2ad4c0a2"; + + private static final String DELETE_MULTIPLE_CHILD_PAYLOAD_HEX_STRING = + "0000001003040002abcdef0120fedcba"; + private static final String[] MULTIPLE_CHILD_SPIS = new String[] {"abcdef01", "20fedcba"}; + + private static final int NUM_CHILD_SPI = 1; + + private static final int PROTOCOL_ID_OFFSET = 4; + private static final int SPI_SIZE_OFFSET = 5; + private static final int NUM_OF_SPI_OFFSET = 6; + + @Test + public void testDecodeDeleteIkePayload() throws Exception { + ByteBuffer inputBuffer = + ByteBuffer.wrap(TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING)); + + IkePayload payload = + IkePayloadFactory.getIkePayload( + IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer) + .first; + + assertTrue(payload instanceof IkeDeletePayload); + + IkeDeletePayload deletePayload = (IkeDeletePayload) payload; + assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId); + assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, deletePayload.spiSize); + assertEquals(0, deletePayload.numSpi); + assertArrayEquals(new int[0], deletePayload.spisToDelete); + } + + @Test + public void testDecodeDeleteChildPayload() throws Exception { + ByteBuffer inputBuffer = + ByteBuffer.wrap(TestUtils.hexStringToByteArray(DELETE_CHILD_PAYLOAD_HEX_STRING)); + + IkePayload payload = + IkePayloadFactory.getIkePayload( + IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer) + .first; + + assertTrue(payload instanceof IkeDeletePayload); + + IkeDeletePayload deletePayload = (IkeDeletePayload) payload; + assertEquals(IkePayload.PROTOCOL_ID_ESP, deletePayload.protocolId); + assertEquals(IkePayload.SPI_LEN_IPSEC, deletePayload.spiSize); + assertEquals(NUM_CHILD_SPI, deletePayload.numSpi); + + int expectedChildSpi = TestUtils.hexStringToInt(CHILD_SPI); + assertArrayEquals(new int[] {expectedChildSpi}, deletePayload.spisToDelete); + } + + @Test + public void testDecodeWithInvalidProtocol() throws Exception { + byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING); + deletePayloadBytes[PROTOCOL_ID_OFFSET] = -1; + ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes); + + try { + IkePayloadFactory.getIkePayload( + IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer); + fail("Expected to fail due to unrecognized protocol ID."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testDecodeWithInvalidSpiSize() throws Exception { + byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING); + deletePayloadBytes[SPI_SIZE_OFFSET] = IkePayload.SPI_LEN_IPSEC; + ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes); + + try { + IkePayloadFactory.getIkePayload( + IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer); + fail("Expected to fail due to invalid SPI size in Delete IKE Payload."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testDecodeWithInvalidNumSpi() throws Exception { + byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING); + deletePayloadBytes[NUM_OF_SPI_OFFSET] = 1; + ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes); + + try { + IkePayloadFactory.getIkePayload( + IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer); + fail("Expected to fail because number of SPI is not zero in Delete IKE Payload."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testDecodeWithInvalidNumSpiAndSpiSize() throws Exception { + byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING); + deletePayloadBytes[SPI_SIZE_OFFSET] = 1; + deletePayloadBytes[NUM_CHILD_SPI] = 4; + ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes); + + try { + IkePayloadFactory.getIkePayload( + IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer); + fail("Expected to fail due to invalid SPI size in Delete IKE Payload."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testOutboundConstructorForIke() throws Exception { + IkeDeletePayload deletePayload = new IkeDeletePayload(); + + assertEquals(PROTOCOL_ID_IKE, deletePayload.protocolId); + assertEquals(SPI_LEN_NOT_INCLUDED, deletePayload.spiSize); + assertEquals(0, deletePayload.numSpi); + assertEquals(0, deletePayload.spisToDelete.length); + } + + @Test + public void testOutboundConstructorWithSingleChildSa() throws Exception { + int[] childSpis = new int[] {TestUtils.hexStringToInt(CHILD_SPI)}; + IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis); + + assertEquals(PROTOCOL_ID_ESP, deletePayload.protocolId); + assertEquals(SPI_LEN_IPSEC, deletePayload.spiSize); + assertEquals(NUM_CHILD_SPI, deletePayload.numSpi); + assertArrayEquals(childSpis, deletePayload.spisToDelete); + } + + @Test + public void testOutboundConstructorWithMultipleChildSas() throws Exception { + int[] childSpis = new int[] {0x1, 0x2, 0xfffffffd, 0xffffffff}; + IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis); + + assertEquals(PROTOCOL_ID_ESP, deletePayload.protocolId); + assertEquals(SPI_LEN_IPSEC, deletePayload.spiSize); + assertEquals(childSpis.length, deletePayload.numSpi); + assertArrayEquals(childSpis, deletePayload.spisToDelete); + } + + @Test + public void testOutboundConstructorWithNoChildSas() throws Exception { + try { + IkeDeletePayload deletePayload = new IkeDeletePayload(new int[] {}); + fail("Expected exception for invalid SPI list"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testEncodeForIke() throws Exception { + IkeDeletePayload deletePayload = new IkeDeletePayload(); + ByteBuffer bb = ByteBuffer.allocate(deletePayload.getPayloadLength()); + + deletePayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, bb); + + byte[] expectedPayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING); + assertArrayEquals(expectedPayloadBytes, bb.array()); + } + + @Test + public void testEncodeWithSingleChildSa() throws Exception { + int[] childSpis = new int[] {TestUtils.hexStringToInt(CHILD_SPI)}; + IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis); + ByteBuffer bb = ByteBuffer.allocate(deletePayload.getPayloadLength()); + + deletePayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, bb); + + byte[] expectedPayloadBytes = + TestUtils.hexStringToByteArray(DELETE_CHILD_PAYLOAD_HEX_STRING); + assertArrayEquals(expectedPayloadBytes, bb.array()); + } + + @Test + public void testEncodeWithMultipleChildSas() throws Exception { + int[] childSpis = + Arrays.stream(MULTIPLE_CHILD_SPIS) + .mapToInt(val -> TestUtils.hexStringToInt(val)) + .toArray(); + IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis); + ByteBuffer bb = ByteBuffer.allocate(deletePayload.getPayloadLength()); + + deletePayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, bb); + + byte[] expectedPayloadBytes = + TestUtils.hexStringToByteArray(DELETE_MULTIPLE_CHILD_PAYLOAD_HEX_STRING); + assertArrayEquals(expectedPayloadBytes, bb.array()); + } + + @Test + public void testPayloadLengthForIke() throws Exception { + IkeDeletePayload deletePayload = new IkeDeletePayload(); + + byte[] expectedPayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING); + assertEquals(expectedPayloadBytes.length, deletePayload.getPayloadLength()); + } + + @Test + public void testPayloadLengthWithSingleChildSa() throws Exception { + int[] childSpis = new int[] {TestUtils.hexStringToInt(CHILD_SPI)}; + IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis); + + byte[] expectedPayloadBytes = + TestUtils.hexStringToByteArray(DELETE_CHILD_PAYLOAD_HEX_STRING); + assertEquals(expectedPayloadBytes.length, deletePayload.getPayloadLength()); + } + + @Test + public void testPayloadLengthWithMultipleChildSas() throws Exception { + int[] childSpis = + Arrays.stream(MULTIPLE_CHILD_SPIS) + .mapToInt(val -> TestUtils.hexStringToInt(val)) + .toArray(); + IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis); + + byte[] expectedPayloadBytes = + TestUtils.hexStringToByteArray(DELETE_MULTIPLE_CHILD_PAYLOAD_HEX_STRING); + assertEquals(expectedPayloadBytes.length, deletePayload.getPayloadLength()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEapPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEapPayloadTest.java new file mode 100644 index 00000000..fff82b60 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEapPayloadTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static com.android.internal.net.TestUtils.hexStringToByteArray; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class IkeEapPayloadTest { + private static final String EAP_SUCCESS_STRING = "03010004"; + private static final byte[] EAP_SUCCESS_PACKET = hexStringToByteArray(EAP_SUCCESS_STRING); + + private static final byte[] IKE_EAP_PAYLOAD = + hexStringToByteArray( + "00000008" + EAP_SUCCESS_STRING); + + @Test + public void testDecodeIkeEapPayload() throws Exception { + ByteBuffer input = ByteBuffer.wrap(IKE_EAP_PAYLOAD); + IkePayload result = IkePayloadFactory + .getIkePayload(IkePayload.PAYLOAD_TYPE_EAP, true, input).first; + + assertTrue(result instanceof IkeEapPayload); + IkeEapPayload ikeEapPayload = (IkeEapPayload) result; + assertArrayEquals(EAP_SUCCESS_PACKET, ikeEapPayload.eapMessage); + } + + @Test + public void testEncodeToByteBuffer() { + IkeEapPayload ikeEapPayload = new IkeEapPayload(EAP_SUCCESS_PACKET); + ByteBuffer result = ByteBuffer.allocate(IKE_EAP_PAYLOAD.length); + + ikeEapPayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, result); + assertArrayEquals(IKE_EAP_PAYLOAD, result.array()); + } + + @Test + public void testOutboundConstructorForIke() { + IkeEapPayload ikeEapPayload = new IkeEapPayload(EAP_SUCCESS_PACKET); + assertArrayEquals(EAP_SUCCESS_PACKET, ikeEapPayload.eapMessage); + } + + @Test + public void testGetPayloadLength() { + IkeEapPayload ikeEapPayload = new IkeEapPayload(EAP_SUCCESS_PACKET); + assertEquals(IKE_EAP_PAYLOAD.length, ikeEapPayload.getPayloadLength()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBodyTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBodyTest.java new file mode 100644 index 00000000..36c7648a --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBodyTest.java @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.crypto.IkeCipher; +import com.android.internal.net.ipsec.ike.crypto.IkeCombinedModeCipher; +import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; +import com.android.internal.net.ipsec.ike.crypto.IkeNormalModeCipher; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; + +import org.junit.Before; +import org.junit.Test; + +import java.security.GeneralSecurityException; +import java.util.Arrays; + +public final class IkeEncryptedPayloadBodyTest { + private static final String IKE_AUTH_INIT_REQUEST_HEADER = + "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec"; + private static final String IKE_AUTH_INIT_REQUEST_SK_HEADER = "230000d0"; + private static final String IKE_AUTH_INIT_REQUEST_IV = "b9132b7bb9f658dfdc648e5017a6322a"; + private static final String IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA = + "030c316ce55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58" + + "46de333ecd3ea2b705d18293b130395300ba92a351041345" + + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f" + + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60" + + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b" + + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423" + + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3" + + "59eb4e81"; + private static final String IKE_AUTH_INIT_REQUEST_CHECKSUM = "ae6e0f22abdad69ba8007d50"; + + private static final String IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA = + "2400000c010000000a50500d2700000c010000000a505050" + + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327" + + "44dfb2c12c00002c00000028010304032ad4c0a20300000c" + + "0100000c800e008003000008030000020000000805000000" + + "2d00001801000000070000100000ffff00000000ffffffff" + + "2900001801000000070000100000ffff00000000ffffffff" + + "29000008000040000000000c0000400100000001"; + private static final String IKE_AUTH_INIT_REQUEST_PADDING = "0000000000000000000000"; + private static final int HMAC_SHA1_CHECKSUM_LEN = 12; + + private static final String ENCR_KEY_FROM_INIT_TO_RESP = "5cbfd33f75796c0188c4a3a546aec4a1"; + private static final String INTE_KEY_FROM_INIT_TO_RESP = + "554fbf5a05b7f511e05a30ce23d874db9ef55e51"; + + private static final String ENCR_ALGO_AES_CBC = "AES/CBC/NoPadding"; + + // Test vectors for IKE message protected by HmacSha1 and 3DES + private static final String HMAC_SHA1_3DES_MSG_HEX_STRING = + "5837b1bd28ec424f85ddd0c609c8dbfe2e20232000000002" + + "00000064300000488beaf41d88544baabd95eac60269f19a" + + "5986295fe318ce02f65368cd957985f36b183794c4c78d35" + + "437762297a131a773d7f7806aaa0c590f48b9d71001f4d65" + + "70a44533"; + + private static final String HMAC_SHA1_3DES_DECRYPTED_BODY_HEX_STRING = + "00000028013c00241a013c001f10dac4f8b759138776091dd0f00033c5b07374726f6e675377616e"; + + private static final String HMAC_SHA1_3DES_MSG_ENCR_KEY = + "ee0fdd6d35bbdbe9eeef2f24495b6632e5047bdd8e413c87"; + private static final String HMAC_SHA1_3DES_MSG_INTE_KEY = + "867a0bd019108db856cf6984fc9fb62d70c0de74"; + + // TODO: b/142753861 Test IKE fragment protected by AES_CBC instead of 3DES + + // Test vectors for IKE fragment protected by HmacSha1 and 3DES + private static final String HMAC_SHA1_3DES_FRAG_HEX_STRING = + "939ae1251d18eb9077a99551b15c6e933520232000000001" + + "000000c0000000a400050005fd7c7931705af184b7be76bb" + + "d45a8ecbb3ffd58b9438b93f67e9fe86b06229f80e9b52d2" + + "ff6afde3f2c13ae93ce55a801f62e1a818c9003880a36bbe" + + "986fe6979ba233b9f4f0ddc992d06dbad5a2b998be18fae9" + + "47e5ccfb37775d069344e711fbf499bb289cf4cca245bd45" + + "0ad89d18689207759507ba18d47247e920b9e000a25a7596" + + "e4130929e5cdc37d5c1b0d90bbaae946c260f4d3cf815f6d"; + private static final String HMAC_SHA1_3DES_FRAG_DECRYPTED_BODY_HEX_STRING = + "54ebd95c572100002002000000000100040a0a0a01000300" + + "040808080800030004080804042c00002c00000028020304" + + "03cc86090a0300000c0100000c800e010003000008030000" + + "0200000008050000002d00001801000000070000100000ff" + + "ff0a0a0a010a0a0a010000001801000000070000100000ff" + + "ff00000000ffffffff"; + private static final String HMAC_SHA1_3DES_FRAG_IV = "fd7c7931705af184"; + private static final String HMAC_SHA1_3DES_FRAG_PADDING = "dcf0fa5e2b64"; + + private static final int HMAC_SHA1_3DES_FRAGMENT_NUM = 5; + private static final int HMAC_SHA1_3DES_TOTAL_FRAGMENTS = 5; + + private static final String HMAC_SHA1_3DES_FRAG_ENCR_KEY = + "6BBF6CB3526D6492F4DA0AF45E9B9FD3E1FF534280352073"; + private static final String HMAC_SHA1_3DES_FRAG_INTE_KEY = + "293449E8E518060780B9C06F15838A06EEF57814"; + + // Test vectors for IKE message protected by AES_GCM_16 + private static final String AES_GCM_MSG_HEX_STRING = + "77c708b4523e39a471dc683c1d4f21362e20230800000005" + + "0000006127000045fbd69d9ee2dafc5e7c03a0106761065b2" + + "8fa8d11aed6046f7f8af117e44da7635be6e0dfafcb0a387c" + + "53fb46ba5d6fa9509161915929de97b7fbe23dc65723b0fe"; + private static final String AES_GCM_MSG_DECRYPTED_BODY_HEX_STRING = + "000000280200000033233837e909ec805d56151bef5b1fa9b8e25b32419c9b3fc96ee699ec29d501"; + private static final String AES_GCM_MSG_IV = "fbd69d9ee2dafc5e"; + private static final String AES_GCM_MSG_ENCR_KEY = + "7C04513660DEC572D896105254EF92608054F8E6EE19E79CE52AB8697B2B5F2C2AA90C29"; + + // Test vectors for IKE fragment protected by AES_GCM_16 + private static final String AES_GCM_FRAG_HEX_STRING = + "77c708b4523e39a471dc683c1d4f213635202320000000010" + + "0000113000000f7000200026faf9e5c04c67571871681d443" + + "01489f99fd78d318b0517a5a99bf6a3e1770f43d7d997c9e0" + + "d186038d16df3fd525eda821f80b3a40fc6bce397ac67539e" + + "40042919a5e9af38c70881d092a8571f0e131f594c0e8d6b8" + + "4ea116f0c95619439b0a267b35bc47dac72bbfb3d3776feb3" + + "86d7d4f819b0248f52f60bf4371ab6384e37819a9685c27d8" + + "e41abe30cd6f60905dd5c05c351ec0a1fcf9b99360161d2f3" + + "4dcf6401829df9392121d88e2201d279200e25d750678af6a" + + "7f4892a5c8d4a7358ec50cdf12cfa7652488f756ba6d07441" + + "e9a27aad3976ac8a705ff796857cb2df9ce360c3992e0285b" + + "34834255b06"; + private static final String AES_GCM_FRAG_DECRYPTED_BODY_HEX_STRING = + "0fce6e996f4936ec8db8097339c371c686be75f4bed3f259c" + + "14d39c3ad90cb864085c6375f75b724d9f9daa8e7b22a106a" + + "488bc48c081997b7416fd33b146882e51ff6a640edf760212" + + "7f2454d502e92262ba3dd07cff52ee1bb1ea85f582db41a68" + + "aaf6dace362e5d8b10cfeb65eebc7572690e2c415a11cae57" + + "020810cf7aa56d9f2d5c2be3a633f8e4c6af5483a2b1f05bd" + + "4340ab551ddf7f51def57eaf5a37793ff6aa1e1ec288a2adf" + + "a647c369f15efa61a619966a320f24e1765c0e00c5ed394aa" + + "ef14512032b005827c000000090100000501"; + private static final String AES_GCM_FRAG_IV = "6faf9e5c04c67571"; + + private static final int AES_GCM_FRAGMENT_NUM = 2; + private static final int AES_GCM_TOTAL_FRAGMENTS = 2; + + private static final String AES_GCM_FRAG_ENCR_KEY = + "955ED949D6F18857220E97B17D9285C830A39F8D4DC46AB43943668093C62A3D66664F8C"; + + private static final int ENCRYPTED_BODY_SK_OFFSET = + IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH; + private static final int ENCRYPTED_BODY_SKF_OFFSET = + ENCRYPTED_BODY_SK_OFFSET + IkeSkfPayload.SKF_HEADER_LEN; + + private IkeNormalModeCipher mAesCbcCipher; + private byte[] mAesCbcKey; + + private IkeMacIntegrity mHmacSha1IntegrityMac; + private byte[] mHmacSha1IntegrityKey; + + private byte[] mDataToPadAndEncrypt; + private byte[] mDataToAuthenticate; + private byte[] mEncryptedPaddedData; + private byte[] mIkeMessage; + + private byte[] mChecksum; + private byte[] mIv; + private byte[] mPadding; + + private IkeNormalModeCipher m3DesCipher; + + private IkeCombinedModeCipher mAesGcm16Cipher; + + private byte[] mAesGcmMsgKey; + private byte[] mAesGcmMsg; + private byte[] mAesGcmUnencryptedData; + + private byte[] mAesGcmFragKey; + private byte[] mAesGcmFragMsg; + private byte[] mAesGcmFragUnencryptedData; + + @Before + public void setUp() throws Exception { + mDataToPadAndEncrypt = + TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA); + String hexStringToAuthenticate = + IKE_AUTH_INIT_REQUEST_HEADER + + IKE_AUTH_INIT_REQUEST_SK_HEADER + + IKE_AUTH_INIT_REQUEST_IV + + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA; + mDataToAuthenticate = TestUtils.hexStringToByteArray(hexStringToAuthenticate); + mEncryptedPaddedData = + TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA); + mIkeMessage = + TestUtils.hexStringToByteArray( + IKE_AUTH_INIT_REQUEST_HEADER + + IKE_AUTH_INIT_REQUEST_SK_HEADER + + IKE_AUTH_INIT_REQUEST_IV + + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA + + IKE_AUTH_INIT_REQUEST_CHECKSUM); + + mChecksum = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_CHECKSUM); + mIv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV); + mPadding = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_PADDING); + + m3DesCipher = + (IkeNormalModeCipher) + IkeCipher.create( + new EncryptionTransform(SaProposal.ENCRYPTION_ALGORITHM_3DES), + IkeMessage.getSecurityProvider()); + + mAesCbcCipher = + (IkeNormalModeCipher) + IkeCipher.create( + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, + SaProposal.KEY_LEN_AES_128), + IkeMessage.getSecurityProvider()); + mAesCbcKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP); + + mHmacSha1IntegrityMac = + IkeMacIntegrity.create( + new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96), + IkeMessage.getSecurityProvider()); + mHmacSha1IntegrityKey = TestUtils.hexStringToByteArray(INTE_KEY_FROM_INIT_TO_RESP); + + mAesGcm16Cipher = + (IkeCombinedModeCipher) + IkeCipher.create( + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16, + SaProposal.KEY_LEN_AES_256), + IkeMessage.getSecurityProvider()); + + mAesGcmMsgKey = TestUtils.hexStringToByteArray(AES_GCM_MSG_ENCR_KEY); + mAesGcmMsg = TestUtils.hexStringToByteArray(AES_GCM_MSG_HEX_STRING); + mAesGcmUnencryptedData = + TestUtils.hexStringToByteArray(AES_GCM_MSG_DECRYPTED_BODY_HEX_STRING); + + mAesGcmFragKey = TestUtils.hexStringToByteArray(AES_GCM_FRAG_ENCR_KEY); + mAesGcmFragMsg = TestUtils.hexStringToByteArray(AES_GCM_FRAG_HEX_STRING); + mAesGcmFragUnencryptedData = + TestUtils.hexStringToByteArray(AES_GCM_FRAG_DECRYPTED_BODY_HEX_STRING); + } + + @Test + public void testValidateChecksum() throws Exception { + IkeEncryptedPayloadBody.validateInboundChecksumOrThrow( + mDataToAuthenticate, mHmacSha1IntegrityMac, mHmacSha1IntegrityKey, mChecksum); + } + + @Test + public void testThrowForInvalidChecksum() throws Exception { + byte[] dataToAuthenticate = Arrays.copyOf(mDataToAuthenticate, mDataToAuthenticate.length); + dataToAuthenticate[0]++; + + try { + IkeEncryptedPayloadBody.validateInboundChecksumOrThrow( + dataToAuthenticate, mHmacSha1IntegrityMac, mHmacSha1IntegrityKey, mChecksum); + fail("Expected GeneralSecurityException due to mismatched checksum."); + } catch (GeneralSecurityException expected) { + } + } + + @Test + public void testCalculatePaddingPlaintextShorterThanBlockSize() throws Exception { + int blockSize = 16; + int plainTextLength = 15; + int expectedPadLength = 0; + + byte[] calculatedPadding = + IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize); + assertEquals(expectedPadLength, calculatedPadding.length); + } + + @Test + public void testCalculatePaddingPlaintextInBlockSize() throws Exception { + int blockSize = 16; + int plainTextLength = 16; + int expectedPadLength = 15; + + byte[] calculatedPadding = + IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize); + assertEquals(expectedPadLength, calculatedPadding.length); + } + + @Test + public void testCalculatePaddingPlaintextLongerThanBlockSize() throws Exception { + int blockSize = 16; + int plainTextLength = 17; + int expectedPadLength = 14; + + byte[] calculatedPadding = + IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize); + assertEquals(expectedPadLength, calculatedPadding.length); + } + + @Test + public void testEncrypt() throws Exception { + byte[] calculatedData = + IkeEncryptedPayloadBody.normalModeEncrypt( + mDataToPadAndEncrypt, mAesCbcCipher, mAesCbcKey, mIv, mPadding); + + assertArrayEquals(mEncryptedPaddedData, calculatedData); + } + + @Test + public void testDecrypt() throws Exception { + byte[] calculatedPlainText = + IkeEncryptedPayloadBody.normalModeDecrypt( + mEncryptedPaddedData, mAesCbcCipher, mAesCbcKey, mIv); + + assertArrayEquals(mDataToPadAndEncrypt, calculatedPlainText); + } + + @Test + public void testBuildAndEncodeOutboundIkeEncryptedPayloadBody() throws Exception { + IkeHeader ikeHeader = new IkeHeader(mIkeMessage); + + IkeEncryptedPayloadBody payloadBody = + new IkeEncryptedPayloadBody( + ikeHeader, + IkePayload.PAYLOAD_TYPE_ID_INITIATOR, + new byte[0] /*skfHeader*/, + mDataToPadAndEncrypt, + mHmacSha1IntegrityMac, + mAesCbcCipher, + mHmacSha1IntegrityKey, + mAesCbcKey, + mIv, + mPadding); + + byte[] expectedEncodedData = + TestUtils.hexStringToByteArray( + IKE_AUTH_INIT_REQUEST_IV + + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA + + IKE_AUTH_INIT_REQUEST_CHECKSUM); + assertArrayEquals(expectedEncodedData, payloadBody.encode()); + } + + @Test + public void testAuthAndDecodeHmacSha1AesCbc() throws Exception { + IkeEncryptedPayloadBody payloadBody = + new IkeEncryptedPayloadBody( + mIkeMessage, + ENCRYPTED_BODY_SK_OFFSET, + mHmacSha1IntegrityMac, + mAesCbcCipher, + mHmacSha1IntegrityKey, + mAesCbcKey); + + assertArrayEquals(mDataToPadAndEncrypt, payloadBody.getUnencryptedData()); + } + + @Test + public void testAuthAndDecodeHmacSha13Des() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_MSG_HEX_STRING); + byte[] expectedDecryptedData = + TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_DECRYPTED_BODY_HEX_STRING); + + IkeEncryptedPayloadBody payloadBody = + new IkeEncryptedPayloadBody( + message, + ENCRYPTED_BODY_SK_OFFSET, + mHmacSha1IntegrityMac, + m3DesCipher, + TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_MSG_INTE_KEY), + TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_MSG_ENCR_KEY)); + + assertArrayEquals(expectedDecryptedData, payloadBody.getUnencryptedData()); + } + + @Test + public void testBuildAndEncodeWithHmacSha13Des() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_HEX_STRING); + IkeHeader ikeHeader = new IkeHeader(message); + + byte[] skfHeaderBytes = + IkeSkfPayload.encodeSkfHeader( + HMAC_SHA1_3DES_FRAGMENT_NUM, HMAC_SHA1_3DES_TOTAL_FRAGMENTS); + + IkeEncryptedPayloadBody payloadBody = + new IkeEncryptedPayloadBody( + ikeHeader, + IkePayload.PAYLOAD_TYPE_NO_NEXT, + skfHeaderBytes, + TestUtils.hexStringToByteArray( + HMAC_SHA1_3DES_FRAG_DECRYPTED_BODY_HEX_STRING), + mHmacSha1IntegrityMac, + m3DesCipher, + TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_INTE_KEY), + TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_ENCR_KEY), + TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_IV), + TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_PADDING)); + + byte[] expectedEncodedData = + Arrays.copyOfRange(message, ENCRYPTED_BODY_SKF_OFFSET, message.length); + + assertArrayEquals(expectedEncodedData, payloadBody.encode()); + } + + @Test + public void testAuthAndDecodeFullMsgWithAesGcm() throws Exception { + IkeEncryptedPayloadBody encryptedBody = + new IkeEncryptedPayloadBody( + mAesGcmMsg, + ENCRYPTED_BODY_SK_OFFSET, + null /*integrityMac*/, + mAesGcm16Cipher, + null /*integrityKey*/, + mAesGcmMsgKey); + + assertArrayEquals(mAesGcmUnencryptedData, encryptedBody.getUnencryptedData()); + } + + @Test + public void testBuildAndEncodeMsgWithAesGcm() throws Exception { + IkeHeader ikeHeader = new IkeHeader(mAesGcmMsg); + + IkeEncryptedPayloadBody payloadBody = + new IkeEncryptedPayloadBody( + ikeHeader, + IkePayload.PAYLOAD_TYPE_AUTH, + new byte[0], + mAesGcmUnencryptedData, + null /*integrityMac*/, + mAesGcm16Cipher, + null /*integrityKey*/, + mAesGcmMsgKey, + TestUtils.hexStringToByteArray(AES_GCM_MSG_IV), + new byte[0] /*padding*/); + + byte[] expectedEncodedData = + Arrays.copyOfRange(mAesGcmMsg, ENCRYPTED_BODY_SK_OFFSET, mAesGcmMsg.length); + + assertArrayEquals(expectedEncodedData, payloadBody.encode()); + } + + @Test + public void testAuthAndDecodeFragMsgWithAesGcm() throws Exception { + IkeEncryptedPayloadBody encryptedBody = + new IkeEncryptedPayloadBody( + mAesGcmFragMsg, + ENCRYPTED_BODY_SKF_OFFSET, + null /*integrityMac*/, + mAesGcm16Cipher, + null /*integrityKey*/, + mAesGcmFragKey); + + assertArrayEquals(mAesGcmFragUnencryptedData, encryptedBody.getUnencryptedData()); + } + + @Test + public void testBuildAndEncodeFragMsgWithAesGcm() throws Exception { + IkeHeader ikeHeader = new IkeHeader(mAesGcmFragMsg); + byte[] skfHeaderBytes = + IkeSkfPayload.encodeSkfHeader(AES_GCM_FRAGMENT_NUM, AES_GCM_TOTAL_FRAGMENTS); + + IkeEncryptedPayloadBody payloadBody = + new IkeEncryptedPayloadBody( + ikeHeader, + IkePayload.PAYLOAD_TYPE_NO_NEXT, + skfHeaderBytes, + mAesGcmFragUnencryptedData, + null /*integrityMac*/, + mAesGcm16Cipher, + null /*integrityKey*/, + mAesGcmFragKey, + TestUtils.hexStringToByteArray(AES_GCM_FRAG_IV), + new byte[0] /*padding*/); + + byte[] expectedEncodedData = + Arrays.copyOfRange( + mAesGcmFragMsg, ENCRYPTED_BODY_SKF_OFFSET, mAesGcmFragMsg.length); + + assertArrayEquals(expectedEncodedData, payloadBody.encode()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeHeaderTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeHeaderTest.java new file mode 100644 index 00000000..4592815d --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeHeaderTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.exceptions.InvalidMajorVersionException; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; + +import org.junit.Test; + +import java.nio.ByteBuffer; + +public final class IkeHeaderTest { + private static final String IKE_HEADER_HEX_STRING = + "8f54bf6d8b48e6e10000000000000000212022080000000000000150"; + private static final String IKE_SA_INIT_RAW_PACKET = + "8f54bf6d8b48e6e100000000000000002120220800000000" + + "00000150220000300000002c010100040300000c0100000c" + + "800e00800300000803000002030000080400000200000008" + + "020000022800008800020000b4a2faf4bb54878ae21d6385" + + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656" + + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37" + + "534036040610ebdd92f46bef84f0be7db860351843858f8a" + + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb" + + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024" + + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7" + + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb" + + "881eab2051d8663f421d10b02b00001c00004005d915368c" + + "a036004cb578ae3e3fb268509aeab1900000002069936922" + + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500"; + + private static final String IKE_INITIATOR_SPI = "8f54bf6d8b48e6e1"; + private static final String IKE_RESPODNER_SPI = "0000000000000000"; + + @IkePayload.PayloadType + private static final byte IKE_FIRST_PAYLOAD_TYPE = IkePayload.PAYLOAD_TYPE_SA; + + private static final byte IKE_MAJOR_VERSION = 2; + private static final byte IKE_MINOR_VERSION = 0; + + @IkeHeader.ExchangeType + private static final int IKE_EXCHANGE_TYPE = IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT; + + private static final int IKE_MSG_ID = 0; + private static final int IKE_MSG_LENGTH = 336; + private static final int IKE_MSG_BODY_LENGTH = IKE_MSG_LENGTH - IkeHeader.IKE_HEADER_LENGTH; + + // Byte offsets of version field in IKE message header. + private static final int VERSION_OFFSET = 17; + // Byte offsets of exchange type in IKE message header. + private static final int EXCHANGE_TYPE_OFFSET = 18; + // Byte offsets of message length in IKE message header. + private static final int MESSAGE_LENGTH_OFFSET = 24; + + @Test + public void testDecodeIkeHeader() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + IkeHeader header = new IkeHeader(inputPacket); + + assertEquals(IKE_MSG_LENGTH, inputPacket.length); + + long initSpi = Long.parseUnsignedLong(IKE_INITIATOR_SPI, 16); + assertEquals(initSpi, header.ikeInitiatorSpi); + long respSpi = Long.parseUnsignedLong(IKE_RESPODNER_SPI, 16); + assertEquals(respSpi, header.ikeResponderSpi); + + assertEquals(IKE_FIRST_PAYLOAD_TYPE, header.nextPayloadType); + assertEquals(IKE_MAJOR_VERSION, header.majorVersion); + assertEquals(IKE_MINOR_VERSION, header.minorVersion); + assertEquals(IKE_EXCHANGE_TYPE, header.exchangeType); + assertFalse(header.isResponseMsg); + assertTrue(header.fromIkeInitiator); + assertEquals(IKE_MSG_ID, header.messageId); + assertEquals(IKE_MSG_LENGTH, header.getInboundMessageLength()); + } + + @Test + public void testDecodeIkeHeaderWithInvalidMajorVersion() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + // Set major version 3. + inputPacket[VERSION_OFFSET] = (byte) 0x30; + // Set Exchange type 0 + inputPacket[EXCHANGE_TYPE_OFFSET] = (byte) 0x00; + + InvalidMajorVersionException exception = + IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg( + inputPacket, InvalidMajorVersionException.class); + + assertEquals(3, exception.getMajorVerion()); + } + + @Test + public void testDecodeIkeHeaderWithInvalidExchangeType() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + // Set Exchange type 0 + inputPacket[EXCHANGE_TYPE_OFFSET] = (byte) 0x00; + + IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class); + } + + @Test + public void testDecodeIkeHeaderWithInvalidPacketLength() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + // Set Exchange type 0 + inputPacket[MESSAGE_LENGTH_OFFSET] = (byte) 0x01; + + IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class); + } + + @Test + public void testEncodeIkeHeader() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + IkeHeader header = new IkeHeader(inputPacket); + + ByteBuffer byteBuffer = ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH); + header.encodeToByteBuffer(byteBuffer, IKE_MSG_BODY_LENGTH); + + byte[] expectedPacket = TestUtils.hexStringToByteArray(IKE_HEADER_HEX_STRING); + assertArrayEquals(expectedPacket, byteBuffer.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeIdPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeIdPayloadTest.java new file mode 100644 index 00000000..75552b31 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeIdPayloadTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeIdentification; +import android.net.ipsec.ike.IkeIpv4AddrIdentification; +import android.net.ipsec.ike.IkeIpv6AddrIdentification; +import android.net.ipsec.ike.IkeKeyIdIdentification; +import android.net.ipsec.ike.IkeRfc822AddrIdentification; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; + +import org.junit.Test; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.ByteBuffer; + +public final class IkeIdPayloadTest { + + private static final String IPV4_ADDR_ID_PAYLOAD_RESPONDER_HEX_STRING = + "2700000c01000000c0000264"; + private static final String IPV4_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING = "01000000c0000264"; + private static final String IPV4_ADDR_STRING = "192.0.2.100"; + + private static final String IPV6_ADDR_ID_PAYLOAD_RESPONDER_HEX_STRING = + "27000018050000000000200100000db80000000000000001"; + private static final String IPV6_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING = + "050000000000200100000db80000000000000001"; + private static final String IPV6_ADDR_STRING = "0:2001:0:db8::1"; + + private static final String FQDN_ID_PAYLOAD_HEX_STRING = + "2500001702000000696B652E616E64726F69642E6E6574"; + private static final String FQDN_ID_PAYLOAD_BODY_HEX_STRING = + "02000000696B652E616E64726F69642E6E6574"; + private static final String FQDN = "ike.android.net"; + + private static final String RFC822_ADDR_ID_PAYLOAD_HEX_STRING = + "2500001e03000000616e64726f6964696b65406578616d706c652e636f6d"; + private static final String RFC822_ADDR_ID_PAYLOAD_BODY_HEX_STRING = + "03000000616e64726f6964696b65406578616d706c652e636f6d"; + private static final String RFC822_NAME = "androidike@example.com"; + + private static final String KEY_ID_PAYLOAD_HEX_STRING = + "250000170b000000616E64726F6964496B654B65794964"; + private static final String KEY_ID_PAYLOAD_BODY_HEX_STRING = + "0b000000616E64726F6964496B654B65794964"; + private static final byte[] KEY_ID = "androidIkeKeyId".getBytes(); + + private static final int ID_TYPE_OFFSET = 0; + + @Test + public void testDecodeIpv4AddrIdPayload() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(IPV4_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING); + IkeIdPayload payload = new IkeIdPayload(false, inputPacket, false); + + assertEquals(IkePayload.PAYLOAD_TYPE_ID_RESPONDER, payload.payloadType); + assertEquals(IkeIdentification.ID_TYPE_IPV4_ADDR, payload.ikeId.idType); + IkeIpv4AddrIdentification ikeId = (IkeIpv4AddrIdentification) payload.ikeId; + Inet4Address expectedAddr = (Inet4Address) Inet4Address.getByName(IPV4_ADDR_STRING); + assertEquals(expectedAddr, ikeId.ipv4Address); + } + + @Test + public void testDecodeIpv6AddrIdPayload() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(IPV6_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING); + IkeIdPayload payload = new IkeIdPayload(false, inputPacket, false); + + assertEquals(IkePayload.PAYLOAD_TYPE_ID_RESPONDER, payload.payloadType); + assertEquals(IkeIdentification.ID_TYPE_IPV6_ADDR, payload.ikeId.idType); + IkeIpv6AddrIdentification ikeId = (IkeIpv6AddrIdentification) payload.ikeId; + Inet6Address expectedAddr = (Inet6Address) Inet6Address.getByName(IPV6_ADDR_STRING); + assertEquals(expectedAddr, ikeId.ipv6Address); + } + + @Test + public void testDecodeFqdnIdPayload() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(FQDN_ID_PAYLOAD_BODY_HEX_STRING); + IkeIdPayload payload = + new IkeIdPayload(false /*critical*/, inputPacket, false /*isInitiator*/); + + assertEquals(IkePayload.PAYLOAD_TYPE_ID_RESPONDER, payload.payloadType); + assertArrayEquals(inputPacket, payload.getEncodedPayloadBody()); + assertEquals(IkeIdentification.ID_TYPE_FQDN, payload.ikeId.idType); + IkeFqdnIdentification ikeId = (IkeFqdnIdentification) payload.ikeId; + assertEquals(FQDN, ikeId.fqdn); + } + + @Test + public void testDecodeRfc822AddrIdPayload() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(RFC822_ADDR_ID_PAYLOAD_BODY_HEX_STRING); + IkeIdPayload payload = + new IkeIdPayload(false /*critical*/, inputPacket, true /*isInitiator*/); + + assertEquals(IkePayload.PAYLOAD_TYPE_ID_INITIATOR, payload.payloadType); + assertEquals(IkeIdentification.ID_TYPE_RFC822_ADDR, payload.ikeId.idType); + IkeRfc822AddrIdentification ikeId = (IkeRfc822AddrIdentification) payload.ikeId; + assertEquals(RFC822_NAME, ikeId.rfc822Name); + } + + @Test + public void testDecodeKeyIdPayload() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(KEY_ID_PAYLOAD_BODY_HEX_STRING); + IkeIdPayload payload = + new IkeIdPayload(false /*critical*/, inputPacket, true /*isInitiator*/); + + assertEquals(IkePayload.PAYLOAD_TYPE_ID_INITIATOR, payload.payloadType); + assertEquals(IkeIdentification.ID_TYPE_KEY_ID, payload.ikeId.idType); + IkeKeyIdIdentification ikeId = (IkeKeyIdIdentification) payload.ikeId; + assertArrayEquals(KEY_ID, ikeId.keyId); + } + + @Test + public void testDecodeUnsupportedIdType() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(IPV4_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING); + inputPacket[ID_TYPE_OFFSET] = 0; + + try { + new IkeIdPayload(false, inputPacket, true); + fail("Expected AuthenticationFailedException: ID Type is unsupported."); + } catch (AuthenticationFailedException expected) { + } + } + + @Test + public void testConstructAndEncodeIpv4AddrIdPayload() throws Exception { + Inet4Address ipv4Address = (Inet4Address) Inet4Address.getByName(IPV4_ADDR_STRING); + IkeIdPayload payload = new IkeIdPayload(false, new IkeIpv4AddrIdentification(ipv4Address)); + + ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength()); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_AUTH, inputBuffer); + + byte[] expectedBytes = + TestUtils.hexStringToByteArray(IPV4_ADDR_ID_PAYLOAD_RESPONDER_HEX_STRING); + assertArrayEquals(expectedBytes, inputBuffer.array()); + } + + @Test + public void testConstructAndEncodeIpv6AddrIdPayload() throws Exception { + Inet6Address ipv6Address = (Inet6Address) Inet6Address.getByName(IPV6_ADDR_STRING); + IkeIdPayload payload = new IkeIdPayload(false, new IkeIpv6AddrIdentification(ipv6Address)); + + ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength()); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_AUTH, inputBuffer); + + byte[] expectedBytes = + TestUtils.hexStringToByteArray(IPV6_ADDR_ID_PAYLOAD_RESPONDER_HEX_STRING); + assertArrayEquals(expectedBytes, inputBuffer.array()); + } + + @Test + public void testConstructAndEncodeFqdnIdPayload() throws Exception { + IkeIdPayload payload = + new IkeIdPayload(false /*isInitiator*/, new IkeFqdnIdentification(FQDN)); + + ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength()); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_CERT, inputBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(FQDN_ID_PAYLOAD_HEX_STRING); + assertArrayEquals(expectedBytes, inputBuffer.array()); + } + + @Test + public void testConstructAndEncodeRfc822AddrIdPayload() throws Exception { + IkeIdPayload payload = + new IkeIdPayload( + true /*isInitiator*/, new IkeRfc822AddrIdentification(RFC822_NAME)); + + ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength()); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_CERT, inputBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(RFC822_ADDR_ID_PAYLOAD_HEX_STRING); + assertArrayEquals(expectedBytes, inputBuffer.array()); + } + + @Test + public void testConstructAndEncodeKeyIdPayload() throws Exception { + IkeIdPayload payload = + new IkeIdPayload(true /*isInitiator*/, new IkeKeyIdIdentification(KEY_ID)); + + ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength()); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_CERT, inputBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(KEY_ID_PAYLOAD_HEX_STRING); + assertArrayEquals(expectedBytes, inputBuffer.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayloadTest.java new file mode 100644 index 00000000..f5046dca --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayloadTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.IkeDhParams; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; +import com.android.internal.net.utils.BigIntegerUtils; + +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.util.Arrays; + +import javax.crypto.spec.DHPrivateKeySpec; + +public final class IkeKePayloadTest { + private static final String KE_PAYLOAD_GENERIC_HEADER = "28000088"; + private static final String KE_PAYLOAD_RAW_PACKET = + "00020000b4a2faf4bb54878ae21d638512ece55d9236fc50" + + "46ab6cef82220f421f3ce6361faf36564ecb6d28798a94aa" + + "d7b2b4b603ddeaaa5630adb9ece8ac37534036040610ebdd" + + "92f46bef84f0be7db860351843858f8acf87056e272377f7" + + "0c9f2d81e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2" + + "6bbeb08214c7071376079587"; + + private static final boolean CRITICAL_BIT = false; + + @IkePayload.PayloadType + private static final int NEXT_PAYLOAD_TYPE = IkePayload.PAYLOAD_TYPE_NONCE; + + private static final int EXPECTED_DH_GROUP = SaProposal.DH_GROUP_1024_BIT_MODP; + + private static final int EXPECTED_KE_DATA_LEN = 128; + + private static final String KEY_EXCHANGE_DATA_RAW_PACKET = + "b4a2faf4bb54878ae21d638512ece55d9236fc5046ab6cef" + + "82220f421f3ce6361faf36564ecb6d28798a94aad7b2b4b6" + + "03ddeaaa5630adb9ece8ac37534036040610ebdd92f46bef" + + "84f0be7db860351843858f8acf87056e272377f70c9f2d81" + + "e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c26bbeb082" + + "14c7071376079587"; + + private static final String PRIME_1024_BIT_MODP_160_SUBGROUP = + "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6" + + "9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0" + + "13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70" + + "98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0" + + "A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708" + + "DF1FB2BC2E4A4371"; + private static final String GENERATOR_1024_BIT_MODP_160_SUBGROUP = + "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F" + + "D6406CFF14266D31266FEA1E5C41564B777E690F5504F213" + + "160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1" + + "909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A" + + "D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24" + + "855E6EEB22B3B2E5"; + private static final String PRIVATE_KEY_LOCAL = "B9A3B3AE8FEFC1A2930496507086F8455D48943E"; + private static final String PUBLIC_KEY_REMOTE = + "717A6CB053371FF4A3B932941C1E5663F861A1D6AD34AE66" + + "576DFB98F6C6CBF9DDD5A56C7833F6BCFDFF095582AD868E" + + "440E8D09FD769E3CECCDC3D3B1E4CFA057776CAAF9739B6A" + + "9FEE8E7411F8D6DAC09D6A4EDB46CC2B5D5203090EAE6126" + + "311E53FD2C14B574E6A3109A3DA1BE41BDCEAA186F5CE067" + + "16A2B6A07B3C33FE"; + private static final String EXPECTED_SHARED_KEY = + "5C804F454D30D9C4DF85271F93528C91DF6B48AB5F80B3B5" + + "9CAAC1B28F8ACBA9CD3E39F3CB614525D9521D2E644C53B8" + + "07B810F340062F257D7D6FBFE8D5E8F072E9B6E9AFDA9413" + + "EAFB2E8B0699B1FB5A0CACEDDEAEAD7E9CFBB36AE2B42083" + + "5BD83A19FB0B5E96BF8FA4D09E345525167ECD9155416F46" + + "F408ED31B63C6E6D"; + private static final String KEY_EXCHANGE_ALGORITHM = "DH"; + + private DHPrivateKeySpec mPrivateKeySpec; + + @Before + public void setUp() throws Exception { + BigInteger primeValue = + BigIntegerUtils.unsignedHexStringToBigInteger(PRIME_1024_BIT_MODP_160_SUBGROUP); + BigInteger baseGenValue = + BigIntegerUtils.unsignedHexStringToBigInteger(GENERATOR_1024_BIT_MODP_160_SUBGROUP); + BigInteger privateKeyValue = + BigIntegerUtils.unsignedHexStringToBigInteger(PRIVATE_KEY_LOCAL); + mPrivateKeySpec = new DHPrivateKeySpec(privateKeyValue, primeValue, baseGenValue); + } + + @Test + public void testDecodeIkeKePayload() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(KE_PAYLOAD_RAW_PACKET); + + IkeKePayload payload = new IkeKePayload(CRITICAL_BIT, inputPacket); + + assertFalse(payload.isOutbound); + assertEquals(EXPECTED_DH_GROUP, payload.dhGroup); + + byte[] keyExchangeData = TestUtils.hexStringToByteArray(KEY_EXCHANGE_DATA_RAW_PACKET); + assertEquals(keyExchangeData.length, payload.keyExchangeData.length); + for (int i = 0; i < keyExchangeData.length; i++) { + assertEquals(keyExchangeData[i], payload.keyExchangeData[i]); + } + } + + @Test + public void testDecodeIkeKePayloadWithInvalidKeData() throws Exception { + // Cut bytes of KE data from original KE payload + String badKeyPayloadPacket = + KE_PAYLOAD_RAW_PACKET.substring(0, KE_PAYLOAD_RAW_PACKET.length() - 2); + byte[] inputPacket = TestUtils.hexStringToByteArray(badKeyPayloadPacket); + + try { + IkeKePayload payload = new IkeKePayload(CRITICAL_BIT, inputPacket); + fail("Expected InvalidSyntaxException: KE data length doesn't match its DH group type"); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testEncodeIkeKePayload() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(KE_PAYLOAD_RAW_PACKET); + IkeKePayload payload = new IkeKePayload(CRITICAL_BIT, inputPacket); + + ByteBuffer byteBuffer = ByteBuffer.allocate(payload.getPayloadLength()); + payload.encodeToByteBuffer(NEXT_PAYLOAD_TYPE, byteBuffer); + + byte[] expectedKePayload = + TestUtils.hexStringToByteArray(KE_PAYLOAD_GENERIC_HEADER + KE_PAYLOAD_RAW_PACKET); + assertArrayEquals(expectedKePayload, byteBuffer.array()); + } + + @Test + public void testGetIkeKePayload() throws Exception { + IkeKePayload payload = new IkeKePayload(SaProposal.DH_GROUP_1024_BIT_MODP); + + // Test DHPrivateKeySpec + assertTrue(payload.isOutbound); + DHPrivateKeySpec privateKeySpec = payload.localPrivateKey; + + BigInteger primeValue = privateKeySpec.getP(); + BigInteger expectedPrimeValue = new BigInteger(IkeDhParams.PRIME_1024_BIT_MODP, 16); + assertEquals(0, expectedPrimeValue.compareTo(primeValue)); + + BigInteger genValue = privateKeySpec.getG(); + BigInteger expectedGenValue = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP); + assertEquals(0, expectedGenValue.compareTo(genValue)); + + // Test IkeKePayload + assertEquals(EXPECTED_DH_GROUP, payload.dhGroup); + assertEquals(EXPECTED_KE_DATA_LEN, payload.keyExchangeData.length); + } + + // Since we didn't find test data for DH group types supported in current IKE library, we use + // test data for "1024-bit MODP Group with 160-bit Prime Order Subgroup" from RFC 5114. The main + // difference is that it uses weaker Prime and Generator values and requires more complicated + // recipient test in real Key Exchange process. But it is suitable for testing. + @Test + public void testGetSharedkey() throws Exception { + byte[] remotePublicKey = TestUtils.hexStringToByteArray(PUBLIC_KEY_REMOTE); + byte[] sharedKeyBytes = IkeKePayload.getSharedKey(mPrivateKeySpec, remotePublicKey); + + byte[] expectedSharedKeyBytes = TestUtils.hexStringToByteArray(EXPECTED_SHARED_KEY); + assertTrue(Arrays.equals(expectedSharedKeyBytes, sharedKeyBytes)); + } + + @Test + public void testGetSharedkeyWithInvalidRemoteKey() throws Exception { + byte[] remotePublicKey = TestUtils.hexStringToByteArray(PRIME_1024_BIT_MODP_160_SUBGROUP); + + try { + byte[] sharedKeyBytes = IkeKePayload.getSharedKey(mPrivateKeySpec, remotePublicKey); + fail("Expected to fail because of invalid remote public key."); + } catch (GeneralSecurityException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeMessageTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeMessageTest.java new file mode 100644 index 00000000..38b41527 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeMessageTest.java @@ -0,0 +1,935 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static com.android.internal.net.ipsec.ike.message.IkeMessage.DECODE_STATUS_OK; +import static com.android.internal.net.ipsec.ike.message.IkeMessage.DECODE_STATUS_PROTECTED_ERROR; +import static com.android.internal.net.ipsec.ike.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_AUTH; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_ID_INITIATOR; +import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NO_NEXT; +import static com.android.internal.net.ipsec.ike.message.IkeTestUtils.makeDummySkfPayload; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.ipsec.ike.exceptions.IkeException; +import android.net.ipsec.ike.exceptions.IkeInternalException; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord; +import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; +import com.android.internal.net.ipsec.ike.crypto.IkeNormalModeCipher; +import com.android.internal.net.ipsec.ike.exceptions.InvalidMessageIdException; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; +import com.android.internal.net.ipsec.ike.exceptions.UnsupportedCriticalPayloadException; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResult; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultError; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultOk; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultPartial; +import com.android.internal.net.ipsec.ike.message.IkePayloadFactory.IIkePayloadDecoder; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.LinkedList; + +import javax.crypto.IllegalBlockSizeException; + +public final class IkeMessageTest { + private static final String IKE_SA_INIT_HEADER_RAW_PACKET = + "8f54bf6d8b48e6e10000000000000000212022080000000000000150"; + private static final String IKE_SA_INIT_BODY_RAW_PACKET = + "220000300000002c010100040300000c0100000c" + + "800e00800300000803000002030000080400000200000008" + + "020000022800008800020000b4a2faf4bb54878ae21d6385" + + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656" + + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37" + + "534036040610ebdd92f46bef84f0be7db860351843858f8a" + + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb" + + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024" + + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7" + + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb" + + "881eab2051d8663f421d10b02b00001c00004005d915368c" + + "a036004cb578ae3e3fb268509aeab1900000002069936922" + + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500"; + private static final String IKE_SA_INIT_RAW_PACKET = + IKE_SA_INIT_HEADER_RAW_PACKET + IKE_SA_INIT_BODY_RAW_PACKET; + + // Byte offsets of first payload type in IKE message header. + private static final int FIRST_PAYLOAD_TYPE_OFFSET = 16; + // Byte offsets of first payload's critical bit in IKE message body. + private static final int PAYLOAD_CRITICAL_BIT_OFFSET = 1; + // Byte offsets of first payload length in IKE message body. + private static final int FIRST_PAYLOAD_LENGTH_OFFSET = 2; + // Byte offsets of last payload length in IKE message body. + private static final int LAST_PAYLOAD_LENGTH_OFFSET = 278; + + private static final String IKE_AUTH_HEADER_HEX_STRING = + "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec"; + private static final String IKE_AUTH_BODY_HEX_STRING = + "230000d0b9132b7bb9f658dfdc648e5017a6322a030c316c" + + "e55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58" + + "46de333ecd3ea2b705d18293b130395300ba92a351041345" + + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f" + + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60" + + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b" + + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423" + + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3" + + "59eb4e81ae6e0f22abdad69ba8007d50"; + + private static final String IKE_AUTH_EXPECTED_CHECKSUM_HEX_STRING = "ae6e0f22abdad69ba8007d50"; + private static final String IKE_AUTH_HEX_STRING = + IKE_AUTH_HEADER_HEX_STRING + IKE_AUTH_BODY_HEX_STRING; + + private static final String IKE_AUTH_UNENCRYPTED_PADDED_DATA_HEX_STRING = + "2400000c010000000a50500d2700000c010000000a505050" + + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327" + + "44dfb2c12c00002c00000028010304032ad4c0a20300000c" + + "0100000c800e008003000008030000020000000805000000" + + "2d00001801000000070000100000ffff00000000ffffffff" + + "2900001801000000070000100000ffff00000000ffffffff" + + "29000008000040000000000c000040010000000100000000" + + "000000000000000b"; + + private static final String IKE_FRAG_HEX_STRING = + "939ae1251d18eb9077a99551b15c6e9335202320000000010000" + + "00c0000000a400020002fd7c7931705af184b7be76bbd45a" + + "8ecbb3ffd58b9438b93f67e9fe86b06229f80e9b52d2ff6a" + + "fde3f2c13ae93ce55a801f62e1a818c9003880a36bbe986f" + + "e6979ba233b9f4f0ddc992d06dbad5a2b998be18fae947e5" + + "ccfb37775d069344e711fbf499bb289cf4cca245bd450ad8" + + "9d18689207759507ba18d47247e920b9e000a25a7596e413" + + "0929e5cdc37d5c1b0d90bbaae946c260f4d3cf815f6d"; + private static final String ID_INIT_PAYLOAD_HEX_STRING = "2400000c010000000a50500d"; + private static final String ID_RESP_PAYLOAD_HEX_STRING = "0000000c010000000a505050"; + + private static final long INIT_SPI = 0x5f54bf6d8b48e6e1L; + private static final long RESP_SPI = 0x909232b3d1edcb5cL; + private static final String IKE_EMPTY_INFO_MSG_HEX_STRING = + "5f54bf6d8b48e6e1909232b3d1edcb5c2e20252800000000" + + "0000004c00000030e376871750fdba9f7012446c5dc3f97a" + + "f83b48ba0dbc68bcc4a78136832100aa4192f251cd4d1b97" + + "d298e550"; + private static final String IKE_EMPTY_INFO_MSG_IV_HEX_STRING = + "e376871750fdba9f7012446c5dc3f97a"; + private static final String IKE_EMPTY_INFO_MSG_ENCRYPTED_DATA_HEX_STRING = + "f83b48ba0dbc68bcc4a78136832100aa"; + private static final String IKE_EMPTY_INFO_MSG_CHECKSUM_HEX_STRING = "4192f251cd4d1b97d298e550"; + + private static final byte[] FRAGMENT_ONE_UNENCRYPTED_DATA = "fragmentOne".getBytes(); + private static final byte[] FRAGMENT_TWO_UNENCRYPTED_DATA = "fragmentTwo".getBytes(); + + private static final int TOTAL_FRAGMENTS = 2; + private static final int FRAGMENT_NUM_ONE = 1; + private static final int FRAGMENT_NUM_TWO = 2; + + private static final int IKE_FRAG_EXPECTED_MESSAGE_ID = 1; + + private static final int IKE_AUTH_EXPECTED_MESSAGE_ID = 1; + private static final int IKE_AUTH_CIPHER_IV_SIZE = 16; + private static final int IKE_AUTH_CIPHER_BLOCK_SIZE = 16; + private static final int IKE_AUTH_PAYLOAD_SIZE = 8; + + private byte[] mIkeAuthPacket; + private byte[] mUnencryptedPaddedData; + private IkeHeader mIkeAuthHeader; + + private IIkePayloadDecoder mSpyIkePayloadDecoder; + + private IkeMacIntegrity mMockIntegrity; + private IkeNormalModeCipher mMockCipher; + private IkeSaRecord mMockIkeSaRecord; + + private byte[] mIkeFragPacketOne; + private byte[] mIkeFragPacketTwo; + private IkeHeader mFragOneHeader; + private IkeHeader mFragTwoHeader; + + private IkeSkfPayload mDummySkfPayloadOne; + private IkeSkfPayload mDummySkfPayloadTwo; + + private static final int[] EXPECTED_IKE_INIT_PAYLOAD_LIST = { + IkePayload.PAYLOAD_TYPE_SA, + IkePayload.PAYLOAD_TYPE_KE, + IkePayload.PAYLOAD_TYPE_NONCE, + IkePayload.PAYLOAD_TYPE_NOTIFY, + IkePayload.PAYLOAD_TYPE_NOTIFY, + IkePayload.PAYLOAD_TYPE_VENDOR + }; + + class TestIkeSupportedPayload extends IkePayload { + TestIkeSupportedPayload(int payload, boolean critical) { + super(payload, critical); + } + + @Override + protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) { + throw new UnsupportedOperationException( + "It is not supported to encode " + getTypeString()); + } + + @Override + protected int getPayloadLength() { + throw new UnsupportedOperationException( + "It is not supported to get payload length of " + getTypeString()); + } + + @Override + public String getTypeString() { + return "Test(" + payloadType + ")"; + } + } + + @Before + public void setUp() throws Exception { + mSpyIkePayloadDecoder = spy(new IkePayloadFactory.IkePayloadDecoder()); + doAnswer( + (invocation) -> { + int payloadType = (int) invocation.getArguments()[0]; + boolean isCritical = (boolean) invocation.getArguments()[1]; + if (support(payloadType)) { + return new TestIkeSupportedPayload(payloadType, isCritical); + } + return new IkeUnsupportedPayload(payloadType, isCritical); + }) + .when(mSpyIkePayloadDecoder) + .decodeIkePayload(anyInt(), anyBoolean(), anyBoolean(), any()); + + IkePayloadFactory.sDecoderInstance = mSpyIkePayloadDecoder; + + mIkeAuthPacket = TestUtils.hexStringToByteArray(IKE_AUTH_HEX_STRING); + mUnencryptedPaddedData = + TestUtils.hexStringToByteArray(IKE_AUTH_UNENCRYPTED_PADDED_DATA_HEX_STRING); + mIkeAuthHeader = new IkeHeader(mIkeAuthPacket); + + mMockIntegrity = mock(IkeMacIntegrity.class); + byte[] expectedChecksum = + TestUtils.hexStringToByteArray(IKE_AUTH_EXPECTED_CHECKSUM_HEX_STRING); + when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(expectedChecksum); + when(mMockIntegrity.getChecksumLen()).thenReturn(expectedChecksum.length); + + mMockCipher = mock(IkeNormalModeCipher.class); + when(mMockCipher.getIvLen()).thenReturn(IKE_AUTH_CIPHER_IV_SIZE); + when(mMockCipher.getBlockSize()).thenReturn(IKE_AUTH_CIPHER_BLOCK_SIZE); + when(mMockCipher.decrypt(any(), any(), any())).thenReturn(mUnencryptedPaddedData); + + mMockIkeSaRecord = mock(IkeSaRecord.class); + when(mMockIkeSaRecord.getInboundDecryptionKey()).thenReturn(new byte[0]); + when(mMockIkeSaRecord.getInboundIntegrityKey()).thenReturn(new byte[0]); + + mIkeFragPacketOne = makeFragmentBytes(1 /*fragNum*/, 2 /*totalFragments*/); + mIkeFragPacketTwo = makeFragmentBytes(2 /*fragNum*/, 2 /*totalFragments*/); + + mFragOneHeader = new IkeHeader(mIkeFragPacketOne); + mFragTwoHeader = new IkeHeader(mIkeFragPacketTwo); + + mDummySkfPayloadOne = + makeDummySkfPayload( + FRAGMENT_ONE_UNENCRYPTED_DATA, FRAGMENT_NUM_ONE, TOTAL_FRAGMENTS); + mDummySkfPayloadTwo = + makeDummySkfPayload( + FRAGMENT_TWO_UNENCRYPTED_DATA, FRAGMENT_NUM_TWO, TOTAL_FRAGMENTS); + } + + private byte[] makeFragmentBytes(int fragNum, int totalFragments) { + byte[] packet = TestUtils.hexStringToByteArray(IKE_FRAG_HEX_STRING); + ByteBuffer byteBuffer = ByteBuffer.wrap(packet); + byteBuffer.get(new byte[IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH]); + + byteBuffer.putShort((short) fragNum).putShort((short) totalFragments); + return byteBuffer.array(); + } + + @After + public void tearDown() { + IkePayloadFactory.sDecoderInstance = new IkePayloadFactory.IkePayloadDecoder(); + } + + private IkeMessage verifyDecodeResultOkAndGetMessage( + DecodeResult decodeResult, byte[] firstPacket) throws Exception { + assertEquals(DECODE_STATUS_OK, decodeResult.status); + + DecodeResultOk resultOk = (DecodeResultOk) decodeResult; + assertNotNull(resultOk.ikeMessage); + assertArrayEquals(firstPacket, resultOk.firstPacket); + + return resultOk.ikeMessage; + } + + private IkeException verifyDecodeResultErrorAndGetIkeException( + DecodeResult decodeResult, int decodeStatus, byte[] firstPacket) throws Exception { + assertEquals(decodeStatus, decodeResult.status); + + DecodeResultError resultError = (DecodeResultError) decodeResult; + assertNotNull(resultError.ikeException); + + return resultError.ikeException; + } + + @Test + public void testDecodeIkeMessage() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + IkeHeader header = new IkeHeader(inputPacket); + + DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket); + + IkeMessage message = verifyDecodeResultOkAndGetMessage(decodeResult, inputPacket); + + assertEquals(EXPECTED_IKE_INIT_PAYLOAD_LIST.length, message.ikePayloadList.size()); + for (int i = 0; i < EXPECTED_IKE_INIT_PAYLOAD_LIST.length; i++) { + assertEquals( + EXPECTED_IKE_INIT_PAYLOAD_LIST[i], message.ikePayloadList.get(i).payloadType); + } + } + + @Test + public void testDecodeMessageWithUnsupportedUncriticalPayload() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + // Set first payload unsupported uncritical + inputPacket[FIRST_PAYLOAD_TYPE_OFFSET] = (byte) 0xff; + IkeHeader header = new IkeHeader(inputPacket); + + DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket); + + IkeMessage message = verifyDecodeResultOkAndGetMessage(decodeResult, inputPacket); + + assertEquals(EXPECTED_IKE_INIT_PAYLOAD_LIST.length - 1, message.ikePayloadList.size()); + for (int i = 0; i < EXPECTED_IKE_INIT_PAYLOAD_LIST.length - 1; i++) { + assertEquals( + EXPECTED_IKE_INIT_PAYLOAD_LIST[i + 1], + message.ikePayloadList.get(i).payloadType); + } + } + + @Test + public void testThrowUnsupportedCriticalPayloadException() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + // Set first payload unsupported critical + inputPacket[FIRST_PAYLOAD_TYPE_OFFSET] = (byte) 0xff; + inputPacket[IkeHeader.IKE_HEADER_LENGTH + PAYLOAD_CRITICAL_BIT_OFFSET] = (byte) 0x80; + + UnsupportedCriticalPayloadException exception = + IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg( + inputPacket, UnsupportedCriticalPayloadException.class); + + assertEquals(1, exception.payloadTypeList.size()); + } + + @Test + public void testDecodeMessageWithTooShortPayloadLength() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + // Set first payload length to 0 + inputPacket[IkeHeader.IKE_HEADER_LENGTH + FIRST_PAYLOAD_LENGTH_OFFSET] = (byte) 0; + inputPacket[IkeHeader.IKE_HEADER_LENGTH + FIRST_PAYLOAD_LENGTH_OFFSET + 1] = (byte) 0; + + IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class); + } + + @Test + public void testDecodeMessageWithTooLongPayloadLength() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + // Increase last payload length by one byte + inputPacket[IkeHeader.IKE_HEADER_LENGTH + LAST_PAYLOAD_LENGTH_OFFSET]++; + + IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class); + } + + @Test + public void testDecodeMessageWithUnexpectedBytesInTheEnd() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET + "0000"); + + IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class); + } + + @Test + public void testDecodeEncryptedMessage() throws Exception { + DecodeResult decodeResult = + IkeMessage.decode( + IKE_AUTH_EXPECTED_MESSAGE_ID, + mMockIntegrity, + mMockCipher, + mMockIkeSaRecord, + mIkeAuthHeader, + mIkeAuthPacket, + null /*collectedFragments*/); + IkeMessage ikeMessage = verifyDecodeResultOkAndGetMessage(decodeResult, mIkeAuthPacket); + + assertEquals(IKE_AUTH_PAYLOAD_SIZE, ikeMessage.ikePayloadList.size()); + } + + @Test + public void testDecodeEncryptedMessageWithWrongId() throws Exception { + DecodeResult decodeResult = + IkeMessage.decode( + 2, + mMockIntegrity, + mMockCipher, + mMockIkeSaRecord, + mIkeAuthHeader, + mIkeAuthPacket, + null /*collectedFragments*/); + IkeException ikeException = + verifyDecodeResultErrorAndGetIkeException( + decodeResult, DECODE_STATUS_UNPROTECTED_ERROR, mIkeAuthPacket); + + assertTrue(ikeException instanceof InvalidMessageIdException); + } + + @Test + public void testDecodeEncryptedMessageWithWrongChecksum() throws Exception { + when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(new byte[0]); + + DecodeResult decodeResult = + IkeMessage.decode( + IKE_AUTH_EXPECTED_MESSAGE_ID, + mMockIntegrity, + mMockCipher, + mMockIkeSaRecord, + mIkeAuthHeader, + mIkeAuthPacket, + null /*collectedFragments*/); + IkeException ikeException = + verifyDecodeResultErrorAndGetIkeException( + decodeResult, DECODE_STATUS_UNPROTECTED_ERROR, mIkeAuthPacket); + + assertTrue( + ((IkeInternalException) ikeException).getCause() + instanceof GeneralSecurityException); + } + + @Test + public void testDecryptFail() throws Exception { + when(mMockCipher.decrypt(any(), any(), any())).thenThrow(IllegalBlockSizeException.class); + + DecodeResult decodeResult = + IkeMessage.decode( + IKE_AUTH_EXPECTED_MESSAGE_ID, + mMockIntegrity, + mMockCipher, + mMockIkeSaRecord, + mIkeAuthHeader, + mIkeAuthPacket, + null /*collectedFragments*/); + + IkeException ikeException = + verifyDecodeResultErrorAndGetIkeException( + decodeResult, DECODE_STATUS_UNPROTECTED_ERROR, mIkeAuthPacket); + assertTrue( + ((IkeInternalException) ikeException).getCause() + instanceof IllegalBlockSizeException); + } + + @Test + public void testParsingErrorInEncryptedMessage() throws Exception { + // Set first payload length to 0 + byte[] decryptedData = + Arrays.copyOfRange(mUnencryptedPaddedData, 0, mUnencryptedPaddedData.length); + decryptedData[FIRST_PAYLOAD_LENGTH_OFFSET] = (byte) 0; + decryptedData[FIRST_PAYLOAD_LENGTH_OFFSET + 1] = (byte) 0; + when(mMockCipher.decrypt(any(), any(), any())).thenReturn(decryptedData); + + DecodeResult decodeResult = + IkeMessage.decode( + IKE_AUTH_EXPECTED_MESSAGE_ID, + mMockIntegrity, + mMockCipher, + mMockIkeSaRecord, + mIkeAuthHeader, + mIkeAuthPacket, + null /*collectedFragments*/); + IkeException ikeException = + verifyDecodeResultErrorAndGetIkeException( + decodeResult, DECODE_STATUS_PROTECTED_ERROR, mIkeAuthPacket); + + assertTrue(ikeException instanceof InvalidSyntaxException); + } + + private boolean support(int payloadType) { + // Supports all payload typs from 33 to 46 + return (payloadType >= 33 && payloadType <= 46); + } + + @Test + public void testAttachEncodedHeader() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET); + byte[] ikeBodyBytes = TestUtils.hexStringToByteArray(IKE_SA_INIT_BODY_RAW_PACKET); + IkeHeader header = new IkeHeader(inputPacket); + IkeMessage message = + ((DecodeResultOk) IkeMessage.decode(0, header, inputPacket)).ikeMessage; + + byte[] encodedIkeMessage = message.attachEncodedHeader(ikeBodyBytes); + assertArrayEquals(inputPacket, encodedIkeMessage); + } + + @Test + public void testEncodeAndEncryptEmptyMsg() throws Exception { + when(mMockCipher.generateIv()) + .thenReturn(TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_IV_HEX_STRING)); + when(mMockCipher.encrypt(any(), any(), any())) + .thenReturn( + TestUtils.hexStringToByteArray( + IKE_EMPTY_INFO_MSG_ENCRYPTED_DATA_HEX_STRING)); + + byte[] checkSum = TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_CHECKSUM_HEX_STRING); + when(mMockIntegrity.getChecksumLen()).thenReturn(checkSum.length); + when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(checkSum); + + IkeHeader ikeHeader = + new IkeHeader( + INIT_SPI, + RESP_SPI, + IkePayload.PAYLOAD_TYPE_SK, + IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, + true /*isResp*/, + true /*fromInit*/, + 0); + IkeMessage ikeMessage = new IkeMessage(ikeHeader, new LinkedList<>()); + + byte[][] ikeMessageBytes = + ikeMessage.encryptAndEncode( + mMockIntegrity, + mMockCipher, + mMockIkeSaRecord, + true /*supportFragment*/, + 1280 /*fragSize*/); + byte[][] expectedBytes = + new byte[][] {TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_HEX_STRING)}; + + assertArrayEquals(expectedBytes, ikeMessageBytes); + } + + private DecodeResultPartial makeDecodeResultForFragOne(DecodeResultPartial collectedFrags) { + return new DecodeResultPartial( + mFragOneHeader, + mIkeFragPacketOne, + mDummySkfPayloadOne, + PAYLOAD_TYPE_AUTH, + collectedFrags); + } + + private DecodeResultPartial makeDecodeResultForFragTwo(DecodeResultPartial collectedFrags) { + return new DecodeResultPartial( + mFragTwoHeader, + mIkeFragPacketTwo, + mDummySkfPayloadTwo, + PAYLOAD_TYPE_NO_NEXT, + collectedFrags); + } + + @Test + public void testConstructDecodePartialFirstFragArriveFirst() throws Exception { + DecodeResultPartial resultPartial = makeDecodeResultForFragOne(null /*collectedFragments*/); + + assertEquals(PAYLOAD_TYPE_AUTH, resultPartial.firstPayloadType); + assertArrayEquals(mIkeFragPacketOne, resultPartial.firstFragBytes); + assertEquals(mFragOneHeader, resultPartial.ikeHeader); + + assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length); + assertArrayEquals( + FRAGMENT_ONE_UNENCRYPTED_DATA, + resultPartial.collectedFragsList[FRAGMENT_NUM_ONE - 1]); + assertFalse(resultPartial.isAllFragmentsReceived()); + } + + @Test + public void testConstructDecodePartialSecondFragArriveFirst() throws Exception { + DecodeResultPartial resultPartial = makeDecodeResultForFragTwo(null /*collectedFragments*/); + + assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType); + assertNull(resultPartial.firstFragBytes); + assertEquals(mFragTwoHeader, resultPartial.ikeHeader); + + assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length); + assertArrayEquals( + FRAGMENT_TWO_UNENCRYPTED_DATA, + resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]); + assertFalse(resultPartial.isAllFragmentsReceived()); + } + + @Test + public void testConstructDecodeResultPartialWithCollectedFrags() throws Exception { + DecodeResultPartial resultPartialIncomplete = + makeDecodeResultForFragTwo(null /*collectedFragments*/); + DecodeResultPartial resultPartialComplete = + makeDecodeResultForFragOne(resultPartialIncomplete); + + assertEquals(PAYLOAD_TYPE_AUTH, resultPartialComplete.firstPayloadType); + assertArrayEquals(mIkeFragPacketOne, resultPartialComplete.firstFragBytes); + assertEquals(mFragTwoHeader, resultPartialComplete.ikeHeader); + + assertEquals(TOTAL_FRAGMENTS, resultPartialComplete.collectedFragsList.length); + assertTrue(resultPartialComplete.isAllFragmentsReceived()); + } + + @Test + public void testReassembleAllFrags() throws Exception { + DecodeResultPartial resultPartialIncomplete = + makeDecodeResultForFragOne(null /*collectedFragments*/); + DecodeResultPartial resultPartialComplete = + makeDecodeResultForFragTwo(resultPartialIncomplete); + + assertEquals(PAYLOAD_TYPE_AUTH, resultPartialIncomplete.firstPayloadType); + assertArrayEquals(mIkeFragPacketOne, resultPartialIncomplete.firstFragBytes); + assertEquals(mFragOneHeader, resultPartialIncomplete.ikeHeader); + + assertEquals(TOTAL_FRAGMENTS, resultPartialIncomplete.collectedFragsList.length); + assertTrue(resultPartialIncomplete.isAllFragmentsReceived()); + + // Verify reassembly result + ByteBuffer expectedBuffer = + ByteBuffer.allocate( + FRAGMENT_ONE_UNENCRYPTED_DATA.length + + FRAGMENT_TWO_UNENCRYPTED_DATA.length); + expectedBuffer.put(FRAGMENT_ONE_UNENCRYPTED_DATA).put(FRAGMENT_TWO_UNENCRYPTED_DATA); + + byte[] reassembledBytes = resultPartialComplete.reassembleAllFrags(); + assertArrayEquals(expectedBuffer.array(), reassembledBytes); + } + + @Test + public void testReassembleIncompleteFragmentsThrows() throws Exception { + DecodeResultPartial resultPartial = makeDecodeResultForFragTwo(null /*collectedFragments*/); + + assertFalse(resultPartial.isAllFragmentsReceived()); + + try { + resultPartial.reassembleAllFrags(); + fail("Expected to fail because reassembly is not done"); + } catch (IllegalStateException expected) { + + } + } + + private void setDecryptSkfPayload(IkeSkfPayload skf) throws Exception { + doReturn(skf) + .when(mSpyIkePayloadDecoder) + .decodeIkeSkPayload( + eq(true), + anyBoolean(), + any(), + eq(mMockIntegrity), + eq(mMockCipher), + any(), + any()); + } + + private DecodeResult decodeSkf( + int expectedMsgId, + IkeHeader header, + byte[] packet, + DecodeResultPartial collectFragments) + throws Exception { + return IkeMessage.decode( + expectedMsgId, + mMockIntegrity, + mMockCipher, + mMockIkeSaRecord, + header, + packet, + collectFragments); + } + + @Test + public void testRcvFirstArrivedFrag() throws Exception { + setDecryptSkfPayload(mDummySkfPayloadTwo); + DecodeResult decodeResult = + decodeSkf( + IKE_FRAG_EXPECTED_MESSAGE_ID, + mFragTwoHeader, + mIkeFragPacketTwo, + null /* collectedFragments*/); + + // Verify decoding result + assertTrue(decodeResult instanceof DecodeResultPartial); + DecodeResultPartial resultPartial = (DecodeResultPartial) decodeResult; + + assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType); + assertNull(resultPartial.firstFragBytes); + assertEquals(mFragTwoHeader, resultPartial.ikeHeader); + + assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length); + assertArrayEquals( + FRAGMENT_TWO_UNENCRYPTED_DATA, + resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]); + assertFalse(resultPartial.isAllFragmentsReceived()); + } + + @Test + public void testRcvLastArrivedFrag() throws Exception { + // Create two dummy SKF Payloads so that the complete unencrypted data is two ID payloads + byte[] idInitPayloadBytes = TestUtils.hexStringToByteArray(ID_INIT_PAYLOAD_HEX_STRING); + byte[] idRespPayloadBytes = TestUtils.hexStringToByteArray(ID_RESP_PAYLOAD_HEX_STRING); + IkeSkfPayload skfOne = + makeDummySkfPayload(idInitPayloadBytes, FRAGMENT_NUM_ONE, TOTAL_FRAGMENTS); + IkeSkfPayload skfTwo = + makeDummySkfPayload(idRespPayloadBytes, FRAGMENT_NUM_TWO, TOTAL_FRAGMENTS); + + DecodeResultPartial resultPartialIncomplete = + new DecodeResultPartial( + mFragOneHeader, + mIkeFragPacketOne, + skfOne, + PAYLOAD_TYPE_ID_INITIATOR, + null /* collectedFragments*/); + + setDecryptSkfPayload(skfTwo); + DecodeResult decodeResult = + decodeSkf( + IKE_FRAG_EXPECTED_MESSAGE_ID, + mFragTwoHeader, + mIkeFragPacketTwo, + resultPartialIncomplete); + + // Verify fragments reassembly has been finished and complete message has been decoded. + assertTrue(decodeResult instanceof DecodeResultOk); + DecodeResultOk resultOk = (DecodeResultOk) decodeResult; + assertArrayEquals(mIkeFragPacketOne, resultOk.firstPacket); + assertEquals(2, resultOk.ikeMessage.ikePayloadList.size()); + } + + @Test + public void testRcvFirstArrivedFragWithUnprotectedError() throws Exception { + DecodeResult decodeResult = + decodeSkf( + IKE_FRAG_EXPECTED_MESSAGE_ID + 1, + mFragTwoHeader, + mIkeFragPacketTwo, + null /* collectedFragments*/); + + // Verify that unprotected error was returned + IkeException ikeException = + verifyDecodeResultErrorAndGetIkeException( + decodeResult, DECODE_STATUS_UNPROTECTED_ERROR, mIkeAuthPacket); + assertTrue(ikeException instanceof InvalidMessageIdException); + } + + @Test + public void testRcvLastArrivedFragWithUnprotectedError() throws Exception { + DecodeResultPartial resultPartialIncomplete = + makeDecodeResultForFragOne(null /* collectedFragments*/); + + DecodeResult decodeResult = + decodeSkf( + IKE_FRAG_EXPECTED_MESSAGE_ID + 1, + mFragTwoHeader, + mIkeFragPacketTwo, + resultPartialIncomplete); + + // Verify that newly received fragment was discarded + assertEquals(resultPartialIncomplete, decodeResult); + } + + @Test + public void testRcvFragWithLargerTotalFragments() throws Exception { + DecodeResultPartial resultPartialIncomplete = + new DecodeResultPartial( + mFragOneHeader, + mIkeFragPacketOne, + mDummySkfPayloadOne, + PAYLOAD_TYPE_NO_NEXT, + null /* collectedFragments*/); + + // Set total fragments of inbound fragment to 5 + int totalFragments = 5; + byte[] fragPacket = makeFragmentBytes(2 /*fragNum*/, totalFragments); + + byte[] unencryptedData = "testRcvFragWithLargerTotalFragments".getBytes(); + IkeSkfPayload skfPayload = + makeDummySkfPayload(unencryptedData, FRAGMENT_NUM_TWO, totalFragments); + setDecryptSkfPayload(skfPayload); + DecodeResult decodeResult = + decodeSkf( + IKE_FRAG_EXPECTED_MESSAGE_ID, + mFragTwoHeader, + fragPacket, + resultPartialIncomplete); + + // Verify that previously collected fragments were all discarded + assertTrue(decodeResult instanceof DecodeResultPartial); + DecodeResultPartial resultPartial = (DecodeResultPartial) decodeResult; + + assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType); + assertNull(resultPartial.firstFragBytes); + assertEquals(mFragTwoHeader, resultPartial.ikeHeader); + + assertEquals(totalFragments, resultPartial.collectedFragsList.length); + assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]); + assertFalse(resultPartial.isAllFragmentsReceived()); + } + + @Test + public void testRcvFragWithSmallerTotalFragments() throws Exception { + int totalFragments = 5; + byte[] unencryptedData = "testRcvFragWithSmallerTotalFragments".getBytes(); + IkeSkfPayload skfPayload = + makeDummySkfPayload(unencryptedData, FRAGMENT_NUM_ONE, totalFragments); + + DecodeResultPartial resultPartialIncomplete = + new DecodeResultPartial( + mFragOneHeader, + mIkeFragPacketOne, + skfPayload, + PAYLOAD_TYPE_AUTH, + null /* collectedFragments*/); + + setDecryptSkfPayload(mDummySkfPayloadTwo); + DecodeResult decodeResult = + decodeSkf( + IKE_FRAG_EXPECTED_MESSAGE_ID, + mFragTwoHeader, + mIkeFragPacketTwo, + resultPartialIncomplete); + + // Verify that newly received fragment was discarded + assertEquals(resultPartialIncomplete, decodeResult); + } + + @Test + public void testRcvReplayFrag() throws Exception { + DecodeResultPartial resultPartialIncomplete = + makeDecodeResultForFragTwo(null /* collectedFragments*/); + + setDecryptSkfPayload(mDummySkfPayloadTwo); + DecodeResult decodeResult = + decodeSkf( + IKE_FRAG_EXPECTED_MESSAGE_ID, + mFragTwoHeader, + mIkeFragPacketTwo, + resultPartialIncomplete); + + // Verify that newly received fragment was discarded + assertEquals(resultPartialIncomplete, decodeResult); + } + + @Test + public void testRcvCompleteMessageDuringReassembly() throws Exception { + DecodeResultPartial resultPartialIncomplete = + makeDecodeResultForFragTwo(null /* collectedFragments*/); + + DecodeResult decodeResult = + decodeSkf( + IKE_AUTH_EXPECTED_MESSAGE_ID, + mIkeAuthHeader, + mIkeAuthPacket, + resultPartialIncomplete); + + // Verify that newly received IKE message was discarded + assertEquals(resultPartialIncomplete, decodeResult); + } + + @Test + public void testEncodeAndEncryptFragments() throws Exception { + int messageId = 1; + int fragSize = 140; + int expectedTotalFragments = 3; + + byte[] integrityKey = new byte[0]; + byte[] encryptionKey = new byte[0]; + byte[] iv = new byte[IKE_AUTH_CIPHER_IV_SIZE]; + + when(mMockCipher.generateIv()).thenReturn(iv); + when(mMockCipher.encrypt(any(), any(), any())) + .thenAnswer( + (invocation) -> { + return (byte[]) invocation.getArguments()[0]; + }); + + IkeHeader ikeHeader = + new IkeHeader( + INIT_SPI, + RESP_SPI, + IkePayload.PAYLOAD_TYPE_SK, + IkeHeader.EXCHANGE_TYPE_IKE_AUTH, + true /*isResp*/, + false /*fromInit*/, + messageId); + + byte[][] packetList = + new IkeMessage.IkeMessageHelper() + .encryptAndEncode( + ikeHeader, + IkePayload.PAYLOAD_TYPE_AUTH, + mUnencryptedPaddedData, + mMockIntegrity, + mMockCipher, + integrityKey, + encryptionKey, + true /*supportFragment*/, + fragSize); + + assertEquals(expectedTotalFragments, packetList.length); + + IkeHeader expectedIkeHeader = + new IkeHeader( + INIT_SPI, + RESP_SPI, + IkePayload.PAYLOAD_TYPE_SKF, + IkeHeader.EXCHANGE_TYPE_IKE_AUTH, + true /*isResp*/, + false /*fromInit*/, + messageId); + for (int i = 0; i < packetList.length; i++) { + byte[] p = packetList[i]; + + // Verify fragment length + assertNotNull(p); + assertTrue(p.length <= fragSize); + + ByteBuffer packetBuffer = ByteBuffer.wrap(p); + + // Verify IKE header + byte[] headerBytes = new byte[IkeHeader.IKE_HEADER_LENGTH]; + packetBuffer.get(headerBytes); + + ByteBuffer expectedHeadBuffer = ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH); + expectedIkeHeader.encodeToByteBuffer( + expectedHeadBuffer, p.length - IkeHeader.IKE_HEADER_LENGTH); + + assertArrayEquals(expectedHeadBuffer.array(), headerBytes); + + // Verify fragment payload header + packetBuffer.get(new byte[IkePayload.GENERIC_HEADER_LENGTH]); + assertEquals(i + 1 /*expetced fragNum*/, Short.toUnsignedInt(packetBuffer.getShort())); + assertEquals(expectedTotalFragments, Short.toUnsignedInt(packetBuffer.getShort())); + } + + verify(mMockCipher, times(expectedTotalFragments + 1)) + .encrypt(any(), eq(encryptionKey), eq(iv)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNoncePayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNoncePayloadTest.java new file mode 100644 index 00000000..dd92a45e --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNoncePayloadTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; + +import com.android.internal.net.TestUtils; + +import org.junit.Test; + +import java.nio.ByteBuffer; + +public final class IkeNoncePayloadTest { + + private static final String NONCE_PAYLOAD_RAW_HEX_STRING = + "29000024c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412"; + private static final String NONCE_DATA_RAW_HEX_STRING = + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412"; + + @IkePayload.PayloadType + private static final int NEXT_PAYLOAD_TYPE = IkePayload.PAYLOAD_TYPE_NOTIFY; + + @Test + public void testEncode() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(NONCE_DATA_RAW_HEX_STRING); + IkeNoncePayload payload = new IkeNoncePayload(false, inputPacket); + + ByteBuffer byteBuffer = ByteBuffer.allocate(payload.getPayloadLength()); + payload.encodeToByteBuffer(NEXT_PAYLOAD_TYPE, byteBuffer); + + byte[] expectedNoncePayload = + TestUtils.hexStringToByteArray(NONCE_PAYLOAD_RAW_HEX_STRING); + assertArrayEquals(expectedNoncePayload, byteBuffer.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java new file mode 100644 index 00000000..884c76eb --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD; +import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.ipsec.ike.SaProposal; +import android.net.ipsec.ike.exceptions.IkeProtocolException; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; +import com.android.internal.net.ipsec.ike.exceptions.InvalidKeException; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; +import com.android.internal.net.ipsec.ike.exceptions.UnrecognizedIkeProtocolException; + +import org.junit.Test; + +import java.net.InetAddress; +import java.nio.ByteBuffer; + +public final class IkeNotifyPayloadTest { + private static final String NOTIFY_NAT_DETECTION_PAYLOAD_HEX_STRING = + "2900001c00004004e54f73b7d83f6beb881eab2051d8663f421d10b0"; + private static final String NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING = + "00004004e54f73b7d83f6beb881eab2051d8663f421d10b0"; + private static final String NAT_DETECTION_DATA_HEX_STRING = + "e54f73b7d83f6beb881eab2051d8663f421d10b0"; + + private static final String NOTIFY_REKEY_PAYLOAD_BODY_HEX_STRING = "030440092ad4c0a2"; + private static final int REKEY_SPI = 0x2ad4c0a2; + + private static final String IKE_INITIATOR_SPI_HEX_STRING = "5f54bf6d8b48e6e1"; + private static final String IKE_RESPODNER_SPI_HEX_STRING = "0000000000000000"; + private static final String IP_ADDR = "10.80.80.13"; + private static final int PORT = 500; + + private static final int PROTOCOL_ID_OFFSET = 0; + + @Test + public void testDecodeNotifyPayloadSpiUnset() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING); + byte[] notifyData = TestUtils.hexStringToByteArray(NAT_DETECTION_DATA_HEX_STRING); + + IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket); + assertEquals(IkePayload.PROTOCOL_ID_UNSET, payload.protocolId); + assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, payload.spiSize); + assertEquals(IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP, payload.notifyType); + assertEquals(IkePayload.SPI_NOT_INCLUDED, payload.spi); + assertArrayEquals(notifyData, payload.notifyData); + } + + @Test + public void testDecodeNotifyPayloadSpiSet() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(NOTIFY_REKEY_PAYLOAD_BODY_HEX_STRING); + + IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket); + assertEquals(IkePayload.PROTOCOL_ID_ESP, payload.protocolId); + assertEquals(IkePayload.SPI_LEN_IPSEC, payload.spiSize); + assertEquals(IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA, payload.notifyType); + assertEquals(REKEY_SPI, payload.spi); + assertArrayEquals(new byte[0], payload.notifyData); + } + + @Test + public void testDecodeNotifyPayloadThrowException() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING); + // Change Protocol ID to ESP + inputPacket[PROTOCOL_ID_OFFSET] = (byte) (IkePayload.PROTOCOL_ID_ESP & 0xFF); + try { + IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket); + fail("Expected InvalidSyntaxException: Protocol ID should not be ESP"); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testGenerateNatDetectionData() throws Exception { + long initiatorIkeSpi = Long.parseLong(IKE_INITIATOR_SPI_HEX_STRING, 16); + long responderIkespi = Long.parseLong(IKE_RESPODNER_SPI_HEX_STRING, 16); + InetAddress inetAddress = InetAddress.getByName(IP_ADDR); + + byte[] netDetectionData = + IkeNotifyPayload.generateNatDetectionData( + initiatorIkeSpi, responderIkespi, inetAddress, PORT); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(NAT_DETECTION_DATA_HEX_STRING); + assertArrayEquals(expectedBytes, netDetectionData); + } + + @Test + public void testBuildIkeErrorNotifyWithData() throws Exception { + int payloadType = 1; + IkeNotifyPayload notifyPayload = + new IkeNotifyPayload( + IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD, + new byte[] {(byte) payloadType}); + + assertArrayEquals(new byte[] {(byte) payloadType}, notifyPayload.notifyData); + assertTrue(notifyPayload.isErrorNotify()); + assertFalse(notifyPayload.isNewChildSaNotify()); + } + + @Test + public void testBuildIkeErrorNotifyWithoutData() throws Exception { + IkeNotifyPayload notifyPayload = + new IkeNotifyPayload(IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX); + + assertArrayEquals(new byte[0], notifyPayload.notifyData); + assertTrue(notifyPayload.isErrorNotify()); + assertFalse(notifyPayload.isNewChildSaNotify()); + } + + @Test + public void testBuildChildConfigNotify() throws Exception { + IkeNotifyPayload notifyPayload = + new IkeNotifyPayload(IkeNotifyPayload.NOTIFY_TYPE_USE_TRANSPORT_MODE); + + assertArrayEquals(new byte[0], notifyPayload.notifyData); + assertFalse(notifyPayload.isErrorNotify()); + assertTrue(notifyPayload.isNewChildSaNotify()); + } + + @Test + public void testBuildChildErrorNotify() throws Exception { + IkeNotifyPayload notifyPayload = + new IkeNotifyPayload(IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE); + + assertArrayEquals(new byte[0], notifyPayload.notifyData); + assertTrue(notifyPayload.isErrorNotify()); + assertTrue(notifyPayload.isNewChildSaNotify()); + } + + @Test + public void testEncodeNotifyPayload() throws Exception { + byte[] inputPacket = + TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING); + IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket); + + ByteBuffer byteBuffer = ByteBuffer.allocate(payload.getPayloadLength()); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NOTIFY, byteBuffer); + + byte[] expectedNoncePayload = + TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_HEX_STRING); + assertArrayEquals(expectedNoncePayload, byteBuffer.array()); + } + + @Test + public void testValidateAndBuildIkeExceptionWithData() throws Exception { + // Invalid Message ID + byte[] dhGroup = new byte[] {(byte) 0x00, (byte) 0x0e}; + int expectedDhGroup = SaProposal.DH_GROUP_2048_BIT_MODP; + + IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_INVALID_KE_PAYLOAD, dhGroup); + IkeProtocolException exception = payload.validateAndBuildIkeException(); + + assertTrue(exception instanceof InvalidKeException); + assertEquals(ERROR_TYPE_INVALID_KE_PAYLOAD, exception.getErrorType()); + assertArrayEquals(dhGroup, exception.getErrorData()); + assertEquals(expectedDhGroup, ((InvalidKeException) exception).getDhGroup()); + } + + @Test + public void testValidateAndBuildIkeExceptionWithoutData() throws Exception { + // Invalid Syntax + IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_AUTHENTICATION_FAILED); + IkeProtocolException exception = payload.validateAndBuildIkeException(); + + assertTrue(exception instanceof AuthenticationFailedException); + assertEquals(ERROR_TYPE_AUTHENTICATION_FAILED, exception.getErrorType()); + assertArrayEquals(new byte[0], exception.getErrorData()); + } + + @Test + public void testValidateAndBuildUnrecognizedIkeException() throws Exception { + int unrecognizedType = 0; + IkeNotifyPayload payload = new IkeNotifyPayload(unrecognizedType); + IkeProtocolException exception = payload.validateAndBuildIkeException(); + + assertTrue(exception instanceof UnrecognizedIkeProtocolException); + assertEquals(unrecognizedType, exception.getErrorType()); + assertArrayEquals(new byte[0], exception.getErrorData()); + } + + @Test + public void testValidateAndBuildIkeExceptionWithInvalidPayload() throws Exception { + // Build a invalid notify payload + IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD); + + try { + payload.validateAndBuildIkeException(); + fail("Expected to fail due to invalid error data"); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testBuildIkeExceptionWithStatusNotify() throws Exception { + // Rekey notification + byte[] inputPacket = TestUtils.hexStringToByteArray(NOTIFY_REKEY_PAYLOAD_BODY_HEX_STRING); + IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket); + + assertFalse(payload.isErrorNotify()); + + try { + payload.validateAndBuildIkeException(); + fail("Expected to fail because it is not error notification"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testGetNotifyTypeString() throws Exception { + IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_AUTHENTICATION_FAILED); + + assertEquals("Notify(Authentication failed)", payload.getTypeString()); + } + + @Test + public void testGetNotifyTypeStringForUnrecoginizedNotify() throws Exception { + int unrecognizedType = 0; + IkeNotifyPayload payload = new IkeNotifyPayload(unrecognizedType); + + assertEquals("Notify(0)", payload.getTypeString()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java new file mode 100644 index 00000000..750ff463 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java @@ -0,0 +1,927 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.net.IpSecManager; +import android.net.IpSecSpiResponse; +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeSaProposal; +import android.net.ipsec.ike.SaProposal; +import android.net.ipsec.ike.exceptions.IkeProtocolException; +import android.util.Pair; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; +import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Attribute; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.AttributeDecoder; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.ChildProposal; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EsnTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IkeProposal; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.KeyLengthAttribute; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Proposal; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Transform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.TransformDecoder; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.UnrecognizedAttribute; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.UnrecognizedTransform; +import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils; +import com.android.server.IpSecService; + +import libcore.net.InetAddressUtils; + +import org.junit.Before; +import org.junit.Test; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +public final class IkeSaPayloadTest { + private static final String OUTBOUND_SA_PAYLOAD_HEADER = "22000030"; + private static final String OUTBOUND_PROPOSAL_RAW_PACKET = + "0000002C010100040300000C0100000C800E0080030000080300000203000008040" + + "000020000000802000002"; + private static final String INBOUND_PROPOSAL_RAW_PACKET = + "0000002c010100040300000c0100000c800e0080030000080300000203000008040" + + "000020000000802000002"; + private static final String INBOUND_TWO_PROPOSAL_RAW_PACKET = + "020000dc010100190300000c0100000c800e00800300000c0100000c800e00c0030" + + "0000c0100000c800e01000300000801000003030000080300000c0300" + + "00080300000d030000080300000e03000008030000020300000803000" + + "005030000080200000503000008020000060300000802000007030000" + + "080200000403000008020000020300000804000013030000080400001" + + "40300000804000015030000080400001c030000080400001d03000008" + + "0400001e030000080400001f030000080400000f03000008040000100" + + "300000804000012000000080400000e000001000201001a0300000c01" + + "000014800e00800300000c01000014800e00c00300000c01000014800" + + "e01000300000c0100001c800e01000300000c01000013800e00800300" + + "000c01000013800e00c00300000c01000013800e01000300000c01000" + + "012800e00800300000c01000012800e00c00300000c01000012800e01" + + "000300000802000005030000080200000603000008020000070300000" + + "802000004030000080200000203000008040000130300000804000014" + + "0300000804000015030000080400001c030000080400001d030000080" + + "400001e030000080400001f030000080400000f030000080400001003" + + "00000804000012000000080400000e"; + private static final String INBOUND_CHILD_PROPOSAL_RAW_PACKET = + "0000002801030403cae7019f0300000c0100000c800e00800300000803000002000" + "0000805000000"; + private static final String INBOUND_CHILD_TWO_PROPOSAL_RAW_PACKET = + "0200002801030403cae7019f0300000c0100000c800e00800300000803000002000" + + "00008050000000000001802030401cae7019e0000000c01000012800e" + + "0080"; + private static final String ENCR_TRANSFORM_RAW_PACKET = "0300000c0100000c800e0080"; + private static final String PRF_TRANSFORM_RAW_PACKET = "0000000802000002"; + private static final String INTEG_TRANSFORM_RAW_PACKET = "0300000803000002"; + private static final String DH_GROUP_TRANSFORM_RAW_PACKET = "0300000804000002"; + private static final String ESN_TRANSFORM_RAW_PACKET = "0000000805000000"; + + private static final int TRANSFORM_TYPE_OFFSET = 4; + private static final int TRANSFORM_ID_OFFSET = 7; + + private static final String ATTRIBUTE_RAW_PACKET = "800e0080"; + + private static final int PROPOSAL_NUMBER = 1; + + private static final int PROPOSAL_NUMBER_OFFSET = 4; + private static final int PROTOCOL_ID_OFFSET = 5; + + // Constants for multiple proposals test + private static final byte[] PROPOSAL_NUMBER_LIST = {1, 2}; + + private static final Inet4Address LOCAL_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("8.8.4.4")); + private static final Inet4Address REMOTE_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("8.8.8.8")); + + private static final int DUMMY_CHILD_SPI_RESOURCE_ID_LOCAL_ONE = 0x1234; + private static final int DUMMY_CHILD_SPI_RESOURCE_ID_LOCAL_TWO = 0x1235; + private static final int DUMMY_CHILD_SPI_RESOURCE_ID_REMOTE = 0x2234; + + private static final int CHILD_SPI_LOCAL_ONE = 0x2ad4c0a2; + private static final int CHILD_SPI_LOCAL_TWO = 0x2ad4c0a3; + private static final int CHILD_SPI_REMOTE = 0xcae70197; + + private AttributeDecoder mMockedAttributeDecoder; + private KeyLengthAttribute mAttributeKeyLength128; + private List<Attribute> mAttributeListWithKeyLength128; + + private EncryptionTransform mEncrAesCbc128Transform; + private EncryptionTransform mEncrAesGcm8Key128Transform; + private IntegrityTransform mIntegHmacSha1Transform; + private PrfTransform mPrfHmacSha1Transform; + private DhGroupTransform mDhGroup1024Transform; + + private Transform[] mValidNegotiatedTransformSet; + + private IkeSaProposal mIkeSaProposalOne; + private IkeSaProposal mIkeSaProposalTwo; + private IkeSaProposal[] mTwoIkeSaProposalsArray; + + private ChildSaProposal mChildSaProposalOne; + private ChildSaProposal mChildSaProposalTwo; + private ChildSaProposal[] mTwoChildSaProposalsArray; + + private MockIpSecTestUtils mMockIpSecTestUtils; + private IpSecService mMockIpSecService; + private IpSecManager mIpSecManager; + + private IpSecSpiResponse mDummyIpSecSpiResponseLocalOne; + private IpSecSpiResponse mDummyIpSecSpiResponseLocalTwo; + private IpSecSpiResponse mDummyIpSecSpiResponseRemote; + + @Before + public void setUp() throws Exception { + mMockedAttributeDecoder = mock(AttributeDecoder.class); + mAttributeKeyLength128 = new KeyLengthAttribute(SaProposal.KEY_LEN_AES_128); + mAttributeListWithKeyLength128 = new LinkedList<>(); + mAttributeListWithKeyLength128.add(mAttributeKeyLength128); + + mEncrAesCbc128Transform = + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128); + mEncrAesGcm8Key128Transform = + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, SaProposal.KEY_LEN_AES_128); + mIntegHmacSha1Transform = + new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96); + mPrfHmacSha1Transform = new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1); + mDhGroup1024Transform = new DhGroupTransform(SaProposal.DH_GROUP_1024_BIT_MODP); + + mValidNegotiatedTransformSet = + new Transform[] { + mEncrAesCbc128Transform, + mIntegHmacSha1Transform, + mPrfHmacSha1Transform, + mDhGroup1024Transform + }; + + mIkeSaProposalOne = + new IkeSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1) + .build(); + + mIkeSaProposalTwo = + new IkeSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, + SaProposal.KEY_LEN_AES_128) + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, + SaProposal.KEY_LEN_AES_128) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .addDhGroup(SaProposal.DH_GROUP_2048_BIT_MODP) + .build(); + mTwoIkeSaProposalsArray = new IkeSaProposal[] {mIkeSaProposalOne, mIkeSaProposalTwo}; + + mChildSaProposalOne = + new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .build(); + mChildSaProposalTwo = + new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, + SaProposal.KEY_LEN_AES_128) + .build(); + mTwoChildSaProposalsArray = + new ChildSaProposal[] {mChildSaProposalOne, mChildSaProposalTwo}; + + mMockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec(); + mIpSecManager = mMockIpSecTestUtils.getIpSecManager(); + + IpSecService mMockIpSecService = mMockIpSecTestUtils.getIpSecService(); + when(mMockIpSecService.allocateSecurityParameterIndex( + eq(LOCAL_ADDRESS.getHostAddress()), anyInt(), anyObject())) + .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(CHILD_SPI_LOCAL_ONE)) + .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(CHILD_SPI_LOCAL_TWO)); + + when(mMockIpSecService.allocateSecurityParameterIndex( + eq(REMOTE_ADDRESS.getHostAddress()), anyInt(), anyObject())) + .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(CHILD_SPI_REMOTE)); + } + + // TODO: Add tearDown() to reset Proposal.sTransformDecoder and Transform.sAttributeDecoder. + + @Test + public void testDecodeAttribute() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ATTRIBUTE_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + Pair<Attribute, Integer> pair = Attribute.readFrom(inputBuffer); + Attribute attribute = pair.first; + + assertTrue(attribute instanceof KeyLengthAttribute); + assertEquals(Attribute.ATTRIBUTE_TYPE_KEY_LENGTH, attribute.type); + assertEquals(SaProposal.KEY_LEN_AES_128, ((KeyLengthAttribute) attribute).keyLength); + } + + @Test + public void testEncodeAttribute() throws Exception { + ByteBuffer byteBuffer = ByteBuffer.allocate(mAttributeKeyLength128.getAttributeLength()); + mAttributeKeyLength128.encodeToByteBuffer(byteBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(ATTRIBUTE_RAW_PACKET); + + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + @Test + public void testDecodeEncryptionTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(mAttributeListWithKeyLength128); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + assertTrue(transform instanceof EncryptionTransform); + assertEquals(Transform.TRANSFORM_TYPE_ENCR, transform.type); + assertEquals(SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, transform.id); + assertTrue(transform.isSupported); + } + + @Test + public void testDecodeEncryptionTransformWithInvalidKeyLength() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + List<Attribute> attributeList = new LinkedList<>(); + Attribute keyLengAttr = new KeyLengthAttribute(SaProposal.KEY_LEN_AES_128 + 1); + attributeList.add(keyLengAttr); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + try { + Transform.readFrom(inputBuffer); + fail("Expected InvalidSyntaxException for invalid key length."); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testEncodeEncryptionTransform() throws Exception { + ByteBuffer byteBuffer = ByteBuffer.allocate(mEncrAesCbc128Transform.getTransformLength()); + mEncrAesCbc128Transform.encodeToByteBuffer(false, byteBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET); + + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + @Test + public void testConstructEncryptionTransformWithUnsupportedId() throws Exception { + try { + new EncryptionTransform(-1); + fail("Expected IllegalArgumentException for unsupported Transform ID"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testConstructEncryptionTransformWithInvalidKeyLength() throws Exception { + try { + new EncryptionTransform(SaProposal.ENCRYPTION_ALGORITHM_3DES, 129); + fail("Expected IllegalArgumentException for invalid key length."); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testDecodePrfTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(PRF_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(new LinkedList<Attribute>()); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + assertTrue(transform instanceof PrfTransform); + assertEquals(Transform.TRANSFORM_TYPE_PRF, transform.type); + assertEquals(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1, transform.id); + assertTrue(transform.isSupported); + } + + @Test + public void testEncodePrfTransform() throws Exception { + ByteBuffer byteBuffer = ByteBuffer.allocate(mPrfHmacSha1Transform.getTransformLength()); + mPrfHmacSha1Transform.encodeToByteBuffer(true, byteBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(PRF_TRANSFORM_RAW_PACKET); + + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + @Test + public void testConstructPrfTransformWithUnsupportedId() throws Exception { + try { + new PrfTransform(-1); + fail("Expected IllegalArgumentException for unsupported Transform ID"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testDecodeIntegrityTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INTEG_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(new LinkedList<Attribute>()); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + assertTrue(transform instanceof IntegrityTransform); + assertEquals(Transform.TRANSFORM_TYPE_INTEG, transform.type); + assertEquals(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96, transform.id); + assertTrue(transform.isSupported); + } + + @Test + public void testDecodeIntegrityTransformWithUnrecognizedAttribute() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INTEG_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(mAttributeListWithKeyLength128); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + assertTrue(transform instanceof IntegrityTransform); + assertEquals(Transform.TRANSFORM_TYPE_INTEG, transform.type); + assertEquals(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96, transform.id); + assertFalse(transform.isSupported); + } + + @Test + public void testEncodeIntegrityTransform() throws Exception { + ByteBuffer byteBuffer = ByteBuffer.allocate(mIntegHmacSha1Transform.getTransformLength()); + mIntegHmacSha1Transform.encodeToByteBuffer(false, byteBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(INTEG_TRANSFORM_RAW_PACKET); + + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + @Test + public void testConstructIntegrityTransformWithUnsupportedId() throws Exception { + try { + new IntegrityTransform(-1); + fail("Expected IllegalArgumentException for unsupported Transform ID"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testDecodeDhGroupTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(DH_GROUP_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(new LinkedList<Attribute>()); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + assertTrue(transform instanceof DhGroupTransform); + assertEquals(Transform.TRANSFORM_TYPE_DH, transform.type); + assertEquals(SaProposal.DH_GROUP_1024_BIT_MODP, transform.id); + assertTrue(transform.isSupported); + } + + @Test + public void testEncodeDhGroupTransform() throws Exception { + ByteBuffer byteBuffer = ByteBuffer.allocate(mDhGroup1024Transform.getTransformLength()); + mDhGroup1024Transform.encodeToByteBuffer(false, byteBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(DH_GROUP_TRANSFORM_RAW_PACKET); + + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + @Test + public void testConstructDhGroupTransformWithUnsupportedId() throws Exception { + try { + new DhGroupTransform(-1); + fail("Expected IllegalArgumentException for unsupported Transform ID"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testDecodeEsnTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(new LinkedList<Attribute>()); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + assertTrue(transform instanceof EsnTransform); + assertEquals(Transform.TRANSFORM_TYPE_ESN, transform.type); + assertEquals(EsnTransform.ESN_POLICY_NO_EXTENDED, transform.id); + assertTrue(transform.isSupported); + } + + @Test + public void testDecodeEsnTransformWithUnsupportedId() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET); + inputPacket[TRANSFORM_ID_OFFSET] = -1; + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(new LinkedList<Attribute>()); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + assertTrue(transform instanceof EsnTransform); + assertEquals(Transform.TRANSFORM_TYPE_ESN, transform.type); + assertFalse(transform.isSupported); + } + + @Test + public void testDecodeEsnTransformWithUnrecognizedAttribute() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(mAttributeListWithKeyLength128); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + assertTrue(transform instanceof EsnTransform); + assertEquals(Transform.TRANSFORM_TYPE_ESN, transform.type); + assertEquals(EsnTransform.ESN_POLICY_NO_EXTENDED, transform.id); + assertFalse(transform.isSupported); + } + + @Test + public void testEncodeEsnTransform() throws Exception { + EsnTransform mEsnTransform = new EsnTransform(); + ByteBuffer byteBuffer = ByteBuffer.allocate(mEsnTransform.getTransformLength()); + mEsnTransform.encodeToByteBuffer(true, byteBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET); + + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + @Test + public void testDecodeUnrecognizedTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET); + inputPacket[TRANSFORM_TYPE_OFFSET] = 6; + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(mAttributeListWithKeyLength128); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + + assertTrue(transform instanceof UnrecognizedTransform); + } + + @Test + public void testDecodeTransformWithRepeatedAttribute() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + List<Attribute> attributeList = new LinkedList<>(); + attributeList.add(mAttributeKeyLength128); + attributeList.add(mAttributeKeyLength128); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + try { + Transform.readFrom(inputBuffer); + fail("Expected InvalidSyntaxException for repeated Attribute Type Key Length."); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testDecodeTransformWithUnrecognizedTransformId() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET); + inputPacket[TRANSFORM_ID_OFFSET] = 1; + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())) + .thenReturn(mAttributeListWithKeyLength128); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + + assertFalse(transform.isSupported); + } + + @Test + public void testDecodeTransformWithUnrecogniedAttributeType() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + + List<Attribute> attributeList = new LinkedList<>(); + attributeList.add(mAttributeKeyLength128); + Attribute attributeUnrecognized = new UnrecognizedAttribute(1, new byte[0]); + attributeList.add(attributeUnrecognized); + + when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList); + Transform.sAttributeDecoder = mMockedAttributeDecoder; + + Transform transform = Transform.readFrom(inputBuffer); + + assertFalse(transform.isSupported); + } + + @Test + public void testTransformEquals() throws Exception { + EncryptionTransform mEncrAesGcm8Key128TransformOther = + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, SaProposal.KEY_LEN_AES_128); + + assertEquals(mEncrAesGcm8Key128Transform, mEncrAesGcm8Key128TransformOther); + + EncryptionTransform mEncrAesGcm8Key192Transform = + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, SaProposal.KEY_LEN_AES_192); + + assertNotEquals(mEncrAesGcm8Key128Transform, mEncrAesGcm8Key192Transform); + + IntegrityTransform mIntegHmacSha1TransformOther = + new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96); + + assertNotEquals(mEncrAesGcm8Key128Transform, mIntegHmacSha1Transform); + assertEquals(mIntegHmacSha1Transform, mIntegHmacSha1TransformOther); + } + + private TransformDecoder getDummyTransformDecoder(Transform[] decodedTransforms) { + return new TransformDecoder() { + @Override + public Transform[] decodeTransforms(int count, ByteBuffer inputBuffer) + throws IkeProtocolException { + for (int i = 0; i < count; i++) { + // Read length field and move position + inputBuffer.getShort(); + int length = Short.toUnsignedInt(inputBuffer.getShort()); + byte[] temp = new byte[length - 4]; + inputBuffer.get(temp); + } + return decodedTransforms; + } + }; + } + + @Test + public void testDecodeSingleProposal() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET); + ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket); + Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]); + + Proposal proposal = Proposal.readFrom(inputBuffer); + + assertEquals(PROPOSAL_NUMBER, proposal.number); + assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId); + assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, proposal.spiSize); + assertEquals(IkePayload.SPI_NOT_INCLUDED, proposal.spi); + assertFalse(proposal.hasUnrecognizedTransform); + assertNotNull(proposal.getSaProposal()); + } + + @Test + public void testDecodeSaRequestWithMultipleProposal() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_TWO_PROPOSAL_RAW_PACKET); + Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]); + + IkeSaPayload payload = new IkeSaPayload(false, false, inputPacket); + + assertEquals(PROPOSAL_NUMBER_LIST.length, payload.proposalList.size()); + for (int i = 0; i < payload.proposalList.size(); i++) { + Proposal proposal = payload.proposalList.get(i); + assertEquals(PROPOSAL_NUMBER_LIST[i], proposal.number); + assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId); + assertEquals(0, proposal.spiSize); + } + } + + @Test + public void testEncodeProposal() throws Exception { + // Construct Proposal for IKE INIT exchange. + IkeProposal proposal = + IkeProposal.createIkeProposal( + (byte) PROPOSAL_NUMBER, + IkePayload.SPI_LEN_NOT_INCLUDED, + mIkeSaProposalOne, + LOCAL_ADDRESS); + + ByteBuffer byteBuffer = ByteBuffer.allocate(proposal.getProposalLength()); + proposal.encodeToByteBuffer(true /*is the last*/, byteBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(OUTBOUND_PROPOSAL_RAW_PACKET); + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + @Test + public void testDecodeSaResponseWithMultipleProposal() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_TWO_PROPOSAL_RAW_PACKET); + Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]); + + try { + new IkeSaPayload(false, true, inputPacket); + fail("Expected to fail due to more than one proposal in response SA payload."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testBuildOutboundIkeRekeySaResponsePayload() throws Exception { + IkeSaPayload saPayload = + IkeSaPayload.createRekeyIkeSaResponsePayload( + (byte) 1, mIkeSaProposalOne, LOCAL_ADDRESS); + + assertTrue(saPayload.isSaResponse); + assertEquals(1, saPayload.proposalList.size()); + + IkeProposal proposal = (IkeProposal) saPayload.proposalList.get(0); + assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId); + assertEquals(IkePayload.SPI_LEN_IKE, proposal.spiSize); + assertEquals(mIkeSaProposalOne, proposal.saProposal); + + assertNotNull(proposal.getIkeSpiResource()); + } + + @Test + public void testBuildOutboundInitialIkeSaRequestPayload() throws Exception { + IkeSaPayload saPayload = IkeSaPayload.createInitialIkeSaPayload(mTwoIkeSaProposalsArray); + + assertFalse(saPayload.isSaResponse); + assertEquals(PROPOSAL_NUMBER_LIST.length, saPayload.proposalList.size()); + + for (int i = 0; i < saPayload.proposalList.size(); i++) { + IkeProposal proposal = (IkeProposal) saPayload.proposalList.get(i); + assertEquals(PROPOSAL_NUMBER_LIST[i], proposal.number); + assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId); + assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, proposal.spiSize); + assertEquals(mTwoIkeSaProposalsArray[i], proposal.saProposal); + + // SA Payload for IKE INIT exchange does not include IKE SPIs. + assertNull(proposal.getIkeSpiResource()); + } + } + + @Test + public void testBuildOutboundChildSaRequest() throws Exception { + IkeSaPayload saPayload = + IkeSaPayload.createChildSaRequestPayload( + mTwoChildSaProposalsArray, mIpSecManager, LOCAL_ADDRESS); + + assertFalse(saPayload.isSaResponse); + assertEquals(PROPOSAL_NUMBER_LIST.length, saPayload.proposalList.size()); + + int[] expectedSpis = new int[] {CHILD_SPI_LOCAL_ONE, CHILD_SPI_LOCAL_TWO}; + for (int i = 0; i < saPayload.proposalList.size(); i++) { + ChildProposal proposal = (ChildProposal) saPayload.proposalList.get(i); + assertEquals(PROPOSAL_NUMBER_LIST[i], proposal.number); + assertEquals(IkePayload.PROTOCOL_ID_ESP, proposal.protocolId); + assertEquals(IkePayload.SPI_LEN_IPSEC, proposal.spiSize); + assertEquals(mTwoChildSaProposalsArray[i], proposal.saProposal); + + assertEquals(expectedSpis[i], proposal.getChildSpiResource().getSpi()); + } + } + + @Test + public void testEncodeIkeSaPayload() throws Exception { + IkeSaPayload saPayload = + IkeSaPayload.createInitialIkeSaPayload(new IkeSaProposal[] {mIkeSaProposalOne}); + + ByteBuffer byteBuffer = ByteBuffer.allocate(saPayload.getPayloadLength()); + saPayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_KE, byteBuffer); + + byte[] expectedBytes = + TestUtils.hexStringToByteArray( + OUTBOUND_SA_PAYLOAD_HEADER + OUTBOUND_PROPOSAL_RAW_PACKET); + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + private void buildAndVerifyIkeSaRespProposal( + byte[] saResponseBytes, Transform[] decodedTransforms) throws Exception { + // Build response SA payload from decoding bytes. + Proposal.sTransformDecoder = getDummyTransformDecoder(decodedTransforms); + IkeSaPayload respPayload = new IkeSaPayload(false, true, saResponseBytes); + + // Build request SA payload for IKE INIT exchange from SaProposal. + IkeSaPayload reqPayload = IkeSaPayload.createInitialIkeSaPayload(mTwoIkeSaProposalsArray); + + Pair<IkeProposal, IkeProposal> negotiatedProposalPair = + IkeSaPayload.getVerifiedNegotiatedIkeProposalPair( + reqPayload, respPayload, REMOTE_ADDRESS); + IkeProposal reqProposal = negotiatedProposalPair.first; + IkeProposal respProposal = negotiatedProposalPair.second; + + assertEquals(respPayload.proposalList.get(0).getSaProposal(), respProposal.getSaProposal()); + + // SA Payload for IKE INIT exchange does not include IKE SPIs. + assertNull(reqProposal.getIkeSpiResource()); + assertNull(respProposal.getIkeSpiResource()); + } + + @Test + public void testGetVerifiedNegotiatedIkeProposal() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET); + + buildAndVerifyIkeSaRespProposal(inputPacket, mValidNegotiatedTransformSet); + } + + private void verifyChildSaNegotiation( + IkeSaPayload reqPayload, + IkeSaPayload respPayload, + IpSecManager ipSecManager, + InetAddress remoteAddress, + boolean isLocalInit) + throws Exception { + // SA negotiation + Pair<ChildProposal, ChildProposal> negotiatedProposalPair = + IkeSaPayload.getVerifiedNegotiatedChildProposalPair( + reqPayload, respPayload, ipSecManager, remoteAddress); + ChildProposal reqProposal = negotiatedProposalPair.first; + ChildProposal respProposal = negotiatedProposalPair.second; + + // Verify results + assertEquals(respPayload.proposalList.get(0).getSaProposal(), respProposal.getSaProposal()); + + int initSpi = isLocalInit ? CHILD_SPI_LOCAL_ONE : CHILD_SPI_REMOTE; + int respSpi = isLocalInit ? CHILD_SPI_REMOTE : CHILD_SPI_LOCAL_ONE; + assertEquals(initSpi, reqProposal.getChildSpiResource().getSpi()); + assertEquals(respSpi, respProposal.getChildSpiResource().getSpi()); + + // Verify SPIs in unselected Proposals have been released. + for (Proposal proposal : reqPayload.proposalList) { + if (proposal != reqProposal) { + assertNull(((ChildProposal) proposal).getChildSpiResource()); + } + } + } + + @Test + public void testGetVerifiedNegotiatedChildProposalForLocalCreate() throws Exception { + // Build local request + IkeSaPayload reqPayload = + IkeSaPayload.createChildSaRequestPayload( + mTwoChildSaProposalsArray, mIpSecManager, LOCAL_ADDRESS); + + // Build remote response + Proposal.sTransformDecoder = + getDummyTransformDecoder(mChildSaProposalOne.getAllTransforms()); + IkeSaPayload respPayload = + new IkeSaPayload( + false /*critical*/, + true /*isResp*/, + TestUtils.hexStringToByteArray(INBOUND_CHILD_PROPOSAL_RAW_PACKET)); + + verifyChildSaNegotiation( + reqPayload, respPayload, mIpSecManager, REMOTE_ADDRESS, true /*isLocalInit*/); + } + + @Test + public void testGetVerifiedNegotiatedChildProposalForRemoteCreate() throws Exception { + Transform[] transformsOne = mChildSaProposalOne.getAllTransforms(); + Transform[] transformsTwo = mChildSaProposalTwo.getAllTransforms(); + Transform[] decodedTransforms = new Transform[transformsOne.length + transformsTwo.length]; + System.arraycopy(transformsOne, 0, decodedTransforms, 0, transformsOne.length); + System.arraycopy( + transformsTwo, 0, decodedTransforms, transformsOne.length, transformsTwo.length); + + // Build remote request + Proposal.sTransformDecoder = getDummyTransformDecoder(decodedTransforms); + IkeSaPayload reqPayload = + new IkeSaPayload( + false /*critical*/, + false /*isResp*/, + TestUtils.hexStringToByteArray(INBOUND_CHILD_TWO_PROPOSAL_RAW_PACKET)); + + // Build local response + IkeSaPayload respPayload = + IkeSaPayload.createChildSaResponsePayload( + (byte) 1, mChildSaProposalOne, mIpSecManager, LOCAL_ADDRESS); + + verifyChildSaNegotiation( + reqPayload, respPayload, mIpSecManager, REMOTE_ADDRESS, false /*isLocalInit*/); + } + + // Test throwing when negotiated proposal in SA response payload has unrecognized Transform. + @Test + public void testGetVerifiedNegotiatedProposalWithUnrecogTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET); + + Transform[] negotiatedTransformSet = + Arrays.copyOfRange( + mValidNegotiatedTransformSet, 0, mValidNegotiatedTransformSet.length); + negotiatedTransformSet[0] = new UnrecognizedTransform(-1, 1, new LinkedList<>()); + + try { + buildAndVerifyIkeSaRespProposal(inputPacket, negotiatedTransformSet); + fail("Expected to fail because negotiated proposal has unrecognized Transform."); + } catch (NoValidProposalChosenException expected) { + } + } + + // Test throwing when negotiated proposal has invalid proposal number. + @Test + public void testGetVerifiedNegotiatedProposalWithInvalidNumber() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET); + inputPacket[PROPOSAL_NUMBER_OFFSET] = (byte) 10; + + try { + buildAndVerifyIkeSaRespProposal(inputPacket, mValidNegotiatedTransformSet); + fail("Expected to fail due to invalid proposal number."); + } catch (NoValidProposalChosenException expected) { + } + } + + // Test throwing when negotiated proposal has mismatched protocol ID. + @Test + public void testGetVerifiedNegotiatedProposalWithMisMatchedProtocol() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET); + inputPacket[PROTOCOL_ID_OFFSET] = IkePayload.PROTOCOL_ID_ESP; + + try { + buildAndVerifyIkeSaRespProposal(inputPacket, mValidNegotiatedTransformSet); + fail("Expected to fail due to mismatched protocol ID."); + } catch (NoValidProposalChosenException expected) { + } + } + + // Test throwing when negotiated proposal has Transform that was not proposed in request. + @Test + public void testGetVerifiedNegotiatedProposalWithMismatchedTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET); + + Transform[] negotiatedTransformSet = + Arrays.copyOfRange( + mValidNegotiatedTransformSet, 0, mValidNegotiatedTransformSet.length); + negotiatedTransformSet[0] = mEncrAesGcm8Key128Transform; + + try { + buildAndVerifyIkeSaRespProposal(inputPacket, negotiatedTransformSet); + fail("Expected to fail due to mismatched Transform."); + } catch (NoValidProposalChosenException expected) { + } + } + + // Test throwing when negotiated proposal is lack of a certain type Transform. + @Test + public void testGetVerifiedNegotiatedProposalWithoutTransform() throws Exception { + byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET); + + try { + buildAndVerifyIkeSaRespProposal(inputPacket, new Transform[0]); + fail("Expected to fail due to absence of Transform."); + } catch (NoValidProposalChosenException expected) { + } + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkPayloadTest.java new file mode 100644 index 00000000..898db30f --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkPayloadTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; + +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.crypto.IkeCipher; +import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public final class IkeSkPayloadTest { + + private static final String IKE_AUTH_INIT_REQUEST_HEX_STRING = + "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec" + + "230000d0b9132b7bb9f658dfdc648e5017a6322a030c316c" + + "e55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58" + + "46de333ecd3ea2b705d18293b130395300ba92a351041345" + + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f" + + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60" + + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b" + + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423" + + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3" + + "59eb4e81ae6e0f22abdad69ba8007d50"; + + private static final String IKE_AUTH_INIT_REQUEST_DECRYPTED_BODY_HEX_STRING = + "2400000c010000000a50500d2700000c010000000a505050" + + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327" + + "44dfb2c12c00002c00000028010304032ad4c0a20300000c" + + "0100000c800e008003000008030000020000000805000000" + + "2d00001801000000070000100000ffff00000000ffffffff" + + "2900001801000000070000100000ffff00000000ffffffff" + + "29000008000040000000000c0000400100000001"; + + private static final String ENCR_KEY_FROM_INIT_TO_RESP = "5cbfd33f75796c0188c4a3a546aec4a1"; + private static final String INTE_KEY_FROM_INIT_TO_RESP = + "554fbf5a05b7f511e05a30ce23d874db9ef55e51"; + + private static final String ENCR_ALGO_AES_CBC = "AES/CBC/NoPadding"; + + private static final int CHECKSUM_LEN = 12; + + private IkeCipher mAesCbcDecryptCipher; + private byte[] mAesCbcDecryptionKey; + + private IkeMacIntegrity mHmacSha1IntegrityMac; + private byte[] mHmacSha1IntegrityKey; + + @Before + public void setUp() throws Exception { + mAesCbcDecryptCipher = + IkeCipher.create( + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, + SaProposal.KEY_LEN_AES_128), + IkeMessage.getSecurityProvider()); + mAesCbcDecryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP); + mHmacSha1IntegrityMac = + IkeMacIntegrity.create( + new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96), + IkeMessage.getSecurityProvider()); + mHmacSha1IntegrityKey = TestUtils.hexStringToByteArray(INTE_KEY_FROM_INIT_TO_RESP); + } + + @Test + public void testEncode() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_HEX_STRING); + byte[] payloadBytes = + Arrays.copyOfRange(message, IkeHeader.IKE_HEADER_LENGTH, message.length); + + IkeSkPayload payload = + IkePayloadFactory.getIkeSkPayload( + false /*isSkf*/, + message, + mHmacSha1IntegrityMac, + mAesCbcDecryptCipher, + mHmacSha1IntegrityKey, + mAesCbcDecryptionKey) + .first; + int payloadLength = payload.getPayloadLength(); + ByteBuffer buffer = ByteBuffer.allocate(payloadLength); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_ID_INITIATOR, buffer); + assertArrayEquals(payloadBytes, buffer.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkfPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkfPayloadTest.java new file mode 100644 index 00000000..3374a7dc --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkfPayloadTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.net.ipsec.ike.SaProposal; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.crypto.IkeCipher; +import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; +import com.android.internal.net.ipsec.ike.crypto.IkeNormalModeCipher; +import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; +import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public final class IkeSkfPayloadTest { + private static final String IKE_FRAG_MSG_HEX_STRING = + "bb3d5237aa558779db24aeb6c9ea183e352023200000000100000134" + + "0000011800020002c1471fc7714a3b77a2bfd78dd2df4c048dfed913" + + "c5de76cb1f4463ef541df2442b43c65308a47b873502268cc1195f99" + + "4f6f1a945f56cb342969936af97d79c560c8e0f8bb1a0874ebfb5d0e" + + "610b0fcff96d4197c06e7aef07a3a9ae487555bec95c78b87fe6483c" + + "be07e3d132f8594c34dba5b5b463b871d0272af6a1ee701fc6b7b70a" + + "22a1b8f63eed50ce6b2253ee63fe2cf0289a5eb715e56b389f72b5ba" + + "ecfb7340f4abf9253a8c973d281ed62f3516d130fcaaf2c2145b3047" + + "f3a243e60beb2fc28bf05183839caf46bfbfc4f28c9a2224e7d49686" + + "52a236a403ecb203a1de1e2144a6f5ce28acc2f93989608af17381fc" + + "f965cabe1a448274264b22167abfa047dc88e4bfdc5a492847d36d8b"; + + private static final String IKE_FRAG_MSG_DECRYPTED_BODY_HEX_STRING = + "dc89d82f4fccff8d3f0c4f4848571e57205f7dbdce954203983d2147" + + "3a9e10ba36876b860d33afbdfe6ebf000240e31f2039f4213e882d1f" + + "6f0a24887aed0584f4b50a016d989990fd58297757c7b842cd72b57c" + + "2f68cba8a5f06d899ce3fcfbd0419402a1d59f1c5b5b23bd0a4ed525" + + "27ed6cef9fd238552fcf6e4cd9f794d2b01ba61438fd21714fbc3e3f" + + "443a816751e55d46009ae7fb9f52db0977e453a2d28b0453a9393778" + + "3a0b625c27d186c052a7169807537d97e731a3543fc10dca605ca86d" + + "1496882e1d009a9216d07d0000001801850014120a00000f02000200" + + "0100000d010000"; + + private static final String IKE_FRAG_MSG_CHECKSUM = "7abfa047dc88e4bfdc5a492847d36d8b"; + private static final String IKE_FRAG_MSG_PADDING = "05d925c1b3804aee08"; + + private static final int FRAGMENT_NUM = 2; + private static final int TOTAL_FRAGMENTS = 2; + + private static final int FRAGMENT_NUM_OFFSET = + IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH; + private static final int TOTAL_FRAGMENTS_OFFET = FRAGMENT_NUM_OFFSET + 2; + + private byte[] mDecryptedData; + + private IkeMacIntegrity mSpyHmacSha256IntegrityMac; + private IkeNormalModeCipher mSpyAesCbcCipher; + + @Before + public void setUp() throws Exception { + mDecryptedData = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_DECRYPTED_BODY_HEX_STRING); + + // Set up integrity algorithm + mSpyHmacSha256IntegrityMac = + spy( + IkeMacIntegrity.create( + new IntegrityTransform( + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128), + IkeMessage.getSecurityProvider())); + byte[] expectedChecksum = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_CHECKSUM); + doReturn(expectedChecksum).when(mSpyHmacSha256IntegrityMac).generateChecksum(any(), any()); + + // Set up encryption algorithm + mSpyAesCbcCipher = + spy( + (IkeNormalModeCipher) + IkeCipher.create( + new EncryptionTransform( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, + SaProposal.KEY_LEN_AES_128), + IkeMessage.getSecurityProvider())); + byte[] expectedDecryptedPaddedData = + TestUtils.hexStringToByteArray( + IKE_FRAG_MSG_DECRYPTED_BODY_HEX_STRING + IKE_FRAG_MSG_PADDING); + doReturn(expectedDecryptedPaddedData).when(mSpyAesCbcCipher).decrypt(any(), any(), any()); + } + + private IkeSkfPayload decodeAndDecryptFragMsg(byte[] message) throws Exception { + IkeSkfPayload payload = + (IkeSkfPayload) + IkePayloadFactory.getIkeSkPayload( + true /*isSkf*/, + message, + mSpyHmacSha256IntegrityMac, + mSpyAesCbcCipher, + new byte[0] /*integrityKey*/, + new byte[0] /*decryptionKey*/) + .first; + return payload; + } + + @Test + public void testDecode() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING); + + IkeSkfPayload payload = decodeAndDecryptFragMsg(message); + + assertEquals(IkePayload.PAYLOAD_TYPE_SKF, payload.payloadType); + assertEquals(FRAGMENT_NUM, payload.fragmentNum); + assertEquals(TOTAL_FRAGMENTS, payload.totalFragments); + assertArrayEquals(mDecryptedData, payload.getUnencryptedData()); + } + + @Test + public void testDecodeThrowsForZeroFragNum() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING); + + // Set Fragment Number to zero + message[FRAGMENT_NUM_OFFSET] = 0; + message[FRAGMENT_NUM_OFFSET + 1] = 0; + + try { + decodeAndDecryptFragMsg(message); + fail("Expected to fail because Fragment Number is zero."); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testDecodeThrowsForZeroTotalFragments() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING); + + // Set Total Fragments Number to zero + message[TOTAL_FRAGMENTS_OFFET] = 0; + message[TOTAL_FRAGMENTS_OFFET + 1] = 0; + + try { + decodeAndDecryptFragMsg(message); + fail("Expected to fail because Total Fragments Number is zero."); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testDecodeThrowsWhenFragNumIsLargerThanTotalFragments() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING); + + // Set Fragment Number to 5 + message[FRAGMENT_NUM_OFFSET] = 0; + message[FRAGMENT_NUM_OFFSET + 1] = 5; + + // Set Total Fragments Number to 2 + message[TOTAL_FRAGMENTS_OFFET] = 0; + message[TOTAL_FRAGMENTS_OFFET + 1] = 2; + + try { + decodeAndDecryptFragMsg(message); + fail( + "Expected to fail because Fragment Number is larger than" + + " Total Fragments Number"); + } catch (InvalidSyntaxException expected) { + } + } + + @Test + public void testEncode() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING); + IkeSkfPayload payload = decodeAndDecryptFragMsg(message); + + int payloadLength = payload.getPayloadLength(); + ByteBuffer buffer = ByteBuffer.allocate(payloadLength); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, buffer); + + byte[] expectedPayloadBytes = + Arrays.copyOfRange(message, IkeHeader.IKE_HEADER_LENGTH, message.length); + assertArrayEquals(expectedPayloadBytes, buffer.array()); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTestUtils.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTestUtils.java new file mode 100644 index 00000000..05474379 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTestUtils.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static com.android.internal.net.ipsec.ike.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.net.ipsec.ike.exceptions.IkeProtocolException; +import android.util.Pair; + +import com.android.internal.net.TestUtils; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResult; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultError; + +import java.nio.ByteBuffer; + +/** + * IkeTestUtils provides utility methods for testing IKE library. + * + * <p>TODO: Consider moving it under ikev2/ + */ +public final class IkeTestUtils { + public static IkePayload hexStringToIkePayload( + @IkePayload.PayloadType int payloadType, boolean isResp, String payloadHexString) + throws IkeProtocolException { + byte[] payloadBytes = TestUtils.hexStringToByteArray(payloadHexString); + // Returned Pair consists of the IkePayload and the following IkePayload's type. + Pair<IkePayload, Integer> pair = + IkePayloadFactory.getIkePayload(payloadType, isResp, ByteBuffer.wrap(payloadBytes)); + return pair.first; + } + + public static <T extends IkeProtocolException> T decodeAndVerifyUnprotectedErrorMsg( + byte[] inputPacket, Class<T> expectedException) throws Exception { + IkeHeader header = new IkeHeader(inputPacket); + DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket); + + assertEquals(DECODE_STATUS_UNPROTECTED_ERROR, decodeResult.status); + DecodeResultError resultError = (DecodeResultError) decodeResult; + assertNotNull(resultError.ikeException); + assertTrue(expectedException.isInstance(resultError.ikeException)); + + return (T) resultError.ikeException; + } + + public static IkeSkfPayload makeDummySkfPayload( + byte[] unencryptedData, int fragNum, int totalFrags) throws Exception { + IkeEncryptedPayloadBody mockEncryptedBody = mock(IkeEncryptedPayloadBody.class); + when(mockEncryptedBody.getUnencryptedData()).thenReturn(unencryptedData); + return new IkeSkfPayload(mockEncryptedBody, fragNum, totalFrags); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTsPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTsPayloadTest.java new file mode 100644 index 00000000..cbd6f930 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTsPayloadTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.net.ipsec.ike.IkeTrafficSelector; + +import com.android.internal.net.TestUtils; + +import libcore.net.InetAddressUtils; + +import org.junit.Test; + +import java.net.Inet4Address; +import java.nio.ByteBuffer; + +public final class IkeTsPayloadTest { + private static final String TS_INITIATOR_PAYLOAD_HEX_STRING = + "2d00002802000000070000100010fff0c0000264c0000365070000100000ffffc0000464c0000466"; + + private static final int NUMBER_OF_TS = 2; + + private static final int TS_ONE_START_PORT = 16; + private static final int TS_ONE_END_PORT = 65520; + private static final Inet4Address TS_ONE_START_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100")); + private static final Inet4Address TS_ONE_END_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.3.101")); + + private static final int TS_TWO_START_PORT = 0; + private static final int TS_TWO_END_PORT = 65535; + private static final Inet4Address TS_TWO_START_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.100")); + private static final Inet4Address TS_TWO_END_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.102")); + + private IkeTrafficSelector mTsOne; + private IkeTrafficSelector mTsTwo; + + public IkeTsPayloadTest() { + mTsOne = + new IkeTrafficSelector( + IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, + TS_ONE_START_PORT, + TS_ONE_END_PORT, + TS_ONE_START_ADDRESS, + TS_ONE_END_ADDRESS); + mTsTwo = + new IkeTrafficSelector( + IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, + TS_TWO_START_PORT, + TS_TWO_END_PORT, + TS_TWO_START_ADDRESS, + TS_TWO_END_ADDRESS); + } + + @Test + public void testDecodeTsInitiatorPayload() throws Exception { + ByteBuffer inputBuffer = + ByteBuffer.wrap(TestUtils.hexStringToByteArray(TS_INITIATOR_PAYLOAD_HEX_STRING)); + + IkePayload payload = + IkePayloadFactory.getIkePayload( + IkePayload.PAYLOAD_TYPE_TS_INITIATOR, false, inputBuffer) + .first; + assertTrue(payload instanceof IkeTsPayload); + + IkeTsPayload tsPayload = (IkeTsPayload) payload; + assertEquals(IkePayload.PAYLOAD_TYPE_TS_INITIATOR, tsPayload.payloadType); + assertEquals(NUMBER_OF_TS, tsPayload.numTs); + } + + @Test + public void testBuildAndEncodeTsPayload() throws Exception { + IkeTsPayload tsPayload = + new IkeTsPayload(true /*isInitiator*/, new IkeTrafficSelector[] {mTsOne, mTsTwo}); + + ByteBuffer byteBuffer = ByteBuffer.allocate(tsPayload.getPayloadLength()); + tsPayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_TS_RESPONDER, byteBuffer); + + byte[] expectedBytes = TestUtils.hexStringToByteArray(TS_INITIATOR_PAYLOAD_HEX_STRING); + assertArrayEquals(expectedBytes, byteBuffer.array()); + } + + @Test + public void testContains() throws Exception { + IkeTrafficSelector tsOneNarrowPortRange = + new IkeTrafficSelector( + IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, + TS_ONE_START_PORT + 1, + TS_ONE_END_PORT, + TS_ONE_START_ADDRESS, + TS_ONE_END_ADDRESS); + + IkeTrafficSelector tsOneNarrowAddressRange = + new IkeTrafficSelector( + IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, + TS_ONE_START_PORT, + TS_ONE_END_PORT, + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200")), + TS_ONE_END_ADDRESS); + + IkeTsPayload tsPayload = + new IkeTsPayload(true /*isInitiator*/, new IkeTrafficSelector[] {mTsOne, mTsTwo}); + + IkeTsPayload tsNarrowPortRangePayload = + new IkeTsPayload( + true /*isInitiator*/, + new IkeTrafficSelector[] {tsOneNarrowPortRange, mTsTwo}); + + IkeTsPayload tsNarrowAddressRangePayload = + new IkeTsPayload( + true /*isInitiator*/, + new IkeTrafficSelector[] {tsOneNarrowAddressRange, mTsTwo}); + + assertTrue(tsPayload.contains(tsPayload)); + assertTrue(tsPayload.contains(tsNarrowPortRangePayload)); + assertTrue(tsPayload.contains(tsNarrowAddressRangePayload)); + + assertFalse(tsNarrowPortRangePayload.contains(tsPayload)); + assertFalse(tsNarrowAddressRangePayload.contains(tsPayload)); + assertFalse(tsNarrowAddressRangePayload.contains(tsNarrowPortRangePayload)); + assertFalse(tsNarrowPortRangePayload.contains(tsNarrowAddressRangePayload)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/CertUtils.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/CertUtils.java new file mode 100644 index 00000000..db128c80 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/CertUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.testutils; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.org.bouncycastle.util.io.pem.PemObject; +import com.android.org.bouncycastle.util.io.pem.PemReader; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.KeyFactory; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; + +/** CertUtils provides utility methods for creating X509 certificate and private key. */ +public final class CertUtils { + private static final String PEM_FOLDER_NAME = "pem"; + private static final String KEY_FOLDER_NAME = "key"; + + /** Creates an X509Certificate with a pem file */ + public static X509Certificate createCertFromPemFile(String fileName) throws Exception { + Context context = InstrumentationRegistry.getContext(); + InputStream inputStream = + context.getResources().getAssets().open(PEM_FOLDER_NAME + "/" + fileName); + + CertificateFactory factory = + CertificateFactory.getInstance("X.509", IkeMessage.getSecurityProvider()); + return (X509Certificate) factory.generateCertificate(inputStream); + } + + /** Creates an private key from a PKCS8 format key file */ + public static RSAPrivateKey createRsaPrivateKeyFromKeyFile(String fileName) throws Exception { + Context context = InstrumentationRegistry.getContext(); + InputStream inputStream = + context.getResources().getAssets().open(KEY_FOLDER_NAME + "/" + fileName); + + PemObject pemObject = new PemReader(new InputStreamReader(inputStream)).readPemObject(); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return (RSAPrivateKey) + keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pemObject.getContent())); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/MockIpSecTestUtils.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/MockIpSecTestUtils.java new file mode 100644 index 00000000..742ff240 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/MockIpSecTestUtils.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.testutils; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.IpSecManager; +import android.net.IpSecSpiResponse; +import android.net.IpSecUdpEncapResponse; +import android.system.Os; + +import androidx.test.InstrumentationRegistry; + +import com.android.server.IpSecService; + +/** This class provides utility methods for mocking IPsec surface. */ +public final class MockIpSecTestUtils { + private static final int DUMMY_CHILD_SPI = 0x2ad4c0a2; + private static final int DUMMY_CHILD_SPI_RESOURCE_ID = 0x1234; + + private static final int DUMMY_UDP_ENCAP_PORT = 34567; + private static final int DUMMY_UDP_ENCAP_RESOURCE_ID = 0x3234; + + private Context mContext; + private IpSecService mMockIpSecService; + private IpSecManager mIpSecManager; + + private MockIpSecTestUtils() throws Exception { + mMockIpSecService = mock(IpSecService.class); + mContext = InstrumentationRegistry.getContext(); + mIpSecManager = new IpSecManager(mContext, mMockIpSecService); + + when(mMockIpSecService.allocateSecurityParameterIndex(anyString(), anyInt(), anyObject())) + .thenReturn( + new IpSecSpiResponse( + IpSecManager.Status.OK, + DUMMY_CHILD_SPI_RESOURCE_ID, + DUMMY_CHILD_SPI)); + + when(mMockIpSecService.openUdpEncapsulationSocket(anyInt(), anyObject())) + .thenReturn( + new IpSecUdpEncapResponse( + IpSecManager.Status.OK, + DUMMY_UDP_ENCAP_RESOURCE_ID, + DUMMY_UDP_ENCAP_PORT, + Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))); + } + + public static MockIpSecTestUtils setUpMockIpSec() throws Exception { + return new MockIpSecTestUtils(); + } + + public static IpSecSpiResponse buildDummyIpSecSpiResponse(int spi) throws Exception { + return new IpSecSpiResponse(IpSecManager.Status.OK, DUMMY_CHILD_SPI_RESOURCE_ID, spi); + } + + public Context getContext() { + return mContext; + } + + public IpSecManager getIpSecManager() { + return mIpSecManager; + } + + public IpSecService getIpSecService() { + return mMockIpSecService; + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/utils/RetransmitterTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/utils/RetransmitterTest.java new file mode 100644 index 00000000..1fddb1d9 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/utils/RetransmitterTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.ipsec.ike.utils; + +import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_RETRANSMIT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.os.Handler; +import android.os.Message; + +import com.android.internal.net.ipsec.ike.message.IkeMessage; + +import org.junit.Before; +import org.junit.Test; + +public final class RetransmitterTest { + private Handler mMockHandler; + private IkeMessage mMockIkeMessage; + private TestRetransmitter mRetransmitter; + + private class TestRetransmitter extends Retransmitter { + int mSendCallCount; // Defaults to 0 + boolean mFailed; // Defaults to false + + TestRetransmitter(Handler handler, IkeMessage message) { + super(handler, message); + } + + @Override + public void send(IkeMessage msg) { + mSendCallCount++; + } + + @Override + public void handleRetransmissionFailure() { + mFailed = true; + } + } + + @Before + public void setUp() throws Exception { + mMockHandler = mock(Handler.class); + when(mMockHandler.obtainMessage(eq(CMD_RETRANSMIT), anyObject())) + .thenReturn(mock(Message.class)); + + mMockIkeMessage = mock(IkeMessage.class); + mRetransmitter = new TestRetransmitter(mMockHandler, mMockIkeMessage); + } + + @Test + public void testSendRequestAndQueueRetransmit() throws Exception { + mRetransmitter.retransmit(); + assertEquals(1, mRetransmitter.mSendCallCount); + verify(mMockHandler).obtainMessage(eq(CMD_RETRANSMIT), eq(mRetransmitter)); + verify(mMockHandler) + .sendMessageDelayed(any(Message.class), eq(Retransmitter.RETRANSMIT_TIMEOUT_MS)); + } + + @Test + public void testRetransmitQueuesExponentialRetransmit() throws Exception { + mRetransmitter.retransmit(); + + for (int i = 0; i <= Retransmitter.RETRANSMIT_MAX_ATTEMPTS; i++) { + long expectedTimeout = + (long) + (Retransmitter.RETRANSMIT_TIMEOUT_MS + * Math.pow(Retransmitter.RETRANSMIT_BACKOFF_FACTOR, i)); + + assertEquals(i + 1, mRetransmitter.mSendCallCount); + assertFalse(mRetransmitter.mFailed); + + // This call happens with the same arguments each time + verify(mMockHandler, times(i + 1)) + .obtainMessage(eq(CMD_RETRANSMIT), eq(mRetransmitter)); + + // But the expected timeout changes every time + verify(mMockHandler).sendMessageDelayed(any(Message.class), eq(expectedTimeout)); + + verifyNoMoreInteractions(mMockHandler); + + // Trigger next round of retransmissions. + mRetransmitter.retransmit(); + } + } + + @Test + public void testRetransmitterCallsRetranmissionsFailedOnMaxTries() throws Exception { + mRetransmitter.retransmit(); + + // Exhaust all retransmit attempts + for (int i = 0; i <= Retransmitter.RETRANSMIT_MAX_ATTEMPTS; i++) { + mRetransmitter.retransmit(); + } + + assertTrue(mRetransmitter.mFailed); + } + + @Test + public void testRetransmitterStopsRetransmitting() throws Exception { + mRetransmitter.stopRetransmitting(); + + verify(mMockHandler).removeMessages(eq(CMD_RETRANSMIT), eq(mRetransmitter)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/utils/BigIntegerUtilsTest.java b/tests/iketests/src/java/com/android/internal/net/utils/BigIntegerUtilsTest.java new file mode 100644 index 00000000..29bb313d --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/utils/BigIntegerUtilsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.utils; + +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +import java.math.BigInteger; + +public final class BigIntegerUtilsTest { + private static final String TEST_BIGINTEGER_A = "3FFF"; + private static final String TEST_BIGINTEGER_B = "FFFF"; + private static final byte[] EXPECTED_BYTE_ARRAY_A_2 = {(byte) 0x3F, (byte) 0xFF}; + private static final byte[] EXPECTED_BYTE_ARRAY_B_2 = {(byte) 0xFF, (byte) 0xFF}; + private static final byte[] EXPECTED_BYTE_ARRAY_A_3 = {0, (byte) 0x3F, (byte) 0xFF}; + private static final byte[] EXPECTED_BYTE_ARRAY_B_3 = {0, (byte) 0xFF, (byte) 0xFF}; + + @Test + public void testBigIntegerToUnsignedByteArray() throws Exception { + BigInteger bigIntA = new BigInteger(TEST_BIGINTEGER_A, 16); + BigInteger bigIntB = new BigInteger(TEST_BIGINTEGER_B, 16); + + byte[] byteArrayA2 = BigIntegerUtils.bigIntegerToUnsignedByteArray(bigIntA, 2); + assertArrayEquals(EXPECTED_BYTE_ARRAY_A_2, byteArrayA2); + byte[] byteArrayB2 = BigIntegerUtils.bigIntegerToUnsignedByteArray(bigIntB, 2); + assertArrayEquals(EXPECTED_BYTE_ARRAY_B_2, byteArrayB2); + + byte[] byteArrayA3 = BigIntegerUtils.bigIntegerToUnsignedByteArray(bigIntA, 3); + + assertArrayEquals(EXPECTED_BYTE_ARRAY_A_3, byteArrayA3); + byte[] byteArrayB3 = BigIntegerUtils.bigIntegerToUnsignedByteArray(bigIntB, 3); + assertArrayEquals(EXPECTED_BYTE_ARRAY_B_3, byteArrayB3); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/utils/LogTest.java b/tests/iketests/src/java/com/android/internal/net/utils/LogTest.java new file mode 100644 index 00000000..23305aa7 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/utils/LogTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.utils; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class LogTest { + private static final String TAG = "IkeLogTest"; + private static final String PII = "123456789ABCDEF"; // "IMSI" + private static final String HEX_STRING = "00112233445566778899AABBCCDDEEFF"; + private static final byte[] HEX_BYTES = new byte[] { + (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44, (byte) 0x55, + (byte) 0x66, (byte) 0x77, (byte) 0x88, (byte) 0x99, (byte) 0xAA, (byte) 0xBB, + (byte) 0xCC, (byte) 0xDD, (byte) 0xEE, (byte) 0xFF + }; + + @Test + public void testPii() { + // Log(String tag, boolean isEngBuild, boolean logSensitive); + String result = new Log(TAG, false, false).pii(PII); + assertEquals(Integer.toString(PII.hashCode()), result); + + result = new Log(TAG, true, false).pii(PII); + assertEquals(Integer.toString(PII.hashCode()), result); + + result = new Log(TAG, false, true).pii(PII); + assertEquals(Integer.toString(PII.hashCode()), result); + + result = new Log(TAG, true, true).pii(PII); + assertEquals(PII, result); + + result = new Log(TAG, true, true).pii(HEX_BYTES); + assertEquals(HEX_STRING, result); + } + + @Test + public void testByteArrayToHexString() { + assertEquals("", Log.byteArrayToHexString(null)); + + assertEquals("", Log.byteArrayToHexString(new byte[0])); + + assertEquals(HEX_STRING, Log.byteArrayToHexString(HEX_BYTES)); + } +} diff --git a/tests/iketests/src/java/com/android/internal/net/utils/SimpleStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/utils/SimpleStateMachineTest.java new file mode 100644 index 00000000..2ee8dc53 --- /dev/null +++ b/tests/iketests/src/java/com/android/internal/net/utils/SimpleStateMachineTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.android.internal.net.utils.SimpleStateMachine.SimpleState; + +import org.junit.Before; +import org.junit.Test; + +public class SimpleStateMachineTest { + private static final String INPUT = "input"; + private static final String OUTPUT = "output"; + + private SimpleStateMachine<String, String> mSimpleStateMachine; + private SimpleState mMockStartState; + private SimpleState mMockFinalState; + + @Before + public void setUp() { + mMockStartState = mock(SimpleState.class); + mMockFinalState = mock(SimpleState.class); + + mSimpleStateMachine = new SimpleStateMachine(){}; + mSimpleStateMachine.transitionTo(mMockStartState); + } + + @Test + public void testProcess() { + when(mMockStartState.process(INPUT)).thenReturn(OUTPUT); + String result = mSimpleStateMachine.process(INPUT); + assertEquals(OUTPUT, result); + verify(mMockStartState).process(INPUT); + } + + @Test + public void testTransitionTo() { + mSimpleStateMachine.transitionTo(mMockFinalState); + assertEquals(mMockFinalState, mSimpleStateMachine.mState); + } + + @Test + public void testTransitionToNull() { + try { + mSimpleStateMachine.transitionTo(null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testTransitionAndProcess() { + when(mMockFinalState.process(INPUT)).thenReturn(OUTPUT); + String result = mSimpleStateMachine.transitionAndProcess(mMockFinalState, INPUT); + assertEquals(OUTPUT, result); + verify(mMockFinalState).process(INPUT); + } + + @Test + public void testTransitionAndProcessToNull() { + try { + mSimpleStateMachine.transitionAndProcess(null, INPUT); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testProcessNullState() { + SimpleStateMachine simpleStateMachine = new SimpleStateMachine() {}; + try { + simpleStateMachine.process(new Object()); + fail("IllegalStateException expected"); + } catch (IllegalStateException expected) { + } + } +} |