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