1 /*
2  * Copyright (C) 2017 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;
18 
19 import android.annotation.NonNull;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.database.ContentObserver;
25 import android.net.Uri;
26 import android.net.wifi.EAPConstants;
27 import android.net.wifi.WifiEnterpriseConfig;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.PersistableBundle;
31 import android.telephony.CarrierConfigManager;
32 import android.telephony.ImsiEncryptionInfo;
33 import android.telephony.SubscriptionInfo;
34 import android.telephony.SubscriptionManager;
35 import android.telephony.TelephonyManager;
36 import android.util.Base64;
37 import android.util.Log;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 
46 /**
47  * Class for maintaining/caching carrier Wi-Fi network configurations.
48  */
49 public class CarrierNetworkConfig {
50     private static final String TAG = "CarrierNetworkConfig";
51 
52     private static final String NETWORK_CONFIG_SEPARATOR = ",";
53     private static final int ENCODED_SSID_INDEX = 0;
54     private static final int EAP_TYPE_INDEX = 1;
55     private static final int CONFIG_ELEMENT_SIZE = 2;
56 
57     private static final Uri CONTENT_URI = Uri.parse("content://carrier_information/carrier");
58 
59     private boolean mDbg = false;
60 
61     private final Map<String, NetworkInfo> mCarrierNetworkMap;
62     private boolean mIsCarrierImsiEncryptionInfoAvailable = false;
63     private ImsiEncryptionInfo mLastImsiEncryptionInfo = null; // used for dumpsys only
64 
65     /**
66      * Enable/disable verbose logging.
67      */
enableVerboseLogging(int verbose)68     public void enableVerboseLogging(int verbose) {
69         mDbg = verbose > 0;
70     }
71 
CarrierNetworkConfig(@onNull Context context, @NonNull Looper looper, @NonNull FrameworkFacade framework)72     public CarrierNetworkConfig(@NonNull Context context, @NonNull Looper looper,
73             @NonNull FrameworkFacade framework) {
74         mCarrierNetworkMap = new HashMap<>();
75         updateNetworkConfig(context);
76 
77         // Monitor for carrier config changes.
78         IntentFilter filter = new IntentFilter();
79         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
80         context.registerReceiver(new BroadcastReceiver() {
81             @Override
82             public void onReceive(Context context, Intent intent) {
83                 updateNetworkConfig(context);
84             }
85         }, filter);
86 
87         framework.registerContentObserver(context, CONTENT_URI, false,
88                 new ContentObserver(new Handler(looper)) {
89                 @Override
90                 public void onChange(boolean selfChange) {
91                     updateNetworkConfig(context);
92                 }
93             });
94     }
95 
96     /**
97      * @return true if the given SSID is associated with a carrier network
98      */
isCarrierNetwork(String ssid)99     public boolean isCarrierNetwork(String ssid) {
100         return mCarrierNetworkMap.containsKey(ssid);
101     }
102 
103     /**
104      * @return the EAP type associated with a carrier AP, or -1 if the specified AP
105      * is not associated with a carrier network
106      */
getNetworkEapType(String ssid)107     public int getNetworkEapType(String ssid) {
108         NetworkInfo info = mCarrierNetworkMap.get(ssid);
109         return info == null ? -1 : info.mEapType;
110     }
111 
112     /**
113      * @return the name of carrier associated with a carrier AP, or null if the specified AP
114      * is not associated with a carrier network.
115      */
getCarrierName(String ssid)116     public String getCarrierName(String ssid) {
117         NetworkInfo info = mCarrierNetworkMap.get(ssid);
118         return info == null ? null : info.mCarrierName;
119     }
120 
121     /**
122      * @return True if carrier IMSI encryption info is available, False otherwise.
123      */
isCarrierEncryptionInfoAvailable()124     public boolean isCarrierEncryptionInfoAvailable() {
125         return mIsCarrierImsiEncryptionInfoAvailable;
126     }
127 
128     /**
129      * Verify whether carrier IMSI encryption info is available.
130      *
131      * @param context Current application context
132      *
133      * @return True if carrier IMSI encryption info is available, False otherwise.
134      */
verifyCarrierImsiEncryptionInfoIsAvailable(Context context)135     private boolean verifyCarrierImsiEncryptionInfoIsAvailable(Context context) {
136         // TODO(b/132188983): Inject this using WifiInjector
137         TelephonyManager telephonyManager =
138                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
139         if (telephonyManager == null) {
140             return false;
141         }
142         try {
143             mLastImsiEncryptionInfo = telephonyManager
144                     .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId())
145                     .getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN);
146             if (mLastImsiEncryptionInfo == null) {
147                 return false;
148             }
149         } catch (RuntimeException e) {
150             Log.e(TAG, "Failed to get imsi encryption info: " + e.getMessage());
151             return false;
152         }
153 
154         return true;
155     }
156 
157     /**
158      * Utility class for storing carrier network information.
159      */
160     private static class NetworkInfo {
161         final int mEapType;
162         final String mCarrierName;
163 
NetworkInfo(int eapType, String carrierName)164         NetworkInfo(int eapType, String carrierName) {
165             mEapType = eapType;
166             mCarrierName = carrierName;
167         }
168 
169         @Override
toString()170         public String toString() {
171             return new StringBuffer("NetworkInfo: eap=").append(mEapType).append(
172                     ", carrier=").append(mCarrierName).toString();
173         }
174     }
175 
176     /**
177      * Update the carrier network map based on the current carrier configuration of the active
178      * subscriptions.
179      *
180      * @param context Current application context
181      */
updateNetworkConfig(Context context)182     private void updateNetworkConfig(Context context) {
183         mIsCarrierImsiEncryptionInfoAvailable = verifyCarrierImsiEncryptionInfoIsAvailable(context);
184 
185         // Reset network map.
186         mCarrierNetworkMap.clear();
187 
188         CarrierConfigManager carrierConfigManager =
189                 (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
190         if (carrierConfigManager == null) {
191             return;
192         }
193 
194         SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService(
195                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
196         if (subscriptionManager == null) {
197             return;
198         }
199         List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
200         if (subInfoList == null) {
201             return;
202         }
203 
204         // Process the carrier config for each active subscription.
205         for (SubscriptionInfo subInfo : subInfoList) {
206             CharSequence displayNameCs = subInfo.getDisplayName();
207             String displayNameStr = displayNameCs == null ? "" : displayNameCs.toString();
208             PersistableBundle bundle = carrierConfigManager.getConfigForSubId(
209                     subInfo.getSubscriptionId());
210             processNetworkConfig(bundle, displayNameStr);
211         }
212     }
213 
214     /**
215      * Process the carrier network config, the network config string is formatted as follow:
216      *
217      * "[Base64 Encoded SSID],[EAP Type]"
218      * Where EAP Type is the standard EAP method number, refer to
219      * http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml for more info.
220 
221      * @param carrierConfig The bundle containing the carrier configuration
222      * @param carrierName The display name of the associated carrier
223      */
processNetworkConfig(PersistableBundle carrierConfig, String carrierName)224     private void processNetworkConfig(PersistableBundle carrierConfig, String carrierName) {
225         if (carrierConfig == null) {
226             return;
227         }
228         String[] networkConfigs = carrierConfig.getStringArray(
229                 CarrierConfigManager.KEY_CARRIER_WIFI_STRING_ARRAY);
230         if (mDbg) {
231             Log.v(TAG, "processNetworkConfig: networkConfigs="
232                     + Arrays.deepToString(networkConfigs));
233         }
234         if (networkConfigs == null) {
235             return;
236         }
237 
238         for (String networkConfig : networkConfigs) {
239             String[] configArr = networkConfig.split(NETWORK_CONFIG_SEPARATOR);
240             if (configArr.length != CONFIG_ELEMENT_SIZE) {
241                 Log.e(TAG, "Ignore invalid config: " + networkConfig);
242                 continue;
243             }
244             try {
245 
246                 String ssid = new String(Base64.decode(
247                         configArr[ENCODED_SSID_INDEX], Base64.NO_WRAP));
248                 int eapType = parseEapType(Integer.parseInt(configArr[EAP_TYPE_INDEX]));
249 
250                 // Verify EAP type, must be a SIM based EAP type.
251                 if (eapType == -1) {
252                     Log.e(TAG, "Invalid EAP type: " + configArr[EAP_TYPE_INDEX]);
253                     continue;
254                 }
255                 mCarrierNetworkMap.put(ssid, new NetworkInfo(eapType, carrierName));
256             } catch (NumberFormatException e) {
257                 Log.e(TAG, "Failed to parse EAP type: '" + configArr[EAP_TYPE_INDEX] + "' "
258                         + e.getMessage());
259             } catch (IllegalArgumentException e) {
260                 Log.e(TAG, "Failed to decode SSID: '" + configArr[ENCODED_SSID_INDEX] + "' "
261                         + e.getMessage());
262             }
263         }
264     }
265 
266     /**
267      * Convert a standard SIM-based EAP type (SIM, AKA, AKA') to the internal EAP type as defined in
268      * {@link WifiEnterpriseConfig.Eap}. -1 will be returned if the given EAP type is not
269      * SIM-based.
270      *
271      * @return SIM-based EAP type as defined in {@link WifiEnterpriseConfig.Eap}, or -1 if not
272      * SIM-based EAP type
273      */
parseEapType(int eapType)274     private static int parseEapType(int eapType) {
275         if (eapType == EAPConstants.EAP_SIM) {
276             return WifiEnterpriseConfig.Eap.SIM;
277         } else if (eapType == EAPConstants.EAP_AKA) {
278             return WifiEnterpriseConfig.Eap.AKA;
279         } else if (eapType == EAPConstants.EAP_AKA_PRIME) {
280             return WifiEnterpriseConfig.Eap.AKA_PRIME;
281         }
282         return -1;
283     }
284 
285     /** Dump state. */
dump(FileDescriptor fd, PrintWriter pw, String[] args)286     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
287         pw.println(TAG + ": ");
288         pw.println("mCarrierNetworkMap=" + mCarrierNetworkMap);
289         pw.println("mIsCarrierImsiEncryptionInfoAvailable="
290                 + mIsCarrierImsiEncryptionInfoAvailable);
291         pw.println("mLastImsiEncryptionInfo=" + mLastImsiEncryptionInfo);
292     }
293 }
294