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 @VisibleForTesting 67 static final int AP_CHANNEL_DEFAULT = 0; 68 69 private WifiConfiguration mWifiApConfig = null; 70 71 private ArrayList<Integer> mAllowed2GChannel = null; 72 73 private final Context mContext; 74 private final String mApConfigFile; 75 private final BackupManagerProxy mBackupManagerProxy; 76 private boolean mRequiresApBandConversion = false; 77 78 WifiApConfigStore(Context context, BackupManagerProxy backupManagerProxy) { 79 this(context, backupManagerProxy, DEFAULT_AP_CONFIG_FILE); 80 } 81 82 WifiApConfigStore(Context context, 83 BackupManagerProxy backupManagerProxy, 84 String apConfigFile) { 85 mContext = context; 86 mBackupManagerProxy = backupManagerProxy; 87 mApConfigFile = apConfigFile; 88 89 String ap2GChannelListStr = mContext.getResources().getString( 90 R.string.config_wifi_framework_sap_2G_channel_list); 91 Log.d(TAG, "2G band allowed channels are:" + ap2GChannelListStr); 92 93 if (ap2GChannelListStr != null) { 94 mAllowed2GChannel = new ArrayList<Integer>(); 95 String channelList[] = ap2GChannelListStr.split(","); 96 for (String tmp : channelList) { 97 mAllowed2GChannel.add(Integer.parseInt(tmp)); 98 } 99 } 100 101 mRequiresApBandConversion = mContext.getResources().getBoolean( 102 R.bool.config_wifi_convert_apband_5ghz_to_any); 103 104 /* Load AP configuration from persistent storage. */ 105 mWifiApConfig = loadApConfiguration(mApConfigFile); 106 if (mWifiApConfig == null) { 107 /* Use default configuration. */ 108 Log.d(TAG, "Fallback to use default AP configuration"); 109 mWifiApConfig = getDefaultApConfiguration(); 110 111 /* Save the default configuration to persistent storage. */ 112 writeApConfiguration(mApConfigFile, mWifiApConfig); 113 } 114 } 115 116 /** 117 * Return the current soft access point configuration. 118 */ 119 public synchronized WifiConfiguration getApConfiguration() { 120 WifiConfiguration config = apBandCheckConvert(mWifiApConfig); 121 if (mWifiApConfig != config) { 122 Log.d(TAG, "persisted config was converted, need to resave it"); 123 mWifiApConfig = config; 124 persistConfigAndTriggerBackupManagerProxy(mWifiApConfig); 125 } 126 return mWifiApConfig; 127 } 128 129 /** 130 * Update the current soft access point configuration. 131 * Restore to default AP configuration if null is provided. 132 * This can be invoked under context of binder threads (WifiManager.setWifiApConfiguration) 133 * and WifiStateMachine thread (CMD_START_AP). 134 */ 135 public synchronized void setApConfiguration(WifiConfiguration config) { 136 if (config == null) { 137 mWifiApConfig = getDefaultApConfiguration(); 138 } else { 139 mWifiApConfig = apBandCheckConvert(config); 140 } 141 persistConfigAndTriggerBackupManagerProxy(mWifiApConfig); 142 } 143 144 public ArrayList<Integer> getAllowed2GChannel() { 145 return mAllowed2GChannel; 146 } 147 148 private WifiConfiguration apBandCheckConvert(WifiConfiguration config) { 149 if (mRequiresApBandConversion) { 150 // some devices are unable to support 5GHz only operation, check for 5GHz and 151 // move to ANY if apBand conversion is required. 152 if (config.apBand == WifiConfiguration.AP_BAND_5GHZ) { 153 Log.w(TAG, "Supplied ap config band was 5GHz only, converting to ANY"); 154 WifiConfiguration convertedConfig = new WifiConfiguration(config); 155 convertedConfig.apBand = WifiConfiguration.AP_BAND_ANY; 156 convertedConfig.apChannel = AP_CHANNEL_DEFAULT; 157 return convertedConfig; 158 } 159 } else { 160 // this is a single mode device, we do not support ANY. Convert all ANY to 5GHz 161 if (config.apBand == WifiConfiguration.AP_BAND_ANY) { 162 Log.w(TAG, "Supplied ap config band was ANY, converting to 5GHz"); 163 WifiConfiguration convertedConfig = new WifiConfiguration(config); 164 convertedConfig.apBand = WifiConfiguration.AP_BAND_5GHZ; 165 convertedConfig.apChannel = AP_CHANNEL_DEFAULT; 166 return convertedConfig; 167 } 168 } 169 return config; 170 } 171 172 private void persistConfigAndTriggerBackupManagerProxy(WifiConfiguration config) { 173 writeApConfiguration(mApConfigFile, mWifiApConfig); 174 // Stage the backup of the SettingsProvider package which backs this up 175 mBackupManagerProxy.notifyDataChanged(); 176 } 177 178 /** 179 * Load AP configuration from persistent storage. 180 */ 181 private static WifiConfiguration loadApConfiguration(final String filename) { 182 WifiConfiguration config = null; 183 DataInputStream in = null; 184 try { 185 config = new WifiConfiguration(); 186 in = new DataInputStream( 187 new BufferedInputStream(new FileInputStream(filename))); 188 189 int version = in.readInt(); 190 if ((version != 1) && (version != 2)) { 191 Log.e(TAG, "Bad version on hotspot configuration file"); 192 return null; 193 } 194 config.SSID = in.readUTF(); 195 196 if (version >= 2) { 197 config.apBand = in.readInt(); 198 config.apChannel = in.readInt(); 199 } 200 201 int authType = in.readInt(); 202 config.allowedKeyManagement.set(authType); 203 if (authType != KeyMgmt.NONE) { 204 config.preSharedKey = in.readUTF(); 205 } 206 } catch (IOException e) { 207 Log.e(TAG, "Error reading hotspot configuration " + e); 208 config = null; 209 } finally { 210 if (in != null) { 211 try { 212 in.close(); 213 } catch (IOException e) { 214 Log.e(TAG, "Error closing hotspot configuration during read" + e); 215 } 216 } 217 } 218 return config; 219 } 220 221 /** 222 * Write AP configuration to persistent storage. 223 */ 224 private static void writeApConfiguration(final String filename, 225 final WifiConfiguration config) { 226 try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream( 227 new FileOutputStream(filename)))) { 228 out.writeInt(AP_CONFIG_FILE_VERSION); 229 out.writeUTF(config.SSID); 230 out.writeInt(config.apBand); 231 out.writeInt(config.apChannel); 232 int authType = config.getAuthType(); 233 out.writeInt(authType); 234 if (authType != KeyMgmt.NONE) { 235 out.writeUTF(config.preSharedKey); 236 } 237 } catch (IOException e) { 238 Log.e(TAG, "Error writing hotspot configuration" + e); 239 } 240 } 241 242 /** 243 * Generate a default WPA2 based configuration with a random password. 244 * We are changing the Wifi Ap configuration storage from secure settings to a 245 * flat file accessible only by the system. A WPA2 based default configuration 246 * will keep the device secure after the update. 247 */ 248 private WifiConfiguration getDefaultApConfiguration() { 249 WifiConfiguration config = new WifiConfiguration(); 250 config.apBand = WifiConfiguration.AP_BAND_2GHZ; 251 config.SSID = mContext.getResources().getString( 252 R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid(); 253 config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK); 254 String randomUUID = UUID.randomUUID().toString(); 255 //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 256 config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13); 257 return config; 258 } 259 260 private static int getRandomIntForDefaultSsid() { 261 Random random = new Random(); 262 return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN; 263 } 264 265 /** 266 * Generate a temporary WPA2 based configuration for use by the local only hotspot. 267 * This config is not persisted and will not be stored by the WifiApConfigStore. 268 */ 269 public static WifiConfiguration generateLocalOnlyHotspotConfig(Context context) { 270 WifiConfiguration config = new WifiConfiguration(); 271 // For local only hotspot we only use 2.4Ghz band. 272 config.apBand = WifiConfiguration.AP_BAND_2GHZ; 273 config.SSID = context.getResources().getString( 274 R.string.wifi_localhotspot_configure_ssid_default) + "_" 275 + getRandomIntForDefaultSsid(); 276 config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK); 277 config.networkId = WifiConfiguration.LOCAL_ONLY_NETWORK_ID; 278 String randomUUID = UUID.randomUUID().toString(); 279 // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 280 config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13); 281 return config; 282 } 283 284 /** 285 * Verify provided SSID for existence, length and conversion to bytes 286 * 287 * @param ssid String ssid name 288 * @return boolean indicating ssid met requirements 289 */ 290 private static boolean validateApConfigSsid(String ssid) { 291 if (TextUtils.isEmpty(ssid)) { 292 Log.d(TAG, "SSID for softap configuration must be set."); 293 return false; 294 } 295 296 if (ssid.length() < SSID_MIN_LEN || ssid.length() > SSID_MAX_LEN) { 297 Log.d(TAG, "SSID for softap configuration string size must be at least " 298 + SSID_MIN_LEN + " and not more than " + SSID_MAX_LEN); 299 return false; 300 } 301 302 try { 303 ssid.getBytes(StandardCharsets.UTF_8); 304 } catch (IllegalArgumentException e) { 305 Log.e(TAG, "softap config SSID verification failed: malformed string " + ssid); 306 return false; 307 } 308 return true; 309 } 310 311 /** 312 * Verify provided preSharedKey in ap config for WPA2_PSK network meets requirements. 313 */ 314 private static boolean validateApConfigPreSharedKey(String preSharedKey) { 315 if (preSharedKey.length() < PSK_MIN_LEN || preSharedKey.length() > PSK_MAX_LEN) { 316 Log.d(TAG, "softap network password string size must be at least " + PSK_MIN_LEN 317 + " and no more than " + PSK_MAX_LEN); 318 return false; 319 } 320 321 try { 322 preSharedKey.getBytes(StandardCharsets.UTF_8); 323 } catch (IllegalArgumentException e) { 324 Log.e(TAG, "softap network password verification failed: malformed string"); 325 return false; 326 } 327 return true; 328 } 329 330 /** 331 * Validate a WifiConfiguration is properly configured for use by SoftApManager. 332 * 333 * This method checks the length of the SSID and for sanity between security settings (if it 334 * requires a password, was one provided?). 335 * 336 * @param apConfig {@link WifiConfiguration} to use for softap mode 337 * @return boolean true if the provided config meets the minimum set of details, false 338 * otherwise. 339 */ 340 static boolean validateApWifiConfiguration(@NonNull WifiConfiguration apConfig) { 341 // first check the SSID 342 if (!validateApConfigSsid(apConfig.SSID)) { 343 // failed SSID verificiation checks 344 return false; 345 } 346 347 // now check security settings: settings app allows open and WPA2 PSK 348 if (apConfig.allowedKeyManagement == null) { 349 Log.d(TAG, "softap config key management bitset was null"); 350 return false; 351 } 352 353 String preSharedKey = apConfig.preSharedKey; 354 boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey); 355 int authType; 356 357 try { 358 authType = apConfig.getAuthType(); 359 } catch (IllegalStateException e) { 360 Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage()); 361 return false; 362 } 363 364 if (authType == KeyMgmt.NONE) { 365 // open networks should not have a password 366 if (hasPreSharedKey) { 367 Log.d(TAG, "open softap network should not have a password"); 368 return false; 369 } 370 } else if (authType == KeyMgmt.WPA2_PSK) { 371 // this is a config that should have a password - check that first 372 if (!hasPreSharedKey) { 373 Log.d(TAG, "softap network password must be set"); 374 return false; 375 } 376 377 if (!validateApConfigPreSharedKey(preSharedKey)) { 378 // failed preSharedKey checks 379 return false; 380 } 381 } else { 382 // this is not a supported security type 383 Log.d(TAG, "softap configs must either be open or WPA2 PSK networks"); 384 return false; 385 } 386 387 return true; 388 } 389} 390