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_PRIME; 20 21 import static com.android.internal.net.eap.EapAuthenticator.LOG; 22 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CLIENT_ERROR; 23 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTN; 24 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF; 25 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF_INPUT; 26 27 import android.annotation.Nullable; 28 import android.content.Context; 29 import android.net.eap.EapSessionConfig.EapAkaPrimeConfig; 30 import android.net.eap.EapSessionConfig.EapMethodConfig.EapMethod; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.net.crypto.KeyGenerationUtils; 34 import com.android.internal.net.eap.EapResult; 35 import com.android.internal.net.eap.crypto.HmacSha256ByteSigner; 36 import com.android.internal.net.eap.message.EapMessage; 37 import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData; 38 import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData.EapAkaPrimeTypeDataDecoder; 39 import com.android.internal.net.eap.message.simaka.EapAkaTypeData; 40 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute; 41 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn; 42 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode; 43 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdf; 44 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdfInput; 45 import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult; 46 47 import java.nio.BufferOverflowException; 48 import java.nio.BufferUnderflowException; 49 import java.nio.ByteBuffer; 50 import java.nio.charset.StandardCharsets; 51 import java.security.GeneralSecurityException; 52 import java.util.Arrays; 53 import java.util.List; 54 import java.util.Map; 55 56 import javax.crypto.Mac; 57 import javax.crypto.spec.SecretKeySpec; 58 59 /** 60 * EapAkaPrimeMethodStateMachine represents the valid paths possible for the EAP-AKA' protocol. 61 * 62 * <p>EAP-AKA' sessions will always follow the path: 63 * 64 * Created --+--> Identity --+--> Challenge --> Final 65 * | | 66 * +---------------+ 67 * 68 * <p>Note: If the EAP-Request/AKA'-Challenge message contains an AUTN with an invalid sequence 69 * number, the peer will indicate a synchronization failure to the server and a new challenge will 70 * be attempted. 71 * 72 * <p>Note: EAP-Request/Notification messages can be received at any point in the above state 73 * machine At most one EAP-AKA'/Notification message is allowed per EAP-AKA' session. 74 * 75 * @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication Protocol 76 * for Authentication and Key Agreement (EAP-AKA)</a> 77 * @see <a href="https://tools.ietf.org/html/rfc5448">RFC 5448, Improved Extensible Authentication 78 * Protocol Method for 3rd Generation Authentication and Key Agreement (EAP-AKA')</a> 79 * @hide 80 */ 81 public class EapAkaPrimeMethodStateMachine extends EapAkaMethodStateMachine { 82 public static final int K_AUT_LEN = 32; 83 public static final int K_RE_LEN = 32; 84 85 // EAP-AKA' identity prefix (RFC 5448#3) 86 private static final String AKA_PRIME_IDENTITY_PREFIX = "6"; 87 private static final int SUPPORTED_KDF = 1; 88 private static final int FC = 0x20; // Required by TS 133 402 Annex A.2 89 private static final int SQN_XOR_AK_LEN = 6; 90 private static final int IK_PRIME_LENGTH = 16; 91 private static final int CK_PRIME_LENGTH = 16; 92 private static final String MAC_ALGORITHM_STRING = "HmacSHA256"; 93 private static final String MK_DATA_PREFIX = "EAP-AKA'"; 94 95 // MK_LEN_BYTES = len(K_encr | K_aut | K_re | MSK | EMSK) 96 private static final int MK_LEN_BYTES = 97 KEY_LEN + K_AUT_LEN + K_RE_LEN + (2 * SESSION_KEY_LENGTH); 98 99 public final byte[] mKRe = new byte[getKReLen()]; 100 101 private final EapAkaPrimeConfig mEapAkaPrimeConfig; 102 private final EapAkaPrimeTypeDataDecoder mEapAkaPrimeTypeDataDecoder; 103 EapAkaPrimeMethodStateMachine( Context context, byte[] eapIdentity, EapAkaPrimeConfig eapAkaPrimeConfig)104 EapAkaPrimeMethodStateMachine( 105 Context context, byte[] eapIdentity, EapAkaPrimeConfig eapAkaPrimeConfig) { 106 this( 107 context, 108 eapIdentity, 109 eapAkaPrimeConfig, 110 EapAkaPrimeTypeData.getEapAkaPrimeTypeDataDecoder()); 111 } 112 113 @VisibleForTesting EapAkaPrimeMethodStateMachine( Context context, byte[] eapIdentity, EapAkaPrimeConfig eapAkaPrimeConfig, EapAkaPrimeTypeDataDecoder eapAkaPrimeTypeDataDecoder)114 protected EapAkaPrimeMethodStateMachine( 115 Context context, 116 byte[] eapIdentity, 117 EapAkaPrimeConfig eapAkaPrimeConfig, 118 EapAkaPrimeTypeDataDecoder eapAkaPrimeTypeDataDecoder) { 119 super(context, eapIdentity, eapAkaPrimeConfig); 120 mEapAkaPrimeConfig = eapAkaPrimeConfig; 121 mEapAkaPrimeTypeDataDecoder = eapAkaPrimeTypeDataDecoder; 122 123 transitionTo(new CreatedState()); 124 } 125 126 @Override 127 @EapMethod getEapMethod()128 int getEapMethod() { 129 return EAP_TYPE_AKA_PRIME; 130 } 131 132 @Override getKAutLength()133 protected int getKAutLength() { 134 return K_AUT_LEN; 135 } 136 getKReLen()137 protected int getKReLen() { 138 return K_RE_LEN; 139 } 140 141 @Override decode(byte[] typeData)142 protected DecodeResult<EapAkaTypeData> decode(byte[] typeData) { 143 return mEapAkaPrimeTypeDataDecoder.decode(typeData); 144 } 145 146 @Override getIdentityPrefix()147 protected String getIdentityPrefix() { 148 return AKA_PRIME_IDENTITY_PREFIX; 149 } 150 151 @Override buildChallengeState()152 protected ChallengeState buildChallengeState() { 153 return new ChallengeState(); 154 } 155 156 @Override buildChallengeState(byte[] identity)157 protected ChallengeState buildChallengeState(byte[] identity) { 158 return new ChallengeState(identity); 159 } 160 161 @Override getMacAlgorithm()162 protected String getMacAlgorithm() { 163 return MAC_ALGORITHM_STRING; 164 } 165 166 protected class ChallengeState extends EapAkaMethodStateMachine.ChallengeState { 167 private final String mTAG = ChallengeState.class.getSimpleName(); 168 ChallengeState()169 ChallengeState() { 170 super(); 171 } 172 ChallengeState(byte[] identity)173 ChallengeState(byte[] identity) { 174 super(identity); 175 } 176 177 @Override handleChallengeAuthentication( EapMessage message, EapAkaTypeData eapAkaTypeData)178 protected EapResult handleChallengeAuthentication( 179 EapMessage message, EapAkaTypeData eapAkaTypeData) { 180 EapAkaPrimeTypeData eapAkaPrimeTypeData = (EapAkaPrimeTypeData) eapAkaTypeData; 181 182 if (!isValidChallengeAttributes(eapAkaPrimeTypeData)) { 183 return buildAuthenticationRejectMessage(message.eapIdentifier); 184 } 185 return super.handleChallengeAuthentication(message, eapAkaPrimeTypeData); 186 } 187 188 @VisibleForTesting isValidChallengeAttributes(EapAkaPrimeTypeData eapAkaPrimeTypeData)189 boolean isValidChallengeAttributes(EapAkaPrimeTypeData eapAkaPrimeTypeData) { 190 Map<Integer, EapSimAkaAttribute> attrs = eapAkaPrimeTypeData.attributeMap; 191 192 if (!attrs.containsKey(EAP_AT_KDF) || !attrs.containsKey(EAP_AT_KDF_INPUT)) { 193 return false; 194 } 195 196 // TODO(b/143073851): implement KDF resolution specified in RFC 5448#3.2 197 // This is safe, as there only exists one defined KDF. 198 AtKdf atKdf = (AtKdf) attrs.get(EAP_AT_KDF); 199 if (atKdf.kdf != SUPPORTED_KDF) { 200 return false; 201 } 202 203 AtKdfInput atKdfInput = (AtKdfInput) attrs.get(EAP_AT_KDF_INPUT); 204 if (atKdfInput.networkName.length == 0) { 205 return false; 206 } 207 208 boolean hasMatchingNetworkNames = 209 hasMatchingNetworkNames( 210 mEapAkaPrimeConfig.getNetworkName(), 211 new String(atKdfInput.networkName, StandardCharsets.UTF_8)); 212 return mEapAkaPrimeConfig.allowsMismatchedNetworkNames() || hasMatchingNetworkNames; 213 } 214 215 /** 216 * Compares the peer's network name against the server's network name. 217 * 218 * <p>RFC 5448#3.1 describes how the network names are to be compared: "each name is broken 219 * down to the fields separated by colons. If one of the names has more colons and fields 220 * than the other one, the additional fields are ignored. The remaining sequences of fields 221 * are compared. This algorithm allows a prefix match". 222 * 223 * @return true iff one network name matches the other, as defined by RC 5448#3.1 224 */ 225 @VisibleForTesting hasMatchingNetworkNames(String peerNetworkName, String serverNetworkName)226 boolean hasMatchingNetworkNames(String peerNetworkName, String serverNetworkName) { 227 // compare network names according to RFC 5448#3.1 228 if (peerNetworkName.isEmpty() || serverNetworkName.isEmpty()) { 229 return true; 230 } 231 232 String[] peerNetworkNameFields = peerNetworkName.split(":"); 233 String[] serverNetworkNameFields = serverNetworkName.split(":"); 234 int numFieldsToCompare = 235 Math.min(peerNetworkNameFields.length, serverNetworkNameFields.length); 236 for (int i = 0; i < numFieldsToCompare; i++) { 237 if (!peerNetworkNameFields[i].equals(serverNetworkNameFields[i])) { 238 LOG.i( 239 mTAG, 240 "EAP-AKA' network names don't match." 241 + " Peer: " + LOG.pii(peerNetworkName) 242 + ", Server: " + LOG.pii(serverNetworkName)); 243 return false; 244 } 245 } 246 247 return true; 248 } 249 250 @Nullable 251 @Override generateAndPersistEapAkaKeys( RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData)252 protected EapResult generateAndPersistEapAkaKeys( 253 RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData) { 254 try { 255 AtKdfInput atKdfInput = 256 (AtKdfInput) eapAkaTypeData.attributeMap.get(EAP_AT_KDF_INPUT); 257 AtAutn atAutn = (AtAutn) eapAkaTypeData.attributeMap.get(EAP_AT_AUTN); 258 259 // Values derived as (CK' | IK'), but PRF' needs (IK' | CK') 260 byte[] ckIkPrime = deriveCkIkPrime(result, atKdfInput, atAutn); 261 ByteBuffer prfKey = ByteBuffer.allocate(IK_PRIME_LENGTH + CK_PRIME_LENGTH); 262 prfKey.put(ckIkPrime, CK_PRIME_LENGTH, IK_PRIME_LENGTH); 263 prfKey.put(ckIkPrime, 0, CK_PRIME_LENGTH); 264 265 int dataToSignLen = MK_DATA_PREFIX.length() + mIdentity.length; 266 ByteBuffer dataToSign = ByteBuffer.allocate(dataToSignLen); 267 dataToSign.put(MK_DATA_PREFIX.getBytes(StandardCharsets.US_ASCII)); 268 dataToSign.put(mIdentity); 269 270 ByteBuffer mk = 271 ByteBuffer.wrap( 272 KeyGenerationUtils.prfPlus( 273 HmacSha256ByteSigner.getInstance(), 274 prfKey.array(), 275 dataToSign.array(), 276 MK_LEN_BYTES)); 277 278 mk.get(mKEncr); 279 mk.get(mKAut); 280 mk.get(mKRe); 281 mk.get(mMsk); 282 mk.get(mEmsk); 283 284 // Log as hash unless PII debug mode enabled 285 LOG.d(mTAG, "K_encr=" + LOG.pii(mKEncr)); 286 LOG.d(mTAG, "K_aut=" + LOG.pii(mKAut)); 287 LOG.d(mTAG, "K_re=" + LOG.pii(mKRe)); 288 LOG.d(mTAG, "MSK=" + LOG.pii(mMsk)); 289 LOG.d(mTAG, "EMSK=" + LOG.pii(mEmsk)); 290 return null; 291 } catch (GeneralSecurityException 292 | BufferOverflowException 293 | BufferUnderflowException ex) { 294 LOG.e(mTAG, "Error while generating keys", ex); 295 return buildClientErrorResponse( 296 eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS); 297 } 298 } 299 300 /** 301 * Derives CK' and IK' values from CK and IK 302 * 303 * <p>CK' and IK' generation is specified in TS 133 402 Annex A.2, which relies on the key 304 * derivation function KDF specified in TS 133 220 Annex B.2. 305 */ 306 @VisibleForTesting deriveCkIkPrime( RandChallengeResult randChallengeResult, AtKdfInput atKdfInput, AtAutn atAutn)307 byte[] deriveCkIkPrime( 308 RandChallengeResult randChallengeResult, AtKdfInput atKdfInput, AtAutn atAutn) 309 throws GeneralSecurityException { 310 final int fcLen = 1; 311 int lengthFieldLen = 2; 312 313 // SQN ^ AK is the first 6B of the AUTN value 314 byte[] sqnXorAk = Arrays.copyOf(atAutn.autn, SQN_XOR_AK_LEN); 315 int sLength = 316 fcLen 317 + atKdfInput.networkName.length + lengthFieldLen 318 + SQN_XOR_AK_LEN + lengthFieldLen; 319 320 ByteBuffer dataToSign = ByteBuffer.allocate(sLength); 321 dataToSign.put((byte) FC); 322 dataToSign.put(atKdfInput.networkName); 323 dataToSign.putShort((short) atKdfInput.networkName.length); 324 dataToSign.put(sqnXorAk); 325 dataToSign.putShort((short) SQN_XOR_AK_LEN); 326 327 int keyLen = randChallengeResult.ck.length + randChallengeResult.ik.length; 328 ByteBuffer key = ByteBuffer.allocate(keyLen); 329 key.put(randChallengeResult.ck); 330 key.put(randChallengeResult.ik); 331 332 Mac mac = Mac.getInstance(MAC_ALGORITHM_STRING); 333 mac.init(new SecretKeySpec(key.array(), MAC_ALGORITHM_STRING)); 334 return mac.doFinal(dataToSign.array()); 335 } 336 } 337 getEapSimAkaTypeData(AtClientErrorCode clientErrorCode)338 EapAkaPrimeTypeData getEapSimAkaTypeData(AtClientErrorCode clientErrorCode) { 339 return new EapAkaPrimeTypeData(EAP_AKA_CLIENT_ERROR, Arrays.asList(clientErrorCode)); 340 } 341 getEapSimAkaTypeData(int eapSubtype, List<EapSimAkaAttribute> attributes)342 EapAkaPrimeTypeData getEapSimAkaTypeData(int eapSubtype, List<EapSimAkaAttribute> attributes) { 343 return new EapAkaPrimeTypeData(eapSubtype, attributes); 344 } 345 } 346