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 static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK; 20 import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION; 21 import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_SAE; 22 import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION; 23 24 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_STATIC_CHIP_INFO; 25 26 import android.annotation.NonNull; 27 import android.app.compat.CompatChanges; 28 import android.content.Context; 29 import android.content.IntentFilter; 30 import android.content.pm.PackageManager; 31 import android.net.MacAddress; 32 import android.net.wifi.SoftApCapability; 33 import android.net.wifi.SoftApConfiguration; 34 import android.net.wifi.SoftApConfiguration.BandType; 35 import android.net.wifi.WifiContext; 36 import android.net.wifi.WifiSsid; 37 import android.net.wifi.util.WifiResourceCache; 38 import android.os.Handler; 39 import android.os.Process; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import android.util.SparseIntArray; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.modules.utils.build.SdkLevel; 46 import com.android.net.module.util.MacAddressUtils; 47 import com.android.server.wifi.util.ApConfigUtil; 48 import com.android.wifi.resources.R; 49 50 import java.nio.charset.CharsetEncoder; 51 import java.nio.charset.StandardCharsets; 52 import java.security.SecureRandom; 53 import java.util.ArrayList; 54 import java.util.Objects; 55 import java.util.Random; 56 57 import javax.annotation.Nullable; 58 59 /** 60 * Provides API for reading/writing soft access point configuration. 61 */ 62 public class WifiApConfigStore { 63 64 // Intent when user has interacted with the softap settings change notification 65 public static final String ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT = 66 "com.android.server.wifi.WifiApConfigStoreUtil.HOTSPOT_CONFIG_USER_TAPPED_CONTENT"; 67 68 private static final String TAG = "WifiApConfigStore"; 69 70 private static final int RAND_SSID_INT_MIN = 1000; 71 private static final int RAND_SSID_INT_MAX = 9999; 72 73 @VisibleForTesting 74 static final int SAE_ASCII_MIN_LEN = 1; 75 @VisibleForTesting 76 static final int PSK_ASCII_MIN_LEN = 8; 77 @VisibleForTesting 78 static final int PSK_SAE_ASCII_MAX_LEN = 63; 79 80 // Should only be accessed via synchronized methods. 81 private SoftApConfiguration mPersistentWifiApConfig = null; 82 private String mLastConfiguredPassphrase = null; 83 84 private final WifiContext mContext; 85 private final Handler mHandler; 86 private final WifiMetrics mWifiMetrics; 87 private final BackupManagerProxy mBackupManagerProxy; 88 private final MacAddressUtil mMacAddressUtil; 89 private final WifiConfigManager mWifiConfigManager; 90 private final ActiveModeWarden mActiveModeWarden; 91 private final WifiNative mWifiNative; 92 private final HalDeviceManager mHalDeviceManager; 93 private final WifiSettingsConfigStore mWifiSettingsConfigStore; 94 private boolean mHasNewDataToSerialize = false; 95 private boolean mForceApChannel = false; 96 private int mForcedApBand; 97 private int mForcedApChannel; 98 private int mForcedApMaximumChannelBandWidth; 99 private final boolean mIsAutoAppendLowerBandEnabled; 100 private final WifiResourceCache mResourceCache; 101 102 /** 103 * Module to interact with the wifi config store. 104 */ 105 private class SoftApStoreDataSource implements SoftApStoreData.DataSource { 106 toSerialize()107 public SoftApConfiguration toSerialize() { 108 mHasNewDataToSerialize = false; 109 return mPersistentWifiApConfig; 110 } 111 fromDeserialized(SoftApConfiguration config)112 public void fromDeserialized(SoftApConfiguration config) { 113 if (config.getPersistentRandomizedMacAddress() == null) { 114 config = updatePersistentRandomizedMacAddress(config); 115 } 116 mPersistentWifiApConfig = new SoftApConfiguration.Builder(config).build(); 117 if (!TextUtils.isEmpty(mPersistentWifiApConfig.getPassphrase())) { 118 mLastConfiguredPassphrase = mPersistentWifiApConfig.getPassphrase(); 119 } 120 } 121 reset()122 public void reset() { 123 mPersistentWifiApConfig = null; 124 } 125 hasNewDataToSerialize()126 public boolean hasNewDataToSerialize() { 127 return mHasNewDataToSerialize; 128 } 129 } 130 WifiApConfigStore(WifiContext context, WifiInjector wifiInjector, Handler handler, BackupManagerProxy backupManagerProxy, WifiConfigStore wifiConfigStore, WifiConfigManager wifiConfigManager, ActiveModeWarden activeModeWarden, WifiMetrics wifiMetrics)131 WifiApConfigStore(WifiContext context, 132 WifiInjector wifiInjector, 133 Handler handler, 134 BackupManagerProxy backupManagerProxy, 135 WifiConfigStore wifiConfigStore, 136 WifiConfigManager wifiConfigManager, 137 ActiveModeWarden activeModeWarden, 138 WifiMetrics wifiMetrics) { 139 mContext = context; 140 mHandler = handler; 141 mBackupManagerProxy = backupManagerProxy; 142 mWifiConfigManager = wifiConfigManager; 143 mActiveModeWarden = activeModeWarden; 144 mWifiMetrics = wifiMetrics; 145 mWifiNative = wifiInjector.getWifiNative(); 146 // Register store data listener 147 wifiConfigStore.registerStoreData( 148 wifiInjector.makeSoftApStoreData(new SoftApStoreDataSource())); 149 150 IntentFilter filter = new IntentFilter(); 151 filter.addAction(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT); 152 mMacAddressUtil = wifiInjector.getMacAddressUtil(); 153 mResourceCache = context.getResourceCache(); 154 mIsAutoAppendLowerBandEnabled = mResourceCache.getBoolean( 155 R.bool.config_wifiSoftapAutoAppendLowerBandsToBandConfigurationEnabled); 156 mHalDeviceManager = wifiInjector.getHalDeviceManager(); 157 mWifiSettingsConfigStore = wifiInjector.getSettingsConfigStore(); 158 mWifiSettingsConfigStore.registerChangeListener(WIFI_STATIC_CHIP_INFO, 159 (key, value) -> { 160 if (mPersistentWifiApConfig != null 161 && mHalDeviceManager.isConcurrencyComboLoadedFromDriver()) { 162 Log.i(TAG, "Chip capability is updated, check config"); 163 SoftApConfiguration.Builder configBuilder = 164 new SoftApConfiguration.Builder(mPersistentWifiApConfig); 165 if (SdkLevel.isAtLeastS() 166 && mPersistentWifiApConfig.getBands().length > 1) { 167 // Current band setting is dual band, check if device supports it. 168 if (!ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative)) { 169 Log.i(TAG, "Chip doesn't support bridgedAp, reset to default band"); 170 configBuilder.setBand(generateDefaultBand(mContext)); 171 persistConfigAndTriggerBackupManagerProxy(configBuilder.build()); 172 } 173 } 174 } 175 }, mHandler); 176 } 177 178 /** 179 * Return the current soft access point configuration. 180 */ getApConfiguration()181 public synchronized SoftApConfiguration getApConfiguration() { 182 if (mPersistentWifiApConfig == null) { 183 /* Use default configuration. */ 184 Log.d(TAG, "Fallback to use default AP configuration"); 185 persistConfigAndTriggerBackupManagerProxy( 186 updatePersistentRandomizedMacAddress(getDefaultApConfiguration())); 187 } 188 SoftApConfiguration sanitizedPersistentconfig = 189 sanitizePersistentApConfig(mPersistentWifiApConfig); 190 if (!Objects.equals(mPersistentWifiApConfig, sanitizedPersistentconfig)) { 191 Log.d(TAG, "persisted config was converted, need to resave it"); 192 persistConfigAndTriggerBackupManagerProxy(sanitizedPersistentconfig); 193 } 194 195 if (mForceApChannel) { 196 Log.d(TAG, "getApConfiguration: Band force to " 197 + mForcedApBand 198 + ", and channel force to " 199 + mForcedApChannel 200 + ", and maximum channel width limited to " 201 + mForcedApMaximumChannelBandWidth); 202 if (SdkLevel.isAtLeastT()) { 203 return mForcedApChannel == 0 204 ? new SoftApConfiguration.Builder(mPersistentWifiApConfig) 205 .setBand(mForcedApBand) 206 .setMaxChannelBandwidth(mForcedApMaximumChannelBandWidth) 207 .build() 208 : new SoftApConfiguration.Builder(mPersistentWifiApConfig) 209 .setChannel(mForcedApChannel, mForcedApBand) 210 .setMaxChannelBandwidth(mForcedApMaximumChannelBandWidth) 211 .build(); 212 } else { 213 return mForcedApChannel == 0 214 ? new SoftApConfiguration.Builder(mPersistentWifiApConfig) 215 .setBand(mForcedApBand) 216 .build() 217 : new SoftApConfiguration.Builder(mPersistentWifiApConfig) 218 .setChannel(mForcedApChannel, mForcedApBand) 219 .build(); 220 } 221 } 222 return mPersistentWifiApConfig; 223 } 224 225 /** 226 * Update the current soft access point configuration. 227 * Restore to default AP configuration if null is provided. 228 * This can be invoked under context of binder threads (WifiManager.setWifiApConfiguration) 229 * and the main Wifi thread (CMD_START_AP). 230 */ setApConfiguration(SoftApConfiguration config)231 public synchronized void setApConfiguration(SoftApConfiguration config) { 232 SoftApConfiguration newConfig = config == null ? getDefaultApConfiguration() 233 : new SoftApConfiguration.Builder(sanitizePersistentApConfig(config)) 234 .setUserConfiguration(true).build(); 235 persistConfigAndTriggerBackupManagerProxy( 236 updatePersistentRandomizedMacAddress(newConfig)); 237 } 238 239 /** 240 * Returns SoftApConfiguration in which some parameters might be upgrade to supported default 241 * configuration. 242 */ upgradeSoftApConfiguration( @onNull SoftApConfiguration config)243 public synchronized SoftApConfiguration upgradeSoftApConfiguration( 244 @NonNull SoftApConfiguration config) { 245 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config); 246 if (SdkLevel.isAtLeastS() && ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative) 247 && config.getBands().length == 1 && mResourceCache.getBoolean( 248 R.bool.config_wifiSoftapAutoUpgradeToBridgedConfigWhenSupported)) { 249 int[] dual_bands = new int[] { 250 SoftApConfiguration.BAND_2GHZ, 251 SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ}; 252 if (SdkLevel.isAtLeastS()) { 253 configBuilder.setBands(dual_bands); 254 } 255 Log.i(TAG, "Device support bridged AP, upgrade band setting to bridged configuration"); 256 } 257 return configBuilder.build(); 258 } 259 260 /** 261 * Returns SoftApConfiguration in which some parameters might be reset to supported default 262 * config since it depends on UI or HW. 263 * 264 * MaxNumberOfClients and isClientControlByUserEnabled will need HAL support client force 265 * disconnect, and Band setting (5g/6g) need HW support. 266 * 267 * HiddenSsid, Channel, ShutdownTimeoutMillis and AutoShutdownEnabled are features 268 * which need UI(Setting) support. 269 * 270 * SAE/SAE-Transition need hardware support, reset to secured WPA2 security type when device 271 * doesn't support it. 272 * 273 * Check band(s) setting to make sure all of the band(s) are supported. 274 * - If previous bands configuration is bridged mode. Reset to 2.4G when device doesn't support 275 * it. 276 */ resetToDefaultForUnsupportedConfig( @onNull SoftApConfiguration config)277 public synchronized SoftApConfiguration resetToDefaultForUnsupportedConfig( 278 @NonNull SoftApConfiguration config) { 279 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config); 280 if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext) 281 || mResourceCache.getBoolean( 282 R.bool.config_wifiSoftapResetUserControlConfig)) 283 && (config.isClientControlByUserEnabled() 284 || config.getBlockedClientList().size() != 0)) { 285 configBuilder.setClientControlByUserEnabled(false); 286 configBuilder.setBlockedClientList(new ArrayList<>()); 287 Log.i(TAG, "Reset ClientControlByUser to false due to device doesn't support"); 288 } 289 290 if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext) 291 || mResourceCache.getBoolean( 292 R.bool.config_wifiSoftapResetMaxClientSettingConfig)) 293 && config.getMaxNumberOfClients() != 0) { 294 configBuilder.setMaxNumberOfClients(0); 295 Log.i(TAG, "Reset MaxNumberOfClients to 0 due to device doesn't support"); 296 } 297 298 if (!ApConfigUtil.isWpa3SaeSupported(mContext) && (config.getSecurityType() 299 == SECURITY_TYPE_WPA3_SAE 300 || config.getSecurityType() 301 == SECURITY_TYPE_WPA3_SAE_TRANSITION)) { 302 try { 303 configBuilder.setPassphrase(generatePassword(), 304 SECURITY_TYPE_WPA2_PSK); 305 } catch (IllegalArgumentException e) { 306 Log.wtf(TAG, "Generated password was invalid: " + e); 307 } 308 Log.i(TAG, "Device doesn't support WPA3-SAE, reset config to WPA2"); 309 } 310 311 if (mResourceCache.getBoolean(R.bool.config_wifiSoftapResetChannelConfig) 312 && config.getChannel() != 0) { 313 // The device might not support customize channel or forced channel might not 314 // work in some countries. Need to reset it. 315 configBuilder.setBand(ApConfigUtil.append24GToBandIf24GSupported( 316 config.getBand(), mContext)); 317 Log.i(TAG, "Reset SAP channel configuration"); 318 } 319 320 if (SdkLevel.isAtLeastS() && config.getBands().length > 1) { 321 if (!ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative) 322 || !isBandsSupported(config.getBands(), mContext)) { 323 int newSingleApBand = 0; 324 for (int targetBand : config.getBands()) { 325 int availableBand = ApConfigUtil.removeUnsupportedBands( 326 mContext, targetBand); 327 newSingleApBand |= availableBand; 328 } 329 newSingleApBand = ApConfigUtil.append24GToBandIf24GSupported( 330 newSingleApBand, mContext); 331 configBuilder.setBand(newSingleApBand); 332 Log.i(TAG, "An unsupported band setting for the bridged mode, force to " 333 + newSingleApBand); 334 } 335 } else { 336 // Single band case, check and remove unsupported band. 337 int newBand = ApConfigUtil.removeUnsupportedBands(mContext, config.getBand()); 338 if (newBand != config.getBand()) { 339 newBand = ApConfigUtil.append24GToBandIf24GSupported(newBand, mContext); 340 Log.i(TAG, "Reset band from " + config.getBand() + " to " 341 + newBand); 342 configBuilder.setBand(newBand); 343 } 344 } 345 346 if (mResourceCache.getBoolean(R.bool.config_wifiSoftapResetHiddenConfig) 347 && config.isHiddenSsid()) { 348 configBuilder.setHiddenSsid(false); 349 Log.i(TAG, "Reset SAP Hidden Network configuration"); 350 } 351 352 if (mResourceCache.getBoolean( 353 R.bool.config_wifiSoftapResetAutoShutdownTimerConfig) 354 && config.getShutdownTimeoutMillis() > 0) { 355 if (CompatChanges.isChangeEnabled( 356 SoftApConfiguration.REMOVE_ZERO_FOR_TIMEOUT_SETTING)) { 357 configBuilder.setShutdownTimeoutMillis(SoftApConfiguration.DEFAULT_TIMEOUT); 358 } else { 359 configBuilder.setShutdownTimeoutMillis(0); 360 } 361 Log.i(TAG, "Reset SAP auto shutdown configuration"); 362 } 363 364 if (!ApConfigUtil.isApMacRandomizationSupported(mContext)) { 365 if (SdkLevel.isAtLeastS()) { 366 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE); 367 Log.i(TAG, "Force set SAP MAC randomization to NONE when not supported"); 368 } 369 } 370 371 mWifiMetrics.noteSoftApConfigReset(config, configBuilder.build()); 372 return configBuilder.build(); 373 } 374 sanitizePersistentApConfig(SoftApConfiguration config)375 private SoftApConfiguration sanitizePersistentApConfig(SoftApConfiguration config) { 376 SoftApConfiguration.Builder convertedConfigBuilder = 377 new SoftApConfiguration.Builder(config); 378 int[] bands = config.getBands(); 379 SparseIntArray newChannels = new SparseIntArray(); 380 // The bands length should always 1 in R. Adding SdkLevel.isAtLeastS for lint check only. 381 for (int i = 0; i < bands.length; i++) { 382 int channel = SdkLevel.isAtLeastS() 383 ? config.getChannels().valueAt(i) : config.getChannel(); 384 int newBand = bands[i]; 385 if (channel == 0 && mIsAutoAppendLowerBandEnabled 386 && ApConfigUtil.isBandSupported(newBand, mContext)) { 387 // some countries are unable to support 5GHz only operation, always allow for 2GHz 388 // when config doesn't force channel 389 if ((newBand & SoftApConfiguration.BAND_2GHZ) == 0) { 390 newBand = ApConfigUtil.append24GToBandIf24GSupported(newBand, mContext); 391 } 392 // If the 6G configuration doesn't includes 5G band (2.4G have appended because 393 // countries reason), it will cause that driver can't switch channel from 6G to 394 // 5G/2.4G when coexistence happened (For instance: wifi connected to 2.4G or 5G 395 // channel). Always append 5G into band configuration when configured band includes 396 // 6G. 397 if ((newBand & SoftApConfiguration.BAND_6GHZ) != 0 398 && (newBand & SoftApConfiguration.BAND_5GHZ) == 0) { 399 newBand = ApConfigUtil.append5GToBandIf5GSupported(newBand, mContext); 400 } 401 } 402 newChannels.put(newBand, channel); 403 } 404 if (SdkLevel.isAtLeastS()) { 405 convertedConfigBuilder.setChannels(newChannels); 406 } else if (bands.length > 0 && newChannels.valueAt(0) == 0) { 407 convertedConfigBuilder.setBand(newChannels.keyAt(0)); 408 } 409 return convertedConfigBuilder.build(); 410 } 411 persistConfigAndTriggerBackupManagerProxy( SoftApConfiguration config)412 private synchronized void persistConfigAndTriggerBackupManagerProxy( 413 SoftApConfiguration config) { 414 mPersistentWifiApConfig = config; 415 if (!TextUtils.isEmpty(config.getPassphrase())) { 416 mLastConfiguredPassphrase = config.getPassphrase(); 417 } 418 mHasNewDataToSerialize = true; 419 mHandler.post(() -> mWifiConfigManager.saveToStore()); 420 mBackupManagerProxy.notifyDataChanged(); 421 } 422 423 /** 424 * Generate a default WPA3 SAE transition (if supported) or WPA2 based 425 * configuration with a random password. 426 * We are changing the Wifi Ap configuration storage from secure settings to a 427 * flat file accessible only by the system. A WPA2 based default configuration 428 * will keep the device secure after the update. 429 */ getDefaultApConfiguration()430 private SoftApConfiguration getDefaultApConfiguration() { 431 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); 432 configBuilder.setBand(generateDefaultBand(mContext)); 433 configBuilder.setSsid(mResourceCache.getString( 434 R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid()); 435 try { 436 if (ApConfigUtil.isWpa3SaeSupported(mContext)) { 437 configBuilder.setPassphrase(generatePassword(), 438 SECURITY_TYPE_WPA3_SAE_TRANSITION); 439 } else { 440 configBuilder.setPassphrase(generatePassword(), 441 SECURITY_TYPE_WPA2_PSK); 442 } 443 } catch (IllegalArgumentException e) { 444 Log.wtf(TAG, "Generated password was invalid: " + e); 445 } 446 447 // It is new overlay configuration, it should always false in R. Add SdkLevel.isAtLeastS for 448 // lint check 449 if (SdkLevel.isAtLeastS()) { 450 boolean isBridgedModeSupported = mHalDeviceManager.isConcurrencyComboLoadedFromDriver() 451 ? ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative) 452 : ApConfigUtil.isBridgedModeSupportedInConfig(mContext); 453 if (isBridgedModeSupported) { 454 int[] dual_bands = new int[] { 455 SoftApConfiguration.BAND_2GHZ, 456 SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ}; 457 configBuilder.setBands(dual_bands); 458 } 459 } 460 461 // Update default MAC randomization setting to NONE when feature doesn't support it. 462 if (!ApConfigUtil.isApMacRandomizationSupported(mContext)) { 463 if (SdkLevel.isAtLeastS()) { 464 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE); 465 } 466 } 467 468 configBuilder.setUserConfiguration(false); 469 return configBuilder.build(); 470 } 471 getRandomIntForDefaultSsid()472 private static int getRandomIntForDefaultSsid() { 473 Random random = new Random(); 474 return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN; 475 } 476 generateLohsSsid(WifiContext context)477 private static String generateLohsSsid(WifiContext context) { 478 return context.getResourceCache().getString( 479 R.string.wifi_localhotspot_configure_ssid_default) + "_" 480 + getRandomIntForDefaultSsid(); 481 } 482 hasAutomotiveFeature(Context context)483 private static boolean hasAutomotiveFeature(Context context) { 484 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 485 } 486 487 /** 488 * Generate a temporary WPA2 based configuration for use by the local only hotspot. 489 * This config is not persisted and will not be stored by the WifiApConfigStore. 490 * 491 * @param context the context of wifi. 492 * @param customConfig customzied softap configuration. 493 * @param capability current softap capability. 494 * @param isExclusive whether customConfig is exclusive (set by privledged app). 495 * @return configuration of local only hotspot. 496 */ generateLocalOnlyHotspotConfig(@onNull WifiContext context, @Nullable SoftApConfiguration customConfig, @NonNull SoftApCapability capability, boolean isExclusive)497 public SoftApConfiguration generateLocalOnlyHotspotConfig(@NonNull WifiContext context, 498 @Nullable SoftApConfiguration customConfig, @NonNull SoftApCapability capability, 499 boolean isExclusive) { 500 SoftApConfiguration.Builder configBuilder; 501 boolean wasSsidAssigned = false; 502 if (customConfig != null && isExclusive) { 503 if (!TextUtils.isEmpty(customConfig.getSsid())) { 504 wasSsidAssigned = true; 505 } 506 configBuilder = new SoftApConfiguration.Builder(customConfig); 507 // Make sure that we use available band on old build. 508 if (!SdkLevel.isAtLeastT() 509 && !isBandsSupported(customConfig.getBands(), context)) { 510 configBuilder.setBand(generateDefaultBand(context)); 511 } 512 } else { 513 configBuilder = new SoftApConfiguration.Builder(); 514 if (customConfig != null && SdkLevel.isAtLeastS()) { 515 configBuilder.setChannels(customConfig.getChannels()); 516 } else { 517 // Make sure the default band configuration is supported. 518 configBuilder.setBand(generateDefaultBand(context)); 519 } 520 // Default to disable the auto shutdown 521 configBuilder.setAutoShutdownEnabled(false); 522 try { 523 if (ApConfigUtil.isWpa3SaeSupported(context)) { 524 if (customConfig != null 525 && customConfig.getBand() == SoftApConfiguration.BAND_6GHZ) { 526 // Requested band is limited to 6GHz only, use SAE. 527 configBuilder.setPassphrase(generatePassword(), 528 SECURITY_TYPE_WPA3_SAE); 529 } else { 530 configBuilder.setPassphrase(generatePassword(), 531 SECURITY_TYPE_WPA3_SAE_TRANSITION); 532 } 533 } else { 534 configBuilder.setPassphrase(generatePassword(), 535 SECURITY_TYPE_WPA2_PSK); 536 } 537 } catch (IllegalArgumentException e) { 538 Log.wtf(TAG, "Generated password was invalid: " + e); 539 } 540 synchronized (this) { 541 // Update default MAC randomization setting to NONE when feature doesn't support 542 // it, or it was disabled in tethered mode. 543 if (!ApConfigUtil.isApMacRandomizationSupported(context) 544 || (mPersistentWifiApConfig != null 545 && mPersistentWifiApConfig.getMacRandomizationSettingInternal() 546 == SoftApConfiguration.RANDOMIZATION_NONE)) { 547 if (SdkLevel.isAtLeastS()) { 548 configBuilder.setMacRandomizationSetting( 549 SoftApConfiguration.RANDOMIZATION_NONE); 550 } 551 } 552 } 553 } 554 555 // Automotive mode can force the LOHS to specific bands 556 if (hasAutomotiveFeature(context)) { 557 int desiredBand = SoftApConfiguration.BAND_2GHZ; 558 if (context.getResourceCache().getBoolean(R.bool.config_wifiLocalOnlyHotspot6ghz) 559 && ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_6GHZ, mContext)) { 560 desiredBand |= SoftApConfiguration.BAND_6GHZ; 561 } 562 if (context.getResourceCache().getBoolean(R.bool.config_wifi_local_only_hotspot_5ghz) 563 && ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_5GHZ, mContext)) { 564 desiredBand |= SoftApConfiguration.BAND_5GHZ; 565 } 566 configBuilder.setBand(desiredBand); 567 } 568 569 if (!wasSsidAssigned) { 570 configBuilder.setSsid(generateLohsSsid(context)); 571 } 572 573 return updatePersistentRandomizedMacAddress(configBuilder.build()); 574 } 575 576 /** 577 * @return a copy of the given SoftApConfig with the BSSID randomized, unless a custom BSSID is 578 * already set. 579 */ randomizeBssidIfUnset(Context context, SoftApConfiguration config)580 SoftApConfiguration randomizeBssidIfUnset(Context context, SoftApConfiguration config) { 581 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config); 582 if (config.getBssid() == null && ApConfigUtil.isApMacRandomizationSupported(mContext) 583 && config.getMacRandomizationSettingInternal() 584 != SoftApConfiguration.RANDOMIZATION_NONE) { 585 MacAddress macAddress = null; 586 if (config.getMacRandomizationSettingInternal() 587 == SoftApConfiguration.RANDOMIZATION_PERSISTENT) { 588 macAddress = config.getPersistentRandomizedMacAddress(); 589 if (macAddress == null) { 590 WifiSsid ssid = config.getWifiSsid(); 591 macAddress = mMacAddressUtil.calculatePersistentMacForSap( 592 ssid != null ? ssid.toString() : null, Process.WIFI_UID); 593 if (macAddress == null) { 594 Log.e(TAG, "Failed to calculate MAC from SSID. " 595 + "Generating new random MAC instead."); 596 } 597 } 598 } 599 if (macAddress == null) { 600 macAddress = MacAddressUtils.createRandomUnicastAddress(); 601 } 602 configBuilder.setBssid(macAddress); 603 if (macAddress != null && SdkLevel.isAtLeastS()) { 604 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE); 605 } 606 } 607 return configBuilder.build(); 608 } 609 610 /** 611 * Verify provided preSharedKey in ap config for WPA2_PSK/WPA3_SAE (Transition) network 612 * meets requirements. 613 */ 614 @SuppressWarnings("ReturnValueIgnored") validateApConfigAsciiPreSharedKey( @oftApConfiguration.SecurityType int securityType, String preSharedKey)615 private static boolean validateApConfigAsciiPreSharedKey( 616 @SoftApConfiguration.SecurityType int securityType, String preSharedKey) { 617 final int sharedKeyLen = preSharedKey.length(); 618 final int keyMinLen = securityType == SECURITY_TYPE_WPA3_SAE 619 ? SAE_ASCII_MIN_LEN : PSK_ASCII_MIN_LEN; 620 if (sharedKeyLen < keyMinLen || sharedKeyLen > PSK_SAE_ASCII_MAX_LEN) { 621 Log.d(TAG, "softap network password string size must be at least " + keyMinLen 622 + " and no more than " + PSK_SAE_ASCII_MAX_LEN + " when type is " 623 + securityType); 624 return false; 625 } 626 627 try { 628 preSharedKey.getBytes(StandardCharsets.UTF_8); 629 } catch (IllegalArgumentException e) { 630 Log.e(TAG, "softap network password verification failed: malformed string"); 631 return false; 632 } 633 return true; 634 } 635 636 /** 637 * Validate a SoftApConfiguration is properly configured for use by SoftApManager. 638 * 639 * This method checks for consistency between security settings (if it requires a password, was 640 * one provided?). 641 * 642 * @param apConfig {@link SoftApConfiguration} to use for softap mode 643 * @param isPrivileged indicate the caller can pass some fields check or not 644 * @param wifiNative to use native API to get iface combinations. 645 * @return boolean true if the provided config meets the minimum set of details, false 646 * otherwise. 647 */ validateApWifiConfiguration(@onNull SoftApConfiguration apConfig, boolean isPrivileged, WifiContext context, WifiNative wifiNative)648 static boolean validateApWifiConfiguration(@NonNull SoftApConfiguration apConfig, 649 boolean isPrivileged, WifiContext context, WifiNative wifiNative) { 650 // first check the SSID 651 WifiSsid ssid = apConfig.getWifiSsid(); 652 if (ssid == null || ssid.getBytes().length == 0) { 653 Log.d(TAG, "SSID for softap configuration cannot be null or 0 length."); 654 return false; 655 } 656 657 // BSSID can be set if caller own permission:android.Manifest.permission.NETWORK_SETTINGS. 658 if (apConfig.getBssid() != null && !isPrivileged) { 659 Log.e(TAG, "Config BSSID needs NETWORK_SETTINGS permission"); 660 return false; 661 } 662 663 String preSharedKey = apConfig.getPassphrase(); 664 boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey); 665 int authType; 666 667 try { 668 authType = apConfig.getSecurityType(); 669 } catch (IllegalStateException e) { 670 Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage()); 671 return false; 672 } 673 674 if (ApConfigUtil.isNonPasswordAP(authType)) { 675 // open networks should not have a password 676 if (hasPreSharedKey) { 677 Log.d(TAG, "open softap network should not have a password"); 678 return false; 679 } 680 } else if (authType == SECURITY_TYPE_WPA2_PSK 681 || authType == SECURITY_TYPE_WPA3_SAE_TRANSITION 682 || authType == SECURITY_TYPE_WPA3_SAE) { 683 // this is a config that should have a password - check that first 684 if (!hasPreSharedKey) { 685 Log.d(TAG, "softap network password must be set"); 686 return false; 687 } 688 689 if (context.getResourceCache().getBoolean( 690 R.bool.config_wifiSoftapPassphraseAsciiEncodableCheck)) { 691 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 692 if (!asciiEncoder.canEncode(preSharedKey)) { 693 Log.d(TAG, "passphrase not ASCII encodable"); 694 return false; 695 } 696 if (!validateApConfigAsciiPreSharedKey(authType, preSharedKey)) { 697 // failed preSharedKey checks for WPA2 and WPA3 SAE (Transition) mode. 698 return false; 699 } 700 } 701 } else { 702 // this is not a supported security type 703 Log.d(TAG, "softap configs must either be open or WPA2 PSK networks"); 704 return false; 705 } 706 707 if (!isBandsSupported(apConfig.getBands(), context)) { 708 return false; 709 } 710 711 if (ApConfigUtil.isSecurityTypeRestrictedFor6gBand(authType)) { 712 for (int band : apConfig.getBands()) { 713 // Only return failure if requested band is limited to 6GHz only 714 if (band == SoftApConfiguration.BAND_6GHZ 715 && !ApConfigUtil.canHALConvertRestrictedSecurityTypeFor6GHz( 716 context.getResourceCache(), authType)) { 717 Log.d(TAG, "security type: " + authType 718 + " is not allowed for softap in 6GHz band"); 719 return false; 720 } 721 } 722 } 723 724 if (SdkLevel.isAtLeastT() 725 && authType == SECURITY_TYPE_WPA3_OWE_TRANSITION) { 726 if (!ApConfigUtil.isBridgedModeSupported(context, wifiNative)) { 727 Log.d(TAG, "softap owe transition needs bridge mode support"); 728 return false; 729 } else if (apConfig.getBands().length > 1) { 730 Log.d(TAG, "softap owe transition must use single band"); 731 return false; 732 } 733 } 734 735 // Hostapd requires 11AX to configure 11BE 736 if (SdkLevel.isAtLeastB() && apConfig.isIeee80211beEnabled() 737 && !apConfig.isIeee80211axEnabledInternal()) { 738 Log.d(TAG, "11AX is required when configuring 11BE"); 739 return false; 740 } 741 742 return true; 743 } 744 generatePassword()745 private static String generatePassword() { 746 // Characters that will be used for password generation. Some characters commonly known to 747 // be confusing like 0 and O excluded from this list. 748 final String allowed = "23456789abcdefghijkmnpqrstuvwxyz"; 749 final int passLength = 15; 750 751 StringBuilder sb = new StringBuilder(passLength); 752 SecureRandom random = new SecureRandom(); 753 for (int i = 0; i < passLength; i++) { 754 sb.append(allowed.charAt(random.nextInt(allowed.length()))); 755 } 756 return sb.toString(); 757 } 758 759 /** 760 * Generate default band base on supported band configuration. 761 * 762 * @param context The caller context used to get value from resource file. 763 * @return A band which will be used for a default band in default configuration. 764 */ generateDefaultBand(WifiContext context)765 public static @BandType int generateDefaultBand(WifiContext context) { 766 for (int band : SoftApConfiguration.BAND_TYPES) { 767 if (ApConfigUtil.isBandSupported(band, context)) { 768 return band; 769 } 770 } 771 Log.e(TAG, "Invalid overlay configuration! No any band supported on SoftAp"); 772 return SoftApConfiguration.BAND_2GHZ; 773 } 774 isBandsSupported(@onNull int[] apBands, WifiContext context)775 private static boolean isBandsSupported(@NonNull int[] apBands, WifiContext context) { 776 for (int band : apBands) { 777 if (!ApConfigUtil.isBandSupported(band, context)) { 778 return false; 779 } 780 } 781 return true; 782 } 783 784 /** 785 * Enable force-soft-AP-channel mode which takes effect when soft AP starts next time 786 * 787 * @param forcedApBand The forced band. 788 * @param forcedApChannel The forced IEEE channel number or 0 when forced AP band only. 789 * @param forcedApMaximumChannelBandWidth The forced maximum channel bandwidth. 790 */ enableForceSoftApBandOrChannel( @andType int forcedApBand, int forcedApChannel, int forcedApMaximumChannelBandWidth)791 public synchronized void enableForceSoftApBandOrChannel( 792 @BandType int forcedApBand, int forcedApChannel, int forcedApMaximumChannelBandWidth) { 793 mForceApChannel = true; 794 mForcedApChannel = forcedApChannel; 795 mForcedApBand = forcedApBand; 796 mForcedApMaximumChannelBandWidth = forcedApMaximumChannelBandWidth; 797 } 798 799 /** 800 * Disable force-soft-AP-channel mode which take effect when soft AP starts next time 801 */ disableForceSoftApBandOrChannel()802 public synchronized void disableForceSoftApBandOrChannel() { 803 mForceApChannel = false; 804 } 805 updatePersistentRandomizedMacAddress(SoftApConfiguration config)806 private SoftApConfiguration updatePersistentRandomizedMacAddress(SoftApConfiguration config) { 807 // Update randomized MacAddress 808 WifiSsid ssid = config.getWifiSsid(); 809 MacAddress randomizedMacAddress = mMacAddressUtil.calculatePersistentMacForSap( 810 ssid != null ? ssid.toString() : null, Process.WIFI_UID); 811 if (randomizedMacAddress != null) { 812 return new SoftApConfiguration.Builder(config) 813 .setRandomizedMacAddress(randomizedMacAddress).build(); 814 } 815 816 if (config.getPersistentRandomizedMacAddress() != null) { 817 return config; 818 } 819 820 randomizedMacAddress = MacAddressUtils.createRandomUnicastAddress(); 821 return new SoftApConfiguration.Builder(config) 822 .setRandomizedMacAddress(randomizedMacAddress).build(); 823 } 824 825 /** 826 * Returns the last configured Wi-Fi tethered AP passphrase. 827 */ getLastConfiguredTetheredApPassphraseSinceBoot()828 public synchronized String getLastConfiguredTetheredApPassphraseSinceBoot() { 829 return mLastConfiguredPassphrase; 830 } 831 } 832