DivestOS/Patches/LineageOS-20.0/android_packages_modules_Wifi/ASB-2023-06/0003-Implement-a-secure-TOFU-flow.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

2186 lines
110 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hai Shalom <haishalom@google.com>
Date: Thu, 2 Mar 2023 23:00:56 +0000
Subject: [PATCH 3/3] Implement a secure TOFU flow
Implement a secure TOFU flow for supporting devices, and
notifications about insecure connections in non-supporting
devices, when insecure configurations are not allowed.
Handle the case where insecure enterprise configurations are
allowed in the new and secure TOFU flow. In this mode, do not
disconnect the network, do not load certificates, and do not
notify the user about anything.
Display the correct certificate information in the dialog,
remove the email and 8-octet signature from the TOFU dialog, and
replace with user verifiable information: certificate expiration
date (locale adjusted) and a SHA-256 fingerprint of the server
certificate which is locally generated.
Network admins can calculate the fingerprint of their server
certificate and publish the result to their users, using:
openssl x509 -in server-cert.pem -noout -fingerprint -sha256
Updated-Overlayable: TRUE
Updated-PDD: TRUE
Bug: 267633332
Bug: 251910611
Bug: 250574778
Test: atest ClientModeImplTest InsecureEapNetworkHandlerTest
Test: atest WifiConfigManagerTest
Test: Integration test on R, and T devices with overlay setting
of insecure networks allowed and not allowed, and with new
configs and insecure (Do not validate) configs made with R.
Test: Functional test, UI verification with multiple locales
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a5227527411bc24e6e2c6276f16559c7305b6783)
Merged-In: I5cac12cd8c52a8a9425e98dad0fb90893f53e374
Change-Id: I5cac12cd8c52a8a9425e98dad0fb90893f53e374
---
.../res/values/overlayable.xml | 25 +
.../res/values/strings.xml | 10 +-
.../android/server/wifi/ClientModeImpl.java | 65 ++-
.../wifi/InsecureEapNetworkHandler.java | 471 +++++++++++++-----
.../server/wifi/WifiConfigManager.java | 32 +-
.../com/android/server/wifi/WifiKeyStore.java | 56 ++-
.../android/server/wifi/WifiServiceImpl.java | 7 +-
service/proto/src/metrics.proto | 3 +
.../server/wifi/ClientModeImplTest.java | 135 +++--
.../wifi/InsecureEapNetworkHandlerTest.java | 374 +++++++++++---
.../server/wifi/WifiConfigManagerTest.java | 69 +++
.../wifi/WifiConfigurationTestUtil.java | 1 +
.../android/server/wifi/WifiKeyStoreTest.java | 31 ++
13 files changed, 971 insertions(+), 308 deletions(-)
diff --git a/service/ServiceWifiResources/res/values/overlayable.xml b/service/ServiceWifiResources/res/values/overlayable.xml
index 2eef3f5156..160b711091 100644
--- a/service/ServiceWifiResources/res/values/overlayable.xml
+++ b/service/ServiceWifiResources/res/values/overlayable.xml
@@ -321,6 +321,31 @@
<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_expiration_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..bfcb96a1f8 100644
--- a/service/ServiceWifiResources/res/values/strings.xml
+++ b/service/ServiceWifiResources/res/values/strings.xml
@@ -200,16 +200,20 @@
<string name="wifi_ca_cert_dialog_message_server_name_text">Server Name:\n<xliff:g id="value">%1$s</xliff:g>\n\n</string>
<string name="wifi_ca_cert_dialog_message_issuer_name_text">Issuer Name:\n<xliff:g id="value">%1$s</xliff:g>\n\n</string>
<string name="wifi_ca_cert_dialog_message_organization_text">Organization:\n<xliff:g id="value">%1$s</xliff:g>\n\n</string>
+ <string name="wifi_ca_cert_dialog_message_expiration_text">Certificate Expiration:\n<xliff:g id="value">%1$s</xliff:g>\n\n</string>
+ <string name="wifi_ca_cert_dialog_message_signature_name_text">SHA-256 Fingerprint:\n<xliff:g id="value">%1$s</xliff:g>\n\n</string>
<string name="wifi_ca_cert_dialog_message_contact_text">Contact:\n<xliff:g id="value">%1$s</xliff:g>\n\n</string>
- <string name="wifi_ca_cert_dialog_message_signature_name_text">Signature:\n<xliff:g id="value">%1$s</xliff:g>\n\n</string>
<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>
- <string name="wifi_ca_cert_dialog_preT_continue_text">Connect anyway</string>
- <string name="wifi_ca_cert_dialog_preT_abort_text">Don\'t connect</string>
+ <string name="wifi_ca_cert_dialog_preT_continue_text">Stay connected</string>
+ <string name="wifi_ca_cert_dialog_preT_abort_text">Disconnect now</string>
<string name="wifi_ca_cert_dialog_preT_message_hint">The network <xliff:g id="ssid">%1$s</xliff:g> is missing a certificate.</string>
<string name="wifi_ca_cert_dialog_preT_message_link">Learn how to add certificates</string>
<string name="wifi_ca_cert_notification_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..bd87041319 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -581,7 +581,7 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
static final int CMD_ACCEPT_EAP_SERVER_CERTIFICATE = BASE + 301;
@VisibleForTesting
- static final int CMD_REJECT_EAP_SERVER_CERTIFICATE = BASE + 302;
+ static final int CMD_REJECT_EAP_INSECURE_CONNECTION = BASE + 302;
/* Tracks if suspend optimizations need to be disabled by DHCP,
* screen or due to high perf mode.
@@ -835,17 +835,17 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
}
@Override
- public void onReject(String ssid) {
+ public void onReject(String ssid, boolean disconnectRequired) {
log("Reject Root CA cert for " + ssid);
- sendMessage(CMD_REJECT_EAP_SERVER_CERTIFICATE,
+ sendMessage(CMD_REJECT_EAP_INSECURE_CONNECTION,
WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_REJECTED_BY_USER,
- 0, ssid);
+ disconnectRequired ? 1 : 0, ssid);
}
@Override
public void onError(String ssid) {
log("Insecure EAP network error for " + ssid);
- sendMessage(CMD_REJECT_EAP_SERVER_CERTIFICATE,
+ sendMessage(CMD_REJECT_EAP_INSECURE_CONNECTION,
WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_EAP_FAILURE,
0, ssid);
}};
@@ -2261,7 +2261,7 @@ 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:
+ case CMD_REJECT_EAP_INSECURE_CONNECTION:
return "CMD_REJECT_EAP_SERVER_CERTIFICATE";
case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
return "SUPPLICANT_STATE_CHANGE_EVENT";
@@ -4021,8 +4021,13 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
break;
}
}
- mInsecureEapNetworkHandler.prepareConnection(mTargetWifiConfiguration);
setSelectedRcoiForPasspoint(config);
+
+ // TOFU flow for devices that do not support this feature
+ mInsecureEapNetworkHandler.prepareConnection(mTargetWifiConfiguration);
+ if (!isTrustOnFirstUseSupported()) {
+ mInsecureEapNetworkHandler.startUserApprovalIfNecessary(mIsUserSelected);
+ }
connectToNetwork(config);
break;
}
@@ -4277,7 +4282,7 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
break;
}
case CMD_ACCEPT_EAP_SERVER_CERTIFICATE:
- case CMD_REJECT_EAP_SERVER_CERTIFICATE:
+ case CMD_REJECT_EAP_INSECURE_CONNECTION:
case CMD_START_ROAM:
case CMD_START_RSSI_MONITORING_OFFLOAD:
case CMD_STOP_RSSI_MONITORING_OFFLOAD:
@@ -5062,6 +5067,18 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
}
break;
}
+ case CMD_REJECT_EAP_INSECURE_CONNECTION: {
+ log("Received CMD_REJECT_EAP_INSECURE_CONNECTION event");
+ boolean disconnectRequired = message.arg2 == 1;
+
+ // TOFU connections are not established until the user approves the certificate.
+ // If TOFU is not supported and the network is already connected, this will
+ // disconnect the network.
+ if (disconnectRequired) {
+ sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_NETWORK_UNTRUSTED);
+ }
+ break;
+ }
default: {
handleStatus = NOT_HANDLED;
break;
@@ -5384,11 +5401,19 @@ 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 and disconnect
+ if (certificateDepth == 0 && mInsecureEapNetworkHandler
+ .startUserApprovalIfNecessary(mIsUserSelected)) {
+ // In the TOFU flow, the user approval dialog is now displayed and the
+ // network remains disconnected and disabled until it is approved.
+ sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_NETWORK_UNTRUSTED);
+ }
break;
default: {
handleStatus = NOT_HANDLED;
@@ -5867,10 +5892,6 @@ public class ClientModeImpl extends StateMachine implements ClientMode {
class L3ProvisioningState extends State {
@Override
public void enter() {
- if (mInsecureEapNetworkHandler.startUserApprovalIfNecessary(mIsUserSelected)) {
- return;
- }
-
startL3Provisioning();
}
@@ -5890,18 +5911,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 +6430,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..6c55feab7f 100644
--- a/service/java/com/android/server/wifi/InsecureEapNetworkHandler.java
+++ b/service/java/com/android/server/wifi/InsecureEapNetworkHandler.java
@@ -31,15 +31,23 @@ import android.net.wifi.WifiContext;
import android.net.wifi.WifiEnterpriseConfig;
import android.os.Handler;
import android.text.TextUtils;
+import android.text.format.DateFormat;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.util.HexDump;
import com.android.server.wifi.util.CertificateSubjectInfo;
-import com.android.server.wifi.util.NativeUtil;
import com.android.wifi.resources.R;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.StringJoiner;
/** This class is used to handle insecure EAP networks. */
public class InsecureEapNetworkHandler {
@@ -58,6 +66,7 @@ public class InsecureEapNetworkHandler {
static final String EXTRA_PENDING_CERT_SSID =
"com.android.server.wifi.ClientModeImpl.EXTRA_PENDING_CERT_SSID";
+ static final String TOFU_ANONYMOUS_IDENTITY = "anonymous";
private final String mCaCertHelpLink;
private final WifiContext mContext;
private final WifiConfigManager mWifiConfigManager;
@@ -71,18 +80,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;
@@ -131,29 +149,30 @@ public class InsecureEapNetworkHandler {
}
/**
- * Prepare data for a new connection.
+ * Prepare TOFU data for a new connection.
*
- * Prepare data if this is an Enterprise configuration, which
+ * Prepare TOFU data if this is an Enterprise configuration, which
* uses Server Cert, without a valid Root CA certificate or user approval.
+ * If TOFU is supported and enabled, this method will also clear the user credentials in the
+ * initial connection to the server.
*
* @param config the running wifi configuration.
*/
public void prepareConnection(@NonNull WifiConfiguration config) {
if (null == config) return;
+ mConnectingConfig = config;
if (!config.isEnterprise()) return;
WifiEnterpriseConfig entConfig = config.enterpriseConfig;
if (!entConfig.isEapMethodServerCertUsed()) return;
if (entConfig.hasCaCertificate()) return;
- clearConnection();
-
Log.d(TAG, "prepareConnection: isTofuSupported=" + mIsTrustOnFirstUseSupported
+ ", isInsecureEapNetworkAllowed=" + mIsInsecureEnterpriseConfigurationAllowed
+ ", isTofuEnabled=" + entConfig.isTrustOnFirstUseEnabled()
+ ", isUserApprovedNoCaCert=" + entConfig.isUserApproveNoCaCert());
// 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.
+ // skip the entire TOFU logic if this network was approved earlier by the user.
if (entConfig.isUserApproveNoCaCert()) {
if (!mIsTrustOnFirstUseSupported) return;
if (mIsInsecureEnterpriseConfigurationAllowed
@@ -162,64 +181,122 @@ public class InsecureEapNetworkHandler {
}
}
- mCurConfig = config;
+ if (mIsTrustOnFirstUseSupported && (entConfig.isTrustOnFirstUseEnabled()
+ || !mIsInsecureEnterpriseConfigurationAllowed)) {
+ /**
+ * 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);
+ if (TextUtils.isEmpty(config.enterpriseConfig.getAnonymousIdentity())) {
+ /**
+ * If anonymous identity was not provided, use "anonymous" to prevent any
+ * untrusted server from tracking real user identities.
+ */
+ config.enterpriseConfig.setAnonymousIdentity(TOFU_ANONYMOUS_IDENTITY);
+ }
+ config.enterpriseConfig.setPassword(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 (useTrustOnFirstUse()) {
+ // Remove cached PMK in the framework and supplicant to avoid skipping the EAP flow
+ // only when TOFU is in use.
+ clearNativeData();
+ Log.d(TAG, "Remove native cached data and networks for TOFU.");
+ }
}
- /** 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 TOFU is not supported return immediately, although this should not happen since
+ // the caller code flow is only active when TOFU is supported.
+ if (!mIsTrustOnFirstUseSupported) return false;
+
+ // If insecure configurations are allowed and this configuration is configured with
+ // "Do not validate" (i.e. TOFU is disabled), skip loading the certificates (no need for
+ // them anyway) and don't disconnect the network.
+ if (mIsInsecureEnterpriseConfigurationAllowed
+ && !mCurrentTofuConfig.enterpriseConfig.isTrustOnFirstUseEnabled()) {
+ Log.d(TAG, "Certificates are not required for this connection");
+ return false;
+ }
+
+ if (depth == 0) {
+ // Disable network selection upon receiving the server certificate
+ putNetworkOnHold();
+ }
+
+ 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;
}
@@ -241,34 +318,42 @@ public class InsecureEapNetworkHandler {
* cert from the server, just mark this network is approved by the user.
*
* @param isUserSelected indicates that this connection is triggered by a user.
- * @return true if the user approval is needed; otherwise, false.
+ * @return true if user approval dialog is displayed and the network is pending.
*/
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.
+ // is not allowed, TOFU must be used for an Enterprise network without certs. This should
+ // not happen because the TOFU flag will be set during boot if these conditions are met.
if (mIsTrustOnFirstUseSupported && !mIsInsecureEnterpriseConfigurationAllowed
- && !mCurConfig.enterpriseConfig.isTrustOnFirstUseEnabled()) {
- Log.d(TAG, "Trust On First Use is not enabled.");
- handleError(mCurConfig.SSID);
- return true;
+ && !mCurrentTofuConfig.enterpriseConfig.isTrustOnFirstUseEnabled()) {
+ Log.e(TAG, "Upgrade insecure connection to TOFU.");
+ mCurrentTofuConfig.enterpriseConfig.enableTrustOnFirstUse(true);
}
if (useTrustOnFirstUse()) {
- if (null == mPendingCaCert) {
- Log.d(TAG, "No valid CA cert for TLS-based connection.");
- handleError(mCurConfig.SSID);
- return true;
- } else if (null == mPendingServerCert) {
- Log.d(TAG, "No valid Server cert for TLS-based connection.");
- handleError(mCurConfig.SSID);
- return true;
+ if (null == mPendingRootCaCert) {
+ Log.e(TAG, "No valid CA cert for TLS-based connection.");
+ handleError(mCurrentTofuConfig.SSID);
+ return false;
}
+ if (null == mPendingServerCert) {
+ Log.e(TAG, "No valid Server cert for TLS-based connection.");
+ handleError(mCurrentTofuConfig.SSID);
+ return false;
+ }
+ if (!isServerCertChainValid()) {
+ Log.e(TAG, "Server cert chain is invalid.");
+ String ssid = mCurrentTofuConfig.SSID;
+ handleError(ssid);
+ createCertificateErrorNotification(isUserSelected, ssid);
+ return false;
+ }
+ } else if (mIsInsecureEnterpriseConfigurationAllowed) {
+ Log.i(TAG, "Insecure networks without a Root CA cert are allowed.");
+ return false;
}
Log.d(TAG, "startUserApprovalIfNecessaryForInsecureEapNetwork: mIsUserSelected="
@@ -282,13 +367,114 @@ public class InsecureEapNetworkHandler {
return true;
}
+ /**
+ * Create a notification or a dialog when a server certificate is invalid
+ */
+ private void createCertificateErrorNotification(boolean isUserSelected, String ssid) {
+ String title = mContext.getString(R.string.wifi_tofu_invalid_cert_chain_title, 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);
+
+ if (TextUtils.isEmpty(title) || TextUtils.isEmpty(message)) return;
+
+ 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());
+ }
+ }
+
+ /**
+ * Disable network selection, disconnect if necessary, and clear PMK cache
+ */
+ private void putNetworkOnHold() {
+ // 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
+ 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 +499,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();
@@ -337,16 +524,22 @@ public class InsecureEapNetworkHandler {
@VisibleForTesting
void handleReject(@NonNull String ssid) {
if (!isConnectionValid(ssid)) return;
+ boolean disconnectRequired = !useTrustOnFirstUse();
- mWifiConfigManager.allowAutojoin(mCurConfig.networkId, false);
+ mWifiConfigManager.updateNetworkSelectionStatus(mCurrentTofuConfig.networkId,
+ WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
dismissDialogAndNotification();
clearInternalData();
- clearNativeData();
-
- if (null != mCallbacks) mCallbacks.onReject(ssid);
+ if (disconnectRequired) clearNativeData();
+ if (null != mCallbacks) mCallbacks.onReject(ssid, disconnectRequired);
}
private void handleError(@Nullable String ssid) {
+ if (mCurrentTofuConfig != null) {
+ mWifiConfigManager.updateNetworkSelectionStatus(mCurrentTofuConfig.networkId,
+ WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER);
+ }
dismissDialogAndNotification();
clearInternalData();
clearNativeData();
@@ -355,9 +548,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 +568,39 @@ 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)) {
+ final Date expiration = mPendingServerCert.getNotAfter();
+ if (expiration != null) {
contentBuilder.append(mContext.getString(
- R.string.wifi_ca_cert_dialog_message_contact_text,
- mPendingCaCertSubjectInfo.email));
+ R.string.wifi_ca_cert_dialog_message_expiration_text,
+ DateFormat.getMediumDateFormat(mContext).format(expiration)));
+ }
+ final String fingerprint = getDigest(mPendingServerCert, "SHA256");
+ if (!TextUtils.isEmpty(fingerprint)) {
+ contentBuilder.append(mContext.getString(
+ R.string.wifi_ca_cert_dialog_message_signature_name_text, fingerprint));
}
- 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 +620,39 @@ public class InsecureEapNetworkHandler {
new WifiDialogManager.SimpleDialogCallback() {
@Override
public void onPositiveButtonClicked() {
- handleAccept(mCurConfig.SSID);
+ if (mCurrentTofuConfig == null) {
+ return;
+ }
+ Log.d(TAG, "User accepted the server certificate");
+ handleAccept(mCurrentTofuConfig.SSID);
}
@Override
public void onNegativeButtonClicked() {
- handleReject(mCurConfig.SSID);
+ if (mCurrentTofuConfig == null) {
+ return;
+ }
+ Log.d(TAG, "User rejected the server certificate");
+ handleReject(mCurrentTofuConfig.SSID);
}
@Override
public void onNeutralButtonClicked() {
// Not used.
- handleReject(mCurConfig.SSID);
+ if (mCurrentTofuConfig == null) {
+ return;
+ }
+ Log.d(TAG, "User input neutral");
+ handleReject(mCurrentTofuConfig.SSID);
}
@Override
public void onCancelled() {
- handleReject(mCurConfig.SSID);
+ if (mCurrentTofuConfig == null) {
+ return;
+ }
+ Log.d(TAG, "User input canceled");
+ handleReject(mCurrentTofuConfig.SSID);
}
},
new WifiThreadRunner(mHandler));
@@ -460,16 +669,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 +691,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 +712,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 +733,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,19 +763,47 @@ 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;
}
return true;
}
+ @VisibleForTesting
+ static String getDigest(X509Certificate x509Certificate, String algorithm) {
+ if (x509Certificate == null) {
+ return "";
+ }
+ try {
+ byte[] bytes = x509Certificate.getEncoded();
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ byte[] digest = md.digest(bytes);
+ return fingerprint(digest);
+ } catch (CertificateEncodingException ignored) {
+ return "";
+ } catch (NoSuchAlgorithmException ignored) {
+ return "";
+ }
+ }
+
+ private static String fingerprint(byte[] bytes) {
+ if (bytes == null) {
+ return "";
+ }
+ StringJoiner sj = new StringJoiner(":");
+ for (byte b : bytes) {
+ sj.add(HexDump.toHexString(b));
+ }
+ return sj.toString();
+ }
+
/** The callbacks object to notify the consumer. */
public static class InsecureEapNetworkHandlerCallbacks {
/**
@@ -576,8 +816,9 @@ public class InsecureEapNetworkHandler {
* When a certificate is rejected, this callback is called.
*
* @param ssid SSID of the network.
+ * @param disconnectRequired Set to true if the network is currently connected
*/
- public void onReject(@NonNull String ssid) {}
+ public void onReject(@NonNull String ssid, boolean disconnectRequired) {}
/**
* When there are no valid data to handle this insecure EAP network,
* this callback is called.
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index d3e20bef27..ee6fea7ac8 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -16,6 +16,7 @@
package com.android.server.wifi;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.wifi.WifiManager.WIFI_FEATURE_TRUST_ON_FIRST_USE;
import android.Manifest;
@@ -1433,6 +1434,30 @@ public class WifiConfigManager {
existingInternalConfig);
}
+ if (config.isEnterprise()
+ && config.enterpriseConfig.isEapMethodServerCertUsed()
+ && !config.enterpriseConfig.isMandatoryParameterSetForServerCertValidation()
+ && !config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
+ boolean isSettingsOrSuw = mContext.checkPermission(Manifest.permission.NETWORK_SETTINGS,
+ -1 /* pid */, uid) == PERMISSION_GRANTED
+ || mContext.checkPermission(Manifest.permission.NETWORK_SETUP_WIZARD,
+ -1 /* pid */, uid) == PERMISSION_GRANTED;
+ if (!(mWifiInjector.getWifiGlobals().isInsecureEnterpriseConfigurationAllowed()
+ && isSettingsOrSuw)) {
+ Log.e(TAG, "Enterprise network configuration is missing either a Root CA "
+ + "or a domain name");
+ return new Pair<>(
+ new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
+ existingInternalConfig);
+ }
+ Log.w(TAG, "Insecure Enterprise network " + config.SSID
+ + " configured by Settings/SUW");
+
+ // Implicit user approval, when creating an insecure connection which is allowed
+ // in the configuration of the device
+ newInternalConfig.enterpriseConfig.setUserApproveNoCaCert(true);
+ }
+
// Update the keys for saved enterprise networks. For Passpoint, the certificates
// and keys are installed at the time the provider is installed. For suggestion enterprise
// network the certificates and keys are installed at the time the suggestion is added
@@ -1484,11 +1509,6 @@ public class WifiConfigManager {
newInternalConfig.getNetworkSelectionStatus().setHasEverConnected(false);
}
- // Ensure that the user approve flag is set to false for a new network.
- if (newNetwork && config.isEnterprise()) {
- config.enterpriseConfig.setUserApproveNoCaCert(false);
- }
-
// Add it to our internal map. This will replace any existing network configuration for
// updates.
try {
@@ -4130,7 +4150,7 @@ public class WifiConfigManager {
try {
if (newConfig.enterpriseConfig.isTrustOnFirstUseEnabled()) {
newConfig.enterpriseConfig.setCaCertificateForTrustOnFirstUse(caCert);
- // setCaCertificate will mark that this CA certifiate should be removed on
+ // setCaCertificate will mark that this CA certificate should be removed on
// removing this configuration.
newConfig.enterpriseConfig.enableTrustOnFirstUse(false);
} else {
diff --git a/service/java/com/android/server/wifi/WifiKeyStore.java b/service/java/com/android/server/wifi/WifiKeyStore.java
index a69614090c..deb2e9d94c 100644
--- a/service/java/com/android/server/wifi/WifiKeyStore.java
+++ b/service/java/com/android/server/wifi/WifiKeyStore.java
@@ -315,38 +315,41 @@ public class WifiKeyStore {
if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)) {
// Read the CA certificates, and initialize
String[] caAliases = config.enterpriseConfig.getCaCertificateAliases();
-
- if (caAliases == null || caAliases.length == 0) {
- Log.e(TAG, "No CA aliases in profile");
- return false;
- }
-
int caCertType = -1;
- int prevCaCertType = -1;
- for (String caAlias : caAliases) {
- Certificate caCert = null;
- try {
- caCert = mKeyStore.getCertificate(caAlias);
- } catch (KeyStoreException e) {
- Log.e(TAG, "Failed to get Suite-B certificate", e);
- }
- if (caCert == null || !(caCert instanceof X509Certificate)) {
- Log.e(TAG, "Failed reading CA certificate for Suite-B");
- return false;
- }
- // Confirm that the CA certificate is compatible with Suite-B requirements
- caCertType = getSuiteBCipherFromCert((X509Certificate) caCert);
- if (caCertType < 0) {
+ // In TOFU mode, configure the security mode based on the user certificate only.
+ if (!config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
+ if (caAliases == null || caAliases.length == 0) {
+ Log.e(TAG, "No CA aliases in profile");
return false;
}
- if (prevCaCertType != -1) {
- if (prevCaCertType != caCertType) {
- Log.e(TAG, "Incompatible CA certificates");
+
+ int prevCaCertType = -1;
+ for (String caAlias : caAliases) {
+ Certificate caCert = null;
+ try {
+ caCert = mKeyStore.getCertificate(caAlias);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, "Failed to get Suite-B certificate", e);
+ }
+ if (caCert == null || !(caCert instanceof X509Certificate)) {
+ Log.e(TAG, "Failed reading CA certificate for Suite-B");
return false;
}
+
+ // Confirm that the CA certificate is compatible with Suite-B requirements
+ caCertType = getSuiteBCipherFromCert((X509Certificate) caCert);
+ if (caCertType < 0) {
+ return false;
+ }
+ if (prevCaCertType != -1) {
+ if (prevCaCertType != caCertType) {
+ Log.e(TAG, "Incompatible CA certificates");
+ return false;
+ }
+ }
+ prevCaCertType = caCertType;
}
- prevCaCertType = caCertType;
}
Certificate clientCert = null;
@@ -366,7 +369,8 @@ public class WifiKeyStore {
return false;
}
- if (clientCertType == caCertType) {
+ if (clientCertType == caCertType
+ || config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
config.enableSuiteBCiphers(
clientCertType == WifiConfiguration.SuiteBCipher.ECDHE_ECDSA,
clientCertType == WifiConfiguration.SuiteBCipher.ECDHE_RSA);
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..cef996fef1 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
@@ -7853,18 +7853,18 @@ public class ClientModeImplTest extends WifiBaseTest {
WifiManager.WIFI_FEATURE_TRUST_ON_FIRST_USE);
}
mCmi.mInsecureEapNetworkHandler = mInsecureEapNetworkHandler;
- when(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(anyBoolean()))
- .thenReturn(true);
WifiConfiguration eapTlsConfig = spy(WifiConfigurationTestUtil.createEapNetwork(
WifiEnterpriseConfig.Eap.TLS, WifiEnterpriseConfig.Phase2.NONE));
eapTlsConfig.networkId = FRAMEWORK_NETWORK_ID;
eapTlsConfig.SSID = TEST_SSID;
- if (isAtLeastT) {
+ if (isAtLeastT && isTrustOnFirstUseSupported) {
eapTlsConfig.enterpriseConfig.enableTrustOnFirstUse(true);
}
eapTlsConfig.enterpriseConfig.setCaPath("");
eapTlsConfig.enterpriseConfig.setDomainSuffixMatch("");
+ eapTlsConfig.setRandomizedMacAddress(TEST_LOCAL_MAC_ADDRESS);
+ eapTlsConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
initializeAndAddNetworkAndVerifySuccess(eapTlsConfig);
@@ -7879,31 +7879,32 @@ 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());
-
+ if (isTrustOnFirstUseSupported) {
+ assertEquals("DisconnectedState", getCurrentState().getName());
+ }
return eapTlsConfig;
}
@@ -7919,9 +7920,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));
}
/**
@@ -7934,9 +7933,15 @@ public class ClientModeImplTest extends WifiBaseTest {
assumeTrue(SdkLevel.isAtLeastT());
WifiConfiguration testConfig = setupTrustOnFirstUse(true, true, true);
- mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID);
+ mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID, false);
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 +7956,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 +7978,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));
}
/**
@@ -7983,9 +7992,15 @@ public class ClientModeImplTest extends WifiBaseTest {
assumeTrue(SdkLevel.isAtLeastT());
WifiConfiguration testConfig = setupTrustOnFirstUse(true, true, false);
- mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID);
+ mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID, false);
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 +8015,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 +8036,8 @@ public class ClientModeImplTest extends WifiBaseTest {
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onAccept(testConfig.SSID);
mLooper.dispatchAll();
- injectDhcpSuccess();
- mLooper.dispatchAll();
- assertEquals("L3ConnectedState", getCurrentState().getName());
+ verify(mWifiMetrics, never()).endConnectionEvent(
+ any(), anyInt(), anyInt(), anyInt(), anyInt());
}
/**
@@ -8030,23 +8050,10 @@ public class ClientModeImplTest extends WifiBaseTest {
assumeFalse(SdkLevel.isAtLeastT());
WifiConfiguration testConfig = setupLegacyEapNetworkTest(true);
- mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID);
- mLooper.dispatchAll();
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
- }
-
- /**
- * Verify legacy EAP network handling.
- * - This network is automatically connected.
- * - Errors occur in InsecureEapNetworkHandler.
- */
- @Test
- public void verifyLegacyEapNetworkErrorWhenConnectByUser() throws Exception {
- assumeFalse(SdkLevel.isAtLeastT());
- WifiConfiguration testConfig = setupLegacyEapNetworkTest(true);
-
- mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onError(testConfig.SSID);
+ mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID, true);
mLooper.dispatchAll();
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
}
@@ -8061,40 +8068,26 @@ public class ClientModeImplTest extends WifiBaseTest {
WifiConfiguration testConfig = setupLegacyEapNetworkTest(false);
mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onAccept(testConfig.SSID);
- injectDhcpSuccess();
mLooper.dispatchAll();
- assertEquals("L3ConnectedState", getCurrentState().getName());
+ verify(mWifiMetrics, never()).endConnectionEvent(
+ any(), anyInt(), anyInt(), anyInt(), anyInt());
}
/**
* Verify legacy EAP network handling.
* - This network is automatically connected.
- * - Tap "Don't connect" on the notification
+ * - Tap "Disconnect now" on the notification
*/
@Test
public void verifyLegacyEapNetworkRejectOnNotificationWhenAutoConnect() throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
WifiConfiguration testConfig = setupLegacyEapNetworkTest(false);
- mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID);
+ mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onReject(testConfig.SSID, true);
mLooper.dispatchAll();
-
verify(mFrameworkFacade, never()).makeAlertDialogBuilder(any());
- verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
- }
-
- /**
- * Verify legacy EAP network handling.
- * - This network is automatically connected.
- * - Errors occur in InsecureEapNetworkHandler.
- */
- @Test
- public void verifyLegacyEapNetworkErrorWhenAutoConnect() throws Exception {
- assumeFalse(SdkLevel.isAtLeastT());
- WifiConfiguration testConfig = setupLegacyEapNetworkTest(false);
-
- mCmi.mInsecureEapNetworkHandlerCallbacksImpl.onError(testConfig.SSID);
- mLooper.dispatchAll();
+ verify(mWifiConnectivityManager, never())
+ .forceConnectivityScan(eq(ClientModeImpl.WIFI_WORK_SOURCE));
verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
}
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..b83f6e7e6c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/InsecureEapNetworkHandlerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/InsecureEapNetworkHandlerTest.java
@@ -16,15 +16,20 @@
package com.android.server.wifi;
+import static com.android.server.wifi.InsecureEapNetworkHandler.TOFU_ANONYMOUS_IDENTITY;
+
import static org.junit.Assert.assertEquals;
+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 +42,15 @@ 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.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,11 @@ 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!";
+ private static final String TEST_EXPECTED_SHA_256_SIGNATURE = "78:A6:27:31:03:D1:7C:39:A0:B6:12"
+ + ":6E:22:6C:EC:70:E3:33:37:F4:BC:6A:38:06:74:01:B5:4A:33:E7:8E:AD";
@Mock WifiContext mContext;
@Mock WifiConfigManager mWifiConfigManager;
@@ -94,11 +111,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 ->
+ "SHA-256 Fingerprint:\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);
@@ -205,8 +245,11 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
- assertTrue(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks).onError(eq(config.SSID));
+ verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER));
}
/**
@@ -220,7 +263,7 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
- assertTrue(mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks, never()).onError(any());
}
@@ -311,9 +354,40 @@ 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))));
+ try {
+ when(mockCert.getEncoded()).thenReturn(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
+ } catch (Exception e) {
+ // nothing
+ }
+ 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 +395,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 +414,34 @@ 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());
+ assertTrue(TextUtils.isEmpty(config.enterpriseConfig.getAnonymousIdentity()));
+ assertEquals(TEST_PASSWORD, config.enterpriseConfig.getPassword());
+ }
mInsecureEapNetworkHandler.prepareConnection(config);
+ if (isTrustOnFirstUseSupported && config.enterpriseConfig.isTrustOnFirstUseEnabled()
+ && (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()));
+ assertEquals(TOFU_ANONYMOUS_IDENTITY, config.enterpriseConfig.getAnonymousIdentity());
+ assertTrue(TextUtils.isEmpty(config.enterpriseConfig.getPassword()));
+ }
+
if (isTrustOnFirstUseSupported && config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
verify(mContext, atLeastOnce()).registerReceiver(
mBroadcastReceiverCaptor.capture(),
@@ -379,34 +475,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 +499,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);
}
/**
@@ -440,18 +516,19 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
public void verifyOnErrorWithoutCert() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
- boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
- assertEquals(needUserApproval,
- mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ 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 the connection should be terminated.
+ * Verify that the connection should be upgraded to TOFU.
* - TOFU is supported.
* - Insecure EAP network is not allowed.
* - TOFU is not enabled
@@ -461,17 +538,18 @@ public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
- boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
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));
+ mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
+ assertTrue(config.enterpriseConfig.isTrustOnFirstUseEnabled());
}
/**
@@ -479,51 +557,179 @@ 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 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));
+ 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));
+
+ 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);
+
+ 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);
+
+ 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);
+ 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);
+ 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,
int action, boolean isTrustOnFirstUseSupported, boolean isUserSelected,
boolean needUserApproval, X509Certificate expectedCaCert,
X509Certificate expectedServerCert) throws Exception {
- assertEquals(needUserApproval,
- mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected));
+ 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 +739,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 +750,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 +775,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,8 +786,11 @@ 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(mCallbacks).onReject(eq(config.SSID));
+ verify(mWifiConfigManager, atLeastOnce())
+ .updateNetworkSelectionStatus(eq(config.networkId),
+ eq(WifiConfiguration.NetworkSelectionStatus
+ .DISABLED_BY_WIFI_MANAGER));
+ verify(mCallbacks).onReject(eq(config.SSID), eq(!isTrustOnFirstUseSupported));
} else if (action == ACTION_TAP) {
verify(mWifiDialogManager).createSimpleDialogWithUrl(
any(), any(), any(), anyInt(), anyInt(), any(), any(), any(), any(), any());
@@ -586,4 +799,47 @@ 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));
+ }
+ }
+
+ @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);
+ }
+
+ /**
+ * Verify the getDigest and fingerprint methods
+ */
+ @Test
+ public void verifyGetDigest() throws Exception {
+ X509Certificate mockServerCert = generateMockCert("server", "ca", false);
+ assertEquals(mInsecureEapNetworkHandler.getDigest(mockServerCert, "SHA256"),
+ TEST_EXPECTED_SHA_256_SIGNATURE);
+ }
}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index efb62b127f..f8c9123694 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -45,6 +45,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
+import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.test.MockAnswerUtil.AnswerWithArguments;
@@ -7665,4 +7666,72 @@ public class WifiConfigManagerTest extends WifiBaseTest {
assertEquals(TEST_UPDATE_NAME, mWifiConfigManager
.getConfiguredNetwork(openNetId).creatorName);
}
+
+ /**
+ * Verify that if the caller has NETWORK_SETTINGS permission, and the overlay
+ * config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW is set, then it can add an
+ * insecure Enterprise network, with Root CA certificate not set and/or domain name not set.
+ */
+ @Test
+ public void testAddInsecureEnterpriseNetworkWithNetworkSettingsPerm() throws Exception {
+ when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mContext.checkPermission(eq(Manifest.permission.NETWORK_SETUP_WIZARD),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+
+ // First set flag to not allow
+ when(mWifiGlobals.isInsecureEnterpriseConfigurationAllowed()).thenReturn(false);
+
+ // Create an insecure Enterprise network
+ WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+ config.enterpriseConfig.setCaPath(null);
+ config.enterpriseConfig.setDomainSuffixMatch(null);
+ assertFalse(config.enterpriseConfig.isUserApproveNoCaCert());
+
+ // Verify operation fails
+ NetworkUpdateResult result = addNetworkToWifiConfigManager(config);
+ assertFalse(result.isSuccess());
+
+ // Set flag to allow
+ when(mWifiGlobals.isInsecureEnterpriseConfigurationAllowed()).thenReturn(true);
+
+ // Verify operation succeeds
+ NetworkUpdateResult res = addNetworkToWifiConfigManager(config);
+ assertTrue(res.isSuccess());
+ config = mWifiConfigManager.getConfiguredNetwork(res.getNetworkId());
+ assertNotNull(config);
+ assertTrue(config.enterpriseConfig.isUserApproveNoCaCert());
+ }
+
+ /**
+ * Verify that if the caller does NOT have NETWORK_SETTINGS permission, then it cannot add an
+ * insecure Enterprise network, with Root CA certificate not set and/or domain name not set,
+ * regardless of the overlay config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW
+ * value.
+ */
+ @Test
+ public void testAddInsecureEnterpriseNetworkWithNoNetworkSettingsPerm() throws Exception {
+ when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+ when(mContext.checkPermission(eq(Manifest.permission.NETWORK_SETUP_WIZARD),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+
+ // First set flag to not allow
+ when(mWifiGlobals.isInsecureEnterpriseConfigurationAllowed()).thenReturn(false);
+
+ // Create an insecure Enterprise network
+ WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+ config.enterpriseConfig.setCaPath(null);
+ config.enterpriseConfig.setDomainSuffixMatch(null);
+
+ // Verify operation fails
+ NetworkUpdateResult result = addNetworkToWifiConfigManager(config);
+ assertFalse(result.isSuccess());
+
+ // Set flag to allow
+ when(mWifiGlobals.isInsecureEnterpriseConfigurationAllowed()).thenReturn(true);
+
+ // Verify operation still fails
+ assertFalse(addNetworkToWifiConfigManager(config).isSuccess());
+ }
}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
index eefa46bb7c..9dc610b275 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
@@ -191,6 +191,7 @@ public class WifiConfigurationTestUtil {
if ((security & SECURITY_EAP_SUITE_B) != 0) {
config.addSecurityParams(
WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+ config.enterpriseConfig.setDomainSuffixMatch(TEST_DOM_SUBJECT_MATCH);
}
if ((security & SECURITY_WAPI_PSK) != 0) {
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiKeyStoreTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiKeyStoreTest.java
index 9de443def4..37fbb8f7b2 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiKeyStoreTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiKeyStoreTest.java
@@ -307,6 +307,37 @@ public class WifiKeyStoreTest extends WifiBaseTest {
savedNetwork.allowedSuiteBCiphers.get(WifiConfiguration.SuiteBCipher.ECDHE_ECDSA));
}
+ /**
+ * Test configuring WPA3-Enterprise in 192-bit mode for RSA 3072 correctly when CA and client
+ * certificates are of RSA 3072 type and the network is Suite-B.
+ */
+ @Test
+ public void testConfigureSuiteBRsa3072UserAndTofu() throws Exception {
+ // No Root CA certificates, but TOFU is on - Setting the suite-b mode by the user cert
+ when(mWifiEnterpriseConfig.getCaCertificateAliases())
+ .thenReturn(null);
+ when(mWifiEnterpriseConfig.getCaCertificate()).thenReturn(null);
+ when(mWifiEnterpriseConfig.getCaCertificates())
+ .thenReturn(null);
+ when(mWifiEnterpriseConfig.isTrustOnFirstUseEnabled()).thenReturn(true);
+
+ when(mWifiEnterpriseConfig.isEapMethodServerCertUsed()).thenReturn(true);
+ when(mWifiEnterpriseConfig.getClientPrivateKey())
+ .thenReturn(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY);
+ when(mWifiEnterpriseConfig.getClientCertificate()).thenReturn(
+ FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+ when(mWifiEnterpriseConfig.getClientCertificateChain())
+ .thenReturn(new X509Certificate[]{FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+ when(mKeyStore.getCertificate(eq(USER_CERT_ALIAS))).thenReturn(
+ FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+ WifiConfiguration savedNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+ WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+ savedNetwork.enterpriseConfig = mWifiEnterpriseConfig;
+ assertTrue(mWifiKeyStore.updateNetworkKeys(savedNetwork, null));
+ assertTrue(savedNetwork.allowedSuiteBCiphers.get(WifiConfiguration.SuiteBCipher.ECDHE_RSA));
+ }
+
+
/**
* Test configuring WPA3-Enterprise in 192-bit mode for RSA 3072 fails when CA and client
* certificates are not of the same type.