1 /*
2  * Copyright (C) 2016 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.server.wifi.util;
18 
19 import android.annotation.NonNull;
20 import android.net.wifi.WifiConfiguration;
21 import android.net.wifi.WifiEnterpriseConfig;
22 import android.telephony.ImsiEncryptionInfo;
23 import android.telephony.SubscriptionManager;
24 import android.telephony.TelephonyManager;
25 import android.text.TextUtils;
26 import android.util.Base64;
27 import android.util.Log;
28 import android.util.Pair;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.server.wifi.CarrierNetworkConfig;
32 import com.android.server.wifi.WifiNative;
33 
34 import java.security.InvalidKeyException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.PublicKey;
37 import java.util.HashMap;
38 
39 import javax.annotation.Nonnull;
40 import javax.crypto.BadPaddingException;
41 import javax.crypto.Cipher;
42 import javax.crypto.IllegalBlockSizeException;
43 import javax.crypto.NoSuchPaddingException;
44 
45 /**
46  * Utilities for the Wifi Service to interact with telephony.
47  * TODO(b/132188983): Refactor into TelephonyFacade which owns all instances of
48  *  TelephonyManager/SubscriptionManager in Wifi
49  */
50 public class TelephonyUtil {
51     public static final String TAG = "TelephonyUtil";
52     public static final String DEFAULT_EAP_PREFIX = "\0";
53 
54     public static final int CARRIER_INVALID_TYPE = -1;
55     public static final int CARRIER_MNO_TYPE = 0; // Mobile Network Operator
56     public static final int CARRIER_MVNO_TYPE = 1; // Mobile Virtual Network Operator
57     public static final String ANONYMOUS_IDENTITY = "anonymous";
58     public static final String THREE_GPP_NAI_REALM_FORMAT = "wlan.mnc%s.mcc%s.3gppnetwork.org";
59 
60     // IMSI encryption method: RSA-OAEP with SHA-256 hash function
61     private static final String IMSI_CIPHER_TRANSFORMATION =
62             "RSA/ECB/OAEPwithSHA-256andMGF1Padding";
63 
64     private static final HashMap<Integer, String> EAP_METHOD_PREFIX = new HashMap<>();
65     static {
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA, "0")66         EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA, "0");
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.SIM, "1")67         EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.SIM, "1");
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA_PRIME, "6")68         EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA_PRIME, "6");
69     }
70 
71     /**
72      * 3GPP TS 11.11  2G_authentication command/response
73      *                Input: [RAND]
74      *                Output: [SRES][Cipher Key Kc]
75      */
76     private static final int START_SRES_POS = 0; // MUST be 0
77     private static final int SRES_LEN = 4;
78     private static final int START_KC_POS = START_SRES_POS + SRES_LEN;
79     private static final int KC_LEN = 8;
80 
81     /**
82      * Get the identity for the current SIM or null if the SIM is not available
83      *
84      * @param tm TelephonyManager instance
85      * @param config WifiConfiguration that indicates what sort of authentication is necessary
86      * @param telephonyUtil TelephonyUtil instance
87      * @param carrierNetworkConfig CarrierNetworkConfig instance
88      * @return Pair<identify, encrypted identity> or null if the SIM is not available
89      * or config is invalid
90      */
getSimIdentity(TelephonyManager tm, TelephonyUtil telephonyUtil, WifiConfiguration config, CarrierNetworkConfig carrierNetworkConfig)91     public static Pair<String, String> getSimIdentity(TelephonyManager tm,
92             TelephonyUtil telephonyUtil,
93             WifiConfiguration config, CarrierNetworkConfig carrierNetworkConfig) {
94         if (tm == null) {
95             Log.e(TAG, "No valid TelephonyManager");
96             return null;
97         }
98         TelephonyManager defaultDataTm = tm.createForSubscriptionId(
99                 SubscriptionManager.getDefaultDataSubscriptionId());
100         if (carrierNetworkConfig == null) {
101             Log.e(TAG, "No valid CarrierNetworkConfig");
102             return null;
103         }
104         String imsi = defaultDataTm.getSubscriberId();
105         String mccMnc = "";
106 
107         if (defaultDataTm.getSimState() == TelephonyManager.SIM_STATE_READY) {
108             mccMnc = defaultDataTm.getSimOperator();
109         }
110 
111         String identity = buildIdentity(getSimMethodForConfig(config), imsi, mccMnc, false);
112         if (identity == null) {
113             Log.e(TAG, "Failed to build the identity");
114             return null;
115         }
116 
117         ImsiEncryptionInfo imsiEncryptionInfo;
118         try {
119             imsiEncryptionInfo = defaultDataTm.getCarrierInfoForImsiEncryption(
120                     TelephonyManager.KEY_TYPE_WLAN);
121         } catch (RuntimeException e) {
122             Log.e(TAG, "Failed to get imsi encryption info: " + e.getMessage());
123             return null;
124         }
125         if (imsiEncryptionInfo == null) {
126             // Does not support encrypted identity.
127             return Pair.create(identity, "");
128         }
129 
130         String encryptedIdentity = buildEncryptedIdentity(telephonyUtil, identity,
131                     imsiEncryptionInfo);
132 
133         // In case of failure for encryption, abort current EAP authentication.
134         if (encryptedIdentity == null) {
135             Log.e(TAG, "failed to encrypt the identity");
136             return null;
137         }
138         return Pair.create(identity, encryptedIdentity);
139     }
140 
141     /**
142      * Gets Anonymous identity for current active SIM.
143      *
144      * @param tm TelephonyManager instance
145      * @return anonymous identity@realm which is based on current MCC/MNC, {@code null} if SIM is
146      * not ready or absent.
147      */
getAnonymousIdentityWith3GppRealm(@onnull TelephonyManager tm)148     public static String getAnonymousIdentityWith3GppRealm(@Nonnull TelephonyManager tm) {
149         if (tm == null) {
150             return null;
151         }
152         TelephonyManager defaultDataTm = tm.createForSubscriptionId(
153                 SubscriptionManager.getDefaultDataSubscriptionId());
154         if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
155             return null;
156         }
157         String mccMnc = defaultDataTm.getSimOperator();
158         if (mccMnc == null || mccMnc.isEmpty()) {
159             return null;
160         }
161 
162         // Extract mcc & mnc from mccMnc
163         String mcc = mccMnc.substring(0, 3);
164         String mnc = mccMnc.substring(3);
165 
166         if (mnc.length() == 2) {
167             mnc = "0" + mnc;
168         }
169 
170         String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
171         return ANONYMOUS_IDENTITY + "@" + realm;
172     }
173 
174     /**
175      * Encrypt the given data with the given public key.  The encrypted data will be returned as
176      * a Base64 encoded string.
177      *
178      * @param key The public key to use for encryption
179      * @param encodingFlag base64 encoding flag
180      * @return Base64 encoded string, or null if encryption failed
181      */
182     @VisibleForTesting
encryptDataUsingPublicKey(PublicKey key, byte[] data, int encodingFlag)183     public String encryptDataUsingPublicKey(PublicKey key, byte[] data, int encodingFlag) {
184         try {
185             Cipher cipher = Cipher.getInstance(IMSI_CIPHER_TRANSFORMATION);
186             cipher.init(Cipher.ENCRYPT_MODE, key);
187             byte[] encryptedBytes = cipher.doFinal(data);
188 
189             return Base64.encodeToString(encryptedBytes, 0, encryptedBytes.length, encodingFlag);
190         } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
191                 | IllegalBlockSizeException | BadPaddingException e) {
192             Log.e(TAG, "Encryption failed: " + e.getMessage());
193             return null;
194         }
195     }
196 
197     /**
198      * Create the encrypted identity.
199      *
200      * Prefix value:
201      * "0" - EAP-AKA Identity
202      * "1" - EAP-SIM Identity
203      * "6" - EAP-AKA' Identity
204      * Encrypted identity format: prefix|IMSI@<NAIRealm>
205      * @param telephonyUtil      TelephonyUtil instance
206      * @param identity           permanent identity with format based on section 4.1.1.6 of RFC 4187
207      *                           and 4.2.1.6 of RFC 4186.
208      * @param imsiEncryptionInfo The IMSI encryption info retrieved from the SIM
209      * @return "\0" + encryptedIdentity + "{, Key Identifier AVP}"
210      */
buildEncryptedIdentity(TelephonyUtil telephonyUtil, String identity, ImsiEncryptionInfo imsiEncryptionInfo)211     private static String buildEncryptedIdentity(TelephonyUtil telephonyUtil, String identity,
212             ImsiEncryptionInfo imsiEncryptionInfo) {
213         if (imsiEncryptionInfo == null) {
214             Log.e(TAG, "imsiEncryptionInfo is not valid");
215             return null;
216         }
217         if (identity == null) {
218             Log.e(TAG, "identity is not valid");
219             return null;
220         }
221 
222         // Build and return the encrypted identity.
223         String encryptedIdentity = telephonyUtil.encryptDataUsingPublicKey(
224                 imsiEncryptionInfo.getPublicKey(), identity.getBytes(), Base64.NO_WRAP);
225         if (encryptedIdentity == null) {
226             Log.e(TAG, "Failed to encrypt IMSI");
227             return null;
228         }
229         encryptedIdentity = DEFAULT_EAP_PREFIX + encryptedIdentity;
230         if (imsiEncryptionInfo.getKeyIdentifier() != null) {
231             // Include key identifier AVP (Attribute Value Pair).
232             encryptedIdentity = encryptedIdentity + "," + imsiEncryptionInfo.getKeyIdentifier();
233         }
234         return encryptedIdentity;
235     }
236 
237     /**
238      * Create an identity used for SIM-based EAP authentication. The identity will be based on
239      * the info retrieved from the SIM card, such as IMSI and IMSI encryption info. The IMSI
240      * contained in the identity will be encrypted if IMSI encryption info is provided.
241      *
242      * See  rfc4186 & rfc4187 & rfc5448:
243      *
244      * Identity format:
245      * Prefix | [IMSI || Encrypted IMSI] | @realm | {, Key Identifier AVP}
246      * where "|" denotes concatenation, "||" denotes exclusive value, "{}"
247      * denotes optional value, and realm is the 3GPP network domain name derived from the given
248      * MCC/MNC according to the 3GGP spec(TS23.003).
249      *
250      * Prefix value:
251      * "\0" - Encrypted Identity
252      * "0" - EAP-AKA Identity
253      * "1" - EAP-SIM Identity
254      * "6" - EAP-AKA' Identity
255      *
256      * Encrypted IMSI:
257      * Base64{RSA_Public_Key_Encryption{eapPrefix | IMSI}}
258      * where "|" denotes concatenation,
259      *
260      * @param eapMethod EAP authentication method: EAP-SIM, EAP-AKA, EAP-AKA'
261      * @param imsi The IMSI retrieved from the SIM
262      * @param mccMnc The MCC MNC identifier retrieved from the SIM
263      * @param isEncrypted Whether the imsi is encrypted or not.
264      * @return the eap identity, built using either the encrypted or un-encrypted IMSI.
265      */
buildIdentity(int eapMethod, String imsi, String mccMnc, boolean isEncrypted)266     private static String buildIdentity(int eapMethod, String imsi, String mccMnc,
267                                         boolean isEncrypted) {
268         if (imsi == null || imsi.isEmpty()) {
269             Log.e(TAG, "No IMSI or IMSI is null");
270             return null;
271         }
272 
273         String prefix = isEncrypted ? DEFAULT_EAP_PREFIX : EAP_METHOD_PREFIX.get(eapMethod);
274         if (prefix == null) {
275             return null;
276         }
277 
278         /* extract mcc & mnc from mccMnc */
279         String mcc;
280         String mnc;
281         if (mccMnc != null && !mccMnc.isEmpty()) {
282             mcc = mccMnc.substring(0, 3);
283             mnc = mccMnc.substring(3);
284             if (mnc.length() == 2) {
285                 mnc = "0" + mnc;
286             }
287         } else {
288             // extract mcc & mnc from IMSI, assume mnc size is 3
289             mcc = imsi.substring(0, 3);
290             mnc = imsi.substring(3, 6);
291         }
292 
293         String naiRealm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
294         return prefix + imsi + "@" + naiRealm;
295     }
296 
297     /**
298      * Return the associated SIM method for the configuration.
299      *
300      * @param config WifiConfiguration corresponding to the network.
301      * @return the outer EAP method associated with this SIM configuration.
302      */
getSimMethodForConfig(WifiConfiguration config)303     private static int getSimMethodForConfig(WifiConfiguration config) {
304         if (config == null || config.enterpriseConfig == null) {
305             return WifiEnterpriseConfig.Eap.NONE;
306         }
307         int eapMethod = config.enterpriseConfig.getEapMethod();
308         if (eapMethod == WifiEnterpriseConfig.Eap.PEAP) {
309             // Translate known inner eap methods into an equivalent outer eap method.
310             switch (config.enterpriseConfig.getPhase2Method()) {
311                 case WifiEnterpriseConfig.Phase2.SIM:
312                     eapMethod = WifiEnterpriseConfig.Eap.SIM;
313                     break;
314                 case WifiEnterpriseConfig.Phase2.AKA:
315                     eapMethod = WifiEnterpriseConfig.Eap.AKA;
316                     break;
317                 case WifiEnterpriseConfig.Phase2.AKA_PRIME:
318                     eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
319                     break;
320             }
321         }
322 
323         return isSimEapMethod(eapMethod) ? eapMethod : WifiEnterpriseConfig.Eap.NONE;
324     }
325 
326     /**
327      * Checks if the network is a SIM config.
328      *
329      * @param config Config corresponding to the network.
330      * @return true if it is a SIM config, false otherwise.
331      */
isSimConfig(WifiConfiguration config)332     public static boolean isSimConfig(WifiConfiguration config) {
333         return getSimMethodForConfig(config) != WifiEnterpriseConfig.Eap.NONE;
334     }
335 
336     /**
337      * Returns true if {@code identity} contains an anonymous@realm identity, false otherwise.
338      */
isAnonymousAtRealmIdentity(String identity)339     public static boolean isAnonymousAtRealmIdentity(String identity) {
340         if (identity == null) return false;
341         return identity.startsWith(TelephonyUtil.ANONYMOUS_IDENTITY + "@");
342     }
343 
344     /**
345      * Checks if the EAP outer method is SIM related.
346      *
347      * @param eapMethod WifiEnterpriseConfig Eap method.
348      * @return true if this EAP outer method is SIM-related, false otherwise.
349      */
isSimEapMethod(int eapMethod)350     public static boolean isSimEapMethod(int eapMethod) {
351         return eapMethod == WifiEnterpriseConfig.Eap.SIM
352                 || eapMethod == WifiEnterpriseConfig.Eap.AKA
353                 || eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME;
354     }
355 
356     // TODO replace some of this code with Byte.parseByte
parseHex(char ch)357     private static int parseHex(char ch) {
358         if ('0' <= ch && ch <= '9') {
359             return ch - '0';
360         } else if ('a' <= ch && ch <= 'f') {
361             return ch - 'a' + 10;
362         } else if ('A' <= ch && ch <= 'F') {
363             return ch - 'A' + 10;
364         } else {
365             throw new NumberFormatException("" + ch + " is not a valid hex digit");
366         }
367     }
368 
parseHex(String hex)369     private static byte[] parseHex(String hex) {
370         /* This only works for good input; don't throw bad data at it */
371         if (hex == null) {
372             return new byte[0];
373         }
374 
375         if (hex.length() % 2 != 0) {
376             throw new NumberFormatException(hex + " is not a valid hex string");
377         }
378 
379         byte[] result = new byte[(hex.length()) / 2 + 1];
380         result[0] = (byte) ((hex.length()) / 2);
381         for (int i = 0, j = 1; i < hex.length(); i += 2, j++) {
382             int val = parseHex(hex.charAt(i)) * 16 + parseHex(hex.charAt(i + 1));
383             byte b = (byte) (val & 0xFF);
384             result[j] = b;
385         }
386 
387         return result;
388     }
389 
parseHexWithoutLength(String hex)390     private static byte[] parseHexWithoutLength(String hex) {
391         byte[] tmpRes = parseHex(hex);
392         if (tmpRes.length == 0) {
393             return tmpRes;
394         }
395 
396         byte[] result = new byte[tmpRes.length - 1];
397         System.arraycopy(tmpRes, 1, result, 0, tmpRes.length - 1);
398 
399         return result;
400     }
401 
makeHex(byte[] bytes)402     private static String makeHex(byte[] bytes) {
403         StringBuilder sb = new StringBuilder();
404         for (byte b : bytes) {
405             sb.append(String.format("%02x", b));
406         }
407         return sb.toString();
408     }
409 
makeHex(byte[] bytes, int from, int len)410     private static String makeHex(byte[] bytes, int from, int len) {
411         StringBuilder sb = new StringBuilder();
412         for (int i = 0; i < len; i++) {
413             sb.append(String.format("%02x", bytes[from + i]));
414         }
415         return sb.toString();
416     }
417 
concatHex(byte[] array1, byte[] array2)418     private static byte[] concatHex(byte[] array1, byte[] array2) {
419 
420         int len = array1.length + array2.length;
421 
422         byte[] result = new byte[len];
423 
424         int index = 0;
425         if (array1.length != 0) {
426             for (byte b : array1) {
427                 result[index] = b;
428                 index++;
429             }
430         }
431 
432         if (array2.length != 0) {
433             for (byte b : array2) {
434                 result[index] = b;
435                 index++;
436             }
437         }
438 
439         return result;
440     }
441 
442     /**
443      * Calculate SRES and KC as 3G authentication.
444      *
445      * Standard       Cellular_auth     Type Command
446      *
447      * 3GPP TS 31.102 3G_authentication [Length][RAND][Length][AUTN]
448      *                         [Length][RES][Length][CK][Length][IK] and more
449      *
450      * @param requestData RAND data from server.
451      * @param tm the instance of TelephonyManager.
452      * @return the response data processed by SIM. If all request data is malformed, then returns
453      * empty string. If request data is invalid, then returns null.
454      */
getGsmSimAuthResponse(String[] requestData, TelephonyManager tm)455     public static String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) {
456         return getGsmAuthResponseWithLength(requestData, tm, TelephonyManager.APPTYPE_USIM);
457     }
458 
459     /**
460      * Calculate SRES and KC as 2G authentication.
461      *
462      * Standard       Cellular_auth     Type Command
463      *
464      * 3GPP TS 31.102 2G_authentication [Length][RAND]
465      *                         [Length][SRES][Length][Cipher Key Kc]
466      *
467      * @param requestData RAND data from server.
468      * @param tm the instance of TelephonyManager.
469      * @return the response data processed by SIM. If all request data is malformed, then returns
470      * empty string. If request data is invalid, then returns null.
471      */
getGsmSimpleSimAuthResponse(String[] requestData, TelephonyManager tm)472     public static String getGsmSimpleSimAuthResponse(String[] requestData, TelephonyManager tm) {
473         return getGsmAuthResponseWithLength(requestData, tm, TelephonyManager.APPTYPE_SIM);
474     }
475 
getGsmAuthResponseWithLength(String[] requestData, TelephonyManager tm, int appType)476     private static String getGsmAuthResponseWithLength(String[] requestData, TelephonyManager tm,
477             int appType) {
478         if (tm == null) {
479             Log.e(TAG, "No valid TelephonyManager");
480             return null;
481         }
482         TelephonyManager defaultDataTm = tm.createForSubscriptionId(
483                 SubscriptionManager.getDefaultDataSubscriptionId());
484         StringBuilder sb = new StringBuilder();
485         for (String challenge : requestData) {
486             if (challenge == null || challenge.isEmpty()) {
487                 continue;
488             }
489             Log.d(TAG, "RAND = " + challenge);
490 
491             byte[] rand = null;
492             try {
493                 rand = parseHex(challenge);
494             } catch (NumberFormatException e) {
495                 Log.e(TAG, "malformed challenge");
496                 continue;
497             }
498 
499             String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP);
500 
501             String tmResponse = defaultDataTm.getIccAuthentication(
502                     appType, TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
503             Log.v(TAG, "Raw Response - " + tmResponse);
504 
505             if (tmResponse == null || tmResponse.length() <= 4) {
506                 Log.e(TAG, "bad response - " + tmResponse);
507                 return null;
508             }
509 
510             byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
511             Log.v(TAG, "Hex Response -" + makeHex(result));
512             int sresLen = result[0];
513             if (sresLen < 0 || sresLen >= result.length) {
514                 Log.e(TAG, "malformed response - " + tmResponse);
515                 return null;
516             }
517             String sres = makeHex(result, 1, sresLen);
518             int kcOffset = 1 + sresLen;
519             if (kcOffset >= result.length) {
520                 Log.e(TAG, "malformed response - " + tmResponse);
521                 return null;
522             }
523             int kcLen = result[kcOffset];
524             if (kcLen < 0 || kcOffset + kcLen > result.length) {
525                 Log.e(TAG, "malformed response - " + tmResponse);
526                 return null;
527             }
528             String kc = makeHex(result, 1 + kcOffset, kcLen);
529             sb.append(":" + kc + ":" + sres);
530             Log.v(TAG, "kc:" + kc + " sres:" + sres);
531         }
532 
533         return sb.toString();
534     }
535 
536     /**
537      * Calculate SRES and KC as 2G authentication.
538      *
539      * Standard       Cellular_auth     Type Command
540      *
541      * 3GPP TS 11.11  2G_authentication [RAND]
542      *                         [SRES][Cipher Key Kc]
543      *
544      * @param requestData RAND data from server.
545      * @param tm the instance of TelephonyManager.
546      * @return the response data processed by SIM. If all request data is malformed, then returns
547      * empty string. If request data is invalid, then returns null.
548      */
getGsmSimpleSimNoLengthAuthResponse(String[] requestData, TelephonyManager tm)549     public static String getGsmSimpleSimNoLengthAuthResponse(String[] requestData,
550             TelephonyManager tm) {
551         if (tm == null) {
552             Log.e(TAG, "No valid TelephonyManager");
553             return null;
554         }
555         TelephonyManager defaultDataTm = tm.createForSubscriptionId(
556                 SubscriptionManager.getDefaultDataSubscriptionId());
557         StringBuilder sb = new StringBuilder();
558         for (String challenge : requestData) {
559             if (challenge == null || challenge.isEmpty()) {
560                 continue;
561             }
562             Log.d(TAG, "RAND = " + challenge);
563 
564             byte[] rand = null;
565             try {
566                 rand = parseHexWithoutLength(challenge);
567             } catch (NumberFormatException e) {
568                 Log.e(TAG, "malformed challenge");
569                 continue;
570             }
571 
572             String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP);
573 
574             String tmResponse = defaultDataTm.getIccAuthentication(TelephonyManager.APPTYPE_SIM,
575                     TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
576             Log.v(TAG, "Raw Response - " + tmResponse);
577 
578             if (tmResponse == null || tmResponse.length() <= 4) {
579                 Log.e(TAG, "bad response - " + tmResponse);
580                 return null;
581             }
582 
583             byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
584             if (SRES_LEN + KC_LEN != result.length) {
585                 Log.e(TAG, "malformed response - " + tmResponse);
586                 return null;
587             }
588             Log.v(TAG, "Hex Response -" + makeHex(result));
589             String sres = makeHex(result, START_SRES_POS, SRES_LEN);
590             String kc = makeHex(result, START_KC_POS, KC_LEN);
591             sb.append(":" + kc + ":" + sres);
592             Log.v(TAG, "kc:" + kc + " sres:" + sres);
593         }
594 
595         return sb.toString();
596     }
597 
598     /**
599      * Data supplied when making a SIM Auth Request
600      */
601     public static class SimAuthRequestData {
SimAuthRequestData()602         public SimAuthRequestData() {}
SimAuthRequestData(int networkId, int protocol, String ssid, String[] data)603         public SimAuthRequestData(int networkId, int protocol, String ssid, String[] data) {
604             this.networkId = networkId;
605             this.protocol = protocol;
606             this.ssid = ssid;
607             this.data = data;
608         }
609 
610         public int networkId;
611         public int protocol;
612         public String ssid;
613         // EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges
614         // EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge
615         public String[] data;
616     }
617 
618     /**
619      * The response to a SIM Auth request if successful
620      */
621     public static class SimAuthResponseData {
SimAuthResponseData(String type, String response)622         public SimAuthResponseData(String type, String response) {
623             this.type = type;
624             this.response = response;
625         }
626 
627         public String type;
628         public String response;
629     }
630 
get3GAuthResponse(SimAuthRequestData requestData, TelephonyManager tm)631     public static SimAuthResponseData get3GAuthResponse(SimAuthRequestData requestData,
632             TelephonyManager tm) {
633         StringBuilder sb = new StringBuilder();
634         byte[] rand = null;
635         byte[] authn = null;
636         String resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTH;
637 
638         if (requestData.data.length == 2) {
639             try {
640                 rand = parseHex(requestData.data[0]);
641                 authn = parseHex(requestData.data[1]);
642             } catch (NumberFormatException e) {
643                 Log.e(TAG, "malformed challenge");
644             }
645         } else {
646             Log.e(TAG, "malformed challenge");
647         }
648 
649         String tmResponse = "";
650         if (rand != null && authn != null) {
651             String base64Challenge = Base64.encodeToString(concatHex(rand, authn), Base64.NO_WRAP);
652             if (tm != null) {
653                 tmResponse = tm
654                         .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId())
655                         .getIccAuthentication(TelephonyManager.APPTYPE_USIM,
656                                 TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge);
657                 Log.v(TAG, "Raw Response - " + tmResponse);
658             } else {
659                 Log.e(TAG, "No valid TelephonyManager");
660             }
661         }
662 
663         boolean goodReponse = false;
664         if (tmResponse != null && tmResponse.length() > 4) {
665             byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
666             Log.e(TAG, "Hex Response - " + makeHex(result));
667             byte tag = result[0];
668             if (tag == (byte) 0xdb) {
669                 Log.v(TAG, "successful 3G authentication ");
670                 int resLen = result[1];
671                 String res = makeHex(result, 2, resLen);
672                 int ckLen = result[resLen + 2];
673                 String ck = makeHex(result, resLen + 3, ckLen);
674                 int ikLen = result[resLen + ckLen + 3];
675                 String ik = makeHex(result, resLen + ckLen + 4, ikLen);
676                 sb.append(":" + ik + ":" + ck + ":" + res);
677                 Log.v(TAG, "ik:" + ik + "ck:" + ck + " res:" + res);
678                 goodReponse = true;
679             } else if (tag == (byte) 0xdc) {
680                 Log.e(TAG, "synchronisation failure");
681                 int autsLen = result[1];
682                 String auts = makeHex(result, 2, autsLen);
683                 resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTS;
684                 sb.append(":" + auts);
685                 Log.v(TAG, "auts:" + auts);
686                 goodReponse = true;
687             } else {
688                 Log.e(TAG, "bad response - unknown tag = " + tag);
689             }
690         } else {
691             Log.e(TAG, "bad response - " + tmResponse);
692         }
693 
694         if (goodReponse) {
695             String response = sb.toString();
696             Log.v(TAG, "Supplicant Response -" + response);
697             return new SimAuthResponseData(resType, response);
698         } else {
699             return null;
700         }
701     }
702 
703     /**
704      * Get the carrier type of current SIM.
705      *
706      * @param tm {@link TelephonyManager} instance
707      * @return carrier type of current active sim, {{@link #CARRIER_INVALID_TYPE}} if sim is not
708      * ready or {@code tm} is {@code null}
709      */
getCarrierType(@onNull TelephonyManager tm)710     public static int getCarrierType(@NonNull TelephonyManager tm) {
711         if (tm == null) {
712             return CARRIER_INVALID_TYPE;
713         }
714         TelephonyManager defaultDataTm = tm.createForSubscriptionId(
715                 SubscriptionManager.getDefaultDataSubscriptionId());
716 
717         if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
718             return CARRIER_INVALID_TYPE;
719         }
720 
721         // If two APIs return the same carrier ID, then is considered as MNO, otherwise MVNO
722         if (defaultDataTm.getCarrierIdFromSimMccMnc() == defaultDataTm.getSimCarrierId()) {
723             return CARRIER_MNO_TYPE;
724         }
725         return CARRIER_MVNO_TYPE;
726     }
727 
728     /**
729      * Returns true if at least one SIM is present on the device, false otherwise.
730      */
isSimPresent(@onnull SubscriptionManager sm)731     public static boolean isSimPresent(@Nonnull SubscriptionManager sm) {
732         return sm.getActiveSubscriptionIdList().length > 0;
733     }
734 
735     /**
736      * Decorates a pseudonym with the NAI realm, in case it wasn't provided by the server
737      *
738      * @param tm TelephonyManager instance
739      * @param pseudonym The pseudonym (temporary identity) provided by the server
740      * @return pseudonym@realm which is based on current MCC/MNC, {@code null} if SIM is
741      * not ready or absent.
742      */
decoratePseudonymWith3GppRealm(@onNull TelephonyManager tm, String pseudonym)743     public static String decoratePseudonymWith3GppRealm(@NonNull TelephonyManager tm,
744             String pseudonym) {
745         if (tm == null || TextUtils.isEmpty(pseudonym)) {
746             return null;
747         }
748         if (pseudonym.contains("@")) {
749             // Pseudonym is already decorated
750             return pseudonym;
751         }
752         TelephonyManager defaultDataTm = tm.createForSubscriptionId(
753                 SubscriptionManager.getDefaultDataSubscriptionId());
754         if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
755             return null;
756         }
757         String mccMnc = defaultDataTm.getSimOperator();
758         if (mccMnc == null || mccMnc.isEmpty()) {
759             return null;
760         }
761 
762         // Extract mcc & mnc from mccMnc
763         String mcc = mccMnc.substring(0, 3);
764         String mnc = mccMnc.substring(3);
765 
766         if (mnc.length() == 2) {
767             mnc = "0" + mnc;
768         }
769 
770         String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
771         return String.format("%s@%s", pseudonym, realm);
772     }
773 }
774