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 android.annotation.NonNull;
20 import android.content.Context;
21 import android.content.IntentFilter;
22 import android.net.MacAddress;
23 import android.net.util.MacAddressUtils;
24 import android.net.wifi.SoftApConfiguration;
25 import android.os.Handler;
26 import android.os.Process;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.server.wifi.util.ApConfigUtil;
32 import com.android.wifi.resources.R;
33 
34 import java.nio.charset.StandardCharsets;
35 import java.security.SecureRandom;
36 import java.util.ArrayList;
37 import java.util.Random;
38 
39 import javax.annotation.Nullable;
40 
41 /**
42  * Provides API for reading/writing soft access point configuration.
43  */
44 public class WifiApConfigStore {
45 
46     // Intent when user has interacted with the softap settings change notification
47     public static final String ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT =
48             "com.android.server.wifi.WifiApConfigStoreUtil.HOTSPOT_CONFIG_USER_TAPPED_CONTENT";
49 
50     private static final String TAG = "WifiApConfigStore";
51 
52     private static final int RAND_SSID_INT_MIN = 1000;
53     private static final int RAND_SSID_INT_MAX = 9999;
54 
55     @VisibleForTesting
56     static final int SSID_MIN_LEN = 1;
57     @VisibleForTesting
58     static final int SSID_MAX_LEN = 32;
59     @VisibleForTesting
60     static final int PSK_MIN_LEN = 8;
61     @VisibleForTesting
62     static final int PSK_MAX_LEN = 63;
63 
64     private SoftApConfiguration mPersistentWifiApConfig = null;
65 
66     private final Context mContext;
67     private final Handler mHandler;
68     private final WifiMetrics mWifiMetrics;
69     private final BackupManagerProxy mBackupManagerProxy;
70     private final MacAddressUtil mMacAddressUtil;
71     private final WifiConfigManager mWifiConfigManager;
72     private final ActiveModeWarden mActiveModeWarden;
73     private boolean mHasNewDataToSerialize = false;
74 
75     /**
76      * Module to interact with the wifi config store.
77      */
78     private class SoftApStoreDataSource implements SoftApStoreData.DataSource {
79 
toSerialize()80         public SoftApConfiguration toSerialize() {
81             mHasNewDataToSerialize = false;
82             return mPersistentWifiApConfig;
83         }
84 
fromDeserialized(SoftApConfiguration config)85         public void fromDeserialized(SoftApConfiguration config) {
86             mPersistentWifiApConfig = new SoftApConfiguration.Builder(config).build();
87         }
88 
reset()89         public void reset() {
90             mPersistentWifiApConfig = null;
91         }
92 
hasNewDataToSerialize()93         public boolean hasNewDataToSerialize() {
94             return mHasNewDataToSerialize;
95         }
96     }
97 
WifiApConfigStore(Context context, WifiInjector wifiInjector, Handler handler, BackupManagerProxy backupManagerProxy, WifiConfigStore wifiConfigStore, WifiConfigManager wifiConfigManager, ActiveModeWarden activeModeWarden, WifiMetrics wifiMetrics)98     WifiApConfigStore(Context context,
99             WifiInjector wifiInjector,
100             Handler handler,
101             BackupManagerProxy backupManagerProxy,
102             WifiConfigStore wifiConfigStore,
103             WifiConfigManager wifiConfigManager,
104             ActiveModeWarden activeModeWarden,
105             WifiMetrics wifiMetrics) {
106         mContext = context;
107         mHandler = handler;
108         mBackupManagerProxy = backupManagerProxy;
109         mWifiConfigManager = wifiConfigManager;
110         mActiveModeWarden = activeModeWarden;
111         mWifiMetrics = wifiMetrics;
112 
113         // Register store data listener
114         wifiConfigStore.registerStoreData(
115                 wifiInjector.makeSoftApStoreData(new SoftApStoreDataSource()));
116 
117         IntentFilter filter = new IntentFilter();
118         filter.addAction(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT);
119         mMacAddressUtil = wifiInjector.getMacAddressUtil();
120     }
121 
122     /**
123      * Return the current soft access point configuration.
124      */
getApConfiguration()125     public synchronized SoftApConfiguration getApConfiguration() {
126         if (mPersistentWifiApConfig == null) {
127             /* Use default configuration. */
128             Log.d(TAG, "Fallback to use default AP configuration");
129             persistConfigAndTriggerBackupManagerProxy(getDefaultApConfiguration());
130         }
131         SoftApConfiguration sanitizedPersistentconfig =
132                 sanitizePersistentApConfig(mPersistentWifiApConfig);
133         if (mPersistentWifiApConfig != sanitizedPersistentconfig) {
134             Log.d(TAG, "persisted config was converted, need to resave it");
135             persistConfigAndTriggerBackupManagerProxy(sanitizedPersistentconfig);
136         }
137         return mPersistentWifiApConfig;
138     }
139 
140     /**
141      * Update the current soft access point configuration.
142      * Restore to default AP configuration if null is provided.
143      * This can be invoked under context of binder threads (WifiManager.setWifiApConfiguration)
144      * and the main Wifi thread (CMD_START_AP).
145      */
setApConfiguration(SoftApConfiguration config)146     public synchronized void setApConfiguration(SoftApConfiguration config) {
147         if (config == null) {
148             config = getDefaultApConfiguration();
149         } else {
150             config = sanitizePersistentApConfig(config);
151         }
152         persistConfigAndTriggerBackupManagerProxy(config);
153     }
154 
155     /**
156      * Returns SoftApConfiguration in which some parameters might be reset to supported default
157      * config since it depends on UI or HW.
158      *
159      * MaxNumberOfClients and isClientControlByUserEnabled will need HAL support client force
160      * disconnect, and Band setting (5g/6g) need HW support.
161      *
162      * HiddenSsid, Channel, ShutdownTimeoutMillis and AutoShutdownEnabled are features
163      * which need UI(Setting) support.
164      *
165      * SAE/SAE-Transition need hardware support, reset to secured WPA2 security type when device
166      * doesn't support it.
167      */
resetToDefaultForUnsupportedConfig( @onNull SoftApConfiguration config)168     public SoftApConfiguration resetToDefaultForUnsupportedConfig(
169             @NonNull SoftApConfiguration config) {
170         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
171         if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext)
172                 || mContext.getResources().getBoolean(
173                 R.bool.config_wifiSoftapResetUserControlConfig))
174                 && (config.isClientControlByUserEnabled()
175                 || config.getBlockedClientList().size() != 0)) {
176             configBuilder.setClientControlByUserEnabled(false);
177             configBuilder.setBlockedClientList(new ArrayList<>());
178             Log.i(TAG, "Reset ClientControlByUser to false due to device doesn't support");
179         }
180 
181         if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext)
182                 || mContext.getResources().getBoolean(
183                 R.bool.config_wifiSoftapResetMaxClientSettingConfig))
184                 && config.getMaxNumberOfClients() != 0) {
185             configBuilder.setMaxNumberOfClients(0);
186             Log.i(TAG, "Reset MaxNumberOfClients to 0 due to device doesn't support");
187         }
188 
189         if (!ApConfigUtil.isWpa3SaeSupported(mContext) && (config.getSecurityType()
190                 == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE
191                 || config.getSecurityType()
192                 == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)) {
193             configBuilder.setPassphrase(generatePassword(),
194                     SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
195             Log.i(TAG, "Device doesn't support WPA3-SAE, reset config to WPA2");
196         }
197 
198         if (mContext.getResources().getBoolean(R.bool.config_wifiSoftapResetChannelConfig)
199                 && config.getChannel() != 0) {
200             // The device might not support customize channel or forced channel might not
201             // work in some countries. Need to reset it.
202             // Add 2.4G by default
203             configBuilder.setBand(config.getBand() | SoftApConfiguration.BAND_2GHZ);
204             Log.i(TAG, "Reset SAP channel configuration");
205         }
206 
207         int newBand = config.getBand();
208         if (!mContext.getResources().getBoolean(R.bool.config_wifi6ghzSupport)
209                 && (newBand & SoftApConfiguration.BAND_6GHZ) != 0) {
210             newBand &= ~SoftApConfiguration.BAND_6GHZ;
211             Log.i(TAG, "Device doesn't support 6g, remove 6G band from band setting");
212         }
213 
214         if (!mContext.getResources().getBoolean(R.bool.config_wifi5ghzSupport)
215                 && (newBand & SoftApConfiguration.BAND_5GHZ) != 0) {
216             newBand &= ~SoftApConfiguration.BAND_5GHZ;
217             Log.i(TAG, "Device doesn't support 5g, remove 5G band from band setting");
218         }
219 
220         if (newBand != config.getBand()) {
221             // Always added 2.4G by default when reset the band.
222             Log.i(TAG, "Reset band from " + config.getBand() + " to "
223                     + (newBand | SoftApConfiguration.BAND_2GHZ));
224             configBuilder.setBand(newBand | SoftApConfiguration.BAND_2GHZ);
225         }
226 
227         if (mContext.getResources().getBoolean(R.bool.config_wifiSoftapResetHiddenConfig)
228                 && config.isHiddenSsid()) {
229             configBuilder.setHiddenSsid(false);
230             Log.i(TAG, "Reset SAP Hidden Network configuration");
231         }
232 
233         if (mContext.getResources().getBoolean(
234                 R.bool.config_wifiSoftapResetAutoShutdownTimerConfig)
235                 && config.getShutdownTimeoutMillis() != 0) {
236             configBuilder.setShutdownTimeoutMillis(0);
237             Log.i(TAG, "Reset SAP auto shutdown configuration");
238         }
239 
240         mWifiMetrics.noteSoftApConfigReset(config, configBuilder.build());
241         return configBuilder.build();
242     }
243 
sanitizePersistentApConfig(SoftApConfiguration config)244     private SoftApConfiguration sanitizePersistentApConfig(SoftApConfiguration config) {
245         SoftApConfiguration.Builder convertedConfigBuilder = null;
246 
247         // some countries are unable to support 5GHz only operation, always allow for 2GHz when
248         // config doesn't force channel
249         if (config.getChannel() == 0 && (config.getBand() & SoftApConfiguration.BAND_2GHZ) == 0) {
250             Log.w(TAG, "Supplied ap config band without 2.4G, add allowing for 2.4GHz");
251             if (convertedConfigBuilder == null) {
252                 convertedConfigBuilder = new SoftApConfiguration.Builder(config);
253             }
254             convertedConfigBuilder.setBand(config.getBand() | SoftApConfiguration.BAND_2GHZ);
255         }
256         return convertedConfigBuilder == null ? config : convertedConfigBuilder.build();
257     }
258 
persistConfigAndTriggerBackupManagerProxy(SoftApConfiguration config)259     private void persistConfigAndTriggerBackupManagerProxy(SoftApConfiguration config) {
260         mPersistentWifiApConfig = config;
261         mHasNewDataToSerialize = true;
262         mWifiConfigManager.saveToStore(true);
263         mBackupManagerProxy.notifyDataChanged();
264     }
265 
266     /**
267      * Generate a default WPA3 SAE transition (if supported) or WPA2 based
268      * configuration with a random password.
269      * We are changing the Wifi Ap configuration storage from secure settings to a
270      * flat file accessible only by the system. A WPA2 based default configuration
271      * will keep the device secure after the update.
272      */
getDefaultApConfiguration()273     private SoftApConfiguration getDefaultApConfiguration() {
274         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
275         configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
276         configBuilder.setSsid(mContext.getResources().getString(
277                 R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid());
278         if (ApConfigUtil.isWpa3SaeSupported(mContext)) {
279             configBuilder.setPassphrase(generatePassword(),
280                     SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
281         } else {
282             configBuilder.setPassphrase(generatePassword(),
283                     SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
284         }
285         return configBuilder.build();
286     }
287 
getRandomIntForDefaultSsid()288     private static int getRandomIntForDefaultSsid() {
289         Random random = new Random();
290         return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN;
291     }
292 
generateLohsSsid(Context context)293     private static String generateLohsSsid(Context context) {
294         return context.getResources().getString(
295                 R.string.wifi_localhotspot_configure_ssid_default) + "_"
296                 + getRandomIntForDefaultSsid();
297     }
298 
299     /**
300      * Generate a temporary WPA2 based configuration for use by the local only hotspot.
301      * This config is not persisted and will not be stored by the WifiApConfigStore.
302      */
generateLocalOnlyHotspotConfig(Context context, int apBand, @Nullable SoftApConfiguration customConfig)303     public static SoftApConfiguration generateLocalOnlyHotspotConfig(Context context, int apBand,
304             @Nullable SoftApConfiguration customConfig) {
305         SoftApConfiguration.Builder configBuilder;
306         if (customConfig != null) {
307             configBuilder = new SoftApConfiguration.Builder(customConfig);
308         } else {
309             configBuilder = new SoftApConfiguration.Builder();
310             // Default to disable the auto shutdown
311             configBuilder.setAutoShutdownEnabled(false);
312         }
313 
314         configBuilder.setBand(apBand);
315 
316         if (customConfig == null || customConfig.getSsid() == null) {
317             configBuilder.setSsid(generateLohsSsid(context));
318         }
319         if (customConfig == null) {
320             if (ApConfigUtil.isWpa3SaeSupported(context)) {
321                 configBuilder.setPassphrase(generatePassword(),
322                         SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
323             } else {
324                 configBuilder.setPassphrase(generatePassword(),
325                         SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
326             }
327         }
328 
329         return configBuilder.build();
330     }
331 
332     /**
333      * @return a copy of the given SoftApConfig with the BSSID randomized, unless a custom BSSID is
334      * already set.
335      */
randomizeBssidIfUnset(Context context, SoftApConfiguration config)336     SoftApConfiguration randomizeBssidIfUnset(Context context, SoftApConfiguration config) {
337         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
338         if (config.getBssid() == null && context.getResources().getBoolean(
339                 R.bool.config_wifi_ap_mac_randomization_supported)) {
340             MacAddress macAddress = mMacAddressUtil.calculatePersistentMac(config.getSsid(),
341                     mMacAddressUtil.obtainMacRandHashFunctionForSap(Process.WIFI_UID));
342             if (macAddress == null) {
343                 Log.e(TAG, "Failed to calculate MAC from SSID. "
344                         + "Generating new random MAC instead.");
345                 macAddress = MacAddressUtils.createRandomUnicastAddress();
346             }
347             configBuilder.setBssid(macAddress);
348         }
349         return configBuilder.build();
350     }
351 
352     /**
353      * Verify provided SSID for existence, length and conversion to bytes
354      *
355      * @param ssid String ssid name
356      * @return boolean indicating ssid met requirements
357      */
validateApConfigSsid(String ssid)358     private static boolean validateApConfigSsid(String ssid) {
359         if (TextUtils.isEmpty(ssid)) {
360             Log.d(TAG, "SSID for softap configuration must be set.");
361             return false;
362         }
363 
364         try {
365             byte[] ssid_bytes = ssid.getBytes(StandardCharsets.UTF_8);
366 
367             if (ssid_bytes.length < SSID_MIN_LEN || ssid_bytes.length > SSID_MAX_LEN) {
368                 Log.d(TAG, "softap SSID is defined as UTF-8 and it must be at least "
369                         + SSID_MIN_LEN + " byte and not more than " + SSID_MAX_LEN + " bytes");
370                 return false;
371             }
372         } catch (IllegalArgumentException e) {
373             Log.e(TAG, "softap config SSID verification failed: malformed string " + ssid);
374             return false;
375         }
376         return true;
377     }
378 
379     /**
380      * Verify provided preSharedKey in ap config for WPA2_PSK network meets requirements.
381      */
validateApConfigPreSharedKey(String preSharedKey)382     private static boolean validateApConfigPreSharedKey(String preSharedKey) {
383         if (preSharedKey.length() < PSK_MIN_LEN || preSharedKey.length() > PSK_MAX_LEN) {
384             Log.d(TAG, "softap network password string size must be at least " + PSK_MIN_LEN
385                     + " and no more than " + PSK_MAX_LEN);
386             return false;
387         }
388 
389         try {
390             preSharedKey.getBytes(StandardCharsets.UTF_8);
391         } catch (IllegalArgumentException e) {
392             Log.e(TAG, "softap network password verification failed: malformed string");
393             return false;
394         }
395         return true;
396     }
397 
398     /**
399      * Validate a SoftApConfiguration is properly configured for use by SoftApManager.
400      *
401      * This method checks the length of the SSID and for sanity between security settings (if it
402      * requires a password, was one provided?).
403      *
404      * @param apConfig {@link SoftApConfiguration} to use for softap mode
405      * @param isPrivileged indicate the caller can pass some fields check or not
406      * @return boolean true if the provided config meets the minimum set of details, false
407      * otherwise.
408      */
validateApWifiConfiguration(@onNull SoftApConfiguration apConfig, boolean isPrivileged)409     static boolean validateApWifiConfiguration(@NonNull SoftApConfiguration apConfig,
410             boolean isPrivileged) {
411         // first check the SSID
412         if (!validateApConfigSsid(apConfig.getSsid())) {
413             // failed SSID verificiation checks
414             return false;
415         }
416 
417         // BSSID can be set if caller own permission:android.Manifest.permission.NETWORK_SETTINGS.
418         if (apConfig.getBssid() != null && !isPrivileged) {
419             Log.e(TAG, "Config BSSID needs NETWORK_SETTINGS permission");
420             return false;
421         }
422 
423         String preSharedKey = apConfig.getPassphrase();
424         boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey);
425         int authType;
426 
427         try {
428             authType = apConfig.getSecurityType();
429         } catch (IllegalStateException e) {
430             Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage());
431             return false;
432         }
433 
434         if (authType == SoftApConfiguration.SECURITY_TYPE_OPEN) {
435             // open networks should not have a password
436             if (hasPreSharedKey) {
437                 Log.d(TAG, "open softap network should not have a password");
438                 return false;
439             }
440         } else if (authType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
441                 || authType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION
442                 || authType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) {
443             // this is a config that should have a password - check that first
444             if (!hasPreSharedKey) {
445                 Log.d(TAG, "softap network password must be set");
446                 return false;
447             }
448             if (authType != SoftApConfiguration.SECURITY_TYPE_WPA3_SAE
449                     && !validateApConfigPreSharedKey(preSharedKey)) {
450                 // failed preSharedKey checks for WPA2 and WPA3 SAE Transition mode.
451                 return false;
452             }
453         } else {
454             // this is not a supported security type
455             Log.d(TAG, "softap configs must either be open or WPA2 PSK networks");
456             return false;
457         }
458 
459         return true;
460     }
461 
generatePassword()462     private static String generatePassword() {
463         // Characters that will be used for password generation. Some characters commonly known to
464         // be confusing like 0 and O excluded from this list.
465         final String allowed = "23456789abcdefghijkmnpqrstuvwxyz";
466         final int passLength = 15;
467 
468         StringBuilder sb = new StringBuilder(passLength);
469         SecureRandom random = new SecureRandom();
470         for (int i = 0; i < passLength; i++) {
471             sb.append(allowed.charAt(random.nextInt(allowed.length())));
472         }
473         return sb.toString();
474     }
475 }
476