aboutsummaryrefslogtreecommitdiff
path: root/tests/iketests/src/java/com/android/internal/net
diff options
context:
space:
mode:
Diffstat (limited to 'tests/iketests/src/java/com/android/internal/net')
-rw-r--r--tests/iketests/src/java/com/android/internal/net/TestUtils.java113
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapAkaPrimeTest.java382
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapAkaTest.java332
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapAuthenticatorTest.java256
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapErrorTest.java33
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapMethodEndToEndTest.java91
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapMsChapV2Test.java173
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapResponseTest.java70
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapSimTest.java298
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapSuccessTest.java54
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/EapTestUtils.java56
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/crypto/Fips186_2PrfTest.java67
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/crypto/HmacSha256ByteSignerTest.java93
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/crypto/ParityBitUtilTest.java65
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/EapDataTest.java95
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/EapMessageTest.java182
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/EapTestMessageDefinitions.java327
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2ChallengeRequestTest.java120
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2ChallengeResponseTest.java109
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2FailureRequestTest.java172
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2FailureResponseTest.java43
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2PacketDefinitions.java284
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2SuccessRequestTest.java187
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2SuccessResponseTest.java43
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/mschapv2/EapMsChapV2TypeDataTest.java154
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapAkaPrimeTypeDataTest.java142
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapAkaTypeDataTest.java176
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapSimAkaAttributeFactoryTest.java94
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/EapSimTypeDataTest.java176
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtAutnTest.java77
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtAutsTest.java77
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtBiddingTest.java99
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtClientErrorCodeTest.java83
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtCounterTest.java122
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtIdReqTest.java149
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtIdentityTest.java85
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtKdfInputTest.java78
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtKdfTest.java78
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtMacTest.java110
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNonceMtTest.java91
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNonceSTest.java92
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtNotificationTest.java109
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtPaddingTest.java81
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtRandAkaTest.java77
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtRandSimTest.java101
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtResTest.java117
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtSelectedVersionTest.java83
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/AtVersionListTest.java85
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/EapSimAkaAttributeTest.java53
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/message/simaka/attributes/EapTestAttributeDefinitions.java120
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/CreatedStateTest.java79
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaChallengeStateTest.java430
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaCreatedStateTest.java110
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaIdentityStateTest.java161
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachineTest.java152
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeChallengeStateTest.java354
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeCreatedStateTest.java85
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeIdentityStateTest.java96
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeMethodStateMachineTest.java74
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeStateTest.java100
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaPrimeTest.java78
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapAkaStateTest.java147
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2AwaitingEapFailureStateTest.java29
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2AwaitingEapSuccessStateTest.java54
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2ChallengeStateTest.java104
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2CreatedStateTest.java80
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2MethodStateMachineTest.java182
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2StateTest.java100
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapMsChapV2ValidateAuthenticatorStateTest.java173
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimAkaMethodStateMachineTest.java475
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimChallengeStateTest.java356
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimCreatedStateTest.java111
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimMethodStateMachineTest.java152
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimStartStateTest.java254
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapSimStateTest.java149
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapStateMachineTest.java81
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapStateTest.java128
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/IdentityStateTest.java101
-rw-r--r--tests/iketests/src/java/com/android/internal/net/eap/statemachine/MethodStateTest.java204
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java1646
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestSchedulerTest.java130
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java4036
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSocketTest.java385
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java336
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipherTest.java160
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacIntegrityTest.java128
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacPrfTest.java187
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeNormalModeCipherTest.java164
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthDigitalSignPayloadTest.java161
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPayloadTest.java133
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPskPayloadTest.java144
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertPayloadTest.java154
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertX509CertPayloadTest.java147
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayloadTest.java631
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeDeletePayloadTest.java264
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEapPayloadTest.java68
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBodyTest.java489
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeHeaderTest.java140
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeIdPayloadTest.java210
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayloadTest.java200
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeMessageTest.java935
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNoncePayloadTest.java49
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java249
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java927
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkPayloadTest.java108
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkfPayloadTest.java201
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTestUtils.java71
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTsPayloadTest.java143
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/CertUtils.java63
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/MockIpSecTestUtils.java91
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/utils/RetransmitterTest.java129
-rw-r--r--tests/iketests/src/java/com/android/internal/net/utils/BigIntegerUtilsTest.java49
-rw-r--r--tests/iketests/src/java/com/android/internal/net/utils/LogTest.java60
-rw-r--r--tests/iketests/src/java/com/android/internal/net/utils/SimpleStateMachineTest.java96
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) {
+ }
+ }
+}