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