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