OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prom
pt_config.h" |
| 6 |
| 7 #include <utility> |
| 8 |
| 9 #include "base/json/json_reader.h" |
| 10 #include "base/memory/ptr_util.h" |
| 11 #include "base/metrics/histogram_macros.h" |
| 12 #include "base/strings/string_number_conversions.h" |
| 13 #include "base/strings/string_piece.h" |
| 14 #include "base/strings/string_util.h" |
| 15 #include "base/values.h" |
| 16 #include "components/url_formatter/url_fixer.h" |
| 17 #include "components/variations/variations_associated_data.h" |
| 18 #include "crypto/sha2.h" |
| 19 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| 20 #include "url/gurl.h" |
| 21 |
| 22 namespace safe_browsing { |
| 23 |
| 24 namespace { |
| 25 |
| 26 const char kSettingsResetPromptFeatureName[] = "SettingsResetPrompt"; |
| 27 const char kDomainHashesParamName[] = "domain_hashes"; |
| 28 |
| 29 } // namespace. |
| 30 |
| 31 const base::Feature kSettingsResetPrompt{kSettingsResetPromptFeatureName, |
| 32 base::FEATURE_DISABLED_BY_DEFAULT}; |
| 33 |
| 34 // static |
| 35 bool SettingsResetPromptConfig::IsPromptEnabled() { |
| 36 // TODO(alito): Add prefs to local state to track when the user was |
| 37 // last prompted and ensure that we only prompt once per reset prompt |
| 38 // wave. |
| 39 return base::FeatureList::IsEnabled(kSettingsResetPrompt); |
| 40 } |
| 41 |
| 42 // static |
| 43 std::unique_ptr<SettingsResetPromptConfig> SettingsResetPromptConfig::Create() { |
| 44 if (!IsPromptEnabled()) |
| 45 return nullptr; |
| 46 |
| 47 auto prompt_config = base::WrapUnique(new SettingsResetPromptConfig()); |
| 48 if (!prompt_config->Init()) |
| 49 return nullptr; |
| 50 |
| 51 return prompt_config; |
| 52 } |
| 53 |
| 54 SettingsResetPromptConfig::SettingsResetPromptConfig() {} |
| 55 |
| 56 SettingsResetPromptConfig::~SettingsResetPromptConfig() {} |
| 57 |
| 58 int SettingsResetPromptConfig::UrlToResetDomainId(const GURL& url) const { |
| 59 DCHECK(IsPromptEnabled()); |
| 60 |
| 61 // Do a best-effort to fix the URL before testing if it is valid. |
| 62 GURL fixed_url = |
| 63 url_formatter::FixupURL(url.possibly_invalid_spec(), std::string()); |
| 64 if (!fixed_url.is_valid()) |
| 65 return -1; |
| 66 |
| 67 // Get the length of the top level domain or registry of the URL. Used |
| 68 // to guard against trying to match the (effective) TLDs themselves. |
| 69 size_t registry_length = net::registry_controlled_domains::GetRegistryLength( |
| 70 fixed_url, net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, |
| 71 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| 72 // Do not proceed, if |fixed_url| does not have a host or consists entirely of |
| 73 // a registry or top domain. |
| 74 if (registry_length == 0 || registry_length == std::string::npos) |
| 75 return -1; |
| 76 |
| 77 // The hashes in the prompt config are generally TLD+1 and identify |
| 78 // only the topmost levels of URLs that we wish to prompt for. Try to |
| 79 // match each sensible suffix of the URL host with the hashes in the |
| 80 // prompt config. For example, if the host is |
| 81 // "www.sub.domain.com", try hashes for: |
| 82 // "www.sub.domain.com" |
| 83 // "sub.domain.com" |
| 84 // "domain.com" |
| 85 // We Do not check top level or registry domains to guard against bad |
| 86 // configuration data. |
| 87 SHA256Hash hash(crypto::kSHA256Length, '\0'); |
| 88 base::StringPiece host = fixed_url.host_piece(); |
| 89 while (host.size() > registry_length) { |
| 90 crypto::SHA256HashString(host, hash.data(), crypto::kSHA256Length); |
| 91 auto iter = domain_hashes_.find(hash); |
| 92 if (iter != domain_hashes_.end()) |
| 93 return iter->second; |
| 94 |
| 95 size_t next_start_pos = host.find('.'); |
| 96 next_start_pos = next_start_pos == base::StringPiece::npos |
| 97 ? base::StringPiece::npos |
| 98 : next_start_pos + 1; |
| 99 host = host.substr(next_start_pos); |
| 100 } |
| 101 |
| 102 return -1; |
| 103 } |
| 104 |
| 105 // Implements the hash function for SHA256Hash objects. Simply uses the |
| 106 // first bytes of the SHA256 hash as its own hash. |
| 107 size_t SettingsResetPromptConfig::SHA256HashHasher::operator()( |
| 108 const SHA256Hash& key) const { |
| 109 DCHECK_EQ(crypto::kSHA256Length, key.size()); |
| 110 // This is safe because |key| contains 32 bytes while a size_t is |
| 111 // either 4 or 8 bytes. |
| 112 return *reinterpret_cast<const size_t*>(key.data()); |
| 113 } |
| 114 |
| 115 // These values are written to logs. New enum values can be added, but |
| 116 // existing enums must never be renumbered or deleted and reused. If you |
| 117 // do add values, also update the corresponding enum definition in the |
| 118 // histograms.xml file. |
| 119 enum SettingsResetPromptConfig::ConfigError : int { |
| 120 CONFIG_ERROR_OK = 1, |
| 121 CONFIG_ERROR_MISSING_DOMAIN_HASHES_PARAM = 2, |
| 122 CONFIG_ERROR_BAD_DOMAIN_HASHES_PARAM = 3, |
| 123 CONFIG_ERROR_BAD_DOMAIN_HASH = 4, |
| 124 CONFIG_ERROR_BAD_DOMAIN_ID = 5, |
| 125 CONFIG_ERROR_DUPLICATE_DOMAIN_HASH = 6, |
| 126 CONFIG_ERROR_MAX |
| 127 }; |
| 128 |
| 129 bool SettingsResetPromptConfig::Init() { |
| 130 if (!IsPromptEnabled()) |
| 131 return false; |
| 132 |
| 133 std::string domain_hashes_json = variations::GetVariationParamValueByFeature( |
| 134 kSettingsResetPrompt, kDomainHashesParamName); |
| 135 ConfigError error = ParseDomainHashes(domain_hashes_json); |
| 136 UMA_HISTOGRAM_ENUMERATION("SettingsResetPrompt.ConfigError", error, |
| 137 CONFIG_ERROR_MAX); |
| 138 return error == CONFIG_ERROR_OK; |
| 139 } |
| 140 |
| 141 SettingsResetPromptConfig::ConfigError |
| 142 SettingsResetPromptConfig::ParseDomainHashes( |
| 143 const std::string& domain_hashes_json) { |
| 144 if (domain_hashes_json.empty()) |
| 145 return CONFIG_ERROR_MISSING_DOMAIN_HASHES_PARAM; |
| 146 |
| 147 // Is the input parseable JSON? |
| 148 std::unique_ptr<base::DictionaryValue> domains_dict = |
| 149 base::DictionaryValue::From(base::JSONReader::Read(domain_hashes_json)); |
| 150 if (!domains_dict || domains_dict->empty()) |
| 151 return CONFIG_ERROR_BAD_DOMAIN_HASHES_PARAM; |
| 152 |
| 153 // The input JSON should be a hash object with hex-encoded 32-byte |
| 154 // hashes as keys and integer IDs as values. For example, |
| 155 // |
| 156 // {"2714..D7": "1", "2821..CB": "2", ...} |
| 157 // |
| 158 // Each key in the hash should be a 64-byte long string and each |
| 159 // integer ID should fit in an int. |
| 160 domain_hashes_.clear(); |
| 161 for (base::DictionaryValue::Iterator iter(*domains_dict); !iter.IsAtEnd(); |
| 162 iter.Advance()) { |
| 163 const std::string& hash_string = iter.key(); |
| 164 if (hash_string.size() != crypto::kSHA256Length * 2) |
| 165 return CONFIG_ERROR_BAD_DOMAIN_HASH; |
| 166 |
| 167 // Convert hex-encoded hash string to its numeric value as bytes. |
| 168 SHA256Hash hash; |
| 169 hash.reserve(crypto::kSHA256Length); |
| 170 if (!base::HexStringToBytes(hash_string, &hash)) |
| 171 return CONFIG_ERROR_BAD_DOMAIN_HASH; |
| 172 |
| 173 // Convert the ID string to an integer. |
| 174 std::string domain_id_string; |
| 175 int domain_id = -1; |
| 176 if (!iter.value().GetAsString(&domain_id_string) || |
| 177 !base::StringToInt(domain_id_string, &domain_id) || domain_id < 0) { |
| 178 return CONFIG_ERROR_BAD_DOMAIN_ID; |
| 179 } |
| 180 |
| 181 if (!domain_hashes_.insert(std::make_pair(std::move(hash), domain_id)) |
| 182 .second) |
| 183 return CONFIG_ERROR_DUPLICATE_DOMAIN_HASH; |
| 184 } |
| 185 |
| 186 return CONFIG_ERROR_OK; |
| 187 } |
| 188 |
| 189 } // namespace safe_browsing. |
OLD | NEW |