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