1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.net.eap.statemachine;
18 
19 import static android.net.eap.EapSessionConfig.EapMethodConfig.EAP_TYPE_AKA;
20 
21 import static com.android.internal.net.eap.EapAuthenticator.LOG;
22 import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS;
23 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_AUTHENTICATION_REJECT;
24 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE;
25 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CLIENT_ERROR;
26 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY;
27 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_NOTIFICATION;
28 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_REAUTHENTICATION;
29 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_SYNCHRONIZATION_FAILURE;
30 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ANY_ID_REQ;
31 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTN;
32 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_BIDDING;
33 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_COUNTER;
34 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ENCR_DATA;
35 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_FULLAUTH_ID_REQ;
36 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_IV;
37 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC;
38 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_NEXT_REAUTH_ID;
39 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_NONCE_S;
40 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_PERMANENT_ID_REQ;
41 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RAND;
42 
43 import android.annotation.Nullable;
44 import android.content.Context;
45 import android.net.eap.EapAkaInfo;
46 import android.net.eap.EapSessionConfig.EapAkaConfig;
47 import android.net.eap.EapSessionConfig.EapMethodConfig.EapMethod;
48 import android.telephony.TelephonyManager;
49 
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.net.eap.EapResult;
52 import com.android.internal.net.eap.EapResult.EapError;
53 import com.android.internal.net.eap.EapResult.EapSuccess;
54 import com.android.internal.net.eap.EapSimAkaIdentityTracker;
55 import com.android.internal.net.eap.EapSimAkaIdentityTracker.ReauthInfo;
56 import com.android.internal.net.eap.crypto.Fips186_2Prf;
57 import com.android.internal.net.eap.exceptions.EapInvalidRequestException;
58 import com.android.internal.net.eap.exceptions.EapSilentException;
59 import com.android.internal.net.eap.exceptions.simaka.EapAkaInvalidAuthenticationResponse;
60 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaAuthenticationFailureException;
61 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaIdentityUnavailableException;
62 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException;
63 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidLengthException;
64 import com.android.internal.net.eap.message.EapMessage;
65 import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData;
66 import com.android.internal.net.eap.message.simaka.EapAkaTypeData;
67 import com.android.internal.net.eap.message.simaka.EapAkaTypeData.EapAkaTypeDataDecoder;
68 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute;
69 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn;
70 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAuts;
71 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtBidding;
72 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode;
73 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtIdentity;
74 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRandAka;
75 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtRes;
76 import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult;
77 
78 import java.nio.BufferUnderflowException;
79 import java.nio.ByteBuffer;
80 import java.nio.charset.StandardCharsets;
81 import java.security.GeneralSecurityException;
82 import java.security.MessageDigest;
83 import java.security.NoSuchAlgorithmException;
84 import java.security.SecureRandom;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.LinkedHashMap;
88 import java.util.List;
89 import java.util.Set;
90 
91 /**
92  * EapAkaMethodStateMachine represents the valid paths possible for the EAP-AKA protocol.
93  *
94  * <p>EAP-AKA sessions will always follow the path:
95  *
96  * Created --+--> Identity ->+--+>----> Challenge ------>+-> Final
97  *           |               ^  v           ^            ^
98  *           |               |  |           |            |
99  *           +---------------+  +--> Re-authentication --+
100  *
101  * <p>Note: If the EAP-Request/AKA-Challenge message contains an AUTN with an invalid sequence
102  * number, the peer will indicate a synchronization failure to the server and a new challenge will
103  * be attempted.
104  *
105  * <p>Note: EAP-Request/Notification messages can be received at any point in the above state
106  * machine At most one EAP-AKA/Notification message is allowed per EAP-AKA session.
107  *
108  * @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication Protocol
109  *     for Authentication and Key Agreement (EAP-AKA)</a>
110  */
111 class EapAkaMethodStateMachine extends EapSimAkaMethodStateMachine {
112     private static final String TAG = EapAkaMethodStateMachine.class.getSimpleName();
113 
114     // EAP-AKA identity prefix (RFC 4187#4.1.1.6)
115     private static final String AKA_IDENTITY_PREFIX = "0";
116 
117     private final EapAkaTypeDataDecoder mEapAkaTypeDataDecoder;
118     private final EapSimAkaIdentityTracker mEapSimAkaIdentityTracker;
119     private final boolean mSupportsEapAkaPrime;
120 
EapAkaMethodStateMachine( Context context, byte[] eapIdentity, EapAkaConfig eapAkaConfig)121     protected EapAkaMethodStateMachine(
122             Context context, byte[] eapIdentity, EapAkaConfig eapAkaConfig) {
123         this(context, eapIdentity, eapAkaConfig, false, null);
124     }
125 
EapAkaMethodStateMachine( Context context, byte[] eapIdentity, EapAkaConfig eapAkaConfig, boolean supportsEapAkaPrime, SecureRandom secureRandom)126     EapAkaMethodStateMachine(
127             Context context,
128             byte[] eapIdentity,
129             EapAkaConfig eapAkaConfig,
130             boolean supportsEapAkaPrime,
131             SecureRandom secureRandom) {
132         this(
133                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
134                 eapIdentity,
135                 eapAkaConfig,
136                 EapAkaTypeData.getEapAkaTypeDataDecoder(),
137                 supportsEapAkaPrime,
138                 secureRandom);
139     }
140 
141     @VisibleForTesting
EapAkaMethodStateMachine( TelephonyManager telephonyManager, byte[] eapIdentity, EapAkaConfig eapAkaConfig, EapAkaTypeDataDecoder eapAkaTypeDataDecoder, boolean supportsEapAkaPrime, SecureRandom secureRandom)142     protected EapAkaMethodStateMachine(
143             TelephonyManager telephonyManager,
144             byte[] eapIdentity,
145             EapAkaConfig eapAkaConfig,
146             EapAkaTypeDataDecoder eapAkaTypeDataDecoder,
147             boolean supportsEapAkaPrime,
148             SecureRandom secureRandom) {
149         super(
150                 telephonyManager.createForSubscriptionId(eapAkaConfig.getSubId()),
151                 eapIdentity,
152                 eapAkaConfig);
153         mEapAkaTypeDataDecoder = eapAkaTypeDataDecoder;
154         mSupportsEapAkaPrime = supportsEapAkaPrime;
155         mEapSimAkaIdentityTracker = EapSimAkaIdentityTracker.getInstance();
156         mSecureRandom = secureRandom;
157         transitionTo(new CreatedState());
158     }
159 
getReauthIdentity()160     private byte[] getReauthIdentity() {
161         EapAkaConfig akaConfig = (EapAkaConfig) mEapUiccConfig;
162         if (akaConfig.getEapAkaOption() != null
163                 && akaConfig.getEapAkaOption().getReauthId() != null) {
164             return akaConfig.getEapAkaOption().getReauthId();
165         }
166         return null;
167     }
168 
getAvailableReauthInfo(byte[] reauthId, byte[] eapId)169     private ReauthInfo getAvailableReauthInfo(byte[] reauthId, byte[] eapId) {
170         if (reauthId == null || eapId == null) {
171             LOG.e(TAG, "getAvailableReauthInfo with NULL ID");
172             return null;
173         }
174         String reauthIdentity = new String(reauthId, StandardCharsets.UTF_8);
175         String permanentIdentity = new String(eapId, StandardCharsets.UTF_8);
176         ReauthInfo reauthInfo =
177                 mEapSimAkaIdentityTracker.getReauthInfo(reauthIdentity, permanentIdentity);
178         mEapSimAkaIdentityTracker.deleteReauthInfo(reauthIdentity, permanentIdentity);
179         if (reauthInfo != null && reauthInfo.isValid()) {
180             return reauthInfo;
181         }
182         return null;
183     }
184 
185     @Override
186     @EapMethod
getEapMethod()187     int getEapMethod() {
188         return EAP_TYPE_AKA;
189     }
190 
decode(byte[] typeData)191     protected DecodeResult<EapAkaTypeData> decode(byte[] typeData) {
192         return mEapAkaTypeDataDecoder.decode(typeData);
193     }
194 
195     /**
196      * This exists so we can override the identity prefix in the EapAkaPrimeMethodStateMachine.
197      *
198      * @return the Identity prefix for this EAP method
199      */
getIdentityPrefix()200     protected String getIdentityPrefix() {
201         return AKA_IDENTITY_PREFIX;
202     }
203 
buildChallengeState()204     protected ChallengeState buildChallengeState() {
205         return new ChallengeState();
206     }
207 
buildChallengeState(byte[] identity)208     protected ChallengeState buildChallengeState(byte[] identity) {
209         return new ChallengeState(identity);
210     }
211 
buildReauthState()212     protected ReauthState buildReauthState() {
213         return new ReauthState();
214     }
215 
buildReauthState(byte[] identity, ReauthInfo reauthInfo)216     protected ReauthState buildReauthState(byte[] identity, ReauthInfo reauthInfo) {
217         return new ReauthState(identity, reauthInfo);
218     }
219 
220     protected class CreatedState extends EapMethodState {
221         private final String mTAG = CreatedState.class.getSimpleName();
222 
process(EapMessage message)223         public EapResult process(EapMessage message) {
224             EapResult result = handleEapSuccessFailureNotification(mTAG, message);
225             if (result != null) {
226                 return result;
227             }
228 
229             DecodeResult<? extends EapAkaTypeData> decodeResult =
230                     decode(message.eapData.eapTypeData);
231             if (!decodeResult.isSuccessfulDecode()) {
232                 return buildClientErrorResponse(
233                         message.eapIdentifier, getEapMethod(), decodeResult.atClientErrorCode);
234             }
235 
236             EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
237             switch (eapAkaTypeData.eapSubtype) {
238                 case EAP_AKA_IDENTITY:
239                     return transitionAndProcess(new IdentityState(), message);
240                 case EAP_AKA_CHALLENGE:
241                     return transitionAndProcess(buildChallengeState(), message);
242                 case EAP_AKA_REAUTHENTICATION:
243                     return transitionAndProcess(buildReauthState(), message);
244                 case EAP_AKA_NOTIFICATION:
245                     return handleEapSimAkaNotification(
246                             mTAG,
247                             true, // isPreChallengeState
248                             false, // isReauthState
249                             false, // hadSuccessfulAuthentication
250                             message.eapIdentifier,
251                             0,
252                             eapAkaTypeData);
253                 default:
254                     return buildClientErrorResponse(
255                             message.eapIdentifier,
256                             getEapMethod(),
257                             AtClientErrorCode.UNABLE_TO_PROCESS);
258             }
259         }
260     }
261 
262     protected class IdentityState extends EapMethodState {
263         private final String mTAG = IdentityState.class.getSimpleName();
264 
265         private byte[] mIdentity;
266         private ReauthInfo mReauthInfo;
267         private byte[] mReauthIdentity;
268 
IdentityState()269         IdentityState() {
270             mReauthIdentity = getReauthIdentity();
271             mReauthInfo = getAvailableReauthInfo(mReauthIdentity, mEapIdentity);
272         }
273 
process(EapMessage message)274         public EapResult process(EapMessage message) {
275             EapResult result = handleEapSuccessFailureNotification(mTAG, message);
276             if (result != null) {
277                 return result;
278             }
279 
280             DecodeResult<? extends EapAkaTypeData> decodeResult =
281                     decode(message.eapData.eapTypeData);
282             if (!decodeResult.isSuccessfulDecode()) {
283                 return buildClientErrorResponse(
284                         message.eapIdentifier, getEapMethod(), decodeResult.atClientErrorCode);
285             }
286 
287             EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
288             switch (eapAkaTypeData.eapSubtype) {
289                 case EAP_AKA_IDENTITY:
290                     break;
291                 case EAP_AKA_CHALLENGE:
292                     return transitionAndProcess(buildChallengeState(mIdentity), message);
293                 case EAP_AKA_REAUTHENTICATION:
294                     return transitionAndProcess(
295                             buildReauthState(mReauthIdentity, mReauthInfo), message);
296                 case EAP_AKA_NOTIFICATION:
297                     return handleEapSimAkaNotification(
298                             mTAG,
299                             true, // isPreChallengeState
300                             false, // isReauthState
301                             false, // hadSuccessfulAuthentication
302                             message.eapIdentifier,
303                             0,
304                             eapAkaTypeData);
305                 default:
306                     return buildClientErrorResponse(
307                             message.eapIdentifier,
308                             getEapMethod(),
309                             AtClientErrorCode.UNABLE_TO_PROCESS);
310             }
311 
312             if (!isValidIdentityAttributes(eapAkaTypeData)) {
313                 LOG.e(mTAG, "Invalid attributes: " + eapAkaTypeData.attributeMap.keySet());
314                 return buildClientErrorResponse(
315                         message.eapIdentifier,
316                         EAP_TYPE_AKA,
317                         AtClientErrorCode.UNABLE_TO_PROCESS);
318             }
319 
320             byte[] identityToResponse;
321             if (eapAkaTypeData.attributeMap.get(EAP_AT_ANY_ID_REQ) != null
322                     && mReauthIdentity != null
323                     && mReauthInfo != null) {
324 
325                 identityToResponse = mReauthIdentity;
326                 LOG.d(
327                         mTAG,
328                         "Send EAP-AKA/Identity response with Reauth ID:"
329                                 + LOG.pii(new String(mIdentity, StandardCharsets.UTF_8)));
330             } else {
331                 String imsi = mTelephonyManager.getSubscriberId();
332                 if (imsi == null) {
333                     int subId = mEapUiccConfig.getSubId();
334                     LOG.e(mTAG, "Unable to get IMSI for subId=" + subId);
335                     return new EapError(
336                             new EapSimAkaIdentityUnavailableException(
337                                     "IMSI for subId (" + subId + ") not available"));
338                 }
339                 String identityString = getIdentityPrefix() + imsi;
340                 mIdentity = identityString.getBytes(StandardCharsets.US_ASCII);
341                 identityToResponse = mIdentity;
342                 LOG.d(mTAG, "Send EAP-AKA/Identity response with permanent ID:"
343                         + LOG.pii(identityString));
344             }
345 
346             AtIdentity atIdentity;
347             try {
348                 atIdentity = AtIdentity.getAtIdentity(identityToResponse);
349             } catch (EapSimAkaInvalidAttributeException ex) {
350                 LOG.wtf(mTAG, "Exception thrown while making AtIdentity attribute", ex);
351                 return new EapError(ex);
352             }
353 
354             return buildResponseMessage(
355                     getEapMethod(),
356                     EAP_AKA_IDENTITY,
357                     message.eapIdentifier,
358                     Arrays.asList(atIdentity));
359         }
360 
isValidIdentityAttributes(EapAkaTypeData eapAkaTypeData)361         private boolean isValidIdentityAttributes(EapAkaTypeData eapAkaTypeData) {
362             Set<Integer> attrs = eapAkaTypeData.attributeMap.keySet();
363 
364             // exactly one ID request type required
365             int idRequests = 0;
366             idRequests += attrs.contains(EAP_AT_PERMANENT_ID_REQ) ? 1 : 0;
367             idRequests += attrs.contains(EAP_AT_ANY_ID_REQ) ? 1 : 0;
368             idRequests += attrs.contains(EAP_AT_FULLAUTH_ID_REQ) ? 1 : 0;
369 
370             if (idRequests != 1) {
371                 return false;
372             }
373 
374             // can't contain mac, iv, encr data
375             if (attrs.contains(EAP_AT_MAC)
376                     || attrs.contains(EAP_AT_IV)
377                     || attrs.contains(EAP_AT_ENCR_DATA)) {
378                 return false;
379             }
380             return true;
381         }
382     }
383 
384     protected class ChallengeState extends EapMethodState {
385         private final String mTAG = ChallengeState.class.getSimpleName();
386 
387         @VisibleForTesting boolean mHadSuccessfulChallenge = false;
388         @VisibleForTesting protected final byte[] mIdentity;
389 
390         // IK and CK lengths defined as 16B (RFC 4187#1)
391         private static final int IK_LEN_BYTES = 16;
392         private static final int CK_LEN_BYTES = 16;
393 
394         // Tags for Successful and Synchronization responses
395         private static final byte RAND_SUCCESS = (byte) 0xDB;
396         private static final byte RAND_SYNCHRONIZATION = (byte) 0xDC;
397 
398         private byte[] mNextReauthIdentity;
399 
ChallengeState()400         ChallengeState() {
401             // use the EAP-Identity for the default value (RFC 4187#7)
402             this(mEapIdentity);
403         }
404 
ChallengeState(byte[] identity)405         ChallengeState(byte[] identity) {
406             this.mIdentity = identity;
407         }
408 
process(EapMessage message)409         public EapResult process(EapMessage message) {
410             if (message.eapCode == EAP_CODE_SUCCESS) {
411                 if (!mHadSuccessfulChallenge) {
412                     LOG.e(mTAG, "Received unexpected EAP-Success");
413                     return new EapError(
414                             new EapInvalidRequestException(
415                                     "Received an EAP-Success in the ChallengeState"));
416                 }
417 
418                 transitionTo(new FinalState());
419                 return processEapSuccess(mNextReauthIdentity, 0 /* reauthCounter */);
420             }
421 
422             EapResult result = handleEapSuccessFailureNotification(mTAG, message);
423             if (result != null) {
424                 return result;
425             }
426 
427             DecodeResult<? extends EapAkaTypeData> decodeResult =
428                     decode(message.eapData.eapTypeData);
429             if (!decodeResult.isSuccessfulDecode()) {
430                 return buildClientErrorResponse(
431                         message.eapIdentifier, getEapMethod(), decodeResult.atClientErrorCode);
432             }
433 
434             EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
435             switch (eapAkaTypeData.eapSubtype) {
436                 case EAP_AKA_CHALLENGE:
437                     break;
438                 case EAP_AKA_NOTIFICATION:
439                     return handleEapSimAkaNotification(
440                             mTAG,
441                             false, // isPreChallengeState
442                             false, // isReauthState
443                             mHadSuccessfulChallenge, // hadSuccessfulAuthentication
444                             message.eapIdentifier,
445                             0,
446                             eapAkaTypeData);
447                 default:
448                     return buildClientErrorResponse(
449                             message.eapIdentifier,
450                             getEapMethod(),
451                             AtClientErrorCode.UNABLE_TO_PROCESS);
452             }
453 
454             if (!isValidChallengeAttributes(eapAkaTypeData)) {
455                 LOG.e(mTAG, "Invalid attributes: " + eapAkaTypeData.attributeMap.keySet());
456                 return buildClientErrorResponse(
457                         message.eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
458             }
459 
460             return handleChallengeAuthentication(message, eapAkaTypeData);
461         }
462 
handleChallengeAuthentication( EapMessage message, EapAkaTypeData eapAkaTypeData)463         protected EapResult handleChallengeAuthentication(
464                 EapMessage message, EapAkaTypeData eapAkaTypeData) {
465             RandChallengeResult result;
466             try {
467                 result = getRandChallengeResult(eapAkaTypeData);
468             } catch (EapAkaInvalidAuthenticationResponse ex) {
469                 return new EapError(ex);
470             } catch (EapSimAkaInvalidLengthException | BufferUnderflowException ex) {
471                 LOG.e(mTAG, "Invalid response returned from SIM", ex);
472                 return buildClientErrorResponse(
473                         message.eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
474             } catch (EapSimAkaAuthenticationFailureException ex) {
475                 // Return EAP-Response/AKA-Authentication-Reject when the AUTN is rejected
476                 // (RFC 4187#6.3.1)
477                 return buildAuthenticationRejectMessage(message.eapIdentifier);
478             }
479 
480             if (!result.isSuccessfulResult()) {
481                 try {
482                     return buildResponseMessage(
483                             getEapMethod(),
484                             EAP_AKA_SYNCHRONIZATION_FAILURE,
485                             message.eapIdentifier,
486                             Arrays.asList(new AtAuts(result.auts)));
487                 } catch (EapSimAkaInvalidAttributeException ex) {
488                     LOG.wtf(mTAG, "Error creating an AtAuts attr", ex);
489                     return new EapError(ex);
490                 }
491             }
492 
493             EapResult eapResult =
494                     generateAndPersistEapAkaKeys(result, message.eapIdentifier, eapAkaTypeData);
495             if (eapResult != null) {
496                 return eapResult;
497             }
498 
499             try {
500                 if (!isValidMac(mTAG, message, eapAkaTypeData, new byte[0])) {
501                     return buildClientErrorResponse(
502                             message.eapIdentifier,
503                             getEapMethod(),
504                             AtClientErrorCode.UNABLE_TO_PROCESS);
505                 }
506             } catch (GeneralSecurityException
507                     | EapSilentException
508                     | EapSimAkaInvalidAttributeException ex) {
509                 // if the MAC can't be generated, we can't continue
510                 LOG.e(mTAG, "Error computing MAC for EapMessage", ex);
511                 return new EapError(ex);
512             }
513 
514             // before sending a response, check for bidding-down attacks (RFC 5448#4)
515             if (mSupportsEapAkaPrime) {
516                 AtBidding atBidding = (AtBidding) eapAkaTypeData.attributeMap.get(EAP_AT_BIDDING);
517                 if (atBidding != null && atBidding.doesServerSupportEapAkaPrime) {
518                     LOG.w(
519                             mTAG,
520                             "Potential bidding down attack. AT_BIDDING attr included and EAP-AKA'"
521                                 + " is supported");
522                     return buildAuthenticationRejectMessage(message.eapIdentifier);
523                 }
524             }
525 
526             // try to retrieve Next Reauth ID
527             mNextReauthIdentity = retrieveNextReauthId(mTAG, eapAkaTypeData);
528 
529             // server has been authenticated, so we can send a response
530             try {
531                 mHadSuccessfulChallenge = true;
532                 return buildResponseMessageWithMac(
533                         message.eapIdentifier,
534                         EAP_AKA_CHALLENGE,
535                         new byte[0],
536                         Arrays.asList(AtRes.getAtRes(result.res)),
537                         (eapAkaTypeData instanceof EapAkaPrimeTypeData)
538                                 ? null /* no flags set */
539                                 : new int[] {
540                                     EapResult.EapResponse.RESPONSE_FLAG_EAP_AKA_SERVER_AUTHENTICATED
541                                 });
542             } catch (EapSimAkaInvalidAttributeException ex) {
543                 LOG.wtf(mTAG, "Error creating AtRes value", ex);
544                 return new EapError(ex);
545             }
546         }
547 
548         @VisibleForTesting
549         class RandChallengeResult {
550             public final byte[] res;
551             public final byte[] ik;
552             public final byte[] ck;
553             public final byte[] auts;
554 
RandChallengeResult(byte[] res, byte[] ik, byte[] ck)555             RandChallengeResult(byte[] res, byte[] ik, byte[] ck)
556                     throws EapSimAkaInvalidLengthException {
557                 if (!AtRes.isValidResLen(res.length)) {
558                     throw new EapSimAkaInvalidLengthException("Invalid RES length");
559                 } else if (ik.length != IK_LEN_BYTES) {
560                     throw new EapSimAkaInvalidLengthException("Invalid IK length");
561                 } else if (ck.length != CK_LEN_BYTES) {
562                     throw new EapSimAkaInvalidLengthException("Invalid CK length");
563                 }
564 
565                 this.res = res;
566                 this.ik = ik;
567                 this.ck = ck;
568                 this.auts = null;
569             }
570 
RandChallengeResult(byte[] auts)571             RandChallengeResult(byte[] auts) throws EapSimAkaInvalidLengthException {
572                 if (auts.length != AtAuts.AUTS_LENGTH) {
573                     throw new EapSimAkaInvalidLengthException("Invalid AUTS length");
574                 }
575 
576                 this.res = null;
577                 this.ik = null;
578                 this.ck = null;
579                 this.auts = auts;
580             }
581 
isSuccessfulResult()582             private boolean isSuccessfulResult() {
583                 return res != null && ik != null && ck != null;
584             }
585         }
586 
isValidChallengeAttributes(EapAkaTypeData eapAkaTypeData)587         private boolean isValidChallengeAttributes(EapAkaTypeData eapAkaTypeData) {
588             Set<Integer> attrs = eapAkaTypeData.attributeMap.keySet();
589 
590             // must contain: AT_RAND, AT_AUTN, AT_MAC
591             return attrs.contains(EAP_AT_RAND)
592                     && attrs.contains(EAP_AT_AUTN)
593                     && attrs.contains(EAP_AT_MAC);
594         }
595 
getRandChallengeResult(EapAkaTypeData eapAkaTypeData)596         private RandChallengeResult getRandChallengeResult(EapAkaTypeData eapAkaTypeData)
597                 throws EapSimAkaAuthenticationFailureException, EapSimAkaInvalidLengthException {
598             AtRandAka atRandAka = (AtRandAka) eapAkaTypeData.attributeMap.get(EAP_AT_RAND);
599             AtAutn atAutn = (AtAutn) eapAkaTypeData.attributeMap.get(EAP_AT_AUTN);
600 
601             // pre-Base64 formatting needs to be: [Length][RAND][Length][AUTN]
602             int randLen = atRandAka.rand.length;
603             int autnLen = atAutn.autn.length;
604             ByteBuffer formattedChallenge = ByteBuffer.allocate(1 + randLen + 1 + autnLen);
605             formattedChallenge.put((byte) randLen);
606             formattedChallenge.put(atRandAka.rand);
607             formattedChallenge.put((byte) autnLen);
608             formattedChallenge.put(atAutn.autn);
609 
610             byte[] challengeResponse =
611                     processUiccAuthentication(
612                             mTAG,
613                             TelephonyManager.AUTHTYPE_EAP_AKA,
614                             formattedChallenge.array());
615             ByteBuffer buffer = ByteBuffer.wrap(challengeResponse);
616             byte tag = buffer.get();
617 
618             switch (tag) {
619                 case RAND_SUCCESS:
620                     // response format: [tag][RES length][RES][CK length][CK][IK length][IK]
621                     // (TS 131 102#7.1.2.1)
622                     break;
623                 case RAND_SYNCHRONIZATION:
624                     // response format: [tag][AUTS length][AUTS]
625                     // (TS 131 102#7.1.2.1)
626                     byte[] auts = new byte[Byte.toUnsignedInt(buffer.get())];
627                     buffer.get(auts);
628 
629                     LOG.i(mTAG, "Synchronization Failure");
630                     LOG.d(
631                             mTAG,
632                             "RAND=" + LOG.pii(atRandAka.rand)
633                                     + " AUTN=" + LOG.pii(atAutn.autn)
634                                     + " AUTS=" + LOG.pii(auts));
635 
636                     return new RandChallengeResult(auts);
637                 default:
638                     throw new EapAkaInvalidAuthenticationResponse(
639                             "Invalid tag for UICC response: " + String.format("%02X", tag));
640             }
641 
642             byte[] res = new byte[Byte.toUnsignedInt(buffer.get())];
643             buffer.get(res);
644 
645             byte[] ck = new byte[Byte.toUnsignedInt(buffer.get())];
646             buffer.get(ck);
647 
648             byte[] ik = new byte[Byte.toUnsignedInt(buffer.get())];
649             buffer.get(ik);
650 
651             LOG.d(mTAG, "RAND=" + LOG.pii(atRandAka.rand));
652             LOG.d(mTAG, "AUTN=" + LOG.pii(atAutn.autn));
653             LOG.d(mTAG, "RES=" + LOG.pii(res));
654             LOG.d(mTAG, "IK=" + LOG.pii(ik));
655             LOG.d(mTAG, "CK=" + LOG.pii(ck));
656 
657             return new RandChallengeResult(res, ik, ck);
658         }
659 
buildAuthenticationRejectMessage(int eapIdentifier)660         protected EapResult buildAuthenticationRejectMessage(int eapIdentifier) {
661             mIsExpectingEapFailure = true;
662             return buildResponseMessage(
663                     getEapMethod(),
664                     EAP_AKA_AUTHENTICATION_REJECT,
665                     eapIdentifier,
666                     new ArrayList<>());
667         }
668 
669         @Nullable
generateAndPersistEapAkaKeys( RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData)670         protected EapResult generateAndPersistEapAkaKeys(
671                 RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData) {
672             try {
673                 MessageDigest sha1 = MessageDigest.getInstance(MASTER_KEY_GENERATION_ALG);
674                 byte[] mkInputData = getMkInputData(result);
675                 generateAndPersistKeys(mTAG, sha1, new Fips186_2Prf(), mkInputData);
676                 return null;
677             } catch (NoSuchAlgorithmException | BufferUnderflowException ex) {
678                 LOG.e(mTAG, "Error while creating keys", ex);
679                 return buildClientErrorResponse(
680                         eapIdentifier, EAP_TYPE_AKA, AtClientErrorCode.UNABLE_TO_PROCESS);
681             }
682         }
683 
getMkInputData(RandChallengeResult result)684         private byte[] getMkInputData(RandChallengeResult result) {
685             int numInputBytes = mIdentity.length + result.ik.length + result.ck.length;
686             ByteBuffer buffer = ByteBuffer.allocate(numInputBytes);
687             buffer.put(mIdentity);
688             buffer.put(result.ik);
689             buffer.put(result.ck);
690             return buffer.array();
691         }
692     }
693 
694     protected class ReauthState extends EapMethodState {
695         private final String mTAG = ReauthState.class.getSimpleName();
696 
697         @VisibleForTesting boolean mHadSuccessfulReauth = false;
698         @VisibleForTesting protected final byte[] mReauthIdentity;
699         private final ReauthInfo mReauthInfo;
700         private byte[] mNextReauthIdentity;
701         private int mReauthCounter;
702 
ReauthState()703         ReauthState() {
704             this.mReauthIdentity = getReauthIdentity();
705             this.mReauthInfo = getAvailableReauthInfo(getReauthIdentity(), mEapIdentity);
706         }
707 
ReauthState(byte[] identity, ReauthInfo reauthInfo)708         ReauthState(byte[] identity, ReauthInfo reauthInfo) {
709             this.mReauthIdentity = identity;
710             this.mReauthInfo = reauthInfo;
711         }
712 
process(EapMessage message)713         public EapResult process(EapMessage message) {
714             if (message.eapCode == EAP_CODE_SUCCESS) {
715                 if (!mHadSuccessfulReauth) {
716                     LOG.e(mTAG, "Received unexpected EAP-Success");
717                     return new EapError(
718                             new EapInvalidRequestException(
719                                     "Received an EAP-Success in the ReauthState"));
720                 }
721                 transitionTo(new FinalState());
722                 return processEapSuccess(mNextReauthIdentity, mReauthCounter);
723             }
724 
725             EapResult result = handleEapSuccessFailureNotification(mTAG, message);
726             if (result != null) {
727                 return result;
728             }
729 
730             DecodeResult<? extends EapAkaTypeData> decodeResult =
731                     decode(message.eapData.eapTypeData);
732             if (!decodeResult.isSuccessfulDecode()) {
733                 return buildClientErrorResponse(
734                         message.eapIdentifier, getEapMethod(), decodeResult.atClientErrorCode);
735             }
736 
737             EapAkaTypeData eapAkaTypeData = decodeResult.eapTypeData;
738             switch (eapAkaTypeData.eapSubtype) {
739                 case EAP_AKA_REAUTHENTICATION:
740                     break;
741 
742                 case EAP_AKA_CHALLENGE:
743                     return transitionAndProcess(buildChallengeState(mEapIdentity), message);
744 
745                 case EAP_AKA_NOTIFICATION:
746                     return handleEapSimAkaNotification(
747                             mTAG,
748                             false, // isPreChallengeState
749                             true, // isReauthState
750                             mHadSuccessfulReauth, // hadSuccessfulAuthentication
751                             message.eapIdentifier,
752                             mReauthCounter,
753                             eapAkaTypeData);
754                 default:
755                     return buildClientErrorResponse(
756                             message.eapIdentifier,
757                             getEapMethod(),
758                             AtClientErrorCode.UNABLE_TO_PROCESS);
759             }
760 
761             if (!isValidReauthAttributes(eapAkaTypeData)) {
762                 LOG.e(mTAG, "Invalid attributes: " + eapAkaTypeData.attributeMap.keySet());
763                 return buildClientErrorResponse(
764                         message.eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
765             }
766 
767             return handleReauthentication(message, eapAkaTypeData);
768         }
769 
handleReauthentication( EapMessage message, EapAkaTypeData eapAkaTypeData)770         protected EapResult handleReauthentication(
771                 EapMessage message, EapAkaTypeData eapAkaTypeData) {
772             if (mReauthIdentity == null || mReauthInfo == null) {
773                 LOG.e(mTAG, "No reauth Info for this re-authentication request");
774                 return buildClientErrorResponse(
775                         message.eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
776             }
777             System.arraycopy(mReauthInfo.getMk(), 0, mMk, 0, MASTER_KEY_LENGTH);
778             System.arraycopy(mReauthInfo.getKeyEncr(), 0, mKEncr, 0, KEY_LEN);
779             System.arraycopy(mReauthInfo.getKeyAut(), 0, mKAut, 0, KEY_LEN);
780 
781             try {
782                 if (!isValidMac(mTAG, message, eapAkaTypeData, new byte[0])) {
783                     return buildClientErrorResponse(
784                             message.eapIdentifier,
785                             getEapMethod(),
786                             AtClientErrorCode.UNABLE_TO_PROCESS);
787                 }
788             } catch (GeneralSecurityException
789                     | EapSilentException
790                     | EapSimAkaInvalidAttributeException ex) {
791                 // if the MAC can't be generated, we can't continue
792                 LOG.e(mTAG, "Error computing MAC for EapMessage", ex);
793                 return new EapError(ex);
794             }
795 
796             LinkedHashMap<Integer, EapSimAkaAttribute> securedAttributes =
797                     retrieveSecuredAttributes(mTAG, eapAkaTypeData);
798 
799             if (!isValidReauthSecuredAttributes(securedAttributes)) {
800                 LOG.e(mTAG, "Invalid attributes: " + securedAttributes.keySet());
801                 return buildClientErrorResponse(
802                         message.eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
803             }
804 
805             EapSimAkaAttribute atNextReauthId = securedAttributes.get(EAP_AT_NEXT_REAUTH_ID);
806             if (atNextReauthId != null) {
807                 mNextReauthIdentity =
808                         ((EapSimAkaAttribute.AtNextReauthId) atNextReauthId).reauthId.clone();
809             } else {
810                 mNextReauthIdentity = null;
811             }
812 
813             int counter =
814                     ((EapSimAkaAttribute.AtCounter) securedAttributes.get(EAP_AT_COUNTER)).counter;
815             byte[] nonceS =
816                     ((EapSimAkaAttribute.AtNonceS) securedAttributes.get(EAP_AT_NONCE_S)).nonceS;
817 
818             if (counter <= mReauthInfo.getReauthCount()) {
819                 LOG.e(mTAG, "COUNTER TOO SMALL");
820                 try {
821                     EapSimAkaAttribute.AtIv atIv = new EapSimAkaAttribute.AtIv(mSecureRandom);
822                     List<EapSimAkaAttribute> attributeList =
823                             buildReauthResponse(counter, true /* isCounterSmall */, mKEncr, atIv);
824 
825                     return buildResponseMessageWithMac(
826                             message.eapIdentifier,
827                             EAP_AKA_REAUTHENTICATION,
828                             nonceS,
829                             attributeList,
830                             null);
831                 } catch (EapSimAkaInvalidAttributeException ex) {
832                     LOG.wtf(mTAG, "Error build EAP response for reauth", ex);
833                     return new EapError(ex);
834                 }
835             } else {
836                 mReauthCounter = counter;
837             }
838 
839             EapResult eapResult =
840                     generateAndPersistEapAkaKeys(message.eapIdentifier, counter, nonceS);
841             if (eapResult != null) {
842                 return eapResult;
843             }
844 
845             // before sending a response, check for bidding-down attacks (RFC 5448#4)
846             if (mSupportsEapAkaPrime) {
847                 AtBidding atBidding = (AtBidding) eapAkaTypeData.attributeMap.get(EAP_AT_BIDDING);
848                 if (atBidding != null && atBidding.doesServerSupportEapAkaPrime) {
849                     LOG.w(
850                             mTAG,
851                             "Potential bidding down attack. AT_BIDDING attr included and EAP-AKA'"
852                                     + " is supported");
853                     return buildAuthenticationRejectMessage(message.eapIdentifier);
854                 }
855             }
856 
857             // server has been authenticated, so we can send a response
858             try {
859                 mHadSuccessfulReauth = true;
860                 EapSimAkaAttribute.AtIv atIv = new EapSimAkaAttribute.AtIv(mSecureRandom);
861                 List<EapSimAkaAttribute> attributeList =
862                         buildReauthResponse(counter, false /* isCounterSmall */, mKEncr, atIv);
863 
864                 return buildResponseMessageWithMac(
865                         message.eapIdentifier,
866                         EAP_AKA_REAUTHENTICATION,
867                         nonceS,
868                         attributeList,
869                         (eapAkaTypeData instanceof EapAkaPrimeTypeData)
870                                 ? null /* no flags set */
871                                 : new int[] {
872                                     EapResult.EapResponse.RESPONSE_FLAG_EAP_AKA_SERVER_AUTHENTICATED
873                                 });
874             } catch (EapSimAkaInvalidAttributeException ex) {
875                 LOG.wtf(mTAG, "Error build EAP response for reauth", ex);
876                 return new EapError(ex);
877             }
878         }
879 
isValidReauthAttributes(EapAkaTypeData eapAkaTypeData)880         private boolean isValidReauthAttributes(EapAkaTypeData eapAkaTypeData) {
881             Set<Integer> attrs = eapAkaTypeData.attributeMap.keySet();
882 
883             // must contain: AT_ENCR_DATA, AT_IV, AT_MAC
884             return attrs.contains(EAP_AT_IV)
885                     && attrs.contains(EAP_AT_ENCR_DATA)
886                     && attrs.contains(EAP_AT_MAC);
887         }
888 
isValidReauthSecuredAttributes( LinkedHashMap<Integer, EapSimAkaAttribute> secureAttributes)889         private boolean isValidReauthSecuredAttributes(
890                 LinkedHashMap<Integer, EapSimAkaAttribute> secureAttributes) {
891             Set<Integer> attrs = secureAttributes.keySet();
892 
893             // must contain: EAP_AT_COUNTER, EAP_AT_NONCE_S
894             return attrs.contains(EAP_AT_COUNTER) && attrs.contains(EAP_AT_NONCE_S);
895         }
896 
buildAuthenticationRejectMessage(int eapIdentifier)897         protected EapResult buildAuthenticationRejectMessage(int eapIdentifier) {
898             mIsExpectingEapFailure = true;
899             return buildResponseMessage(
900                     getEapMethod(),
901                     EAP_AKA_AUTHENTICATION_REJECT,
902                     eapIdentifier,
903                     new ArrayList<>());
904         }
905 
906         @Nullable
generateAndPersistEapAkaKeys( int eapIdentifier, int counter, byte[] nonceS)907         protected EapResult generateAndPersistEapAkaKeys(
908                 int eapIdentifier, int counter, byte[] nonceS) {
909             try {
910                 MessageDigest sha1 = MessageDigest.getInstance(MASTER_KEY_GENERATION_ALG);
911                 generateAndPersistReauthKeys(
912                         mTAG,
913                         sha1,
914                         new Fips186_2Prf(),
915                         mReauthIdentity,
916                         counter,
917                         nonceS,
918                         mReauthInfo.getMk());
919                 return null;
920             } catch (NoSuchAlgorithmException | BufferUnderflowException ex) {
921                 LOG.e(mTAG, "Error while creating keys", ex);
922                 return buildClientErrorResponse(
923                         eapIdentifier, EAP_TYPE_AKA, AtClientErrorCode.UNABLE_TO_PROCESS);
924             }
925         }
926     }
927 
processEapSuccess(byte[] nextReauthId, int reauthCounter)928     private EapSuccess processEapSuccess(byte[] nextReauthId, int reauthCounter) {
929         EapSuccess eapSuccess;
930         if (nextReauthId != null) {
931             mEapSimAkaIdentityTracker.registerReauthCredentials(
932                     new String(nextReauthId, StandardCharsets.UTF_8),
933                     new String(mEapIdentity, StandardCharsets.UTF_8),
934                     reauthCounter,
935                     mMk,
936                     mKEncr,
937                     mKAut);
938             eapSuccess =
939                     new EapSuccess(
940                             mMsk,
941                             mEmsk,
942                             new EapAkaInfo.Builder().setReauthId(nextReauthId).build());
943         } else {
944             eapSuccess = new EapSuccess(mMsk, mEmsk);
945         }
946         return eapSuccess;
947     }
948 
getEapSimAkaTypeData(AtClientErrorCode clientErrorCode)949     EapAkaTypeData getEapSimAkaTypeData(AtClientErrorCode clientErrorCode) {
950         return new EapAkaTypeData(EAP_AKA_CLIENT_ERROR, Arrays.asList(clientErrorCode));
951     }
952 
getEapSimAkaTypeData(int eapSubtype, List<EapSimAkaAttribute> attributes)953     EapAkaTypeData getEapSimAkaTypeData(int eapSubtype, List<EapSimAkaAttribute> attributes) {
954         return new EapAkaTypeData(eapSubtype, attributes);
955     }
956 }
957