aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachine.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachine.java')
-rw-r--r--src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachine.java612
1 files changed, 612 insertions, 0 deletions
diff --git a/src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachine.java b/src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachine.java
new file mode 100644
index 00000000..b26c8e95
--- /dev/null
+++ b/src/java/com/android/internal/net/eap/statemachine/EapAkaMethodStateMachine.java
@@ -0,0 +1,612 @@
+/*
+ * 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.EapAuthenticator.LOG;
+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_FAILURE;
+import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS;
+import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_AUTHENTICATION_REJECT;
+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_CLIENT_ERROR;
+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.EapAkaTypeData.EAP_AKA_SYNCHRONIZATION_FAILURE;
+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_BIDDING;
+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_FULLAUTH_ID_REQ;
+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_RAND;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.eap.EapSessionConfig.EapAkaConfig;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+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.crypto.Fips186_2Prf;
+import com.android.internal.net.eap.exceptions.EapInvalidRequestException;
+import com.android.internal.net.eap.exceptions.EapSilentException;
+import com.android.internal.net.eap.exceptions.simaka.EapAkaInvalidAuthenticationResponse;
+import com.android.internal.net.eap.exceptions.simaka.EapSimAkaAuthenticationFailureException;
+import com.android.internal.net.eap.exceptions.simaka.EapSimAkaIdentityUnavailableException;
+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.EapMethod;
+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;
+import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn;
+import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAuts;
+import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtBidding;
+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.AtRandAka;
+import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRes;
+import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+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();
+
+ // EAP-AKA identity prefix (RFC 4187#4.1.1.6)
+ private static final String AKA_IDENTITY_PREFIX = "0";
+
+ private final EapAkaTypeDataDecoder mEapAkaTypeDataDecoder;
+ private final boolean mSupportsEapAkaPrime;
+
+ protected EapAkaMethodStateMachine(
+ Context context, byte[] eapIdentity, EapAkaConfig eapAkaConfig) {
+ this(context, eapIdentity, eapAkaConfig, false);
+ }
+
+ EapAkaMethodStateMachine(
+ Context context,
+ byte[] eapIdentity,
+ EapAkaConfig eapAkaConfig,
+ boolean supportsEapAkaPrime) {
+ this(
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
+ eapIdentity,
+ eapAkaConfig,
+ EapAkaTypeData.getEapAkaTypeDataDecoder(),
+ supportsEapAkaPrime);
+ }
+
+ @VisibleForTesting
+ protected EapAkaMethodStateMachine(
+ TelephonyManager telephonyManager,
+ byte[] eapIdentity,
+ EapAkaConfig eapAkaConfig,
+ EapAkaTypeDataDecoder eapAkaTypeDataDecoder,
+ boolean supportsEapAkaPrime) {
+ super(
+ telephonyManager.createForSubscriptionId(eapAkaConfig.subId),
+ eapIdentity,
+ eapAkaConfig);
+ mEapAkaTypeDataDecoder = eapAkaTypeDataDecoder;
+ mSupportsEapAkaPrime = supportsEapAkaPrime;
+
+ transitionTo(new CreatedState());
+ }
+
+ @Override
+ @EapMethod
+ int getEapMethod() {
+ return EAP_TYPE_AKA;
+ }
+
+ protected DecodeResult<EapAkaTypeData> decode(byte[] typeData) {
+ return mEapAkaTypeDataDecoder.decode(typeData);
+ }
+
+ /**
+ * This exists so we can override the identity prefix in the EapAkaPrimeMethodStateMachine.
+ *
+ * @return the Identity prefix for this EAP method
+ */
+ protected String getIdentityPrefix() {
+ return AKA_IDENTITY_PREFIX;
+ }
+
+ protected ChallengeState buildChallengeState() {
+ return new ChallengeState();
+ }
+
+ protected ChallengeState buildChallengeState(byte[] identity) {
+ return new ChallengeState(identity);
+ }
+
+ protected class CreatedState extends EapMethodState {
+ private final String mTAG = CreatedState.class.getSimpleName();
+
+ public EapResult process(EapMessage message) {
+ EapResult result = handleEapSuccessFailureNotification(mTAG, message);
+ if (result != null) {
+ return result;
+ }
+
+ DecodeResult<? extends EapAkaTypeData> decodeResult =
+ decode(message.eapData.eapTypeData);
+ if (!decodeResult.isSuccessfulDecode()) {
+ return buildClientErrorResponse(
+ message.eapIdentifier, getEapMethod(), decodeResult.atClientErrorCode);
+ }
+
+ EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
+ switch (eapAkaTypeData.eapSubtype) {
+ case EAP_AKA_IDENTITY:
+ return transitionAndProcess(new IdentityState(), message);
+ case EAP_AKA_CHALLENGE:
+ return transitionAndProcess(buildChallengeState(), message);
+ case EAP_AKA_NOTIFICATION:
+ return handleEapSimAkaNotification(
+ mTAG,
+ true, // isPreChallengeState
+ message.eapIdentifier,
+ eapAkaTypeData);
+ default:
+ return buildClientErrorResponse(
+ message.eapIdentifier,
+ getEapMethod(),
+ AtClientErrorCode.UNABLE_TO_PROCESS);
+ }
+ }
+ }
+
+ protected class IdentityState extends EapMethodState {
+ private final String mTAG = IdentityState.class.getSimpleName();
+
+ private byte[] mIdentity;
+
+ public EapResult process(EapMessage message) {
+ EapResult result = handleEapSuccessFailureNotification(mTAG, message);
+ if (result != null) {
+ return result;
+ }
+
+ DecodeResult<? extends EapAkaTypeData> decodeResult =
+ decode(message.eapData.eapTypeData);
+ if (!decodeResult.isSuccessfulDecode()) {
+ return buildClientErrorResponse(
+ message.eapIdentifier, getEapMethod(), decodeResult.atClientErrorCode);
+ }
+
+ EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
+ switch (eapAkaTypeData.eapSubtype) {
+ case EAP_AKA_IDENTITY:
+ break;
+ case EAP_AKA_CHALLENGE:
+ return transitionAndProcess(buildChallengeState(mIdentity), message);
+ case EAP_AKA_NOTIFICATION:
+ return handleEapSimAkaNotification(
+ mTAG,
+ true, // isPreChallengeState
+ message.eapIdentifier,
+ eapAkaTypeData);
+ default:
+ return buildClientErrorResponse(
+ message.eapIdentifier,
+ getEapMethod(),
+ AtClientErrorCode.UNABLE_TO_PROCESS);
+ }
+
+ if (!isValidIdentityAttributes(eapAkaTypeData)) {
+ LOG.e(mTAG, "Invalid attributes: " + eapAkaTypeData.attributeMap.keySet());
+ 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=" + mEapUiccConfig.subId);
+ return new EapError(
+ new EapSimAkaIdentityUnavailableException(
+ "IMSI for subId (" + mEapUiccConfig.subId + ") not available"));
+ }
+ String identityString = getIdentityPrefix() + imsi;
+ mIdentity = identityString.getBytes(StandardCharsets.US_ASCII);
+ LOG.d(mTAG, "EAP-AKA/Identity=" + LOG.pii(identityString));
+
+ AtIdentity atIdentity;
+ try {
+ atIdentity = AtIdentity.getAtIdentity(mIdentity);
+ } catch (EapSimAkaInvalidAttributeException ex) {
+ LOG.wtf(mTAG, "Exception thrown while making AtIdentity attribute", ex);
+ return new EapError(ex);
+ }
+
+ return buildResponseMessage(
+ getEapMethod(),
+ 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 EapMethodState {
+ private final String mTAG = ChallengeState.class.getSimpleName();
+
+ @VisibleForTesting boolean mHadSuccessfulChallenge = false;
+ @VisibleForTesting protected final byte[] mIdentity;
+
+ // IK and CK lengths defined as 16B (RFC 4187#1)
+ private final int mIkLenBytes = 16;
+ private final int mCkLenBytes = 16;
+
+ // Tags for Successful and Synchronization responses
+ private final byte mSuccess = (byte) 0xDB;
+ private final byte mSynchronization = (byte) 0xDC;
+
+ ChallengeState() {
+ // use the EAP-Identity for the default value (RFC 4187#7)
+ this(mEapIdentity);
+ }
+
+ ChallengeState(byte[] identity) {
+ this.mIdentity = identity;
+ }
+
+ public EapResult process(EapMessage message) {
+ if (message.eapCode == EAP_CODE_SUCCESS) {
+ if (!mHadSuccessfulChallenge) {
+ LOG.e(mTAG, "Received unexpected EAP-Success");
+ return new EapError(
+ new EapInvalidRequestException(
+ "Received an EAP-Success in the ChallengeState"));
+ }
+ transitionTo(new FinalState());
+ return new EapSuccess(mMsk, mEmsk);
+ } else if (message.eapCode == EAP_CODE_FAILURE) {
+ transitionTo(new FinalState());
+ return new EapFailure();
+ } else if (message.eapData.eapType == EAP_NOTIFICATION) {
+ return handleEapNotification(mTAG, message);
+ }
+
+ if (message.eapData.eapType != getEapMethod()) {
+ return new EapError(new EapInvalidRequestException(
+ "Expected EAP Type " + getEapMethod()
+ + ", received " + message.eapData.eapType));
+ }
+
+ DecodeResult<? extends EapAkaTypeData> decodeResult =
+ decode(message.eapData.eapTypeData);
+ if (!decodeResult.isSuccessfulDecode()) {
+ return buildClientErrorResponse(
+ message.eapIdentifier, getEapMethod(), decodeResult.atClientErrorCode);
+ }
+
+ EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
+ switch (eapAkaTypeData.eapSubtype) {
+ case EAP_AKA_CHALLENGE:
+ break;
+ case EAP_AKA_NOTIFICATION:
+ return handleEapSimAkaNotification(
+ mTAG,
+ false, // isPreChallengeState
+ message.eapIdentifier,
+ eapAkaTypeData);
+ default:
+ return buildClientErrorResponse(
+ message.eapIdentifier,
+ getEapMethod(),
+ AtClientErrorCode.UNABLE_TO_PROCESS);
+ }
+
+ if (!isValidChallengeAttributes(eapAkaTypeData)) {
+ LOG.e(mTAG, "Invalid attributes: " + eapAkaTypeData.attributeMap.keySet());
+ return buildClientErrorResponse(
+ message.eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
+ }
+
+ return handleChallengeAuthentication(message, eapAkaTypeData);
+ }
+
+ protected EapResult handleChallengeAuthentication(
+ EapMessage message, EapAkaTypeData eapAkaTypeData) {
+ RandChallengeResult result;
+ try {
+ result = getRandChallengeResult(eapAkaTypeData);
+ } catch (EapAkaInvalidAuthenticationResponse ex) {
+ return new EapError(ex);
+ } catch (EapSimAkaInvalidLengthException | BufferUnderflowException ex) {
+ LOG.e(mTAG, "Invalid response returned from SIM", ex);
+ return buildClientErrorResponse(
+ message.eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
+ } catch (EapSimAkaAuthenticationFailureException ex) {
+ // Return EAP-Response/AKA-Authentication-Reject when the AUTN is rejected
+ // (RFC 4187#6.3.1)
+ return buildAuthenticationRejectMessage(message.eapIdentifier);
+ }
+
+ if (!result.isSuccessfulResult()) {
+ try {
+ return buildResponseMessage(
+ getEapMethod(),
+ EAP_AKA_SYNCHRONIZATION_FAILURE,
+ message.eapIdentifier,
+ Arrays.asList(new AtAuts(result.auts)));
+ } catch (EapSimAkaInvalidAttributeException ex) {
+ LOG.wtf(mTAG, "Error creating an AtAuts attr", ex);
+ return new EapError(ex);
+ }
+ }
+
+ EapResult eapResult =
+ generateAndPersistEapAkaKeys(result, message.eapIdentifier, eapAkaTypeData);
+ if (eapResult != null) {
+ return eapResult;
+ }
+
+ try {
+ if (!isValidMac(mTAG, message, eapAkaTypeData, new byte[0])) {
+ return buildClientErrorResponse(
+ message.eapIdentifier,
+ getEapMethod(),
+ AtClientErrorCode.UNABLE_TO_PROCESS);
+ }
+ } catch (GeneralSecurityException
+ | EapSilentException
+ | EapSimAkaInvalidAttributeException ex) {
+ // if the MAC can't be generated, we can't continue
+ LOG.e(mTAG, "Error computing MAC for EapMessage", ex);
+ return new EapError(ex);
+ }
+
+ // before sending a response, check for bidding-down attacks (RFC 5448#4)
+ if (mSupportsEapAkaPrime) {
+ AtBidding atBidding = (AtBidding) eapAkaTypeData.attributeMap.get(EAP_AT_BIDDING);
+ if (atBidding != null && atBidding.doesServerSupportEapAkaPrime) {
+ LOG.w(
+ mTAG,
+ "Potential bidding down attack. AT_BIDDING attr included and EAP-AKA'"
+ + " is supported");
+ return buildAuthenticationRejectMessage(message.eapIdentifier);
+ }
+ }
+
+ // server has been authenticated, so we can send a response
+ try {
+ mHadSuccessfulChallenge = true;
+ return buildResponseMessageWithMac(
+ message.eapIdentifier,
+ EAP_AKA_CHALLENGE,
+ new byte[0],
+ Arrays.asList(AtRes.getAtRes(result.res)));
+ } catch (EapSimAkaInvalidAttributeException ex) {
+ LOG.wtf(mTAG, "Error creating AtRes value", ex);
+ return new EapError(ex);
+ }
+ }
+
+ @VisibleForTesting
+ class RandChallengeResult {
+ public final byte[] res;
+ public final byte[] ik;
+ public final byte[] ck;
+ public final byte[] auts;
+
+ RandChallengeResult(byte[] res, byte[] ik, byte[] ck)
+ throws EapSimAkaInvalidLengthException {
+ if (!AtRes.isValidResLen(res.length)) {
+ throw new EapSimAkaInvalidLengthException("Invalid RES length");
+ } else if (ik.length != mIkLenBytes) {
+ throw new EapSimAkaInvalidLengthException("Invalid IK length");
+ } else if (ck.length != mCkLenBytes) {
+ throw new EapSimAkaInvalidLengthException("Invalid CK length");
+ }
+
+ this.res = res;
+ this.ik = ik;
+ this.ck = ck;
+ this.auts = null;
+ }
+
+ RandChallengeResult(byte[] auts) throws EapSimAkaInvalidLengthException {
+ if (auts.length != AtAuts.AUTS_LENGTH) {
+ throw new EapSimAkaInvalidLengthException("Invalid AUTS length");
+ }
+
+ this.res = null;
+ this.ik = null;
+ this.ck = null;
+ this.auts = auts;
+ }
+
+ private boolean isSuccessfulResult() {
+ return res != null && ik != null && ck != null;
+ }
+ }
+
+ private boolean isValidChallengeAttributes(EapAkaTypeData eapAkaTypeData) {
+ Set<Integer> attrs = eapAkaTypeData.attributeMap.keySet();
+
+ // must contain: AT_RAND, AT_AUTN, AT_MAC
+ return attrs.contains(EAP_AT_RAND)
+ && attrs.contains(EAP_AT_AUTN)
+ && attrs.contains(EAP_AT_MAC);
+ }
+
+ private RandChallengeResult getRandChallengeResult(EapAkaTypeData eapAkaTypeData)
+ throws EapSimAkaAuthenticationFailureException, EapSimAkaInvalidLengthException {
+ AtRandAka atRandAka = (AtRandAka) eapAkaTypeData.attributeMap.get(EAP_AT_RAND);
+ AtAutn atAutn = (AtAutn) eapAkaTypeData.attributeMap.get(EAP_AT_AUTN);
+
+ // pre-Base64 formatting needs to be: [Length][RAND][Length][AUTN]
+ int randLen = atRandAka.rand.length;
+ int autnLen = atAutn.autn.length;
+ ByteBuffer formattedChallenge = ByteBuffer.allocate(1 + randLen + 1 + autnLen);
+ formattedChallenge.put((byte) randLen);
+ formattedChallenge.put(atRandAka.rand);
+ formattedChallenge.put((byte) autnLen);
+ formattedChallenge.put(atAutn.autn);
+
+ byte[] challengeResponse =
+ processUiccAuthentication(
+ mTAG,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ formattedChallenge.array());
+ ByteBuffer buffer = ByteBuffer.wrap(challengeResponse);
+ byte tag = buffer.get();
+
+ switch (tag) {
+ case mSuccess:
+ // response format: [tag][RES length][RES][IK length][IK][CK length][CK]
+ break;
+ case mSynchronization:
+ // response format: [tag][AUTS length][AUTS]
+ byte[] auts = new byte[Byte.toUnsignedInt(buffer.get())];
+ buffer.get(auts);
+
+ LOG.i(mTAG, "Synchronization Failure");
+ LOG.d(
+ mTAG,
+ "RAND=" + LOG.pii(atRandAka.rand)
+ + " AUTN=" + LOG.pii(atAutn.autn)
+ + " AUTS=" + LOG.pii(auts));
+
+ return new RandChallengeResult(auts);
+ default:
+ throw new EapAkaInvalidAuthenticationResponse(
+ "Invalid tag for UICC response: " + String.format("%02X", tag));
+ }
+
+ byte[] res = new byte[Byte.toUnsignedInt(buffer.get())];
+ buffer.get(res);
+
+ byte[] ik = new byte[Byte.toUnsignedInt(buffer.get())];
+ buffer.get(ik);
+
+ byte[] ck = new byte[Byte.toUnsignedInt(buffer.get())];
+ buffer.get(ck);
+
+ LOG.d(
+ mTAG,
+ "RAND=" + LOG.pii(atRandAka.rand)
+ + " AUTN=" + LOG.pii(atAutn.autn)
+ + " RES=" + LOG.pii(res)
+ + " IK=" + LOG.pii(ik)
+ + " CK=" + LOG.pii(ck));
+
+ return new RandChallengeResult(res, ik, ck);
+ }
+
+ protected EapResult buildAuthenticationRejectMessage(int eapIdentifier) {
+ return buildResponseMessage(
+ getEapMethod(),
+ EAP_AKA_AUTHENTICATION_REJECT,
+ eapIdentifier,
+ new ArrayList<>());
+ }
+
+ @Nullable
+ protected EapResult generateAndPersistEapAkaKeys(
+ RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData) {
+ try {
+ MessageDigest sha1 = MessageDigest.getInstance(MASTER_KEY_GENERATION_ALG);
+ byte[] mkInputData = getMkInputData(result);
+ generateAndPersistKeys(mTAG, sha1, new Fips186_2Prf(), mkInputData);
+ return null;
+ } catch (NoSuchAlgorithmException | BufferUnderflowException ex) {
+ LOG.e(mTAG, "Error while creating keys", ex);
+ return buildClientErrorResponse(
+ eapIdentifier, EAP_TYPE_AKA, AtClientErrorCode.UNABLE_TO_PROCESS);
+ }
+ }
+
+ private byte[] getMkInputData(RandChallengeResult result) {
+ int numInputBytes = mIdentity.length + result.ik.length + result.ck.length;
+ ByteBuffer buffer = ByteBuffer.allocate(numInputBytes);
+ buffer.put(mIdentity);
+ buffer.put(result.ik);
+ buffer.put(result.ck);
+ return buffer.array();
+ }
+ }
+
+ 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);
+ }
+}