diff options
author | Luca Stefani <luca.stefani.ge1@gmail.com> | 2020-03-07 13:20:46 +0100 |
---|---|---|
committer | Luca Stefani <luca.stefani.ge1@gmail.com> | 2020-03-07 13:20:46 +0100 |
commit | 876987a7579be182a3e4f86947bec75434be9bc4 (patch) | |
tree | 9a2d569d8fdbb69ca255c0c87c04eafca5e4bf33 | |
parent | fb7bd6cb89ad593e571f864866c88d577bff2ce5 (diff) | |
parent | aa0a79a0e9aa1a6c0c6a1c83360f6f1048fc59db (diff) | |
download | android_frameworks_opt_net_wifi-876987a7579be182a3e4f86947bec75434be9bc4.tar.gz android_frameworks_opt_net_wifi-876987a7579be182a3e4f86947bec75434be9bc4.tar.bz2 android_frameworks_opt_net_wifi-876987a7579be182a3e4f86947bec75434be9bc4.zip |
Merge tag 'android-10.0.0_r31' into lineage-17.1-android-10.0.0_r31
Android 10.0.0 release 31
* tag 'android-10.0.0_r31': (36 commits)
fix soft reboot caused by KeyStore exception
p2p: validate the network name of a group
Notification to set MAC randomization setting
MAC randomization SSID hotlist support
Fix race in StaEvents metrics collection
Wifi: Fix connectivity issues with PSK-SHA256+SAE mode APs
Reset num saved networks with mac randomization before counting
Fix boot regression from KeyStore being slow
[MAC rand] Fix unit test slowness
WifiConfigStoreEncryptionUtil: Use 256 bit secret key
Update the WifiConfig each time evalutor return a candidate.
Revert submission
[WPA3] Fix WPA3-Personal transition mode
WifiConfigStore: Encrypt credentials for networks (4/4)
WifiConfigStore: Encrypt credentials for networks (3/4)
WifiConfigStore: Encrypt credentials for networks (2/4)
WifiConfigStore: Encrypt credentials for networks (1/4)
[EAP-SIM] Add NAI realm decoration to pseudonym
[WifiRtt] add check to verify bw and preamble combination valid
[MAC rand] Removing persistent storage
...
Change-Id: I66be9db60b65cdd4ed515a48bb8b0c69b66f860a
62 files changed, 2649 insertions, 1173 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java index b36e0cb7b..0468f5ac3 100644 --- a/service/java/com/android/server/wifi/ClientModeImpl.java +++ b/service/java/com/android/server/wifi/ClientModeImpl.java @@ -163,7 +163,7 @@ public class ClientModeImpl extends StateMachine { private static final String EXTRA_UID = "uid"; private static final String EXTRA_PACKAGE_NAME = "PackageName"; private static final String EXTRA_PASSPOINT_CONFIGURATION = "PasspointConfiguration"; - private static final int IPCLIENT_TIMEOUT_MS = 10_000; + private static final int IPCLIENT_TIMEOUT_MS = 60_000; private boolean mVerboseLoggingEnabled = false; private final WifiPermissionsWrapper mWifiPermissionsWrapper; @@ -765,6 +765,7 @@ public class ClientModeImpl extends StateMachine { private WifiStateTracker mWifiStateTracker; private final BackupManagerProxy mBackupManagerProxy; private final WrongPasswordNotifier mWrongPasswordNotifier; + private final ConnectionFailureNotifier mConnectionFailureNotifier; private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager; private boolean mConnectedMacRandomzationSupported; // Maximum duration to continue to log Wifi usability stats after a data stall is triggered. @@ -818,7 +819,8 @@ public class ClientModeImpl extends StateMachine { mSupplicantStateTracker = mFacade.makeSupplicantStateTracker(context, mWifiConfigManager, getHandler()); mWifiConnectivityManager = mWifiInjector.makeWifiConnectivityManager(this); - + mConnectionFailureNotifier = mWifiInjector.makeConnectionFailureNotifier( + mWifiConnectivityManager); mLinkProperties = new LinkProperties(); mMcastLockManagerFilterController = new McastLockManagerFilterController(); @@ -1787,12 +1789,14 @@ public class ClientModeImpl extends StateMachine { * Remove a Passpoint configuration synchronously. * * @param channel Channel for communicating with the state machine + * @param privileged Whether the caller is a privileged entity * @param fqdn The FQDN of the Passpoint configuration to remove * @return true on success */ - public boolean syncRemovePasspointConfig(AsyncChannel channel, String fqdn) { + public boolean syncRemovePasspointConfig(AsyncChannel channel, boolean privileged, + String fqdn) { Message resultMsg = channel.sendMessageSynchronously(CMD_REMOVE_PASSPOINT_CONFIG, - fqdn); + privileged ? 1 : 0, 0, fqdn); if (messageIsNull(resultMsg)) return false; boolean result = (resultMsg.arg1 == SUCCESS); resultMsg.recycle(); @@ -1803,10 +1807,13 @@ public class ClientModeImpl extends StateMachine { * Get the list of installed Passpoint configurations synchronously. * * @param channel Channel for communicating with the state machine + * @param privileged Whether the caller is a privileged entity * @return List of {@link PasspointConfiguration} */ - public List<PasspointConfiguration> syncGetPasspointConfigs(AsyncChannel channel) { - Message resultMsg = channel.sendMessageSynchronously(CMD_GET_PASSPOINT_CONFIGS); + public List<PasspointConfiguration> syncGetPasspointConfigs(AsyncChannel channel, + boolean privileged) { + Message resultMsg = channel.sendMessageSynchronously(CMD_GET_PASSPOINT_CONFIGS, + privileged ? 1 : 0); if (messageIsNull(resultMsg)) return null; List<PasspointConfiguration> result = (List<PasspointConfiguration>) resultMsg.obj; resultMsg.recycle(); @@ -3160,6 +3167,16 @@ public class ClientModeImpl extends StateMachine { mWifiScoreCard.noteConnectionFailure(mWifiInfo, level2FailureCode, connectivityFailureCode); } + boolean isAssociationRejection = level2FailureCode + == WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION; + boolean isAuthenticationFailure = level2FailureCode + == WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE + && level2FailureReason != WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD; + if ((isAssociationRejection || isAuthenticationFailure) + && mWifiConfigManager.isInFlakyRandomizationSsidHotlist(mTargetNetworkId)) { + mConnectionFailureNotifier + .showFailedToConnectDueToNoRandomizedMacSupportNotification(mTargetNetworkId); + } // if connected, this should be non-null. WifiConfiguration configuration = getCurrentWifiConfiguration(); if (configuration == null) { @@ -3387,12 +3404,14 @@ public class ClientModeImpl extends StateMachine { Log.e(TAG, "No config to change MAC address to"); return; } - MacAddress currentMac = MacAddress.fromString(mWifiNative.getMacAddress(mInterfaceName)); + String currentMacString = mWifiNative.getMacAddress(mInterfaceName); + MacAddress currentMac = currentMacString == null ? null : + MacAddress.fromString(currentMacString); MacAddress newMac = config.getOrCreateRandomizedMacAddress(); mWifiConfigManager.setNetworkRandomizedMacAddress(config.networkId, newMac); if (!WifiConfiguration.isValidMacAddressForRandomization(newMac)) { Log.wtf(TAG, "Config generated an invalid MAC address"); - } else if (currentMac.equals(newMac)) { + } else if (newMac.equals(currentMac)) { Log.d(TAG, "No changes in MAC address"); } else { mWifiMetrics.logStaEvent(StaEvent.TYPE_MAC_CHANGE, config); @@ -3400,7 +3419,7 @@ public class ClientModeImpl extends StateMachine { mWifiNative.setMacAddress(mInterfaceName, newMac); Log.d(TAG, "ConnectedMacRandomization SSID(" + config.getPrintableSsid() + "). setMacAddress(" + newMac.toString() + ") from " - + currentMac.toString() + " = " + setMacSuccess); + + currentMacString + " = " + setMacSuccess); } } @@ -3696,11 +3715,13 @@ public class ClientModeImpl extends StateMachine { break; case CMD_REMOVE_PASSPOINT_CONFIG: int removeResult = mPasspointManager.removeProvider( - (String) message.obj) ? SUCCESS : FAILURE; + message.sendingUid, message.arg1 == 1, (String) message.obj) + ? SUCCESS : FAILURE; replyToMessage(message, message.what, removeResult); break; case CMD_GET_PASSPOINT_CONFIGS: - replyToMessage(message, message.what, mPasspointManager.getProviderConfigs()); + replyToMessage(message, message.what, mPasspointManager.getProviderConfigs( + message.sendingUid, message.arg1 == 1)); break; case CMD_RESET_SIM_NETWORKS: /* Defer this message until supplicant is started. */ @@ -4475,14 +4496,25 @@ public class ClientModeImpl extends StateMachine { config.enterpriseConfig.getEapMethod())) { String anonymousIdentity = mWifiNative.getEapAnonymousIdentity(mInterfaceName); - if (mVerboseLoggingEnabled) { - log("EAP Pseudonym: " + anonymousIdentity); - } - if (!TelephonyUtil.isAnonymousAtRealmIdentity(anonymousIdentity)) { + if (!TextUtils.isEmpty(anonymousIdentity) + && !TelephonyUtil + .isAnonymousAtRealmIdentity(anonymousIdentity)) { + String decoratedPseudonym = TelephonyUtil + .decoratePseudonymWith3GppRealm(getTelephonyManager(), + anonymousIdentity); + if (decoratedPseudonym != null) { + anonymousIdentity = decoratedPseudonym; + } + if (mVerboseLoggingEnabled) { + log("EAP Pseudonym: " + anonymousIdentity); + } // Save the pseudonym only if it is a real one config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity); - mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID); + } else { + // Clear any stored pseudonyms + config.enterpriseConfig.setAnonymousIdentity(null); } + mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID); } sendNetworkStateChangeBroadcast(mLastBssid); transitionTo(mObtainingIpState); @@ -4535,7 +4567,8 @@ public class ClientModeImpl extends StateMachine { break; case CMD_REMOVE_PASSPOINT_CONFIG: String fqdn = (String) message.obj; - if (mPasspointManager.removeProvider(fqdn)) { + if (mPasspointManager.removeProvider( + message.sendingUid, message.arg1 == 1, fqdn)) { if (isProviderOwnedNetwork(mTargetNetworkId, fqdn) || isProviderOwnedNetwork(mLastNetworkId, fqdn)) { logd("Disconnect from current network since its provider is removed"); @@ -5372,7 +5405,6 @@ public class ClientModeImpl extends StateMachine { .withApfCapabilities(mWifiNative.getApfCapabilities(mInterfaceName)) .withNetwork(getCurrentNetwork()) .withDisplayName(currentConfig.SSID) - .withRandomMacAddress() .build(); } else { StaticIpConfiguration staticIpConfig = currentConfig.getStaticIpConfiguration(); diff --git a/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java b/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java new file mode 100644 index 000000000..f4f89f09e --- /dev/null +++ b/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import android.annotation.NonNull; +import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.net.wifi.WifiConfiguration; +import android.os.Handler; +import android.view.WindowManager; + +import com.android.internal.R; +import com.android.internal.notification.SystemNotificationChannels; + +/** + * Helper class for ConnectionFailureNotifier. + */ +public class ConnectionFailureNotificationBuilder { + private static final String TAG = "ConnectionFailureNotifier"; + + public static final String ACTION_SHOW_SET_RANDOMIZATION_DETAILS = + "com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS"; + public static final String RANDOMIZATION_SETTINGS_NETWORK_ID = + "com.android.server.wifi.RANDOMIZATION_SETTINGS_NETWORK_ID"; + public static final String RANDOMIZATION_SETTINGS_NETWORK_SSID = + "com.android.server.wifi.RANDOMIZATION_SETTINGS_NETWORK_SSID"; + + private Context mContext; + private String mPackageName; + private Resources mResources; + private FrameworkFacade mFrameworkFacade; + private WifiConnectivityManager mWifiConnectivityManager; + private NotificationManager mNotificationManager; + private Handler mHandler; + + public ConnectionFailureNotificationBuilder(Context context, String packageName, + FrameworkFacade framework) { + mContext = context; + mPackageName = packageName; + mResources = context.getResources(); + mFrameworkFacade = framework; + } + + /** + * Creates a notification that alerts the user that the connection may be failing due to + * MAC randomization. + * @param config + */ + public Notification buildNoMacRandomizationSupportNotification( + @NonNull WifiConfiguration config) { + String ssid = config.SSID; + String ssidAndSecurityType = config.getSsidAndSecurityTypeString(); + String title = mResources.getString( + R.string.wifi_cannot_connect_with_randomized_mac_title, ssid); + String content = mResources.getString( + R.string.wifi_cannot_connect_with_randomized_mac_message); + + Intent showDetailIntent = new Intent(ACTION_SHOW_SET_RANDOMIZATION_DETAILS) + .setPackage(mPackageName); + showDetailIntent.putExtra(RANDOMIZATION_SETTINGS_NETWORK_ID, config.networkId); + showDetailIntent.putExtra(RANDOMIZATION_SETTINGS_NETWORK_SSID, ssidAndSecurityType); + PendingIntent pendingShowDetailIntent = mFrameworkFacade.getBroadcast( + mContext, 0, showDetailIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + return mFrameworkFacade.makeNotificationBuilder(mContext, + SystemNotificationChannels.NETWORK_ALERTS) + .setSmallIcon(R.drawable.stat_notify_wifi_in_range) + .setTicker(title) + .setContentTitle(title) + .setContentText(content) + .setContentIntent(pendingShowDetailIntent) + .setShowWhen(false) + .setLocalOnly(true) + .setColor(mResources.getColor(R.color.system_notification_accent_color, + mContext.getTheme())) + .setAutoCancel(true) + .build(); + } + + /** + * Creates an AlertDialog that allows the user to disable MAC randomization for a network. + * @param ssid the displayed SSID in the dialog + * @param onUserConfirm + */ + public AlertDialog buildChangeMacRandomizationSettingDialog( + String ssid, DialogInterface.OnClickListener onUserConfirm) { + AlertDialog.Builder builder = mFrameworkFacade.makeAlertDialogBuilder(mContext) + .setTitle(mResources.getString( + R.string.wifi_disable_mac_randomization_dialog_title)) + .setMessage(mResources.getString( + R.string.wifi_disable_mac_randomization_dialog_message, ssid)) + .setPositiveButton( + mResources.getString( + R.string.wifi_disable_mac_randomization_dialog_confirm_text), + onUserConfirm) + // A null listener allows the dialog to be dismissed directly. + .setNegativeButton(R.string.no, null); + AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + return dialog; + } +} diff --git a/service/java/com/android/server/wifi/ConnectionFailureNotifier.java b/service/java/com/android/server/wifi/ConnectionFailureNotifier.java new file mode 100644 index 000000000..bbef2ff6e --- /dev/null +++ b/service/java/com/android/server/wifi/ConnectionFailureNotifier.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.net.wifi.WifiConfiguration; +import android.os.Handler; +import android.os.Process; +import android.util.Log; + +import com.android.internal.R; + +/** + * This class may be used to launch notifications when wifi connections fail. + */ +public class ConnectionFailureNotifier { + private static final String TAG = "ConnectionFailureNotifier"; + public static final int NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID = 123; + + private Context mContext; + private WifiInjector mWifiInjector; + private Resources mResources; + private FrameworkFacade mFrameworkFacade; + private WifiConfigManager mWifiConfigManager; + private WifiConnectivityManager mWifiConnectivityManager; + private NotificationManager mNotificationManager; + private Handler mHandler; + private ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder; + + public ConnectionFailureNotifier( + Context context, WifiInjector wifiInjector, FrameworkFacade framework, + WifiConfigManager wifiConfigManager, WifiConnectivityManager wifiConnectivityManager, + Handler handler) { + mContext = context; + mWifiInjector = wifiInjector; + mResources = context.getResources(); + mFrameworkFacade = framework; + mWifiConfigManager = wifiConfigManager; + mWifiConnectivityManager = wifiConnectivityManager; + mNotificationManager = mWifiInjector.getNotificationManager(); + mHandler = handler; + mConnectionFailureNotificationBuilder = + mWifiInjector.getConnectionFailureNotificationBuilder(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectionFailureNotificationBuilder + .ACTION_SHOW_SET_RANDOMIZATION_DETAILS); + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ConnectionFailureNotificationBuilder + .ACTION_SHOW_SET_RANDOMIZATION_DETAILS)) { + int networkId = intent.getIntExtra( + ConnectionFailureNotificationBuilder + .RANDOMIZATION_SETTINGS_NETWORK_ID, + WifiConfiguration.INVALID_NETWORK_ID); + String ssidAndSecurityType = intent.getStringExtra( + ConnectionFailureNotificationBuilder + .RANDOMIZATION_SETTINGS_NETWORK_SSID); + showRandomizationSettingsDialog(networkId, ssidAndSecurityType); + } + } + }, filter); + } + + /** + * Shows a notification which will bring up a dialog which offers the user an option to disable + * MAC randomization on |networkdId|. + * @param networkId + */ + public void showFailedToConnectDueToNoRandomizedMacSupportNotification(int networkId) { + WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(networkId); + if (config == null) { + return; + } + Notification notification = mConnectionFailureNotificationBuilder + .buildNoMacRandomizationSupportNotification(config); + mNotificationManager.notify(NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID, notification); + } + + class DisableMacRandomizationListener implements DialogInterface.OnClickListener { + private WifiConfiguration mConfig; + + DisableMacRandomizationListener(WifiConfiguration config) { + mConfig = config; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + mHandler.post(() -> { + mConfig.macRandomizationSetting = + WifiConfiguration.RANDOMIZATION_NONE; + mWifiConfigManager.addOrUpdateNetwork(mConfig, Process.SYSTEM_UID); + WifiConfiguration updatedConfig = + mWifiConfigManager.getConfiguredNetwork(mConfig.networkId); + if (updatedConfig.macRandomizationSetting + == WifiConfiguration.RANDOMIZATION_NONE) { + String message = mResources.getString( + R.string.wifi_disable_mac_randomization_dialog_success); + mFrameworkFacade.showToast(mContext, message); + mWifiConfigManager.enableNetwork(updatedConfig.networkId, true, + Process.SYSTEM_UID); + mWifiConnectivityManager.forceConnectivityScan( + ClientModeImpl.WIFI_WORK_SOURCE); + } else { + // Shouldn't ever fail, but here for completeness + String message = mResources.getString( + R.string.wifi_disable_mac_randomization_dialog_failure); + mFrameworkFacade.showToast(mContext, message); + Log.e(TAG, "Failed to modify mac randomization setting"); + } + }); + } + } + + /** + * Class to show a AlertDialog which notifies the user of a network not being privacy + * compliant and then suggests an action. + */ + private void showRandomizationSettingsDialog(int networkId, String ssidAndSecurityType) { + WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(networkId); + // Make sure the networkId is still pointing to the correct WifiConfiguration since + // there might be a large time gap between when the notification shows and when + // it's tapped. + if (config == null || ssidAndSecurityType == null + || !ssidAndSecurityType.equals(config.getSsidAndSecurityTypeString())) { + String message = mResources.getString( + R.string.wifi_disable_mac_randomization_dialog_network_not_found); + mFrameworkFacade.showToast(mContext, message); + return; + } + + AlertDialog dialog = mConnectionFailureNotificationBuilder + .buildChangeMacRandomizationSettingDialog(config.SSID, + new DisableMacRandomizationListener(config)); + dialog.show(); + } +} diff --git a/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java b/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java index 0c064884c..b71d5a023 100644 --- a/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java +++ b/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java @@ -16,6 +16,9 @@ package com.android.server.wifi; +import android.annotation.Nullable; + +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; @@ -44,7 +47,8 @@ public class DeletedEphemeralSsidsStoreData implements WifiConfigStore.StoreData } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { if (mSsidToTimeMap != null) { XmlUtil.writeNextValue(out, XML_TAG_SSID_LIST, mSsidToTimeMap); @@ -52,7 +56,9 @@ public class DeletedEphemeralSsidsStoreData implements WifiConfigStore.StoreData } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { // Ignore empty reads. if (in == null) { diff --git a/service/java/com/android/server/wifi/DeviceConfigFacade.java b/service/java/com/android/server/wifi/DeviceConfigFacade.java index a9889f424..25cc2f72d 100644 --- a/service/java/com/android/server/wifi/DeviceConfigFacade.java +++ b/service/java/com/android/server/wifi/DeviceConfigFacade.java @@ -17,7 +17,9 @@ package com.android.server.wifi; import android.provider.DeviceConfig; +import android.util.ArraySet; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -105,4 +107,23 @@ public class DeviceConfigFacade { return DeviceConfig.getInt(NAMESPACE, "data_stall_cca_level_thr", DEFAULT_DATA_STALL_CCA_LEVEL_THR); } + + /** + * Gets the Set of SSIDs in the flaky SSID hotlist. + */ + public Set<String> getRandomizationFlakySsidHotlist() { + String ssidHotlist = DeviceConfig.getString(NAMESPACE, + "randomization_flaky_ssid_hotlist", ""); + Set<String> result = new ArraySet<String>(); + String[] ssidHotlistArray = ssidHotlist.split(","); + for (int i = 0; i < ssidHotlistArray.length; i++) { + String cur = ssidHotlistArray[i]; + if (cur.length() == 0) { + continue; + } + // Make sure the SSIDs are quoted. Server side should not quote ssids. + result.add("\"" + cur + "\""); + } + return result; + } } diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java index f3c5d4b3d..fe3027e50 100644 --- a/service/java/com/android/server/wifi/FrameworkFacade.java +++ b/service/java/com/android/server/wifi/FrameworkFacade.java @@ -17,6 +17,7 @@ package com.android.server.wifi; import android.app.ActivityManagerInternal; +import android.app.AlertDialog; import android.app.AppGlobals; import android.app.Notification; import android.app.PendingIntent; @@ -35,6 +36,7 @@ import android.os.ServiceManager; import android.os.storage.StorageManager; import android.provider.Settings; import android.telephony.CarrierConfigManager; +import android.widget.Toast; import com.android.internal.app.IBatteryStats; import com.android.server.LocalServices; @@ -45,6 +47,11 @@ import com.android.server.wifi.util.WifiAsyncChannel; */ public class FrameworkFacade { public static final String TAG = "FrameworkFacade"; + /** + * NIAP global settings flag. + * Note: This should be added to {@link android.provider.Settings.Global}. + */ + private static final String NIAP_MODE_SETTINGS_NAME = "niap_mode"; private ActivityManagerInternal mActivityManagerInternal; @@ -83,6 +90,13 @@ public class FrameworkFacade { } /** + * Returns whether the device is in NIAP mode or not. + */ + public boolean isNiapModeOn(Context context) { + return getIntegerSetting(context, NIAP_MODE_SETTINGS_NAME, 0) == 1; + } + + /** * Helper method for classes to register a ContentObserver * {@see ContentResolver#registerContentObserver(Uri,boolean,ContentObserver)}. * @@ -215,4 +229,23 @@ public class FrameworkFacade { public Notification.Builder makeNotificationBuilder(Context context, String channelId) { return new Notification.Builder(context, channelId); } + + /** + * Create a new instance of {@link AlertDialog.Builder}. + * @param context reference to a Context + * @return an instance of AlertDialog.Builder + */ + public AlertDialog.Builder makeAlertDialogBuilder(Context context) { + return new AlertDialog.Builder(context); + } + + /** + * Show a toast message + * @param context reference to a Context + * @param text the message to display + */ + public void showToast(Context context, String text) { + Toast toast = Toast.makeText(context, text, Toast.LENGTH_SHORT); + toast.show(); + } } diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java index d2ad47f48..e10234f68 100644 --- a/service/java/com/android/server/wifi/HalDeviceManager.java +++ b/service/java/com/android/server/wifi/HalDeviceManager.java @@ -1723,9 +1723,11 @@ public class HalDeviceManager { int requestedIfaceType, WifiIfaceInfo[][] currentIfaces, int numNecessaryInterfaces) { // rule 0: check for any low priority interfaces int numAvailableLowPriorityInterfaces = 0; - for (InterfaceCacheEntry entry : mInterfaceInfoCache.values()) { - if (entry.type == existingIfaceType && entry.isLowPriority) { - numAvailableLowPriorityInterfaces++; + synchronized (mLock) { + for (InterfaceCacheEntry entry : mInterfaceInfoCache.values()) { + if (entry.type == existingIfaceType && entry.isLowPriority) { + numAvailableLowPriorityInterfaces++; + } } } if (numAvailableLowPriorityInterfaces >= numNecessaryInterfaces) { @@ -1780,8 +1782,10 @@ public class HalDeviceManager { LongSparseArray<WifiIfaceInfo> orderedListLowPriority = new LongSparseArray<>(); LongSparseArray<WifiIfaceInfo> orderedList = new LongSparseArray<>(); for (WifiIfaceInfo info : interfaces) { - InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get( - Pair.create(info.name, getType(info.iface))); + InterfaceCacheEntry cacheEntry; + synchronized (mLock) { + cacheEntry = mInterfaceInfoCache.get(Pair.create(info.name, getType(info.iface))); + } if (cacheEntry == null) { Log.e(TAG, "selectInterfacesToDelete: can't find cache entry with name=" + info.name); diff --git a/service/java/com/android/server/wifi/MacAddressUtil.java b/service/java/com/android/server/wifi/MacAddressUtil.java new file mode 100644 index 000000000..4739b6141 --- /dev/null +++ b/service/java/com/android/server/wifi/MacAddressUtil.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import android.net.MacAddress; +import android.net.wifi.WifiConfiguration; +import android.security.keystore.AndroidKeyStoreProvider; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.util.Log; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.ProviderException; +import java.security.UnrecoverableKeyException; +import java.util.Arrays; + +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; + +/** + * Contains helper methods to support MAC randomization. + */ +public class MacAddressUtil { + private static final String TAG = "MacAddressUtil"; + private static final String MAC_RANDOMIZATION_ALIAS = "MacRandSecret"; + private static final long MAC_ADDRESS_VALID_LONG_MASK = (1L << 48) - 1; + private static final long MAC_ADDRESS_LOCALLY_ASSIGNED_MASK = 1L << 41; + private static final long MAC_ADDRESS_MULTICAST_MASK = 1L << 40; + + /** + * Computes the persistent randomized MAC of the given configuration using the given + * hash function. + * @param config the WifiConfiguration to compute MAC address for + * @param hashFunction the hash function that will perform the MAC address computation. + * @return The persistent randomized MAC address or null if inputs are invalid. + */ + public MacAddress calculatePersistentMacForConfiguration(WifiConfiguration config, + Mac hashFunction) { + if (config == null || hashFunction == null) { + return null; + } + byte[] hashedBytes; + try { + hashedBytes = hashFunction.doFinal(config.getSsidAndSecurityTypeString() + .getBytes(StandardCharsets.UTF_8)); + } catch (ProviderException | IllegalStateException e) { + Log.e(TAG, "Failure in calculatePersistentMac", e); + return null; + } + ByteBuffer bf = ByteBuffer.wrap(hashedBytes); + long longFromSsid = bf.getLong(); + /** + * Masks the generated long so that it represents a valid randomized MAC address. + * Specifically, this sets the locally assigned bit to 1, multicast bit to 0 + */ + longFromSsid &= MAC_ADDRESS_VALID_LONG_MASK; + longFromSsid |= MAC_ADDRESS_LOCALLY_ASSIGNED_MASK; + longFromSsid &= ~MAC_ADDRESS_MULTICAST_MASK; + bf.clear(); + bf.putLong(0, longFromSsid); + + // MacAddress.fromBytes requires input of length 6, which is obtained from the + // last 6 bytes from the generated long. + MacAddress macAddress = MacAddress.fromBytes(Arrays.copyOfRange(bf.array(), 2, 8)); + return macAddress; + } + + /** + * Retrieves a Hash function that could be used to calculate the persistent randomized MAC + * for a WifiConfiguration. + * @param uid the UID of the KeyStore to get the secret of the hash function from. + */ + public Mac obtainMacRandHashFunction(int uid) { + try { + KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(uid); + // tries to retrieve the secret, and generate a new one if it's unavailable. + Key key = keyStore.getKey(MAC_RANDOMIZATION_ALIAS, null); + if (key == null) { + key = generateAndPersistNewMacRandomizationSecret(uid); + } + if (key == null) { + Log.e(TAG, "Failed to generate secret for " + MAC_RANDOMIZATION_ALIAS); + return null; + } + Mac result = Mac.getInstance("HmacSHA256"); + result.init(key); + return result; + } catch (KeyStoreException | NoSuchAlgorithmException | InvalidKeyException + | UnrecoverableKeyException | NoSuchProviderException e) { + Log.e(TAG, "Failure in obtainMacRandHashFunction", e); + return null; + } + } + + /** + * Generates and returns a secret key to use for Mac randomization. + * Will also persist the generated secret inside KeyStore, accessible in the + * future with KeyGenerator#getKey. + */ + private SecretKey generateAndPersistNewMacRandomizationSecret(int uid) { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore"); + keyGenerator.init( + new KeyGenParameterSpec.Builder(MAC_RANDOMIZATION_ALIAS, + KeyProperties.PURPOSE_SIGN) + .setUid(uid) + .build()); + return keyGenerator.generateKey(); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException + | NoSuchProviderException | ProviderException e) { + Log.e(TAG, "Failure in generateMacRandomizationSecret", e); + return null; + } + } +} diff --git a/service/java/com/android/server/wifi/NetworkListStoreData.java b/service/java/com/android/server/wifi/NetworkListStoreData.java index 696647185..52e655b1e 100644 --- a/service/java/com/android/server/wifi/NetworkListStoreData.java +++ b/service/java/com/android/server/wifi/NetworkListStoreData.java @@ -16,6 +16,9 @@ package com.android.server.wifi; +import static com.android.server.wifi.WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION; + +import android.annotation.Nullable; import android.content.Context; import android.net.IpConfiguration; import android.net.wifi.WifiConfiguration; @@ -25,6 +28,7 @@ import android.os.Process; import android.util.Log; import android.util.Pair; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil; import com.android.server.wifi.util.XmlUtil.NetworkSelectionStatusXmlUtil; @@ -66,19 +70,22 @@ public abstract class NetworkListStoreData implements WifiConfigStore.StoreData } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { - serializeNetworkList(out, mConfigurations); + serializeNetworkList(out, mConfigurations, encryptionUtil); } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { // Ignore empty reads. if (in == null) { return; } - mConfigurations = parseNetworkList(in, outerTagDepth); + mConfigurations = parseNetworkList(in, outerTagDepth, version, encryptionUtil); } @Override @@ -118,33 +125,38 @@ public abstract class NetworkListStoreData implements WifiConfigStore.StoreData * * @param out The output stream to serialize the data to * @param networkList The network list to serialize + * @param encryptionUtil Instance of {@link WifiConfigStoreEncryptionUtil} * @throws XmlPullParserException * @throws IOException */ - private void serializeNetworkList(XmlSerializer out, List<WifiConfiguration> networkList) + private void serializeNetworkList(XmlSerializer out, List<WifiConfiguration> networkList, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { if (networkList == null) { return; } for (WifiConfiguration network : networkList) { - serializeNetwork(out, network); + serializeNetwork(out, network, encryptionUtil); } } /** * Serialize a {@link WifiConfiguration} to an output stream in XML format. - * @param out - * @param config + * + * @param out The output stream to serialize the data to + * @param config The network config to serialize + * @param encryptionUtil Instance of {@link WifiConfigStoreEncryptionUtil} * @throws XmlPullParserException * @throws IOException */ - private void serializeNetwork(XmlSerializer out, WifiConfiguration config) + private void serializeNetwork(XmlSerializer out, WifiConfiguration config, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK); // Serialize WifiConfiguration. XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); - WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, config); + WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, config, encryptionUtil); XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); // Serialize network selection status. @@ -162,7 +174,7 @@ public abstract class NetworkListStoreData implements WifiConfigStore.StoreData && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) { XmlUtil.writeNextSectionStart( out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION); - WifiEnterpriseConfigXmlUtil.writeToXml(out, config.enterpriseConfig); + WifiEnterpriseConfigXmlUtil.writeToXml(out, config.enterpriseConfig, encryptionUtil); XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION); } @@ -174,11 +186,15 @@ public abstract class NetworkListStoreData implements WifiConfigStore.StoreData * * @param in The input stream to read from * @param outerTagDepth The XML tag depth of the outer XML block + * @param version Version of config store file. + * @param encryptionUtil Instance of {@link WifiConfigStoreEncryptionUtil} * @return List of {@link WifiConfiguration} * @throws XmlPullParserException * @throws IOException */ - private List<WifiConfiguration> parseNetworkList(XmlPullParser in, int outerTagDepth) + private List<WifiConfiguration> parseNetworkList(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { List<WifiConfiguration> networkList = new ArrayList<>(); while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_SECTION_HEADER_NETWORK, @@ -186,7 +202,8 @@ public abstract class NetworkListStoreData implements WifiConfigStore.StoreData // Try/catch only runtime exceptions (like illegal args), any XML/IO exceptions are // fatal and should abort the entire loading process. try { - WifiConfiguration config = parseNetwork(in, outerTagDepth + 1); + WifiConfiguration config = + parseNetwork(in, outerTagDepth + 1, version, encryptionUtil); networkList.add(config); } catch (RuntimeException e) { // Failed to parse this network, skip it. @@ -201,11 +218,15 @@ public abstract class NetworkListStoreData implements WifiConfigStore.StoreData * * @param in The input stream to read from * @param outerTagDepth The XML tag depth of the outer XML block + * @param version Version of config store file. + * @param encryptionUtil Instance of {@link WifiConfigStoreEncryptionUtil} * @return {@link WifiConfiguration} * @throws XmlPullParserException * @throws IOException */ - private WifiConfiguration parseNetwork(XmlPullParser in, int outerTagDepth) + private WifiConfiguration parseNetwork(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { Pair<String, WifiConfiguration> parsedConfig = null; NetworkSelectionStatus status = null; @@ -220,7 +241,9 @@ public abstract class NetworkListStoreData implements WifiConfigStore.StoreData throw new XmlPullParserException("Detected duplicate tag for: " + XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); } - parsedConfig = WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth + 1); + parsedConfig = WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth + 1, + version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + encryptionUtil); break; case XML_TAG_SECTION_HEADER_NETWORK_STATUS: if (status != null) { @@ -242,7 +265,9 @@ public abstract class NetworkListStoreData implements WifiConfigStore.StoreData + XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION); } enterpriseConfig = - WifiEnterpriseConfigXmlUtil.parseFromXml(in, outerTagDepth + 1); + WifiEnterpriseConfigXmlUtil.parseFromXml(in, outerTagDepth + 1, + version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + encryptionUtil); break; default: throw new XmlPullParserException("Unknown tag under " diff --git a/service/java/com/android/server/wifi/NetworkRequestStoreData.java b/service/java/com/android/server/wifi/NetworkRequestStoreData.java index 8d1244f05..7457079ae 100644 --- a/service/java/com/android/server/wifi/NetworkRequestStoreData.java +++ b/service/java/com/android/server/wifi/NetworkRequestStoreData.java @@ -16,10 +16,12 @@ package com.android.server.wifi; +import android.annotation.Nullable; import android.net.MacAddress; import android.util.Log; import com.android.server.wifi.WifiNetworkFactory.AccessPoint; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil; @@ -87,13 +89,16 @@ public class NetworkRequestStoreData implements WifiConfigStore.StoreData { } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { serializeApprovedAccessPointsMap(out, mDataSource.toSerialize()); } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { // Ignore empty reads. if (in == null) { diff --git a/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java b/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java index 9627a9daa..e973bdbe2 100644 --- a/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java +++ b/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java @@ -16,6 +16,9 @@ package com.android.server.wifi; +import static com.android.server.wifi.WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION; + +import android.annotation.Nullable; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; import android.net.wifi.WifiNetworkSuggestion; @@ -26,6 +29,7 @@ import android.util.Pair; import com.android.internal.util.XmlUtils; import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion; import com.android.server.wifi.WifiNetworkSuggestionsManager.PerAppInfo; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil; @@ -98,19 +102,23 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { - serializeNetworkSuggestionsMap(out, mDataSource.toSerialize()); + serializeNetworkSuggestionsMap(out, mDataSource.toSerialize(), encryptionUtil); } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { // Ignore empty reads. if (in == null) { return; } - mDataSource.fromDeserialized(parseNetworkSuggestionsMap(in, outerTagDepth)); + mDataSource.fromDeserialized( + parseNetworkSuggestionsMap(in, outerTagDepth, version, encryptionUtil)); } @Override @@ -140,7 +148,8 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { * @throws IOException */ private void serializeNetworkSuggestionsMap( - XmlSerializer out, final Map<String, PerAppInfo> networkSuggestionsMap) + XmlSerializer out, final Map<String, PerAppInfo> networkSuggestionsMap, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { if (networkSuggestionsMap == null) { return; @@ -155,7 +164,7 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_PACKAGE_NAME, packageName); XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_HAS_USER_APPROVED, hasUserApproved); XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_MAX_SIZE, maxSize); - serializeExtNetworkSuggestions(out, networkSuggestions); + serializeExtNetworkSuggestions(out, networkSuggestions, encryptionUtil); XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_PER_APP); } } @@ -167,10 +176,11 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { * @throws IOException */ private void serializeExtNetworkSuggestions( - XmlSerializer out, final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) + XmlSerializer out, final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) { - serializeNetworkSuggestion(out, extNetworkSuggestion.wns); + serializeNetworkSuggestion(out, extNetworkSuggestion.wns, encryptionUtil); } } @@ -181,13 +191,15 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { * @throws IOException */ private void serializeNetworkSuggestion(XmlSerializer out, - final WifiNetworkSuggestion suggestion) + final WifiNetworkSuggestion suggestion, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION); // Serialize WifiConfiguration. XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); - WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, suggestion.wifiConfiguration); + WifiConfigurationXmlUtil.writeToXmlForConfigStore( + out, suggestion.wifiConfiguration, encryptionUtil); XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); // Serialize enterprise configuration for enterprise networks. if (suggestion.wifiConfiguration.enterpriseConfig != null @@ -196,7 +208,7 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { XmlUtil.writeNextSectionStart( out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION); XmlUtil.WifiEnterpriseConfigXmlUtil.writeToXml( - out, suggestion.wifiConfiguration.enterpriseConfig); + out, suggestion.wifiConfiguration.enterpriseConfig, encryptionUtil); XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION); } @@ -218,7 +230,9 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { * @throws XmlPullParserException * @throws IOException */ - private Map<String, PerAppInfo> parseNetworkSuggestionsMap(XmlPullParser in, int outerTagDepth) + private Map<String, PerAppInfo> parseNetworkSuggestionsMap(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { Map<String, PerAppInfo> networkSuggestionsMap = new HashMap<>(); while (XmlUtil.gotoNextSectionWithNameOrEnd( @@ -233,7 +247,8 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { int maxSize = (int) XmlUtil.readNextValueWithName(in, XML_TAG_SUGGESTOR_MAX_SIZE); PerAppInfo perAppInfo = new PerAppInfo(packageName); Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = - parseExtNetworkSuggestions(in, outerTagDepth + 1, perAppInfo); + parseExtNetworkSuggestions( + in, outerTagDepth + 1, version, encryptionUtil, perAppInfo); perAppInfo.hasUserApproved = hasUserApproved; perAppInfo.maxSize = maxSize; perAppInfo.extNetworkSuggestions.addAll(extNetworkSuggestions); @@ -253,7 +268,8 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { * @throws IOException */ private Set<ExtendedWifiNetworkSuggestion> parseExtNetworkSuggestions( - XmlPullParser in, int outerTagDepth, PerAppInfo perAppInfo) + XmlPullParser in, int outerTagDepth, @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil, PerAppInfo perAppInfo) throws XmlPullParserException, IOException { Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>(); while (XmlUtil.gotoNextSectionWithNameOrEnd( @@ -262,7 +278,7 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { // fatal and should abort the entire loading process. try { WifiNetworkSuggestion networkSuggestion = - parseNetworkSuggestion(in, outerTagDepth + 1); + parseNetworkSuggestion(in, outerTagDepth + 1, version, encryptionUtil); extNetworkSuggestions.add(ExtendedWifiNetworkSuggestion.fromWns( networkSuggestion, perAppInfo)); } catch (RuntimeException e) { @@ -279,7 +295,9 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { * @throws XmlPullParserException * @throws IOException */ - private WifiNetworkSuggestion parseNetworkSuggestion(XmlPullParser in, int outerTagDepth) + private WifiNetworkSuggestion parseNetworkSuggestion(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { Pair<String, WifiConfiguration> parsedConfig = null; WifiEnterpriseConfig enterpriseConfig = null; @@ -324,7 +342,9 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { + XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); } parsedConfig = WifiConfigurationXmlUtil.parseFromXml( - in, outerTagDepth + 1); + in, outerTagDepth + 1, + version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + encryptionUtil); break; case XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION: if (enterpriseConfig != null) { @@ -332,7 +352,9 @@ public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData { + XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION); } enterpriseConfig = XmlUtil.WifiEnterpriseConfigXmlUtil.parseFromXml( - in, outerTagDepth + 1); + in, outerTagDepth + 1, + version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + encryptionUtil); break; default: throw new XmlPullParserException("Unknown tag under " diff --git a/service/java/com/android/server/wifi/RandomizedMacStoreData.java b/service/java/com/android/server/wifi/RandomizedMacStoreData.java index 1e4d972ef..ecbd7177f 100644 --- a/service/java/com/android/server/wifi/RandomizedMacStoreData.java +++ b/service/java/com/android/server/wifi/RandomizedMacStoreData.java @@ -16,6 +16,9 @@ package com.android.server.wifi; +import android.annotation.Nullable; + +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; @@ -40,7 +43,8 @@ public class RandomizedMacStoreData implements WifiConfigStore.StoreData { RandomizedMacStoreData() {} @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { if (mMacMapping != null) { XmlUtil.writeNextValue(out, XML_TAG_MAC_MAP, mMacMapping); @@ -48,7 +52,9 @@ public class RandomizedMacStoreData implements WifiConfigStore.StoreData { } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { // Ignore empty reads. if (in == null) { diff --git a/service/java/com/android/server/wifi/SsidSetStoreData.java b/service/java/com/android/server/wifi/SsidSetStoreData.java index 7d1b99340..36b547cd2 100644 --- a/service/java/com/android/server/wifi/SsidSetStoreData.java +++ b/service/java/com/android/server/wifi/SsidSetStoreData.java @@ -16,8 +16,10 @@ package com.android.server.wifi; +import android.annotation.Nullable; import android.text.TextUtils; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; @@ -74,7 +76,8 @@ public class SsidSetStoreData implements WifiConfigStore.StoreData { } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { Set<String> ssidSet = mDataSource.getSsids(); if (ssidSet != null && !ssidSet.isEmpty()) { @@ -83,7 +86,9 @@ public class SsidSetStoreData implements WifiConfigStore.StoreData { } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { // Ignore empty reads. if (in == null) { diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java index 86518c761..8db7c90ee 100644 --- a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java +++ b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java @@ -3074,10 +3074,10 @@ public class SupplicantStaIfaceHal { * This is a v1.2+ HAL feature. * On error, or if these features are not supported, 0 is returned. */ - public int getAdvancedKeyMgmtCapabilities(@NonNull String ifaceName) { + public long getAdvancedKeyMgmtCapabilities(@NonNull String ifaceName) { final String methodStr = "getAdvancedKeyMgmtCapabilities"; - int advancedCapabilities = 0; + long advancedCapabilities = 0; int keyMgmtCapabilities = getKeyMgmtCapabilities(ifaceName); if ((keyMgmtCapabilities & android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork diff --git a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java index 9255fc231..dd56b5f57 100644 --- a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java +++ b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java @@ -339,19 +339,11 @@ public class SupplicantStaNetworkHal { Log.e(TAG, config.SSID + ": failed to set hiddenSSID: " + config.hiddenSSID); return false; } - // The logic below is skipping WPA2-Enterprise explicit setting of PMF to disabled - // in order to allow connection to networks with PMF required. Skipping means that - // wpa_supplicant will use the global setting (optional/capable). - // TODO(b/130755779): A permanent fix should convert requirePMF to a tri-state variablbe - boolean wpa2EnterpriseSkipPmf = !config.requirePMF - && (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) - || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)); + /** RequirePMF */ - if (!wpa2EnterpriseSkipPmf) { - if (!setRequirePmf(config.requirePMF)) { - Log.e(TAG, config.SSID + ": failed to set requirePMF: " + config.requirePMF); - return false; - } + if (!setRequirePmf(config.requirePMF)) { + Log.e(TAG, config.SSID + ": failed to set requirePMF: " + config.requirePMF); + return false; } /** Key Management Scheme */ if (config.allowedKeyManagement.cardinality() != 0) { diff --git a/service/java/com/android/server/wifi/WakeupConfigStoreData.java b/service/java/com/android/server/wifi/WakeupConfigStoreData.java index d191ee3d6..847d8fbbc 100644 --- a/service/java/com/android/server/wifi/WakeupConfigStoreData.java +++ b/service/java/com/android/server/wifi/WakeupConfigStoreData.java @@ -16,10 +16,12 @@ package com.android.server.wifi; +import android.annotation.Nullable; import android.util.ArraySet; import android.util.Log; import com.android.server.wifi.WifiConfigStore.StoreData; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; @@ -94,7 +96,8 @@ public class WakeupConfigStoreData implements StoreData { } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { writeFeatureState(out); @@ -141,7 +144,9 @@ public class WakeupConfigStoreData implements StoreData { } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { if (!mHasBeenRead) { Log.d(TAG, "WifiWake user data has been read"); diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java index d1734d445..c617b9e1f 100644 --- a/service/java/com/android/server/wifi/WifiConfigManager.java +++ b/service/java/com/android/server/wifi/WifiConfigManager.java @@ -76,8 +76,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.crypto.Mac; - /** * This class provides the APIs to manage configured Wi-Fi networks. * It deals with the following: @@ -276,8 +274,8 @@ public class WifiConfigManager { private final WifiPermissionsUtil mWifiPermissionsUtil; private final WifiPermissionsWrapper mWifiPermissionsWrapper; private final WifiInjector mWifiInjector; + private final MacAddressUtil mMacAddressUtil; private boolean mConnectedMacRandomzationSupported; - private final Mac mMac; /** * Local log used for debugging any WifiConfigManager issues. @@ -320,6 +318,7 @@ public class WifiConfigManager { private final int mMaxNumActiveChannelsForPartialScans; private final FrameworkFacade mFrameworkFacade; + private final DeviceConfigFacade mDeviceConfigFacade; /** * Verbose logging flag. Toggled by developer options. @@ -377,6 +376,7 @@ public class WifiConfigManager { private boolean mPnoFrequencyCullingEnabled = false; private boolean mPnoRecencySortingEnabled = false; + private Set<String> mRandomizationFlakySsidHotlist; @@ -394,7 +394,8 @@ public class WifiConfigManager { NetworkListUserStoreData networkListUserStoreData, DeletedEphemeralSsidsStoreData deletedEphemeralSsidsStoreData, RandomizedMacStoreData randomizedMacStoreData, - FrameworkFacade frameworkFacade, Looper looper) { + FrameworkFacade frameworkFacade, Looper looper, + DeviceConfigFacade deviceConfigFacade) { mContext = context; mClock = clock; mUserManager = userManager; @@ -446,17 +447,21 @@ public class WifiConfigManager { updatePnoRecencySortingSetting(); mConnectedMacRandomzationSupported = mContext.getResources() .getBoolean(R.bool.config_wifi_connected_mac_randomization_supported); + mDeviceConfigFacade = deviceConfigFacade; + mDeviceConfigFacade.addOnPropertiesChangedListener( + command -> new Handler(looper).post(command), + properties -> { + mRandomizationFlakySsidHotlist = + mDeviceConfigFacade.getRandomizationFlakySsidHotlist(); + }); + mRandomizationFlakySsidHotlist = mDeviceConfigFacade.getRandomizationFlakySsidHotlist(); try { mSystemUiUid = mContext.getPackageManager().getPackageUidAsUser(SYSUI_PACKAGE_NAME, PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to resolve SystemUI's UID."); } - mMac = WifiConfigurationUtil.obtainMacRandHashFunction(Process.WIFI_UID); - if (mMac == null) { - Log.wtf(TAG, "Failed to obtain secret for MAC randomization." - + " All randomized MAC addresses are lost!"); - } + mMacAddressUtil = mWifiInjector.getMacAddressUtil(); } /** @@ -508,7 +513,18 @@ public class WifiConfigManager { mRandomizedMacAddressMapping.remove(config.getSsidAndSecurityTypeString()); } } - return WifiConfigurationUtil.calculatePersistentMacForConfiguration(config, mMac); + MacAddress result = mMacAddressUtil.calculatePersistentMacForConfiguration( + config, mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID)); + if (result == null) { + result = mMacAddressUtil.calculatePersistentMacForConfiguration( + config, mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID)); + } + if (result == null) { + Log.wtf(TAG, "Failed to generate MAC address from KeyStore even after retrying. " + + "Using locally generated MAC address instead."); + result = MacAddress.createRandomUnicastAddress(); + } + return result; } /** @@ -1525,6 +1541,19 @@ public class WifiConfigManager { } /** + * Check whether a network belong to a known list of networks that may not support randomized + * MAC. + * @param networkId + * @return true if the network is in the hotlist and MAC randomization is enabled. + */ + public boolean isInFlakyRandomizationSsidHotlist(int networkId) { + WifiConfiguration config = getConfiguredNetwork(networkId); + return config != null + && config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT + && mRandomizationFlakySsidHotlist.contains(config.SSID); + } + + /** * Helper method to mark a network enabled for network selection. */ private void setNetworkSelectionEnabled(WifiConfiguration config) { @@ -3131,7 +3160,8 @@ public class WifiConfigManager { if (mDeferredUserUnlockRead) { Log.i(TAG, "Handling user unlock before loading from store."); List<WifiConfigStore.StoreFile> userStoreFiles = - WifiConfigStore.createUserFiles(mCurrentUserId); + WifiConfigStore.createUserFiles( + mCurrentUserId, mFrameworkFacade.isNiapModeOn(mContext)); if (userStoreFiles == null) { Log.wtf(TAG, "Failed to create user store files"); return false; @@ -3170,7 +3200,8 @@ public class WifiConfigManager { private boolean loadFromUserStoreAfterUnlockOrSwitch(int userId) { try { List<WifiConfigStore.StoreFile> userStoreFiles = - WifiConfigStore.createUserFiles(userId); + WifiConfigStore.createUserFiles( + userId, mFrameworkFacade.isNiapModeOn(mContext)); if (userStoreFiles == null) { Log.e(TAG, "Failed to create user store files"); return false; @@ -3180,8 +3211,8 @@ public class WifiConfigManager { Log.wtf(TAG, "Reading from new store failed. All saved private networks are lost!", e); return false; } catch (XmlPullParserException e) { - Log.wtf(TAG, "XML deserialization of store failed. All saved private networks are" + - "lost!", e); + Log.wtf(TAG, "XML deserialization of store failed. All saved private networks are " + + "lost!", e); return false; } loadInternalDataFromUserStore(mNetworkListUserStoreData.getConfigurations(), diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java index e189d00e1..42d9f82cb 100644 --- a/service/java/com/android/server/wifi/WifiConfigStore.java +++ b/service/java/com/android/server/wifi/WifiConfigStore.java @@ -35,8 +35,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.AtomicFile; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; -import com.android.server.wifi.util.DataIntegrityChecker; import com.android.server.wifi.util.EncryptedData; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; @@ -53,7 +53,6 @@ import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -62,6 +61,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * This class provides a mechanism to save data to persistent store files {@link StoreFile}. @@ -101,22 +101,36 @@ public class WifiConfigStore { private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData"; private static final String XML_TAG_VERSION = "Version"; private static final String XML_TAG_HEADER_INTEGRITY = "Integrity"; - private static final String XML_TAG_INTEGRITY_ENCRYPTED_DATA = "EncryptedData"; - private static final String XML_TAG_INTEGRITY_IV = "IV"; /** * Current config store data version. This will be incremented for any additions. */ - private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 2; + private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 3; /** This list of older versions will be used to restore data from older config store. */ /** * First version of the config store data format. */ - private static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1; + public static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1; /** * Second version of the config store data format, introduced: * - Integrity info. */ - private static final int INTEGRITY_CONFIG_STORE_DATA_VERSION = 2; + public static final int INTEGRITY_CONFIG_STORE_DATA_VERSION = 2; + /** + * Third version of the config store data format, + * introduced: + * - Encryption of credentials + * removed: + * - Integrity info. + */ + public static final int ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION = 3; + + @IntDef(suffix = { "_VERSION" }, value = { + INITIAL_CONFIG_STORE_DATA_VERSION, + INTEGRITY_CONFIG_STORE_DATA_VERSION, + ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Version { } /** * Alarm tag to use for starting alarms for buffering file writes. @@ -157,12 +171,6 @@ public class WifiConfigStore { put(STORE_FILE_USER_GENERAL, STORE_FILE_NAME_USER_GENERAL); put(STORE_FILE_USER_NETWORK_SUGGESTIONS, STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS); }}; - - @VisibleForTesting - public static final EncryptedData ZEROED_ENCRYPTED_DATA = - new EncryptedData( - new byte[EncryptedData.ENCRYPTED_DATA_LENGTH], - new byte[EncryptedData.IV_LENGTH]); /** * Handler instance to post alarm timeouts to */ @@ -280,9 +288,11 @@ public class WifiConfigStore { * @param storeBaseDir Base directory under which the store file is to be stored. The store file * will be at <storeBaseDir>/wifi/WifiConfigStore.xml. * @param fileId Identifier for the file. See {@link StoreFileId}. + * @param shouldEncryptCredentials Whether to encrypt credentials or not. * @return new instance of the store file or null if the directory cannot be created. */ - private static @Nullable StoreFile createFile(File storeBaseDir, @StoreFileId int fileId) { + private static @Nullable StoreFile createFile(File storeBaseDir, @StoreFileId int fileId, + boolean shouldEncryptCredentials) { File storeDir = new File(storeBaseDir, STORE_DIRECTORY_NAME); if (!storeDir.exists()) { if (!storeDir.mkdir()) { @@ -291,17 +301,22 @@ public class WifiConfigStore { } } File file = new File(storeDir, STORE_ID_TO_FILE_NAME.get(fileId)); - DataIntegrityChecker dataIntegrityChecker = new DataIntegrityChecker(file.getName()); - return new StoreFile(file, fileId, dataIntegrityChecker); + WifiConfigStoreEncryptionUtil encryptionUtil = null; + if (shouldEncryptCredentials) { + encryptionUtil = new WifiConfigStoreEncryptionUtil(file.getName()); + } + return new StoreFile(file, fileId, encryptionUtil); } /** * Create a new instance of the shared store file. * + * @param shouldEncryptCredentials Whether to encrypt credentials or not. * @return new instance of the store file or null if the directory cannot be created. */ - public static @Nullable StoreFile createSharedFile() { - return createFile(Environment.getDataMiscDirectory(), STORE_FILE_SHARED_GENERAL); + public static @Nullable StoreFile createSharedFile(boolean shouldEncryptCredentials) { + return createFile(Environment.getDataMiscDirectory(), STORE_FILE_SHARED_GENERAL, + shouldEncryptCredentials); } /** @@ -309,14 +324,18 @@ public class WifiConfigStore { * The user store file is inside the user's encrypted data directory. * * @param userId userId corresponding to the currently logged-in user. + * @param shouldEncryptCredentials Whether to encrypt credentials or not. * @return List of new instances of the store files created or null if the directory cannot be * created. */ - public static @Nullable List<StoreFile> createUserFiles(int userId) { + public static @Nullable List<StoreFile> createUserFiles(int userId, + boolean shouldEncryptCredentials) { List<StoreFile> storeFiles = new ArrayList<>(); for (int fileId : Arrays.asList( STORE_FILE_USER_GENERAL, STORE_FILE_USER_NETWORK_SUGGESTIONS)) { - StoreFile storeFile = createFile(Environment.getDataMiscCeDirectory(userId), fileId); + StoreFile storeFile = + createFile(Environment.getDataMiscCeDirectory(userId), fileId, + shouldEncryptCredentials); if (storeFile == null) { return null; } @@ -427,88 +446,18 @@ public class WifiConfigStore { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - // To compute integrity, write zeroes in the integrity fields. Once the integrity is - // computed, go back and modfiy the XML fields in place with the computed values. - writeDocumentMetadata(out, ZEROED_ENCRYPTED_DATA); + // First XML header. + XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER); + // Next version. + XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_CONFIG_STORE_DATA_VERSION); for (StoreData storeData : storeDataList) { String tag = storeData.getName(); XmlUtil.writeNextSectionStart(out, tag); - storeData.serializeData(out); + storeData.serializeData(out, storeFile.getEncryptionUtil()); XmlUtil.writeNextSectionEnd(out, tag); } XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER); - - byte[] outBytes = outputStream.toByteArray(); - EncryptedData encryptedData = storeFile.computeIntegrity(outBytes); - if (encryptedData == null) { - // should never happen, this is a fatal failure. Abort file write. - Log.wtf(TAG, "Failed to compute integrity, failing write"); - return null; - } - return rewriteDocumentMetadataRawBytes(outBytes, encryptedData); - } - - /** - * Helper method to write the metadata at the start of every config store file. - * The metadata consists of: - * a) Version - * b) Integrity data computed on the data contents. - */ - private void writeDocumentMetadata(XmlSerializer out, EncryptedData encryptedData) - throws XmlPullParserException, IOException { - // First XML header. - XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER); - // Next version. - XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_CONFIG_STORE_DATA_VERSION); - - // Next integrity data. - XmlUtil.writeNextSectionStart(out, XML_TAG_HEADER_INTEGRITY); - XmlUtil.writeNextValue(out, XML_TAG_INTEGRITY_ENCRYPTED_DATA, - encryptedData.getEncryptedData()); - XmlUtil.writeNextValue(out, XML_TAG_INTEGRITY_IV, encryptedData.getIv()); - XmlUtil.writeNextSectionEnd(out, XML_TAG_HEADER_INTEGRITY); - } - - /** - * Helper method to generate the raw bytes containing the the metadata at the start of every - * config store file. - * - * NOTE: This does not create a fully formed XML document (the start tag is not closed for - * example). This only creates the top portion of the XML which contains the modified - * integrity data & version along with the XML prolog (metadata): - * <?xml version='1.0' encoding='utf-8' standalone='yes' ?> - * <WifiConfigStoreData> - * <int name="Version" value="2" /> - * <Integrity> - * <byte-array name="EncryptedData" num="48">!EncryptedData!</byte-array> - * <byte-array name="IV" num="12">!IV!</byte-array> - * </Integrity> - */ - private byte[] generateDocumentMetadataRawBytes(EncryptedData encryptedData) - throws XmlPullParserException, IOException { - final XmlSerializer outXml = new FastXmlSerializer(); - final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - outXml.setOutput(outStream, StandardCharsets.UTF_8.name()); - writeDocumentMetadata(outXml, encryptedData); - outXml.endDocument(); - return outStream.toByteArray(); - } - - /** - * Helper method to rewrite the existing document metadata in the incoming raw bytes in - * |inBytes| with the new document metadata created with the provided |encryptedData|. - * - * NOTE: This assumes that the metadata in existing XML inside |inBytes| aligns exactly - * with the new metadata created. So, a simple in place rewrite of the first few bytes ( - * corresponding to metadata section of the document) from |inBytes| will preserve the overall - * document structure. - */ - private byte[] rewriteDocumentMetadataRawBytes(byte[] inBytes, EncryptedData encryptedData) - throws XmlPullParserException, IOException { - byte[] replaceMetadataBytes = generateDocumentMetadataRawBytes(encryptedData); - ByteBuffer outByteBuffer = ByteBuffer.wrap(inBytes); - outByteBuffer.put(replaceMetadataBytes); - return outByteBuffer.array(); + return outputStream.toByteArray(); } /** @@ -629,10 +578,11 @@ public class WifiConfigStore { } // Inform all the provided store data clients that there is nothing in the store for them. - private void indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet) + private void indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet, + @Version int version, @NonNull WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { for (StoreData storeData : storeDataSet) { - storeData.deserializeData(null, 0); + storeData.deserializeData(null, 0, version, encryptionUtil); } } @@ -654,7 +604,8 @@ public class WifiConfigStore { throws XmlPullParserException, IOException { List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile); if (dataBytes == null) { - indicateNoDataForStoreDatas(storeDataList); + indicateNoDataForStoreDatas(storeDataList, -1 /* unknown */, + storeFile.getEncryptionUtil()); return; } final XmlPullParser in = Xml.newPullParser(); @@ -665,21 +616,10 @@ public class WifiConfigStore { int rootTagDepth = in.getDepth() + 1; XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER); - int version = parseVersionFromXml(in); - // Version 2 onwards contains integrity data, so check the integrity of the file. - if (version >= INTEGRITY_CONFIG_STORE_DATA_VERSION) { - EncryptedData integrityData = parseIntegrityDataFromXml(in, rootTagDepth); - // To compute integrity, write zeroes in the integrity fields. - dataBytes = rewriteDocumentMetadataRawBytes(dataBytes, ZEROED_ENCRYPTED_DATA); - if (!storeFile.checkIntegrity(dataBytes, integrityData)) { - Log.wtf(TAG, "Integrity mismatch, discarding data from " + storeFile.mFileName); - return; - } - } else { - // When integrity checking is introduced. The existing data will have no related - // integrity file for validation. Thus, we will assume the existing data is correct. - // Integrity will be computed for the next write. - Log.d(TAG, "No integrity data to check; thus vacously true"); + @Version int version = parseVersionFromXml(in); + // Version 2 contains the now unused integrity data, parse & then discard the information. + if (version == INTEGRITY_CONFIG_STORE_DATA_VERSION) { + parseAndDiscardIntegrityDataFromXml(in, rootTagDepth); } String[] headerName = new String[1]; @@ -695,14 +635,15 @@ public class WifiConfigStore { throw new XmlPullParserException("Unknown store data: " + headerName[0] + ". List of store data: " + storeDataList); } - storeData.deserializeData(in, rootTagDepth + 1); + storeData.deserializeData(in, rootTagDepth + 1, version, + storeFile.getEncryptionUtil()); storeDatasInvoked.add(storeData); } // Inform all the other registered store data clients that there is nothing in the store // for them. Set<StoreData> storeDatasNotInvoked = new HashSet<>(storeDataList); storeDatasNotInvoked.removeAll(storeDatasInvoked); - indicateNoDataForStoreDatas(storeDatasNotInvoked); + indicateNoDataForStoreDatas(storeDatasNotInvoked, version, storeFile.getEncryptionUtil()); } /** @@ -712,7 +653,7 @@ public class WifiConfigStore { * @param in XmlPullParser instance pointing to the XML stream. * @return version number retrieved from the Xml stream. */ - private static int parseVersionFromXml(XmlPullParser in) + private static @Version int parseVersionFromXml(XmlPullParser in) throws XmlPullParserException, IOException { int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION); if (version < INITIAL_CONFIG_STORE_DATA_VERSION @@ -723,22 +664,15 @@ public class WifiConfigStore { } /** - * Parse the integrity data structure from the XML stream. - * This is used for both the shared and user config store data. + * Parse the integrity data structure from the XML stream and discard it. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth Outer tag depth. - * @return Instance of {@link EncryptedData} retrieved from the Xml stream. */ - private static @NonNull EncryptedData parseIntegrityDataFromXml( - XmlPullParser in, int outerTagDepth) + private static void parseAndDiscardIntegrityDataFromXml(XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException { XmlUtil.gotoNextSectionWithName(in, XML_TAG_HEADER_INTEGRITY, outerTagDepth); - byte[] encryptedData = - (byte[]) XmlUtil.readNextValueWithName(in, XML_TAG_INTEGRITY_ENCRYPTED_DATA); - byte[] iv = - (byte[]) XmlUtil.readNextValueWithName(in, XML_TAG_INTEGRITY_IV); - return new EncryptedData(encryptedData, iv); + XmlUtil.EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1); } /** @@ -746,6 +680,13 @@ public class WifiConfigStore { */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Dump of WifiConfigStore"); + pw.println("WifiConfigStore - Store File Begin ----"); + Stream.of(Arrays.asList(mSharedStore), mUserStores) + .flatMap(List::stream) + .forEach((storeFile) -> { + pw.print("Name: " + storeFile.mFileName); + pw.println(", Credentials encrypted: " + storeFile.getEncryptionUtil() != null); + }); pw.println("WifiConfigStore - Store Data Begin ----"); for (StoreData storeData : mStoreDataList) { pw.print("StoreData =>"); @@ -790,14 +731,14 @@ public class WifiConfigStore { /** * Integrity checking for the store file. */ - private final DataIntegrityChecker mDataIntegrityChecker; + private final WifiConfigStoreEncryptionUtil mEncryptionUtil; public StoreFile(File file, @StoreFileId int fileId, - @NonNull DataIntegrityChecker dataIntegrityChecker) { + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) { mAtomicFile = new AtomicFile(file); mFileName = file.getAbsolutePath(); mFileId = fileId; - mDataIntegrityChecker = dataIntegrityChecker; + mEncryptionUtil = encryptionUtil; } /** @@ -810,6 +751,13 @@ public class WifiConfigStore { } /** + * @return Returns the encryption util used for this store file. + */ + public @Nullable WifiConfigStoreEncryptionUtil getEncryptionUtil() { + return mEncryptionUtil; + } + + /** * Read the entire raw data from the store file and return in a byte array. * * @return raw data read from the file or null if the file is not found or the data has @@ -862,33 +810,6 @@ public class WifiConfigStore { // Reset the pending write data after write. mWriteData = null; } - - /** - * Compute integrity of |dataWithZeroedIntegrityFields| to be written to the file. - * - * @param dataWithZeroedIntegrityFields raw data to be written to the file with the - * integrity fields zeroed out for integrity - * calculation. - * @return Instance of {@link EncryptedData} holding the encrypted integrity data for the - * raw data to be written to the file. - */ - public EncryptedData computeIntegrity(byte[] dataWithZeroedIntegrityFields) { - return mDataIntegrityChecker.compute(dataWithZeroedIntegrityFields); - } - - /** - * Check integrity of |dataWithZeroedIntegrityFields| read from the file with the integrity - * data parsed from the file. - * @param dataWithZeroedIntegrityFields raw data read from the file with the integrity - * fields zeroed out for integrity calculation. - * @param parsedEncryptedData Instance of {@link EncryptedData} parsed from the integrity - * fields in the raw data. - * @return true if the integrity matches, false otherwise. - */ - public boolean checkIntegrity(byte[] dataWithZeroedIntegrityFields, - EncryptedData parsedEncryptedData) { - return mDataIntegrityChecker.isOk(dataWithZeroedIntegrityFields, parsedEncryptedData); - } } /** @@ -908,8 +829,10 @@ public class WifiConfigStore { * Serialize a XML data block to the output stream. * * @param out The output stream to serialize the data to + * @param encryptionUtil Utility to help encrypt any credential data. */ - void serializeData(XmlSerializer out) + void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException; /** @@ -918,10 +841,14 @@ public class WifiConfigStore { * @param in The input stream to read the data from. This could be null if there is * nothing in the store. * @param outerTagDepth The depth of the outer tag in the XML document + * @param version Version of config store file. + * @param encryptionUtil Utility to help decrypt any credential data. + * * Note: This will be invoked every time a store file is read, even if there is nothing * in the store for them. */ - void deserializeData(@Nullable XmlPullParser in, int outerTagDepth) + void deserializeData(@Nullable XmlPullParser in, int outerTagDepth, @Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException; /** diff --git a/service/java/com/android/server/wifi/WifiCountryCode.java b/service/java/com/android/server/wifi/WifiCountryCode.java index 499c159fa..f9d147c6b 100644 --- a/service/java/com/android/server/wifi/WifiCountryCode.java +++ b/service/java/com/android/server/wifi/WifiCountryCode.java @@ -84,18 +84,6 @@ public class WifiCountryCode { } /** - * This is called when airplane mode is enabled. - * In this case we should invalidate all other country code except the - * phone default one. - */ - public synchronized void airplaneModeEnabled() { - Log.d(TAG, "Airplane Mode Enabled"); - // Airplane mode is enabled, we need to reset the country code to phone default. - // Country code will be set upon when wpa_supplicant starts next time. - mTelephonyCountryCode = null; - } - - /** * Change the state to indicates if wpa_supplicant is ready to handle country code changing * request or not. * We call native code to request country code changes only when wpa_supplicant is diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index fe9ebea17..339e169f6 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AppOpsManager; +import android.app.NotificationManager; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.SystemSensorManager; @@ -154,6 +155,8 @@ public class WifiInjector { private final LinkProbeManager mLinkProbeManager; private final IpMemoryStore mIpMemoryStore; private final CellularLinkLayerStatsCollector mCellularLinkLayerStatsCollector; + private final MacAddressUtil mMacAddressUtil; + private final ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder; public WifiInjector(Context context) { if (context == null) { @@ -168,8 +171,11 @@ public class WifiInjector { sWifiInjector = this; + mMacAddressUtil = new MacAddressUtil(); mContext = context; mDeviceConfigFacade = new DeviceConfigFacade(); + mConnectionFailureNotificationBuilder = new ConnectionFailureNotificationBuilder( + mContext, getWifiStackPackageName(), mFrameworkFacade); mWifiScoreCard = new WifiScoreCard(mClock, Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID)); mSettingsStore = new WifiSettingsStore(mContext); @@ -241,7 +247,7 @@ public class WifiInjector { mWifiKeyStore = new WifiKeyStore(mKeyStore); mWifiConfigStore = new WifiConfigStore( mContext, clientModeImplLooper, mClock, mWifiMetrics, - WifiConfigStore.createSharedFile()); + WifiConfigStore.createSharedFile(mFrameworkFacade.isNiapModeOn(mContext))); SubscriptionManager subscriptionManager = mContext.getSystemService(SubscriptionManager.class); // Config Manager @@ -251,7 +257,7 @@ public class WifiInjector { mWifiPermissionsWrapper, this, new NetworkListSharedStoreData(mContext), new NetworkListUserStoreData(mContext), new DeletedEphemeralSsidsStoreData(mClock), new RandomizedMacStoreData(), - mFrameworkFacade, mWifiCoreHandlerThread.getLooper()); + mFrameworkFacade, mWifiCoreHandlerThread.getLooper(), mDeviceConfigFacade); mWifiMetrics.setWifiConfigManager(mWifiConfigManager); mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative); mConnectivityLocalLog = new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 256 : 512); @@ -621,6 +627,17 @@ public class WifiInjector { } /** + * Construct a new instance of ConnectionFailureNotifier. + * @param wifiConnectivityManager + * @return the created instance + */ + public ConnectionFailureNotifier makeConnectionFailureNotifier( + WifiConnectivityManager wifiConnectivityManager) { + return new ConnectionFailureNotifier(mContext, this, mFrameworkFacade, mWifiConfigManager, + wifiConnectivityManager, new Handler(mWifiCoreHandlerThread.getLooper())); + } + + /** * Construct a new instance of {@link WifiNetworkFactory}. * TODO(b/116233964): Remove cyclic dependency between WifiConnectivityManager & ClientModeImpl. */ @@ -695,6 +712,18 @@ public class WifiInjector { return mRttHandlerThread; } + public MacAddressUtil getMacAddressUtil() { + return mMacAddressUtil; + } + + public NotificationManager getNotificationManager() { + return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + public ConnectionFailureNotificationBuilder getConnectionFailureNotificationBuilder() { + return mConnectionFailureNotificationBuilder; + } + /** * Returns a single instance of HalDeviceManager for injection. */ @@ -753,4 +782,8 @@ public class WifiInjector { public HostapdHal getHostapdHal() { return mHostapdHal; } + + public String getWifiStackPackageName() { + return mContext.getPackageName(); + } } diff --git a/service/java/com/android/server/wifi/WifiLockManager.java b/service/java/com/android/server/wifi/WifiLockManager.java index e292a84d8..45e68824c 100644 --- a/service/java/com/android/server/wifi/WifiLockManager.java +++ b/service/java/com/android/server/wifi/WifiLockManager.java @@ -183,10 +183,6 @@ public class WifiLockManager { * @return true if the lock was successfully acquired, false if the lockMode was invalid. */ public boolean acquireWifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) { - if (!isValidLockMode(lockMode)) { - throw new IllegalArgumentException("lockMode =" + lockMode); - } - // Make a copy of the WorkSource before adding it to the WakeLock // This is to make sure worksource value can not be changed by caller // after function returns. @@ -384,7 +380,13 @@ public class WifiLockManager { } } - private static boolean isValidLockMode(int lockMode) { + /** + * Validate that the lock mode is valid - i.e. one of the supported enumerations. + * + * @param lockMode The lock mode to verify. + * @return true for valid lock modes, false otherwise. + */ + public static boolean isValidLockMode(int lockMode) { if (lockMode != WifiManager.WIFI_MODE_FULL && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY && lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java index 1b7e8cdb3..7578d3723 100644 --- a/service/java/com/android/server/wifi/WifiMetrics.java +++ b/service/java/com/android/server/wifi/WifiMetrics.java @@ -2942,6 +2942,7 @@ public class WifiMetrics { public void updateSavedNetworks(List<WifiConfiguration> networks) { synchronized (mLock) { mWifiLogProto.numSavedNetworks = networks.size(); + mWifiLogProto.numSavedNetworksWithMacRandomization = 0; mWifiLogProto.numOpenNetworks = 0; mWifiLogProto.numLegacyPersonalNetworks = 0; mWifiLogProto.numLegacyEnterpriseNetworks = 0; @@ -3790,9 +3791,11 @@ public class WifiMetrics { mLastScore = -1; mLastWifiUsabilityScore = -1; mLastPredictionHorizonSec = -1; - mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis())); - // Prune StaEventList if it gets too long - if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove(); + synchronized (mLock) { + mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis())); + // Prune StaEventList if it gets too long + if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove(); + } } private ConfigInfo createConfigInfo(WifiConfiguration config) { @@ -4624,6 +4627,7 @@ public class WifiMetrics { } } mWifiUsabilityStatsCounter = 0; + mWifiUsabilityStatsEntriesList.clear(); } } diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java index c375e9f59..4b5866b44 100644 --- a/service/java/com/android/server/wifi/WifiNetworkFactory.java +++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java @@ -82,6 +82,8 @@ public class WifiNetworkFactory extends NetworkFactory { @VisibleForTesting private static final int SCORE_FILTER = 60; @VisibleForTesting + public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 20 * 1000; // 20 seconds + @VisibleForTesting public static final int PERIODIC_SCAN_INTERVAL_MS = 10 * 1000; // 10 seconds @VisibleForTesting public static final int NETWORK_CONNECTION_TIMEOUT_MS = 30 * 1000; // 30 seconds @@ -230,37 +232,10 @@ public class WifiNetworkFactory extends NetworkFactory { if (mVerboseLoggingEnabled) { Log.v(TAG, "Received " + scanResults.length + " scan results"); } - List<ScanResult> matchedScanResults = - getNetworksMatchingActiveNetworkRequest(scanResults); - if (mActiveMatchedScanResults == null) { - // only note the first match size in metrics (chances of this changing in further - // scans is pretty low) - mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram( - matchedScanResults.size()); - } - mActiveMatchedScanResults = matchedScanResults; - - ScanResult approvedScanResult = null; - if (isActiveRequestForSingleAccessPoint()) { - approvedScanResult = - findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults(); - } - if (approvedScanResult != null - && !mWifiConfigManager.wasEphemeralNetworkDeleted( - ScanResultUtil.createQuotedSSID(approvedScanResult.SSID))) { - Log.v(TAG, "Approved access point found in matching scan results. " - + "Triggering connect " + approvedScanResult); - handleConnectToNetworkUserSelectionInternal( - ScanResultUtil.createNetworkFromScanResult(approvedScanResult)); - mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass(); - // TODO (b/122658039): Post notification. - } else { - if (mVerboseLoggingEnabled) { - Log.v(TAG, "No approved access points found in matching scan results. " - + "Sending match callback"); - } - sendNetworkRequestMatchCallbacksForActiveRequest(matchedScanResults); - // Didn't find an approved match, schedule the next scan. + if (!handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(scanResults)) { + // Didn't find an approved match, send the matching results to UI and schedule the + // next scan. + sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults); scheduleNextPeriodicScan(); } } @@ -453,7 +428,12 @@ public class WifiNetworkFactory extends NetworkFactory { new NetworkFactoryUserSelectionCallback(mActiveSpecificNetworkRequest)); } catch (RemoteException e) { Log.e(TAG, "Unable to invoke user selection registration callback " + callback, e); + return; } + + // If we are already in the midst of processing a request, send matching callbacks + // immediately on registering the callback. + sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults); } /** @@ -612,10 +592,19 @@ public class WifiNetworkFactory extends NetworkFactory { wns.requestorUid, wns.requestorPackageName); mWifiMetrics.incrementNetworkRequestApiNumRequest(); - // Start UI to let the user grant/disallow this request from the app. - startUi(); - // Trigger periodic scans for finding a network in the request. - startPeriodicScans(); + // Fetch the latest cached scan results to speed up network matching. + ScanResult[] cachedScanResults = getFilteredCachedScanResults(); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Using cached " + cachedScanResults.length + " scan results"); + } + if (!handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(cachedScanResults)) { + // Start UI to let the user grant/disallow this request from the app. + startUi(); + // Didn't find an approved match, send the matching results to UI and trigger + // periodic scans for finding a network in the request. + sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults); + startPeriodicScans(); + } } } @@ -712,6 +701,11 @@ public class WifiNetworkFactory extends NetworkFactory { WifiConfiguration existingSavedNetwork = mWifiConfigManager.getConfiguredNetwork(network.configKey()); if (existingSavedNetwork != null) { + if (WifiConfigurationUtil.hasCredentialChanged(existingSavedNetwork, network)) { + // TODO (b/142035508): What if the user has a saved network with different + // credentials? + Log.w(TAG, "Network config already present in config manager, reusing"); + } return existingSavedNetwork.networkId; } NetworkUpdateResult networkUpdateResult = @@ -724,6 +718,32 @@ public class WifiNetworkFactory extends NetworkFactory { return networkUpdateResult.netId; } + // Helper method to remove the provided network configuration from WifiConfigManager, if it was + // added by an app's specifier request. + private void disconnectAndRemoveNetworkFromWifiConfigManager( + @Nullable WifiConfiguration network) { + // Trigger a disconnect first. + mWifiInjector.getClientModeImpl().disconnectCommand(); + + if (network == null) return; + WifiConfiguration wcmNetwork = + mWifiConfigManager.getConfiguredNetwork(network.configKey()); + if (wcmNetwork == null) { + Log.e(TAG, "Network not present in config manager"); + return; + } + // Remove the network if it was added previously by an app's specifier request. + if (wcmNetwork.ephemeral && wcmNetwork.fromWifiNetworkSpecifier) { + boolean success = + mWifiConfigManager.removeNetwork(wcmNetwork.networkId, wcmNetwork.creatorUid); + if (!success) { + Log.e(TAG, "Failed to remove network from config manager"); + } else if (mVerboseLoggingEnabled) { + Log.v(TAG, "Removed network from config manager " + wcmNetwork.networkId); + } + } + } + // Helper method to trigger a connection request & schedule a timeout alarm to track the // connection request. private void connectToNetwork(@NonNull WifiConfiguration network) { @@ -762,7 +782,6 @@ public class WifiNetworkFactory extends NetworkFactory { networkToConnect.SSID = network.SSID; // Set the WifiConfiguration.BSSID field to prevent roaming. networkToConnect.BSSID = findBestBssidFromActiveMatchedScanResultsForNetwork(network); - // Mark the network ephemeral so that it's automatically removed at the end of connection. networkToConnect.ephemeral = true; networkToConnect.fromWifiNetworkSpecifier = true; @@ -770,7 +789,8 @@ public class WifiNetworkFactory extends NetworkFactory { mUserSelectedNetwork = networkToConnect; // Disconnect from the current network before issuing a new connect request. - mWifiInjector.getClientModeImpl().disconnectCommand(); + disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork); + // Trigger connection to the network. connectToNetwork(networkToConnect); // Triggered connection to network, now wait for the connection status. @@ -966,6 +986,7 @@ public class WifiNetworkFactory extends NetworkFactory { mConnectedSpecificNetworkRequestSpecifier = mActiveSpecificNetworkRequestSpecifier; mActiveSpecificNetworkRequest = null; mActiveSpecificNetworkRequestSpecifier = null; + mActiveMatchedScanResults = null; mPendingConnectionSuccess = false; // Cancel connection timeout alarm. cancelConnectionTimeout(); @@ -974,7 +995,7 @@ public class WifiNetworkFactory extends NetworkFactory { // Invoked at the termination of current connected request processing. private void teardownForConnectedNetwork() { Log.i(TAG, "Disconnecting from network on reset"); - mWifiInjector.getClientModeImpl().disconnectCommand(); + disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork); mConnectedSpecificNetworkRequest = null; mConnectedSpecificNetworkRequestSpecifier = null; // ensure there is no active request in progress. @@ -1112,7 +1133,8 @@ public class WifiNetworkFactory extends NetworkFactory { } private void sendNetworkRequestMatchCallbacksForActiveRequest( - List<ScanResult> matchedScanResults) { + @Nullable List<ScanResult> matchedScanResults) { + if (matchedScanResults == null || matchedScanResults.isEmpty()) return; if (mRegisteredCallbacks.getNumCallbacks() == 0) { Log.e(TAG, "No callback registered for sending network request matches. " + "Ignoring..."); @@ -1300,6 +1322,65 @@ public class WifiNetworkFactory extends NetworkFactory { } /** + * Handle scan results + * a) Find all scan results matching the active network request. + * b) If the request is for a single bssid, check if the matching ScanResult was pre-approved + * by the user. + * c) If yes to (b), trigger a connect immediately and returns true. Else, returns false. + * + * @param scanResults Array of {@link ScanResult} to be processed. + * @return true if a pre-approved network was found for connection, false otherwise. + */ + private boolean handleScanResultsAndTriggerConnectIfUserApprovedMatchFound( + ScanResult[] scanResults) { + List<ScanResult> matchedScanResults = + getNetworksMatchingActiveNetworkRequest(scanResults); + if ((mActiveMatchedScanResults == null || mActiveMatchedScanResults.isEmpty()) + && !matchedScanResults.isEmpty()) { + // only note the first match size in metrics (chances of this changing in further + // scans is pretty low) + mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram( + matchedScanResults.size()); + } + mActiveMatchedScanResults = matchedScanResults; + + ScanResult approvedScanResult = null; + if (isActiveRequestForSingleAccessPoint()) { + approvedScanResult = + findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults(); + } + if (approvedScanResult != null + && !mWifiConfigManager.wasEphemeralNetworkDeleted( + ScanResultUtil.createQuotedSSID(approvedScanResult.SSID))) { + Log.v(TAG, "Approved access point found in matching scan results. " + + "Triggering connect " + approvedScanResult); + handleConnectToNetworkUserSelectionInternal( + ScanResultUtil.createNetworkFromScanResult(approvedScanResult)); + mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass(); + return true; + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "No approved access points found in matching scan results"); + } + return false; + } + + /** + * Retrieve the latest cached scan results from wifi scanner and filter out any + * {@link ScanResult} older than {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}. + */ + private @NonNull ScanResult[] getFilteredCachedScanResults() { + List<ScanResult> cachedScanResults = mWifiScanner.getSingleScanResults(); + if (cachedScanResults == null || cachedScanResults.isEmpty()) return new ScanResult[0]; + long currentTimeInMillis = mClock.getElapsedSinceBootMillis(); + return cachedScanResults.stream() + .filter(scanResult + -> ((currentTimeInMillis - (scanResult.timestamp / 1000)) + < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS)) + .toArray(ScanResult[]::new); + } + + /** * Clean up least recently used Access Points if specified app reach the limit. */ private static void cleanUpLRUAccessPoints(Set<AccessPoint> approvedAccessPoints) { diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index ed796c0a6..54c2b6121 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -561,10 +561,6 @@ public class WifiServiceImpl extends BaseWifiService { if (mSettingsStore.handleAirplaneModeToggled()) { mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED); } - if (mSettingsStore.isAirplaneModeOn()) { - Log.d(TAG, "resetting country code because Airplane mode is ON"); - mCountryCode.airplaneModeEnabled(); - } } }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)); @@ -2346,18 +2342,16 @@ public class WifiServiceImpl extends BaseWifiService { @Override public boolean removePasspointConfiguration(String fqdn, String packageName) { final int uid = Binder.getCallingUid(); - if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) - && !mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) { - if (mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q, uid)) { - return false; - } - throw new SecurityException(TAG + ": Permission denied"); + boolean privileged = false; + if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) + || mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) { + privileged = true; } mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush(); if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_PASSPOINT)) { return false; } - return mClientModeImpl.syncRemovePasspointConfig(mClientModeImplChannel, fqdn); + return mClientModeImpl.syncRemovePasspointConfig(mClientModeImplChannel, privileged, fqdn); } /** @@ -2370,13 +2364,10 @@ public class WifiServiceImpl extends BaseWifiService { @Override public List<PasspointConfiguration> getPasspointConfigurations(String packageName) { final int uid = Binder.getCallingUid(); - mAppOps.checkPackage(uid, packageName); - if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) - && !mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)) { - if (mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q, uid)) { - return new ArrayList<>(); - } - throw new SecurityException(TAG + ": Permission denied"); + boolean privileged = false; + if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) + || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)) { + privileged = true; } if (mVerboseLoggingEnabled) { mLog.info("getPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush(); @@ -2385,7 +2376,7 @@ public class WifiServiceImpl extends BaseWifiService { PackageManager.FEATURE_WIFI_PASSPOINT)) { return new ArrayList<>(); } - return mClientModeImpl.syncGetPasspointConfigs(mClientModeImplChannel); + return mClientModeImpl.syncGetPasspointConfigs(mClientModeImplChannel, privileged); } /** @@ -2869,6 +2860,10 @@ public class WifiServiceImpl extends BaseWifiService { WorkSource updatedWs = (ws == null || ws.isEmpty()) ? new WorkSource(Binder.getCallingUid()) : ws; + if (!WifiLockManager.isValidLockMode(lockMode)) { + throw new IllegalArgumentException("lockMode =" + lockMode); + } + Mutable<Boolean> lockSuccess = new Mutable<>(); boolean runWithScissorsSuccess = mWifiInjector.getClientModeImplHandler().runWithScissors( () -> { @@ -3012,7 +3007,7 @@ public class WifiServiceImpl extends BaseWifiService { if (mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI_PASSPOINT)) { List<PasspointConfiguration> configs = mClientModeImpl.syncGetPasspointConfigs( - mClientModeImplChannel); + mClientModeImplChannel, true); if (configs != null) { for (PasspointConfiguration config : configs) { removePasspointConfiguration(config.getHomeSp().getFqdn(), packageName); diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java index 464a34007..e780c10c4 100644 --- a/service/java/com/android/server/wifi/WifiVendorHal.java +++ b/service/java/com/android/server/wifi/WifiVendorHal.java @@ -1072,7 +1072,7 @@ public class WifiVendorHal { /** * Translation table used by getSupportedFeatureSet for translating IWifiChip caps for V1.1 */ - private static final int[][] sChipFeatureCapabilityTranslation = { + private static final long[][] sChipFeatureCapabilityTranslation = { {WifiManager.WIFI_FEATURE_TX_POWER_LIMIT, android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT }, @@ -1139,7 +1139,7 @@ public class WifiVendorHal { /** * Translation table used by getSupportedFeatureSet for translating IWifiStaIface caps */ - private static final int[][] sStaFeatureCapabilityTranslation = { + private static final long[][] sStaFeatureCapabilityTranslation = { {WifiManager.WIFI_FEATURE_INFRA_5G, IWifiStaIface.StaIfaceCapabilityMask.STA_5G }, @@ -1188,8 +1188,8 @@ public class WifiVendorHal { * @return bitmask defined by WifiManager.WIFI_FEATURE_* */ @VisibleForTesting - int wifiFeatureMaskFromStaCapabilities(int capabilities) { - int features = 0; + long wifiFeatureMaskFromStaCapabilities(int capabilities) { + long features = 0; for (int i = 0; i < sStaFeatureCapabilityTranslation.length; i++) { if ((capabilities & sStaFeatureCapabilityTranslation[i][1]) != 0) { features |= sStaFeatureCapabilityTranslation[i][0]; diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreData.java b/service/java/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreData.java index 419ea7993..7f5a6b408 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreData.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreData.java @@ -16,7 +16,10 @@ package com.android.server.wifi.hotspot2; +import android.annotation.Nullable; + import com.android.server.wifi.WifiConfigStore; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; @@ -72,13 +75,16 @@ public class PasspointConfigSharedStoreData implements WifiConfigStore.StoreData } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { serializeShareData(out); } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { // Ignore empty reads. if (in == null) { diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java b/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java index 0114cfb21..123cf8982 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java @@ -16,6 +16,7 @@ package com.android.server.wifi.hotspot2; +import android.annotation.Nullable; import android.net.wifi.hotspot2.PasspointConfiguration; import android.text.TextUtils; @@ -23,6 +24,7 @@ import com.android.internal.util.XmlUtils; import com.android.server.wifi.SIMAccessor; import com.android.server.wifi.WifiConfigStore; import com.android.server.wifi.WifiKeyStore; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; @@ -103,13 +105,16 @@ public class PasspointConfigUserStoreData implements WifiConfigStore.StoreData { } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { serializeUserData(out); } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, + @WifiConfigStore.Version int version, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { // Ignore empty reads. if (in == null) { diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java index e54cee8e9..c46873761 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java @@ -288,7 +288,7 @@ public class PasspointManager { for (Map.Entry<String, PasspointProvider> entry : getPasspointProviderWithPackage( packageName).entrySet()) { String fqdn = entry.getValue().getConfig().getHomeSp().getFqdn(); - removeProvider(fqdn); + removeProvider(Process.WIFI_UID /* ignored */, true, fqdn); disconnectIfPasspointNetwork(fqdn); } } @@ -658,18 +658,26 @@ public class PasspointManager { /** * Remove a Passpoint provider identified by the given FQDN. * + * @param callingUid Calling UID. + * @param privileged Whether the caller is a privileged entity * @param fqdn The FQDN of the provider to remove * @return true if a provider is removed, false otherwise */ - public boolean removeProvider(String fqdn) { + public boolean removeProvider(int callingUid, boolean privileged, String fqdn) { mWifiMetrics.incrementNumPasspointProviderUninstallation(); String packageName; - if (!mProviders.containsKey(fqdn)) { + PasspointProvider provider = mProviders.get(fqdn); + if (provider == null) { Log.e(TAG, "Config doesn't exist"); return false; } - mProviders.get(fqdn).uninstallCertsAndKeys(); - packageName = mProviders.get(fqdn).getPackageName(); + if (!privileged && callingUid != provider.getCreatorUid()) { + Log.e(TAG, "UID " + callingUid + " cannot remove profile created by " + + provider.getCreatorUid()); + return false; + } + provider.uninstallCertsAndKeys(); + packageName = provider.getPackageName(); mProviders.remove(fqdn); mWifiConfigManager.saveToStore(true /* forceWrite */); @@ -702,12 +710,17 @@ public class PasspointManager { * * An empty list will be returned when no provider is installed. * + * @param callingUid Calling UID. + * @param privileged Whether the caller is a privileged entity * @return A list of {@link PasspointConfiguration} */ - public List<PasspointConfiguration> getProviderConfigs() { + public List<PasspointConfiguration> getProviderConfigs(int callingUid, boolean privileged) { List<PasspointConfiguration> configs = new ArrayList<>(); for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { - configs.add(entry.getValue().getConfig()); + PasspointProvider provider = entry.getValue(); + if (privileged || callingUid == provider.getCreatorUid()) { + configs.add(provider.getConfig()); + } } return configs; } @@ -1007,7 +1020,8 @@ public class PasspointManager { public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders( List<OsuProvider> osuProviders) { Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigs = new HashMap<>(); - List<PasspointConfiguration> passpointConfigurations = getProviderConfigs(); + List<PasspointConfiguration> passpointConfigurations = + getProviderConfigs(Process.WIFI_UID /* ignored */, true); for (OsuProvider osuProvider : osuProviders) { Map<String, String> friendlyNamesForOsuProvider = osuProvider.getFriendlyNameList(); diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java index ec8a009d9..148af39c6 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java @@ -231,24 +231,20 @@ public class PasspointNetworkEvaluator implements WifiNetworkSelector.NetworkEva if (existingNetwork != null) { WifiConfiguration.NetworkSelectionStatus status = existingNetwork.getNetworkSelectionStatus(); - if (!status.isNetworkEnabled()) { - boolean isSuccess = mWifiConfigManager.tryEnableNetwork(existingNetwork.networkId); - if (isSuccess) { - return existingNetwork; - } + if (!status.isNetworkEnabled() + && !mWifiConfigManager.tryEnableNetwork(existingNetwork.networkId)) { localLog("Current configuration for the Passpoint AP " + config.SSID + " is disabled, skip this candidate"); return null; } - return existingNetwork; } - // Add the newly created WifiConfiguration to WifiConfigManager. + // Add or update with the newly created WifiConfiguration to WifiConfigManager. NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID); if (!result.isSuccess()) { localLog("Failed to add passpoint network"); - return null; + return existingNetwork; } mWifiConfigManager.enableNetwork(result.getNetworkId(), false, Process.WIFI_UID); mWifiConfigManager.setNetworkCandidateScanResult(result.getNetworkId(), diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java index a6098be2b..532659102 100644 --- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java +++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java @@ -100,6 +100,7 @@ import com.android.server.wifi.util.WifiPermissionsWrapper; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.InetAddress; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -148,6 +149,11 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { android.Manifest.permission.ACCESS_WIFI_STATE }; + // Maximum number of bytes allowed for a network name, i.e. SSID. + private static final int MAX_NETWORK_NAME_BYTES = 32; + // Minimum number of bytes for a network name, i.e. DIRECT-xy. + private static final int MIN_NETWORK_NAME_BYTES = 9; + // Two minutes comes from the wpa_supplicant setting private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000; private static int sGroupCreatingTimeoutIndex = 0; @@ -3260,6 +3266,23 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { } /** + * Check the network name complies standard SSID naming rules. + * + * The network name of a group is also the broadcasting SSID, + * as a result, the network name must complies standard SSID naming + * rules. + */ + private boolean isValidNetworkName(String networkName) { + if (TextUtils.isEmpty(networkName)) return false; + + byte[] ssidBytes = networkName.getBytes(StandardCharsets.UTF_8); + if (ssidBytes.length < MIN_NETWORK_NAME_BYTES) return false; + if (ssidBytes.length > MAX_NETWORK_NAME_BYTES) return false; + + return true; + } + + /** * A config is valid as a group if it has network name and passphrase. * Supplicant can construct a group on the fly for creating a group with specified config * or join a group without negotiation and WPS. @@ -3269,7 +3292,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { private boolean isConfigValidAsGroup(WifiP2pConfig config) { if (config == null) return false; if (TextUtils.isEmpty(config.deviceAddress)) return false; - if (!TextUtils.isEmpty(config.networkName) + if (isValidNetworkName(config.networkName) && !TextUtils.isEmpty(config.passphrase)) { return true; } diff --git a/service/java/com/android/server/wifi/rtt/RttNative.java b/service/java/com/android/server/wifi/rtt/RttNative.java index eaf947062..ffbf5bef9 100644 --- a/service/java/com/android/server/wifi/rtt/RttNative.java +++ b/service/java/com/android/server/wifi/rtt/RttNative.java @@ -311,6 +311,7 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub { config.channel.centerFreq1 = responder.centerFreq1; config.bw = halRttChannelBandwidthFromResponderChannelWidth(responder.channelWidth); config.preamble = halRttPreambleFromResponderPreamble(responder.preamble); + validateBwAndPreambleCombination(config.bw, config.preamble); if (config.peer == RttPeerType.NAN) { config.mustRequestLci = false; @@ -349,6 +350,20 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub { return rttConfigs; } + private static void validateBwAndPreambleCombination(int bw, int preamble) { + if (bw <= RttBw.BW_20MHZ) { + return; + } + if (bw == RttBw.BW_40MHZ && preamble >= RttPreamble.HT) { + return; + } + if (bw >= RttBw.BW_80MHZ && preamble >= RttPreamble.VHT) { + return; + } + throw new IllegalArgumentException( + "bw and preamble combination is invalid, bw: " + bw + " preamble: " + preamble); + } + private static int halRttPeerTypeFromResponderType(int responderType) { switch (responderType) { case ResponderConfig.RESPONDER_AP: diff --git a/service/java/com/android/server/wifi/util/EncryptedData.java b/service/java/com/android/server/wifi/util/EncryptedData.java index 91342d335..baec20426 100644 --- a/service/java/com/android/server/wifi/util/EncryptedData.java +++ b/service/java/com/android/server/wifi/util/EncryptedData.java @@ -18,21 +18,19 @@ package com.android.server.wifi.util; import com.android.internal.util.Preconditions; +import java.util.Arrays; +import java.util.Objects; + /** - * A class to store data created by {@link DataIntegrityChecker}. + * A class to store data created by {@link WifiConfigStoreEncryptionUtil}. */ public class EncryptedData { - public static final int ENCRYPTED_DATA_LENGTH = 48; - public static final int IV_LENGTH = 12; - private final byte[] mEncryptedData; private final byte[] mIv; public EncryptedData(byte[] encryptedData, byte[] iv) { - Preconditions.checkNotNull(encryptedData, iv); - Preconditions.checkState(encryptedData.length == ENCRYPTED_DATA_LENGTH, - "encryptedData.length=" + encryptedData.length); - Preconditions.checkState(iv.length == IV_LENGTH, "iv.length=" + iv.length); + Preconditions.checkNotNull(encryptedData); + Preconditions.checkNotNull(iv); mEncryptedData = encryptedData; mIv = iv; } @@ -44,4 +42,17 @@ public class EncryptedData { public byte[] getIv() { return mIv; } + + @Override + public boolean equals(Object other) { + if (!(other instanceof EncryptedData)) return false; + EncryptedData otherEncryptedData = (EncryptedData) other; + return Arrays.equals(this.mEncryptedData, otherEncryptedData.mEncryptedData) + && Arrays.equals(this.mIv, otherEncryptedData.mIv); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(mEncryptedData), Arrays.hashCode(mIv)); + } } diff --git a/service/java/com/android/server/wifi/util/ScanResultUtil.java b/service/java/com/android/server/wifi/util/ScanResultUtil.java index 39e9d2c40..b92483899 100644 --- a/service/java/com/android/server/wifi/util/ScanResultUtil.java +++ b/service/java/com/android/server/wifi/util/ScanResultUtil.java @@ -104,10 +104,10 @@ public class ScanResultUtil { /** * Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition - * network. This checks if the provided capabilities string contains PSK+SAE or not. + * network. This checks if the provided capabilities string contains both PSK and SAE or not. */ public static boolean isScanResultForPskSaeTransitionNetwork(ScanResult scanResult) { - return scanResult.capabilities.contains("PSK+SAE"); + return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE"); } /** diff --git a/service/java/com/android/server/wifi/util/TelephonyUtil.java b/service/java/com/android/server/wifi/util/TelephonyUtil.java index 4af40ddf2..3154df978 100644 --- a/service/java/com/android/server/wifi/util/TelephonyUtil.java +++ b/service/java/com/android/server/wifi/util/TelephonyUtil.java @@ -22,6 +22,7 @@ import android.net.wifi.WifiEnterpriseConfig; import android.telephony.ImsiEncryptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Base64; import android.util.Log; import android.util.Pair; @@ -730,4 +731,43 @@ public class TelephonyUtil { public static boolean isSimPresent(@Nonnull SubscriptionManager sm) { return sm.getActiveSubscriptionIdList().length > 0; } + + /** + * Decorates a pseudonym with the NAI realm, in case it wasn't provided by the server + * + * @param tm TelephonyManager instance + * @param pseudonym The pseudonym (temporary identity) provided by the server + * @return pseudonym@realm which is based on current MCC/MNC, {@code null} if SIM is + * not ready or absent. + */ + public static String decoratePseudonymWith3GppRealm(@NonNull TelephonyManager tm, + String pseudonym) { + if (tm == null || TextUtils.isEmpty(pseudonym)) { + return null; + } + if (pseudonym.contains("@")) { + // Pseudonym is already decorated + return pseudonym; + } + TelephonyManager defaultDataTm = tm.createForSubscriptionId( + SubscriptionManager.getDefaultDataSubscriptionId()); + if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) { + return null; + } + String mccMnc = defaultDataTm.getSimOperator(); + if (mccMnc == null || mccMnc.isEmpty()) { + return null; + } + + // Extract mcc & mnc from mccMnc + String mcc = mccMnc.substring(0, 3); + String mnc = mccMnc.substring(3); + + if (mnc.length() == 2) { + mnc = "0" + mnc; + } + + String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc); + return String.format("%s@%s", pseudonym, realm); + } } diff --git a/service/java/com/android/server/wifi/util/DataIntegrityChecker.java b/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java index 6f03a4861..46bf0fee1 100644 --- a/service/java/com/android/server/wifi/util/DataIntegrityChecker.java +++ b/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java @@ -17,22 +17,22 @@ package com.android.server.wifi.util; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Process; import android.os.SystemProperties; +import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.text.TextUtils; import android.util.Log; -import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.UnrecoverableEntryException; -import java.security.cert.CertificateException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -43,34 +43,27 @@ import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; /** - * Tools to provide integrity checking of byte arrays based on NIAP Common Criteria Protection - * Profile <a href="https://www.niap-ccevs.org/MMO/PP/-417-/#FCS_STG_EXT.3.1">FCS_STG_EXT.3.1</a>. + * Tools to help encrypt/decrypt */ -public class DataIntegrityChecker { - private static final String TAG = "DataIntegrityChecker"; +public class WifiConfigStoreEncryptionUtil { + private static final String TAG = "WifiConfigStoreEncryptionUtil"; - private static final String ALIAS_SUFFIX = ".data-integrity-checker-key"; + private static final String ALIAS_SUFFIX = ".data-encryption-key"; private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; - private static final String DIGEST_ALGORITHM = "SHA-256"; private static final int GCM_TAG_LENGTH = 128; + private static final int KEY_LENGTH = 256; private static final String KEY_STORE = "AndroidKeyStore"; - /** - * When KEYSTORE_FAILURE_RETURN_VALUE is true, all cryptographic operation failures will not - * enforce security and {@link #isOk(byte[], EncryptedData)} always return true. - */ - private static final boolean KEYSTORE_FAILURE_RETURN_VALUE = true; - private final String mDataFileName; /** - * Construct a new integrity checker to update and check if/when a data file was altered - * outside expected conditions. + * Construct a new util to help {@link com.android.server.wifi.WifiConfigStore.StoreData} + * modules to encrypt/decrypt credential data written/read from this config store file. * - * @param dataFileName The full path of the data file for which integrity check is performed. + * @param dataFileName The full path of the data file. * @throws NullPointerException When data file is empty string. */ - public DataIntegrityChecker(@NonNull String dataFileName) { + public WifiConfigStoreEncryptionUtil(@NonNull String dataFileName) { if (TextUtils.isEmpty(dataFileName)) { throw new NullPointerException("dataFileName must not be null or the empty " + "string"); @@ -83,80 +76,16 @@ public class DataIntegrityChecker { } /** - * Computes a digest of a byte array, encrypt it, and store the result - * - * Call this method immediately before storing the byte array - * - * @param data The data desired to ensure integrity - * @return Instance of {@link EncryptedData} containing the encrypted integrity data. - */ - public EncryptedData compute(byte[] data) { - if (data == null || data.length < 1) { - reportException(new Exception("No data to compute"), "No data to compute."); - return null; - } - byte[] digest = getDigest(data); - if (digest == null || digest.length < 1) { - reportException(new Exception("digest null in compute"), - "digest null in compute"); - return null; - } - EncryptedData integrityData = encrypt(digest, getKeyAlias()); - if (integrityData == null) { - reportException(new Exception("integrityData null in compute"), - "integrityData null in compute"); - } - return integrityData; - } - - - /** - * Check the integrity of a given byte array - * - * Call this method immediately before trusting the byte array. This method will return false - * when the integrity data calculated on the byte array does not match the encrypted integrity - * data provided to compare or if there is an underlying issue with the cryptographic functions - * or the key store. + * Encrypt the provided data blob. * - * @param data The data to check if its been altered. - * @param integrityData Encrypted integrity data to be used for comparison. - * @return true if the integrity data computed on |data| matches the provided |integrityData|. + * @param data Data blob to be encrypted. + * @return Instance of {@link EncryptedData} containing the encrypted info. */ - public boolean isOk(@NonNull byte[] data, @NonNull EncryptedData integrityData) { - if (data == null || data.length < 1) { - return KEYSTORE_FAILURE_RETURN_VALUE; - } - byte[] currentDigest = getDigest(data); - if (currentDigest == null || currentDigest.length < 1) { - reportException(new Exception("current digest null"), "current digest null"); - return KEYSTORE_FAILURE_RETURN_VALUE; - } - if (integrityData == null) { - reportException(new Exception("integrityData null in isOk"), - "integrityData null in isOk"); - return KEYSTORE_FAILURE_RETURN_VALUE; - } - byte[] storedDigest = decrypt(integrityData, getKeyAlias()); - if (storedDigest == null) { - return KEYSTORE_FAILURE_RETURN_VALUE; - } - return constantTimeEquals(storedDigest, currentDigest); - } - - private byte[] getDigest(byte[] data) { - try { - return MessageDigest.getInstance(DIGEST_ALGORITHM).digest(data); - } catch (NoSuchAlgorithmException e) { - reportException(e, "getDigest could not find algorithm: " + DIGEST_ALGORITHM); - return null; - } - } - - private EncryptedData encrypt(byte[] data, String keyAlias) { + public @Nullable EncryptedData encrypt(byte[] data) { EncryptedData encryptedData = null; try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - SecretKey secretKeyReference = getOrCreateSecretKey(keyAlias); + SecretKey secretKeyReference = getOrCreateSecretKey(getKeyAlias()); if (secretKeyReference != null) { cipher.init(Cipher.ENCRYPT_MODE, secretKeyReference); encryptedData = new EncryptedData(cipher.doFinal(data), cipher.getIV()); @@ -178,12 +107,18 @@ public class DataIntegrityChecker { return encryptedData; } - private byte[] decrypt(EncryptedData encryptedData, String keyAlias) { + /** + * Decrypt the original data blob from the provided {@link EncryptedData}. + * + * @param encryptedData Instance of {@link EncryptedData} containing the encrypted info. + * @return Original data blob that was encrypted. + */ + public @Nullable byte[] decrypt(@NonNull EncryptedData encryptedData) { byte[] decryptedData = null; try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, encryptedData.getIv()); - SecretKey secretKeyReference = getOrCreateSecretKey(keyAlias); + SecretKey secretKeyReference = getOrCreateSecretKey(getKeyAlias()); if (secretKeyReference != null) { cipher.init(Cipher.DECRYPT_MODE, secretKeyReference, spec); decryptedData = cipher.doFinal(encryptedData.getEncryptedData()); @@ -207,8 +142,7 @@ public class DataIntegrityChecker { private SecretKey getOrCreateSecretKey(String keyAlias) { SecretKey secretKey = null; try { - KeyStore keyStore = KeyStore.getInstance(KEY_STORE); - keyStore.load(null); + KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(Process.WIFI_UID); if (keyStore.containsAlias(keyAlias)) { // The key exists in key store. Get the key. KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore .getEntry(keyAlias, null); @@ -227,17 +161,15 @@ public class DataIntegrityChecker { KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(KEY_LENGTH) + .setUid(Process.WIFI_UID) .build(); keyGenerator.init(keyGenParameterSpec); secretKey = keyGenerator.generateKey(); } - } catch (CertificateException e) { - reportException(e, "getOrCreateSecretKey had a certificate exception."); } catch (InvalidAlgorithmParameterException e) { reportException(e, "getOrCreateSecretKey had an invalid algorithm parameter"); - } catch (IOException e) { - reportException(e, "getOrCreateSecretKey had an IO exception."); } catch (KeyStoreException e) { reportException(e, "getOrCreateSecretKey cannot find the keystore: " + KEY_STORE); } catch (NoSuchAlgorithmException e) { @@ -250,22 +182,6 @@ public class DataIntegrityChecker { return secretKey; } - private boolean constantTimeEquals(byte[] a, byte[] b) { - if (a == null && b == null) { - return true; - } - - if (a == null || b == null || a.length != b.length) { - return false; - } - - byte differenceAccumulator = 0; - for (int i = 0; i < a.length; ++i) { - differenceAccumulator |= a[i] ^ b[i]; - } - return (differenceAccumulator == 0); - } - /* TODO(b/128526030): Remove this error reporting code upon resolving the bug. */ private static final boolean REQUEST_BUG_REPORT = false; private void reportException(Exception exception, String error) { @@ -275,4 +191,5 @@ public class DataIntegrityChecker { SystemProperties.set("ctl.start", "bugreport"); } } + } diff --git a/service/java/com/android/server/wifi/util/XmlUtil.java b/service/java/com/android/server/wifi/util/XmlUtil.java index 188d3b5c7..db0f4289b 100644 --- a/service/java/com/android/server/wifi/util/XmlUtil.java +++ b/service/java/com/android/server/wifi/util/XmlUtil.java @@ -16,6 +16,7 @@ package com.android.server.wifi.util; +import android.annotation.Nullable; import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; @@ -28,6 +29,7 @@ import android.net.StaticIpConfiguration; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; import android.net.wifi.WifiEnterpriseConfig; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -377,19 +379,51 @@ public class XmlUtil { } /** + * Write preshared key to the XML stream. + * + * If encryptionUtil is null or if encryption fails for some reason, the pre-shared + * key is stored in plaintext, else the encrypted psk is stored. + */ + private static void writePreSharedKeyToXml( + XmlSerializer out, String preSharedKey, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) + throws XmlPullParserException, IOException { + EncryptedData encryptedData = null; + if (encryptionUtil != null) { + if (preSharedKey != null) { + encryptedData = encryptionUtil.encrypt(preSharedKey.getBytes()); + if (encryptedData == null) { + // We silently fail encryption failures! + Log.wtf(TAG, "Encryption of preSharedKey failed"); + } + } + } + if (encryptedData != null) { + XmlUtil.writeNextSectionStart(out, XML_TAG_PRE_SHARED_KEY); + EncryptedDataXmlUtil.writeToXml(out, encryptedData); + XmlUtil.writeNextSectionEnd(out, XML_TAG_PRE_SHARED_KEY); + } else { + XmlUtil.writeNextValue(out, XML_TAG_PRE_SHARED_KEY, preSharedKey); + } + } + + /** * Write the Configuration data elements that are common for backup & config store to the * XML stream. * - * @param out XmlSerializer instance pointing to the XML stream. + * @param out XmlSerializer instance pointing to the XML stream. * @param configuration WifiConfiguration object to be serialized. + * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. Backup/restore stores + * keys unencrypted. */ public static void writeCommonElementsToXml( - XmlSerializer out, WifiConfiguration configuration) + XmlSerializer out, WifiConfiguration configuration, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { XmlUtil.writeNextValue(out, XML_TAG_CONFIG_KEY, configuration.configKey()); XmlUtil.writeNextValue(out, XML_TAG_SSID, configuration.SSID); XmlUtil.writeNextValue(out, XML_TAG_BSSID, configuration.BSSID); - XmlUtil.writeNextValue(out, XML_TAG_PRE_SHARED_KEY, configuration.preSharedKey); + writePreSharedKeyToXml(out, configuration.preSharedKey, encryptionUtil); writeWepKeysToXml(out, configuration.wepKeys); XmlUtil.writeNextValue(out, XML_TAG_WEP_TX_KEY_INDEX, configuration.wepTxKeyIndex); XmlUtil.writeNextValue(out, XML_TAG_HIDDEN_SSID, configuration.hiddenSSID); @@ -428,7 +462,7 @@ public class XmlUtil { */ public static void writeToXmlForBackup(XmlSerializer out, WifiConfiguration configuration) throws XmlPullParserException, IOException { - writeCommonElementsToXml(out, configuration); + writeCommonElementsToXml(out, configuration, null); XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, configuration.meteredOverride); } @@ -436,13 +470,15 @@ public class XmlUtil { * Write the Configuration data elements for config store from the provided Configuration * to the XML stream. * - * @param out XmlSerializer instance pointing to the XML stream. + * @param out XmlSerializer instance pointing to the XML stream. * @param configuration WifiConfiguration object to be serialized. + * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. */ public static void writeToXmlForConfigStore( - XmlSerializer out, WifiConfiguration configuration) + XmlSerializer out, WifiConfiguration configuration, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { - writeCommonElementsToXml(out, configuration); + writeCommonElementsToXml(out, configuration, encryptionUtil); XmlUtil.writeNextValue(out, XML_TAG_STATUS, configuration.status); XmlUtil.writeNextValue(out, XML_TAG_FQDN, configuration.FQDN); XmlUtil.writeNextValue( @@ -509,13 +545,16 @@ public class XmlUtil { * Note: This is used for parsing both backup data and config store data. Looping through * the tags make it easy to add or remove elements in the future versions if needed. * - * @param in XmlPullParser instance pointing to the XML stream. + * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. + * @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not. + * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. * @return Pair<Config key, WifiConfiguration object> if parsing is successful, * null otherwise. */ public static Pair<String, WifiConfiguration> parseFromXml( - XmlPullParser in, int outerTagDepth) + XmlPullParser in, int outerTagDepth, boolean shouldExpectEncryptedCredentials, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { WifiConfiguration configuration = new WifiConfiguration(); String configKeyInData = null; @@ -523,147 +562,175 @@ public class XmlUtil { // Loop through and parse out all the elements from the stream within this section. while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { - String[] valueName = new String[1]; - Object value = XmlUtil.readCurrentValue(in, valueName); - if (valueName[0] == null) { - throw new XmlPullParserException("Missing value name"); - } - switch (valueName[0]) { - case XML_TAG_CONFIG_KEY: - configKeyInData = (String) value; - break; - case XML_TAG_SSID: - configuration.SSID = (String) value; - break; - case XML_TAG_BSSID: - configuration.BSSID = (String) value; - break; - case XML_TAG_PRE_SHARED_KEY: - configuration.preSharedKey = (String) value; - break; - case XML_TAG_WEP_KEYS: - populateWepKeysFromXmlValue(value, configuration.wepKeys); - break; - case XML_TAG_WEP_TX_KEY_INDEX: - configuration.wepTxKeyIndex = (int) value; - break; - case XML_TAG_HIDDEN_SSID: - configuration.hiddenSSID = (boolean) value; - break; - case XML_TAG_REQUIRE_PMF: - configuration.requirePMF = (boolean) value; - break; - case XML_TAG_ALLOWED_KEY_MGMT: - byte[] allowedKeyMgmt = (byte[]) value; - configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt); - break; - case XML_TAG_ALLOWED_PROTOCOLS: - byte[] allowedProtocols = (byte[]) value; - configuration.allowedProtocols = BitSet.valueOf(allowedProtocols); - break; - case XML_TAG_ALLOWED_AUTH_ALGOS: - byte[] allowedAuthAlgorithms = (byte[]) value; - configuration.allowedAuthAlgorithms = BitSet.valueOf(allowedAuthAlgorithms); - break; - case XML_TAG_ALLOWED_GROUP_CIPHERS: - byte[] allowedGroupCiphers = (byte[]) value; - configuration.allowedGroupCiphers = BitSet.valueOf(allowedGroupCiphers); - break; - case XML_TAG_ALLOWED_PAIRWISE_CIPHERS: - byte[] allowedPairwiseCiphers = (byte[]) value; - configuration.allowedPairwiseCiphers = - BitSet.valueOf(allowedPairwiseCiphers); - break; - case XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS: - byte[] allowedGroupMgmtCiphers = (byte[]) value; - configuration.allowedGroupManagementCiphers = - BitSet.valueOf(allowedGroupMgmtCiphers); - break; - case XML_TAG_ALLOWED_SUITE_B_CIPHERS: - byte[] allowedSuiteBCiphers = (byte[]) value; - configuration.allowedSuiteBCiphers = - BitSet.valueOf(allowedSuiteBCiphers); - break; - case XML_TAG_SHARED: - configuration.shared = (boolean) value; - break; - case XML_TAG_STATUS: - int status = (int) value; - // Any network which was CURRENT before reboot needs - // to be restored to ENABLED. - if (status == WifiConfiguration.Status.CURRENT) { - status = WifiConfiguration.Status.ENABLED; - } - configuration.status = status; - break; - case XML_TAG_FQDN: - configuration.FQDN = (String) value; - break; - case XML_TAG_PROVIDER_FRIENDLY_NAME: - configuration.providerFriendlyName = (String) value; - break; - case XML_TAG_LINKED_NETWORKS_LIST: - configuration.linkedConfigurations = (HashMap<String, Integer>) value; - break; - case XML_TAG_DEFAULT_GW_MAC_ADDRESS: - configuration.defaultGwMacAddress = (String) value; - break; - case XML_TAG_VALIDATED_INTERNET_ACCESS: - configuration.validatedInternetAccess = (boolean) value; - break; - case XML_TAG_NO_INTERNET_ACCESS_EXPECTED: - configuration.noInternetAccessExpected = (boolean) value; - break; - case XML_TAG_USER_APPROVED: - configuration.userApproved = (int) value; - break; - case XML_TAG_METERED_HINT: - configuration.meteredHint = (boolean) value; - break; - case XML_TAG_METERED_OVERRIDE: - configuration.meteredOverride = (int) value; - break; - case XML_TAG_USE_EXTERNAL_SCORES: - configuration.useExternalScores = (boolean) value; - break; - case XML_TAG_NUM_ASSOCIATION: - configuration.numAssociation = (int) value; - break; - case XML_TAG_CREATOR_UID: - configuration.creatorUid = (int) value; - break; - case XML_TAG_CREATOR_NAME: - configuration.creatorName = (String) value; - break; - case XML_TAG_CREATION_TIME: - configuration.creationTime = (String) value; - break; - case XML_TAG_LAST_UPDATE_UID: - configuration.lastUpdateUid = (int) value; - break; - case XML_TAG_LAST_UPDATE_NAME: - configuration.lastUpdateName = (String) value; - break; - case XML_TAG_LAST_CONNECT_UID: - configuration.lastConnectUid = (int) value; - break; - case XML_TAG_IS_LEGACY_PASSPOINT_CONFIG: - configuration.isLegacyPasspointConfig = (boolean) value; - break; - case XML_TAG_ROAMING_CONSORTIUM_OIS: - configuration.roamingConsortiumIds = (long[]) value; - break; - case XML_TAG_RANDOMIZED_MAC_ADDRESS: - configuration.setRandomizedMacAddress( - MacAddress.fromString((String) value)); - break; - case XML_TAG_MAC_RANDOMIZATION_SETTING: - configuration.macRandomizationSetting = (int) value; - macRandomizationSettingExists = true; - break; - default: - throw new XmlPullParserException( - "Unknown value name found: " + valueName[0]); + if (in.getAttributeValue(null, "name") != null) { + // Value elements. + String[] valueName = new String[1]; + Object value = XmlUtil.readCurrentValue(in, valueName); + if (valueName[0] == null) { + throw new XmlPullParserException("Missing value name"); + } + switch (valueName[0]) { + case XML_TAG_CONFIG_KEY: + configKeyInData = (String) value; + break; + case XML_TAG_SSID: + configuration.SSID = (String) value; + break; + case XML_TAG_BSSID: + configuration.BSSID = (String) value; + break; + case XML_TAG_PRE_SHARED_KEY: + configuration.preSharedKey = (String) value; + break; + case XML_TAG_WEP_KEYS: + populateWepKeysFromXmlValue(value, configuration.wepKeys); + break; + case XML_TAG_WEP_TX_KEY_INDEX: + configuration.wepTxKeyIndex = (int) value; + break; + case XML_TAG_HIDDEN_SSID: + configuration.hiddenSSID = (boolean) value; + break; + case XML_TAG_REQUIRE_PMF: + configuration.requirePMF = (boolean) value; + break; + case XML_TAG_ALLOWED_KEY_MGMT: + byte[] allowedKeyMgmt = (byte[]) value; + configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt); + break; + case XML_TAG_ALLOWED_PROTOCOLS: + byte[] allowedProtocols = (byte[]) value; + configuration.allowedProtocols = BitSet.valueOf(allowedProtocols); + break; + case XML_TAG_ALLOWED_AUTH_ALGOS: + byte[] allowedAuthAlgorithms = (byte[]) value; + configuration.allowedAuthAlgorithms = BitSet.valueOf( + allowedAuthAlgorithms); + break; + case XML_TAG_ALLOWED_GROUP_CIPHERS: + byte[] allowedGroupCiphers = (byte[]) value; + configuration.allowedGroupCiphers = BitSet.valueOf(allowedGroupCiphers); + break; + case XML_TAG_ALLOWED_PAIRWISE_CIPHERS: + byte[] allowedPairwiseCiphers = (byte[]) value; + configuration.allowedPairwiseCiphers = + BitSet.valueOf(allowedPairwiseCiphers); + break; + case XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS: + byte[] allowedGroupMgmtCiphers = (byte[]) value; + configuration.allowedGroupManagementCiphers = + BitSet.valueOf(allowedGroupMgmtCiphers); + break; + case XML_TAG_ALLOWED_SUITE_B_CIPHERS: + byte[] allowedSuiteBCiphers = (byte[]) value; + configuration.allowedSuiteBCiphers = + BitSet.valueOf(allowedSuiteBCiphers); + break; + case XML_TAG_SHARED: + configuration.shared = (boolean) value; + break; + case XML_TAG_STATUS: + int status = (int) value; + // Any network which was CURRENT before reboot needs + // to be restored to ENABLED. + if (status == WifiConfiguration.Status.CURRENT) { + status = WifiConfiguration.Status.ENABLED; + } + configuration.status = status; + break; + case XML_TAG_FQDN: + configuration.FQDN = (String) value; + break; + case XML_TAG_PROVIDER_FRIENDLY_NAME: + configuration.providerFriendlyName = (String) value; + break; + case XML_TAG_LINKED_NETWORKS_LIST: + configuration.linkedConfigurations = (HashMap<String, Integer>) value; + break; + case XML_TAG_DEFAULT_GW_MAC_ADDRESS: + configuration.defaultGwMacAddress = (String) value; + break; + case XML_TAG_VALIDATED_INTERNET_ACCESS: + configuration.validatedInternetAccess = (boolean) value; + break; + case XML_TAG_NO_INTERNET_ACCESS_EXPECTED: + configuration.noInternetAccessExpected = (boolean) value; + break; + case XML_TAG_USER_APPROVED: + configuration.userApproved = (int) value; + break; + case XML_TAG_METERED_HINT: + configuration.meteredHint = (boolean) value; + break; + case XML_TAG_METERED_OVERRIDE: + configuration.meteredOverride = (int) value; + break; + case XML_TAG_USE_EXTERNAL_SCORES: + configuration.useExternalScores = (boolean) value; + break; + case XML_TAG_NUM_ASSOCIATION: + configuration.numAssociation = (int) value; + break; + case XML_TAG_CREATOR_UID: + configuration.creatorUid = (int) value; + break; + case XML_TAG_CREATOR_NAME: + configuration.creatorName = (String) value; + break; + case XML_TAG_CREATION_TIME: + configuration.creationTime = (String) value; + break; + case XML_TAG_LAST_UPDATE_UID: + configuration.lastUpdateUid = (int) value; + break; + case XML_TAG_LAST_UPDATE_NAME: + configuration.lastUpdateName = (String) value; + break; + case XML_TAG_LAST_CONNECT_UID: + configuration.lastConnectUid = (int) value; + break; + case XML_TAG_IS_LEGACY_PASSPOINT_CONFIG: + configuration.isLegacyPasspointConfig = (boolean) value; + break; + case XML_TAG_ROAMING_CONSORTIUM_OIS: + configuration.roamingConsortiumIds = (long[]) value; + break; + case XML_TAG_RANDOMIZED_MAC_ADDRESS: + configuration.setRandomizedMacAddress( + MacAddress.fromString((String) value)); + break; + case XML_TAG_MAC_RANDOMIZATION_SETTING: + configuration.macRandomizationSetting = (int) value; + macRandomizationSettingExists = true; + break; + default: + throw new XmlPullParserException( + "Unknown value name found: " + valueName[0]); + } + } else { + String tagName = in.getName(); + if (tagName == null) { + throw new XmlPullParserException("Unexpected null tag found"); + } + switch (tagName) { + case XML_TAG_PRE_SHARED_KEY: + if (!shouldExpectEncryptedCredentials || encryptionUtil == null) { + throw new XmlPullParserException( + "Encrypted preSharedKey section not expected"); + } + EncryptedData encryptedData = + EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1); + byte[] preSharedKeyBytes = encryptionUtil.decrypt(encryptedData); + if (preSharedKeyBytes == null) { + Log.wtf(TAG, "Decryption of preSharedKey failed"); + } else { + configuration.preSharedKey = new String(preSharedKeyBytes); + } + break; + default: + throw new XmlPullParserException( + "Unknown tag name found: " + tagName); + } } } if (!macRandomizationSettingExists) { @@ -1019,20 +1086,52 @@ public class XmlUtil { public static final String XML_TAG_REALM = "Realm"; /** + * Write password key to the XML stream. + * + * If encryptionUtil is null or if encryption fails for some reason, the password is stored + * in plaintext, else the encrypted psk is stored. + */ + private static void writePasswordToXml( + XmlSerializer out, String password, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) + throws XmlPullParserException, IOException { + EncryptedData encryptedData = null; + if (encryptionUtil != null) { + if (password != null) { + encryptedData = encryptionUtil.encrypt(password.getBytes()); + if (encryptedData == null) { + // We silently fail encryption failures! + Log.wtf(TAG, "Encryption of password failed"); + } + } + } + if (encryptedData != null) { + XmlUtil.writeNextSectionStart(out, XML_TAG_PASSWORD); + EncryptedDataXmlUtil.writeToXml(out, encryptedData); + XmlUtil.writeNextSectionEnd(out, XML_TAG_PASSWORD); + } else { + XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, password); + } + } + + /** * Write the WifiEnterpriseConfig data elements from the provided config to the XML * stream. * - * @param out XmlSerializer instance pointing to the XML stream. + * @param out XmlSerializer instance pointing to the XML stream. * @param enterpriseConfig WifiEnterpriseConfig object to be serialized. + * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. */ - public static void writeToXml(XmlSerializer out, WifiEnterpriseConfig enterpriseConfig) + public static void writeToXml(XmlSerializer out, WifiEnterpriseConfig enterpriseConfig, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { XmlUtil.writeNextValue(out, XML_TAG_IDENTITY, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY)); XmlUtil.writeNextValue(out, XML_TAG_ANON_IDENTITY, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY)); - XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, - enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY)); + writePasswordToXml( + out, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY), + encryptionUtil); XmlUtil.writeNextValue(out, XML_TAG_CLIENT_CERT, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY)); XmlUtil.writeNextValue(out, XML_TAG_CA_CERT, @@ -1060,15 +1159,170 @@ public class XmlUtil { /** * Parses the data elements from the provided XML stream to a WifiEnterpriseConfig object. * - * @param in XmlPullParser instance pointing to the XML stream. + * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. + * @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not. + * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. * @return WifiEnterpriseConfig object if parsing is successful, null otherwise. */ - public static WifiEnterpriseConfig parseFromXml(XmlPullParser in, int outerTagDepth) + public static WifiEnterpriseConfig parseFromXml(XmlPullParser in, int outerTagDepth, + boolean shouldExpectEncryptedCredentials, + @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig(); // Loop through and parse out all the elements from the stream within this section. + while (XmlUtils.nextElementWithin(in, outerTagDepth)) { + if (in.getAttributeValue(null, "name") != null) { + // Value elements. + String[] valueName = new String[1]; + Object value = XmlUtil.readCurrentValue(in, valueName); + if (valueName[0] == null) { + throw new XmlPullParserException("Missing value name"); + } + switch (valueName[0]) { + case XML_TAG_IDENTITY: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.IDENTITY_KEY, (String) value); + break; + case XML_TAG_ANON_IDENTITY: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.ANON_IDENTITY_KEY, (String) value); + break; + case XML_TAG_PASSWORD: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.PASSWORD_KEY, (String) value); + if (shouldExpectEncryptedCredentials + && !TextUtils.isEmpty(enterpriseConfig.getFieldValue( + WifiEnterpriseConfig.PASSWORD_KEY))) { + // Indicates that encryption of password failed when it was last + // written. + Log.e(TAG, "password value not expected"); + } + break; + case XML_TAG_CLIENT_CERT: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.CLIENT_CERT_KEY, (String) value); + break; + case XML_TAG_CA_CERT: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.CA_CERT_KEY, (String) value); + break; + case XML_TAG_SUBJECT_MATCH: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.SUBJECT_MATCH_KEY, (String) value); + break; + case XML_TAG_ENGINE: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.ENGINE_KEY, (String) value); + break; + case XML_TAG_ENGINE_ID: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.ENGINE_ID_KEY, (String) value); + break; + case XML_TAG_PRIVATE_KEY_ID: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, (String) value); + break; + case XML_TAG_ALT_SUBJECT_MATCH: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, (String) value); + break; + case XML_TAG_DOM_SUFFIX_MATCH: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, (String) value); + break; + case XML_TAG_CA_PATH: + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.CA_PATH_KEY, (String) value); + break; + case XML_TAG_EAP_METHOD: + enterpriseConfig.setEapMethod((int) value); + break; + case XML_TAG_PHASE2_METHOD: + enterpriseConfig.setPhase2Method((int) value); + break; + case XML_TAG_PLMN: + enterpriseConfig.setPlmn((String) value); + break; + case XML_TAG_REALM: + enterpriseConfig.setRealm((String) value); + break; + default: + throw new XmlPullParserException( + "Unknown value name found: " + valueName[0]); + } + } else { + String tagName = in.getName(); + if (tagName == null) { + throw new XmlPullParserException("Unexpected null tag found"); + } + switch (tagName) { + case XML_TAG_PASSWORD: + if (!shouldExpectEncryptedCredentials || encryptionUtil == null) { + throw new XmlPullParserException( + "encrypted password section not expected"); + } + EncryptedData encryptedData = + EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1); + byte[] passwordBytes = encryptionUtil.decrypt(encryptedData); + if (passwordBytes == null) { + Log.wtf(TAG, "Decryption of password failed"); + } else { + enterpriseConfig.setFieldValue( + WifiEnterpriseConfig.PASSWORD_KEY, + new String(passwordBytes)); + } + break; + default: + throw new XmlPullParserException( + "Unknown tag name found: " + tagName); + } + } + } + return enterpriseConfig; + } + } + + /** + * Utility class to serialize and deseriaize {@link EncryptedData} object to XML & + * vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module. + */ + public static class EncryptedDataXmlUtil { + /** + * List of XML tags corresponding to EncryptedData object elements. + */ + private static final String XML_TAG_ENCRYPTED_DATA = "EncryptedData"; + private static final String XML_TAG_IV = "IV"; + + /** + * Write the NetworkSelectionStatus data elements from the provided status to the XML + * stream. + * + * @param out XmlSerializer instance pointing to the XML stream. + * @param encryptedData EncryptedData object to be serialized. + */ + public static void writeToXml(XmlSerializer out, EncryptedData encryptedData) + throws XmlPullParserException, IOException { + XmlUtil.writeNextValue( + out, XML_TAG_ENCRYPTED_DATA, encryptedData.getEncryptedData()); + XmlUtil.writeNextValue(out, XML_TAG_IV, encryptedData.getIv()); + } + + /** + * Parses the EncryptedData data elements from the provided XML stream to a + * EncryptedData object. + * + * @param in XmlPullParser instance pointing to the XML stream. + * @param outerTagDepth depth of the outer tag in the XML document. + * @return EncryptedData object if parsing is successful, null otherwise. + */ + public static EncryptedData parseFromXml(XmlPullParser in, int outerTagDepth) + throws XmlPullParserException, IOException { + byte[] encryptedData = null; + byte[] iv = null; + + // Loop through and parse out all the elements from the stream within this section. while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); @@ -1076,72 +1330,18 @@ public class XmlUtil { throw new XmlPullParserException("Missing value name"); } switch (valueName[0]) { - case XML_TAG_IDENTITY: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.IDENTITY_KEY, (String) value); - break; - case XML_TAG_ANON_IDENTITY: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.ANON_IDENTITY_KEY, (String) value); + case XML_TAG_ENCRYPTED_DATA: + encryptedData = (byte[]) value; break; - case XML_TAG_PASSWORD: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.PASSWORD_KEY, (String) value); - break; - case XML_TAG_CLIENT_CERT: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.CLIENT_CERT_KEY, (String) value); - break; - case XML_TAG_CA_CERT: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.CA_CERT_KEY, (String) value); - break; - case XML_TAG_SUBJECT_MATCH: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.SUBJECT_MATCH_KEY, (String) value); - break; - case XML_TAG_ENGINE: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.ENGINE_KEY, (String) value); - break; - case XML_TAG_ENGINE_ID: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.ENGINE_ID_KEY, (String) value); - break; - case XML_TAG_PRIVATE_KEY_ID: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, (String) value); - break; - case XML_TAG_ALT_SUBJECT_MATCH: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, (String) value); - break; - case XML_TAG_DOM_SUFFIX_MATCH: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, (String) value); - break; - case XML_TAG_CA_PATH: - enterpriseConfig.setFieldValue( - WifiEnterpriseConfig.CA_PATH_KEY, (String) value); - break; - case XML_TAG_EAP_METHOD: - enterpriseConfig.setEapMethod((int) value); - break; - case XML_TAG_PHASE2_METHOD: - enterpriseConfig.setPhase2Method((int) value); - break; - case XML_TAG_PLMN: - enterpriseConfig.setPlmn((String) value); - break; - case XML_TAG_REALM: - enterpriseConfig.setRealm((String) value); + case XML_TAG_IV: + iv = (byte[]) value; break; default: throw new XmlPullParserException( "Unknown value name found: " + valueName[0]); } } - return enterpriseConfig; + return new EncryptedData(encryptedData, iv); } } } diff --git a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java index 7896a8f0b..afb3ef5f4 100644 --- a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java +++ b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java @@ -384,7 +384,7 @@ public class ClientModeImplTest { @Mock AsyncChannel mNullAsyncChannel; @Mock CarrierNetworkConfig mCarrierNetworkConfig; @Mock Handler mNetworkAgentHandler; - + @Mock ConnectionFailureNotifier mConnectionFailureNotifier; final ArgumentCaptor<WifiNative.InterfaceCallback> mInterfaceCallbackCaptor = ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class); @@ -441,6 +441,8 @@ public class ClientModeImplTest { when(mWifiInjector.getWifiScoreCard()).thenReturn(mWifiScoreCard); when(mWifiInjector.getWifiLockManager()).thenReturn(mWifiLockManager); when(mWifiInjector.getCarrierNetworkConfig()).thenReturn(mCarrierNetworkConfig); + when(mWifiInjector.makeConnectionFailureNotifier(any())) + .thenReturn(mConnectionFailureNotifier); when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any())) .thenReturn(Pair.create(Process.INVALID_UID, "")); when(mWifiNative.initialize()).thenReturn(true); @@ -1047,6 +1049,9 @@ public class ClientModeImplTest { when(mCarrierNetworkConfig.isCarrierEncryptionInfoAvailable()).thenReturn(true); + // Initial value should be "not set" + assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); + triggerConnect(); // CMD_START_CONNECT should have set anonymousIdentity to anonymous@<realm> @@ -1066,15 +1071,15 @@ public class ClientModeImplTest { mLooper.dispatchAll(); verify(mWifiNative).getEapAnonymousIdentity(any()); - // check that the anonymous identity remains anonymous@<realm> for subsequent connections. - assertEquals(expectedAnonymousIdentity, - mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); - // verify that WifiConfigManager#addOrUpdateNetwork() was never called if there is no - // real pseudonym to be stored. i.e. Encrypted IMSI will be always used + + // Post connection value should remain "not set" + assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); + // verify that WifiConfigManager#addOrUpdateNetwork() was called to clear any previously + // stored pseudonym. i.e. to enable Encrypted IMSI for subsequent connections. // Note: This test will fail if future logic will have additional conditions that would // trigger "add or update network" operation. The test needs to be updated to account for // this change. - verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt()); + verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt()); } /** @@ -1127,6 +1132,55 @@ public class ClientModeImplTest { } /** + * Tests anonymous identity is set again whenever a connection is established for the carrier + * that supports encrypted IMSI and anonymous identity but real but not decorated pseudonym was + * provided for subsequent connections. + */ + @Test + public void testSetAnonymousIdentityWhenConnectionIsEstablishedWithNonDecoratedPseudonym() + throws Exception { + mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork( + WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE)); + when(mDataTelephonyManager.getSimOperator()).thenReturn("123456"); + when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY); + mConnectedNetwork.enterpriseConfig.setAnonymousIdentity(""); + + String realm = "wlan.mnc456.mcc123.3gppnetwork.org"; + String expectedAnonymousIdentity = "anonymous"; + String pseudonym = "83bcca9384fca"; + + when(mCarrierNetworkConfig.isCarrierEncryptionInfoAvailable()).thenReturn(true); + + triggerConnect(); + + // CMD_START_CONNECT should have set anonymousIdentity to anonymous@<realm> + assertEquals(expectedAnonymousIdentity + "@" + realm, + mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); + + when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID)) + .thenReturn(mScanDetailCache); + when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn( + getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq)); + when(mScanDetailCache.getScanResult(sBSSID)).thenReturn( + getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult()); + when(mWifiNative.getEapAnonymousIdentity(anyString())) + .thenReturn(pseudonym); + + mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID); + mLooper.dispatchAll(); + + verify(mWifiNative).getEapAnonymousIdentity(any()); + assertEquals(pseudonym + "@" + realm, + mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); + // Verify that WifiConfigManager#addOrUpdateNetwork() was called if there we received a + // real pseudonym to be stored. i.e. Encrypted IMSI will be used once, followed by + // pseudonym usage in all subsequent connections. + // Note: This test will fail if future logic will have additional conditions that would + // trigger "add or update network" operation. The test needs to be updated to account for + // this change. + verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt()); + } + /** * Tests the Passpoint information is set in WifiInfo for Passpoint AP connection. */ @Test @@ -1789,10 +1843,10 @@ public class ClientModeImplTest { /** Verifies that syncGetSupportedFeatures() masks out capabilities based on system flags. */ @Test public void syncGetSupportedFeatures() { - final int featureAware = WifiManager.WIFI_FEATURE_AWARE; - final int featureInfra = WifiManager.WIFI_FEATURE_INFRA; - final int featureD2dRtt = WifiManager.WIFI_FEATURE_D2D_RTT; - final int featureD2apRtt = WifiManager.WIFI_FEATURE_D2AP_RTT; + final long featureAware = WifiManager.WIFI_FEATURE_AWARE; + final long featureInfra = WifiManager.WIFI_FEATURE_INFRA; + final long featureD2dRtt = WifiManager.WIFI_FEATURE_D2D_RTT; + final long featureD2apRtt = WifiManager.WIFI_FEATURE_D2AP_RTT; final long featureLongBits = 0x1100000000L; assertEquals(0, testGetSupportedFeaturesCase(0, false)); @@ -1866,15 +1920,15 @@ public class ClientModeImplTest { @Test public void syncRemovePasspointConfig() throws Exception { String fqdn = "test.com"; - when(mPasspointManager.removeProvider(fqdn)).thenReturn(true); + when(mPasspointManager.removeProvider(anyInt(), anyBoolean(), eq(fqdn))).thenReturn(true); mLooper.startAutoDispatch(); - assertTrue(mCmi.syncRemovePasspointConfig(mCmiAsyncChannel, fqdn)); + assertTrue(mCmi.syncRemovePasspointConfig(mCmiAsyncChannel, true, fqdn)); mLooper.stopAutoDispatch(); reset(mPasspointManager); - when(mPasspointManager.removeProvider(fqdn)).thenReturn(false); + when(mPasspointManager.removeProvider(anyInt(), anyBoolean(), eq(fqdn))).thenReturn(false); mLooper.startAutoDispatch(); - assertFalse(mCmi.syncRemovePasspointConfig(mCmiAsyncChannel, fqdn)); + assertFalse(mCmi.syncRemovePasspointConfig(mCmiAsyncChannel, true, fqdn)); mLooper.stopAutoDispatch(); } @@ -1902,16 +1956,17 @@ public class ClientModeImplTest { config.setHomeSp(homeSp); expectedConfigs.add(config); - when(mPasspointManager.getProviderConfigs()).thenReturn(expectedConfigs); + when(mPasspointManager.getProviderConfigs(anyInt(), anyBoolean())) + .thenReturn(expectedConfigs); mLooper.startAutoDispatch(); - assertEquals(expectedConfigs, mCmi.syncGetPasspointConfigs(mCmiAsyncChannel)); + assertEquals(expectedConfigs, mCmi.syncGetPasspointConfigs(mCmiAsyncChannel, true)); mLooper.stopAutoDispatch(); reset(mPasspointManager); - when(mPasspointManager.getProviderConfigs()) - .thenReturn(new ArrayList<PasspointConfiguration>()); + when(mPasspointManager.getProviderConfigs(anyInt(), anyBoolean())) + .thenReturn(new ArrayList<>()); mLooper.startAutoDispatch(); - assertTrue(mCmi.syncGetPasspointConfigs(mCmiAsyncChannel).isEmpty()); + assertTrue(mCmi.syncGetPasspointConfigs(mCmiAsyncChannel, true).isEmpty()); mLooper.stopAutoDispatch(); } @@ -2748,6 +2803,59 @@ public class ClientModeImplTest { } /** + * Verify that we don't crash when WifiNative returns null as the current MAC address. + * @throws Exception + */ + @Test + public void testMacRandomizationWifiNativeReturningNull() throws Exception { + when(mWifiNative.getMacAddress(anyString())).thenReturn(null); + initializeAndAddNetworkAndVerifySuccess(); + assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest()); + assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState()); + + connect(); + verify(mWifiNative).setMacAddress(WIFI_IFACE_NAME, TEST_LOCAL_MAC_ADDRESS); + } + + /** + * Verifies that a notification is posted when a connection failure happens on a network + * in the hotlist. Then verify that tapping on the notification launches an dialog, which + * could be used to set the randomization setting for a network to "Trusted". + */ + @Test + public void testConnectionFailureSendRandomizationSettingsNotification() throws Exception { + when(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(anyInt())).thenReturn(true); + // Setup CONNECT_MODE & a WifiConfiguration + initializeAndAddNetworkAndVerifySuccess(); + mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, sBSSID); + mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, + WifiManager.ERROR_AUTH_FAILURE_TIMEOUT); + mLooper.dispatchAll(); + + WifiConfiguration config = mCmi.getCurrentWifiConfiguration(); + verify(mConnectionFailureNotifier) + .showFailedToConnectDueToNoRandomizedMacSupportNotification(FRAMEWORK_NETWORK_ID); + } + + /** + * Verifies that a notification is not posted when a wrong password failure happens on a + * network in the hotlist. + */ + @Test + public void testNotCallingIsInFlakyRandomizationSsidHotlistOnWrongPassword() throws Exception { + when(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(anyInt())).thenReturn(true); + // Setup CONNECT_MODE & a WifiConfiguration + initializeAndAddNetworkAndVerifySuccess(); + mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, sBSSID); + mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, + WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD); + mLooper.dispatchAll(); + + verify(mConnectionFailureNotifier, never()) + .showFailedToConnectDueToNoRandomizedMacSupportNotification(anyInt()); + } + + /** * Verifies that CMD_START_CONNECT make WifiDiagnostics report * CONNECTION_EVENT_STARTED * @throws Exception @@ -3544,14 +3652,15 @@ public class ClientModeImplTest { @Test public void testRemovePasspointConfig() throws Exception { String fqdn = "test.com"; - when(mPasspointManager.removeProvider(anyString())).thenReturn(true); + when(mPasspointManager.removeProvider(anyInt(), anyBoolean(), anyString())) + .thenReturn(true); // switch to connect mode and verify wifi is reported as enabled startSupplicantAndDispatchMessages(); - mCmi.sendMessage(ClientModeImpl.CMD_REMOVE_PASSPOINT_CONFIG, fqdn); + mCmi.sendMessage(ClientModeImpl.CMD_REMOVE_PASSPOINT_CONFIG, TEST_UID, 0, fqdn); mLooper.dispatchAll(); - verify(mWifiConfigManager).removePasspointConfiguredNetwork(eq(fqdn)); + verify(mWifiConfigManager).removePasspointConfiguredNetwork(fqdn); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java new file mode 100644 index 000000000..8bf07b8c3 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.net.wifi.WifiConfiguration; +import android.os.Handler; +import android.os.Process; +import android.os.test.TestLooper; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ConnectionFailureNotifier}. + */ +@SmallTest +public class ConnectionFailureNotifierTest { + @Mock private Context mContext; + @Mock private WifiInjector mWifiInjector; + @Mock private Resources mResources; + @Mock private FrameworkFacade mFrameworkFacade; + @Mock private WifiConfigManager mWifiConfigManager; + @Mock private WifiConnectivityManager mWifiConnectivityManager; + @Mock private NotificationManager mNotificationManager; + @Mock private ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder; + @Mock private Notification mNotification; + @Mock private AlertDialog mAlertDialog; + + final ArgumentCaptor<BroadcastReceiver> mBroadCastReceiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + private ConnectionFailureNotifier mConnectionFailureNotifier; + TestLooper mLooper; + + /** Initialize objects before each test run. */ + @Before + public void setUp() throws Exception { + // Ensure looper exists + mLooper = new TestLooper(); + MockitoAnnotations.initMocks(this); + when(mContext.getResources()).thenReturn(mResources); + when(mWifiInjector.getNotificationManager()).thenReturn(mNotificationManager); + when(mWifiInjector.getConnectionFailureNotificationBuilder()) + .thenReturn(mConnectionFailureNotificationBuilder); + when(mConnectionFailureNotificationBuilder + .buildNoMacRandomizationSupportNotification(any())).thenReturn(mNotification); + when(mConnectionFailureNotificationBuilder.buildChangeMacRandomizationSettingDialog(any(), + any())).thenReturn(mAlertDialog); + mConnectionFailureNotifier = new ConnectionFailureNotifier(mContext, mWifiInjector, + mFrameworkFacade, mWifiConfigManager, mWifiConnectivityManager, + new Handler(mLooper.getLooper())); + + verify(mContext).registerReceiver(mBroadCastReceiverCaptor.capture(), any()); + } + + private class DisableMacRandomizationMatcher implements ArgumentMatcher<WifiConfiguration> { + @Override + public boolean matches(WifiConfiguration config) { + return config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NONE; + } + } + + // Returns an intent that simulates the broadcast which is received when the user tap + // on the notification to change MAC randomization settings. + private Intent buildBroadcastForRandomizationSettingsDialog(WifiConfiguration config) { + Intent intent = mock(Intent.class); + when(intent.getAction()).thenReturn(ConnectionFailureNotificationBuilder + .ACTION_SHOW_SET_RANDOMIZATION_DETAILS); + when(intent.getIntExtra(eq(ConnectionFailureNotificationBuilder + .RANDOMIZATION_SETTINGS_NETWORK_ID), anyInt())).thenReturn(config.networkId); + when(intent.getStringExtra( + eq(ConnectionFailureNotificationBuilder.RANDOMIZATION_SETTINGS_NETWORK_SSID))) + .thenReturn(config.getSsidAndSecurityTypeString()); + return intent; + } + + /** + * Verify that a notification is posted when a connection failure happens on a network + * in the hotlist. Then verify that tapping on the notification launches an dialog, which + * could be used to set the randomization setting for a network to "Trusted". + */ + @Test + public void testConnectionFailureSendRandomizationSettingsNotification() { + // Verify that the network is using randomized MAC at the start. + WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork(); + when(mWifiConfigManager.getConfiguredNetwork(config.networkId)).thenReturn(config); + assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT, config.macRandomizationSetting); + + mConnectionFailureNotifier.showFailedToConnectDueToNoRandomizedMacSupportNotification( + config.networkId); + // verify that a notification is sent + verify(mNotificationManager).notify( + eq(ConnectionFailureNotifier.NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID), + eq(mNotification)); + + // sets up the intent that simulates the user tapping on the notification. + Intent intent = buildBroadcastForRandomizationSettingsDialog(config); + + // simulate the user tapping on the notification, then verify the dialog shows up, and + // the appropriate callback is registered + ArgumentCaptor<DialogInterface.OnClickListener> onClickListenerArgumentCaptor = + ArgumentCaptor.forClass(DialogInterface.OnClickListener.class); + mBroadCastReceiverCaptor.getValue().onReceive(mContext, intent); + verify(mConnectionFailureNotificationBuilder).buildChangeMacRandomizationSettingDialog( + eq(config.SSID), onClickListenerArgumentCaptor.capture()); + + // simulate the user tapping on the option to reset MAC address to factory MAC + onClickListenerArgumentCaptor.getValue().onClick(null, 0); + mLooper.dispatchAll(); + + // verify the WifiConfiguration is updated properly. + verify(mWifiConfigManager).addOrUpdateNetwork( + argThat(new DisableMacRandomizationMatcher()), eq(Process.SYSTEM_UID)); + // verify that we try to connect to the updated network. + verify(mWifiConnectivityManager).forceConnectivityScan(any()); + } + + /** + * Verify that if the WifiConfiguration if not found (may have been deleted by the timed the + * notification is tapped), then the AlertDialog does not show up. + */ + @Test + public void testWifiConfigurationMismatch() { + WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork(); + when(mWifiConfigManager.getConfiguredNetwork(config.networkId)).thenReturn(config); + mConnectionFailureNotifier.showFailedToConnectDueToNoRandomizedMacSupportNotification( + config.networkId); + // verify that a notification is sent + verify(mNotificationManager).notify( + eq(ConnectionFailureNotifier.NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID), + any()); + + // sets up the intent that simulates the user tapping on the notification. + Intent intent = buildBroadcastForRandomizationSettingsDialog(config); + + // the WifiConfiguration that is found doesn't match with the one received from broadcast. + when(mWifiConfigManager.getConfiguredNetwork(anyInt())) + .thenReturn(WifiConfigurationTestUtil.createOpenNetwork()); + mBroadCastReceiverCaptor.getValue().onReceive(mContext, intent); + + // verify that the AlertDialog is not launched in this case + verify(mConnectionFailureNotificationBuilder, never()) + .buildChangeMacRandomizationSettingDialog(any(), any()); + + verify(mFrameworkFacade, never()).makeAlertDialogBuilder(any()); + // instead we are showings a toast due to failing to find the network + verify(mFrameworkFacade).showToast(any(), any()); + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java index 702aa99df..17b9d1c2e 100644 --- a/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java @@ -24,6 +24,7 @@ import android.util.Xml; import androidx.test.filters.SmallTest; import com.android.internal.util.FastXmlSerializer; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import org.junit.Before; import org.junit.Test; @@ -78,7 +79,8 @@ public class DeletedEphemeralSsidsStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mDeletedEphemeralSsidsStoreData.serializeData(out); + mDeletedEphemeralSsidsStoreData.serializeData( + out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -94,7 +96,9 @@ public class DeletedEphemeralSsidsStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mDeletedEphemeralSsidsStoreData.deserializeData(in, in.getDepth()); + mDeletedEphemeralSsidsStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); return mDeletedEphemeralSsidsStoreData.getSsidToTimeMap(); } diff --git a/tests/wifitests/src/com/android/server/wifi/MacAddressUtilTest.java b/tests/wifitests/src/com/android/server/wifi/MacAddressUtilTest.java new file mode 100644 index 000000000..7e598db31 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/MacAddressUtilTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import android.net.MacAddress; +import android.net.wifi.WifiConfiguration; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.security.ProviderException; +import java.util.Random; + +import javax.crypto.Mac; + +/** + * Unit tests for {@link com.android.server.wifi.MacAddressUtil}. + */ +@SmallTest +public class MacAddressUtilTest { + private MacAddressUtil mMacAddressUtil; + + @Mock private Mac mMac; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mMacAddressUtil = new MacAddressUtil(); + } + + /** + * Verifies that calculatePersistentMacForConfiguration valid randomized MACs. + */ + @Test + public void testCalculatePersistentMacForConfiguration() { + // verify null inputs + assertNull(mMacAddressUtil.calculatePersistentMacForConfiguration(null, null)); + + Random rand = new Random(); + // Verify that a the MAC address calculated is valid + for (int i = 0; i < 10; i++) { + WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork(); + + byte[] bytes = new byte[32]; + rand.nextBytes(bytes); + when(mMac.doFinal(any())).thenReturn(bytes); + MacAddress macAddress = mMacAddressUtil.calculatePersistentMacForConfiguration( + config, mMac); + assertTrue(WifiConfiguration.isValidMacAddressForRandomization(macAddress)); + } + } + + /** + * Verify the java.security.ProviderException is caught. + */ + @Test + public void testCalculatePersistentMacCatchesException() { + when(mMac.doFinal(any())).thenThrow(new ProviderException("error occurred")); + try { + WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork(); + assertNull(mMacAddressUtil.calculatePersistentMacForConfiguration(config, mMac)); + } catch (Exception e) { + fail("Exception not caught."); + } + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java index 7336c4119..20b6c4f76 100644 --- a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java @@ -31,6 +31,7 @@ import android.util.Xml; import androidx.test.filters.SmallTest; import com.android.internal.util.FastXmlSerializer; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtilTest; import org.junit.Before; @@ -213,7 +214,7 @@ public class NetworkListStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mNetworkListSharedStoreData.serializeData(out); + mNetworkListSharedStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -229,7 +230,9 @@ public class NetworkListStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mNetworkListSharedStoreData.deserializeData(in, in.getDepth()); + mNetworkListSharedStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); return mNetworkListSharedStoreData.getConfigurations(); } diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkRequestStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkRequestStoreDataTest.java index f40f71bcf..c0f03505e 100644 --- a/tests/wifitests/src/com/android/server/wifi/NetworkRequestStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/NetworkRequestStoreDataTest.java @@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.FastXmlSerializer; import com.android.server.wifi.WifiNetworkFactory.AccessPoint; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import org.junit.Before; import org.junit.Test; @@ -80,7 +81,7 @@ public class NetworkRequestStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mNetworkRequestStoreData.serializeData(out); + mNetworkRequestStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -92,7 +93,9 @@ public class NetworkRequestStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mNetworkRequestStoreData.deserializeData(in, in.getDepth()); + mNetworkRequestStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java index 5c1dcb459..a35c510fc 100644 --- a/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java @@ -28,6 +28,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.FastXmlSerializer; import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion; import com.android.server.wifi.WifiNetworkSuggestionsManager.PerAppInfo; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import org.junit.Before; import org.junit.Test; @@ -119,7 +120,7 @@ public class NetworkSuggestionStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mNetworkSuggestionStoreData.serializeData(out); + mNetworkSuggestionStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -131,7 +132,9 @@ public class NetworkSuggestionStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mNetworkSuggestionStoreData.deserializeData(in, in.getDepth()); + mNetworkSuggestionStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/RandomizedMacStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/RandomizedMacStoreDataTest.java index 4df560fd2..cdd4e6c84 100644 --- a/tests/wifitests/src/com/android/server/wifi/RandomizedMacStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/RandomizedMacStoreDataTest.java @@ -24,6 +24,7 @@ import android.util.Xml; import androidx.test.filters.SmallTest; import com.android.internal.util.FastXmlSerializer; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import org.junit.Before; import org.junit.Test; @@ -62,7 +63,7 @@ public class RandomizedMacStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mRandomizedMacStoreData.serializeData(out); + mRandomizedMacStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -78,7 +79,9 @@ public class RandomizedMacStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mRandomizedMacStoreData.deserializeData(in, in.getDepth()); + mRandomizedMacStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); return mRandomizedMacStoreData.getMacMapping(); } diff --git a/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java index ac6ae21a2..feedc0d2a 100644 --- a/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -29,6 +30,7 @@ import android.util.Xml; import androidx.test.filters.SmallTest; import com.android.internal.util.FastXmlSerializer; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import org.junit.Before; import org.junit.Test; @@ -80,7 +82,7 @@ public class SsidSetStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mSsidSetStoreData.serializeData(out); + mSsidSetStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -95,7 +97,9 @@ public class SsidSetStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mSsidSetStoreData.deserializeData(in, in.getDepth()); + mSsidSetStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java index c814aef1a..df93eb4fb 100644 --- a/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,6 +29,7 @@ import android.util.Xml; import androidx.test.filters.SmallTest; import com.android.internal.util.FastXmlSerializer; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.google.android.collect.Sets; @@ -74,7 +76,7 @@ public class WakeupConfigStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mWakeupConfigData.serializeData(out); + mWakeupConfigData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -88,7 +90,9 @@ public class WakeupConfigStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mWakeupConfigData.deserializeData(in, in.getDepth()); + mWakeupConfigData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); } /** @@ -177,7 +181,9 @@ public class WakeupConfigStoreDataTest { */ @Test public void hasBeenReadIsTrueWhenUserStoreIsLoaded() throws Exception { - mWakeupConfigData.deserializeData(null /* in */, 0 /* outerTagDepth */); + mWakeupConfigData.deserializeData(null /* in */, 0 /* outerTagDepth */, + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); assertTrue(mWakeupConfigData.hasBeenRead()); } diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java index 009429b3f..a004995b9 100644 --- a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,6 +39,7 @@ import android.provider.Settings; import androidx.test.filters.SmallTest; import com.android.server.wifi.util.ScanResultUtil; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import org.junit.Before; import org.junit.Test; @@ -149,7 +151,9 @@ public class WakeupControllerTest { private void readUserStore() { try { - mWakeupConfigStoreData.deserializeData(null, 0); + mWakeupConfigStoreData.deserializeData(null, 0, + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); } catch (XmlPullParserException | IOException e) { // unreachable } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java index 0badc6fbd..6fa1868cb 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java @@ -42,6 +42,7 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.os.test.TestLooper; +import android.provider.DeviceConfig.OnPropertiesChangedListener; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -70,6 +71,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -135,7 +137,11 @@ public class WifiConfigManagerTest { @Mock private WifiConfigManager.OnSavedNetworkUpdateListener mWcmListener; @Mock private FrameworkFacade mFrameworkFacade; @Mock private CarrierNetworkConfig mCarrierNetworkConfig; + @Mock private MacAddressUtil mMacAddressUtil; + @Mock DeviceConfigFacade mDeviceConfigFacade; + final ArgumentCaptor<OnPropertiesChangedListener> mOnPropertiesChangedListenerCaptor = + ArgumentCaptor.forClass(OnPropertiesChangedListener.class); private MockResources mResources; private InOrder mContextConfigStoreMockOrder; private InOrder mNetworkListStoreDataMockOrder; @@ -169,6 +175,8 @@ public class WifiConfigManagerTest { TEST_MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCAN); mResources.setBoolean(R.bool.config_wifi_connected_mac_randomization_supported, true); when(mContext.getResources()).thenReturn(mResources); + when(mDeviceConfigFacade.getRandomizationFlakySsidHotlist()).thenReturn( + Collections.emptySet()); // Setup UserManager profiles for the default user. setupUserProfiles(TEST_DEFAULT_USER); @@ -216,6 +224,10 @@ public class WifiConfigManagerTest { when(mWifiInjector.getWifiLastResortWatchdog().shouldIgnoreSsidUpdate()) .thenReturn(false); when(mWifiInjector.getCarrierNetworkConfig()).thenReturn(mCarrierNetworkConfig); + when(mWifiInjector.getMacAddressUtil()).thenReturn(mMacAddressUtil); + when(mMacAddressUtil.calculatePersistentMacForConfiguration(any(), any())) + .thenReturn(TEST_RANDOMIZED_MAC); + createWifiConfigManager(); mWifiConfigManager.setOnSavedNetworkUpdateListener(mWcmListener); ArgumentCaptor<ContentObserver> observerCaptor = @@ -231,13 +243,12 @@ public class WifiConfigManagerTest { // static mocking mSession = ExtendedMockito.mockitoSession() .mockStatic(WifiConfigStore.class, withSettings().lenient()) - .spyStatic(WifiConfigurationUtil.class) .strictness(Strictness.LENIENT) .startMocking(); - when(WifiConfigStore.createUserFiles(anyInt())).thenReturn(mock(List.class)); + when(WifiConfigStore.createUserFiles(anyInt(), anyBoolean())).thenReturn(mock(List.class)); when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mDataTelephonyManager); - when(WifiConfigurationUtil.calculatePersistentMacForConfiguration(any(), any())) - .thenReturn(TEST_RANDOMIZED_MAC); + verify(mDeviceConfigFacade).addOnPropertiesChangedListener(any(), + mOnPropertiesChangedListenerCaptor.capture()); } /** @@ -292,6 +303,30 @@ public class WifiConfigManagerTest { } /** + * Verify that a randomized MAC address is generated even if the KeyStore operation fails. + */ + @Test + public void testRandomizedMacIsGeneratedEvenIfKeyStoreFails() { + when(mMacAddressUtil.calculatePersistentMacForConfiguration(any(), any())).thenReturn(null); + + // Try adding a network. + WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); + List<WifiConfiguration> networks = new ArrayList<>(); + networks.add(openNetwork); + verifyAddNetworkToWifiConfigManager(openNetwork); + List<WifiConfiguration> retrievedNetworks = + mWifiConfigManager.getConfiguredNetworksWithPasswords(); + + // Verify that we have attempted to generate the MAC address twice (1 retry) + verify(mMacAddressUtil, times(2)).calculatePersistentMacForConfiguration(any(), any()); + assertEquals(1, retrievedNetworks.size()); + + // Verify that despite KeyStore returning null, we are still getting a valid MAC address. + assertNotEquals(WifiInfo.DEFAULT_MAC_ADDRESS, + retrievedNetworks.get(0).getRandomizedMacAddress().toString()); + } + + /** * Verifies the addition of a single network using * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} */ @@ -4620,7 +4655,7 @@ public class WifiConfigManagerTest { mWifiPermissionsUtil, mWifiPermissionsWrapper, mWifiInjector, mNetworkListSharedStoreData, mNetworkListUserStoreData, mDeletedEphemeralSsidsStoreData, mRandomizedMacStoreData, - mFrameworkFacade, mLooper.getLooper()); + mFrameworkFacade, mLooper.getLooper(), mDeviceConfigFacade); mWifiConfigManager.enableVerboseLogging(1); } @@ -5349,4 +5384,32 @@ public class WifiConfigManagerTest { assertFalse(mWifiConfigManager.getConfiguredNetwork(networkId) .getNetworkSelectionStatus().isNetworkTemporaryDisabled()); } + + /** + * Verifies that isInFlakyRandomizationSsidHotlist returns true if the network's SSID is in + * the hotlist and the network is using randomized MAC. + */ + @Test + public void testFlakyRandomizationSsidHotlist() { + WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); + NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork); + int networkId = result.getNetworkId(); + + // should return false when there is nothing in the hotlist + assertFalse(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(networkId)); + + // add the network's SSID to the hotlist and verify the method returns true + Set<String> ssidHotlist = new HashSet<>(); + ssidHotlist.add(openNetwork.SSID); + when(mDeviceConfigFacade.getRandomizationFlakySsidHotlist()).thenReturn(ssidHotlist); + mOnPropertiesChangedListenerCaptor.getValue().onPropertiesChanged(null); + assertTrue(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(networkId)); + + // Now change the macRandomizationSetting to "trusted" and then verify + // isInFlakyRandomizationSsidHotlist returns false + openNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE; + NetworkUpdateResult networkUpdateResult = updateNetworkToWifiConfigManager(openNetwork); + assertNotEquals(WifiConfiguration.INVALID_NETWORK_ID, networkUpdateResult.getNetworkId()); + assertFalse(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(networkId)); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java index b59e367dd..efa2d4336 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java @@ -16,12 +16,9 @@ package com.android.server.wifi; -import static com.android.server.wifi.WifiConfigStore.ZEROED_ENCRYPTED_DATA; - import static org.junit.Assert.*; import static org.mockito.Mockito.*; -import android.app.test.MockAnswerUtil.AnswerWithArguments; import android.app.test.TestAlarmManager; import android.content.Context; import android.content.pm.PackageManager; @@ -34,8 +31,8 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.ArrayUtils; import com.android.server.wifi.WifiConfigStore.StoreData; import com.android.server.wifi.WifiConfigStore.StoreFile; -import com.android.server.wifi.util.DataIntegrityChecker; import com.android.server.wifi.util.EncryptedData; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import libcore.util.HexEncoding; @@ -43,7 +40,6 @@ import libcore.util.HexEncoding; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.xmlpull.v1.XmlPullParser; @@ -73,13 +69,7 @@ public class WifiConfigStoreTest { private static final String TEST_DATA_XML_STRING_FORMAT = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<WifiConfigStoreData>\n" - + "<int name=\"Version\" value=\"2\" />\n" - + "<Integrity>\n" - + "<byte-array name=\"EncryptedData\" num=\"48\">000000000000000000000000000000" - + "000000000000000000000000000000000000000000000000000000000000000000" - + "</byte-array>\n" - + "<byte-array name=\"IV\" num=\"12\">000000000000000000000000</byte-array>\n" - + "</Integrity>\n" + + "<int name=\"Version\" value=\"3\" />\n" + "<NetworkList>\n" + "<Network>\n" + "<WifiConfiguration>\n" @@ -172,7 +162,7 @@ public class WifiConfigStoreTest { private TestLooper mLooper; @Mock private Clock mClock; @Mock private WifiMetrics mWifiMetrics; - @Mock private DataIntegrityChecker mDataIntegrityChecker; + @Mock private WifiConfigStoreEncryptionUtil mEncryptionUtil; private MockStoreFile mSharedStore; private MockStoreFile mUserStore; private MockStoreFile mUserNetworkSuggestionsStore; @@ -196,10 +186,10 @@ public class WifiConfigStoreTest { .thenReturn(mAlarmManager.getAlarmManager()); when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getNameForUid(anyInt())).thenReturn(TEST_CREATOR_NAME); - when(mDataIntegrityChecker.compute(any(byte[].class))) - .thenReturn(ZEROED_ENCRYPTED_DATA); - when(mDataIntegrityChecker.isOk(any(byte[].class), any(EncryptedData.class))) - .thenReturn(true); + when(mEncryptionUtil.encrypt(any(byte[].class))) + .thenReturn(new EncryptedData(new byte[0], new byte[0])); + when(mEncryptionUtil.decrypt(any(EncryptedData.class))) + .thenReturn(new byte[0]); mSharedStore = new MockStoreFile(WifiConfigStore.STORE_FILE_SHARED_GENERAL); mUserStore = new MockStoreFile(WifiConfigStore.STORE_FILE_USER_GENERAL); mUserNetworkSuggestionsStore = @@ -432,9 +422,9 @@ public class WifiConfigStoreTest { // Ensure that we got the call to deserialize empty shared data, but no user data. verify(sharedStoreData).resetData(); - verify(sharedStoreData).deserializeData(eq(null), anyInt()); + verify(sharedStoreData).deserializeData(eq(null), anyInt(), anyInt(), any()); verify(userStoreData, never()).resetData(); - verify(userStoreData, never()).deserializeData(any(), anyInt()); + verify(userStoreData, never()).deserializeData(any(), anyInt(), anyInt(), any()); } /** @@ -462,9 +452,9 @@ public class WifiConfigStoreTest { // Ensure that we got the call to deserialize empty shared & user data. verify(userStoreData).resetData(); - verify(userStoreData).deserializeData(eq(null), anyInt()); + verify(userStoreData).deserializeData(eq(null), anyInt(), anyInt(), any()); verify(sharedStoreData).resetData(); - verify(sharedStoreData).deserializeData(eq(null), anyInt()); + verify(sharedStoreData).deserializeData(eq(null), anyInt(), anyInt(), any()); } /** @@ -639,9 +629,9 @@ public class WifiConfigStoreTest { mUserStore.storeRawDataToWrite(null); mWifiConfigStore.read(); - verify(storeData1).deserializeData(notNull(), anyInt()); - verify(storeData1, never()).deserializeData(eq(null), anyInt()); - verify(storeData2).deserializeData(eq(null), anyInt()); + verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any()); + verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any()); + verify(storeData2).deserializeData(eq(null), anyInt(), anyInt(), any()); reset(storeData1, storeData2); // Scenario 2: StoreData2 in user store file. @@ -655,9 +645,9 @@ public class WifiConfigStoreTest { mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes()); mWifiConfigStore.read(); - verify(storeData1).deserializeData(eq(null), anyInt()); - verify(storeData2).deserializeData(notNull(), anyInt()); - verify(storeData2, never()).deserializeData(eq(null), anyInt()); + verify(storeData1).deserializeData(eq(null), anyInt(), anyInt(), any()); + verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any()); + verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any()); reset(storeData1, storeData2); // Scenario 3: StoreData1 in shared store file & StoreData2 in user store file. @@ -671,10 +661,10 @@ public class WifiConfigStoreTest { mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes()); mWifiConfigStore.read(); - verify(storeData1).deserializeData(notNull(), anyInt()); - verify(storeData1, never()).deserializeData(eq(null), anyInt()); - verify(storeData2).deserializeData(notNull(), anyInt()); - verify(storeData2, never()).deserializeData(eq(null), anyInt()); + verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any()); + verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any()); + verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any()); + verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any()); reset(storeData1, storeData2); // Scenario 4: StoreData1 & StoreData2 in shared store file. @@ -689,10 +679,10 @@ public class WifiConfigStoreTest { mUserStore.storeRawDataToWrite(null); mWifiConfigStore.read(); - verify(storeData1).deserializeData(notNull(), anyInt()); - verify(storeData1, never()).deserializeData(eq(null), anyInt()); - verify(storeData2).deserializeData(notNull(), anyInt()); - verify(storeData2, never()).deserializeData(eq(null), anyInt()); + verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any()); + verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any()); + verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any()); + verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any()); reset(storeData1, storeData2); } @@ -739,9 +729,9 @@ public class WifiConfigStoreTest { verify(userStoreNetworkSuggestionsData).hasNewDataToSerialize(); // Verify that we serialized data from the first 2 data source, but not from the last one. - verify(sharedStoreData).serializeData(any()); - verify(userStoreData).serializeData(any()); - verify(userStoreNetworkSuggestionsData, never()).serializeData(any()); + verify(sharedStoreData).serializeData(any(), any()); + verify(userStoreData).serializeData(any(), any()); + verify(userStoreNetworkSuggestionsData, never()).serializeData(any(), any()); } /** @@ -815,190 +805,21 @@ public class WifiConfigStoreTest { // Read and verify the data content in the store file (metadata stripped out) has been sent // to the corresponding store data when integrity check passes. mWifiConfigStore.read(); - verify(sharedStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); - verify(userStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); - - // We shouldn't perform any data integrity checks on version 1 file. - verifyZeroInteractions(mDataIntegrityChecker); - } - - /** - * Tests the read API behaviour when integrity check fails. - * Expected behaviour: The read should return an empty store data. - */ - @Test - public void testReadWhenIntegrityCheckFails() throws Exception { - // Register data container. - StoreData sharedStoreData = mock(StoreData.class); - when(sharedStoreData.getStoreFileId()) - .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); - when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA); - StoreData userStoreData = mock(StoreData.class); - when(userStoreData.getStoreFileId()) - .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); - when(userStoreData.getName()).thenReturn(TEST_USER_DATA); - mWifiConfigStore.registerStoreData(sharedStoreData); - mWifiConfigStore.registerStoreData(userStoreData); - - // Read both share and user config store. - mWifiConfigStore.setUserStores(mUserStores); - - // Now store some content in the shared and user data files. - mUserStore.storeRawDataToWrite( - String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), - TEST_USER_DATA).getBytes()); - mSharedStore.storeRawDataToWrite( - String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), - TEST_SHARE_DATA).getBytes()); - - // Read and verify the data content in the store file (metadata stripped out) has been sent - // to the corresponding store data when integrity check passes. - mWifiConfigStore.read(); - verify(sharedStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); - verify(userStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); - - // Read and verify the data content in the store file (metadata stripped out) has not been - // sent to the corresponding store data when integrity check fails. - when(mDataIntegrityChecker.isOk(any(byte[].class), any(EncryptedData.class))) - .thenReturn(false); - mWifiConfigStore.read(); - verify(sharedStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); - verify(userStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); + verify(sharedStoreData, times(1)).deserializeData( + any(XmlPullParser.class), anyInt(), + eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any()); + verify(userStoreData, times(1)).deserializeData( + any(XmlPullParser.class), anyInt(), + eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any()); } /** - * Tests the write API behaviour when integrity check fails. - * Expected behaviour: The read should return an empty store data. + * Tests the read API behaviour to ensure that the integrity data is parsed from the file. */ @Test - public void testWriteWhenIntegrityComputeFails() throws Exception { - // Register data container. - StoreData sharedStoreData = mock(StoreData.class); - when(sharedStoreData.getStoreFileId()) - .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); - when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA); - when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true); - StoreData userStoreData = mock(StoreData.class); - when(userStoreData.getStoreFileId()) - .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); - when(userStoreData.getName()).thenReturn(TEST_USER_DATA); - when(userStoreData.hasNewDataToSerialize()).thenReturn(true); - mWifiConfigStore.registerStoreData(sharedStoreData); - mWifiConfigStore.registerStoreData(userStoreData); - - // Read both share and user config store. - mWifiConfigStore.setUserStores(mUserStores); - - // Reset store file contents & ensure that the user and store data files are empty. - mUserStore.storeRawDataToWrite(null); - mSharedStore.storeRawDataToWrite(null); - assertNull(mUserStore.getStoreBytes()); - assertNull(mSharedStore.getStoreBytes()); - - // Write and verify that the data is written to the config store file when integrity - // computation passes. - mWifiConfigStore.write(true); - assertNotNull(mUserStore.getStoreBytes()); - assertNotNull(mSharedStore.getStoreBytes()); - assertTrue(new String(mUserStore.getStoreBytes()).contains(TEST_USER_DATA)); - assertTrue(new String(mSharedStore.getStoreBytes()).contains(TEST_SHARE_DATA)); - - // Reset store file contents & ensure that the user and store data files are empty. - mUserStore.storeRawDataToWrite(null); - mSharedStore.storeRawDataToWrite(null); - assertNull(mUserStore.getStoreBytes()); - assertNull(mSharedStore.getStoreBytes()); - - // Write and verify that the data is not written to the config store file when integrity - // computation fails. - when(mDataIntegrityChecker.compute(any(byte[].class))).thenReturn(null); - mWifiConfigStore.write(true); - assertNull(mUserStore.getStoreBytes()); - assertNull(mSharedStore.getStoreBytes()); - } - - /** - * Tests the write API behaviour to ensure that the integrity data is written to the file. - */ - @Test - public void testWriteContainsIntegrityData() throws Exception { - byte[] encryptedData = new byte[EncryptedData.ENCRYPTED_DATA_LENGTH]; - byte[] iv = new byte[EncryptedData.IV_LENGTH]; - Random random = new Random(); - random.nextBytes(encryptedData); - random.nextBytes(iv); - final EncryptedData testEncryptedData = new EncryptedData(encryptedData, iv); - - doAnswer(new AnswerWithArguments() { - public EncryptedData answer(byte[] data) { - String storeXmlString = new String(data); - // Verify that we fill in zeros to the data when we compute integrity. - if (storeXmlString.contains(TEST_SHARE_DATA)) { - assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), - TEST_SHARE_DATA), storeXmlString); - } else if (storeXmlString.contains(TEST_USER_DATA)) { - assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), - TEST_USER_DATA), storeXmlString); - } - return testEncryptedData; - } - }).when(mDataIntegrityChecker).compute(any(byte[].class)); - - // Register data container. - StoreData sharedStoreData = mock(StoreData.class); - when(sharedStoreData.getStoreFileId()) - .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); - when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA); - when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true); - StoreData userStoreData = mock(StoreData.class); - when(userStoreData.getStoreFileId()) - .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); - when(userStoreData.getName()).thenReturn(TEST_USER_DATA); - when(userStoreData.hasNewDataToSerialize()).thenReturn(true); - mWifiConfigStore.registerStoreData(sharedStoreData); - mWifiConfigStore.registerStoreData(userStoreData); - - // Read both share and user config store. - mWifiConfigStore.setUserStores(mUserStores); - - // Write and verify that the data is written to the config store file when integrity - // computation passes. - mWifiConfigStore.write(true); - - // Verify that we fill in zeros to the data when we computed integrity. - verify(mDataIntegrityChecker, times(2)).compute(any(byte[].class)); - - // Verify the parsed integrity data - assertNotNull(mUserStore.getStoreBytes()); - assertNotNull(mSharedStore.getStoreBytes()); - String userStoreXmlString = new String(mUserStore.getStoreBytes()); - String sharedStoreXmlString = new String(mSharedStore.getStoreBytes()); - assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, - HexEncoding.encodeToString(encryptedData).toLowerCase(), - HexEncoding.encodeToString(iv).toLowerCase(), - TEST_USER_DATA), userStoreXmlString); - assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, - HexEncoding.encodeToString(encryptedData).toLowerCase(), - HexEncoding.encodeToString(iv).toLowerCase(), - TEST_SHARE_DATA), sharedStoreXmlString); - } - - /** - * Tests the read API behaviour to ensure that the integrity data is parsed from the file and - * used for checking integrity of the file. - */ - @Test - public void testReadParsesIntegrityData() throws Exception { - byte[] encryptedData = new byte[EncryptedData.ENCRYPTED_DATA_LENGTH]; - byte[] iv = new byte[EncryptedData.IV_LENGTH]; + public void testReadVersion2StoreFile() throws Exception { + byte[] encryptedData = new byte[0]; + byte[] iv = new byte[0]; Random random = new Random(); random.nextBytes(encryptedData); random.nextBytes(iv); @@ -1033,40 +854,14 @@ public class WifiConfigStoreTest { TEST_SHARE_DATA).getBytes()); // Read and verify the data content in the store file (metadata stripped out) has been sent - // to the corresponding store data when integrity check passes. + // to the corresponding store data. mWifiConfigStore.read(); - verify(sharedStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); - verify(userStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); - - // Verify that we parsed the integrity data and used it for checking integrity of the file. - ArgumentCaptor<EncryptedData> integrityCaptor = - ArgumentCaptor.forClass(EncryptedData.class); - ArgumentCaptor<byte[]> dataCaptor = ArgumentCaptor.forClass(byte[].class); - // Will be invoked twice for each file - shared & user store file. - verify(mDataIntegrityChecker, times(2)).isOk( - dataCaptor.capture(), integrityCaptor.capture()); - // Verify the parsed integrity data - assertEquals(2, integrityCaptor.getAllValues().size()); - EncryptedData parsedEncryptedData1 = integrityCaptor.getAllValues().get(0); - assertArrayEquals(encryptedData, parsedEncryptedData1.getEncryptedData()); - assertArrayEquals(iv, parsedEncryptedData1.getIv()); - EncryptedData parsedEncryptedData2 = integrityCaptor.getAllValues().get(1); - assertArrayEquals(encryptedData, parsedEncryptedData2.getEncryptedData()); - assertArrayEquals(iv, parsedEncryptedData2.getIv()); - - // Verify that we fill in zeros to the data when we performed integrity checked. - assertEquals(2, dataCaptor.getAllValues().size()); - String sharedStoreXmlStringWithZeroedIntegrity = - new String(dataCaptor.getAllValues().get(0)); - assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), - TEST_SHARE_DATA), sharedStoreXmlStringWithZeroedIntegrity); - String userStoreXmlStringWithZeroedIntegrity = new String(dataCaptor.getAllValues().get(1)); - assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), - HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), - TEST_USER_DATA), userStoreXmlStringWithZeroedIntegrity); + verify(sharedStoreData, times(1)) + .deserializeData(any(XmlPullParser.class), anyInt(), + eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any()); + verify(userStoreData, times(1)) + .deserializeData(any(XmlPullParser.class), anyInt(), + eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any()); } /** @@ -1078,7 +873,7 @@ public class WifiConfigStoreTest { private boolean mStoreWritten; MockStoreFile(@WifiConfigStore.StoreFileId int fileId) { - super(new File("MockStoreFile"), fileId, mDataIntegrityChecker); + super(new File("MockStoreFile"), fileId, mEncryptionUtil); } @Override @@ -1129,13 +924,14 @@ public class WifiConfigStoreTest { } @Override - public void serializeData(XmlSerializer out) + public void serializeData(XmlSerializer out, WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mData); } @Override - public void deserializeData(XmlPullParser in, int outerTagDepth) + public void deserializeData(XmlPullParser in, int outerTagDepth, int version, + WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { if (in == null) { return; diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java index a40de55e9..255073b8b 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java @@ -161,17 +161,6 @@ public class WifiLockManagerTest { } /** - * Test to verify that the lock mode is verified before adding a lock. - * - * Steps: call acquireWifiLock with an invalid lock mode. - * Expected: the call should throw an IllegalArgumentException. - */ - @Test(expected = IllegalArgumentException.class) - public void acquireWifiLockShouldThrowExceptionOnInivalidLockMode() throws Exception { - mWifiLockManager.acquireWifiLock(WIFI_LOCK_MODE_INVALID, "", mBinder, mWorkSource); - } - - /** * Test that a call to acquireWifiLock with valid parameters works. * * Steps: call acquireWifiLock on the empty WifiLockManager. diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java index 7e086d4ce..73ac30f41 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java @@ -2999,9 +2999,10 @@ public class WifiMetricsTest { dumpProtoAndDeserialize(); assertEquals(2 * WifiMetrics.MAX_WIFI_USABILITY_STATS_PER_TYPE_TO_UPLOAD, mDecodedProto.wifiUsabilityStatsList.length); - for (int i = 0; i < mDecodedProto.wifiUsabilityStatsList.length; i++) { + for (int i = 0; i < WifiMetrics.MAX_WIFI_USABILITY_STATS_PER_TYPE_TO_UPLOAD; i++) { assertEquals(WifiMetrics.MAX_WIFI_USABILITY_STATS_ENTRIES_LIST_SIZE, - mDecodedProto.wifiUsabilityStatsList[i].stats.length); + mDecodedProto.wifiUsabilityStatsList[2 * i].stats.length); + assertEquals(2, mDecodedProto.wifiUsabilityStatsList[2 * i + 1].stats.length); } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java index 63cc8bf60..d20c99c4f 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java @@ -68,6 +68,7 @@ import com.android.internal.util.FastXmlSerializer; import com.android.server.wifi.WifiNetworkFactory.AccessPoint; import com.android.server.wifi.nano.WifiMetricsProto; import com.android.server.wifi.util.ScanResultUtil; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.WifiPermissionsUtil; import org.junit.After; @@ -84,6 +85,8 @@ import org.xmlpull.v1.XmlSerializer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -184,6 +187,7 @@ public class WifiNetworkFactoryTest { when(mWifiInjector.getClientModeImpl()).thenReturn(mClientModeImpl); when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString())) .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID_1)); + when(mWifiScanner.getSingleScanResults()).thenReturn(Collections.emptyList()); mWifiNetworkFactory = new WifiNetworkFactory(mLooper.getLooper(), mContext, mNetworkCapabilities, mActivityManager, mAlarmManager, mAppOpsManager, mClock, @@ -951,16 +955,11 @@ public class WifiNetworkFactoryTest { verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); - ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor = - ArgumentCaptor.forClass(List.class); - verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); - - assertNotNull(matchedScanResultsCaptor.getValue()); // We expect no network match in this case. - assertEquals(0, matchedScanResultsCaptor.getValue().size()); + verify(mNetworkRequestMatchCallback, never()).onMatch(any()); - verify(mWifiMetrics).incrementNetworkRequestApiMatchSizeHistogram( - matchedScanResultsCaptor.getValue().size()); + // Don't increment metrics until we have a match + verify(mWifiMetrics, never()).incrementNetworkRequestApiMatchSizeHistogram(anyInt()); } /** @@ -996,13 +995,8 @@ public class WifiNetworkFactoryTest { verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); - ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor = - ArgumentCaptor.forClass(List.class); - verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); - - assertNotNull(matchedScanResultsCaptor.getValue()); // We expect no network match in this case. - assertEquals(0, matchedScanResultsCaptor.getValue().size()); + verify(mNetworkRequestMatchCallback, never()).onMatch(any()); } /** @@ -1615,9 +1609,17 @@ public class WifiNetworkFactoryTest { verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue()); // Now release the network request. + WifiConfiguration wcmNetwork = new WifiConfiguration(mSelectedNetwork); + wcmNetwork.networkId = TEST_NETWORK_ID_1; + wcmNetwork.creatorUid = TEST_UID_1; + wcmNetwork.fromWifiNetworkSpecifier = true; + wcmNetwork.ephemeral = true; + when(mWifiConfigManager.getConfiguredNetwork(mSelectedNetwork.configKey())) + .thenReturn(wcmNetwork); mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest); // Verify that we triggered a disconnect. verify(mClientModeImpl, times(2)).disconnectCommand(); + verify(mWifiConfigManager).removeNetwork(TEST_NETWORK_ID_1, TEST_UID_1); // Re-enable connectivity manager . verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false); } @@ -1676,6 +1678,7 @@ public class WifiNetworkFactoryTest { mLooper.dispatchAll(); verify(mNetworkRequestMatchCallback).onAbort(); + verify(mWifiScanner, times(2)).getSingleScanResults(); verify(mWifiScanner, times(2)).startScan(any(), any(), any()); verifyUnfullfillableDispatched(mConnectivityMessenger); @@ -1717,6 +1720,7 @@ public class WifiNetworkFactoryTest { mLooper.dispatchAll(); verify(mNetworkRequestMatchCallback).onAbort(); + verify(mWifiScanner, times(2)).getSingleScanResults(); verify(mWifiScanner, times(2)).startScan(any(), any(), any()); verify(mAlarmManager).cancel(mPeriodicScanListenerArgumentCaptor.getValue()); verifyUnfullfillableDispatched(mConnectivityMessenger); @@ -1751,6 +1755,7 @@ public class WifiNetworkFactoryTest { verify(mNetworkRequestMatchCallback).onAbort(); verify(mWifiConnectivityManager, times(1)).setSpecificNetworkRequestInProgress(true); + verify(mWifiScanner, times(2)).getSingleScanResults(); verify(mWifiScanner, times(2)).startScan(any(), any(), any()); verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue()); @@ -1790,6 +1795,7 @@ public class WifiNetworkFactoryTest { mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); verify(mWifiConnectivityManager, times(1)).setSpecificNetworkRequestInProgress(true); + verify(mWifiScanner, times(2)).getSingleScanResults(); verify(mWifiScanner, times(2)).startScan(any(), any(), any()); // we shouldn't disconnect until the user accepts the next request. verify(mClientModeImpl, times(1)).disconnectCommand(); @@ -2082,10 +2088,10 @@ public class WifiNetworkFactoryTest { /** * Verify the user approval bypass for a specific request for an access point that was already - * approved previously. + * approved previously with no cached scan results matching. */ @Test - public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchPreviouslyApproved() + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithNoCache() throws Exception { // 1. First request (no user approval bypass) sendNetworkRequestAndSetupForConnectionStatus(); @@ -2105,6 +2111,9 @@ public class WifiNetworkFactoryTest { WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1); mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + validateUiStartParams(true); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, TEST_CALLBACK_IDENTIFIER); // Trigger scan results & ensure we triggered a connect. @@ -2126,8 +2135,7 @@ public class WifiNetworkFactoryTest { * approved previously, but then the user forgot it sometime after. */ @Test - public void - testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchPreviouslyApprovedNForgot() + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedNForgot() throws Exception { // 1. First request (no user approval bypass) sendNetworkRequestAndSetupForConnectionStatus(); @@ -2171,7 +2179,7 @@ public class WifiNetworkFactoryTest { * not approved previously. */ @Test - public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchNotPreviouslyApproved() + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchNotApproved() throws Exception { // 1. First request (no user approval bypass) sendNetworkRequestAndSetupForConnectionStatus(); @@ -2212,7 +2220,7 @@ public class WifiNetworkFactoryTest { * (not access point) that was approved previously. */ @Test - public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchPreviouslyApproved() + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchApproved() throws Exception { // 1. First request (no user approval bypass) sendNetworkRequestAndSetupForConnectionStatus(); @@ -2404,7 +2412,7 @@ public class WifiNetworkFactoryTest { * Verify the config store save and load could preserve the elements order. */ @Test - public void testStoteConfigSaveAndLoadPreserveOrder() throws Exception { + public void testStoreConfigSaveAndLoadPreserveOrder() throws Exception { LinkedHashSet<AccessPoint> approvedApSet = new LinkedHashSet<>(); approvedApSet.add(new AccessPoint(TEST_SSID_1, MacAddress.fromString(TEST_BSSID_1), WifiConfiguration.SECURITY_TYPE_PSK)); @@ -2429,6 +2437,159 @@ public class WifiNetworkFactoryTest { assertArrayEquals(approvedApSet.toArray(), storedApSet.toArray()); } + /** + * Verify the user approval bypass for a specific request for an access point that was already + * approved previously and the scan result is present in the cached scan results. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithCache() + throws Exception { + // 1. First request (no user approval bypass) + sendNetworkRequestAndSetupForConnectionStatus(); + + mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER); + reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl); + + // 2. Second request for the same access point (user approval bypass). + ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0]; + // simulate no cache expiry + when(mClock.getElapsedSinceBootMillis()).thenReturn(0L); + // Simulate the cached results matching. + when(mWifiScanner.getSingleScanResults()) + .thenReturn(Arrays.asList(mTestScanDatas[0].getResults())); + + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.fromString(matchingScanResult.BSSID), + MacAddress.BROADCAST_ADDRESS); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, + WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Verify we did not trigger the UI for the second request. + verify(mContext, times(1)).startActivityAsUser(any(), any()); + // Verify we did not trigger a scan. + verify(mWifiScanner, never()).startScan(any(), any(), any()); + // Verify we did not trigger the match callback. + verify(mNetworkRequestMatchCallback, never()).onMatch(anyList()); + // Verify that we sent a connection attempt to ClientModeImpl + verify(mClientModeImpl).sendMessage(any()); + + verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass(); + } + + /** + * Verify the user approval bypass for a specific request for an access point that was already + * approved previously and the scan result is present in the cached scan results, but the + * results are stale. + */ + @Test + public void + testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithStaleCache() + throws Exception { + // 1. First request (no user approval bypass) + sendNetworkRequestAndSetupForConnectionStatus(); + + mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER); + reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl); + + long scanResultsTimestampInUs = 39484839202L; + mTestScanDatas[0].getResults()[0].timestamp = scanResultsTimestampInUs; + mTestScanDatas[0].getResults()[1].timestamp = scanResultsTimestampInUs; + mTestScanDatas[0].getResults()[2].timestamp = scanResultsTimestampInUs; + mTestScanDatas[0].getResults()[3].timestamp = scanResultsTimestampInUs; + + // 2. Second request for the same access point (user approval bypass). + ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0]; + // simulate cache expiry + when(mClock.getElapsedSinceBootMillis()) + .thenReturn(Long.valueOf( + scanResultsTimestampInUs / 1000 + + WifiNetworkFactory.CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS + 1)); + // Simulate the cached results matching. + when(mWifiScanner.getSingleScanResults()) + .thenReturn(Arrays.asList(mTestScanDatas[0].getResults())); + + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.fromString(matchingScanResult.BSSID), + MacAddress.BROADCAST_ADDRESS); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, + WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Ensure we brought up the UI while the scan is ongoing. + validateUiStartParams(true); + + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Trigger scan results & ensure we triggered a connect. + verify(mWifiScanner).startScan(any(), mScanListenerArgumentCaptor.capture(), any()); + ScanListener scanListener = mScanListenerArgumentCaptor.getValue(); + assertNotNull(scanListener); + scanListener.onResults(mTestScanDatas); + + // Verify we did not trigger the match callback. + verify(mNetworkRequestMatchCallback, never()).onMatch(anyList()); + // Verify that we sent a connection attempt to ClientModeImpl + verify(mClientModeImpl).sendMessage(any()); + + verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass(); + } + + /** + * Verify network specifier matching for a specifier containing a specific SSID match using + * 4 WPA_PSK scan results, each with unique SSID when the UI callback registration is delayed. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchCallbackRegistrationDelayed() + throws Exception { + // Setup scan data for open networks. + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // Setup network specifier for open networks. + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1, + TEST_PACKAGE_NAME_1); + + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + validateUiStartParams(true); + + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + + // Ensure we did not send any match callbacks, until the callback is registered + verify(mNetworkRequestMatchCallback, never()).onMatch(any()); + + // Register the callback & ensure we triggered the on match callback. + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); + + assertNotNull(matchedScanResultsCaptor.getValue()); + // We only expect 1 network match in this case. + validateScanResults(matchedScanResultsCaptor.getValue(), mTestScanDatas[0].getResults()[0]); + + verify(mWifiMetrics).incrementNetworkRequestApiMatchSizeHistogram( + matchedScanResultsCaptor.getValue().size()); + } + private Messenger sendNetworkRequestAndSetupForConnectionStatus() throws RemoteException { return sendNetworkRequestAndSetupForConnectionStatus(TEST_SSID_1); } @@ -2498,6 +2659,8 @@ public class WifiNetworkFactoryTest { mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + validateUiStartParams(true); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, TEST_CALLBACK_IDENTIFIER); verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration( @@ -2505,7 +2668,7 @@ public class WifiNetworkFactoryTest { verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); - verify(mNetworkRequestMatchCallback).onMatch(anyList()); + verify(mNetworkRequestMatchCallback, atLeastOnce()).onMatch(anyList()); } // Simulates the periodic scans performed to find a matching network. @@ -2520,6 +2683,10 @@ public class WifiNetworkFactoryTest { ScanListener scanListener = null; mInOrder = inOrder(mWifiScanner, mAlarmManager); + + // Before we start scans, ensure that we look at the latest cached scan results. + mInOrder.verify(mWifiScanner).getSingleScanResults(); + for (int i = 0; i < expectedIntervalsInSeconds.length - 1; i++) { long expectedCurrentIntervalInMs = expectedIntervalsInSeconds[i]; long expectedNextIntervalInMs = expectedIntervalsInSeconds[i + 1]; @@ -2697,7 +2864,7 @@ public class WifiNetworkFactoryTest { private void validateUiStartParams(boolean expectedIsReqForSingeNetwork) { ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(mContext).startActivityAsUser( + verify(mContext, atLeastOnce()).startActivityAsUser( intentArgumentCaptor.capture(), eq(UserHandle.getUserHandleForUid(TEST_UID_1))); Intent intent = intentArgumentCaptor.getValue(); assertNotNull(intent); @@ -2766,7 +2933,7 @@ public class WifiNetworkFactoryTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mNetworkRequestStoreData.serializeData(out); + mNetworkRequestStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -2781,6 +2948,8 @@ public class WifiNetworkFactoryTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mNetworkRequestStoreData.deserializeData(in, in.getDepth()); + mNetworkRequestStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java index 9a3bd75be..d5f7dd981 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java @@ -174,6 +174,7 @@ public class WifiServiceImplTest { private static final String WIFI_IFACE_NAME2 = "wlan1"; private static final String TEST_COUNTRY_CODE = "US"; private static final String TEST_FACTORY_MAC = "10:22:34:56:78:92"; + private static final String TEST_FQDN = "testfqdn"; private static final List<WifiConfiguration> TEST_WIFI_CONFIGURATION_LIST = Arrays.asList( WifiConfigurationTestUtil.generateWifiConfig( 0, 1000000, "\"red\"", true, true, null, null), @@ -2788,77 +2789,57 @@ public class WifiServiceImplTest { } /** - * Verify that the call to getPasspointConfigurations is not redirected to specific API - * syncGetPasspointConfigs when the caller doesn't have NETWORK_SETTINGS permissions and - * NETWORK_SETUP_WIZARD. + * Verify the call to getPasspointConfigurations when the caller doesn't have + * NETWORK_SETTINGS and NETWORK_SETUP_WIZARD permissions. */ - @Test(expected = SecurityException.class) - public void testGetPasspointConfigurationsWithOutPermissions() { + @Test + public void testGetPasspointConfigurationsWithOutPrivilegedPermissions() { when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false); when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(false); mWifiServiceImpl.getPasspointConfigurations(TEST_PACKAGE_NAME); + + verify(mClientModeImpl).syncGetPasspointConfigs(any(), eq(false)); } /** - * Verify that getPasspointConfigurations called by apps that has invalid package will - * throw {@link SecurityException}. + * Verify that the call to getPasspointConfigurations when the caller does have + * NETWORK_SETTINGS permission. */ - @Test(expected = SecurityException.class) - public void testGetPasspointConfigurationWithInvalidPackage() { - doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), - eq(TEST_PACKAGE_NAME)); + @Test + public void testGetPasspointConfigurationsWithPrivilegedPermissions() { when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true); - when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(true); mWifiServiceImpl.getPasspointConfigurations(TEST_PACKAGE_NAME); - } - /** - * Verify that getPasspointConfigurations called by apps targeting below Q SDK will return - * empty list if the caller doesn't have NETWORK_SETTINGS permissions and NETWORK_SETUP_WIZARD. - */ - @Test - public void testGetPasspointConfigurationForAppsTargetingBelowQSDK() { - when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false); - when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(false); - when(mWifiPermissionsUtil.isTargetSdkLessThan(eq(TEST_PACKAGE_NAME), - eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true); - - List<PasspointConfiguration> result = mWifiServiceImpl.getPasspointConfigurations( - TEST_PACKAGE_NAME); - assertNotNull(result); - assertEquals(0, result.size()); + verify(mClientModeImpl).syncGetPasspointConfigs(any(), eq(true)); } /** - * Verify that the call to removePasspointConfiguration is not redirected to specific API - * syncRemovePasspointConfig when the caller doesn't have NETWORK_SETTINGS and - * NETWORK_CARRIER_PROVISIONING permission. + * Verify the call to removePasspointConfigurations when the caller doesn't have + * NETWORK_SETTINGS and NETWORK_CARRIER_PROVISIONING permissions. */ - @Test(expected = SecurityException.class) - public void testRemovePasspointConfigurationWithOutPermissions() { + @Test + public void testRemovePasspointConfigurationWithOutPrivilegedPermissions() { when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false); when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(anyInt())).thenReturn( false); - mWifiServiceImpl.removePasspointConfiguration(null, null); + mWifiServiceImpl.removePasspointConfiguration(TEST_FQDN, TEST_PACKAGE_NAME); + verify(mClientModeImpl).syncRemovePasspointConfig(any(), eq(false), eq(TEST_FQDN)); } /** - * Verify that the call to removePasspointConfiguration for apps targeting below Q SDK will - * return false if the caller doesn't have NETWORK_SETTINGS and NETWORK_CARRIER_PROVISIONING - * permission. + * Verify the call to removePasspointConfigurations when the caller does have + * NETWORK_CARRIER_PROVISIONING permission. */ @Test - public void testRemovePasspointConfigurationForAppsTargetingBelowQSDK() { - when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false); + public void testRemovePasspointConfigurationWithPrivilegedPermissions() { when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(anyInt())).thenReturn( - false); - when(mWifiPermissionsUtil.isTargetSdkLessThan(isNull(), - eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true); + true); - assertFalse(mWifiServiceImpl.removePasspointConfiguration(null, null)); + mWifiServiceImpl.removePasspointConfiguration(TEST_FQDN, TEST_PACKAGE_NAME); + verify(mClientModeImpl).syncRemovePasspointConfig(any(), eq(true), eq(TEST_FQDN)); } /** @@ -3449,6 +3430,25 @@ public class WifiServiceImplTest { } /** + * Verifies that entering airplane mode does not reset country code. + */ + @Test + public void testEnterAirplaneModeNotResetCountryCode() { + mWifiServiceImpl.checkAndStartWifi(); + verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), + (IntentFilter) argThat((IntentFilter filter) -> + filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED))); + + when(mSettingsStore.isAirplaneModeOn()).thenReturn(true); + + // Send the broadcast + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent); + + verifyNoMoreInteractions(mWifiCountryCode); + } + + /** * Verify calls to notify users of a softap config change check the NETWORK_SETTINGS permission. */ @Test @@ -3650,13 +3650,14 @@ public class WifiServiceImplTest { mWifiServiceImpl.mClientModeImplChannel = mAsyncChannel; when(mClientModeImpl.syncGetConfiguredNetworks(anyInt(), any(), anyInt())) .thenReturn(Arrays.asList(network)); - when(mClientModeImpl.syncGetPasspointConfigs(any())).thenReturn(Arrays.asList(config)); + when(mClientModeImpl.syncGetPasspointConfigs(any(), anyBoolean())) + .thenReturn(Arrays.asList(config)); mWifiServiceImpl.factoryReset(TEST_PACKAGE_NAME); mLooper.dispatchAll(); verify(mClientModeImpl).syncRemoveNetwork(mAsyncChannel, network.networkId); - verify(mClientModeImpl).syncRemovePasspointConfig(mAsyncChannel, fqdn); + verify(mClientModeImpl).syncRemovePasspointConfig(mAsyncChannel, true, fqdn); verify(mWifiConfigManager).clearDeletedEphemeralNetworks(); verify(mClientModeImpl).clearNetworkRequestUserApprovedAccessPoints(); verify(mWifiNetworkSuggestionsManager).clear(); @@ -3679,8 +3680,9 @@ public class WifiServiceImplTest { mLooper.dispatchAll(); verify(mClientModeImpl).syncGetConfiguredNetworks(anyInt(), any(), anyInt()); - verify(mClientModeImpl, never()).syncGetPasspointConfigs(any()); - verify(mClientModeImpl, never()).syncRemovePasspointConfig(any(), anyString()); + verify(mClientModeImpl, never()).syncGetPasspointConfigs(any(), anyBoolean()); + verify(mClientModeImpl, never()).syncRemovePasspointConfig( + any(), anyBoolean(), anyString()); verify(mWifiConfigManager).clearDeletedEphemeralNetworks(); verify(mClientModeImpl).clearNetworkRequestUserApprovedAccessPoints(); verify(mWifiNetworkSuggestionsManager).clear(); @@ -3703,7 +3705,7 @@ public class WifiServiceImplTest { } catch (SecurityException e) { } verify(mClientModeImpl, never()).syncGetConfiguredNetworks(anyInt(), any(), anyInt()); - verify(mClientModeImpl, never()).syncGetPasspointConfigs(any()); + verify(mClientModeImpl, never()).syncGetPasspointConfigs(any(), eq(false)); } /** @@ -4269,4 +4271,17 @@ public class WifiServiceImplTest { } catch (RemoteException e) { } } + + /** + * Test to verify that the lock mode is verified before dispatching the operation + * + * Steps: call acquireWifiLock with an invalid lock mode. + * Expected: the call should throw an IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void acquireWifiLockShouldThrowExceptionOnInvalidLockMode() throws Exception { + final int wifiLockModeInvalid = -1; + + mWifiServiceImpl.acquireWifiLock(mAppBinder, wifiLockModeInvalid, "", null); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java index aa44023ae..c33a4d55e 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java @@ -730,7 +730,7 @@ public class WifiVendorHalTest { IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN | IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS ); - int expected = ( + long expected = ( WifiManager.WIFI_FEATURE_SCANNER | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS); assertEquals(expected, mWifiVendorHal.wifiFeatureMaskFromStaCapabilities(caps)); @@ -749,7 +749,7 @@ public class WifiVendorHalTest { | android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2D_RTT | android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2AP_RTT ); - int expected = ( + long expected = ( WifiManager.WIFI_FEATURE_TX_POWER_LIMIT | WifiManager.WIFI_FEATURE_D2D_RTT | WifiManager.WIFI_FEATURE_D2AP_RTT @@ -768,7 +768,7 @@ public class WifiVendorHalTest { android.hardware.wifi.V1_3.IWifiChip.ChipCapabilityMask.SET_LATENCY_MODE | android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2D_RTT ); - int expected = ( + long expected = ( WifiManager.WIFI_FEATURE_LOW_LATENCY | WifiManager.WIFI_FEATURE_D2D_RTT ); @@ -794,7 +794,7 @@ public class WifiVendorHalTest { add(IfaceType.STA); add(IfaceType.P2P); }}; - int expectedFeatureSet = ( + long expectedFeatureSet = ( WifiManager.WIFI_FEATURE_SCANNER | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS | WifiManager.WIFI_FEATURE_TX_POWER_LIMIT diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreDataTest.java index c76e2c878..7a815001c 100644 --- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreDataTest.java @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.FastXmlSerializer; import com.android.server.wifi.WifiConfigStore; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import org.junit.Before; import org.junit.Test; @@ -62,7 +63,7 @@ public class PasspointConfigSharedStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mConfigStoreData.serializeData(out); + mConfigStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -77,7 +78,9 @@ public class PasspointConfigSharedStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mConfigStoreData.deserializeData(in, in.getDepth()); + mConfigStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java index 82cdb5a90..5278e1933 100644 --- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java @@ -32,6 +32,7 @@ import com.android.internal.util.FastXmlSerializer; import com.android.server.wifi.SIMAccessor; import com.android.server.wifi.WifiConfigStore; import com.android.server.wifi.WifiKeyStore; +import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import org.junit.Before; import org.junit.Test; @@ -213,7 +214,7 @@ public class PasspointConfigUserStoreDataTest { final XmlSerializer out = new FastXmlSerializer(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); - mConfigStoreData.serializeData(out); + mConfigStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class)); out.flush(); return outputStream.toByteArray(); } @@ -228,7 +229,9 @@ public class PasspointConfigUserStoreDataTest { final XmlPullParser in = Xml.newPullParser(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); - mConfigStoreData.deserializeData(in, in.getDepth()); + mConfigStoreData.deserializeData(in, in.getDepth(), + WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, + mock(WifiConfigStoreEncryptionUtil.class)); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java index 338f7bc40..131425af8 100644 --- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java @@ -268,7 +268,8 @@ public class PasspointManagerTest { * @param expectedConfig The expected installed Passpoint configuration */ private void verifyInstalledConfig(PasspointConfiguration expectedConfig) { - List<PasspointConfiguration> installedConfigs = mManager.getProviderConfigs(); + List<PasspointConfiguration> installedConfigs = + mManager.getProviderConfigs(TEST_CREATOR_UID, true); assertEquals(1, installedConfigs.size()); assertEquals(expectedConfig, installedConfigs.get(0)); } @@ -283,6 +284,7 @@ public class PasspointManagerTest { PasspointProvider provider = mock(PasspointProvider.class); when(provider.installCertsAndKeys()).thenReturn(true); lenient().when(provider.getConfig()).thenReturn(config); + lenient().when(provider.getCreatorUid()).thenReturn(TEST_CREATOR_UID); return provider; } @@ -651,14 +653,14 @@ public class PasspointManagerTest { // Provider index start with 0, should be 1 after adding a provider. assertEquals(1, mSharedDataSource.getProviderIndex()); - // Remove the provider. - assertTrue(mManager.removeProvider(TEST_FQDN)); + // Remove the provider as the creator app. + assertTrue(mManager.removeProvider(TEST_CREATOR_UID, false, TEST_FQDN)); verify(provider).uninstallCertsAndKeys(); verify(mWifiConfigManager).saveToStore(true); verify(mWifiMetrics).incrementNumPasspointProviderUninstallation(); verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess(); verify(mAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class)); - assertTrue(mManager.getProviderConfigs().isEmpty()); + assertTrue(mManager.getProviderConfigs(TEST_CREATOR_UID, false).isEmpty()); // Verify content in the data source. assertTrue(mUserDataSource.getProviders().isEmpty()); @@ -694,13 +696,13 @@ public class PasspointManagerTest { // Provider index start with 0, should be 1 after adding a provider. assertEquals(1, mSharedDataSource.getProviderIndex()); - // Remove the provider. - assertTrue(mManager.removeProvider(TEST_FQDN)); + // Remove the provider as a privileged non-creator app. + assertTrue(mManager.removeProvider(TEST_UID, true, TEST_FQDN)); verify(provider).uninstallCertsAndKeys(); verify(mWifiConfigManager).saveToStore(true); verify(mWifiMetrics).incrementNumPasspointProviderUninstallation(); verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess(); - assertTrue(mManager.getProviderConfigs().isEmpty()); + assertTrue(mManager.getProviderConfigs(TEST_UID, true).isEmpty()); // Verify content in the data source. assertTrue(mUserDataSource.getProviders().isEmpty()); @@ -824,7 +826,7 @@ public class PasspointManagerTest { */ @Test public void removeNonExistingProvider() throws Exception { - assertFalse(mManager.removeProvider(TEST_FQDN)); + assertFalse(mManager.removeProvider(TEST_CREATOR_UID, true, TEST_FQDN)); verify(mWifiMetrics).incrementNumPasspointProviderUninstallation(); verify(mWifiMetrics, never()).incrementNumPasspointProviderUninstallSuccess(); } @@ -1285,8 +1287,8 @@ public class PasspointManagerTest { mUserDataSource.setProviders(providers); // Verify the providers maintained by PasspointManager. - assertEquals(1, mManager.getProviderConfigs().size()); - assertEquals(config, mManager.getProviderConfigs().get(0)); + assertEquals(1, mManager.getProviderConfigs(TEST_CREATOR_UID, true).size()); + assertEquals(config, mManager.getProviderConfigs(TEST_CREATOR_UID, true).get(0)); } /** @@ -1800,7 +1802,7 @@ public class PasspointManagerTest { verify(mAppOpsManager).startWatchingMode(eq(OPSTR_CHANGE_WIFI_STATE), eq(TEST_PACKAGE), mAppOpChangedListenerCaptor.capture()); - assertEquals(1, mManager.getProviderConfigs().size()); + assertEquals(1, mManager.getProviderConfigs(TEST_CREATOR_UID, true).size()); AppOpsManager.OnOpChangedListener listener = mAppOpChangedListenerCaptor.getValue(); assertNotNull(listener); @@ -1814,6 +1816,45 @@ public class PasspointManagerTest { verify(mAppOpsManager).stopWatchingMode(mAppOpChangedListenerCaptor.getValue()); verify(mClientModeImpl).disconnectCommand(); - assertTrue(mManager.getProviderConfigs().isEmpty()); + assertTrue(mManager.getProviderConfigs(TEST_CREATOR_UID, true).isEmpty()); + } + + /** + * Verify that removing a provider with a different UID will not succeed. + * + * @throws Exception + */ + @Test + public void removeGetProviderWithDifferentUid() throws Exception { + PasspointConfiguration config = createTestConfigWithSimCredential(TEST_FQDN, TEST_IMSI, + TEST_REALM); + PasspointProvider provider = createMockProvider(config); + when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore), + eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE))).thenReturn( + provider); + assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE)); + verifyInstalledConfig(config); + verify(mWifiConfigManager).saveToStore(true); + verify(mWifiMetrics).incrementNumPasspointProviderInstallation(); + verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess(); + reset(mWifiMetrics); + reset(mWifiConfigManager); + + // no profiles available for TEST_UID + assertTrue(mManager.getProviderConfigs(TEST_UID, false).isEmpty()); + // 1 profile available for TEST_CREATOR_UID + assertFalse(mManager.getProviderConfigs(TEST_CREATOR_UID, false).isEmpty()); + + // Remove the provider as a non-privileged non-creator app. + assertFalse(mManager.removeProvider(TEST_UID, false, TEST_FQDN)); + verify(provider, never()).uninstallCertsAndKeys(); + verify(mWifiConfigManager, never()).saveToStore(true); + verify(mWifiMetrics).incrementNumPasspointProviderUninstallation(); + verify(mWifiMetrics, never()).incrementNumPasspointProviderUninstallSuccess(); + + // no profiles available for TEST_UID + assertTrue(mManager.getProviderConfigs(TEST_UID, false).isEmpty()); + // 1 profile available for TEST_CREATOR_UID + assertFalse(mManager.getProviderConfigs(TEST_CREATOR_UID, false).isEmpty()); } } diff --git a/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java index 15d9ef915..1091c1dac 100644 --- a/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java +++ b/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java @@ -3812,4 +3812,28 @@ public class WifiP2pServiceImplTest { mLooper.dispatchAll(); verify(mWifiNative).p2pStopFind(); } + + /** + * Verify a network name which is too long is rejected. + */ + @Test + public void testSendConnectMsgWithTooLongNetworkName() throws Exception { + mTestWifiP2pFastConnectionConfig.networkName = "DIRECT-xy-abcdefghijklmnopqrstuvw"; + sendConnectMsg(mClientMessenger, mTestWifiP2pFastConnectionConfig); + verify(mClientHandler).sendMessage(mMessageCaptor.capture()); + Message message = mMessageCaptor.getValue(); + assertEquals(WifiP2pManager.CONNECT_FAILED, message.what); + } + + /** + * Verify a network name which is too short is rejected. + */ + @Test + public void testSendConnectMsgWithTooShortNetworkName() throws Exception { + mTestWifiP2pFastConnectionConfig.networkName = "DIRECT-x"; + sendConnectMsg(mClientMessenger, mTestWifiP2pFastConnectionConfig); + verify(mClientHandler).sendMessage(mMessageCaptor.capture()); + Message message = mMessageCaptor.getValue(); + assertEquals(WifiP2pManager.CONNECT_FAILED, message.what); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java index d2f22da6a..bd0ad321d 100644 --- a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java +++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java @@ -18,6 +18,7 @@ package com.android.server.wifi.rtt; import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -42,6 +43,7 @@ import android.hardware.wifi.V1_0.WifiStatus; import android.hardware.wifi.V1_0.WifiStatusCode; import android.net.MacAddress; import android.net.wifi.rtt.RangingRequest; +import android.net.wifi.rtt.ResponderConfig; import androidx.test.filters.SmallTest; @@ -465,6 +467,28 @@ public class RttNativeTest { } } + /** + * Validation ranging with invalid bw and preamble combination will be ignored. + */ + @Test + public void testRangingWithInvalidParameterCombination() throws Exception { + int cmdId = 88; + RangingRequest request = new RangingRequest.Builder().build(); + ResponderConfig invalidConfig = new ResponderConfig( + MacAddress.fromString("08:09:08:07:06:88"), ResponderConfig.RESPONDER_AP, true, + ResponderConfig.CHANNEL_WIDTH_80MHZ, 0, 0, 0, ResponderConfig.PREAMBLE_HT); + ResponderConfig config = new ResponderConfig(MacAddress.fromString("08:09:08:07:06:89"), + ResponderConfig.RESPONDER_AP, true, + ResponderConfig.CHANNEL_WIDTH_80MHZ, 0, 0, 0, ResponderConfig.PREAMBLE_VHT); + + // Add a ResponderConfig with invalid parameter, should be ignored. + request.mRttPeers.add(invalidConfig); + request.mRttPeers.add(config); + mDut.rangeRequest(cmdId, request, true); + verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture()); + assertEquals(request.mRttPeers.size() - 1, mRttConfigCaptor.getValue().size()); + } + // Utilities /** diff --git a/tests/wifitests/src/com/android/server/wifi/util/DataIntegrityCheckerTest.java b/tests/wifitests/src/com/android/server/wifi/util/DataIntegrityCheckerTest.java deleted file mode 100644 index c281b6440..000000000 --- a/tests/wifitests/src/com/android/server/wifi/util/DataIntegrityCheckerTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wifi.util; - -import static org.junit.Assert.*; - -import org.junit.Ignore; -import org.junit.Test; - -import java.io.File; - -/** - * Unit tests for {@link com.android.server.wifi.util.DataIntegrityChecker}. - */ -public class DataIntegrityCheckerTest { - private static byte[] sGoodData = {1, 2, 3, 4}; - private static byte[] sBadData = {5, 6, 7, 8}; - - /** - * Verify that updating the integrity token with known data and alias will - * pass the integrity test. This test ensure the expected outcome for - * unedited data succeeds. - * - * @throws Exception - */ - @Test - @Ignore - public void testIntegrityWithKnownDataAndKnownAlias() throws Exception { - File integrityFile = File.createTempFile("testIntegrityWithKnownDataAndKnownAlias", - ".tmp"); - DataIntegrityChecker dataIntegrityChecker = new DataIntegrityChecker( - integrityFile.getParent()); - EncryptedData encryptedData = dataIntegrityChecker.compute(sGoodData); - assertTrue(dataIntegrityChecker.isOk(sGoodData, encryptedData)); - } - - /** - * Verify that checking the integrity of unknown data and a known alias - * will fail the integrity test. This test ensure the expected failure for - * altered data, in fact, fails. - * - * - * @throws Exception - */ - @Test - @Ignore - public void testIntegrityWithUnknownDataAndKnownAlias() throws Exception { - File integrityFile = File.createTempFile("testIntegrityWithUnknownDataAndKnownAlias", - ".tmp"); - DataIntegrityChecker dataIntegrityChecker = new DataIntegrityChecker( - integrityFile.getParent()); - EncryptedData encryptedData = dataIntegrityChecker.compute(sGoodData); - assertFalse(dataIntegrityChecker.isOk(sBadData, encryptedData)); - } -} diff --git a/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java index 45adffd1d..266a2ce23 100644 --- a/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java +++ b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java @@ -116,6 +116,82 @@ public class ScanResultUtilTest { assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)); } + /** + * Test that a PSK-SHA256+SAE network is detected as transition mode + */ + @Test + public void testPskSha256SaeTransitionModeCheck() { + final String ssid = "WPA3-Transition"; + String caps = "[WPA2-FT/PSK-CCMP][RSN-FT/PSK+PSK-SHA256+SAE+FT/SAE-CCMP][ESS][WPS]"; + + ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid, + "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0, + 0, true); + + input.informationElements = new InformationElement[] { + createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8)) + }; + + assertTrue(ScanResultUtil.isScanResultForPskSaeTransitionNetwork(input)); + } + + /** + * Test that a PSK+SAE network is detected as transition mode + */ + @Test + public void testPskSaeTransitionModeCheck() { + final String ssid = "WPA3-Transition"; + String caps = "[WPA2-FT/PSK+PSK+SAE+FT/SAE-CCMP][ESS][WPS]"; + + ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid, + "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0, + 0, true); + + input.informationElements = new InformationElement[] { + createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8)) + }; + + assertTrue(ScanResultUtil.isScanResultForPskSaeTransitionNetwork(input)); + } + + /** + * Test that a PSK network is not detected as transition mode + */ + @Test + public void testPskNotInTransitionModeCheck() { + final String ssid = "WPA2-Network"; + String caps = "[WPA2-FT/PSK+PSK][ESS][WPS]"; + + ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid, + "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0, + 0, true); + + input.informationElements = new InformationElement[] { + createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8)) + }; + + assertFalse(ScanResultUtil.isScanResultForPskSaeTransitionNetwork(input)); + } + + /** + * Test that an SAE network is not detected as transition mode + */ + @Test + public void testSaeNotInTransitionModeCheck() { + final String ssid = "WPA3-Network"; + String caps = "[WPA2-FT/SAE+SAE][ESS][WPS]"; + + ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid, + "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0, + 0, true); + + input.informationElements = new InformationElement[] { + createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8)) + }; + + assertFalse(ScanResultUtil.isScanResultForPskSaeTransitionNetwork(input)); + } + private static InformationElement createIE(int id, byte[] bytes) { InformationElement ie = new InformationElement(); ie.id = id; diff --git a/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java index 85b4a9370..8f96bc106 100644 --- a/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java +++ b/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java @@ -35,7 +35,9 @@ import com.android.server.wifi.util.XmlUtil.NetworkSelectionStatusXmlUtil; import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil; import com.android.server.wifi.util.XmlUtil.WifiEnterpriseConfigXmlUtil; +import org.junit.Before; import org.junit.Test; +import org.mockito.MockitoAnnotations; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -73,6 +75,13 @@ public class XmlUtilTest { private static final int TEST_PHASE2_METHOD = WifiEnterpriseConfig.Phase2.MSCHAPV2; private final String mXmlDocHeader = "XmlUtilTest"; + private WifiConfigStoreEncryptionUtil mWifiConfigStoreEncryptionUtil = null; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + /** * Verify that a open WifiConfiguration is serialized & deserialized correctly. */ @@ -101,6 +110,22 @@ public class XmlUtilTest { } /** + * Verify that a psk WifiConfiguration is serialized & deserialized correctly. + */ + @Test + public void testPskWifiConfigurationSerializeDeserializeWithEncryption() + throws IOException, XmlPullParserException { + mWifiConfigStoreEncryptionUtil = mock(WifiConfigStoreEncryptionUtil.class); + WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork(); + EncryptedData encryptedData = new EncryptedData(new byte[0], new byte[0]); + when(mWifiConfigStoreEncryptionUtil.encrypt(pskNetwork.preSharedKey.getBytes())) + .thenReturn(encryptedData); + when(mWifiConfigStoreEncryptionUtil.decrypt(encryptedData)) + .thenReturn(pskNetwork.preSharedKey.getBytes()); + serializeDeserializeWifiConfiguration(pskNetwork); + } + + /** * Verify that a psk hidden WifiConfiguration is serialized & deserialized correctly. */ @Test @@ -382,6 +407,37 @@ public class XmlUtilTest { } /** + * Verify that a WifiEnterpriseConfig object is serialized & deserialized correctly. + */ + @Test + public void testWifiEnterpriseConfigSerializeDeserializeWithEncryption() + throws IOException, XmlPullParserException { + WifiEnterpriseConfig config = new WifiEnterpriseConfig(); + config.setFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, TEST_IDENTITY); + config.setFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, TEST_ANON_IDENTITY); + config.setFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, TEST_PASSWORD); + config.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, TEST_CLIENT_CERT); + config.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, TEST_CA_CERT); + config.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, TEST_SUBJECT_MATCH); + config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, TEST_ENGINE); + config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, TEST_ENGINE_ID); + config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, TEST_PRIVATE_KEY_ID); + config.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, TEST_ALTSUBJECT_MATCH); + config.setFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, TEST_DOM_SUFFIX_MATCH); + config.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, TEST_CA_PATH); + config.setEapMethod(TEST_EAP_METHOD); + config.setPhase2Method(TEST_PHASE2_METHOD); + + mWifiConfigStoreEncryptionUtil = mock(WifiConfigStoreEncryptionUtil.class); + EncryptedData encryptedData = new EncryptedData(new byte[0], new byte[0]); + when(mWifiConfigStoreEncryptionUtil.encrypt(TEST_PASSWORD.getBytes())) + .thenReturn(encryptedData); + when(mWifiConfigStoreEncryptionUtil.decrypt(encryptedData)) + .thenReturn(TEST_PASSWORD.getBytes()); + serializeDeserializeWifiEnterpriseConfig(config); + } + + /** * Verify that an illegal argument exception is thrown when trying to parse out a corrupted * WifiEnterpriseConfig. * @@ -473,7 +529,8 @@ public class XmlUtilTest { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); XmlUtil.writeDocumentStart(out, mXmlDocHeader); - WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, configuration); + WifiConfigurationXmlUtil.writeToXmlForConfigStore( + out, configuration, mWifiConfigStoreEncryptionUtil); XmlUtil.writeDocumentEnd(out, mXmlDocHeader); return outputStream.toByteArray(); } @@ -485,7 +542,10 @@ public class XmlUtilTest { ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); XmlUtil.gotoDocumentStart(in, mXmlDocHeader); - return WifiConfigurationXmlUtil.parseFromXml(in, in.getDepth()); + return WifiConfigurationXmlUtil.parseFromXml( + in, in.getDepth(), + mWifiConfigStoreEncryptionUtil != null, + mWifiConfigStoreEncryptionUtil); } /** @@ -593,7 +653,8 @@ public class XmlUtilTest { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); out.setOutput(outputStream, StandardCharsets.UTF_8.name()); XmlUtil.writeDocumentStart(out, mXmlDocHeader); - WifiEnterpriseConfigXmlUtil.writeToXml(out, config); + WifiEnterpriseConfigXmlUtil.writeToXml( + out, config, mWifiConfigStoreEncryptionUtil); XmlUtil.writeDocumentEnd(out, mXmlDocHeader); return outputStream.toByteArray(); } @@ -604,7 +665,9 @@ public class XmlUtilTest { ByteArrayInputStream inputStream = new ByteArrayInputStream(data); in.setInput(inputStream, StandardCharsets.UTF_8.name()); XmlUtil.gotoDocumentStart(in, mXmlDocHeader); - return WifiEnterpriseConfigXmlUtil.parseFromXml(in, in.getDepth()); + return WifiEnterpriseConfigXmlUtil.parseFromXml( + in, in.getDepth(), mWifiConfigStoreEncryptionUtil != null, + mWifiConfigStoreEncryptionUtil); } private void serializeDeserializeWifiEnterpriseConfig(WifiEnterpriseConfig config) |