Enable captive portal interstitial on Android
Captive portal interstitial is currently only compiled if
ENABLE_CAPTIVE_PORTAL_DETECTION is present. This flag enables Chrome's
own captive portal detection and is off on Android. However, we still
want to be able to display a captive portal interstitial on Android, by
way of checking the certificates served by portals.
For that reason, this CL enables the captive portal interstitial on Android
and uses the CaptivePortalCertificateList feature to detect captive portal
related certificates. It also adds a Java test for the new interstitial.
Bug: 642993
Change-Id: I6be15ce10a5039fb1b1d8da0e7ef2f2c27dc48b4
Reviewed-on: https://chromium-review.googlesource.com/661795
Commit-Queue: Mustafa Emre Acer <[email protected]>
Reviewed-by: Ted Choc <[email protected]>
Reviewed-by: Emily Stark <[email protected]>
Reviewed-by: Matt Mueller <[email protected]>
Cr-Commit-Position: refs/heads/master@{#504457}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index a3c0ec83..2dc5e3f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -152,6 +152,7 @@
public static final String ANDROID_PAYMENT_APPS = "AndroidPaymentApps";
public static final String ANDROID_SIGNIN_PROMOS = "AndroidSigninPromos";
public static final String AUTOFILL_SCAN_CARDHOLDER_NAME = "AutofillScanCardholderName";
+ public static final String CAPTIVE_PORTAL_CERTIFICATE_LIST = "CaptivePortalCertificateList";
public static final String CCT_BACKGROUND_TAB = "CCTBackgroundTab";
public static final String CCT_EXTERNAL_LINK_HANDLING = "CCTExternalLinkHandling";
public static final String CCT_POST_MESSAGE_API = "CCTPostMessageAPI";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ssl/CaptivePortalHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ssl/CaptivePortalHelper.java
new file mode 100644
index 0000000..ea514052
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ssl/CaptivePortalHelper.java
@@ -0,0 +1,47 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ssl;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/** Helper class for captive portal related methods on Android. */
+@JNINamespace("chrome::android")
+public class CaptivePortalHelper {
+ public static void addCaptivePortalCertificateForTesting(String spkiHash) {
+ nativeAddCaptivePortalCertificateForTesting(spkiHash);
+ }
+
+ @CalledByNative
+ private static String getCaptivePortalServerUrl() {
+ // Since Android N MR2 it is possible that a captive portal was detected with a different
+ // URL than getCaptivePortalServerUrl(). By default, Android uses the URL from
+ // getCaptivePortalServerUrl() first, but there are also two additional fallback HTTP URLs
+ // to probe if the first HTTP probe does not find anything. Using the default URL is
+ // acceptable as the return value is only used by the captive portal interstitial.
+ try {
+ Context context = ContextUtils.getApplicationContext();
+ ConnectivityManager connectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ Method getCaptivePortalServerUrlMethod =
+ connectivityManager.getClass().getMethod("getCaptivePortalServerUrl");
+ return (String) getCaptivePortalServerUrlMethod.invoke(connectivityManager);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ // To avoid crashing, return the default portal check URL on Android.
+ return "http://connectivitycheck.gstatic.com/generate_204";
+ }
+ }
+
+ private CaptivePortalHelper() {}
+
+ private static native void nativeAddCaptivePortalCertificateForTesting(String spkiHash);
+}
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index ab09d1d..c1de1ab 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1039,6 +1039,7 @@
"java/src/org/chromium/chrome/browser/snackbar/TemplatePreservingTextView.java",
"java/src/org/chromium/chrome/browser/snackbar/smartlockautosignin/AutoSigninSnackbarController.java",
"java/src/org/chromium/chrome/browser/snackbar/undo/UndoBarController.java",
+ "java/src/org/chromium/chrome/browser/ssl/CaptivePortalHelper.java",
"java/src/org/chromium/chrome/browser/ssl/SecurityStateModel.java",
"java/src/org/chromium/chrome/browser/suggestions/ContextualSuggestionsCardViewHolder.java",
"java/src/org/chromium/chrome/browser/suggestions/DestructionObserver.java",
@@ -1660,6 +1661,7 @@
"javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java",
"javatests/src/org/chromium/chrome/browser/provider/ProviderBookmarkNodeUnitTest.java",
"javatests/src/org/chromium/chrome/browser/provider/ProviderBookmarksUriTest.java",
+ "javatests/src/org/chromium/chrome/browser/ssl/CaptivePortalTest.java",
"javatests/src/org/chromium/chrome/browser/provider/ProviderSearchesUriTest.java",
"javatests/src/org/chromium/chrome/browser/provider/ProviderTestRule.java",
"javatests/src/org/chromium/chrome/browser/push_messaging/PushMessagingTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ssl/CaptivePortalTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ssl/CaptivePortalTest.java
new file mode 100644
index 0000000..1f53f36
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ssl/CaptivePortalTest.java
@@ -0,0 +1,105 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ssl;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.util.Base64;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.parameter.CommandLineParameter;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.chrome.test.util.browser.TabTitleObserver;
+import org.chromium.content.browser.test.util.Criteria;
+import org.chromium.content.browser.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.net.X509Util;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.ServerCertificate;
+import org.chromium.net.test.util.CertTestUtil;
+
+import java.util.concurrent.Callable;
+
+/** Tests for the Captive portal interstitial. */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@MediumTest
[email protected]({
+ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+ ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
+@CommandLineParameter({"", "enable-features=" + ChromeFeatureList.CAPTIVE_PORTAL_CERTIFICATE_LIST})
+public class CaptivePortalTest {
+ private static final String CAPTIVE_PORTAL_INTERSTITIAL_TITLE_PREFIX = "Connect to";
+ private static final int INTERSTITIAL_TITLE_UPDATE_TIMEOUT_SECONDS = 5;
+
+ @Rule
+ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+
+ private EmbeddedTestServer mServer;
+
+ @Before
+ public void setUp() throws Exception {
+ mActivityTestRule.startMainActivityFromLauncher();
+ mServer = EmbeddedTestServer.createAndStartHTTPSServer(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ ServerCertificate.CERT_MISMATCHED_NAME);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mServer.stopAndDestroyServer();
+ }
+
+ private void waitForInterstitial(final WebContents webContents, final boolean shouldBeShown) {
+ CriteriaHelper.pollUiThread(Criteria.equals(shouldBeShown, new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return webContents.isShowingInterstitialPage();
+ }
+ }));
+ }
+
+ @Test
+ public void testCaptivePortalInterstitial() throws Exception {
+ // Add the SPKI of the root cert to captive portal certificate list.
+ byte[] rootCertSPKI = CertTestUtil.getPublicKeySha256(X509Util.createCertificateFromBytes(
+ CertTestUtil.pemToDer(mServer.getRootCertPemPath())));
+ Assert.assertTrue(rootCertSPKI != null);
+ CaptivePortalHelper.addCaptivePortalCertificateForTesting(
+ "sha256/" + Base64.encodeToString(rootCertSPKI, Base64.NO_WRAP));
+
+ // Navigate the tab to an interstitial with a name mismatch error. This should
+ // result in a captive portal interstitial since the certificate's SPKI hash
+ // is added to the captive portal certificate list.
+ Tab tab = mActivityTestRule.getActivity().getActivityTab();
+ ChromeTabUtils.loadUrlOnUiThread(
+ tab, mServer.getURL("/chrome/test/data/android/navigate/simple.html"));
+ waitForInterstitial(tab.getWebContents(), true);
+ Assert.assertTrue(tab.isShowingInterstitialPage());
+
+ new TabTitleObserver(tab, CAPTIVE_PORTAL_INTERSTITIAL_TITLE_PREFIX) {
+ @Override
+ protected boolean doesTitleMatch(String expectedTitle, String actualTitle) {
+ return actualTitle.indexOf(expectedTitle) == 0;
+ }
+ }
+ .waitForTitleUpdate(INTERSTITIAL_TITLE_UPDATE_TIMEOUT_SECONDS);
+
+ Assert.assertEquals(0, tab.getTitle().indexOf(CAPTIVE_PORTAL_INTERSTITIAL_TITLE_PREFIX));
+ }
+}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 9af9994..a9ede67 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1340,6 +1340,8 @@
"speech/tts_win.cc",
"ssl/bad_clock_blocking_page.cc",
"ssl/bad_clock_blocking_page.h",
+ "ssl/captive_portal_blocking_page.cc",
+ "ssl/captive_portal_blocking_page.h",
"ssl/cert_report_helper.cc",
"ssl/cert_report_helper.h",
"ssl/chrome_expect_ct_reporter.cc",
@@ -2572,8 +2574,6 @@
"captive_portal/captive_portal_tab_reloader.h",
"component_updater/ssl_error_assistant_component_installer.cc",
"component_updater/ssl_error_assistant_component_installer.h",
- "ssl/captive_portal_blocking_page.cc",
- "ssl/captive_portal_blocking_page.h",
"ssl/captive_portal_metrics_recorder.cc",
"ssl/captive_portal_metrics_recorder.h",
]
@@ -3089,6 +3089,7 @@
"search_engines/template_url_service_android.h",
"signin/oauth2_token_service_delegate_android.cc",
"signin/oauth2_token_service_delegate_android.h",
+ "ssl/captive_portal_helper_android.cc",
"ssl/security_state_model_android.cc",
"sync/glue/synced_tab_delegate_android.cc",
"sync/glue/synced_tab_delegate_android.h",
@@ -4185,6 +4186,7 @@
"../android/java/src/org/chromium/chrome/browser/signin/SigninManager.java",
"../android/java/src/org/chromium/chrome/browser/signin/SigninPromoUtil.java",
"../android/java/src/org/chromium/chrome/browser/snackbar/smartlockautosignin/AutoSigninSnackbarController.java",
+ "../android/java/src/org/chromium/chrome/browser/ssl/CaptivePortalHelper.java",
"../android/java/src/org/chromium/chrome/browser/ssl/SecurityStateModel.java",
"../android/java/src/org/chromium/chrome/browser/suggestions/MostVisitedSites.java",
"../android/java/src/org/chromium/chrome/browser/suggestions/MostVisitedSitesBridge.java",
diff --git a/chrome/browser/ssl/captive_portal_blocking_page.cc b/chrome/browser/ssl/captive_portal_blocking_page.cc
index 17961f5..f74ff8d 100644
--- a/chrome/browser/ssl/captive_portal_blocking_page.cc
+++ b/chrome/browser/ssl/captive_portal_blocking_page.cc
@@ -37,6 +37,14 @@
#include "net/ssl/ssl_info.h"
#include "ui/base/l10n/l10n_util.h"
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "chrome/browser/ssl/captive_portal_helper_android.h"
+#include "content/public/common/referrer.h"
+#include "net/android/network_library.h"
+#include "ui/base/window_open_disposition.h"
+#endif
+
namespace {
const char kMetricsName[] = "captive_portal";
@@ -75,8 +83,6 @@
login_url_(login_url),
ssl_info_(ssl_info),
callback_(callback) {
- DCHECK(login_url_.is_valid());
-
if (ssl_cert_reporter) {
cert_report_helper_.reset(new CertReportHelper(
std::move(ssl_cert_reporter), web_contents, request_url, ssl_info,
@@ -118,6 +124,8 @@
return std::string();
#elif defined(OS_LINUX)
ssid = net::GetWifiSSID();
+#elif defined(OS_ANDROID)
+ ssid = net::android::GetWifiSSID();
#endif
// TODO(meacer): Handle non UTF8 SSIDs.
if (!base::IsStringUTF8(ssid))
@@ -155,10 +163,15 @@
load_time_data->SetString("heading", tab_title);
base::string16 paragraph;
- if (login_url_.spec() == captive_portal::CaptivePortalDetector::kDefaultURL) {
- // Captive portal may intercept requests without HTTP redirects, in which
- // case the login url would be the same as the captive portal detection url.
- // Don't show the login url in that case.
+ if (login_url_.is_empty() ||
+ login_url_.spec() == captive_portal::CaptivePortalDetector::kDefaultURL) {
+ // Don't show the login url when it's empty or is the portal detection URL.
+ // login_url_ can be empty when:
+ // - The captive portal intercepted requests without HTTP redirects, in
+ // which case the login url would be the same as the captive portal
+ // detection url.
+ // - The captive portal was detected via Captive portal certificate list.
+ // - The captive portal was reported by the OS.
if (wifi_ssid.empty()) {
paragraph = l10n_util::GetStringUTF16(
is_wifi ? IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIFI
@@ -216,7 +229,21 @@
case security_interstitials::CMD_OPEN_LOGIN:
captive_portal::CaptivePortalMetrics::LogCaptivePortalBlockingPageEvent(
captive_portal::CaptivePortalMetrics::OPEN_LOGIN_PAGE);
+#if defined(OS_ANDROID)
+ {
+ // CaptivePortalTabHelper is not available on Android. Simply open the
+ // login URL in a new tab. login_url_ is also always empty on Android,
+ // use the platform's portal detection URL.
+ const std::string url = chrome::android::GetCaptivePortalServerUrl(
+ base::android::AttachCurrentThread());
+ content::OpenURLParams params(GURL(url), content::Referrer(),
+ WindowOpenDisposition::NEW_FOREGROUND_TAB,
+ ui::PAGE_TRANSITION_LINK, false);
+ web_contents()->OpenURL(params);
+ }
+#else
CaptivePortalTabHelper::OpenLoginTabForWebContents(web_contents(), true);
+#endif
break;
case security_interstitials::CMD_DO_REPORT:
controller()->SetReportingPreference(true);
diff --git a/chrome/browser/ssl/captive_portal_blocking_page.h b/chrome/browser/ssl/captive_portal_blocking_page.h
index c4c234a..dd6cab3 100644
--- a/chrome/browser/ssl/captive_portal_blocking_page.h
+++ b/chrome/browser/ssl/captive_portal_blocking_page.h
@@ -16,10 +16,6 @@
#include "net/ssl/ssl_info.h"
#include "url/gurl.h"
-#if !BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
-#error This file must be built with ENABLE_CAPTIVE_PORTAL_DETECTION flag.
-#endif
-
namespace content {
class NavigationEntry;
class WebContents;
@@ -77,6 +73,8 @@
private:
// URL of the login page, opened when the user clicks the "Connect" button.
+ // If empty, the default captive portal detection URL for the platform will be
+ // used.
const GURL login_url_;
std::unique_ptr<CertReportHelper> cert_report_helper_;
const net::SSLInfo ssl_info_;
diff --git a/chrome/browser/ssl/captive_portal_helper_android.cc b/chrome/browser/ssl/captive_portal_helper_android.cc
new file mode 100644
index 0000000..53f49607
--- /dev/null
+++ b/chrome/browser/ssl/captive_portal_helper_android.cc
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_string.h"
+#include "base/logging.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/ssl/ssl_error_handler.h"
+#include "content/public/browser/browser_thread.h"
+#include "jni/CaptivePortalHelper_jni.h"
+
+namespace chrome {
+namespace android {
+
+void AddCaptivePortalCertificateForTesting(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jclass>& jcaller,
+ const base::android::JavaParamRef<jstring>& jhash) {
+ const std::string hash = ConvertJavaStringToUTF8(env, jhash);
+ auto config_proto =
+ base::MakeUnique<chrome_browser_ssl::SSLErrorAssistantConfig>();
+ config_proto->set_version_id(INT_MAX);
+ config_proto->add_captive_portal_cert()->set_sha256_hash(hash);
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::BindOnce(SSLErrorHandler::SetErrorAssistantProto,
+ std::move(config_proto)));
+}
+
+std::string GetCaptivePortalServerUrl(JNIEnv* env) {
+ return base::android::ConvertJavaStringToUTF8(
+ Java_CaptivePortalHelper_getCaptivePortalServerUrl(env));
+}
+
+} // namespace android
+} // namespace chrome
diff --git a/chrome/browser/ssl/captive_portal_helper_android.h b/chrome/browser/ssl/captive_portal_helper_android.h
new file mode 100644
index 0000000..351f7e3
--- /dev/null
+++ b/chrome/browser/ssl/captive_portal_helper_android.h
@@ -0,0 +1,20 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SSL_CAPTIVE_PORTAL_HELPER_ANDROID_H_
+#define CHROME_BROWSER_SSL_CAPTIVE_PORTAL_HELPER_ANDROID_H_
+
+#include <jni.h>
+#include <stddef.h>
+#include <string>
+
+namespace chrome {
+namespace android {
+
+std::string GetCaptivePortalServerUrl(JNIEnv* env);
+
+} // namespace android
+} // namespace chrome
+
+#endif // CHROME_BROWSER_SSL_CAPTIVE_PORTAL_HELPER_ANDROID_H_
diff --git a/chrome/browser/ssl/ssl_error_handler.cc b/chrome/browser/ssl/ssl_error_handler.cc
index 7698873..3792635 100644
--- a/chrome/browser/ssl/ssl_error_handler.cc
+++ b/chrome/browser/ssl/ssl_error_handler.cc
@@ -22,6 +22,7 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/bad_clock_blocking_page.h"
+#include "chrome/browser/ssl/captive_portal_blocking_page.h"
#include "chrome/browser/ssl/mitm_software_blocking_page.h"
#include "chrome/browser/ssl/ssl_blocking_page.h"
#include "chrome/browser/ssl/ssl_cert_reporter.h"
@@ -46,7 +47,6 @@
#include "chrome/browser/captive_portal/captive_portal_service.h"
#include "chrome/browser/captive_portal/captive_portal_service_factory.h"
#include "chrome/browser/captive_portal/captive_portal_tab_helper.h"
-#include "chrome/browser/ssl/captive_portal_blocking_page.h"
#endif
#if defined(OS_WIN)
@@ -63,10 +63,10 @@
#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
const base::Feature kCaptivePortalInterstitial{
"CaptivePortalInterstitial", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
const base::Feature kCaptivePortalCertificateList{
"CaptivePortalCertificateList", base::FEATURE_DISABLED_BY_DEFAULT};
-#endif
const base::Feature kSSLCommonNameMismatchHandling{
"SSLCommonNameMismatchHandling", base::FEATURE_ENABLED_BY_DEFAULT};
@@ -187,6 +187,7 @@
bool IsCaptivePortalInterstitialEnabled() {
return base::FeatureList::IsEnabled(kCaptivePortalInterstitial);
}
+#endif
std::unique_ptr<std::unordered_set<std::string>> LoadCaptivePortalCertHashes(
const chrome_browser_ssl::SSLErrorAssistantConfig& proto) {
@@ -197,7 +198,6 @@
}
return hashes;
}
-#endif
bool IsMITMSoftwareInterstitialEnabled() {
return base::FeatureList::IsEnabled(kMITMSoftwareInterstitial);
@@ -269,12 +269,10 @@
base::Clock* clock() const;
network_time::NetworkTimeTracker* network_time_tracker() const;
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
// Returns true if any of the SHA256 hashes in |ssl_info| is of a captive
// portal certificate. The set of captive portal hashes is loaded on first
// use.
bool IsKnownCaptivePortalCert(const net::SSLInfo& ssl_info);
-#endif
// Returns the name of a known MITM software provider that matches the
// certificate passed in as the |cert| parameter. Returns empty string if
@@ -290,6 +288,7 @@
void SetClockForTesting(base::Clock* clock);
void SetNetworkTimeTrackerForTesting(
network_time::NetworkTimeTracker* tracker);
+
void SetErrorAssistantProto(
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig>
error_assistant_proto);
@@ -325,12 +324,10 @@
};
EnterpriseManaged is_enterprise_managed_for_testing_;
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
// SPKI hashes belonging to certs treated as captive portals. Null until the
// first time IsKnownCaptivePortalCert() or SetErrorAssistantProto()
// is called.
std::unique_ptr<std::unordered_set<std::string>> captive_portal_spki_hashes_;
-#endif
};
ConfigSingleton::ConfigSingleton()
@@ -366,10 +363,7 @@
error_assistant_proto_.reset();
mitm_software_list_.reset();
is_enterprise_managed_for_testing_ = ENTERPRISE_MANAGED_STATUS_NOT_SET;
-
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
captive_portal_spki_hashes_.reset();
-#endif
}
void ConfigSingleton::SetInterstitialDelayForTesting(
@@ -429,7 +423,6 @@
return true;
}
#endif // #if defined(OS_WIN)
-
return false;
}
@@ -457,13 +450,10 @@
mitm_software_list_ = LoadMITMSoftwareList(*error_assistant_proto_);
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
captive_portal_spki_hashes_ =
LoadCaptivePortalCertHashes(*error_assistant_proto_);
-#endif // ENABLE_CAPTIVE_PORTAL_DETECTION
}
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
bool ConfigSingleton::IsKnownCaptivePortalCert(const net::SSLInfo& ssl_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!captive_portal_spki_hashes_) {
@@ -484,7 +474,6 @@
}
return false;
}
-#endif
bool RegexMatchesAny(const std::vector<std::string>& organization_names,
const std::string& pattern) {
@@ -655,15 +644,11 @@
void SSLErrorHandlerDelegateImpl::ShowCaptivePortalInterstitial(
const GURL& landing_url) {
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
// Show captive portal blocking page. The interstitial owns the blocking page.
(new CaptivePortalBlockingPage(web_contents_, request_url_, landing_url,
std::move(ssl_cert_reporter_), ssl_info_,
callback_))
->Show();
-#else
- NOTREACHED();
-#endif
}
void SSLErrorHandlerDelegateImpl::ShowMITMSoftwareInterstitial(
@@ -821,7 +806,6 @@
const bool only_error_is_name_mismatch =
IsOnlyCertError(net::CERT_STATUS_COMMON_NAME_INVALID);
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
// Check known captive portal certificate list if the only error is
// name-mismatch. If there are multiple errors, it indicates that the captive
// portal landing page itself will have SSL errors, and so it's not a very
@@ -830,11 +814,9 @@
only_error_is_name_mismatch &&
g_config.Pointer()->IsKnownCaptivePortalCert(ssl_info_)) {
RecordUMA(CAPTIVE_PORTAL_CERT_FOUND);
- ShowCaptivePortalInterstitial(
- GURL(captive_portal::CaptivePortalDetector::kDefaultURL));
+ ShowCaptivePortalInterstitial(GURL());
return;
}
-#endif
// The MITM software interstitial is displayed if and only if:
// - the error thrown is not overridable
@@ -913,18 +895,15 @@
}
void SSLErrorHandler::ShowCaptivePortalInterstitial(const GURL& landing_url) {
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
// Show captive portal blocking page. The interstitial owns the blocking page.
RecordUMA(delegate_->IsErrorOverridable()
? SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE
: SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE);
delegate_->ShowCaptivePortalInterstitial(landing_url);
+
// Once an interstitial is displayed, no need to keep the handler around.
// This is the equivalent of "delete this". It also destroys the timer.
web_contents_->RemoveUserData(UserDataKey());
-#else
- NOTREACHED();
-#endif
}
void SSLErrorHandler::ShowMITMSoftwareInterstitial(
diff --git a/chrome/browser/ssl/ssl_error_handler_unittest.cc b/chrome/browser/ssl/ssl_error_handler_unittest.cc
index 6808c22..4c319d82 100644
--- a/chrome/browser/ssl/ssl_error_handler_unittest.cc
+++ b/chrome/browser/ssl/ssl_error_handler_unittest.cc
@@ -437,7 +437,9 @@
RunCaptivePortalTest();
- // Timer should start for captive portal detection.
+#if !defined(OS_ANDROID)
+ // On non-Android platforms (except for iOS where this code is disabled),
+ // timer should start for captive portal detection.
EXPECT_TRUE(error_handler()->IsTimerRunningForTesting());
EXPECT_TRUE(delegate()->captive_portal_checked());
EXPECT_FALSE(delegate()->ssl_interstitial_shown());
@@ -447,10 +449,30 @@
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(error_handler()->IsTimerRunningForTesting());
+
+ // Captive portal should be checked on non-Android platforms.
EXPECT_TRUE(delegate()->captive_portal_checked());
EXPECT_TRUE(delegate()->ssl_interstitial_shown());
EXPECT_FALSE(delegate()->captive_portal_interstitial_shown());
EXPECT_FALSE(delegate()->suggested_url_checked());
+#else
+ // On Android there is no custom captive portal detection logic, so the
+ // timer should not start and an SSL interstitial should be shown
+ // immediately.
+ EXPECT_FALSE(error_handler()->IsTimerRunningForTesting());
+ EXPECT_FALSE(delegate()->captive_portal_checked());
+ EXPECT_TRUE(delegate()->ssl_interstitial_shown());
+ EXPECT_FALSE(delegate()->captive_portal_interstitial_shown());
+ EXPECT_FALSE(delegate()->suggested_url_checked());
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(error_handler()->IsTimerRunningForTesting());
+ EXPECT_FALSE(delegate()->captive_portal_checked());
+ EXPECT_TRUE(delegate()->ssl_interstitial_shown());
+ EXPECT_FALSE(delegate()->captive_portal_interstitial_shown());
+ EXPECT_FALSE(delegate()->suggested_url_checked());
+#endif
// Check that the histogram for the captive portal cert was recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
@@ -1030,8 +1052,6 @@
ASSERT_TRUE(test_server()->ShutdownAndWaitUntilComplete());
}
-#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
-
// Tests that a certificate marked as a known captive portal certificate causes
// the captive portal interstitial to be shown.
TEST_F(SSLErrorAssistantTest, CaptivePortal_FeatureEnabled) {
@@ -1125,42 +1145,6 @@
TestNoCaptivePortalInterstitial();
}
-#else
-
-TEST_F(SSLErrorAssistantTest, CaptivePortal_DisabledByBuild) {
- SetCaptivePortalFeatureEnabled(true);
-
- // Default error for SSLErrorHandlerNameMismatchTest tests is name mismatch,
- // but the feature is disabled by build so a generic SSL interstitial will be
- // displayed.
- base::HistogramTester histograms;
-
- RunCaptivePortalTest();
-
- EXPECT_FALSE(error_handler()->IsTimerRunningForTesting());
- EXPECT_FALSE(delegate()->captive_portal_checked());
- EXPECT_TRUE(delegate()->ssl_interstitial_shown());
- EXPECT_FALSE(delegate()->captive_portal_interstitial_shown());
- EXPECT_FALSE(delegate()->suggested_url_checked());
-
- base::RunLoop().RunUntilIdle();
-
- EXPECT_FALSE(error_handler()->IsTimerRunningForTesting());
- EXPECT_FALSE(delegate()->captive_portal_checked());
- EXPECT_TRUE(delegate()->ssl_interstitial_shown());
- EXPECT_FALSE(delegate()->captive_portal_interstitial_shown());
- EXPECT_FALSE(delegate()->suggested_url_checked());
-
- histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
- histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
- SSLErrorHandler::HANDLE_ALL, 1);
- histograms.ExpectBucketCount(
- SSLErrorHandler::GetHistogramNameForTesting(),
- SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
-}
-
-#endif // BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
-
// Tests that if a certificate matches the issuer common name regex of a MITM
// software entry but not the issuer organization name a MITM software
// interstitial will not be displayed.
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/TabTitleObserver.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/TabTitleObserver.java
index b1a1c8dd..b140b05 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/TabTitleObserver.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/TabTitleObserver.java
@@ -14,7 +14,8 @@
import java.util.concurrent.TimeoutException;
/**
- * A utility class to get notified of title change in a tab.
+ * A utility class to get notified of title change in a tab. Subclasses can
+ * override doesTitleMatch() to customize title matching.
*/
public class TabTitleObserver extends EmptyTabObserver {
private final String mExpectedTitle;
@@ -51,7 +52,7 @@
}
private boolean notifyCallbackIfTitleMatches(Tab tab) {
- if (TextUtils.equals(tab.getTitle(), mExpectedTitle)) {
+ if (doesTitleMatch(mExpectedTitle, tab.getTitle())) {
mCallback.notifyCalled();
return true;
}
@@ -62,4 +63,9 @@
public void onTitleUpdated(Tab tab) {
notifyCallbackIfTitleMatches(tab);
}
+
+ /** @return Whether the title matches the expected condition. */
+ protected boolean doesTitleMatch(String expectedTitle, String actualTitle) {
+ return TextUtils.equals(expectedTitle, actualTitle);
+ }
}
\ No newline at end of file
diff --git a/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java b/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java
index 153760e..4318a70 100644
--- a/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java
+++ b/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java
@@ -475,4 +475,16 @@
}
destroy();
}
+
+ /** Get the path of the PEM file of the root cert. */
+ public String getRootCertPemPath() {
+ try {
+ synchronized (mImplMonitor) {
+ checkServiceLocked();
+ return mImpl.getRootCertPemPath();
+ }
+ } catch (RemoteException e) {
+ throw new EmbeddedTestServerFailure("Failed to get root cert's path", e);
+ }
+ }
}