WifiApConfigStore.java revision 0cafbe0c8e1bb5b9900908a44df232251c2042ea
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.server.wifi;
18
19import android.annotation.NonNull;
20import android.content.Context;
21import android.net.wifi.WifiConfiguration;
22import android.net.wifi.WifiConfiguration.KeyMgmt;
23import android.os.Environment;
24import android.text.TextUtils;
25import android.util.Log;
26
27import com.android.internal.R;
28import com.android.internal.annotations.VisibleForTesting;
29
30import java.io.BufferedInputStream;
31import java.io.BufferedOutputStream;
32import java.io.DataInputStream;
33import java.io.DataOutputStream;
34import java.io.FileInputStream;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.nio.charset.StandardCharsets;
38import java.util.ArrayList;
39import java.util.Random;
40import java.util.UUID;
41
42/**
43 * Provides API for reading/writing soft access point configuration.
44 */
45public class WifiApConfigStore {
46
47    private static final String TAG = "WifiApConfigStore";
48
49    private static final String DEFAULT_AP_CONFIG_FILE =
50            Environment.getDataDirectory() + "/misc/wifi/softap.conf";
51
52    private static final int AP_CONFIG_FILE_VERSION = 2;
53
54    private static final int RAND_SSID_INT_MIN = 1000;
55    private static final int RAND_SSID_INT_MAX = 9999;
56
57    @VisibleForTesting
58    static final int SSID_MIN_LEN = 1;
59    @VisibleForTesting
60    static final int SSID_MAX_LEN = 32;
61    @VisibleForTesting
62    static final int PSK_MIN_LEN = 8;
63    @VisibleForTesting
64    static final int PSK_MAX_LEN = 63;
65
66    private WifiConfiguration mWifiApConfig = null;
67
68    private ArrayList<Integer> mAllowed2GChannel = null;
69
70    private final Context mContext;
71    private final String mApConfigFile;
72    private final BackupManagerProxy mBackupManagerProxy;
73
74    WifiApConfigStore(Context context, BackupManagerProxy backupManagerProxy) {
75        this(context, backupManagerProxy, DEFAULT_AP_CONFIG_FILE);
76    }
77
78    WifiApConfigStore(Context context,
79                      BackupManagerProxy backupManagerProxy,
80                      String apConfigFile) {
81        mContext = context;
82        mBackupManagerProxy = backupManagerProxy;
83        mApConfigFile = apConfigFile;
84
85        String ap2GChannelListStr = mContext.getResources().getString(
86                R.string.config_wifi_framework_sap_2G_channel_list);
87        Log.d(TAG, "2G band allowed channels are:" + ap2GChannelListStr);
88
89        if (ap2GChannelListStr != null) {
90            mAllowed2GChannel = new ArrayList<Integer>();
91            String channelList[] = ap2GChannelListStr.split(",");
92            for (String tmp : channelList) {
93                mAllowed2GChannel.add(Integer.parseInt(tmp));
94            }
95        }
96
97        /* Load AP configuration from persistent storage. */
98        mWifiApConfig = loadApConfiguration(mApConfigFile);
99        if (mWifiApConfig == null) {
100            /* Use default configuration. */
101            Log.d(TAG, "Fallback to use default AP configuration");
102            mWifiApConfig = getDefaultApConfiguration();
103
104            /* Save the default configuration to persistent storage. */
105            writeApConfiguration(mApConfigFile, mWifiApConfig);
106        }
107    }
108
109    /**
110     * Return the current soft access point configuration.
111     */
112    public synchronized WifiConfiguration getApConfiguration() {
113        return mWifiApConfig;
114    }
115
116    /**
117     * Update the current soft access point configuration.
118     * Restore to default AP configuration if null is provided.
119     * This can be invoked under context of binder threads (WifiManager.setWifiApConfiguration)
120     * and WifiStateMachine thread (CMD_START_AP).
121     */
122    public synchronized void setApConfiguration(WifiConfiguration config) {
123        if (config == null) {
124            mWifiApConfig = getDefaultApConfiguration();
125        } else {
126            mWifiApConfig = config;
127        }
128        writeApConfiguration(mApConfigFile, mWifiApConfig);
129
130        // Stage the backup of the SettingsProvider package which backs this up
131        mBackupManagerProxy.notifyDataChanged();
132    }
133
134    public ArrayList<Integer> getAllowed2GChannel() {
135        return mAllowed2GChannel;
136    }
137
138    /**
139     * Load AP configuration from persistent storage.
140     */
141    private static WifiConfiguration loadApConfiguration(final String filename) {
142        WifiConfiguration config = null;
143        DataInputStream in = null;
144        try {
145            config = new WifiConfiguration();
146            in = new DataInputStream(
147                    new BufferedInputStream(new FileInputStream(filename)));
148
149            int version = in.readInt();
150            if ((version != 1) && (version != 2)) {
151                Log.e(TAG, "Bad version on hotspot configuration file");
152                return null;
153            }
154            config.SSID = in.readUTF();
155
156            if (version >= 2) {
157                config.apBand = in.readInt();
158                config.apChannel = in.readInt();
159            }
160
161            int authType = in.readInt();
162            config.allowedKeyManagement.set(authType);
163            if (authType != KeyMgmt.NONE) {
164                config.preSharedKey = in.readUTF();
165            }
166        } catch (IOException e) {
167            Log.e(TAG, "Error reading hotspot configuration " + e);
168            config = null;
169        } finally {
170            if (in != null) {
171                try {
172                    in.close();
173                } catch (IOException e) {
174                    Log.e(TAG, "Error closing hotspot configuration during read" + e);
175                }
176            }
177        }
178        return config;
179    }
180
181    /**
182     * Write AP configuration to persistent storage.
183     */
184    private static void writeApConfiguration(final String filename,
185                                             final WifiConfiguration config) {
186        try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
187                        new FileOutputStream(filename)))) {
188            out.writeInt(AP_CONFIG_FILE_VERSION);
189            out.writeUTF(config.SSID);
190            out.writeInt(config.apBand);
191            out.writeInt(config.apChannel);
192            int authType = config.getAuthType();
193            out.writeInt(authType);
194            if (authType != KeyMgmt.NONE) {
195                out.writeUTF(config.preSharedKey);
196            }
197        } catch (IOException e) {
198            Log.e(TAG, "Error writing hotspot configuration" + e);
199        }
200    }
201
202    /**
203     * Generate a default WPA2 based configuration with a random password.
204     * We are changing the Wifi Ap configuration storage from secure settings to a
205     * flat file accessible only by the system. A WPA2 based default configuration
206     * will keep the device secure after the update.
207     */
208    private WifiConfiguration getDefaultApConfiguration() {
209        WifiConfiguration config = new WifiConfiguration();
210        config.SSID = mContext.getResources().getString(
211                R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid();
212        config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
213        String randomUUID = UUID.randomUUID().toString();
214        //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
215        config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
216        return config;
217    }
218
219    private static int getRandomIntForDefaultSsid() {
220        Random random = new Random();
221        return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN;
222    }
223
224    /**
225     * Generate a temporary WPA2 based configuration for use by the local only hotspot.
226     * This config is not persisted and will not be stored by the WifiApConfigStore.
227     */
228    public static WifiConfiguration generateLocalOnlyHotspotConfig(Context context) {
229        WifiConfiguration config = new WifiConfiguration();
230        config.SSID = context.getResources().getString(
231              R.string.wifi_localhotspot_configure_ssid_default) + "_"
232                      + getRandomIntForDefaultSsid();
233        config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
234        config.networkId = WifiConfiguration.LOCAL_ONLY_NETWORK_ID;
235        String randomUUID = UUID.randomUUID().toString();
236        // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
237        config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
238        return config;
239    }
240
241    /**
242     * Verify provided SSID for existence, length and conversion to bytes
243     *
244     * @param ssid String ssid name
245     * @return boolean indicating ssid met requirements
246     */
247    private static boolean validateApConfigSsid(String ssid) {
248        if (TextUtils.isEmpty(ssid)) {
249            Log.d(TAG, "SSID for softap configuration must be set.");
250            return false;
251        }
252
253        if (ssid.length() < SSID_MIN_LEN || ssid.length() > SSID_MAX_LEN) {
254            Log.d(TAG, "SSID for softap configuration string size must be at least "
255                    + SSID_MIN_LEN + " and not more than " + SSID_MAX_LEN);
256            return false;
257        }
258
259        try {
260            ssid.getBytes(StandardCharsets.UTF_8);
261        } catch (IllegalArgumentException e) {
262            Log.e(TAG, "softap config SSID verification failed: malformed string " + ssid);
263            return false;
264        }
265        return true;
266    }
267
268    /**
269     * Verify provided preSharedKey in ap config for WPA2_PSK network meets requirements.
270     */
271    private static boolean validateApConfigPreSharedKey(String preSharedKey) {
272        if (preSharedKey.length() < PSK_MIN_LEN || preSharedKey.length() > PSK_MAX_LEN) {
273            Log.d(TAG, "softap network password string size must be at least " + PSK_MIN_LEN
274                    + " and no more than " + PSK_MAX_LEN);
275            return false;
276        }
277
278        try {
279            preSharedKey.getBytes(StandardCharsets.UTF_8);
280        } catch (IllegalArgumentException e) {
281            Log.e(TAG, "softap network password verification failed: malformed string");
282            return false;
283        }
284        return true;
285    }
286
287    /**
288     * Validate a WifiConfiguration is properly configured for use by SoftApManager.
289     *
290     * This method checks the length of the SSID and for sanity between security settings (if it
291     * requires a password, was one provided?).
292     *
293     * @param apConfig {@link WifiConfiguration} to use for softap mode
294     * @return boolean true if the provided config meets the minimum set of details, false
295     * otherwise.
296     */
297    static boolean validateApWifiConfiguration(@NonNull WifiConfiguration apConfig) {
298        // first check the SSID
299        if (!validateApConfigSsid(apConfig.SSID)) {
300            // failed SSID verificiation checks
301            return false;
302        }
303
304        // now check security settings: settings app allows open and WPA2 PSK
305        if (apConfig.allowedKeyManagement == null) {
306            Log.d(TAG, "softap config key management bitset was null");
307            return false;
308        }
309
310        String preSharedKey = apConfig.preSharedKey;
311        boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey);
312        int authType;
313
314        try {
315            authType = apConfig.getAuthType();
316        } catch (IllegalStateException e) {
317            Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage());
318            return false;
319        }
320
321        if (authType == KeyMgmt.NONE) {
322            // open networks should not have a password
323            if (hasPreSharedKey) {
324                Log.d(TAG, "open softap network should not have a password");
325                return false;
326            }
327        } else if (authType == KeyMgmt.WPA2_PSK) {
328            // this is a config that should have a password - check that first
329            if (!hasPreSharedKey) {
330                Log.d(TAG, "softap network password must be set");
331                return false;
332            }
333
334            if (!validateApConfigPreSharedKey(preSharedKey)) {
335                // failed preSharedKey checks
336                return false;
337            }
338        } else {
339            // this is not a supported security type
340            Log.d(TAG, "softap configs must either be open or WPA2 PSK networks");
341            return false;
342        }
343
344        return true;
345    }
346}
347