| /* |
| * 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.ike.eap; |
| |
| import static android.telephony.TelephonyManager.APPTYPE_USIM; |
| |
| import static com.android.ike.TestUtils.hexStringToByteArray; |
| import static com.android.ike.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.telephony.TelephonyManager; |
| |
| import com.android.ike.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); |
| } |
| } |