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 com.android.internal.net.eap.EapAuthenticator.LOG; 20 import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_REQUEST; 21 import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_RESPONSE; 22 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtEncrData.CIPHER_BLOCK_LENGTH; 23 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_COUNTER; 24 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ENCR_DATA; 25 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_IV; 26 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_MAC; 27 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_NEXT_REAUTH_ID; 28 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_NOTIFICATION; 29 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_PADDING; 30 31 import android.net.eap.EapSessionConfig.EapUiccConfig; 32 import android.telephony.TelephonyManager; 33 import android.util.Base64; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.internal.net.eap.EapResult; 37 import com.android.internal.net.eap.EapResult.EapError; 38 import com.android.internal.net.eap.EapResult.EapResponse; 39 import com.android.internal.net.eap.crypto.Fips186_2Prf; 40 import com.android.internal.net.eap.exceptions.EapInvalidRequestException; 41 import com.android.internal.net.eap.exceptions.EapSilentException; 42 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaAuthenticationFailureException; 43 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; 44 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaUnsupportedAttributeException; 45 import com.android.internal.net.eap.message.EapData; 46 import com.android.internal.net.eap.message.EapMessage; 47 import com.android.internal.net.eap.message.simaka.EapAkaAttributeFactory; 48 import com.android.internal.net.eap.message.simaka.EapAkaTypeData; 49 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; 50 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; 51 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtEncrData; 52 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtIv; 53 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; 54 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNextReauthId; 55 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification; 56 import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData; 57 import com.android.internal.net.utils.Log; 58 59 import java.nio.ByteBuffer; 60 import java.security.GeneralSecurityException; 61 import java.security.MessageDigest; 62 import java.security.SecureRandom; 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.LinkedHashMap; 66 import java.util.List; 67 import java.util.Set; 68 69 import javax.crypto.Mac; 70 import javax.crypto.spec.SecretKeySpec; 71 72 /** 73 * EapSimAkaMethodStateMachine represents an abstract state machine for managing EAP-SIM and EAP-AKA 74 * sessions. 75 * 76 * @see <a href="https://tools.ietf.org/html/rfc4186">RFC 4186, Extensible Authentication 77 * Protocol for Subscriber Identity Modules (EAP-SIM)</a> 78 * @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication 79 * Protocol for Authentication and Key Agreement (EAP-AKA)</a> 80 */ 81 public abstract class EapSimAkaMethodStateMachine extends EapMethodStateMachine { 82 public static final String MASTER_KEY_GENERATION_ALG = "SHA-1"; 83 public static final String MAC_ALGORITHM_STRING = "HmacSHA1"; 84 85 // Master Key(SHA 1) length is 20 bytes(160bits) (RFC 3174 #1) 86 public static final int MASTER_KEY_LENGTH = 20; 87 // K_encr and K_aut lengths are 16 bytes (RFC 4186#7, RFC 4187#7) 88 public static final int KEY_LEN = 16; 89 90 // Session Key lengths are 64 bytes (RFC 4186#7, RFC 4187#7) 91 public static final int SESSION_KEY_LENGTH = 64; 92 93 // COUNTER SIZE 2bytes(16bit)(RFC 4187 #10.16) 94 private static final int COUNTER_SIZE = 2; 95 96 public final byte[] mMk = new byte[getMkLength()]; 97 public final byte[] mKEncr = new byte[getKEncrLength()]; 98 public final byte[] mKAut = new byte[getKAutLength()]; 99 public final byte[] mMsk = new byte[getMskLength()]; 100 public final byte[] mEmsk = new byte[getEmskLength()]; 101 @VisibleForTesting boolean mHasReceivedSimAkaNotification = false; 102 103 final TelephonyManager mTelephonyManager; 104 final byte[] mEapIdentity; 105 final EapUiccConfig mEapUiccConfig; 106 107 @VisibleForTesting Mac mMacAlgorithm; 108 @VisibleForTesting SecureRandom mSecureRandom; 109 EapSimAkaMethodStateMachine( TelephonyManager telephonyManager, byte[] eapIdentity, EapUiccConfig eapUiccConfig)110 EapSimAkaMethodStateMachine( 111 TelephonyManager telephonyManager, byte[] eapIdentity, EapUiccConfig eapUiccConfig) { 112 if (telephonyManager == null) { 113 throw new IllegalArgumentException("TelephonyManager must be non-null"); 114 } else if (eapIdentity == null) { 115 throw new IllegalArgumentException("EapIdentity must be non-null"); 116 } else if (eapUiccConfig == null) { 117 throw new IllegalArgumentException("EapUiccConfig must be non-null"); 118 } 119 this.mTelephonyManager = telephonyManager; 120 this.mEapIdentity = eapIdentity; 121 this.mEapUiccConfig = eapUiccConfig; 122 123 LOG.d( 124 this.getClass().getSimpleName(), 125 mEapUiccConfig.getClass().getSimpleName() + ":" 126 + " subId=" + mEapUiccConfig.getSubId() 127 + " apptype=" + mEapUiccConfig.getAppType()); 128 } 129 getMkLength()130 protected int getMkLength() { 131 return MASTER_KEY_LENGTH; 132 } 133 getKEncrLength()134 protected int getKEncrLength() { 135 return KEY_LEN; 136 } 137 getKAutLength()138 protected int getKAutLength() { 139 return KEY_LEN; 140 } 141 getMskLength()142 protected int getMskLength() { 143 return SESSION_KEY_LENGTH; 144 } 145 getEmskLength()146 protected int getEmskLength() { 147 return SESSION_KEY_LENGTH; 148 } 149 150 @Override handleEapNotification(String tag, EapMessage message)151 EapResult handleEapNotification(String tag, EapMessage message) { 152 return EapStateMachine.handleNotification(tag, message); 153 } 154 getMacAlgorithm()155 protected String getMacAlgorithm() { 156 return MAC_ALGORITHM_STRING; 157 } 158 159 @VisibleForTesting buildClientErrorResponse( int eapIdentifier, int eapMethodType, AtClientErrorCode clientErrorCode)160 EapResult buildClientErrorResponse( 161 int eapIdentifier, 162 int eapMethodType, 163 AtClientErrorCode clientErrorCode) { 164 mIsExpectingEapFailure = true; 165 EapSimAkaTypeData eapSimAkaTypeData = getEapSimAkaTypeData(clientErrorCode); 166 byte[] encodedTypeData = eapSimAkaTypeData.encode(); 167 168 EapData eapData = new EapData(eapMethodType, encodedTypeData); 169 try { 170 EapMessage response = new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, eapData); 171 return EapResult.EapResponse.getEapResponse(response); 172 } catch (EapSilentException ex) { 173 return new EapResult.EapError(ex); 174 } 175 } 176 177 @VisibleForTesting buildResponseMessage( int eapType, int eapSubtype, int identifier, List<EapSimAkaAttribute> attributes)178 EapResult buildResponseMessage( 179 int eapType, 180 int eapSubtype, 181 int identifier, 182 List<EapSimAkaAttribute> attributes) { 183 EapSimAkaTypeData eapSimTypeData = getEapSimAkaTypeData(eapSubtype, attributes); 184 EapData eapData = new EapData(eapType, eapSimTypeData.encode()); 185 186 try { 187 EapMessage eapMessage = new EapMessage(EAP_CODE_RESPONSE, identifier, eapData); 188 return EapResult.EapResponse.getEapResponse(eapMessage); 189 } catch (EapSilentException ex) { 190 return new EapResult.EapError(ex); 191 } 192 } 193 194 @VisibleForTesting generateAndPersistKeys( String tag, MessageDigest sha1, Fips186_2Prf prf, byte[] mkInput)195 protected void generateAndPersistKeys( 196 String tag, MessageDigest sha1, Fips186_2Prf prf, byte[] mkInput) { 197 byte[] mk = sha1.digest(mkInput); 198 System.arraycopy(mk, 0, mMk, 0, MASTER_KEY_LENGTH); 199 200 // run mk through FIPS 186-2 201 int outputBytes = mKEncr.length + mKAut.length + mMsk.length + mEmsk.length; 202 byte[] prfResult = prf.getRandom(mk, outputBytes); 203 204 ByteBuffer prfResultBuffer = ByteBuffer.wrap(prfResult); 205 prfResultBuffer.get(mKEncr); 206 prfResultBuffer.get(mKAut); 207 prfResultBuffer.get(mMsk); 208 prfResultBuffer.get(mEmsk); 209 210 // Log as hash unless PII debug mode enabled 211 LOG.d(tag, "MK input=" + LOG.pii(mkInput)); 212 LOG.d(tag, "MK=" + LOG.pii(mk)); 213 LOG.d(tag, "K_encr=" + LOG.pii(mKEncr)); 214 LOG.d(tag, "K_aut=" + LOG.pii(mKAut)); 215 LOG.d(tag, "MSK=" + LOG.pii(mMsk)); 216 LOG.d(tag, "EMSK=" + LOG.pii(mEmsk)); 217 } 218 219 @VisibleForTesting generateAndPersistReauthKeys( String tag, MessageDigest sha1, Fips186_2Prf prf, byte[] reauthId, int count, byte[] nonceS, byte[] mk)220 protected void generateAndPersistReauthKeys( 221 String tag, 222 MessageDigest sha1, 223 Fips186_2Prf prf, 224 byte[] reauthId, 225 int count, 226 byte[] nonceS, 227 byte[] mk) { 228 int numInputBytes = reauthId.length + COUNTER_SIZE + nonceS.length + mk.length; 229 230 // XKEY' generated per reauth key generation procedure RFC 4187: 231 // SHA1(Identity|counter|NONCE_S| MK) 232 ByteBuffer buffer = ByteBuffer.allocate(numInputBytes); 233 buffer.put(reauthId); 234 buffer.putShort((short) count); 235 buffer.put(nonceS); 236 buffer.put(mk); 237 byte[] xKeyPrimeInput = buffer.array(); 238 byte[] xKeyPrime = sha1.digest(xKeyPrimeInput); 239 240 // run mk through FIPS 186-2 241 int outputBytes = mMsk.length + mEmsk.length; 242 byte[] prfResult = prf.getRandom(xKeyPrime, outputBytes); 243 244 ByteBuffer prfResultBuffer = ByteBuffer.wrap(prfResult); 245 prfResultBuffer.get(mMsk); 246 prfResultBuffer.get(mEmsk); 247 248 // Log as hash unless PII debug mode enabled 249 LOG.d(tag, "MK=" + LOG.pii(mk)); 250 LOG.d(tag, "XKEY' INPUT=" + LOG.pii(xKeyPrimeInput)); 251 LOG.d(tag, "XKEY' =" + LOG.pii(xKeyPrime)); 252 LOG.d(tag, "K_encr=" + LOG.pii(mKEncr)); 253 LOG.d(tag, "K_aut=" + LOG.pii(mKAut)); 254 LOG.d(tag, "MSK=" + LOG.pii(mMsk)); 255 LOG.d(tag, "EMSK=" + LOG.pii(mEmsk)); 256 } 257 258 @VisibleForTesting processUiccAuthentication(String tag, int authType, byte[] formattedChallenge)259 byte[] processUiccAuthentication(String tag, int authType, byte[] formattedChallenge) throws 260 EapSimAkaAuthenticationFailureException { 261 String base64Challenge = Base64.encodeToString(formattedChallenge, Base64.NO_WRAP); 262 String base64Response = 263 mTelephonyManager.getIccAuthentication( 264 mEapUiccConfig.getAppType(), authType, base64Challenge); 265 266 if (base64Response == null) { 267 String msg = "UICC authentication failed. Input: " + LOG.pii(formattedChallenge); 268 LOG.e(tag, msg); 269 throw new EapSimAkaAuthenticationFailureException(msg); 270 } 271 272 return Base64.decode(base64Response, Base64.DEFAULT); 273 } 274 275 @VisibleForTesting isValidMac(String tag, EapMessage message, EapSimAkaTypeData typeData, byte[] extraData)276 boolean isValidMac(String tag, EapMessage message, EapSimAkaTypeData typeData, byte[] extraData) 277 throws GeneralSecurityException, EapSimAkaInvalidAttributeException, 278 EapSilentException { 279 mMacAlgorithm = Mac.getInstance(getMacAlgorithm()); 280 mMacAlgorithm.init(new SecretKeySpec(mKAut, getMacAlgorithm())); 281 282 LOG.d(tag, "Computing MAC (raw msg): " + LOG.pii(message.encode())); 283 284 byte[] mac = getMac(message.eapCode, message.eapIdentifier, typeData, extraData); 285 286 // attributes are 'valid', so must have AtMac 287 AtMac atMac = (AtMac) typeData.attributeMap.get(EAP_AT_MAC); 288 289 boolean isValidMac = Arrays.equals(mac, atMac.mac); 290 if (!isValidMac) { 291 // MAC in message != calculated mac 292 LOG.e( 293 tag, 294 "Received message with invalid Mac." 295 + " received=" + Log.byteArrayToHexString(atMac.mac) 296 + ", computed=" + Log.byteArrayToHexString(mac)); 297 } 298 299 return isValidMac; 300 } 301 302 @VisibleForTesting retrieveSecuredAttributes( String tag, EapSimAkaTypeData typeData)303 LinkedHashMap<Integer, EapSimAkaAttribute> retrieveSecuredAttributes( 304 String tag, EapSimAkaTypeData typeData) { 305 AtEncrData atEncrData = (AtEncrData) typeData.attributeMap.get(EAP_AT_ENCR_DATA); 306 307 if (atEncrData == null) { 308 LOG.d(tag, "AT_ENCR_DATA is not included."); 309 return null; 310 } else { 311 AtIv atIv = (AtIv) typeData.attributeMap.get(EAP_AT_IV); 312 if (atIv == null) { 313 LOG.d(tag, "AT_IV is not included. can't decrypt ENCR DATA"); 314 return null; 315 } 316 317 byte[] decryptedData; 318 try { 319 decryptedData = atEncrData.getDecryptedData(mKEncr, atIv.iv); 320 } catch (EapSimAkaInvalidAttributeException e) { 321 LOG.d(tag, "Decrypt Fail, can't decrypt ENCR DATA"); 322 return null; 323 } 324 325 LinkedHashMap<Integer, EapSimAkaAttribute> securedAttributes; 326 try { 327 securedAttributes = getSecureAttributes(tag, decryptedData); 328 } catch (EapSimAkaInvalidAttributeException e) { 329 LOG.d(tag, "Decode Fail, can't decode decrypted ENCR DATA."); 330 return null; 331 } 332 return securedAttributes; 333 } 334 } 335 336 @VisibleForTesting retrieveNextReauthId(String tag, EapAkaTypeData typeData)337 byte[] retrieveNextReauthId(String tag, EapAkaTypeData typeData) { 338 LinkedHashMap<Integer, EapSimAkaAttribute> securedAttributes = 339 retrieveSecuredAttributes(tag, typeData); 340 if (securedAttributes == null) { 341 return null; 342 } else { 343 AtNextReauthId atNextReauthId = 344 (AtNextReauthId) securedAttributes.get(EAP_AT_NEXT_REAUTH_ID); 345 if (atNextReauthId != null && atNextReauthId.reauthId != null) { 346 return atNextReauthId.reauthId.clone(); 347 } else { 348 return null; 349 } 350 } 351 } 352 353 @VisibleForTesting getSecureAttributes(String tag, byte[] decryptedData)354 static LinkedHashMap<Integer, EapSimAkaAttribute> getSecureAttributes(String tag, 355 byte[] decryptedData) 356 throws EapSimAkaInvalidAttributeException { 357 ByteBuffer secureDataByteBuffer = ByteBuffer.wrap(decryptedData); 358 LinkedHashMap<Integer, EapSimAkaAttribute> attributeMap = new LinkedHashMap<>(); 359 EapAkaAttributeFactory attributeFactory = EapAkaAttributeFactory.getInstance(); 360 while (secureDataByteBuffer.hasRemaining()) { 361 EapSimAkaAttribute attribute; 362 try { 363 attribute = attributeFactory.getAttribute(secureDataByteBuffer); 364 } catch (EapSimAkaUnsupportedAttributeException e) { 365 LOG.e(tag, "Unrecognized, non-skippable attribute encountered", e); 366 throw new EapSimAkaInvalidAttributeException("Decode fail"); 367 } 368 if (attributeMap.containsKey(attribute.attributeType)) { 369 // Duplicate attributes are not allowed (RFC 4186#6.3.1, RFC 4187#6.3.1) 370 LOG.e(tag, "Duplicate attribute in parsed EAP-Message"); 371 throw new EapSimAkaInvalidAttributeException("Duplicated attributes"); 372 } 373 374 if (attribute instanceof EapSimAkaAttribute.EapSimAkaUnsupportedAttribute) { 375 LOG.d(tag, "Unsupported EAP attribute during decoding: " + attribute.attributeType); 376 } 377 attributeMap.put(attribute.attributeType, attribute); 378 } 379 return attributeMap; 380 } 381 382 @VisibleForTesting buildReauthResponse( int counter, boolean isCounterSmall, byte[] kEncr, AtIv atIv)383 static List<EapSimAkaAttribute> buildReauthResponse( 384 int counter, boolean isCounterSmall, byte[] kEncr, AtIv atIv) 385 throws EapSimAkaInvalidAttributeException { 386 List<EapSimAkaAttribute> attrList = new ArrayList<>(); 387 ByteBuffer buffer; 388 EapSimAkaAttribute atCounter = new EapSimAkaAttribute.AtCounter(counter); 389 if (isCounterSmall) { 390 EapSimAkaAttribute atCounterSmall = new EapSimAkaAttribute.AtCounterTooSmall(); 391 int paddingSize = 392 getPaddingSize( 393 CIPHER_BLOCK_LENGTH, 394 atCounter.lengthInBytes + atCounterSmall.lengthInBytes); 395 EapSimAkaAttribute atPadding = new EapSimAkaAttribute.AtPadding(paddingSize); 396 buffer = 397 ByteBuffer.allocate( 398 atCounter.lengthInBytes + atCounterSmall.lengthInBytes + paddingSize); 399 atCounterSmall.encode(buffer); 400 atCounter.encode(buffer); 401 atPadding.encode(buffer); 402 } else { 403 int paddingSize = getPaddingSize(CIPHER_BLOCK_LENGTH, atCounter.lengthInBytes); 404 EapSimAkaAttribute atPadding = new EapSimAkaAttribute.AtPadding(paddingSize); 405 buffer = ByteBuffer.allocate(atCounter.lengthInBytes + paddingSize); 406 atCounter.encode(buffer); 407 atPadding.encode(buffer); 408 } 409 EapSimAkaAttribute atEncrData = 410 new EapSimAkaAttribute.AtEncrData(buffer.array(), kEncr, atIv.iv); 411 attrList.add(atIv); 412 attrList.add(atEncrData); 413 return attrList; 414 } 415 416 @VisibleForTesting getPaddingSize(int blockSize, int dataLength)417 static int getPaddingSize(int blockSize, int dataLength) { 418 int remain = dataLength % blockSize; 419 if (remain == 0) return 0; 420 else return blockSize - remain; 421 } 422 423 @VisibleForTesting getMac(int eapCode, int eapIdentifier, EapSimAkaTypeData typeData, byte[] extraData)424 byte[] getMac(int eapCode, int eapIdentifier, EapSimAkaTypeData typeData, byte[] extraData) 425 throws EapSimAkaInvalidAttributeException, EapSilentException { 426 if (mMacAlgorithm == null) { 427 throw new IllegalStateException( 428 "Can't calculate MAC before mMacAlgorithm is set in ChallengeState"); 429 } 430 431 // cache original Mac so it can be restored after calculating the Mac 432 AtMac originalMac = (AtMac) typeData.attributeMap.get(EAP_AT_MAC); 433 typeData.attributeMap.put(EAP_AT_MAC, originalMac.getAtMacWithMacCleared()); 434 435 byte[] typeDataWithEmptyMac = typeData.encode(); 436 EapData eapData = new EapData(getEapMethod(), typeDataWithEmptyMac); 437 EapMessage messageForMac = new EapMessage(eapCode, eapIdentifier, eapData); 438 439 LOG.d(this.getClass().getSimpleName(), 440 "Computing MAC (mac cleared): " + LOG.pii(messageForMac.encode())); 441 442 ByteBuffer buffer = ByteBuffer.allocate(messageForMac.eapLength + extraData.length); 443 buffer.put(messageForMac.encode()); 444 buffer.put(extraData); 445 byte[] mac = mMacAlgorithm.doFinal(buffer.array()); 446 447 typeData.attributeMap.put(EAP_AT_MAC, originalMac); 448 449 // need HMAC-SHA1-128 - first 16 bytes of SHA1 (RFC 4186#10.14, RFC 4187#10.15) 450 return Arrays.copyOfRange(mac, 0, AtMac.MAC_LENGTH); 451 } 452 453 @VisibleForTesting buildResponseMessageWithMac(int identifier, int eapSubtype, byte[] extraData)454 EapResult buildResponseMessageWithMac(int identifier, int eapSubtype, byte[] extraData) { 455 // capacity of 1 for AtMac to be added 456 return buildResponseMessageWithMac( 457 identifier, 458 eapSubtype, 459 extraData, 460 new ArrayList<>(1) /* attributes */, 461 null /* flagsToAdd */); 462 } 463 464 @VisibleForTesting buildResponseMessageWithMac( int identifier, int eapSubtype, byte[] extraData, List<EapSimAkaAttribute> attributes, @EapResponse.EapResponseFlag int[] flagsToAdd)465 EapResult buildResponseMessageWithMac( 466 int identifier, 467 int eapSubtype, 468 byte[] extraData, 469 List<EapSimAkaAttribute> attributes, 470 @EapResponse.EapResponseFlag int[] flagsToAdd) { 471 try { 472 attributes = new ArrayList<>(attributes); 473 attributes.add(new AtMac()); 474 EapSimAkaTypeData eapSimAkaTypeData = getEapSimAkaTypeData(eapSubtype, attributes); 475 476 byte[] mac = getMac(EAP_CODE_RESPONSE, identifier, eapSimAkaTypeData, extraData); 477 478 eapSimAkaTypeData.attributeMap.put(EAP_AT_MAC, new AtMac(mac)); 479 EapData eapData = new EapData(getEapMethod(), eapSimAkaTypeData.encode()); 480 EapMessage eapMessage = new EapMessage(EAP_CODE_RESPONSE, identifier, eapData); 481 return EapResponse.getEapResponse(eapMessage, flagsToAdd); 482 } catch (EapSimAkaInvalidAttributeException | EapSilentException ex) { 483 // this should never happen 484 return new EapError(ex); 485 } 486 } 487 488 // AT_COUNTER attribute MUST be included in EAP-AKA notifications and MUST be encrypted. validateReauthAkaNotifyAndGetCounter(EapSimAkaTypeData eapSimAkaTypeData)489 private int validateReauthAkaNotifyAndGetCounter(EapSimAkaTypeData eapSimAkaTypeData) { 490 Set<Integer> attrs = eapSimAkaTypeData.attributeMap.keySet(); 491 if (attrs.contains(EAP_AT_IV) 492 && attrs.contains(EAP_AT_ENCR_DATA) 493 && attrs.contains(EAP_AT_MAC)) { 494 LinkedHashMap<Integer, EapSimAkaAttribute> securedAttributes = 495 retrieveSecuredAttributes("Notification", eapSimAkaTypeData); 496 Set<Integer> securedAttrKeySet = securedAttributes.keySet(); 497 498 if (securedAttrKeySet.contains(EAP_AT_COUNTER) 499 && (securedAttrKeySet.size() == 1 500 || (securedAttrKeySet.size() == 2 501 && securedAttrKeySet.contains(EAP_AT_PADDING)))) { 502 return ((EapSimAkaAttribute.AtCounter) securedAttributes.get(EAP_AT_COUNTER)) 503 .counter; 504 } 505 } 506 return -1; 507 } 508 509 @VisibleForTesting handleEapSimAkaNotification( String tag, boolean isPreChallengeState, boolean isReauthState, boolean hadSuccessfulAuthLocal, int identifier, int counterForReauth, EapSimAkaTypeData eapSimAkaTypeData)510 EapResult handleEapSimAkaNotification( 511 String tag, 512 boolean isPreChallengeState, 513 boolean isReauthState, 514 boolean hadSuccessfulAuthLocal, 515 int identifier, 516 int counterForReauth, 517 EapSimAkaTypeData eapSimAkaTypeData) { 518 // EAP-SIM exchanges must not include more than one EAP-SIM notification round 519 // (RFC 4186#6.1, RFC 4187#6.1) 520 if (mHasReceivedSimAkaNotification) { 521 return new EapError( 522 new EapInvalidRequestException("Received multiple EAP-SIM notifications")); 523 } 524 525 mHasReceivedSimAkaNotification = true; 526 AtNotification atNotification = 527 (AtNotification) eapSimAkaTypeData.attributeMap.get(EAP_AT_NOTIFICATION); 528 529 LOG.d( 530 tag, 531 "Received AtNotification:" 532 + " S=" + (atNotification.isSuccessCode ? "1" : "0") 533 + " P=" + (atNotification.isPreSuccessfulChallenge ? "1" : "0") 534 + " Code=" + atNotification.notificationCode); 535 536 // Notification with P bit being set is accepted in both before and in ChallengeState. 537 // Specifically in ChallengeState, after client sends out the challenge response, it's 538 // unclear to the client whether the challenge succeeds or not, and server might send at 539 // most one AKA-Notification after that. Thus, P-0 should be accepted in ChallengeState. 540 if (atNotification.isPreSuccessfulChallenge) { 541 // AT_MAC attribute must not be included when the P bit is set (RFC 4186#9.8, 542 // RFC 4187#9.10) 543 if (eapSimAkaTypeData.attributeMap.containsKey(EAP_AT_MAC)) { 544 return buildClientErrorResponse( 545 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 546 } 547 548 return buildResponseMessage( 549 getEapMethod(), eapSimAkaTypeData.eapSubtype, identifier, Arrays.asList()); 550 } 551 552 // Notification with an unset P bit MUST not be sent before the challenge exchange. 553 if (isPreChallengeState) { 554 return buildClientErrorResponse( 555 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 556 } 557 558 if (!eapSimAkaTypeData.attributeMap.containsKey(EAP_AT_MAC) || !hadSuccessfulAuthLocal) { 559 // Zero P bit notification should be received after server authenticated & MAC must be 560 // included in that notification. (RFC 4186#9.8, RFC 4187#9.10) 561 return buildClientErrorResponse( 562 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 563 } 564 565 try { 566 byte[] mac = getMac(EAP_CODE_REQUEST, identifier, eapSimAkaTypeData, new byte[0]); 567 AtMac atMac = (AtMac) eapSimAkaTypeData.attributeMap.get(EAP_AT_MAC); 568 if (!Arrays.equals(mac, atMac.mac)) { 569 // MAC in message != calculated mac 570 return buildClientErrorResponse( 571 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 572 } 573 574 if (!isReauthState) { 575 // server has been authenticated, so we can send a response 576 return buildResponseMessageWithMac( 577 identifier, eapSimAkaTypeData.eapSubtype, new byte[0]); 578 } else { 579 // AT_COUNTER attribute MUST be included in EAP-AKA notifications, if they are used 580 // after successful authentication in order to provide replay protection. 581 int receivedCounter = validateReauthAkaNotifyAndGetCounter(eapSimAkaTypeData); 582 LOG.d( 583 tag, 584 "Counter in Notification: " 585 + receivedCounter 586 + ", Expecting counter for reauth" 587 + counterForReauth); 588 if (counterForReauth == receivedCounter) { 589 EapSimAkaAttribute.AtIv atIv = new EapSimAkaAttribute.AtIv(mSecureRandom); 590 List<EapSimAkaAttribute> attributeList = 591 buildReauthResponse(counterForReauth, false, mKEncr, atIv); 592 return buildResponseMessageWithMac( 593 identifier, 594 eapSimAkaTypeData.eapSubtype, 595 new byte[0], 596 attributeList, 597 null); 598 } else { 599 return buildClientErrorResponse( 600 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 601 } 602 } 603 } catch (EapSilentException | EapSimAkaInvalidAttributeException ex) { 604 // We can't continue if the MAC can't be generated 605 return new EapError(ex); 606 } 607 } 608 getEapSimAkaTypeData(AtClientErrorCode clientErrorCode)609 abstract EapSimAkaTypeData getEapSimAkaTypeData(AtClientErrorCode clientErrorCode); getEapSimAkaTypeData( int eapSubtype, List<EapSimAkaAttribute> attributes)610 abstract EapSimAkaTypeData getEapSimAkaTypeData( 611 int eapSubtype, List<EapSimAkaAttribute> attributes); 612 } 613