• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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