blob: b70d6ced887d2a2ecb576b20d79e7fc4272e2054 [file] [log] [blame]
// Copyright 2015 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 "chrome/common/secure_origin_whitelist.h"
#include "base/command_line.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "extensions/common/constants.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/scheme_host_port.h"
namespace {
// Given a hostname pattern with a wildcard such as "*.foo.com", returns
// true if |hostname_pattern| meets both of these conditions:
// 1.) A string matching |hostname_pattern| is a valid hostname.
// 2.) Wildcards only appear beyond the eTLD+1. "*.foo.com" is considered
// valid but "*.com" is not.
bool IsValidWildcardPattern(const std::string& hostname_pattern) {
// Replace wildcards with dummy values to check whether a matching origin is
// valid.
std::string wildcards_replaced;
if (!base::ReplaceChars(hostname_pattern, "*", "a", &wildcards_replaced))
return false;
// Construct a SchemeHostPort with a dummy scheme and port to check that the
// hostname is valid.
url::SchemeHostPort scheme_host_port(
GURL(base::StringPrintf("http://%s:80", wildcards_replaced.c_str())));
if (scheme_host_port.IsInvalid())
return false;
// Check that wildcards only appear beyond the eTLD+1.
size_t registry_length =
net::registry_controlled_domains::PermissiveGetHostRegistryLength(
hostname_pattern,
net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
// std::string::npos should only be returned for empty inputs, which should be
// filtered out by the IsInvalid() check above.
CHECK(registry_length != std::string::npos);
// If there is no registrar portion, the pattern is considered invalid.
if (registry_length == 0)
return false;
// If there is no component before the registrar portion, or if the component
// immediately preceding the registrar portion contains a wildcard, the
// pattern is not considered valid.
std::string host_before_registrar =
hostname_pattern.substr(0, hostname_pattern.size() - registry_length);
std::vector<std::string> components =
base::SplitString(host_before_registrar, ".", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
if (components.size() == 0)
return false;
if (components.back().find("*") != std::string::npos)
return false;
return true;
}
// Canonicalizes each component of |hostname_pattern|, making no changes to
// wildcard components or components that fail canonicalization. For example,
// given a |hostname_pattern| of "TeSt.*.%46oo.com", the output will be
// "test.*.foo.com".
std::string CanonicalizePatternComponents(const std::string& hostname_pattern) {
std::string canonical_host; // Do not modify outside of canon_output.
canonical_host.reserve(hostname_pattern.length());
url::StdStringCanonOutput canon_output(&canonical_host);
for (size_t current = 0; current < hostname_pattern.length(); current++) {
size_t begin = current;
// Advance to next "." or end.
current = hostname_pattern.find('.', begin);
if (current == std::string::npos)
current = hostname_pattern.length();
// Try to append the canonicalized version of this component.
int current_len = base::checked_cast<int>(current - begin);
if (hostname_pattern.substr(begin, current_len) == "*" ||
!url::CanonicalizeHostSubstring(
hostname_pattern.data(),
url::Component(base::checked_cast<int>(begin), current_len),
&canon_output)) {
// Failed to canonicalize this component; append as-is.
canon_output.Append(hostname_pattern.substr(begin, current_len).data(),
current_len);
}
if (current < hostname_pattern.length())
canon_output.push_back('.');
}
canon_output.Complete();
return canonical_host;
}
} // namespace
namespace secure_origin_whitelist {
std::vector<std::string> ParseWhitelist(const std::string& origins_str) {
std::vector<std::string> origin_patterns;
for (const std::string& origin_str : base::SplitString(
origins_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
if (origin_str.find("*") != std::string::npos) {
if (IsValidWildcardPattern(origin_str)) {
std::string canonicalized_pattern =
CanonicalizePatternComponents(origin_str);
if (!canonicalized_pattern.empty()) {
origin_patterns.push_back(canonicalized_pattern);
continue;
}
}
LOG(ERROR) << "Whitelisted secure origin pattern " << origin_str
<< " is not valid; ignoring.";
continue;
}
// Drop .unique() origins, as they are unequal to any other origins.
url::Origin origin(url::Origin::Create(GURL(origin_str)));
if (!origin.opaque())
origin_patterns.push_back(origin.Serialize());
}
UMA_HISTOGRAM_COUNTS_100("Security.TreatInsecureOriginAsSecure",
origin_patterns.size());
#if defined(OS_CHROMEOS)
// For Crostini, we allow access to the default VM/container as a secure
// origin via the hostname penguin.linux.test. We are required to use a
// wildcard for the prefix because we do not know what the port number is.
// https://chromium.googlesource.com/chromiumos/docs/+/master/containers_and_vms.md
origin_patterns.push_back("*.linux.test");
#endif
return origin_patterns;
}
std::vector<std::string> GetWhitelist() {
// If kUnsafelyTreatInsecureOriginAsSecure option is given, then treat the
// value as a comma-separated list of origins or origin patterns. Callers that
// need to also check the kUnsafelyTreatInsecureOriginAsSecure pref value must
// instead use ParseWhitelist directly (as there is no way for GetWhitelist()
// to access prefs). For renderer processes the pref and the switch will
// match, but for non-renderer processes the switch may not be set.
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
std::string origins_str = "";
if (command_line.HasSwitch(switches::kUnsafelyTreatInsecureOriginAsSecure)) {
origins_str = command_line.GetSwitchValueASCII(
switches::kUnsafelyTreatInsecureOriginAsSecure);
}
return ParseWhitelist(origins_str);
}
std::set<std::string> GetSchemesBypassingSecureContextCheck() {
std::set<std::string> schemes;
schemes.insert(extensions::kExtensionScheme);
return schemes;
}
void RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(prefs::kUnsafelyTreatInsecureOriginAsSecure,
/* default_value */ "");
}
} // namespace secure_origin_whitelist