• 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.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