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.subId 106 + " apptype=" + mEapUiccConfig.apptype); 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.apptype, 200 authType, 201 base64Challenge); 202 203 if (base64Response == null) { 204 String msg = "UICC authentication failed. Input: " + LOG.pii(formattedChallenge); 205 LOG.e(tag, msg); 206 throw new EapSimAkaAuthenticationFailureException(msg); 207 } 208 209 return Base64.decode(base64Response, Base64.DEFAULT); 210 } 211 212 @VisibleForTesting isValidMac(String tag, EapMessage message, EapSimAkaTypeData typeData, byte[] extraData)213 boolean isValidMac(String tag, EapMessage message, EapSimAkaTypeData typeData, byte[] extraData) 214 throws GeneralSecurityException, EapSimAkaInvalidAttributeException, 215 EapSilentException { 216 mMacAlgorithm = Mac.getInstance(getMacAlgorithm()); 217 mMacAlgorithm.init(new SecretKeySpec(mKAut, getMacAlgorithm())); 218 219 LOG.d(tag, "Computing MAC (raw msg): " + LOG.pii(message.encode())); 220 221 byte[] mac = getMac(message.eapCode, message.eapIdentifier, typeData, extraData); 222 // attributes are 'valid', so must have AtMac 223 AtMac atMac = (AtMac) typeData.attributeMap.get(EAP_AT_MAC); 224 225 boolean isValidMac = Arrays.equals(mac, atMac.mac); 226 if (!isValidMac) { 227 // MAC in message != calculated mac 228 LOG.e( 229 tag, 230 "Received message with invalid Mac." 231 + " received=" + Log.byteArrayToHexString(atMac.mac) 232 + ", computed=" + Log.byteArrayToHexString(mac)); 233 } 234 235 return isValidMac; 236 } 237 238 @VisibleForTesting getMac(int eapCode, int eapIdentifier, EapSimAkaTypeData typeData, byte[] extraData)239 byte[] getMac(int eapCode, int eapIdentifier, EapSimAkaTypeData typeData, byte[] extraData) 240 throws EapSimAkaInvalidAttributeException, EapSilentException { 241 if (mMacAlgorithm == null) { 242 throw new IllegalStateException( 243 "Can't calculate MAC before mMacAlgorithm is set in ChallengeState"); 244 } 245 246 // cache original Mac so it can be restored after calculating the Mac 247 AtMac originalMac = (AtMac) typeData.attributeMap.get(EAP_AT_MAC); 248 typeData.attributeMap.put(EAP_AT_MAC, new AtMac()); 249 250 byte[] typeDataWithEmptyMac = typeData.encode(); 251 EapData eapData = new EapData(getEapMethod(), typeDataWithEmptyMac); 252 EapMessage messageForMac = new EapMessage(eapCode, eapIdentifier, eapData); 253 254 LOG.d(this.getClass().getSimpleName(), 255 "Computing MAC (mac cleared): " + LOG.pii(messageForMac.encode())); 256 257 ByteBuffer buffer = ByteBuffer.allocate(messageForMac.eapLength + extraData.length); 258 buffer.put(messageForMac.encode()); 259 buffer.put(extraData); 260 byte[] mac = mMacAlgorithm.doFinal(buffer.array()); 261 262 typeData.attributeMap.put(EAP_AT_MAC, originalMac); 263 264 // need HMAC-SHA1-128 - first 16 bytes of SHA1 (RFC 4186#10.14, RFC 4187#10.15) 265 return Arrays.copyOfRange(mac, 0, AtMac.MAC_LENGTH); 266 } 267 268 @VisibleForTesting buildResponseMessageWithMac(int identifier, int eapSubtype, byte[] extraData)269 EapResult buildResponseMessageWithMac(int identifier, int eapSubtype, byte[] extraData) { 270 // capacity of 1 for AtMac to be added 271 return buildResponseMessageWithMac(identifier, eapSubtype, extraData, new ArrayList<>(1)); 272 } 273 274 @VisibleForTesting buildResponseMessageWithMac( int identifier, int eapSubtype, byte[] extraData, List<EapSimAkaAttribute> attributes)275 EapResult buildResponseMessageWithMac( 276 int identifier, int eapSubtype, byte[] extraData, List<EapSimAkaAttribute> attributes) { 277 try { 278 attributes = new ArrayList<>(attributes); 279 attributes.add(new AtMac()); 280 EapSimAkaTypeData eapSimAkaTypeData = getEapSimAkaTypeData(eapSubtype, attributes); 281 282 byte[] mac = getMac(EAP_CODE_RESPONSE, identifier, eapSimAkaTypeData, extraData); 283 284 eapSimAkaTypeData.attributeMap.put(EAP_AT_MAC, new AtMac(mac)); 285 EapData eapData = new EapData(getEapMethod(), eapSimAkaTypeData.encode()); 286 EapMessage eapMessage = new EapMessage(EAP_CODE_RESPONSE, identifier, eapData); 287 return EapResponse.getEapResponse(eapMessage); 288 } catch (EapSimAkaInvalidAttributeException | EapSilentException ex) { 289 // this should never happen 290 return new EapError(ex); 291 } 292 } 293 294 @VisibleForTesting handleEapSimAkaNotification( String tag, boolean isPreChallengeState, int identifier, EapSimAkaTypeData eapSimAkaTypeData)295 EapResult handleEapSimAkaNotification( 296 String tag, 297 boolean isPreChallengeState, 298 int identifier, 299 EapSimAkaTypeData eapSimAkaTypeData) { 300 // EAP-SIM exchanges must not include more than one EAP-SIM notification round 301 // (RFC 4186#6.1, RFC 4187#6.1) 302 if (mHasReceivedSimAkaNotification) { 303 return new EapError( 304 new EapInvalidRequestException("Received multiple EAP-SIM notifications")); 305 } 306 307 mHasReceivedSimAkaNotification = true; 308 AtNotification atNotification = 309 (AtNotification) eapSimAkaTypeData.attributeMap.get(EAP_AT_NOTIFICATION); 310 311 LOG.d( 312 tag, 313 "Received AtNotification:" 314 + " S=" + (atNotification.isSuccessCode ? "1" : "0") 315 + " P=" + (atNotification.isPreSuccessfulChallenge ? "1" : "0") 316 + " Code=" + atNotification.notificationCode); 317 318 // P bit of notification code is only allowed after a successful challenge round. This is 319 // only possible in the ChallengeState (RFC 4186#6.1, RFC 4187#6.1) 320 if (isPreChallengeState && !atNotification.isPreSuccessfulChallenge) { 321 return buildClientErrorResponse( 322 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 323 } 324 325 if (atNotification.isPreSuccessfulChallenge) { 326 // AT_MAC attribute must not be included when the P bit is set (RFC 4186#9.8, 327 // RFC 4187#9.10) 328 if (eapSimAkaTypeData.attributeMap.containsKey(EAP_AT_MAC)) { 329 return buildClientErrorResponse( 330 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 331 } 332 333 return buildResponseMessage( 334 getEapMethod(), eapSimAkaTypeData.eapSubtype, identifier, Arrays.asList()); 335 } else if (!eapSimAkaTypeData.attributeMap.containsKey(EAP_AT_MAC)) { 336 // MAC must be included for messages with their P bit not set (RFC 4186#9.8, 337 // RFC 4187#9.10) 338 return buildClientErrorResponse( 339 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 340 } 341 342 try { 343 byte[] mac = getMac(EAP_CODE_REQUEST, identifier, eapSimAkaTypeData, new byte[0]); 344 345 AtMac atMac = (AtMac) eapSimAkaTypeData.attributeMap.get(EAP_AT_MAC); 346 if (!Arrays.equals(mac, atMac.mac)) { 347 // MAC in message != calculated mac 348 return buildClientErrorResponse( 349 identifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 350 } 351 } catch (EapSilentException | EapSimAkaInvalidAttributeException ex) { 352 // We can't continue if the MAC can't be generated 353 return new EapError(ex); 354 } 355 356 // server has been authenticated, so we can send a response 357 return buildResponseMessageWithMac(identifier, eapSimAkaTypeData.eapSubtype, new byte[0]); 358 } 359 getEapSimAkaTypeData(AtClientErrorCode clientErrorCode)360 abstract EapSimAkaTypeData getEapSimAkaTypeData(AtClientErrorCode clientErrorCode); getEapSimAkaTypeData( int eapSubtype, List<EapSimAkaAttribute> attributes)361 abstract EapSimAkaTypeData getEapSimAkaTypeData( 362 int eapSubtype, List<EapSimAkaAttribute> attributes); 363 } 364