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
17package com.android.server.wifi.util;
18
19import android.net.wifi.WifiConfiguration;
20import android.net.wifi.WifiEnterpriseConfig;
21import android.telephony.TelephonyManager;
22import android.util.Base64;
23import android.util.Log;
24
25import com.android.server.wifi.WifiNative;
26
27/**
28 * Utilities for the Wifi Service to interact with telephony.
29 */
30public 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