blob: c6ccb0d27dec3e6b4356de2bfeda90b03aac3aa8 [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.eap.EapAuthenticator.LOG;
import static com.android.ike.eap.message.EapData.EAP_TYPE_AKA;
import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE;
import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_CLIENT_ERROR;
import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY;
import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_NOTIFICATION;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ANY_ID_REQ;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ENCR_DATA;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_FULLAUTH_ID_REQ;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_IV;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_PERMANENT_ID_REQ;
import android.content.Context;
import android.telephony.TelephonyManager;
import com.android.ike.eap.EapResult;
import com.android.ike.eap.EapResult.EapError;
import com.android.ike.eap.EapSessionConfig.EapAkaConfig;
import com.android.ike.eap.exceptions.simaka.EapSimAkaIdentityUnavailableException;
import com.android.ike.eap.exceptions.simaka.EapSimAkaInvalidAttributeException;
import com.android.ike.eap.message.EapData.EapMethod;
import com.android.ike.eap.message.EapMessage;
import com.android.ike.eap.message.simaka.EapAkaTypeData;
import com.android.ike.eap.message.simaka.EapAkaTypeData.EapAkaTypeDataDecoder;
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.EapSimAkaTypeData.DecodeResult;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* EapAkaMethodStateMachine represents the valid paths possible for the EAP-AKA protocol.
*
* <p>EAP-AKA sessions will always follow the path:
*
* Created --+--> Identity --+--> Challenge --> Final
* | |
* +---------------+
*
* Note: If the EAP-Request/AKA-Challenge message contains an AUTN with an invalid sequence number,
* the peer will indicate a synchronization failure to the server and a new challenge will be
* attempted.
*
* Note: EAP-Request/Notification messages can be received at any point in the above state machine
* At most one EAP-AKA/Notification message is allowed per EAP-AKA session.
*
* @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication
* Protocol for Authentication and Key Agreement (EAP-AKA)</a>
*/
class EapAkaMethodStateMachine extends EapSimAkaMethodStateMachine {
private static final String TAG = EapAkaMethodStateMachine.class.getSimpleName();
private final TelephonyManager mTelephonyManager;
private final EapAkaConfig mEapAkaConfig;
private final EapAkaTypeDataDecoder mEapAkaTypeDataDecoder;
EapAkaMethodStateMachine(Context context, EapAkaConfig eapAkaConfig) {
this(
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
eapAkaConfig,
EapAkaTypeData.getEapAkaTypeDataDecoder());
}
@VisibleForTesting
protected EapAkaMethodStateMachine(
TelephonyManager telephonyManager,
EapAkaConfig eapAkaConfig,
EapAkaTypeDataDecoder eapAkaTypeDataDecoder) {
mTelephonyManager = telephonyManager.createForSubscriptionId(eapAkaConfig.subId);
mEapAkaConfig = eapAkaConfig;
mEapAkaTypeDataDecoder = eapAkaTypeDataDecoder;
transitionTo(new CreatedState());
}
@Override
@EapMethod
int getEapMethod() {
return EAP_TYPE_AKA;
}
protected class CreatedState extends EapState {
private final String mTAG = CreatedState.class.getSimpleName();
public EapResult process(EapMessage message) {
EapResult result = handleEapSuccessFailureNotification(mTAG, message);
if (result != null) {
return result;
}
DecodeResult<EapAkaTypeData> decodeResult =
mEapAkaTypeDataDecoder.decode(message.eapData.eapTypeData);
if (!decodeResult.isSuccessfulDecode()) {
return buildClientErrorResponse(
message.eapIdentifier,
EAP_TYPE_AKA,
decodeResult.atClientErrorCode);
}
EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
switch (eapAkaTypeData.eapSubtype) {
case EAP_AKA_IDENTITY:
return transitionAndProcess(new IdentityState(), message);
case EAP_AKA_CHALLENGE:
return transitionAndProcess(new ChallengeState(), message);
case EAP_AKA_NOTIFICATION:
// TODO(b/139808612): move EAP-SIM/AKA notification handling to superclass
throw new UnsupportedOperationException(
"EAP-AKA notifications not supported yet");
default:
return buildClientErrorResponse(
message.eapIdentifier,
EAP_TYPE_AKA,
AtClientErrorCode.UNABLE_TO_PROCESS);
}
}
}
protected class IdentityState extends EapState {
private final String mTAG = IdentityState.class.getSimpleName();
public EapResult process(EapMessage message) {
EapResult result = handleEapSuccessFailureNotification(mTAG, message);
if (result != null) {
return result;
}
DecodeResult<EapAkaTypeData> decodeResult =
mEapAkaTypeDataDecoder.decode(message.eapData.eapTypeData);
if (!decodeResult.isSuccessfulDecode()) {
return buildClientErrorResponse(
message.eapIdentifier,
EAP_TYPE_AKA,
decodeResult.atClientErrorCode);
}
EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
switch (eapAkaTypeData.eapSubtype) {
case EAP_AKA_IDENTITY:
break;
case EAP_AKA_CHALLENGE:
return transitionAndProcess(new ChallengeState(), message);
case EAP_AKA_NOTIFICATION:
// TODO(b/139808612): move EAP-SIM/AKA notification handling to superclass
throw new UnsupportedOperationException(
"EAP-AKA notifications not supported yet");
default:
return buildClientErrorResponse(
message.eapIdentifier,
EAP_TYPE_AKA,
AtClientErrorCode.UNABLE_TO_PROCESS);
}
if (!isValidIdentityAttributes(eapAkaTypeData)) {
return buildClientErrorResponse(
message.eapIdentifier,
EAP_TYPE_AKA,
AtClientErrorCode.UNABLE_TO_PROCESS);
}
String imsi = mTelephonyManager.getSubscriberId();
if (imsi == null) {
LOG.e(mTAG, "Unable to get IMSI for subId=" + mEapAkaConfig.subId);
return new EapError(
new EapSimAkaIdentityUnavailableException(
"IMSI for subId (" + mEapAkaConfig.subId + ") not available"));
}
String identity = "0" + imsi;
AtIdentity atIdentity;
try {
atIdentity = AtIdentity.getAtIdentity(identity.getBytes());
} catch (EapSimAkaInvalidAttributeException ex) {
LOG.wtf(mTAG, "Exception thrown while making AtIdentity attribute", ex);
return new EapError(ex);
}
return buildResponseMessage(
EAP_TYPE_AKA,
EAP_AKA_IDENTITY,
message.eapIdentifier,
Arrays.asList(atIdentity));
}
private boolean isValidIdentityAttributes(EapAkaTypeData eapAkaTypeData) {
Set<Integer> attrs = eapAkaTypeData.attributeMap.keySet();
// exactly one ID request type required
int idRequests = 0;
idRequests += attrs.contains(EAP_AT_PERMANENT_ID_REQ) ? 1 : 0;
idRequests += attrs.contains(EAP_AT_ANY_ID_REQ) ? 1 : 0;
idRequests += attrs.contains(EAP_AT_FULLAUTH_ID_REQ) ? 1 : 0;
if (idRequests != 1) {
return false;
}
// can't contain mac, iv, encr data
if (attrs.contains(EAP_AT_MAC)
|| attrs.contains(EAP_AT_IV)
|| attrs.contains(EAP_AT_ENCR_DATA)) {
return false;
}
return true;
}
}
protected class ChallengeState extends EapState {
private final String mTAG = ChallengeState.class.getSimpleName();
public EapResult process(EapMessage message) {
// TODO(b/133879622): implement ChallengeState#process with EapAkaTypeData decoding
return null;
}
}
EapAkaTypeData getEapSimAkaTypeData(AtClientErrorCode clientErrorCode) {
return new EapAkaTypeData(EAP_AKA_CLIENT_ERROR, Arrays.asList(clientErrorCode));
}
EapAkaTypeData getEapSimAkaTypeData(int eapSubtype, List<EapSimAkaAttribute> attributes) {
return new EapAkaTypeData(eapSubtype, attributes);
}
}