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