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