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.EAP_AT_MAC; 23 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_NOTIFICATION; 24 25 import android.net.eap.EapSessionConfig.EapUiccConfig; 26 import android.telephony.TelephonyManager; 27 import android.util.Base64; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.net.eap.EapResult; 31 import com.android.internal.net.eap.EapResult.EapError; 32 import com.android.internal.net.eap.EapResult.EapResponse; 33 import com.android.internal.net.eap.crypto.Fips186_2Prf; 34 import com.android.internal.net.eap.exceptions.EapInvalidRequestException; 35 import com.android.internal.net.eap.exceptions.EapSilentException; 36 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaAuthenticationFailureException; 37 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException; 38 import com.android.internal.net.eap.message.EapData; 39 import com.android.internal.net.eap.message.EapMessage; 40 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; 41 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; 42 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtMac; 43 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtNotification; 44 import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData; 45 import com.android.internal.net.utils.Log; 46 47 import java.nio.ByteBuffer; 48 import java.security.GeneralSecurityException; 49 import java.security.MessageDigest; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.List; 53 54 import javax.crypto.Mac; 55 import javax.crypto.spec.SecretKeySpec; 56 57 /** 58 * EapSimAkaMethodStateMachine represents an abstract state machine for managing EAP-SIM and EAP-AKA 59 * sessions. 60 * 61 * @see <a href="https://tools.ietf.org/html/rfc4186">RFC 4186, Extensible Authentication 62 * Protocol for Subscriber Identity Modules (EAP-SIM)</a> 63 * @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication 64 * Protocol for Authentication and Key Agreement (EAP-AKA)</a> 65 */ 66 public abstract class EapSimAkaMethodStateMachine extends EapMethodStateMachine { 67 public static final String MASTER_KEY_GENERATION_ALG = "SHA-1"; 68 public static final String MAC_ALGORITHM_STRING = "HmacSHA1"; 69 70 // K_encr and K_aut lengths are 16 bytes (RFC 4186#7, RFC 4187#7) 71 public static final int KEY_LEN = 16; 72 73 // Session Key lengths are 64 bytes (RFC 4186#7, RFC 4187#7) 74 public static final int SESSION_KEY_LENGTH = 64; 75 76 public final byte[] mKEncr = new byte[getKEncrLength()]; 77 public final byte[] mKAut = new byte[getKAutLength()]; 78 public final byte[] mMsk = new byte[getMskLength()]; 79 public final byte[] mEmsk = new byte[getEmskLength()]; 80 81 @VisibleForTesting boolean mHasReceivedSimAkaNotification = false; 82 83 final TelephonyManager mTelephonyManager; 84 final byte[] mEapIdentity; 85 final EapUiccConfig mEapUiccConfig; 86 87 @VisibleForTesting Mac mMacAlgorithm; 88 EapSimAkaMethodStateMachine( TelephonyManager telephonyManager, byte[] eapIdentity, EapUiccConfig eapUiccConfig)89 EapSimAkaMethodStateMachine( 90 TelephonyManager telephonyManager, byte[] eapIdentity, EapUiccConfig eapUiccConfig) { 91 if (telephonyManager == null) { 92 throw new IllegalArgumentException("TelephonyManager must be non-null"); 93 } else if (eapIdentity == null) { 94 throw new IllegalArgumentException("EapIdentity must be non-null"); 95 } else if (eapUiccConfig == null) { 96 throw new IllegalArgumentException("EapUiccConfig must be non-null"); 97 } 98 this.mTelephonyManager = telephonyManager; 99 this.mEapIdentity = eapIdentity; 100 this.mEapUiccConfig = eapUiccConfig; 101 102 LOG.d( 103 this.getClass().getSimpleName(), 104 mEapUiccConfig.getClass().getSimpleName() + ":" 105 + " subId=" + mEapUiccConfig.getSubId() 106 + " apptype=" + mEapUiccConfig.getAppType()); 107 } 108 getKEncrLength()109 protected int getKEncrLength() { 110 return KEY_LEN; 111 } 112 getKAutLength()113 protected int getKAutLength() { 114 return KEY_LEN; 115 } 116 getMskLength()117 protected int getMskLength() { 118 return SESSION_KEY_LENGTH; 119 } 120 getEmskLength()121 protected int getEmskLength() { 122 return SESSION_KEY_LENGTH; 123 } 124 125 @Override handleEapNotification(String tag, EapMessage message)126 EapResult handleEapNotification(String tag, EapMessage message) { 127 return EapStateMachine.handleNotification(tag, message); 128 } 129 getMacAlgorithm()130 protected String getMacAlgorithm() { 131 return MAC_ALGORITHM_STRING; 132 } 133 134 @VisibleForTesting buildClientErrorResponse( int eapIdentifier, int eapMethodType, AtClientErrorCode clientErrorCode)135 EapResult buildClientErrorResponse( 136 int eapIdentifier, 137 int eapMethodType, 138 AtClientErrorCode clientErrorCode) { 139 mIsExpectingEapFailure = true; 140 EapSimAkaTypeData eapSimAkaTypeData = getEapSimAkaTypeData(clientErrorCode); 141 byte[] encodedTypeData = eapSimAkaTypeData.encode(); 142 143 EapData eapData = new EapData(eapMethodType, encodedTypeData); 144 try { 145 EapMessage response = new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, eapData); 146 return EapResult.EapResponse.getEapResponse(response); 147 } catch (EapSilentException ex) { 148 return new EapResult.EapError(ex); 149 } 150 } 151 152 @VisibleForTesting buildResponseMessage( int eapType, int eapSubtype, int identifier, List<EapSimAkaAttribute> attributes)153 EapResult buildResponseMessage( 154 int eapType, 155 int eapSubtype, 156 int identifier, 157 List<EapSimAkaAttribute> attributes) { 158 EapSimAkaTypeData eapSimTypeData = getEapSimAkaTypeData(eapSubtype, attributes); 159 EapData eapData = new EapData(eapType, eapSimTypeData.encode()); 160 161 try { 162 EapMessage eapMessage = new EapMessage(EAP_CODE_RESPONSE, identifier, eapData); 163 return EapResult.EapResponse.getEapResponse(eapMessage); 164 } catch (EapSilentException ex) { 165 return new EapResult.EapError(ex); 166 } 167 } 168 169 @VisibleForTesting generateAndPersistKeys( String tag, MessageDigest sha1, Fips186_2Prf prf, byte[] mkInput)170 protected void generateAndPersistKeys( 171 String tag, MessageDigest sha1, Fips186_2Prf prf, byte[] mkInput) { 172 byte[] mk = sha1.digest(mkInput); 173 174 // run mk through FIPS 186-2 175 int outputBytes = mKEncr.length + mKAut.length + mMsk.length + mEmsk.length; 176 byte[] prfResult = prf.getRandom(mk, outputBytes); 177 178 ByteBuffer prfResultBuffer = ByteBuffer.wrap(prfResult); 179 prfResultBuffer.get(mKEncr); 180 prfResultBuffer.get(mKAut); 181 prfResultBuffer.get(mMsk); 182 prfResultBuffer.get(mEmsk); 183 184 // Log as hash unless PII debug mode enabled 185 LOG.d(tag, "MK input=" + LOG.pii(mkInput)); 186 LOG.d(tag, "MK=" + LOG.pii(mk)); 187 LOG.d(tag, "K_encr=" + LOG.pii(mKEncr)); 188 LOG.d(tag, "K_aut=" + LOG.pii(mKAut)); 189 LOG.d(tag, "MSK=" + LOG.pii(mMsk)); 190 LOG.d(tag, "EMSK=" + LOG.pii(mEmsk)); 191 } 192 193 @VisibleForTesting processUiccAuthentication(String tag, int authType, byte[] formattedChallenge)194 byte[] processUiccAuthentication(String tag, int authType, byte[] formattedChallenge) throws 195 EapSimAkaAuthenticationFailureException { 196 String base64Challenge = Base64.encodeToString(formattedChallenge, Base64.NO_WRAP); 197 String base64Response = 198 mTelephonyManager.getIccAuthentication( 199 mEapUiccConfig.getAppType(), authType, base64Challenge); 200 201 if (base64Response == null) { 202 String msg = "UICC authentication failed. Input: " + LOG.pii(formattedChallenge); 203 LOG.e(tag, msg); 204 throw new EapSimAkaAuthenticationFailureException(msg); 205 } 206 207 return Base64.decode(base64Response, Base64.DEFAULT); 208 } 209 210 @VisibleForTesting isValidMac(String tag, EapMessage message, EapSimAkaTypeData typeData, byte[] extraData)211 boolean isValidMac(String tag, EapMessage message, EapSimAkaTypeData typeData, byte[] extraData) 212 throws GeneralSecurityException, EapSimAkaInvalidAttributeException, 213 EapSilentException { 214 mMacAlgorithm = Mac.getInstance(getMacAlgorithm()); 215 mMacAlgorithm.init(new SecretKeySpec(mKAut, getMacAlgorithm())); 216 217 LOG.d(tag, "Computing MAC (raw msg): " + LOG.pii(message.encode())); 218 219 byte[] mac = getMac(message.eapCode, message.eapIdentifier, typeData, extraData); 220 // attributes are 'valid', so must have AtMac 221 AtMac atMac = (AtMac) typeData.attributeMap.get(EAP_AT_MAC); 222 223 boolean isValidMac = Arrays.equals(mac, atMac.mac); 224 if (!isValidMac) { 225 // MAC in message != calculated mac 226 LOG.e( 227 tag, 228 "Received message with invalid Mac." 229 + " received=" + Log.byteArrayToHexString(atMac.mac) 230 + ", computed=" + Log.byteArrayToHexString(mac)); 231 } 232 233 return isValidMac; 234 } 235 236 @VisibleForTesting getMac(int eapCode, int eapIdentifier, EapSimAkaTypeData typeData, byte[] extraData)237 byte[] getMac(int eapCode, int eapIdentifier, EapSimAkaTypeData typeData, byte[] extraData) 238 throws EapSimAkaInvalidAttributeException, EapSilentException { 239 if (mMacAlgorithm == null) { 240 throw new IllegalStateException( 241 "Can't calculate MAC before mMacAlgorithm is set in ChallengeState"); 242 } 243 244 // cache original Mac so it can be restored after calculating the Mac 245 AtMac originalMac = (AtMac) typeData.attributeMap.get(EAP_AT_MAC); 246 typeData.attributeMap.put(EAP_AT_MAC, originalMac.getAtMacWithMacCleared()); 247 248 byte[] typeDataWithEmptyMac = typeData.encode(); 249 EapData eapData = new EapData(getEapMethod(), typeDataWithEmptyMac); 250 EapMessage messageForMac = new EapMessage(eapCode, eapIdentifier, eapData); 251 252 LOG.d(this.getClass().getSimpleName(), 253 "Computing MAC (mac cleared): " + LOG.pii(messageForMac.encode())); 254 255 ByteBuffer buffer = ByteBuffer.allocate(messageForMac.eapLength + extraData.length); 256 buffer.put(messageForMac.encode()); 257 buffer.put(extraData); 258 byte[] mac = mMacAlgorithm.doFinal(buffer.array()); 259 260 typeData.attributeMap.put(EAP_AT_MAC, originalMac); 261 262 // need HMAC-SHA1-128 - first 16 bytes of SHA1 (RFC 4186#10.14, RFC 4187#10.15) 263 return Arrays.copyOfRange(mac, 0, AtMac.MAC_LENGTH); 264 } 265 266 @VisibleForTesting buildResponseMessageWithMac(int identifier, int eapSubtype, byte[] extraData)267 EapResult buildResponseMessageWithMac(int identifier, int eapSubtype, byte[] extraData) { 268 // capacity of 1 for AtMac to be added 269 return buildResponseMessageWithMac(identifier, eapSubtype, extraData, new ArrayList<>(1)); 270 } 271 272 @VisibleForTesting buildResponseMessageWithMac( int identifier, int eapSubtype, byte[] extraData, List<EapSimAkaAttribute> attributes)273 EapResult buildResponseMessageWithMac( 274 int identifier, int eapSubtype, byte[] extraData, List<EapSimAkaAttribute> attributes) { 275 try { 276 attributes = new ArrayList<>(attributes); 277 attributes.add(new AtMac()); 278 EapSimAkaTypeData eapSimAkaTypeData = getEapSimAkaTypeData(eapSubtype, attributes); 279 280 byte[] mac = getMac(EAP_CODE_RESPONSE, identifier, eapSimAkaTypeData, extraData); 281 282 eapSimAkaTypeData.attributeMap.put(EAP_AT_MAC, new AtMac(mac)); 283 EapData eapData = new EapData(getEapMethod(), eapSimAkaTypeData.encode()); 284 EapMessage eapMessage = new EapMessage(EAP_CODE_RESPONSE, identifier, eapData); 285 return EapResponse.getEapResponse(eapMessage); 286 } catch (EapSimAkaInvalidAttributeException | EapSilentException ex) { 287 // this should never happen 288 return new EapError(ex); 289 } 290 } 291 292 @VisibleForTesting handleEapSimAkaNotification( String tag, boolean isPreChallengeState, int identifier, EapSimAkaTypeData eapSimAkaTypeData)293 EapResult handleEapSimAkaNotification( 294 String tag, 295 boolean isPreChallengeState, 296 int identifier, 297 EapSimAkaTypeData eapSimAkaTypeData) { 298 // EAP-SIM exchanges must not include more than one EAP-SIM notification round 299 // (RFC 4186#6.1, RFC 4187#6.1) 300 if (mHasReceivedSimAkaNotification) { 301 return new EapError( 302 new EapInvalidRequestException("Received multiple EAP-SIM notifications")); 303 } 304 305 mHasReceivedSimAkaNotification = true; 306 AtNotification atNotification = 307 (AtNotification) eapSimAkaTypeData.attributeMap.get(EAP_AT_NOTIFICATION); 308 309 LOG.d( 310 tag, 311 "Received AtNotification:" 312 + " S=" + (atNotification.isSuccessCode ? "1" : "0") 313 + " P=" + (atNotification.isPreSuccessfulChallenge ? "1" : "0") 314 + " Code=" + atNotification.notificationCode); 315 316 // P bit of notification code is only allowed after a successful challenge round. This is 317 // only possible in the ChallengeState (RFC 4186#6.1, RFC 4187#6.1) 318 if (isPreChallengeState && !atNotification.isPreSuccessfulChallenge) { 319 return buildClientErrorResponse( 320 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 321 } 322 323 if (atNotification.isPreSuccessfulChallenge) { 324 // AT_MAC attribute must not be included when the P bit is set (RFC 4186#9.8, 325 // RFC 4187#9.10) 326 if (eapSimAkaTypeData.attributeMap.containsKey(EAP_AT_MAC)) { 327 return buildClientErrorResponse( 328 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 329 } 330 331 return buildResponseMessage( 332 getEapMethod(), eapSimAkaTypeData.eapSubtype, identifier, Arrays.asList()); 333 } else if (!eapSimAkaTypeData.attributeMap.containsKey(EAP_AT_MAC)) { 334 // MAC must be included for messages with their P bit not set (RFC 4186#9.8, 335 // RFC 4187#9.10) 336 return buildClientErrorResponse( 337 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 338 } 339 340 try { 341 byte[] mac = getMac(EAP_CODE_REQUEST, identifier, eapSimAkaTypeData, new byte[0]); 342 343 AtMac atMac = (AtMac) eapSimAkaTypeData.attributeMap.get(EAP_AT_MAC); 344 if (!Arrays.equals(mac, atMac.mac)) { 345 // MAC in message != calculated mac 346 return buildClientErrorResponse( 347 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 348 } 349 } catch (EapSilentException | EapSimAkaInvalidAttributeException ex) { 350 // We can't continue if the MAC can't be generated 351 return new EapError(ex); 352 } 353 354 // server has been authenticated, so we can send a response 355 return buildResponseMessageWithMac(identifier, eapSimAkaTypeData.eapSubtype, new byte[0]); 356 } 357 getEapSimAkaTypeData(AtClientErrorCode clientErrorCode)358 abstract EapSimAkaTypeData getEapSimAkaTypeData(AtClientErrorCode clientErrorCode); getEapSimAkaTypeData( int eapSubtype, List<EapSimAkaAttribute> attributes)359 abstract EapSimAkaTypeData getEapSimAkaTypeData( 360 int eapSubtype, List<EapSimAkaAttribute> attributes); 361 } 362