DivestOS/Patches/LineageOS-20.0/android_packages_modules_Wifi/ASB-2023-06/0001-Don-t-send-credentials-in-an-unauthenticated-TLS-tun.patch
Tad 0dde119d7e
20.0 June ASB work + churn
QPR3 is delayed a week now

Patches pulled from GrapheneOS and checked against CalyxOS

Signed-off-by: Tad <tad@spotco.us>
2023-06-12 21:06:42 -04:00

1693 lines
85 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jimmy Chen <jimmycmchen@google.com>
Date: Wed, 28 Dec 2022 15:19:08 +0800
Subject: [PATCH 1/3] Don't send credentials in an unauthenticated TLS tunnel
Fix the security vulnerability reported in the security report.
Do not send the user credentials in phase2 before the user
approves the server certificate. This is done by connecting
with no credentials for the purpose of getting the server
certificate chain only, and reconnecting once the user approves
with full certificate chain authentication.
Updated-PDD: TRUE
Bug: 250574778
Bug: 251910611
Test: atest InsecureEapNetworkHandlerTest ClientModeImplTest
Test: Integration test with WPA-Enterprise network with an S
device and a T device.
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:fb630e6a13189321d0e83037adc9e7b30dc6d796)
Merged-In: Ib3501f5e04881b11ea9ab52472dd233f2aa82c7a
Change-Id: Ib3501f5e04881b11ea9ab52472dd233f2aa82c7a
---
.../res/values/overlayable.xml | 24 ++
.../res/values/strings.xml | 3 +
.../android/server/wifi/ClientModeImpl.java | 56 ++-
.../wifi/InsecureEapNetworkHandler.java | 394 +++++++++++++-----
.../android/server/wifi/WifiServiceImpl.java | 7 +-
service/proto/src/metrics.proto | 3 +
.../server/wifi/ClientModeImplTest.java | 136 ++++--
.../wifi/InsecureEapNetworkHandlerTest.java | 342 +++++++++++++--
8 files changed, 745 insertions(+), 220 deletions(-)
diff --git a/service/ServiceWifiResources/res/values/overlayable.xml b/service/ServiceWifiResources/res/values/overlayable.xml
index 2eef3f5156..75c90a3887 100644
--- a/service/ServiceWifiResources/res/values/overlayable.xml
+++ b/service/ServiceWifiResources/res/values/overlayable.xml
@@ -321,6 +321,30 @@
<item type="string" name="wifi_interface_priority_message_plural" />
<item type="string" name="wifi_interface_priority_approve" />
<item type="string" name="wifi_interface_priority_reject" />
+ <item type="string" name="wifi_ca_cert_dialog_title" />
+ <item type="string" name="wifi_ca_cert_dialog_continue_text" />
+ <item type="string" name="wifi_ca_cert_dialog_abort_text" />
+ <item type="string" name="wifi_ca_cert_dialog_message_hint" />
+ <item type="string" name="wifi_ca_cert_dialog_message_server_name_text" />
+ <item type="string" name="wifi_ca_cert_dialog_message_issuer_name_text" />
+ <item type="string" name="wifi_ca_cert_dialog_message_organization_text" />
+ <item type="string" name="wifi_ca_cert_dialog_message_contact_text" />
+ <item type="string" name="wifi_ca_cert_dialog_message_signature_name_text" />
+ <item type="string" name="wifi_ca_cert_notification_title" />
+ <item type="string" name="wifi_ca_cert_notification_message" />
+ <item type="string" name="wifi_ca_cert_failed_to_install_ca_cert" />
+ <item type="string" name="wifi_ca_cert_dialog_preT_title" />
+ <item type="string" name="wifi_ca_cert_dialog_preT_continue_text" />
+ <item type="string" name="wifi_ca_cert_dialog_preT_abort_text" />
+ <item type="string" name="wifi_ca_cert_dialog_preT_message_hint" />
+ <item type="string" name="wifi_ca_cert_dialog_preT_message_link" />
+ <item type="string" name="wifi_ca_cert_notification_preT_title" />
+ <item type="string" name="wifi_ca_cert_notification_preT_message" />
+ <item type="string" name="wifi_ca_cert_notification_preT_continue_text" />
+ <item type="string" name="wifi_ca_cert_notification_preT_abort_text" />
+ <item type="string" name="wifi_tofu_invalid_cert_chain_title" />
+ <item type="string" name="wifi_tofu_invalid_cert_chain_message" />
+ <item type="string" name="wifi_tofu_invalid_cert_chain_ok_text" />
<!-- Params from strings.xml that can be overlayed -->
<!-- Params from styles.xml that can be overlayed -->
diff --git a/service/ServiceWifiResources/res/values/strings.xml b/service/ServiceWifiResources/res/values/strings.xml
index 15ffcf0ada..db331b2e7b 100644
--- a/service/ServiceWifiResources/res/values/strings.xml
+++ b/service/ServiceWifiResources/res/values/strings.xml
@@ -205,6 +205,9 @@
<string name="wifi_ca_cert_notification_title">Network needs to be verified</string>
<string name="wifi_ca_cert_notification_message">Review network details for <xliff:g id="ssid">%1$s</xliff:g> before connecting. Tap to continue.</string>
<string name="wifi_ca_cert_failed_to_install_ca_cert">Certificate installation failed.</string>
+ <string name="wifi_tofu_invalid_cert_chain_title">Can\'t connect to <xliff:g id="value">%1$s</xliff:g></string>
+ <string name="wifi_tofu_invalid_cert_chain_message">The server certificate chain is invalid.</string>
+ <string name="wifi_tofu_invalid_cert_chain_ok_text">OK</string>
<!-- Legacy EAP network dialog and notification text on Pre-T devices. -->
<string name="wifi_ca_cert_dialog_preT_title">Can\'t verify this network</string>
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 931374bccb..9ec12bdad8 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -580,9 +580,6 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
@VisibleForTesting
static final int CMD_ACCEPT_EAP_SERVER_CERTIFICATE = BASE + 301;
- @VisibleForTesting
- static final int CMD_REJECT_EAP_SERVER_CERTIFICATE = BASE + 302;
-
/* Tracks if suspend optimizations need to be disabled by DHCP,
* screen or due to high perf mode.
* When any of them needs to disable it, we keep the suspend optimizations
@@ -837,17 +834,11 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
@Override
public void onReject(String ssid) {
log("Reject Root CA cert for " + ssid);
- sendMessage(CMD_REJECT_EAP_SERVER_CERTIFICATE,
- WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_REJECTED_BY_USER,
- 0, ssid);
}
@Override
public void onError(String ssid) {
log("Insecure EAP network error for " + ssid);
- sendMessage(CMD_REJECT_EAP_SERVER_CERTIFICATE,
- WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_EAP_FAILURE,
- 0, ssid);
}};
mInsecureEapNetworkHandler = new InsecureEapNetworkHandler(
mContext,
@@ -2261,8 +2252,6 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
return "CMD_UPDATE_LINKPROPERTIES";
case CMD_ACCEPT_EAP_SERVER_CERTIFICATE:
return "CMD_ACCEPT_EAP_SERVER_CERTIFICATE";
- case CMD_REJECT_EAP_SERVER_CERTIFICATE:
- return "CMD_REJECT_EAP_SERVER_CERTIFICATE";
case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
return "SUPPLICANT_STATE_CHANGE_EVENT";
case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
@@ -4021,8 +4010,21 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
break;
}
}
- mInsecureEapNetworkHandler.prepareConnection(mTargetWifiConfiguration);
setSelectedRcoiForPasspoint(config);
+ if (mInsecureEapNetworkHandler.prepareConnection(mTargetWifiConfiguration)) {
+ /* If TOFU is not supported and the user did not approve to connect to an
+ insecure network before, do not connect now and instead, display a dialog
+ or a notification, and keep network disconnected to avoid sending the
+ credentials.
+ */
+ mInsecureEapNetworkHandler.startUserApprovalIfNecessary(mIsUserSelected);
+ reportConnectionAttemptEnd(
+ WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
+ WifiMetricsProto.ConnectionEvent.HLF_NONE,
+ WifiMetricsProto.ConnectionEvent.DISCONNECTED_USER_APPROVAL_NEEDED);
+ transitionTo(mDisconnectedState);
+ break;
+ }
connectToNetwork(config);
break;
}
@@ -4277,7 +4279,6 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
break;
}
case CMD_ACCEPT_EAP_SERVER_CERTIFICATE:
- case CMD_REJECT_EAP_SERVER_CERTIFICATE:
case CMD_START_ROAM:
case CMD_START_RSSI_MONITORING_OFFLOAD:
case CMD_STOP_RSSI_MONITORING_OFFLOAD:
@@ -5384,11 +5385,16 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
}
case WifiMonitor.TOFU_ROOT_CA_CERTIFICATE:
if (null == mTargetWifiConfiguration) break;
- if (!mInsecureEapNetworkHandler.setPendingCertificate(
+ int certificateDepth = message.arg2;
+ if (!mInsecureEapNetworkHandler.addPendingCertificate(
mTargetWifiConfiguration.SSID, message.arg2,
(X509Certificate) message.obj)) {
Log.d(TAG, "Cannot set pending cert.");
}
+ // Launch user approval upon receiving the server certificate
+ if (certificateDepth == 0) {
+ mInsecureEapNetworkHandler.startUserApprovalIfNecessary(mIsUserSelected);
+ }
break;
default: {
handleStatus = NOT_HANDLED;
@@ -5867,10 +5873,6 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
class L3ProvisioningState extends State {
@Override
public void enter() {
- if (mInsecureEapNetworkHandler.startUserApprovalIfNecessary(mIsUserSelected)) {
- return;
- }
-
startL3Provisioning();
}
@@ -5890,18 +5892,6 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
handleStatus = NOT_HANDLED;
break;
}
- case CMD_ACCEPT_EAP_SERVER_CERTIFICATE:
- startL3Provisioning();
- break;
- case CMD_REJECT_EAP_SERVER_CERTIFICATE: {
- int l2FailureReason = message.arg1;
- reportConnectionAttemptEnd(
- WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
- WifiMetricsProto.ConnectionEvent.HLF_NONE,
- l2FailureReason);
- mWifiNative.disconnect(mInterfaceName);
- break;
- }
default: {
handleStatus = NOT_HANDLED;
break;
@@ -6421,6 +6411,12 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
}
break;
}
+ case CMD_ACCEPT_EAP_SERVER_CERTIFICATE:
+ // Got an approval for a TOFU network, trigger a scan to accelerate the
+ // auto-connection.
+ logd("User accepted TOFU provided certificate");
+ mWifiConnectivityManager.forceConnectivityScan(ClientModeImpl.WIFI_WORK_SOURCE);
+ break;
default: {
handleStatus = NOT_HANDLED;
break;
diff --git a/service/java/com/android/server/wifi/InsecureEapNetworkHandler.java b/service/java/com/android/server/wifi/InsecureEapNetworkHandler.java
index 225d01c7e5..b7389952e0 100644
--- a/service/java/com/android/server/wifi/InsecureEapNetworkHandler.java
+++ b/service/java/com/android/server/wifi/InsecureEapNetworkHandler.java
@@ -40,6 +40,8 @@ import com.android.server.wifi.util.NativeUtil;
import com.android.wifi.resources.R;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
/** This class is used to handle insecure EAP networks. */
public class InsecureEapNetworkHandler {
@@ -71,18 +73,27 @@ public class InsecureEapNetworkHandler {
private final String mInterfaceName;
private final Handler mHandler;
+ // The latest connecting configuration from the caller, it is updated on calling
+ // prepareConnection() always. This is used to ensure that current TOFU config is aligned
+ // with the caller connecting config.
@NonNull
- private WifiConfiguration mCurConfig = null;
- private int mPendingCaCertDepth = -1;
+ private WifiConfiguration mConnectingConfig = null;
+ // The connecting configuration which is a valid TOFU configuration, it is updated
+ // only when the connecting configuration is a valid TOFU configuration and used
+ // by later TOFU procedure.
+ @NonNull
+ private WifiConfiguration mCurrentTofuConfig = null;
+ private int mPendingRootCaCertDepth = -1;
@Nullable
- private X509Certificate mPendingCaCert = null;
+ private X509Certificate mPendingRootCaCert = null;
@Nullable
private X509Certificate mPendingServerCert = null;
- // This is updated on setting a pending CA cert.
- private CertificateSubjectInfo mPendingCaCertSubjectInfo = null;
- // This is updated on setting a pending CA cert.
- private CertificateSubjectInfo mPendingCaCertIssuerInfo = null;
- @Nullable
+ // This is updated on setting a pending server cert.
+ private CertificateSubjectInfo mPendingServerCertSubjectInfo = null;
+ // This is updated on setting a pending server cert.
+ private CertificateSubjectInfo mPendingServerCertIssuerInfo = null;
+ // Record the whole server cert chain from Root CA to the server cert.
+ private List<X509Certificate> mServerCertChain = new ArrayList<>();
private WifiDialogManager.DialogHandle mTofuAlertDialog = null;
private boolean mIsCertNotificationReceiverRegistered = false;
@@ -137,16 +148,17 @@ public class InsecureEapNetworkHandler {
* uses Server Cert, without a valid Root CA certificate or user approval.
*
* @param config the running wifi configuration.
+ * @return true if user needs to be notified about an insecure network but TOFU is not supported
+ * by the device, or false otherwise.
*/
- public void prepareConnection(@NonNull WifiConfiguration config) {
- if (null == config) return;
+ public boolean prepareConnection(@NonNull WifiConfiguration config) {
+ if (null == config) return false;
+ mConnectingConfig = config;
- if (!config.isEnterprise()) return;
+ if (!config.isEnterprise()) return false;
WifiEnterpriseConfig entConfig = config.enterpriseConfig;
- if (!entConfig.isEapMethodServerCertUsed()) return;
- if (entConfig.hasCaCertificate()) return;
-
- clearConnection();
+ if (!entConfig.isEapMethodServerCertUsed()) return false;
+ if (entConfig.hasCaCertificate()) return false;
Log.d(TAG, "prepareConnection: isTofuSupported=" + mIsTrustOnFirstUseSupported
+ ", isInsecureEapNetworkAllowed=" + mIsInsecureEnterpriseConfigurationAllowed
@@ -155,71 +167,115 @@ public class InsecureEapNetworkHandler {
// If TOFU is not supported or insecure EAP network is allowed without TOFU enabled,
// return to skip the dialog if this network is approved before.
if (entConfig.isUserApproveNoCaCert()) {
- if (!mIsTrustOnFirstUseSupported) return;
+ if (!mIsTrustOnFirstUseSupported) return false;
if (mIsInsecureEnterpriseConfigurationAllowed
&& !entConfig.isTrustOnFirstUseEnabled()) {
- return;
+ return false;
}
}
- mCurConfig = config;
+ if (mIsTrustOnFirstUseSupported) {
+ /**
+ * Clear the user credentials from this copy of the configuration object.
+ * Supplicant will start the phase-1 TLS session to acquire the server certificate chain
+ * which will be provided to the framework. Then since the callbacks for identity and
+ * password requests are not populated, it will fail the connection and disconnect.
+ * This will allow the user to review the certificates at their own pace, and a
+ * reconnection would automatically take place with full verification of the chain once
+ * they approve.
+ */
+ if (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS
+ || config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP) {
+ config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+ config.enterpriseConfig.setIdentity(null);
+ config.enterpriseConfig.setPassword(null);
+ } else if (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS) {
+ config.enterpriseConfig.setClientCertificateAlias(null);
+ }
+ }
+ mCurrentTofuConfig = config;
+ mServerCertChain.clear();
+ dismissDialogAndNotification();
registerCertificateNotificationReceiver();
- // Remove cached PMK in the framework and supplicant to avoid
- // skipping the EAP flow.
- clearNativeData();
- Log.d(TAG, "Remove native cached data and networks for TOFU.");
+ if (!mIsTrustOnFirstUseSupported) {
+ /**
+ * Devices with no TOFU support, do not connect to the network until the user is
+ * aware that the network is insecure, and approves the connection.
+ */
+ putNetworkOnHold(false);
+ } else {
+ // Remove cached PMK in the framework and supplicant to avoid skipping the EAP flow.
+ clearNativeData();
+ Log.d(TAG, "Remove native cached data and networks for TOFU.");
+ }
+ return !mIsTrustOnFirstUseSupported;
}
- /** Clear data on disconnecting a connection. */
- private void clearConnection() {
- unregisterCertificateNotificationReceiver();
+ /**
+ * Do necessary clean up on stopping client mode.
+ */
+ public void cleanup() {
dismissDialogAndNotification();
+ unregisterCertificateNotificationReceiver();
clearInternalData();
}
/**
- * Store the received certifiate for later use.
+ * Stores a received certificate for later use.
*
* @param ssid the target network SSID.
* @param depth the depth of this cert. The Root CA should be 0 or
* a positive number, and the server cert is 0.
- * @param cert the Root CA certificate from the server.
+ * @param cert a certificate from the server.
* @return true if the cert is cached; otherwise, false.
*/
- public boolean setPendingCertificate(@NonNull String ssid, int depth,
+ public boolean addPendingCertificate(@NonNull String ssid, int depth,
@NonNull X509Certificate cert) {
+ String configProfileKey = mCurrentTofuConfig != null
+ ? mCurrentTofuConfig.getProfileKey() : "null";
Log.d(TAG, "setPendingCertificate: " + "ssid=" + ssid + " depth=" + depth
- + " current config=" + mCurConfig);
+ + " current config=" + configProfileKey);
if (TextUtils.isEmpty(ssid)) return false;
- if (null == mCurConfig) return false;
- if (!TextUtils.equals(ssid, mCurConfig.SSID)) return false;
+ if (null == mCurrentTofuConfig) return false;
+ if (!TextUtils.equals(ssid, mCurrentTofuConfig.SSID)) return false;
if (null == cert) return false;
if (depth < 0) return false;
+
+ if (depth == 0) {
+ // Disable network selection upon receiving the server certificate
+ putNetworkOnHold(true);
+ }
+
+ if (!mServerCertChain.contains(cert)) {
+ mServerCertChain.add(cert);
+ }
+
// 0 is the tail, i.e. the server cert.
if (depth == 0 && null == mPendingServerCert) {
mPendingServerCert = cert;
Log.d(TAG, "Pending server certificate: " + mPendingServerCert);
+ mPendingServerCertSubjectInfo = CertificateSubjectInfo.parse(
+ cert.getSubjectX500Principal().getName());
+ if (null == mPendingServerCertSubjectInfo) {
+ Log.e(TAG, "CA cert has no valid subject.");
+ return false;
+ }
+ mPendingServerCertIssuerInfo = CertificateSubjectInfo.parse(
+ cert.getIssuerX500Principal().getName());
+ if (null == mPendingServerCertIssuerInfo) {
+ Log.e(TAG, "CA cert has no valid issuer.");
+ return false;
+ }
}
- if (depth < mPendingCaCertDepth) {
+
+ // Root or intermediate cert.
+ if (depth < mPendingRootCaCertDepth) {
Log.d(TAG, "Ignore intermediate cert." + cert);
return true;
}
-
- mPendingCaCertSubjectInfo = CertificateSubjectInfo.parse(
- cert.getSubjectDN().getName());
- if (null == mPendingCaCertSubjectInfo) {
- Log.e(TAG, "CA cert has no valid subject.");
- return false;
- }
- mPendingCaCertIssuerInfo = CertificateSubjectInfo.parse(
- cert.getIssuerDN().getName());
- if (null == mPendingCaCertIssuerInfo) {
- Log.e(TAG, "CA cert has no valid issuer.");
- return false;
- }
- mPendingCaCertDepth = depth;
- mPendingCaCert = cert;
- Log.d(TAG, "Pending Root CA certificate: " + mPendingCaCert);
+ mPendingRootCaCertDepth = depth;
+ mPendingRootCaCert = cert;
+ Log.d(TAG, "Pending Root CA certificate: " + mPendingRootCaCert);
return true;
}
@@ -244,31 +300,89 @@ public class InsecureEapNetworkHandler {
* @return true if the user approval is needed; otherwise, false.
*/
public boolean startUserApprovalIfNecessary(boolean isUserSelected) {
- if (null == mCurConfig) return false;
- if (!mCurConfig.isEnterprise()) return false;
- WifiEnterpriseConfig entConfig = mCurConfig.enterpriseConfig;
- if (!entConfig.isEapMethodServerCertUsed()) return false;
- if (entConfig.hasCaCertificate()) return false;
+ if (null == mConnectingConfig || null == mCurrentTofuConfig) return false;
+ if (mConnectingConfig.networkId != mCurrentTofuConfig.networkId) return false;
// If Trust On First Use is supported and insecure enterprise configuration
// is not allowed, TOFU must be used for an Enterprise network without certs.
if (mIsTrustOnFirstUseSupported && !mIsInsecureEnterpriseConfigurationAllowed
- && !mCurConfig.enterpriseConfig.isTrustOnFirstUseEnabled()) {
+ && !mCurrentTofuConfig.enterpriseConfig.isTrustOnFirstUseEnabled()) {
Log.d(TAG, "Trust On First Use is not enabled.");
- handleError(mCurConfig.SSID);
+ handleError(mCurrentTofuConfig.SSID);
return true;
}
if (useTrustOnFirstUse()) {
- if (null == mPendingCaCert) {
- Log.d(TAG, "No valid CA cert for TLS-based connection.");
- handleError(mCurConfig.SSID);
+ if (null == mPendingRootCaCert) {
+ Log.e(TAG, "No valid CA cert for TLS-based connection.");
+ handleError(mCurrentTofuConfig.SSID);
return true;
} else if (null == mPendingServerCert) {
- Log.d(TAG, "No valid Server cert for TLS-based connection.");
- handleError(mCurConfig.SSID);
+ Log.e(TAG, "No valid Server cert for TLS-based connection.");
+ handleError(mCurrentTofuConfig.SSID);
+ return true;
+ } else if (!isServerCertChainValid()) {
+ Log.e(TAG, "Server cert chain is invalid.");
+ String title = mContext.getString(R.string.wifi_tofu_invalid_cert_chain_title,
+ mCurrentTofuConfig.SSID);
+ String message = mContext.getString(R.string.wifi_tofu_invalid_cert_chain_message);
+ String okButtonText = mContext.getString(
+ R.string.wifi_tofu_invalid_cert_chain_ok_text);
+
+ handleError(mCurrentTofuConfig.SSID);
+
+ if (TextUtils.isEmpty(title) || TextUtils.isEmpty(message)) return true;
+
+ if (isUserSelected) {
+ mTofuAlertDialog = mWifiDialogManager.createSimpleDialog(
+ title,
+ message,
+ null /* positiveButtonText */,
+ null /* negativeButtonText */,
+ okButtonText,
+ new WifiDialogManager.SimpleDialogCallback() {
+ @Override
+ public void onPositiveButtonClicked() {
+ // Not used.
+ }
+
+ @Override
+ public void onNegativeButtonClicked() {
+ // Not used.
+ }
+
+ @Override
+ public void onNeutralButtonClicked() {
+ // Not used.
+ }
+
+ @Override
+ public void onCancelled() {
+ // Not used.
+ }
+ },
+ new WifiThreadRunner(mHandler));
+ mTofuAlertDialog.launchDialog();
+ } else {
+ Notification.Builder builder = mFacade.makeNotificationBuilder(mContext,
+ WifiService.NOTIFICATION_NETWORK_ALERTS)
+ .setSmallIcon(
+ Icon.createWithResource(mContext.getWifiOverlayApkPkgName(),
+ com.android.wifi.resources.R
+ .drawable.stat_notify_wifi_in_range))
+ .setContentTitle(title)
+ .setContentText(message)
+ .setStyle(new Notification.BigTextStyle().bigText(message))
+ .setColor(mContext.getResources().getColor(
+ android.R.color.system_notification_accent_color));
+ mNotificationManager.notify(SystemMessage.NOTE_SERVER_CA_CERTIFICATE,
+ builder.build());
+ }
return true;
}
+ } else if (mIsInsecureEnterpriseConfigurationAllowed) {
+ Log.i(TAG, "networks without the server cert are allowed, skip it.");
+ return false;
}
Log.d(TAG, "startUserApprovalIfNecessaryForInsecureEapNetwork: mIsUserSelected="
@@ -282,13 +396,56 @@ public class InsecureEapNetworkHandler {
return true;
}
+ /**
+ * Disable network selection, disconnect if necessary, and clear PMK cache
+ */
+ private void putNetworkOnHold(boolean needToDisconnect) {
+ // Disable network selection upon receiving the server certificate
+ mWifiConfigManager.updateNetworkSelectionStatus(mCurrentTofuConfig.networkId,
+ WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER);
+
+ // Force disconnect and clear PMK cache to avoid supplicant reconnection
+ if (needToDisconnect) mWifiNative.disconnect(mInterfaceName);
+ clearNativeData();
+ }
+
+ private boolean isServerCertChainValid() {
+ if (mServerCertChain.size() == 0) return false;
+
+ X509Certificate parentCert = null;
+ for (X509Certificate cert: mServerCertChain) {
+ String subject = cert.getSubjectX500Principal().getName();
+ String issuer = cert.getIssuerX500Principal().getName();
+ boolean isCa = cert.getBasicConstraints() >= 0;
+ Log.d(TAG, "Subject: " + subject + ", Issuer: " + issuer + ", isCA: " + isCa);
+
+ if (parentCert == null) {
+ // The root cert, it should be a CA cert or a self-signed cert.
+ if (!isCa && !subject.equals(issuer)) {
+ Log.e(TAG, "The root cert is not a CA cert or a self-signed cert.");
+ return false;
+ }
+ } else {
+ // The issuer of intermediate cert of the leaf cert should be
+ // the same as the subject of its parent cert.
+ if (!parentCert.getSubjectX500Principal().getName().equals(issuer)) {
+ Log.e(TAG, "The issuer does not match the subject of its parent.");
+ return false;
+ }
+ }
+ parentCert = cert;
+ }
+ return true;
+ }
+
private boolean useTrustOnFirstUse() {
return mIsTrustOnFirstUseSupported
- && mCurConfig.enterpriseConfig.isTrustOnFirstUseEnabled();
+ && mCurrentTofuConfig.enterpriseConfig.isTrustOnFirstUseEnabled();
}
private void registerCertificateNotificationReceiver() {
- if (mIsCertNotificationReceiverRegistered) return;
+ unregisterCertificateNotificationReceiver();
IntentFilter filter = new IntentFilter();
if (useTrustOnFirstUse()) {
@@ -313,21 +470,22 @@ public class InsecureEapNetworkHandler {
if (!isConnectionValid(ssid)) return;
if (!useTrustOnFirstUse()) {
- mWifiConfigManager.setUserApproveNoCaCert(mCurConfig.networkId, true);
+ mWifiConfigManager.setUserApproveNoCaCert(mCurrentTofuConfig.networkId, true);
} else {
- if (null == mPendingCaCert || null == mPendingServerCert) {
+ if (null == mPendingRootCaCert || null == mPendingServerCert) {
handleError(ssid);
return;
}
if (!mWifiConfigManager.updateCaCertificate(
- mCurConfig.networkId, mPendingCaCert, mPendingServerCert)) {
+ mCurrentTofuConfig.networkId, mPendingRootCaCert, mPendingServerCert)) {
// The user approved this network,
// keep the connection regardless of the result.
- Log.e(TAG, "Cannot update CA cert to network " + mCurConfig.getProfileKey()
- + ", CA cert = " + mPendingCaCert);
+ Log.e(TAG, "Cannot update CA cert to network " + mCurrentTofuConfig.getProfileKey()
+ + ", CA cert = " + mPendingRootCaCert);
}
}
- mWifiConfigManager.allowAutojoin(mCurConfig.networkId, true);
+ mWifiConfigManager.updateNetworkSelectionStatus(mCurrentTofuConfig.networkId,
+ WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE);
dismissDialogAndNotification();
clearInternalData();
@@ -338,7 +496,8 @@ public class InsecureEapNetworkHandler {
void handleReject(@NonNull String ssid) {
if (!isConnectionValid(ssid)) return;
- mWifiConfigManager.allowAutojoin(mCurConfig.networkId, false);
+ mWifiConfigManager.updateNetworkSelectionStatus(mCurrentTofuConfig.networkId,
+ WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
dismissDialogAndNotification();
clearInternalData();
clearNativeData();
@@ -347,6 +506,11 @@ public class InsecureEapNetworkHandler {
}
private void handleError(@Nullable String ssid) {
+ if (mCurrentTofuConfig != null) {
+ mWifiConfigManager.updateNetworkSelectionStatus(mCurrentTofuConfig.networkId,
+ WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER);
+ }
dismissDialogAndNotification();
clearInternalData();
clearNativeData();
@@ -355,9 +519,9 @@ public class InsecureEapNetworkHandler {
}
private void askForUserApprovalForCaCertificate() {
- if (mCurConfig == null || TextUtils.isEmpty(mCurConfig.SSID)) return;
+ if (mCurrentTofuConfig == null || TextUtils.isEmpty(mCurrentTofuConfig.SSID)) return;
if (useTrustOnFirstUse()) {
- if (null == mPendingCaCert || null == mPendingServerCert) {
+ if (null == mPendingRootCaCert || null == mPendingServerCert) {
Log.e(TAG, "Cannot launch a dialog for TOFU without "
+ "a valid pending CA certificate.");
return;
@@ -375,39 +539,42 @@ public class InsecureEapNetworkHandler {
? mContext.getString(R.string.wifi_ca_cert_dialog_abort_text)
: mContext.getString(R.string.wifi_ca_cert_dialog_preT_abort_text);
- String message = null;
+ String message;
String messageUrl = null;
int messageUrlStart = 0;
int messageUrlEnd = 0;
if (useTrustOnFirstUse()) {
- String signature = NativeUtil.hexStringFromByteArray(
- mPendingCaCert.getSignature());
StringBuilder contentBuilder = new StringBuilder()
.append(mContext.getString(R.string.wifi_ca_cert_dialog_message_hint))
.append(mContext.getString(
R.string.wifi_ca_cert_dialog_message_server_name_text,
- mPendingCaCertSubjectInfo.commonName))
+ mPendingServerCertSubjectInfo.commonName))
.append(mContext.getString(
R.string.wifi_ca_cert_dialog_message_issuer_name_text,
- mPendingCaCertIssuerInfo.commonName));
- if (!TextUtils.isEmpty(mPendingCaCertSubjectInfo.organization)) {
+ mPendingServerCertIssuerInfo.commonName));
+ if (!TextUtils.isEmpty(mPendingServerCertSubjectInfo.organization)) {
contentBuilder.append(mContext.getString(
R.string.wifi_ca_cert_dialog_message_organization_text,
- mPendingCaCertSubjectInfo.organization));
+ mPendingServerCertSubjectInfo.organization));
}
- if (!TextUtils.isEmpty(mPendingCaCertSubjectInfo.email)) {
+ if (!TextUtils.isEmpty(mPendingServerCertSubjectInfo.email)) {
contentBuilder.append(mContext.getString(
R.string.wifi_ca_cert_dialog_message_contact_text,
- mPendingCaCertSubjectInfo.email));
+ mPendingServerCertSubjectInfo.email));
+ }
+ byte[] signature = mPendingServerCert.getSignature();
+ if (signature != null) {
+ String signatureString = NativeUtil.hexStringFromByteArray(signature);
+ if (signatureString.length() > 16) {
+ signatureString = signatureString.substring(0, 16);
+ }
+ contentBuilder.append(mContext.getString(
+ R.string.wifi_ca_cert_dialog_message_signature_name_text, signatureString));
}
- contentBuilder
- .append(mContext.getString(
- R.string.wifi_ca_cert_dialog_message_signature_name_text,
- signature.substring(0, 16)));
message = contentBuilder.toString();
} else {
String hint = mContext.getString(
- R.string.wifi_ca_cert_dialog_preT_message_hint, mCurConfig.SSID);
+ R.string.wifi_ca_cert_dialog_preT_message_hint, mCurrentTofuConfig.SSID);
String linkText = mContext.getString(
R.string.wifi_ca_cert_dialog_preT_message_link);
message = hint + " " + linkText;
@@ -427,23 +594,35 @@ public class InsecureEapNetworkHandler {
new WifiDialogManager.SimpleDialogCallback() {
@Override
public void onPositiveButtonClicked() {
- handleAccept(mCurConfig.SSID);
+ if (mCurrentTofuConfig == null) {
+ return;
+ }
+ handleAccept(mCurrentTofuConfig.SSID);
}
@Override
public void onNegativeButtonClicked() {
- handleReject(mCurConfig.SSID);
+ if (mCurrentTofuConfig == null) {
+ return;
+ }
+ handleReject(mCurrentTofuConfig.SSID);
}
@Override
public void onNeutralButtonClicked() {
// Not used.
- handleReject(mCurConfig.SSID);
+ if (mCurrentTofuConfig == null) {
+ return;
+ }
+ handleReject(mCurrentTofuConfig.SSID);
}
@Override
public void onCancelled() {
- handleReject(mCurConfig.SSID);
+ if (mCurrentTofuConfig == null) {
+ return;
+ }
+ handleReject(mCurrentTofuConfig.SSID);
}
},
new WifiThreadRunner(mHandler));
@@ -460,16 +639,16 @@ public class InsecureEapNetworkHandler {
}
private void notifyUserForCaCertificate() {
- if (mCurConfig == null) return;
+ if (mCurrentTofuConfig == null) return;
if (useTrustOnFirstUse()) {
- if (null == mPendingCaCert) return;
+ if (null == mPendingRootCaCert) return;
if (null == mPendingServerCert) return;
}
dismissDialogAndNotification();
PendingIntent tapPendingIntent;
if (useTrustOnFirstUse()) {
- tapPendingIntent = genCaCertNotifIntent(ACTION_CERT_NOTIF_TAP, mCurConfig.SSID);
+ tapPendingIntent = genCaCertNotifIntent(ACTION_CERT_NOTIF_TAP, mCurrentTofuConfig.SSID);
} else {
Intent openLinkIntent = new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(mCaCertHelpLink))
@@ -482,9 +661,10 @@ public class InsecureEapNetworkHandler {
? mContext.getString(R.string.wifi_ca_cert_notification_title)
: mContext.getString(R.string.wifi_ca_cert_notification_preT_title);
String content = useTrustOnFirstUse()
- ? mContext.getString(R.string.wifi_ca_cert_notification_message, mCurConfig.SSID)
+ ? mContext.getString(R.string.wifi_ca_cert_notification_message,
+ mCurrentTofuConfig.SSID)
: mContext.getString(R.string.wifi_ca_cert_notification_preT_message,
- mCurConfig.SSID);
+ mCurrentTofuConfig.SSID);
Notification.Builder builder = mFacade.makeNotificationBuilder(mContext,
WifiService.NOTIFICATION_NETWORK_ALERTS)
.setSmallIcon(Icon.createWithResource(mContext.getWifiOverlayApkPkgName(),
@@ -502,11 +682,13 @@ public class InsecureEapNetworkHandler {
Notification.Action acceptAction = new Notification.Action.Builder(
null /* icon */,
mContext.getString(R.string.wifi_ca_cert_dialog_preT_continue_text),
- genCaCertNotifIntent(ACTION_CERT_NOTIF_ACCEPT, mCurConfig.SSID)).build();
+ genCaCertNotifIntent(ACTION_CERT_NOTIF_ACCEPT, mCurrentTofuConfig.SSID))
+ .build();
Notification.Action rejectAction = new Notification.Action.Builder(
null /* icon */,
mContext.getString(R.string.wifi_ca_cert_dialog_preT_abort_text),
- genCaCertNotifIntent(ACTION_CERT_NOTIF_REJECT, mCurConfig.SSID)).build();
+ genCaCertNotifIntent(ACTION_CERT_NOTIF_REJECT, mCurrentTofuConfig.SSID))
+ .build();
builder.addAction(rejectAction).addAction(acceptAction);
}
mNotificationManager.notify(SystemMessage.NOTE_SERVER_CA_CERTIFICATE, builder.build());
@@ -521,18 +703,18 @@ public class InsecureEapNetworkHandler {
}
private void clearInternalData() {
- mPendingCaCertDepth = -1;
- mPendingCaCert = null;
+ mPendingRootCaCertDepth = -1;
+ mPendingRootCaCert = null;
mPendingServerCert = null;
- mPendingCaCertSubjectInfo = null;
- mPendingCaCertIssuerInfo = null;
- mCurConfig = null;
+ mPendingServerCertSubjectInfo = null;
+ mPendingServerCertIssuerInfo = null;
+ mCurrentTofuConfig = null;
}
private void clearNativeData() {
// PMK should be cleared or it would skip EAP flow next time.
- if (null != mCurConfig) {
- mWifiNative.removeNetworkCachedData(mCurConfig.networkId);
+ if (null != mCurrentTofuConfig) {
+ mWifiNative.removeNetworkCachedData(mCurrentTofuConfig.networkId);
}
// remove network so that supplicant's PMKSA cache is cleared
mWifiNative.removeAllNetworks(mInterfaceName);
@@ -551,13 +733,13 @@ public class InsecureEapNetworkHandler {
// If condition #2 occurs, clear existing data and notify the client mode
// via onError callback.
private boolean isConnectionValid(@Nullable String ssid) {
- if (TextUtils.isEmpty(ssid) || null == mCurConfig) {
+ if (TextUtils.isEmpty(ssid) || null == mCurrentTofuConfig) {
handleError(null);
return false;
}
- if (!TextUtils.equals(ssid, mCurConfig.SSID)) {
- Log.w(TAG, "Target SSID " + mCurConfig.SSID
+ if (!TextUtils.equals(ssid, mCurrentTofuConfig.SSID)) {
+ Log.w(TAG, "Target SSID " + mCurrentTofuConfig.SSID
+ " is different from TOFU returned SSID" + ssid);
return false;
}
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 40acc85cc5..b044508eb7 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -528,9 +528,6 @@ public class WifiServiceImpl extends BaseWifiService {
if (!mWifiConfigManager.loadFromStore()) {
Log.e(TAG, "Failed to load from config store");
}
- if (!mWifiGlobals.isInsecureEnterpriseConfigurationAllowed()) {
- mWifiConfigManager.updateTrustOnFirstUseFlag(isTrustOnFirstUseSupported());
- }
mWifiConfigManager.incrementNumRebootsSinceLastUse();
// config store is read, check if verbose logging is enabled.
enableVerboseLoggingInternal(
@@ -795,6 +792,10 @@ public class WifiServiceImpl extends BaseWifiService {
mLohsSoftApTracker.handleBootCompleted();
mWifiInjector.getSarManager().handleBootCompleted();
mIsBootComplete = true;
+ // HW capabilities is ready after boot completion.
+ if (!mWifiGlobals.isInsecureEnterpriseConfigurationAllowed()) {
+ mWifiConfigManager.updateTrustOnFirstUseFlag(isTrustOnFirstUseSupported());
+ }
});
}
diff --git a/service/proto/src/metrics.proto b/service/proto/src/metrics.proto
index 09a596f984..871eb2c750 100644
--- a/service/proto/src/metrics.proto
+++ b/service/proto/src/metrics.proto
@@ -1047,6 +1047,9 @@ message ConnectionEvent {
// The reason code if a user rejects this connection.
AUTH_FAILURE_REJECTED_BY_USER = 7;
+
+ // The reason code if an insecure Enterprise connection requires user's approval
+ DISCONNECTED_USER_APPROVAL_NEEDED = 8;
}
// Entity that recommended connecting to this network.
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
index ca8f5b031c..e2309e9ce4 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
@@ -7860,11 +7860,18 @@ public class ClientModeImplTest extends WifiBaseTest {
WifiEnterpriseConfig.Eap.TLS, WifiEnterpriseConfig.Phase2.NONE));
eapTlsConfig.networkId = FRAMEWORK_NETWORK_ID;
eapTlsConfig.SSID = TEST_SSID;
- if (isAtLeastT) {
+ if (isAtLeastT && isTrustOnFirstUseSupported) {
eapTlsConfig.enterpriseConfig.enableTrustOnFirstUse(true);
+ when(mInsecureEapNetworkHandler.prepareConnection(any(WifiConfiguration.class)))
+ .thenReturn(false);
+ } else {
+ when(mInsecureEapNetworkHandler.prepareConnection(any(WifiConfiguration.class)))
+ .thenReturn(true);
}
eapTlsConfig.enterpriseConfig.setCaPath("");
eapTlsConfig.enterpriseConfig.setDomainSuffixMatch("");
+ eapTlsConfig.setRandomizedMacAddress(TEST_LOCAL_MAC_ADDRESS);
+ eapTlsConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
initializeAndAddNetworkAndVerifySuccess(eapTlsConfig);
@@ -7879,31 +7886,31 @@ public class ClientModeImplTest extends WifiBaseTest {
}
verify(mInsecureEapNetworkHandler).prepareConnection(eq(eapTlsConfig));
- mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
- new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
- SupplicantState.ASSOCIATED));
- mLooper.dispatchAll();
-
if (isTrustOnFirstUseSupported) {
+ mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+ new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+ SupplicantState.ASSOCIATED));
+ mLooper.dispatchAll();
+
mCmi.sendMessage(WifiMonitor.TOFU_ROOT_CA_CERTIFICATE,
FRAMEWORK_NETWORK_ID, 0, FakeKeys.CA_CERT0);
mLooper.dispatchAll();
- verify(mInsecureEapNetworkHandler).setPendingCertificate(
+ verify(mInsecureEapNetworkHandler).addPendingCertificate(
eq(eapTlsConfig.SSID), eq(0), eq(FakeKeys.CA_CERT0));
- }
- mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
- new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
- mLooper.dispatchAll();
-
- mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
- new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
- SupplicantState.COMPLETED));
- mLooper.dispatchAll();
+ // Adding a certificate in depth 0 will cause a disconnection when TOFU is supported
+ DisconnectEventInfo disconnectEventInfo =
+ new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 3, true);
+ mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+ mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+ new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+ SupplicantState.DISCONNECTED));
+ mLooper.dispatchAll();
+ }
verify(mInsecureEapNetworkHandler).startUserApprovalIfNecessary(eq(isUserSelected));
- assertEquals("L3ProvisioningState", getCurrentState().getName());
-
+ // In any case, we end up in the disconnected state
+ assertEquals("DisconnectedState", getCurrentState().getName());
return eapTlsConfig;
}
@@ -7919,9 +7926,22 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onAccept(testConfig.SSID);
mLooper.dispatchAll();
- injectDhcpSuccess();
+ verify(mWifiConnectivityManager).forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ }
+
+ /**
+ * Verify logic when Trust On First Use is not supported
+ * - This network is selected by a user.
+ * - Network gets connected
+ */
+ @Test
+ public void verifyTrustOnFirstUseAcceptWhenConnectByUserNoTofu() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ WifiConfiguration testConfig = setupTrustOnFirstUse(true, false, true);
+
+ mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onAccept(testConfig.SSID);
mLooper.dispatchAll();
- assertEquals("L3ConnectedState", getCurrentState().getName());
+ verify(mWifiConnectivityManager).forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
}
/**
@@ -7936,7 +7956,13 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID);
mLooper.dispatchAll();
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ verify(mWifiMetrics).endConnectionEvent(
+ any(), eq(WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION),
+ eq(WifiMetricsProto.ConnectionEvent.HLF_NONE),
+ eq(WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN),
+ anyInt());
}
/**
@@ -7951,7 +7977,13 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onError(testConfig.SSID);
mLooper.dispatchAll();
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ verify(mWifiMetrics).endConnectionEvent(
+ any(), eq(WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION),
+ eq(WifiMetricsProto.ConnectionEvent.HLF_NONE),
+ eq(WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN),
+ anyInt());
}
/**
@@ -7967,9 +7999,7 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onAccept(testConfig.SSID);
mLooper.dispatchAll();
- injectDhcpSuccess();
- mLooper.dispatchAll();
- assertEquals("L3ConnectedState", getCurrentState().getName());
+ verify(mWifiConnectivityManager).forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
}
/**
@@ -7985,7 +8015,13 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID);
mLooper.dispatchAll();
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ verify(mWifiMetrics).endConnectionEvent(
+ any(), eq(WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION),
+ eq(WifiMetricsProto.ConnectionEvent.HLF_NONE),
+ eq(WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN),
+ anyInt());
}
/**
@@ -8000,7 +8036,13 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onError(testConfig.SSID);
mLooper.dispatchAll();
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ verify(mWifiMetrics).endConnectionEvent(
+ any(), eq(WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION),
+ eq(WifiMetricsProto.ConnectionEvent.HLF_NONE),
+ eq(WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN),
+ anyInt());
}
/**
@@ -8015,9 +8057,7 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onAccept(testConfig.SSID);
mLooper.dispatchAll();
- injectDhcpSuccess();
- mLooper.dispatchAll();
- assertEquals("L3ConnectedState", getCurrentState().getName());
+ verify(mWifiConnectivityManager).forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
}
/**
@@ -8032,7 +8072,13 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID);
mLooper.dispatchAll();
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ verify(mWifiMetrics).endConnectionEvent(
+ any(), eq(WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED),
+ eq(WifiMetricsProto.ConnectionEvent.HLF_NONE),
+ eq(WifiMetricsProto.ConnectionEvent.DISCONNECTED_USER_APPROVAL_NEEDED),
+ anyInt());
}
/**
@@ -8047,7 +8093,13 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onError(testConfig.SSID);
mLooper.dispatchAll();
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ verify(mWifiMetrics).endConnectionEvent(
+ any(), eq(WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED),
+ eq(WifiMetricsProto.ConnectionEvent.HLF_NONE),
+ eq(WifiMetricsProto.ConnectionEvent.DISCONNECTED_USER_APPROVAL_NEEDED),
+ anyInt());
}
/**
@@ -8061,9 +8113,8 @@ public class ClientModeImplTest extends WifiBaseTest {
WifiConfiguration testConfig = setupLegacyEapNetworkTest(false);
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onAccept(testConfig.SSID);
- injectDhcpSuccess();
mLooper.dispatchAll();
- assertEquals("L3ConnectedState", getCurrentState().getName());
+ verify(mWifiConnectivityManager).forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
}
/**
@@ -8078,9 +8129,14 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID);
mLooper.dispatchAll();
-
verify(mFrameworkFacade, never()).makeAlertDialogBuilder(any());
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ verify(mWifiMetrics).endConnectionEvent(
+ any(), eq(WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED),
+ eq(WifiMetricsProto.ConnectionEvent.HLF_NONE),
+ eq(WifiMetricsProto.ConnectionEvent.DISCONNECTED_USER_APPROVAL_NEEDED),
+ anyInt());
}
/**
@@ -8095,7 +8151,13 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onError(testConfig.SSID);
mLooper.dispatchAll();
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
+ verify(mWifiMetrics).endConnectionEvent(
+ any(), eq(WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED),
+ eq(WifiMetricsProto.ConnectionEvent.HLF_NONE),
+ eq(WifiMetricsProto.ConnectionEvent.DISCONNECTED_USER_APPROVAL_NEEDED),
+ anyInt());
}
private void setScanResultWithMloInfo() {
diff --git a/service/tests/wifitests/src/com/android/server/wifi/InsecureEapNetworkHandlerTest.java b/service/tests/wifitests/src/com/android/server/wifi/InsecureEapNetworkHandlerTest.java
index aed3753ffc..6e2e67a8a2 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/InsecureEapNetworkHandlerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/InsecureEapNetworkHandlerTest.java
@@ -17,14 +17,18 @@
package com.android.server.wifi;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.validateMockitoUsage;
@@ -37,11 +41,16 @@ import android.content.Intent;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiContext;
import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.util.HexEncoding;
import android.os.Handler;
+import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.util.CertificateSubjectInfo;
+import com.android.server.wifi.util.NativeUtil;
+import com.android.wifi.resources.R;
import org.junit.After;
import org.junit.Before;
@@ -51,9 +60,13 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
+import javax.security.auth.x500.X500Principal;
+
/**
* Unit tests for {@link com.android.server.wifi.InsecureEapNetworkHandlerTest}.
*/
@@ -65,7 +78,9 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
private static final int ACTION_TAP = 2;
private static final String WIFI_IFACE_NAME = "wlan-test-9";
private static final int FRAMEWORK_NETWORK_ID = 2;
- private static final String TEST_SSID = "test_ssid";
+ private static final String TEST_SSID = "\"test_ssid\"";
+ private static final String TEST_IDENTITY = "userid";
+ private static final String TEST_PASSWORD = "myPassWord!";
@Mock WifiContext mContext;
@Mock WifiConfigManager mWifiConfigManager;
@@ -94,11 +109,34 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
when(mContext.getString(anyInt())).thenReturn("TestString");
when(mContext.getString(anyInt(), any())).thenReturn("TestStringWithArgument");
when(mContext.getText(anyInt())).thenReturn("TestStr");
+ when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_issuer_name_text),
+ anyString()))
+ .thenAnswer((Answer<String>) invocation ->
+ "Issuer Name:\n" + invocation.getArguments()[1] + "\n\n");
+ when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_server_name_text),
+ anyString()))
+ .thenAnswer((Answer<String>) invocation ->
+ "Server Name:\n" + invocation.getArguments()[1] + "\n\n");
+ when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_organization_text),
+ anyString()))
+ .thenAnswer((Answer<String>) invocation ->
+ "Organization:\n" + invocation.getArguments()[1] + "\n\n");
+ when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_contact_text),
+ anyString()))
+ .thenAnswer((Answer<String>) invocation ->
+ "Contact:\n" + invocation.getArguments()[1] + "\n\n");
+ when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_signature_name_text),
+ anyString()))
+ .thenAnswer((Answer<String>) invocation ->
+ "Signature:\n" + invocation.getArguments()[1] + "\n\n");
when(mContext.getWifiOverlayApkPkgName()).thenReturn("test.com.android.wifi.resources");
when(mContext.getResources()).thenReturn(mResources);
when(mWifiDialogManager.createSimpleDialogWithUrl(
any(), any(), any(), anyInt(), anyInt(), any(), any(), any(), any(), any()))
.thenReturn(mTofuAlertDialog);
+ when(mWifiDialogManager.createSimpleDialog(
+ any(), any(), any(), any(), any(), any(), any()))
+ .thenReturn(mTofuAlertDialog);
when(mFrameworkFacade.makeNotificationBuilder(any(), any()))
.thenReturn(mNotificationBuilder);
@@ -207,6 +245,9 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
assertTrue(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
verify(mCallbacks).onError(eq(config.SSID));
+ verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER));
}
/**
@@ -311,9 +352,36 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
+ private X509Certificate generateMockCert(String subject, String issuer, boolean isCa) {
+ X509Certificate mockCert = mock(X509Certificate.class);
+ X500Principal mockSubjectPrincipal = mock(X500Principal.class);
+ when(mockCert.getSubjectX500Principal()).thenReturn(mockSubjectPrincipal);
+ when(mockSubjectPrincipal.getName()).thenReturn("C=TW,ST=Taiwan,L=Taipei"
+ + ",O=" + subject + " Organization"
+ + ",CN=" + subject
+ + ",1.2.840.113549.1.9.1=#1614" + String.valueOf(HexEncoding.encode(
+ (subject + "@email.com").getBytes(StandardCharsets.UTF_8))));
+
+ X500Principal mockIssuerX500Principal = mock(X500Principal.class);
+ when(mockCert.getIssuerX500Principal()).thenReturn(mockIssuerX500Principal);
+ when(mockIssuerX500Principal.getName()).thenReturn("C=TW,ST=Taiwan,L=Taipei"
+ + ",O=" + issuer + " Organization"
+ + ",CN=" + issuer
+ + ",1.2.840.113549.1.9.1=#1614" + String.valueOf(HexEncoding.encode(
+ (issuer + "@email.com").getBytes(StandardCharsets.UTF_8))));
+
+ when(mockCert.getSignature()).thenReturn(new byte[]{
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
+ (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78,
+ (byte) 0x90, (byte) 0xab, (byte) 0xcd, (byte) 0xef});
+
+ when(mockCert.getBasicConstraints()).thenReturn(isCa ? 99 : -1);
+ return mockCert;
+ }
+
private WifiConfiguration prepareWifiConfiguration(boolean isAtLeastT) {
WifiConfiguration config = spy(WifiConfigurationTestUtil.createEapNetwork(
- WifiEnterpriseConfig.Eap.TLS, WifiEnterpriseConfig.Phase2.NONE));
+ WifiEnterpriseConfig.Eap.TTLS, WifiEnterpriseConfig.Phase2.MSCHAPV2));
config.networkId = FRAMEWORK_NETWORK_ID;
config.SSID = TEST_SSID;
if (isAtLeastT) {
@@ -321,6 +389,8 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
}
config.enterpriseConfig.setCaPath("");
config.enterpriseConfig.setDomainSuffixMatch("");
+ config.enterpriseConfig.setIdentity(TEST_IDENTITY);
+ config.enterpriseConfig.setPassword(TEST_PASSWORD);
return config;
}
@@ -338,14 +408,32 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
mWifiNative,
mFrameworkFacade,
mWifiNotificationManager,
- mWifiDialogManager, isTrustOnFirstUseSupported,
+ mWifiDialogManager,
+ isTrustOnFirstUseSupported,
isInsecureEnterpriseConfigurationAllowed,
mCallbacks,
WIFI_IFACE_NAME,
mHandler);
+ if (isTrustOnFirstUseSupported
+ && (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS
+ || config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP)
+ && config.enterpriseConfig.getPhase2Method() != WifiEnterpriseConfig.Phase2.NONE) {
+ // Verify that the configuration contains an identity
+ assertEquals(TEST_IDENTITY, config.enterpriseConfig.getIdentity());
+ assertEquals(TEST_PASSWORD, config.enterpriseConfig.getPassword());
+ }
mInsecureEapNetworkHandler.prepareConnection(config);
+ if (isTrustOnFirstUseSupported
+ && (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS
+ || config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP)
+ && config.enterpriseConfig.getPhase2Method() != WifiEnterpriseConfig.Phase2.NONE) {
+ // Verify identities are cleared
+ assertTrue(TextUtils.isEmpty(config.enterpriseConfig.getIdentity()));
+ assertTrue(TextUtils.isEmpty(config.enterpriseConfig.getPassword()));
+ }
+
if (isTrustOnFirstUseSupported && config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
verify(mContext, atLeastOnce()).registerReceiver(
mBroadcastReceiverCaptor.capture(),
@@ -379,34 +467,13 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 1, FakeKeys.CA_CERT0);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 0, FakeKeys.CLIENT_CERT);
-
- verifyTrustOnFirstUseFlow(config, ACTION_ACCEPT, isTrustOnFirstUseSupported,
- isUserSelected, needUserApproval, FakeKeys.CA_CERT0, FakeKeys.CLIENT_CERT);
- }
-
- /**
- * Verify Trust On First Use flow with a reversal cert chain
- * - This network is selected by a user.
- * - Accept the connection.
- */
- @Test
- public void verifyTrustOnFirstUseAcceptWhenConnectByUserWithReversalOrderChain()
- throws Exception {
- assumeTrue(SdkLevel.isAtLeastT());
- boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
- boolean needUserApproval = true;
-
- WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
- setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
-
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 0, FakeKeys.CLIENT_CERT);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 1, FakeKeys.CA_CERT1);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 2, FakeKeys.CA_CERT0);
+ X509Certificate mockCaCert = generateMockCert("ca", "ca", true);
+ X509Certificate mockServerCert = generateMockCert("server", "ca", false);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1, mockCaCert);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockServerCert);
verifyTrustOnFirstUseFlow(config, ACTION_ACCEPT, isTrustOnFirstUseSupported,
- isUserSelected, needUserApproval, FakeKeys.CA_CERT0, FakeKeys.CLIENT_CERT);
+ isUserSelected, needUserApproval, mockCaCert, mockServerCert);
}
/**
@@ -424,10 +491,11 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 0, FakeKeys.CA_CERT0);
+ X509Certificate mockSelfSignedCert = generateMockCert("self", "self", false);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockSelfSignedCert);
verifyTrustOnFirstUseFlow(config, ACTION_ACCEPT, isTrustOnFirstUseSupported,
- isUserSelected, needUserApproval, FakeKeys.CA_CERT0, FakeKeys.CA_CERT0);
+ isUserSelected, needUserApproval, mockSelfSignedCert, mockSelfSignedCert);
}
/**
@@ -448,6 +516,9 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
assertEquals(needUserApproval,
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
verify(mCallbacks).onError(eq(config.SSID));
+ verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER));
}
/**
@@ -467,11 +538,17 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
config.enterpriseConfig.enableTrustOnFirstUse(false);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 0, FakeKeys.CA_CERT0);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1,
+ generateMockCert("ca", "ca", true));
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0,
+ generateMockCert("server", "ca", false));
assertEquals(needUserApproval,
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
verify(mCallbacks).onError(eq(config.SSID));
+ verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER));
}
/**
@@ -479,36 +556,161 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
* - TOFU is supported.
* - Insecure EAP network is allowed.
* - TOFU is not enabled
+ * - No user approval is needed.
*/
@Test
public void verifyNoErrorWithTofuDisabledWhenInsecureEapNetworkIsAllowed()
throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
- boolean needUserApproval = true, isInsecureEnterpriseConfigurationAllowed = true;
+ boolean needUserApproval = false, isInsecureEnterpriseConfigurationAllowed = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
config.enterpriseConfig.enableTrustOnFirstUse(false);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported,
isInsecureEnterpriseConfigurationAllowed);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 0, FakeKeys.CA_CERT0);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1,
+ generateMockCert("ca", "ca", true));
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0,
+ generateMockCert("server", "ca", false));
assertEquals(needUserApproval,
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
verify(mCallbacks, never()).onError(any());
}
+ /**
+ * Verify that it reports errors if the cert chain is headless.
+ */
+ @Test
+ public void verifyOnErrorWithHeadlessCertChain() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
+
+ WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
+ setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
+
+ // Missing root CA cert.
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0,
+ generateMockCert("server", "ca", false));
+
+ assertTrue(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ verify(mCallbacks).onError(eq(config.SSID));
+ verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER));
+ }
+
+ /**
+ * Verify that is reports errors if the server cert issuer does not match the parent subject.
+ */
+ @Test
+ public void verifyOnErrorWithIncompleteChain() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
+
+ WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
+ setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
+
+ X509Certificate mockCaCert = generateMockCert("ca", "ca", true);
+ // Missing intermediate cert.
+ X509Certificate mockServerCert = generateMockCert("server", "intermediate", false);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1, mockCaCert);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockServerCert);
+
+ assertTrue(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ verify(mCallbacks).onError(eq(config.SSID));
+ verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER));
+ }
+
+ /**
+ * Verify that setting pending certificate won't crash with no current configuration.
+ */
+ @Test
+ public void verifySetPendingCertificateNoCrashWithNoConfig()
+ throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ mInsecureEapNetworkHandler = new InsecureEapNetworkHandler(
+ mContext,
+ mWifiConfigManager,
+ mWifiNative,
+ mFrameworkFacade,
+ mWifiNotificationManager,
+ mWifiDialogManager,
+ true /* isTrustOnFirstUseSupported */,
+ false /* isInsecureEnterpriseConfigurationAllowed */,
+ mCallbacks,
+ WIFI_IFACE_NAME,
+ mHandler);
+ X509Certificate mockSelfSignedCert = generateMockCert("self", "self", false);
+ mInsecureEapNetworkHandler.addPendingCertificate("NotExist", 0, mockSelfSignedCert);
+ }
+
+ @Test
+ public void testExistingCertChainIsClearedOnPreparingNewConnection() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
+
+ WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
+ setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
+
+ // Missing root CA cert.
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0,
+ generateMockCert("server", "ca", false));
+
+ // The wrong cert chain should be cleared after this call.
+ mInsecureEapNetworkHandler.prepareConnection(config);
+
+ X509Certificate mockSelfSignedCert = generateMockCert("self", "self", false);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockSelfSignedCert);
+
+ assertTrue(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ verify(mCallbacks, never()).onError(any());
+ }
+
+ @Test
+ public void verifyUserApprovalIsNotNeededWithDifferentTargetConfig() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
+
+ WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
+ setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
+
+ X509Certificate mockSelfSignedCert = generateMockCert("self", "self", false);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockSelfSignedCert);
+
+ // Pass another PSK config which is not the same as the current one.
+ WifiConfiguration pskConfig = WifiConfigurationTestUtil.createPskNetwork();
+ pskConfig.networkId = FRAMEWORK_NETWORK_ID + 2;
+ mInsecureEapNetworkHandler.prepareConnection(pskConfig);
+ assertFalse(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ verify(mCallbacks, never()).onError(any());
+
+ // Pass another non-TOFU EAP config which is not the same as the current one.
+ WifiConfiguration anotherEapConfig = spy(WifiConfigurationTestUtil.createEapNetwork(
+ WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
+ anotherEapConfig.networkId = FRAMEWORK_NETWORK_ID + 1;
+ mInsecureEapNetworkHandler.prepareConnection(anotherEapConfig);
+ assertFalse(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ verify(mCallbacks, never()).onError(any());
+ }
+
private void verifyTrustOnFirstUseFlowWithDefaultCerts(WifiConfiguration config,
int action, boolean isTrustOnFirstUseSupported, boolean isUserSelected,
boolean needUserApproval) throws Exception {
+ X509Certificate mockCaCert = generateMockCert("ca", "ca", true);
+ X509Certificate mockServerCert = generateMockCert("server", "middle", false);
if (isTrustOnFirstUseSupported) {
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 2, FakeKeys.CA_CERT0);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 1, FakeKeys.CA_CERT1);
- mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 0, FakeKeys.CLIENT_CERT);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 2, mockCaCert);
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1,
+ generateMockCert("middle", "ca", false));
+ mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockServerCert);
}
verifyTrustOnFirstUseFlow(config, action, isTrustOnFirstUseSupported,
- isUserSelected, needUserApproval, FakeKeys.CA_CERT0, FakeKeys.CLIENT_CERT);
+ isUserSelected, needUserApproval, mockCaCert, mockServerCert);
}
private void verifyTrustOnFirstUseFlow(WifiConfiguration config,
@@ -518,12 +720,17 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
assertEquals(needUserApproval,
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ ArgumentCaptor<String> dialogMessageCaptor = ArgumentCaptor.forClass(String.class);
if (isUserSelected) {
ArgumentCaptor<WifiDialogManager.SimpleDialogCallback> dialogCallbackCaptor =
ArgumentCaptor.forClass(WifiDialogManager.SimpleDialogCallback.class);
verify(mWifiDialogManager).createSimpleDialogWithUrl(
- any(), any(), any(), anyInt(), anyInt(), any(), any(), any(),
- dialogCallbackCaptor.capture(), any());
+ any(), dialogMessageCaptor.capture(), any(), anyInt(), anyInt(), any(), any(),
+ any(), dialogCallbackCaptor.capture(), any());
+ if (isTrustOnFirstUseSupported) {
+ assertTofuDialogMessage(expectedCaCert, expectedServerCert,
+ dialogMessageCaptor.getValue());
+ }
if (action == ACTION_ACCEPT) {
dialogCallbackCaptor.getValue().onPositiveButtonClicked();
} else if (action == ACTION_REJECT) {
@@ -533,6 +740,7 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
verify(mFrameworkFacade, never()).makeAlertDialogBuilder(any());
verify(mFrameworkFacade).makeNotificationBuilder(
eq(mContext), eq(WifiService.NOTIFICATION_NETWORK_ALERTS));
+
// Trust On First Use notification has no accept and reject action buttons.
// It only supports TAP and launch the dialog.
if (isTrustOnFirstUseSupported) {
@@ -543,8 +751,10 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
ArgumentCaptor<WifiDialogManager.SimpleDialogCallback> dialogCallbackCaptor =
ArgumentCaptor.forClass(WifiDialogManager.SimpleDialogCallback.class);
verify(mWifiDialogManager).createSimpleDialogWithUrl(
- any(), any(), any(), anyInt(), anyInt(), any(), any(), any(),
- dialogCallbackCaptor.capture(), any());
+ any(), dialogMessageCaptor.capture(), any(), anyInt(), anyInt(), any(),
+ any(), any(), dialogCallbackCaptor.capture(), any());
+ assertTofuDialogMessage(expectedCaCert, expectedServerCert,
+ dialogMessageCaptor.getValue());
if (action == ACTION_ACCEPT) {
dialogCallbackCaptor.getValue().onPositiveButtonClicked();
} else if (action == ACTION_REJECT) {
@@ -566,7 +776,8 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
}
if (action == ACTION_ACCEPT) {
- verify(mWifiConfigManager).allowAutojoin(eq(config.networkId), eq(true));
+ verify(mWifiConfigManager).updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE));
if (isTrustOnFirstUseSupported) {
verify(mWifiConfigManager).updateCaCertificate(
eq(config.networkId), eq(expectedCaCert), eq(expectedServerCert));
@@ -576,7 +787,10 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
}
verify(mCallbacks).onAccept(eq(config.SSID));
} else if (action == ACTION_REJECT) {
- verify(mWifiConfigManager).allowAutojoin(eq(config.networkId), eq(false));
+ verify(mWifiConfigManager, atLeastOnce())
+ .updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER));
verify(mCallbacks).onReject(eq(config.SSID));
} else if (action == ACTION_TAP) {
verify(mWifiDialogManager).createSimpleDialogWithUrl(
@@ -586,4 +800,44 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
verify(mCallbacks, never()).onError(any());
}
+ private void assertTofuDialogMessage(
+ X509Certificate rootCaCert,
+ X509Certificate serverCert,
+ String message) {
+ CertificateSubjectInfo serverCertSubjectInfo =
+ CertificateSubjectInfo.parse(serverCert.getSubjectX500Principal().getName());
+ CertificateSubjectInfo serverCertIssuerInfo =
+ CertificateSubjectInfo.parse(serverCert.getIssuerX500Principal().getName());
+ assertNotNull("Server cert subject info is null", serverCertSubjectInfo);
+ assertNotNull("Server cert issuer info is null", serverCertIssuerInfo);
+
+ assertTrue("TOFU dialog message does not contain server cert subject name ",
+ message.contains(serverCertSubjectInfo.commonName));
+ assertTrue("TOFU dialog message does not contain server cert issuer name",
+ message.contains(serverCertIssuerInfo.commonName));
+ if (!TextUtils.isEmpty(serverCertSubjectInfo.organization)) {
+ assertTrue("TOFU dialog message does not contain server cert organization",
+ message.contains(serverCertSubjectInfo.organization));
+ }
+ if (!TextUtils.isEmpty(serverCertSubjectInfo.email)) {
+ assertTrue("TOFU dialog message does not contain server cert email",
+ message.contains(serverCertSubjectInfo.email));
+ }
+ assertTrue("TOFU dialog message does not contain server cert signature",
+ message.contains(NativeUtil.hexStringFromByteArray(
+ rootCaCert.getSignature()).substring(0, 16)));
+ }
+
+ @Test
+ public void testCleanUp() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+
+ boolean isAtLeastT = true, isTrustOnFirstUseSupported = true;
+ WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
+ setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
+
+ BroadcastReceiver br = mBroadcastReceiverCaptor.getValue();
+ mInsecureEapNetworkHandler.cleanup();
+ verify(mContext).unregisterReceiver(br);
+ }
}