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 17 package com.android.server.wifi; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.content.IntentFilter; 22 import android.net.MacAddress; 23 import android.net.util.MacAddressUtils; 24 import android.net.wifi.SoftApConfiguration; 25 import android.os.Handler; 26 import android.os.Process; 27 import android.text.TextUtils; 28 import android.util.Log; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.server.wifi.util.ApConfigUtil; 32 import com.android.wifi.resources.R; 33 34 import java.nio.charset.StandardCharsets; 35 import java.security.SecureRandom; 36 import java.util.ArrayList; 37 import java.util.Random; 38 39 import javax.annotation.Nullable; 40 41 /** 42 * Provides API for reading/writing soft access point configuration. 43 */ 44 public class WifiApConfigStore { 45 46 // Intent when user has interacted with the softap settings change notification 47 public static final String ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT = 48 "com.android.server.wifi.WifiApConfigStoreUtil.HOTSPOT_CONFIG_USER_TAPPED_CONTENT"; 49 50 private static final String TAG = "WifiApConfigStore"; 51 52 private static final int RAND_SSID_INT_MIN = 1000; 53 private static final int RAND_SSID_INT_MAX = 9999; 54 55 @VisibleForTesting 56 static final int SSID_MIN_LEN = 1; 57 @VisibleForTesting 58 static final int SSID_MAX_LEN = 32; 59 @VisibleForTesting 60 static final int PSK_MIN_LEN = 8; 61 @VisibleForTesting 62 static final int PSK_MAX_LEN = 63; 63 64 private SoftApConfiguration mPersistentWifiApConfig = null; 65 66 private final Context mContext; 67 private final Handler mHandler; 68 private final WifiMetrics mWifiMetrics; 69 private final BackupManagerProxy mBackupManagerProxy; 70 private final MacAddressUtil mMacAddressUtil; 71 private final WifiConfigManager mWifiConfigManager; 72 private final ActiveModeWarden mActiveModeWarden; 73 private boolean mHasNewDataToSerialize = false; 74 75 /** 76 * Module to interact with the wifi config store. 77 */ 78 private class SoftApStoreDataSource implements SoftApStoreData.DataSource { 79 toSerialize()80 public SoftApConfiguration toSerialize() { 81 mHasNewDataToSerialize = false; 82 return mPersistentWifiApConfig; 83 } 84 fromDeserialized(SoftApConfiguration config)85 public void fromDeserialized(SoftApConfiguration config) { 86 mPersistentWifiApConfig = new SoftApConfiguration.Builder(config).build(); 87 } 88 reset()89 public void reset() { 90 mPersistentWifiApConfig = null; 91 } 92 hasNewDataToSerialize()93 public boolean hasNewDataToSerialize() { 94 return mHasNewDataToSerialize; 95 } 96 } 97 WifiApConfigStore(Context context, WifiInjector wifiInjector, Handler handler, BackupManagerProxy backupManagerProxy, WifiConfigStore wifiConfigStore, WifiConfigManager wifiConfigManager, ActiveModeWarden activeModeWarden, WifiMetrics wifiMetrics)98 WifiApConfigStore(Context context, 99 WifiInjector wifiInjector, 100 Handler handler, 101 BackupManagerProxy backupManagerProxy, 102 WifiConfigStore wifiConfigStore, 103 WifiConfigManager wifiConfigManager, 104 ActiveModeWarden activeModeWarden, 105 WifiMetrics wifiMetrics) { 106 mContext = context; 107 mHandler = handler; 108 mBackupManagerProxy = backupManagerProxy; 109 mWifiConfigManager = wifiConfigManager; 110 mActiveModeWarden = activeModeWarden; 111 mWifiMetrics = wifiMetrics; 112 113 // Register store data listener 114 wifiConfigStore.registerStoreData( 115 wifiInjector.makeSoftApStoreData(new SoftApStoreDataSource())); 116 117 IntentFilter filter = new IntentFilter(); 118 filter.addAction(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT); 119 mMacAddressUtil = wifiInjector.getMacAddressUtil(); 120 } 121 122 /** 123 * Return the current soft access point configuration. 124 */ getApConfiguration()125 public synchronized SoftApConfiguration getApConfiguration() { 126 if (mPersistentWifiApConfig == null) { 127 /* Use default configuration. */ 128 Log.d(TAG, "Fallback to use default AP configuration"); 129 persistConfigAndTriggerBackupManagerProxy(getDefaultApConfiguration()); 130 } 131 SoftApConfiguration sanitizedPersistentconfig = 132 sanitizePersistentApConfig(mPersistentWifiApConfig); 133 if (mPersistentWifiApConfig != sanitizedPersistentconfig) { 134 Log.d(TAG, "persisted config was converted, need to resave it"); 135 persistConfigAndTriggerBackupManagerProxy(sanitizedPersistentconfig); 136 } 137 return mPersistentWifiApConfig; 138 } 139 140 /** 141 * Update the current soft access point configuration. 142 * Restore to default AP configuration if null is provided. 143 * This can be invoked under context of binder threads (WifiManager.setWifiApConfiguration) 144 * and the main Wifi thread (CMD_START_AP). 145 */ setApConfiguration(SoftApConfiguration config)146 public synchronized void setApConfiguration(SoftApConfiguration config) { 147 if (config == null) { 148 config = getDefaultApConfiguration(); 149 } else { 150 config = sanitizePersistentApConfig(config); 151 } 152 persistConfigAndTriggerBackupManagerProxy(config); 153 } 154 155 /** 156 * Returns SoftApConfiguration in which some parameters might be reset to supported default 157 * config since it depends on UI or HW. 158 * 159 * MaxNumberOfClients and isClientControlByUserEnabled will need HAL support client force 160 * disconnect, and Band setting (5g/6g) need HW support. 161 * 162 * HiddenSsid, Channel, ShutdownTimeoutMillis and AutoShutdownEnabled are features 163 * which need UI(Setting) support. 164 * 165 * SAE/SAE-Transition need hardware support, reset to secured WPA2 security type when device 166 * doesn't support it. 167 */ resetToDefaultForUnsupportedConfig( @onNull SoftApConfiguration config)168 public SoftApConfiguration resetToDefaultForUnsupportedConfig( 169 @NonNull SoftApConfiguration config) { 170 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config); 171 if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext) 172 || mContext.getResources().getBoolean( 173 R.bool.config_wifiSoftapResetUserControlConfig)) 174 && (config.isClientControlByUserEnabled() 175 || config.getBlockedClientList().size() != 0)) { 176 configBuilder.setClientControlByUserEnabled(false); 177 configBuilder.setBlockedClientList(new ArrayList<>()); 178 Log.i(TAG, "Reset ClientControlByUser to false due to device doesn't support"); 179 } 180 181 if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext) 182 || mContext.getResources().getBoolean( 183 R.bool.config_wifiSoftapResetMaxClientSettingConfig)) 184 && config.getMaxNumberOfClients() != 0) { 185 configBuilder.setMaxNumberOfClients(0); 186 Log.i(TAG, "Reset MaxNumberOfClients to 0 due to device doesn't support"); 187 } 188 189 if (!ApConfigUtil.isWpa3SaeSupported(mContext) && (config.getSecurityType() 190 == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE 191 || config.getSecurityType() 192 == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)) { 193 configBuilder.setPassphrase(generatePassword(), 194 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); 195 Log.i(TAG, "Device doesn't support WPA3-SAE, reset config to WPA2"); 196 } 197 198 if (mContext.getResources().getBoolean(R.bool.config_wifiSoftapResetChannelConfig) 199 && config.getChannel() != 0) { 200 // The device might not support customize channel or forced channel might not 201 // work in some countries. Need to reset it. 202 // Add 2.4G by default 203 configBuilder.setBand(config.getBand() | SoftApConfiguration.BAND_2GHZ); 204 Log.i(TAG, "Reset SAP channel configuration"); 205 } 206 207 int newBand = config.getBand(); 208 if (!mContext.getResources().getBoolean(R.bool.config_wifi6ghzSupport) 209 && (newBand & SoftApConfiguration.BAND_6GHZ) != 0) { 210 newBand &= ~SoftApConfiguration.BAND_6GHZ; 211 Log.i(TAG, "Device doesn't support 6g, remove 6G band from band setting"); 212 } 213 214 if (!mContext.getResources().getBoolean(R.bool.config_wifi5ghzSupport) 215 && (newBand & SoftApConfiguration.BAND_5GHZ) != 0) { 216 newBand &= ~SoftApConfiguration.BAND_5GHZ; 217 Log.i(TAG, "Device doesn't support 5g, remove 5G band from band setting"); 218 } 219 220 if (newBand != config.getBand()) { 221 // Always added 2.4G by default when reset the band. 222 Log.i(TAG, "Reset band from " + config.getBand() + " to " 223 + (newBand | SoftApConfiguration.BAND_2GHZ)); 224 configBuilder.setBand(newBand | SoftApConfiguration.BAND_2GHZ); 225 } 226 227 if (mContext.getResources().getBoolean(R.bool.config_wifiSoftapResetHiddenConfig) 228 && config.isHiddenSsid()) { 229 configBuilder.setHiddenSsid(false); 230 Log.i(TAG, "Reset SAP Hidden Network configuration"); 231 } 232 233 if (mContext.getResources().getBoolean( 234 R.bool.config_wifiSoftapResetAutoShutdownTimerConfig) 235 && config.getShutdownTimeoutMillis() != 0) { 236 configBuilder.setShutdownTimeoutMillis(0); 237 Log.i(TAG, "Reset SAP auto shutdown configuration"); 238 } 239 240 mWifiMetrics.noteSoftApConfigReset(config, configBuilder.build()); 241 return configBuilder.build(); 242 } 243 sanitizePersistentApConfig(SoftApConfiguration config)244 private SoftApConfiguration sanitizePersistentApConfig(SoftApConfiguration config) { 245 SoftApConfiguration.Builder convertedConfigBuilder = null; 246 247 // some countries are unable to support 5GHz only operation, always allow for 2GHz when 248 // config doesn't force channel 249 if (config.getChannel() == 0 && (config.getBand() & SoftApConfiguration.BAND_2GHZ) == 0) { 250 Log.w(TAG, "Supplied ap config band without 2.4G, add allowing for 2.4GHz"); 251 if (convertedConfigBuilder == null) { 252 convertedConfigBuilder = new SoftApConfiguration.Builder(config); 253 } 254 convertedConfigBuilder.setBand(config.getBand() | SoftApConfiguration.BAND_2GHZ); 255 } 256 return convertedConfigBuilder == null ? config : convertedConfigBuilder.build(); 257 } 258 persistConfigAndTriggerBackupManagerProxy(SoftApConfiguration config)259 private void persistConfigAndTriggerBackupManagerProxy(SoftApConfiguration config) { 260 mPersistentWifiApConfig = config; 261 mHasNewDataToSerialize = true; 262 mWifiConfigManager.saveToStore(true); 263 mBackupManagerProxy.notifyDataChanged(); 264 } 265 266 /** 267 * Generate a default WPA3 SAE transition (if supported) or WPA2 based 268 * configuration with a random password. 269 * We are changing the Wifi Ap configuration storage from secure settings to a 270 * flat file accessible only by the system. A WPA2 based default configuration 271 * will keep the device secure after the update. 272 */ getDefaultApConfiguration()273 private SoftApConfiguration getDefaultApConfiguration() { 274 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); 275 configBuilder.setBand(SoftApConfiguration.BAND_2GHZ); 276 configBuilder.setSsid(mContext.getResources().getString( 277 R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid()); 278 if (ApConfigUtil.isWpa3SaeSupported(mContext)) { 279 configBuilder.setPassphrase(generatePassword(), 280 SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION); 281 } else { 282 configBuilder.setPassphrase(generatePassword(), 283 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); 284 } 285 return configBuilder.build(); 286 } 287 getRandomIntForDefaultSsid()288 private static int getRandomIntForDefaultSsid() { 289 Random random = new Random(); 290 return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN; 291 } 292 generateLohsSsid(Context context)293 private static String generateLohsSsid(Context context) { 294 return context.getResources().getString( 295 R.string.wifi_localhotspot_configure_ssid_default) + "_" 296 + getRandomIntForDefaultSsid(); 297 } 298 299 /** 300 * Generate a temporary WPA2 based configuration for use by the local only hotspot. 301 * This config is not persisted and will not be stored by the WifiApConfigStore. 302 */ generateLocalOnlyHotspotConfig(Context context, int apBand, @Nullable SoftApConfiguration customConfig)303 public static SoftApConfiguration generateLocalOnlyHotspotConfig(Context context, int apBand, 304 @Nullable SoftApConfiguration customConfig) { 305 SoftApConfiguration.Builder configBuilder; 306 if (customConfig != null) { 307 configBuilder = new SoftApConfiguration.Builder(customConfig); 308 } else { 309 configBuilder = new SoftApConfiguration.Builder(); 310 // Default to disable the auto shutdown 311 configBuilder.setAutoShutdownEnabled(false); 312 } 313 314 configBuilder.setBand(apBand); 315 316 if (customConfig == null || customConfig.getSsid() == null) { 317 configBuilder.setSsid(generateLohsSsid(context)); 318 } 319 if (customConfig == null) { 320 if (ApConfigUtil.isWpa3SaeSupported(context)) { 321 configBuilder.setPassphrase(generatePassword(), 322 SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION); 323 } else { 324 configBuilder.setPassphrase(generatePassword(), 325 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); 326 } 327 } 328 329 return configBuilder.build(); 330 } 331 332 /** 333 * @return a copy of the given SoftApConfig with the BSSID randomized, unless a custom BSSID is 334 * already set. 335 */ randomizeBssidIfUnset(Context context, SoftApConfiguration config)336 SoftApConfiguration randomizeBssidIfUnset(Context context, SoftApConfiguration config) { 337 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config); 338 if (config.getBssid() == null && context.getResources().getBoolean( 339 R.bool.config_wifi_ap_mac_randomization_supported)) { 340 MacAddress macAddress = mMacAddressUtil.calculatePersistentMac(config.getSsid(), 341 mMacAddressUtil.obtainMacRandHashFunctionForSap(Process.WIFI_UID)); 342 if (macAddress == null) { 343 Log.e(TAG, "Failed to calculate MAC from SSID. " 344 + "Generating new random MAC instead."); 345 macAddress = MacAddressUtils.createRandomUnicastAddress(); 346 } 347 configBuilder.setBssid(macAddress); 348 } 349 return configBuilder.build(); 350 } 351 352 /** 353 * Verify provided SSID for existence, length and conversion to bytes 354 * 355 * @param ssid String ssid name 356 * @return boolean indicating ssid met requirements 357 */ validateApConfigSsid(String ssid)358 private static boolean validateApConfigSsid(String ssid) { 359 if (TextUtils.isEmpty(ssid)) { 360 Log.d(TAG, "SSID for softap configuration must be set."); 361 return false; 362 } 363 364 try { 365 byte[] ssid_bytes = ssid.getBytes(StandardCharsets.UTF_8); 366 367 if (ssid_bytes.length < SSID_MIN_LEN || ssid_bytes.length > SSID_MAX_LEN) { 368 Log.d(TAG, "softap SSID is defined as UTF-8 and it must be at least " 369 + SSID_MIN_LEN + " byte and not more than " + SSID_MAX_LEN + " bytes"); 370 return false; 371 } 372 } catch (IllegalArgumentException e) { 373 Log.e(TAG, "softap config SSID verification failed: malformed string " + ssid); 374 return false; 375 } 376 return true; 377 } 378 379 /** 380 * Verify provided preSharedKey in ap config for WPA2_PSK network meets requirements. 381 */ validateApConfigPreSharedKey(String preSharedKey)382 private static boolean validateApConfigPreSharedKey(String preSharedKey) { 383 if (preSharedKey.length() < PSK_MIN_LEN || preSharedKey.length() > PSK_MAX_LEN) { 384 Log.d(TAG, "softap network password string size must be at least " + PSK_MIN_LEN 385 + " and no more than " + PSK_MAX_LEN); 386 return false; 387 } 388 389 try { 390 preSharedKey.getBytes(StandardCharsets.UTF_8); 391 } catch (IllegalArgumentException e) { 392 Log.e(TAG, "softap network password verification failed: malformed string"); 393 return false; 394 } 395 return true; 396 } 397 398 /** 399 * Validate a SoftApConfiguration is properly configured for use by SoftApManager. 400 * 401 * This method checks the length of the SSID and for sanity between security settings (if it 402 * requires a password, was one provided?). 403 * 404 * @param apConfig {@link SoftApConfiguration} to use for softap mode 405 * @param isPrivileged indicate the caller can pass some fields check or not 406 * @return boolean true if the provided config meets the minimum set of details, false 407 * otherwise. 408 */ validateApWifiConfiguration(@onNull SoftApConfiguration apConfig, boolean isPrivileged)409 static boolean validateApWifiConfiguration(@NonNull SoftApConfiguration apConfig, 410 boolean isPrivileged) { 411 // first check the SSID 412 if (!validateApConfigSsid(apConfig.getSsid())) { 413 // failed SSID verificiation checks 414 return false; 415 } 416 417 // BSSID can be set if caller own permission:android.Manifest.permission.NETWORK_SETTINGS. 418 if (apConfig.getBssid() != null && !isPrivileged) { 419 Log.e(TAG, "Config BSSID needs NETWORK_SETTINGS permission"); 420 return false; 421 } 422 423 String preSharedKey = apConfig.getPassphrase(); 424 boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey); 425 int authType; 426 427 try { 428 authType = apConfig.getSecurityType(); 429 } catch (IllegalStateException e) { 430 Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage()); 431 return false; 432 } 433 434 if (authType == SoftApConfiguration.SECURITY_TYPE_OPEN) { 435 // open networks should not have a password 436 if (hasPreSharedKey) { 437 Log.d(TAG, "open softap network should not have a password"); 438 return false; 439 } 440 } else if (authType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK 441 || authType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION 442 || authType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) { 443 // this is a config that should have a password - check that first 444 if (!hasPreSharedKey) { 445 Log.d(TAG, "softap network password must be set"); 446 return false; 447 } 448 if (authType != SoftApConfiguration.SECURITY_TYPE_WPA3_SAE 449 && !validateApConfigPreSharedKey(preSharedKey)) { 450 // failed preSharedKey checks for WPA2 and WPA3 SAE Transition mode. 451 return false; 452 } 453 } else { 454 // this is not a supported security type 455 Log.d(TAG, "softap configs must either be open or WPA2 PSK networks"); 456 return false; 457 } 458 459 return true; 460 } 461 generatePassword()462 private static String generatePassword() { 463 // Characters that will be used for password generation. Some characters commonly known to 464 // be confusing like 0 and O excluded from this list. 465 final String allowed = "23456789abcdefghijkmnpqrstuvwxyz"; 466 final int passLength = 15; 467 468 StringBuilder sb = new StringBuilder(passLength); 469 SecureRandom random = new SecureRandom(); 470 for (int i = 0; i < passLength; i++) { 471 sb.append(allowed.charAt(random.nextInt(allowed.length()))); 472 } 473 return sb.toString(); 474 } 475 } 476