mirror of
https://github.com/Divested-Mobile/DivestOS-Build.git
synced 2024-10-01 01:35:54 -04:00
0dde119d7e
QPR3 is delayed a week now Patches pulled from GrapheneOS and checked against CalyxOS Signed-off-by: Tad <tad@spotco.us>
2186 lines
110 KiB
Diff
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.
|