Block port 443 for all protocols other than HTTPS or WSS.

This addresses the history leak (on non-preloaded HSTS sites) from https://crbug.com/436451:

  "If we ask Chrome to load http://example.com:443, it will definitely fail, because Chrome will make plain-text HTTP request to port 443 of the server. However, if example.com is a Known HSTS Host of Chrome (meaning either the user has visited https://example.com before, or it is on the HSTS preload list), it will send request to https://example.com:443, and the request will succeed. We can use JavaScript to differentiate the two cases, since in the first case, onerror event is triggered, while in the second case, onload event is triggered.

  Therefore, a malicious website can include well-chosen cross-domain images and use this trick to brute-force a list of domains that users have visited. Note that the list could only contain HSTS-enabled but not preloaded websites."

BUG=436451

Review URL: https://codereview.chromium.org/770343003

Cr-Commit-Position: refs/heads/master@{#306959}
diff --git a/net/base/net_util.cc b/net/base/net_util.cc
index 3b49dff..6f28068 100644
--- a/net/base/net_util.cc
+++ b/net/base/net_util.cc
@@ -107,6 +107,7 @@
   143,  // imap2
   179,  // BGP
   389,  // ldap
+  443,  // https / wss (see https://crbug.com/436451)
   465,  // smtp+ssl
   512,  // print / exec
   513,  // login
@@ -144,6 +145,11 @@
   22,   // ssh
 };
 
+// HTTPS and WSS override the following restricted port.
+static const int kAllowedHttpsOrWssPorts[] = {
+  443,  // https / wss
+};
+
 bool IPNumberPrefixCheck(const IPAddressNumber& ip_number,
                          const unsigned char* ip_prefix,
                          size_t prefix_length_in_bits) {
@@ -320,6 +326,29 @@
   return IsPortAllowedByDefault(port);
 }
 
+bool IsPortAllowedByHttpsOrWss(int port) {
+  int array_size = arraysize(kAllowedHttpsOrWssPorts);
+  for (int i = 0; i < array_size; i++) {
+    if (kAllowedHttpsOrWssPorts[i] == port) {
+        return true;
+    }
+  }
+  // Port not explicitly allowed by HTTPS or WSS, so return the default
+  // restrictions.
+  return IsPortAllowedByDefault(port);
+}
+
+bool IsEffectivePortAllowedByScheme(const GURL& url) {
+  int port = url.EffectiveIntPort();
+  if (url.SchemeIs("ftp")) {
+    return IsPortAllowedByFtp(port);
+  } else if (url.SchemeIs("https") || url.SchemeIs("wss")) {
+    return IsPortAllowedByHttpsOrWss(port);
+  } else {
+    return IsPortAllowedByDefault(port);
+  }
+}
+
 bool IsPortAllowedByOverride(int port) {
   if (g_explicitly_allowed_ports.Get().empty())
     return false;
diff --git a/net/base/net_util.h b/net/base/net_util.h
index 9e9dbad..9802097 100644
--- a/net/base/net_util.h
+++ b/net/base/net_util.h
@@ -260,6 +260,15 @@
 // protocol.  Returns true if |port| is allowed, false if it is restricted.
 NET_EXPORT_PRIVATE bool IsPortAllowedByFtp(int port);
 
+// Checks |port| against a list of ports which are restricted by the HTTPS/WSS
+// protocols.  Returns true if |port| is allowed, false if it is restricted.
+NET_EXPORT_PRIVATE bool IsPortAllowedByHttpsOrWss(int port);
+
+// Checks the effective port of the |url| against a list of ports which are
+// restricted by the scheme of the |url|. Returns true if the port is allowed,
+// false if it is restricted.
+NET_EXPORT_PRIVATE bool IsEffectivePortAllowedByScheme(const GURL& url);
+
 // Check if banned |port| has been overriden by an entry in
 // |explicitly_allowed_ports_|.
 NET_EXPORT_PRIVATE bool IsPortAllowedByOverride(int port);
diff --git a/net/http/http_stream_factory_impl_job.cc b/net/http/http_stream_factory_impl_job.cc
index 83694437..5887d2a 100644
--- a/net/http/http_stream_factory_impl_job.cc
+++ b/net/http/http_stream_factory_impl_job.cc
@@ -623,15 +623,15 @@
                                  &request_info_.url, &origin_url_,
                                  priority_));
 
-  // Don't connect to restricted ports.
-  bool is_port_allowed = IsPortAllowedByDefault(origin_.port());
   if (request_info_.url.SchemeIs("ftp")) {
     // Never share connection with other jobs for FTP requests.
     DCHECK(!waiting_job_);
-
-    is_port_allowed = IsPortAllowedByFtp(origin_.port());
   }
-  if (!is_port_allowed && !IsPortAllowedByOverride(origin_.port())) {
+
+  // Don't connect to restricted ports.
+  // Note: origin_.port() == request_info_.url.EffectiveIntPort()
+  if (!IsEffectivePortAllowedByScheme(request_info_.url) &&
+      !IsPortAllowedByOverride(origin_.port())) {
     if (waiting_job_) {
       waiting_job_->Resume(this);
       waiting_job_ = NULL;
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index b114ecc..39b5979 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -5437,6 +5437,25 @@
   }
 }
 
+// Make sure an HTTP request using the "unsafe" port 443 fails.
+// See: https://crbug.com/436451
+TEST_F(URLRequestTestHTTP, UnsafePort) {
+  TestDelegate d;
+  {
+    scoped_ptr<URLRequest> r(default_context_.CreateRequest(
+        GURL("http://www.google.com:443/"), DEFAULT_PRIORITY, &d, NULL));
+
+    r->Start();
+    EXPECT_TRUE(r->is_pending());
+
+    base::RunLoop().Run();
+
+    EXPECT_FALSE(r->is_pending());
+    EXPECT_EQ(URLRequestStatus::FAILED, r->status().status());
+    EXPECT_EQ(ERR_UNSAFE_PORT, r->status().error());
+  }
+}
+
 // Tests that redirection to an unsafe URL is allowed when it has been marked as
 // safe.
 TEST_F(URLRequestTestHTTP, UnsafeRedirectToWhitelistedUnsafeURL) {