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