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;
18
19import android.content.pm.UserInfo;
20import android.net.IpConfiguration;
21import android.net.wifi.WifiConfiguration;
22import android.net.wifi.WifiEnterpriseConfig;
23import android.net.wifi.WifiScanner;
24import android.os.UserHandle;
25
26import com.android.internal.annotations.VisibleForTesting;
27
28import java.security.cert.X509Certificate;
29import java.util.Arrays;
30import java.util.Comparator;
31import java.util.List;
32import java.util.Objects;
33
34/**
35 * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations.
36 * Currently contains:
37 *   > Helper method to check if the WifiConfiguration object is visible to the provided users.
38 *   > Helper methods to identify the encryption of a WifiConfiguration object.
39 */
40public class WifiConfigurationUtil {
41    /**
42     * Check whether a network configuration is visible to a user or any of its managed profiles.
43     *
44     * @param config   the network configuration whose visibility should be checked
45     * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
46     *                 via {@link android.os.UserManager#getProfiles})
47     * @return whether the network configuration is visible to the user or any of its managed
48     * profiles
49     */
50    public static boolean isVisibleToAnyProfile(WifiConfiguration config, List<UserInfo> profiles) {
51        return (config.shared || doesUidBelongToAnyProfile(config.creatorUid, profiles));
52    }
53
54    /**
55     * Check whether a uid belong to a user or any of its managed profiles.
56     *
57     * @param uid      uid of the app.
58     * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
59     *                 via {@link android.os.UserManager#getProfiles})
60     * @return whether the uid belongs to the user or any of its managed profiles.
61     */
62    public static boolean doesUidBelongToAnyProfile(int uid, List<UserInfo> profiles) {
63        final int userId = UserHandle.getUserId(uid);
64        for (UserInfo profile : profiles) {
65            if (profile.id == userId) {
66                return true;
67            }
68        }
69        return false;
70    }
71
72    /**
73     * Checks if the provided |wepKeys| array contains any non-null value;
74     */
75    public static boolean hasAnyValidWepKey(String[] wepKeys) {
76        for (int i = 0; i < wepKeys.length; i++) {
77            if (wepKeys[i] != null) {
78                return true;
79            }
80        }
81        return false;
82    }
83
84    /**
85     * Helper method to check if the provided |config| corresponds to a PSK network or not.
86     */
87    public static boolean isConfigForPskNetwork(WifiConfiguration config) {
88        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK);
89    }
90
91    /**
92     * Helper method to check if the provided |config| corresponds to a EAP network or not.
93     */
94    public static boolean isConfigForEapNetwork(WifiConfiguration config) {
95        return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
96                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
97    }
98
99    /**
100     * Helper method to check if the provided |config| corresponds to a WEP network or not.
101     */
102    public static boolean isConfigForWepNetwork(WifiConfiguration config) {
103        return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
104                && hasAnyValidWepKey(config.wepKeys));
105    }
106
107    /**
108     * Helper method to check if the provided |config| corresponds to an open network or not.
109     */
110    public static boolean isConfigForOpenNetwork(WifiConfiguration config) {
111        return !(isConfigForWepNetwork(config) || isConfigForPskNetwork(config)
112                || isConfigForEapNetwork(config));
113    }
114
115    /**
116     * Compare existing and new WifiConfiguration objects after a network update and return if
117     * IP parameters have changed or not.
118     *
119     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
120     * @param newConfig      New WifiConfiguration object corresponding to the network.
121     * @return true if IP parameters have changed, false otherwise.
122     */
123    public static boolean hasIpChanged(WifiConfiguration existingConfig,
124            WifiConfiguration newConfig) {
125        if (existingConfig.getIpAssignment() != newConfig.getIpAssignment()) {
126            return true;
127        }
128        if (newConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
129            return !Objects.equals(existingConfig.getStaticIpConfiguration(),
130                    newConfig.getStaticIpConfiguration());
131        }
132        return false;
133    }
134
135    /**
136     * Compare existing and new WifiConfiguration objects after a network update and return if
137     * proxy parameters have changed or not.
138     *
139     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
140     * @param newConfig      New WifiConfiguration object corresponding to the network.
141     * @return true if proxy parameters have changed, false if no existing config and proxy settings
142     * are NONE, false otherwise.
143     */
144    public static boolean hasProxyChanged(WifiConfiguration existingConfig,
145            WifiConfiguration newConfig) {
146        if (existingConfig == null) {
147            return newConfig.getProxySettings() != IpConfiguration.ProxySettings.NONE;
148        }
149        if (newConfig.getProxySettings() != existingConfig.getProxySettings()) {
150            return true;
151        }
152        return !Objects.equals(existingConfig.getHttpProxy(), newConfig.getHttpProxy());
153    }
154
155    /**
156     * Compare existing and new WifiEnterpriseConfig objects after a network update and return if
157     * credential parameters have changed or not.
158     *
159     * @param existingEnterpriseConfig Existing WifiConfiguration object corresponding to the
160     *                                 network.
161     * @param newEnterpriseConfig      New WifiConfiguration object corresponding to the network.
162     * @return true if credentials have changed, false otherwise.
163     */
164    @VisibleForTesting
165    public static boolean hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig,
166            WifiEnterpriseConfig newEnterpriseConfig) {
167        if (existingEnterpriseConfig != null && newEnterpriseConfig != null) {
168            if (existingEnterpriseConfig.getEapMethod() != newEnterpriseConfig.getEapMethod()) {
169                return true;
170            }
171            if (existingEnterpriseConfig.getPhase2Method()
172                    != newEnterpriseConfig.getPhase2Method()) {
173                return true;
174            }
175            X509Certificate[] existingCaCerts = existingEnterpriseConfig.getCaCertificates();
176            X509Certificate[] newCaCerts = newEnterpriseConfig.getCaCertificates();
177            if (!Arrays.equals(existingCaCerts, newCaCerts)) {
178                return true;
179            }
180        } else {
181            // One of the configs may have an enterpriseConfig
182            if (existingEnterpriseConfig != null || newEnterpriseConfig != null) {
183                return true;
184            }
185        }
186        return false;
187    }
188
189    /**
190     * Compare existing and new WifiConfiguration objects after a network update and return if
191     * credential parameters have changed or not.
192     *
193     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
194     * @param newConfig      New WifiConfiguration object corresponding to the network.
195     * @return true if credentials have changed, false otherwise.
196     */
197    public static boolean hasCredentialChanged(WifiConfiguration existingConfig,
198            WifiConfiguration newConfig) {
199        if (!Objects.equals(existingConfig.allowedKeyManagement,
200                newConfig.allowedKeyManagement)) {
201            return true;
202        }
203        if (!Objects.equals(existingConfig.allowedProtocols, newConfig.allowedProtocols)) {
204            return true;
205        }
206        if (!Objects.equals(existingConfig.allowedAuthAlgorithms,
207                newConfig.allowedAuthAlgorithms)) {
208            return true;
209        }
210        if (!Objects.equals(existingConfig.allowedPairwiseCiphers,
211                newConfig.allowedPairwiseCiphers)) {
212            return true;
213        }
214        if (!Objects.equals(existingConfig.allowedGroupCiphers,
215                newConfig.allowedGroupCiphers)) {
216            return true;
217        }
218        if (!Objects.equals(existingConfig.preSharedKey, newConfig.preSharedKey)) {
219            return true;
220        }
221        if (!Arrays.equals(existingConfig.wepKeys, newConfig.wepKeys)) {
222            return true;
223        }
224        if (existingConfig.wepTxKeyIndex != newConfig.wepTxKeyIndex) {
225            return true;
226        }
227        if (existingConfig.hiddenSSID != newConfig.hiddenSSID) {
228            return true;
229        }
230        if (hasEnterpriseConfigChanged(existingConfig.enterpriseConfig,
231                newConfig.enterpriseConfig)) {
232            return true;
233        }
234        return false;
235    }
236
237    /**
238     * Check if the provided two networks are the same.
239     *
240     * @param config      Configuration corresponding to a network.
241     * @param config1      Configuration corresponding to another network.
242     *
243     * @return true if |config| and |config1| are the same network.
244     *         false otherwise.
245     */
246    public static boolean isSameNetwork(WifiConfiguration config, WifiConfiguration config1) {
247        if (config == null && config1 == null) {
248            return true;
249        }
250        if (config == null || config1 == null) {
251            return false;
252        }
253        if (config.networkId != config1.networkId) {
254            return false;
255        }
256        if (!Objects.equals(config.SSID, config1.SSID)) {
257            return false;
258        }
259        String networkSelectionBSSID = config.getNetworkSelectionStatus()
260                .getNetworkSelectionBSSID();
261        String networkSelectionBSSID1 = config1.getNetworkSelectionStatus()
262                .getNetworkSelectionBSSID();
263        if (!Objects.equals(networkSelectionBSSID, networkSelectionBSSID1)) {
264            return false;
265        }
266        if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
267            return false;
268        }
269        return true;
270    }
271
272    /**
273     * Create a PnoNetwork object from the provided WifiConfiguration.
274     *
275     * @param config      Configuration corresponding to the network.
276     * @param newPriority New priority to be assigned to the network.
277     * @return PnoNetwork object corresponding to the network.
278     */
279    public static WifiScanner.PnoSettings.PnoNetwork createPnoNetwork(
280            WifiConfiguration config, int newPriority) {
281        WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
282                new WifiScanner.PnoSettings.PnoNetwork(config.SSID);
283        if (config.hiddenSSID) {
284            pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN;
285        }
286        pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND;
287        pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND;
288        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
289            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK;
290        } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
291                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
292            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL;
293        } else {
294            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN;
295        }
296        return pnoNetwork;
297    }
298
299
300    /**
301     * General WifiConfiguration list sorting algorithm:
302     * 1, Place the fully enabled networks first.
303     * 2. Next place all the temporarily disabled networks.
304     * 3. Place the permanently disabled networks last (Permanently disabled networks are removed
305     * before WifiConfigManager uses this comparator today!).
306     *
307     * Among the networks with the same status, sort them in the order determined by the return of
308     * {@link #compareNetworksWithSameStatus(WifiConfiguration, WifiConfiguration)} method
309     * implementation.
310     */
311    public abstract static class WifiConfigurationComparator implements
312            Comparator<WifiConfiguration> {
313        private static final int ENABLED_NETWORK_SCORE = 3;
314        private static final int TEMPORARY_DISABLED_NETWORK_SCORE = 2;
315        private static final int PERMANENTLY_DISABLED_NETWORK_SCORE = 1;
316
317        @Override
318        public int compare(WifiConfiguration a, WifiConfiguration b) {
319            int configAScore = getNetworkStatusScore(a);
320            int configBScore = getNetworkStatusScore(b);
321            if (configAScore == configBScore) {
322                return compareNetworksWithSameStatus(a, b);
323            } else {
324                return Integer.compare(configBScore, configAScore);
325            }
326        }
327
328        // This needs to be implemented by the connected/disconnected PNO list comparator.
329        abstract int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b);
330
331        /**
332         * Returns an integer representing a score for each configuration. The scores are assigned
333         * based on the status of the configuration. The scores are assigned according to the order:
334         * Fully enabled network > Temporarily disabled network > Permanently disabled network.
335         */
336        private int getNetworkStatusScore(WifiConfiguration config) {
337            if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
338                return ENABLED_NETWORK_SCORE;
339            } else if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
340                return TEMPORARY_DISABLED_NETWORK_SCORE;
341            } else {
342                return PERMANENTLY_DISABLED_NETWORK_SCORE;
343            }
344        }
345    }
346}
347