1/*
2 * Copyright (C) 2010, 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.connectivitymanagertest;
18
19import android.net.IpConfiguration.IpAssignment;
20import android.net.IpConfiguration.ProxySettings;
21import android.net.LinkAddress;
22import android.net.StaticIpConfiguration;
23import android.net.wifi.WifiConfiguration;
24import android.net.wifi.WifiConfiguration.AuthAlgorithm;
25import android.net.wifi.WifiConfiguration.KeyMgmt;
26import android.net.wifi.WifiEnterpriseConfig;
27
28import org.json.JSONArray;
29import org.json.JSONException;
30import org.json.JSONObject;
31
32import java.net.InetAddress;
33import java.net.UnknownHostException;
34import java.util.ArrayList;
35import java.util.List;
36
37/**
38 * Helper for dealing with creating {@link WifiConfiguration} objects.
39 */
40public class WifiConfigurationHelper {
41    private static final int NONE = 0;
42    private static final int WEP = 1;
43    private static final int PSK = 2;
44    private static final int EAP = 3;
45
46    /**
47     * Private constructor since this a static class.
48     */
49    private WifiConfigurationHelper() {}
50
51    /**
52     * Create a {@link WifiConfiguration} for an open network
53     *
54     * @param ssid The SSID of the wifi network
55     * @return The {@link WifiConfiguration}
56     */
57    public static WifiConfiguration createOpenConfig(String ssid) {
58        WifiConfiguration config = createGenericConfig(ssid);
59
60        config.allowedKeyManagement.set(KeyMgmt.NONE);
61        return config;
62    }
63
64    /**
65     * Create a {@link WifiConfiguration} for a WEP secured network
66     *
67     * @param ssid The SSID of the wifi network
68     * @param password Either a 10, 26, or 58 character hex string or the plain text password
69     * @return The {@link WifiConfiguration}
70     */
71    public static WifiConfiguration createWepConfig(String ssid, String password) {
72        WifiConfiguration config = createGenericConfig(ssid);
73
74        if (isHex(password, 10) || isHex(password, 26) || isHex(password, 58)) {
75            config.wepKeys[0] = password;
76        } else {
77            config.wepKeys[0] = quotedString(password);
78        }
79
80        config.allowedKeyManagement.set(KeyMgmt.NONE);
81        config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
82        config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
83        return config;
84    }
85
86    /**
87     * Create a {@link WifiConfiguration} for a PSK secured network
88     *
89     * @param ssid The SSID of the wifi network
90     * @param password Either a 64 character hex string or the plain text password
91     * @return The {@link WifiConfiguration}
92     */
93    public static WifiConfiguration createPskConfig(String ssid, String password) {
94        WifiConfiguration config = createGenericConfig(ssid);
95
96        if (isHex(password, 64)) {
97            config.preSharedKey = password;
98        } else {
99            config.preSharedKey = quotedString(password);
100        }
101        config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
102        return config;
103    }
104
105    /**
106     * Create a {@link WifiConfiguration} for an EAP secured network
107     *
108     * @param ssid The SSID of the wifi network
109     * @param password The password
110     * @param eapMethod The EAP method
111     * @param phase2 The phase 2 method or null
112     * @param identity The identity or null
113     * @param anonymousIdentity The anonymous identity or null
114     * @param caCert The CA certificate or null
115     * @param clientCert The client certificate or null
116     * @return The {@link WifiConfiguration}
117     */
118    public static WifiConfiguration createEapConfig(String ssid, String password, int eapMethod,
119            Integer phase2, String identity, String anonymousIdentity, String caCert,
120            String clientCert) {
121        WifiConfiguration config = new WifiConfiguration();
122        config.SSID = quotedString(ssid);
123
124        config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
125        config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
126
127        // Set defaults
128        if (phase2 == null) phase2 = WifiEnterpriseConfig.Phase2.NONE;
129        if (identity == null) identity = "";
130        if (anonymousIdentity == null) anonymousIdentity = "";
131        if (caCert == null) caCert = "";
132        if (clientCert == null) clientCert = "";
133
134        config.enterpriseConfig.setPassword(password);
135        config.enterpriseConfig.setEapMethod(eapMethod);
136        config.enterpriseConfig.setPhase2Method(phase2);
137        config.enterpriseConfig.setIdentity(identity);
138        config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
139        config.enterpriseConfig.setCaCertificateAlias(caCert);
140        config.enterpriseConfig.setClientCertificateAlias(clientCert);
141        return config;
142    }
143
144    /**
145     * Create a generic {@link WifiConfiguration} used by the other create methods.
146     */
147    private static WifiConfiguration createGenericConfig(String ssid) {
148        WifiConfiguration config = new WifiConfiguration();
149        config.SSID = quotedString(ssid);
150        config.setIpAssignment(IpAssignment.DHCP);
151        config.setProxySettings(ProxySettings.NONE);
152        return config;
153    }
154
155    /**
156     * Parse a JSON string for WiFi configurations stored as a JSON string.
157     * <p>
158     * This json string should be a list of dictionaries, with each dictionary containing a single
159     * wifi configuration. The wifi configuration requires the fields "ssid" and "security" with
160     * security being one of NONE, WEP, PSK, or EAP. If WEP, PSK, or EAP are selected, the field
161     * "password" must also be provided.  If EAP is selected, then the fiels "eap", "phase2",
162     * "identity", "ananymous_identity", "ca_cert", and "client_cert" are also required. Lastly,
163     * static IP settings are also supported.  If the field "ip" is set, then the fields "gateway",
164     * "prefix_length", "dns1", and "dns2" are required.
165     * </p>
166     * @throws IllegalArgumentException if the input string was not valid JSON or if any mandatory
167     * fields are missing.
168     */
169    public static List<WifiConfiguration> parseJson(String in) {
170        try {
171            JSONArray jsonConfigs = new JSONArray(in);
172            List<WifiConfiguration> wifiConfigs = new ArrayList<>(jsonConfigs.length());
173
174            for (int i = 0; i < jsonConfigs.length(); i++) {
175                JSONObject jsonConfig = jsonConfigs.getJSONObject(i);
176
177                wifiConfigs.add(getWifiConfiguration(jsonConfig));
178            }
179            return wifiConfigs;
180        } catch (JSONException e) {
181            throw new IllegalArgumentException(e);
182        }
183    }
184
185    /**
186     * Parse a {@link JSONObject} and return the wifi configuration.
187     *
188     * @throws IllegalArgumentException if any mandatory fields are missing.
189     */
190    private static WifiConfiguration getWifiConfiguration(JSONObject jsonConfig)
191            throws JSONException {
192        String ssid = jsonConfig.getString("ssid");
193        String password = null;
194        WifiConfiguration config;
195
196        int securityType = getSecurityType(jsonConfig.getString("security"));
197        switch (securityType) {
198            case NONE:
199                config = createOpenConfig(ssid);
200                break;
201            case WEP:
202                password = jsonConfig.getString("password");
203                config = createWepConfig(ssid, password);
204                break;
205            case PSK:
206                password = jsonConfig.getString("password");
207                config = createPskConfig(ssid, password);
208                break;
209            case EAP:
210                password = jsonConfig.getString("password");
211                int eapMethod = getEapMethod(jsonConfig.getString("eap"));
212                Integer phase2 = null;
213                if (jsonConfig.has("phase2")) {
214                    phase2 = getPhase2(jsonConfig.getString("phase2"));
215                }
216                String identity = null;
217                if (jsonConfig.has("identity")) {
218                    identity = jsonConfig.getString("identity");
219                }
220                String anonymousIdentity = null;
221                if (jsonConfig.has("anonymous_identity")) {
222                    anonymousIdentity = jsonConfig.getString("anonymous_identity");
223                }
224                String caCert = null;
225                if (jsonConfig.has("ca_cert")) {
226                    caCert = (jsonConfig.getString("ca_cert"));
227                }
228                String clientCert = null;
229                if (jsonConfig.has("client_cert")) {
230                    clientCert = jsonConfig.getString("client_cert");
231                }
232                config = createEapConfig(ssid, password, eapMethod, phase2, identity,
233                        anonymousIdentity, caCert, clientCert);
234                break;
235            default:
236                // Should never reach here as getSecurityType will already throw an exception
237                throw new IllegalArgumentException();
238        }
239
240        if (jsonConfig.has("ip")) {
241            StaticIpConfiguration staticIpConfig = new StaticIpConfiguration();
242
243            InetAddress ipAddress = getInetAddress(jsonConfig.getString("ip"));
244            int prefixLength = getPrefixLength(jsonConfig.getInt("prefix_length"));
245            staticIpConfig.ipAddress = new LinkAddress(ipAddress, prefixLength);
246            staticIpConfig.gateway = getInetAddress(jsonConfig.getString("gateway"));
247            staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns1")));
248            staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns2")));
249
250            config.setIpAssignment(IpAssignment.STATIC);
251            config.setStaticIpConfiguration(staticIpConfig);
252        } else {
253            config.setIpAssignment(IpAssignment.DHCP);
254        }
255
256        config.setProxySettings(ProxySettings.NONE);
257        return config;
258    }
259
260    private static String quotedString(String s) {
261        return String.format("\"%s\"", s);
262    }
263
264    /**
265     * Get the security type from a string.
266     *
267     * @throws IllegalArgumentException if the string is not a supported security type.
268     */
269    private static int getSecurityType(String security) {
270        if ("NONE".equalsIgnoreCase(security)) {
271            return NONE;
272        }
273        if ("WEP".equalsIgnoreCase(security)) {
274            return WEP;
275        }
276        if ("PSK".equalsIgnoreCase(security)) {
277            return PSK;
278        }
279        if ("EAP".equalsIgnoreCase(security)) {
280            return EAP;
281        }
282        throw new IllegalArgumentException("Security type must be one of NONE, WEP, PSK, or EAP");
283    }
284
285    /**
286     * Get the EAP method from a string.
287     *
288     * @throws IllegalArgumentException if the string is not a supported EAP method.
289     */
290    private static int getEapMethod(String eapMethod) {
291        if ("TLS".equalsIgnoreCase(eapMethod)) {
292            return WifiEnterpriseConfig.Eap.TLS;
293        }
294        if ("TTLS".equalsIgnoreCase(eapMethod)) {
295            return WifiEnterpriseConfig.Eap.TTLS;
296        }
297        if ("PEAP".equalsIgnoreCase(eapMethod)) {
298            return WifiEnterpriseConfig.Eap.PEAP;
299        }
300        throw new IllegalArgumentException("EAP method must be one of TLS, TTLS, or PEAP");
301    }
302
303    /**
304     * Get the phase 2 method from a string.
305     *
306     * @throws IllegalArgumentException if the string is not a supported phase 2 method.
307     */
308    private static int getPhase2(String phase2) {
309        if ("PAP".equalsIgnoreCase(phase2)) {
310            return WifiEnterpriseConfig.Phase2.PAP;
311        }
312        if ("MSCHAP".equalsIgnoreCase(phase2)) {
313            return WifiEnterpriseConfig.Phase2.MSCHAP;
314        }
315        if ("MSCHAPV2".equalsIgnoreCase(phase2)) {
316            return WifiEnterpriseConfig.Phase2.MSCHAPV2;
317        }
318        if ("GTC".equalsIgnoreCase(phase2)) {
319            return WifiEnterpriseConfig.Phase2.GTC;
320        }
321        throw new IllegalArgumentException("Phase2 must be one of PAP, MSCHAP, MSCHAPV2, or GTC");
322    }
323
324    /**
325     * Get an {@link InetAddress} from a string
326     *
327     * @throws IllegalArgumentException if the string is not a valid IP address.
328     */
329    private static InetAddress getInetAddress(String ipAddress) {
330        if (!InetAddress.isNumeric(ipAddress)) {
331            throw new IllegalArgumentException(
332                    String.format("IP address %s is not numeric", ipAddress));
333        }
334
335        try {
336            return InetAddress.getByName(ipAddress);
337        } catch (UnknownHostException e) {
338            throw new IllegalArgumentException(
339                    String.format("IP address %s could not be resolved", ipAddress));
340        }
341    }
342
343    /**
344     * Get the prefix length from an int.
345     *
346     * @throws IllegalArgumentException if the prefix length is less than 0 or greater than 32.
347     */
348    private static int getPrefixLength(int prefixLength) {
349        if (prefixLength < 0 || prefixLength > 32) {
350            throw new IllegalArgumentException("Prefix length cannot be less than 0 or more than 32");
351        }
352        return prefixLength;
353    }
354
355    /**
356     * Utility method to check if a given string is a hexadecimal string of given length
357     */
358    public static boolean isHex(String input, int length) {
359        if (input == null || length < 0) {
360            return false;
361        }
362        return input.matches(String.format("[0-9A-Fa-f]{%d}", length));
363    }
364}
365