Home | History | Annotate | Download | only in util
      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.net.wifi.WifiConfiguration;
     20 import android.net.wifi.WifiEnterpriseConfig;
     21 import android.telephony.TelephonyManager;
     22 import android.util.Base64;
     23 import android.util.Log;
     24 
     25 import com.android.server.wifi.WifiNative;
     26 
     27 /**
     28  * Utilities for the Wifi Service to interact with telephony.
     29  */
     30 public class TelephonyUtil {
     31     public static final String TAG = "TelephonyUtil";
     32 
     33     /**
     34      * Get the identity for the current SIM or null if the SIM is not available
     35      *
     36      * @param tm TelephonyManager instance
     37      * @param config WifiConfiguration that indicates what sort of authentication is necessary
     38      * @return String with the identity or none if the SIM is not available or config is invalid
     39      */
     40     public static String getSimIdentity(TelephonyManager tm, WifiConfiguration config) {
     41         if (tm == null) {
     42             Log.e(TAG, "No valid TelephonyManager");
     43             return null;
     44         }
     45         String imsi = tm.getSubscriberId();
     46         String mccMnc = "";
     47 
     48         if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) {
     49             mccMnc = tm.getSimOperator();
     50         }
     51 
     52         return buildIdentity(getSimMethodForConfig(config), imsi, mccMnc);
     53     }
     54 
     55     /**
     56      * create Permanent Identity base on IMSI,
     57      *
     58      * rfc4186 & rfc4187:
     59      * identity = usernam@realm
     60      * with username = prefix | IMSI
     61      * and realm is derived MMC/MNC tuple according 3GGP spec(TS23.003)
     62      */
     63     private static String buildIdentity(int eapMethod, String imsi, String mccMnc) {
     64         if (imsi == null || imsi.isEmpty()) {
     65             Log.e(TAG, "No IMSI or IMSI is null");
     66             return null;
     67         }
     68 
     69         String prefix;
     70         if (eapMethod == WifiEnterpriseConfig.Eap.SIM) {
     71             prefix = "1";
     72         } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA) {
     73             prefix = "0";
     74         } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME) {
     75             prefix = "6";
     76         } else {
     77             Log.e(TAG, "Invalid EAP method");
     78             return null;
     79         }
     80 
     81         /* extract mcc & mnc from mccMnc */
     82         String mcc;
     83         String mnc;
     84         if (mccMnc != null && !mccMnc.isEmpty()) {
     85             mcc = mccMnc.substring(0, 3);
     86             mnc = mccMnc.substring(3);
     87             if (mnc.length() == 2) {
     88                 mnc = "0" + mnc;
     89             }
     90         } else {
     91             // extract mcc & mnc from IMSI, assume mnc size is 3
     92             mcc = imsi.substring(0, 3);
     93             mnc = imsi.substring(3, 6);
     94         }
     95 
     96         return prefix + imsi + "@wlan.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org";
     97     }
     98 
     99     /**
    100      * Return the associated SIM method for the configuration.
    101      *
    102      * @param config WifiConfiguration corresponding to the network.
    103      * @return the outer EAP method associated with this SIM configuration.
    104      */
    105     private static int getSimMethodForConfig(WifiConfiguration config) {
    106         if (config == null || config.enterpriseConfig == null) {
    107             return WifiEnterpriseConfig.Eap.NONE;
    108         }
    109         int eapMethod = config.enterpriseConfig.getEapMethod();
    110         if (eapMethod == WifiEnterpriseConfig.Eap.PEAP) {
    111             // Translate known inner eap methods into an equivalent outer eap method.
    112             switch (config.enterpriseConfig.getPhase2Method()) {
    113                 case WifiEnterpriseConfig.Phase2.SIM:
    114                     eapMethod = WifiEnterpriseConfig.Eap.SIM;
    115                     break;
    116                 case WifiEnterpriseConfig.Phase2.AKA:
    117                     eapMethod = WifiEnterpriseConfig.Eap.AKA;
    118                     break;
    119                 case WifiEnterpriseConfig.Phase2.AKA_PRIME:
    120                     eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
    121                     break;
    122             }
    123         }
    124 
    125         return isSimEapMethod(eapMethod) ? eapMethod : WifiEnterpriseConfig.Eap.NONE;
    126     }
    127 
    128     /**
    129      * Checks if the network is a SIM config.
    130      *
    131      * @param config Config corresponding to the network.
    132      * @return true if it is a SIM config, false otherwise.
    133      */
    134     public static boolean isSimConfig(WifiConfiguration config) {
    135         return getSimMethodForConfig(config) != WifiEnterpriseConfig.Eap.NONE;
    136     }
    137 
    138     /**
    139      * Checks if the EAP outer method is SIM related.
    140      *
    141      * @param eapMethod WifiEnterpriseConfig Eap method.
    142      * @return true if this EAP outer method is SIM-related, false otherwise.
    143      */
    144     public static boolean isSimEapMethod(int eapMethod) {
    145         return eapMethod == WifiEnterpriseConfig.Eap.SIM
    146                 || eapMethod == WifiEnterpriseConfig.Eap.AKA
    147                 || eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME;
    148     }
    149 
    150     // TODO replace some of this code with Byte.parseByte
    151     private static int parseHex(char ch) {
    152         if ('0' <= ch && ch <= '9') {
    153             return ch - '0';
    154         } else if ('a' <= ch && ch <= 'f') {
    155             return ch - 'a' + 10;
    156         } else if ('A' <= ch && ch <= 'F') {
    157             return ch - 'A' + 10;
    158         } else {
    159             throw new NumberFormatException("" + ch + " is not a valid hex digit");
    160         }
    161     }
    162 
    163     private static byte[] parseHex(String hex) {
    164         /* This only works for good input; don't throw bad data at it */
    165         if (hex == null) {
    166             return new byte[0];
    167         }
    168 
    169         if (hex.length() % 2 != 0) {
    170             throw new NumberFormatException(hex + " is not a valid hex string");
    171         }
    172 
    173         byte[] result = new byte[(hex.length()) / 2 + 1];
    174         result[0] = (byte) ((hex.length()) / 2);
    175         for (int i = 0, j = 1; i < hex.length(); i += 2, j++) {
    176             int val = parseHex(hex.charAt(i)) * 16 + parseHex(hex.charAt(i + 1));
    177             byte b = (byte) (val & 0xFF);
    178             result[j] = b;
    179         }
    180 
    181         return result;
    182     }
    183 
    184     private static String makeHex(byte[] bytes) {
    185         StringBuilder sb = new StringBuilder();
    186         for (byte b : bytes) {
    187             sb.append(String.format("%02x", b));
    188         }
    189         return sb.toString();
    190     }
    191 
    192     private static String makeHex(byte[] bytes, int from, int len) {
    193         StringBuilder sb = new StringBuilder();
    194         for (int i = 0; i < len; i++) {
    195             sb.append(String.format("%02x", bytes[from + i]));
    196         }
    197         return sb.toString();
    198     }
    199 
    200     private static byte[] concatHex(byte[] array1, byte[] array2) {
    201 
    202         int len = array1.length + array2.length;
    203 
    204         byte[] result = new byte[len];
    205 
    206         int index = 0;
    207         if (array1.length != 0) {
    208             for (byte b : array1) {
    209                 result[index] = b;
    210                 index++;
    211             }
    212         }
    213 
    214         if (array2.length != 0) {
    215             for (byte b : array2) {
    216                 result[index] = b;
    217                 index++;
    218             }
    219         }
    220 
    221         return result;
    222     }
    223 
    224     public static String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) {
    225         if (tm == null) {
    226             Log.e(TAG, "No valid TelephonyManager");
    227             return null;
    228         }
    229         StringBuilder sb = new StringBuilder();
    230         for (String challenge : requestData) {
    231             if (challenge == null || challenge.isEmpty()) {
    232                 continue;
    233             }
    234             Log.d(TAG, "RAND = " + challenge);
    235 
    236             byte[] rand = null;
    237             try {
    238                 rand = parseHex(challenge);
    239             } catch (NumberFormatException e) {
    240                 Log.e(TAG, "malformed challenge");
    241                 continue;
    242             }
    243 
    244             String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP);
    245 
    246             // Try USIM first for authentication.
    247             String tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
    248                     TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
    249             if (tmResponse == null) {
    250                 // Then, in case of failure, issue may be due to sim type, retry as a simple sim
    251                 tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_SIM,
    252                         TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
    253             }
    254             Log.v(TAG, "Raw Response - " + tmResponse);
    255 
    256             if (tmResponse == null || tmResponse.length() <= 4) {
    257                 Log.e(TAG, "bad response - " + tmResponse);
    258                 return null;
    259             }
    260 
    261             byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
    262             Log.v(TAG, "Hex Response -" + makeHex(result));
    263             int sresLen = result[0];
    264             if (sresLen >= result.length) {
    265                 Log.e(TAG, "malfomed response - " + tmResponse);
    266                 return null;
    267             }
    268             String sres = makeHex(result, 1, sresLen);
    269             int kcOffset = 1 + sresLen;
    270             if (kcOffset >= result.length) {
    271                 Log.e(TAG, "malfomed response - " + tmResponse);
    272                 return null;
    273             }
    274             int kcLen = result[kcOffset];
    275             if (kcOffset + kcLen > result.length) {
    276                 Log.e(TAG, "malfomed response - " + tmResponse);
    277                 return null;
    278             }
    279             String kc = makeHex(result, 1 + kcOffset, kcLen);
    280             sb.append(":" + kc + ":" + sres);
    281             Log.v(TAG, "kc:" + kc + " sres:" + sres);
    282         }
    283 
    284         return sb.toString();
    285     }
    286 
    287     /**
    288      * Data supplied when making a SIM Auth Request
    289      */
    290     public static class SimAuthRequestData {
    291         public SimAuthRequestData() {}
    292         public SimAuthRequestData(int networkId, int protocol, String ssid, String[] data) {
    293             this.networkId = networkId;
    294             this.protocol = protocol;
    295             this.ssid = ssid;
    296             this.data = data;
    297         }
    298 
    299         public int networkId;
    300         public int protocol;
    301         public String ssid;
    302         // EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges
    303         // EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge
    304         public String[] data;
    305     }
    306 
    307     /**
    308      * The response to a SIM Auth request if successful
    309      */
    310     public static class SimAuthResponseData {
    311         public SimAuthResponseData(String type, String response) {
    312             this.type = type;
    313             this.response = response;
    314         }
    315 
    316         public String type;
    317         public String response;
    318     }
    319 
    320     public static SimAuthResponseData get3GAuthResponse(SimAuthRequestData requestData,
    321             TelephonyManager tm) {
    322         StringBuilder sb = new StringBuilder();
    323         byte[] rand = null;
    324         byte[] authn = null;
    325         String resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTH;
    326 
    327         if (requestData.data.length == 2) {
    328             try {
    329                 rand = parseHex(requestData.data[0]);
    330                 authn = parseHex(requestData.data[1]);
    331             } catch (NumberFormatException e) {
    332                 Log.e(TAG, "malformed challenge");
    333             }
    334         } else {
    335             Log.e(TAG, "malformed challenge");
    336         }
    337 
    338         String tmResponse = "";
    339         if (rand != null && authn != null) {
    340             String base64Challenge = Base64.encodeToString(concatHex(rand, authn), Base64.NO_WRAP);
    341             if (tm != null) {
    342                 tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
    343                         TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge);
    344                 Log.v(TAG, "Raw Response - " + tmResponse);
    345             } else {
    346                 Log.e(TAG, "No valid TelephonyManager");
    347             }
    348         }
    349 
    350         boolean goodReponse = false;
    351         if (tmResponse != null && tmResponse.length() > 4) {
    352             byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
    353             Log.e(TAG, "Hex Response - " + makeHex(result));
    354             byte tag = result[0];
    355             if (tag == (byte) 0xdb) {
    356                 Log.v(TAG, "successful 3G authentication ");
    357                 int resLen = result[1];
    358                 String res = makeHex(result, 2, resLen);
    359                 int ckLen = result[resLen + 2];
    360                 String ck = makeHex(result, resLen + 3, ckLen);
    361                 int ikLen = result[resLen + ckLen + 3];
    362                 String ik = makeHex(result, resLen + ckLen + 4, ikLen);
    363                 sb.append(":" + ik + ":" + ck + ":" + res);
    364                 Log.v(TAG, "ik:" + ik + "ck:" + ck + " res:" + res);
    365                 goodReponse = true;
    366             } else if (tag == (byte) 0xdc) {
    367                 Log.e(TAG, "synchronisation failure");
    368                 int autsLen = result[1];
    369                 String auts = makeHex(result, 2, autsLen);
    370                 resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTS;
    371                 sb.append(":" + auts);
    372                 Log.v(TAG, "auts:" + auts);
    373                 goodReponse = true;
    374             } else {
    375                 Log.e(TAG, "bad response - unknown tag = " + tag);
    376             }
    377         } else {
    378             Log.e(TAG, "bad response - " + tmResponse);
    379         }
    380 
    381         if (goodReponse) {
    382             String response = sb.toString();
    383             Log.v(TAG, "Supplicant Response -" + response);
    384             return new SimAuthResponseData(resType, response);
    385         } else {
    386             return null;
    387         }
    388     }
    389 }
    390