[TOFU] 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.

Change-Id: Ib3501f5e04881b11ea9ab52472dd233f2aa82c7a
Merged-In: Ib3501f5e04881b11ea9ab52472dd233f2aa82c7a
(cherry picked from commit fb630e6a13189321d0e83037adc9e7b30dc6d796)
Merged-In: Ib3501f5e04881b11ea9ab52472dd233f2aa82c7a
diff --git a/service/ServiceWifiResources/res/values/overlayable.xml b/service/ServiceWifiResources/res/values/overlayable.xml
index 2eef3f5..75c90a3 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 15ffcf0..db331b2 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 92cc9ff..0ae71d9 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -580,9 +580,6 @@
     @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 @@
                 @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 @@
                 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 @@
                             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;
                 }
@@ -4266,7 +4268,6 @@
                     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:
@@ -5373,11 +5374,16 @@
                 }
                 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;
@@ -5856,10 +5862,6 @@
     class L3ProvisioningState extends State {
         @Override
         public void enter() {
-            if (mInsecureEapNetworkHandler.startUserApprovalIfNecessary(mIsUserSelected)) {
-                return;
-            }
-
             startL3Provisioning();
         }
 
@@ -5879,18 +5881,6 @@
                     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;
@@ -6410,6 +6400,12 @@
                     }
                     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 225d01c..b738995 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.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 @@
     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 @@
      * 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 @@
         // 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 @@
      * @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 @@
         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 @@
         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 @@
     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 @@
     }
 
     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 @@
     }
 
     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 @@
                 ? 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));
             }
-            contentBuilder
-                    .append(mContext.getString(
-                            R.string.wifi_ca_cert_dialog_message_signature_name_text,
-                            signature.substring(0, 16)));
+            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));
+            }
             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 @@
                 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 @@
     }
 
     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 @@
                 ? 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 @@
             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 @@
     }
 
     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 @@
     // 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 d63246a..6a065ce 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -528,9 +528,6 @@
             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 @@
             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 09a596f..871eb2c 100644
--- a/service/proto/src/metrics.proto
+++ b/service/proto/src/metrics.proto
@@ -1047,6 +1047,9 @@
 
     // 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 ca8f5b0..e2309e9 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 @@
                 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 @@
         }
         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));
+
+            // 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();
         }
 
-        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();
-
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
         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 @@
 
         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 @@
 
         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 aed3753..6e2e67a 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.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.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 @@
     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 @@
         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 @@
         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 @@
                 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 @@
         }
         config.enterpriseConfig.setCaPath("");
         config.enterpriseConfig.setDomainSuffixMatch("");
+        config.enterpriseConfig.setIdentity(TEST_IDENTITY);
+        config.enterpriseConfig.setPassword(TEST_PASSWORD);
         return config;
     }
 
@@ -338,14 +408,32 @@
                 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 @@
         WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
         setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
 
-        mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 1, FakeKeys.CA_CERT0);
-        mInsecureEapNetworkHandler.setPendingCertificate(config.SSID, 0, FakeKeys.CLIENT_CERT);
+        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);
-    }
-
-    /**
-     * 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);
-
-        verifyTrustOnFirstUseFlow(config, ACTION_ACCEPT, isTrustOnFirstUseSupported,
-                isUserSelected, needUserApproval, FakeKeys.CA_CERT0, FakeKeys.CLIENT_CERT);
+                isUserSelected, needUserApproval, mockCaCert, mockServerCert);
     }
 
     /**
@@ -424,10 +491,11 @@
         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 @@
         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 @@
         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 @@
      * - 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 @@
         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 @@
             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 @@
                 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 @@
         }
 
         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 @@
             }
             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 @@
         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);
+    }
 }