blob: 47721da7cc3afac380d4ba1694a952842cbad21b [file] [log] [blame]
/*
* 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.statemachine;
import static com.android.ike.TestUtils.hexStringToByteArray;
import static com.android.ike.eap.message.EapData.EAP_TYPE_SIM;
import static com.android.ike.eap.message.EapMessage.EAP_CODE_REQUEST;
import static com.android.ike.eap.message.EapTestMessageDefinitions.COMPUTED_MAC;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_CHALLENGE_RESPONSE_MAC_INPUT;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_CHALLENGE_RESPONSE_WITH_MAC;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_CLIENT_ERROR_RESPONSE;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_IDENTITY;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_REQUEST_WITH_EMPTY_MAC;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_RESPONSE;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_RESPONSE_WITH_EMPTY_MAC;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_NOTIFICATION_RESPONSE_WITH_MAC;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_SIM_RESPONSE_PACKET;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EMSK;
import static com.android.ike.eap.message.EapTestMessageDefinitions.EMSK_STRING;
import static com.android.ike.eap.message.EapTestMessageDefinitions.ID_INT;
import static com.android.ike.eap.message.EapTestMessageDefinitions.KC_1;
import static com.android.ike.eap.message.EapTestMessageDefinitions.KC_2;
import static com.android.ike.eap.message.EapTestMessageDefinitions.K_AUT;
import static com.android.ike.eap.message.EapTestMessageDefinitions.K_AUT_STRING;
import static com.android.ike.eap.message.EapTestMessageDefinitions.K_ENCR;
import static com.android.ike.eap.message.EapTestMessageDefinitions.K_ENCR_STRING;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MAC_INPUT;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MK;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSK;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSK_STRING;
import static com.android.ike.eap.message.EapTestMessageDefinitions.ORIGINAL_MAC;
import static com.android.ike.eap.message.EapTestMessageDefinitions.SRES_1;
import static com.android.ike.eap.message.EapTestMessageDefinitions.SRES_BYTES;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_POST_CHALLENGE;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtNotification.GENERAL_FAILURE_PRE_CHALLENGE;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC;
import static com.android.ike.eap.message.simaka.EapSimTypeData.EAP_SIM_CHALLENGE;
import static com.android.ike.eap.message.simaka.EapSimTypeData.EAP_SIM_CLIENT_ERROR;
import static com.android.ike.eap.message.simaka.EapSimTypeData.EAP_SIM_NOTIFICATION;
import static com.android.ike.eap.message.simaka.EapSimTypeData.EAP_SIM_START;
import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.AT_IDENTITY;
import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.IDENTITY;
import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_MT;
import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.NONCE_MT_STRING;
import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1_BYTES;
import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_2_BYTES;
import static com.android.ike.eap.statemachine.EapSimAkaMethodStateMachine.KEY_LEN;
import static com.android.ike.eap.statemachine.EapSimAkaMethodStateMachine.MAC_ALGORITHM_STRING;
import static com.android.ike.eap.statemachine.EapSimAkaMethodStateMachine.MASTER_KEY_GENERATION_ALG;
import static com.android.ike.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.telephony.TelephonyManager;
import com.android.ike.eap.EapResult;
import com.android.ike.eap.EapResult.EapError;
import com.android.ike.eap.EapResult.EapResponse;
import com.android.ike.eap.EapSessionConfig.EapSimConfig;
import com.android.ike.eap.crypto.Fips186_2Prf;
import com.android.ike.eap.exceptions.EapInvalidRequestException;
import com.android.ike.eap.exceptions.simaka.EapSimAkaAuthenticationFailureException;
import com.android.ike.eap.message.EapData;
import com.android.ike.eap.message.EapMessage;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtIdentity;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtMac;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtNotification;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtRandSim;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtSelectedVersion;
import com.android.ike.eap.message.simaka.EapSimAkaTypeData;
import com.android.ike.eap.message.simaka.EapSimTypeData;
import com.android.ike.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());
}
}