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