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 const char kConfigErrorMetric[] = "SettingsResetPrompt.ConfigError"; | |
29 | |
30 } // namespace. | |
31 | |
32 const base::Feature kSettingsResetPrompt{kSettingsResetPromptFeatureName, | |
33 base::FEATURE_DISABLED_BY_DEFAULT}; | |
34 | |
35 // static | |
36 bool SettingsResetPromptConfig::IsPromptEnabled() { | |
37 // TODO(alito): Add prefs to local state to track when the user was | |
38 // last prompted and ensure that we only prompt once per reset prompt | |
39 // wave. | |
40 return base::FeatureList::IsEnabled(kSettingsResetPrompt); | |
41 } | |
42 | |
43 // static | |
44 std::unique_ptr<SettingsResetPromptConfig> SettingsResetPromptConfig::Create() { | |
45 if (!IsPromptEnabled()) | |
46 return nullptr; | |
47 | |
48 auto prompt_config = base::WrapUnique(new SettingsResetPromptConfig()); | |
49 if (!prompt_config->Init()) | |
50 return nullptr; | |
51 | |
52 return prompt_config; | |
53 } | |
54 | |
55 SettingsResetPromptConfig::SettingsResetPromptConfig() {} | |
56 | |
57 SettingsResetPromptConfig::~SettingsResetPromptConfig() {} | |
58 | |
59 int SettingsResetPromptConfig::UrlToResetDomainId(const GURL& url) const { | |
60 DCHECK(IsPromptEnabled()); | |
61 | |
62 // Do a best-effort to fix the URL before testing if it is valid. | |
63 GURL fixed_url = | |
64 url_formatter::FixupURL(url.possibly_invalid_spec(), std::string()); | |
65 if (!fixed_url.is_valid()) | |
66 return -1; | |
67 | |
68 // Get the length of the top level domain or registry of the URL. Used | |
69 // to guard against trying to match the (effective) TLDs themselves. | |
70 size_t registry_length = net::registry_controlled_domains::GetRegistryLength( | |
71 fixed_url, net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, | |
72 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); | |
73 // Do not proceed, if |fixed_url| does not have a host or consists entirely of | |
74 // a registry or top domain. | |
75 if (registry_length == 0 || registry_length == std::string::npos) | |
76 return -1; | |
77 | |
78 // The hashes in the prompt config are generally TLD+1 and identify | |
79 // only the topmost levels of URLs that we wish to prompt for. Try to | |
80 // match each sensible suffix of the URL host with the hashes in the | |
81 // prompt config. For example, if the host is | |
82 // "www.sub.domain.com", try hashes for: | |
83 // "www.sub.domain.com" | |
84 // "sub.domain.com" | |
85 // "domain.com" | |
86 // We Do not check top level or registry domains to guard against bad | |
87 // configuration data. | |
88 SHA256Hash hash(crypto::kSHA256Length, '\0'); | |
89 base::StringPiece host = fixed_url.host_piece(); | |
90 while (host.size() > registry_length) { | |
91 crypto::SHA256HashString(host, hash.data(), crypto::kSHA256Length); | |
92 auto iter = domain_hashes_.find(hash); | |
93 if (iter != domain_hashes_.end()) | |
94 return iter->second; | |
95 | |
96 size_t next_start_pos = host.find('.'); | |
97 next_start_pos = next_start_pos == base::StringPiece::npos | |
98 ? base::StringPiece::npos | |
99 : next_start_pos + 1; | |
100 host = host.substr(next_start_pos); | |
101 } | |
102 | |
103 return -1; | |
104 } | |
105 | |
106 // Implements the hash function for SHA256Hash objects. Simply uses the | |
107 // first bytes of the SHA256 hash as its own hash. | |
108 size_t SettingsResetPromptConfig::SHA256HashHasher::operator()( | |
109 const SHA256Hash& key) const { | |
110 DCHECK_EQ(crypto::kSHA256Length, key.size()); | |
111 // This is safe because |key| contains 32 bytes while a size_t is | |
112 // either 4 or 8 bytes. | |
113 return *reinterpret_cast<const size_t*>(key.data()); | |
114 } | |
115 | |
116 // These values are written to logs. New enum values can be added, but | |
117 // existing enums must never be renumbered or deleted and reused. If you | |
118 // do add values, also update the corresponding enum definition in the | |
119 // histograms.xml file. | |
120 enum SettingsResetPromptConfig::ConfigError : int { | |
121 CONFIG_ERROR_OK = 1, | |
122 CONFIG_ERROR_MISSING_DOMAIN_HASHES_PARAM = 2, | |
123 CONFIG_ERROR_BAD_DOMAIN_HASHES_PARAM = 3, | |
124 CONFIG_ERROR_BAD_DOMAIN_HASH = 4, | |
125 CONFIG_ERROR_BAD_DOMAIN_ID = 5, | |
126 CONFIG_ERROR_DUPLICATE_DOMAIN_HASH = 6, | |
127 CONFIG_ERROR_MAX | |
128 }; | |
129 | |
130 bool SettingsResetPromptConfig::Init() { | |
131 if (!IsPromptEnabled()) | |
132 return false; | |
133 | |
134 std::string domain_hashes_json = variations::GetVariationParamValueByFeature( | |
135 kSettingsResetPrompt, kDomainHashesParamName); | |
136 ConfigError error = ParseDomainHashes(domain_hashes_json); | |
137 UMA_HISTOGRAM_ENUMERATION(kConfigErrorMetric, error, CONFIG_ERROR_MAX); | |
rkaplow
2017/01/12 18:42:33
generally people inline the metric name
alito
2017/01/12 18:55:36
Done.
| |
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 |