• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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