| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 // Copyright 2016 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 "components/certificate_transparency/ct_policy_manager.h" | 
|  | 6 | 
|  | 7 #include <map> | 
|  | 8 #include <set> | 
|  | 9 #include <string> | 
|  | 10 | 
|  | 11 #include "base/bind.h" | 
|  | 12 #include "base/callback.h" | 
|  | 13 #include "base/location.h" | 
|  | 14 #include "base/sequenced_task_runner.h" | 
|  | 15 #include "base/strings/string_util.h" | 
|  | 16 #include "base/threading/sequenced_task_runner_handle.h" | 
|  | 17 #include "base/values.h" | 
|  | 18 #include "components/certificate_transparency/pref_names.h" | 
|  | 19 #include "components/prefs/pref_registry_simple.h" | 
|  | 20 #include "components/prefs/pref_service.h" | 
|  | 21 #include "components/url_formatter/url_fixer.h" | 
|  | 22 #include "components/url_matcher/url_matcher.h" | 
|  | 23 #include "net/base/host_port_pair.h" | 
|  | 24 | 
|  | 25 namespace certificate_transparency { | 
|  | 26 | 
|  | 27 class CTPolicyManager::CTDelegate | 
|  | 28     : public net::TransportSecurityState::RequireCTDelegate { | 
|  | 29  public: | 
|  | 30   explicit CTDelegate( | 
|  | 31       scoped_refptr<base::SequencedTaskRunner> network_task_runner); | 
|  | 32   ~CTDelegate() override = default; | 
|  | 33 | 
|  | 34   // Called on the prefs task runner. Updates the CTDelegate to require CT | 
|  | 35   // for |required_hosts|, and exclude |excluded_hosts| from CT policies. | 
|  | 36   void UpdateFromPrefs(const base::ListValue* required_hosts, | 
|  | 37                        const base::ListValue* excluded_hosts); | 
|  | 38 | 
|  | 39   // RequireCTDelegate implementation | 
|  | 40   // Called on the network task runner. | 
|  | 41   CTRequirementLevel IsCTRequiredForHost(const std::string& hostname) override; | 
|  | 42 | 
|  | 43  private: | 
|  | 44   struct Filter { | 
|  | 45     bool ct_required = false; | 
|  | 46     bool match_subdomains = false; | 
|  | 47     size_t host_length = 0; | 
|  | 48   }; | 
|  | 49 | 
|  | 50   // Called on the |network_task_runner_|, updates the |url_matcher_| to | 
|  | 51   // require CT for |required_hosts| and exclude |excluded_hosts|, both | 
|  | 52   // of which are Lists of Strings which are URLBlacklist filters. | 
|  | 53   void Update(base::ListValue* required_hosts, base::ListValue* excluded_hosts); | 
|  | 54 | 
|  | 55   // Parses the filters from |host_patterns|, adding them as filters to | 
|  | 56   // |filters_| (with |ct_required| indicating whether or not CT is required | 
|  | 57   // for that host), and updating |*conditions| with the corresponding | 
|  | 58   // URLMatcher::Conditions to match the host. | 
|  | 59   void AddFilters(bool ct_required, | 
|  | 60                   base::ListValue* host_patterns, | 
|  | 61                   url_matcher::URLMatcherConditionSet::Vector* conditions); | 
|  | 62 | 
|  | 63   // Returns true if |lhs| has greater precedence than |rhs|. | 
|  | 64   bool FilterTakesPrecedence(const Filter& lhs, const Filter& rhs) const; | 
|  | 65 | 
|  | 66   scoped_refptr<base::SequencedTaskRunner> network_task_runner_; | 
|  | 67   std::unique_ptr<url_matcher::URLMatcher> url_matcher_; | 
|  | 68   url_matcher::URLMatcherConditionSet::ID next_id_; | 
|  | 69   std::map<url_matcher::URLMatcherConditionSet::ID, Filter> filters_; | 
|  | 70 | 
|  | 71   DISALLOW_COPY_AND_ASSIGN(CTDelegate); | 
|  | 72 }; | 
|  | 73 | 
|  | 74 CTPolicyManager::CTDelegate::CTDelegate( | 
|  | 75     scoped_refptr<base::SequencedTaskRunner> network_task_runner) | 
|  | 76     : network_task_runner_(std::move(network_task_runner)), | 
|  | 77       url_matcher_(new url_matcher::URLMatcher), | 
|  | 78       next_id_(0) {} | 
|  | 79 | 
|  | 80 void CTPolicyManager::CTDelegate::UpdateFromPrefs( | 
|  | 81     const base::ListValue* required_hosts, | 
|  | 82     const base::ListValue* excluded_hosts) { | 
|  | 83   network_task_runner_->PostTask( | 
|  | 84       FROM_HERE, | 
|  | 85       base::Bind(&CTDelegate::Update, base::Unretained(this), | 
|  | 86                  base::Owned(required_hosts->CreateDeepCopy().release()), | 
|  | 87                  base::Owned(excluded_hosts->CreateDeepCopy().release()))); | 
|  | 88 } | 
|  | 89 | 
|  | 90 net::TransportSecurityState::RequireCTDelegate::CTRequirementLevel | 
|  | 91 CTPolicyManager::CTDelegate::IsCTRequiredForHost(const std::string& hostname) { | 
|  | 92   DCHECK(network_task_runner_->RunsTasksOnCurrentThread()); | 
|  | 93 | 
|  | 94   // Scheme and port are ignored by the policy, so it's OK to construct a | 
|  | 95   // new GURL here. However, |hostname| is in network form, not URL form, | 
|  | 96   // so it's necessary to wrap IPv6 addresses in brackets. | 
|  | 97   std::set<url_matcher::URLMatcherConditionSet::ID> matching_ids = | 
|  | 98       url_matcher_->MatchURL( | 
|  | 99           GURL("https://" + net::HostPortPair(hostname, 443).HostForURL())); | 
|  | 100   if (matching_ids.empty()) | 
|  | 101     return CTRequirementLevel::DEFAULT; | 
|  | 102 | 
|  | 103   // Determine the overall policy by determining the most specific policy. | 
|  | 104   std::map<url_matcher::URLMatcherConditionSet::ID, Filter>::const_iterator it = | 
|  | 105       filters_.begin(); | 
|  | 106   const Filter* active_filter = nullptr; | 
|  | 107   for (const auto& match : matching_ids) { | 
|  | 108     // Because both |filters_| and |matching_ids| are sorted on the ID, | 
|  | 109     // treat both as forward-only iterators. | 
|  | 110     while (it != filters_.end() && it->first < match) | 
|  | 111       ++it; | 
|  | 112     if (it == filters_.end()) { | 
|  | 113       NOTREACHED(); | 
|  | 114       break; | 
|  | 115     } | 
|  | 116 | 
|  | 117     if (!active_filter || FilterTakesPrecedence(it->second, *active_filter)) | 
|  | 118       active_filter = &it->second; | 
|  | 119   } | 
|  | 120   CHECK(active_filter); | 
|  | 121 | 
|  | 122   return active_filter->ct_required ? CTRequirementLevel::REQUIRED | 
|  | 123                                     : CTRequirementLevel::NOT_REQUIRED; | 
|  | 124 } | 
|  | 125 | 
|  | 126 void CTPolicyManager::CTDelegate::Update(base::ListValue* required_hosts, | 
|  | 127                                          base::ListValue* excluded_hosts) { | 
|  | 128   DCHECK(network_task_runner_->RunsTasksOnCurrentThread()); | 
|  | 129 | 
|  | 130   url_matcher_.reset(new url_matcher::URLMatcher); | 
|  | 131   filters_.clear(); | 
|  | 132   next_id_ = 0; | 
|  | 133 | 
|  | 134   url_matcher::URLMatcherConditionSet::Vector all_conditions; | 
|  | 135   AddFilters(true, required_hosts, &all_conditions); | 
|  | 136   AddFilters(false, excluded_hosts, &all_conditions); | 
|  | 137 | 
|  | 138   url_matcher_->AddConditionSets(all_conditions); | 
|  | 139 } | 
|  | 140 | 
|  | 141 void CTPolicyManager::CTDelegate::AddFilters( | 
|  | 142     bool ct_required, | 
|  | 143     base::ListValue* hosts, | 
|  | 144     url_matcher::URLMatcherConditionSet::Vector* conditions) { | 
|  | 145   for (size_t i = 0; i < hosts->GetSize(); ++i) { | 
|  | 146     std::string pattern; | 
|  | 147     if (!hosts->GetString(i, &pattern)) | 
|  | 148       continue; | 
|  | 149 | 
|  | 150     Filter filter; | 
|  | 151     filter.ct_required = ct_required; | 
|  | 152 | 
|  | 153     // Parse the pattern just to the hostname, ignoring all other portions of | 
|  | 154     // the URL. | 
|  | 155     url::Parsed parsed; | 
|  | 156     std::string ignored_scheme = url_formatter::SegmentURL(pattern, &parsed); | 
|  | 157     if (!parsed.host.is_nonempty()) | 
|  | 158       continue;  // If there is no host to match, can't apply the filter. | 
|  | 159 | 
|  | 160     std::string lc_host = base::ToLowerASCII( | 
|  | 161         base::StringPiece(pattern).substr(parsed.host.begin, parsed.host.len)); | 
|  | 162     if (lc_host == "*") { | 
|  | 163       // Wildcard hosts are not allowed and ignored. | 
|  | 164       continue; | 
|  | 165     } else if (lc_host[0] == '.') { | 
|  | 166       // A leading dot means exact match and to not match subdomains. | 
|  | 167       lc_host.erase(0, 1); | 
|  | 168       filter.match_subdomains = false; | 
|  | 169     } else { | 
|  | 170       // Canonicalize the host to make sure it's an actual hostname, not an | 
|  | 171       // IP address or a BROKEN canonical host, as matching subdomains is | 
|  | 172       // not desirable for those. | 
|  | 173       url::RawCanonOutputT<char> output; | 
|  | 174       url::CanonHostInfo host_info; | 
|  | 175       url::CanonicalizeHostVerbose(pattern.c_str(), parsed.host, &output, | 
|  | 176                                    &host_info); | 
|  | 177       // TODO(rsleevi): Use canonicalized form? | 
|  | 178       if (host_info.family == url::CanonHostInfo::NEUTRAL) { | 
|  | 179         // Match subdomains (implicit by the omission of '.'). Add in a | 
|  | 180         // leading dot to make sure matches only happen at the domain | 
|  | 181         // component boundary. | 
|  | 182         lc_host.insert(lc_host.begin(), '.'); | 
|  | 183         filter.match_subdomains = true; | 
|  | 184       } else { | 
|  | 185         filter.match_subdomains = false; | 
|  | 186       } | 
|  | 187     } | 
|  | 188     filter.host_length = lc_host.size(); | 
|  | 189 | 
|  | 190     // Create a condition for the URLMatcher that matches the hostname (and/or | 
|  | 191     // subdomains). | 
|  | 192     url_matcher::URLMatcherConditionFactory* condition_factory = | 
|  | 193         url_matcher_->condition_factory(); | 
|  | 194     std::set<url_matcher::URLMatcherCondition> condition_set; | 
|  | 195     condition_set.insert( | 
|  | 196         filter.match_subdomains | 
|  | 197             ? condition_factory->CreateHostSuffixCondition(lc_host) | 
|  | 198             : condition_factory->CreateHostEqualsCondition(lc_host)); | 
|  | 199     conditions->push_back( | 
|  | 200         new url_matcher::URLMatcherConditionSet(next_id_, condition_set)); | 
|  | 201     filters_[next_id_] = filter; | 
|  | 202     ++next_id_; | 
|  | 203   } | 
|  | 204 } | 
|  | 205 | 
|  | 206 bool CTPolicyManager::CTDelegate::FilterTakesPrecedence( | 
|  | 207     const Filter& lhs, | 
|  | 208     const Filter& rhs) const { | 
|  | 209   if (lhs.match_subdomains != rhs.match_subdomains) | 
|  | 210     return !lhs.match_subdomains;  // Prefer the more explicit policy. | 
|  | 211 | 
|  | 212   if (lhs.host_length != rhs.host_length) | 
|  | 213     return lhs.host_length > rhs.host_length;  // Prefer the longer host match. | 
|  | 214 | 
|  | 215   if (lhs.ct_required != rhs.ct_required) | 
|  | 216     return lhs.ct_required;  // Prefer the policy that requires CT. | 
|  | 217 | 
|  | 218   return false; | 
|  | 219 } | 
|  | 220 | 
|  | 221 // static | 
|  | 222 void CTPolicyManager::RegisterPrefs(PrefRegistrySimple* registry) { | 
|  | 223   registry->RegisterListPref(prefs::kCTRequiredHosts); | 
|  | 224   registry->RegisterListPref(prefs::kCTExcludedHosts); | 
|  | 225 } | 
|  | 226 | 
|  | 227 CTPolicyManager::CTPolicyManager( | 
|  | 228     PrefService* pref_service, | 
|  | 229     scoped_refptr<base::SequencedTaskRunner> network_task_runner) | 
|  | 230     : delegate_(new CTDelegate(std::move(network_task_runner))), | 
|  | 231       weak_factory_(this) { | 
|  | 232   pref_change_registrar_.Init(pref_service); | 
|  | 233   pref_change_registrar_.Add( | 
|  | 234       prefs::kCTRequiredHosts, | 
|  | 235       base::Bind(&CTPolicyManager::ScheduleUpdate, base::Unretained(this))); | 
|  | 236   pref_change_registrar_.Add( | 
|  | 237       prefs::kCTExcludedHosts, | 
|  | 238       base::Bind(&CTPolicyManager::ScheduleUpdate, base::Unretained(this))); | 
|  | 239 | 
|  | 240   ScheduleUpdate(); | 
|  | 241 } | 
|  | 242 | 
|  | 243 CTPolicyManager::~CTPolicyManager() {} | 
|  | 244 | 
|  | 245 void CTPolicyManager::Shutdown() { | 
|  | 246   // Cancel any pending updates, since the preferences are going away. | 
|  | 247   weak_factory_.InvalidateWeakPtrs(); | 
|  | 248   pref_change_registrar_.RemoveAll(); | 
|  | 249 } | 
|  | 250 | 
|  | 251 net::TransportSecurityState::RequireCTDelegate* CTPolicyManager::GetDelegate() { | 
|  | 252   return delegate_.get(); | 
|  | 253 } | 
|  | 254 | 
|  | 255 void CTPolicyManager::ScheduleUpdate() { | 
|  | 256   // Cancel any pending updates, and schedule a new update. If this method | 
|  | 257   // is called again, this pending update will be cancelled because the weak | 
|  | 258   // pointer is invalidated, and the new update will take precedence. | 
|  | 259   weak_factory_.InvalidateWeakPtrs(); | 
|  | 260   base::SequencedTaskRunnerHandle::Get()->PostTask( | 
|  | 261       FROM_HERE, | 
|  | 262       base::Bind(&CTPolicyManager::Update, weak_factory_.GetWeakPtr())); | 
|  | 263 } | 
|  | 264 | 
|  | 265 void CTPolicyManager::Update() { | 
|  | 266   delegate_->UpdateFromPrefs( | 
|  | 267       pref_change_registrar_.prefs()->GetList(prefs::kCTRequiredHosts), | 
|  | 268       pref_change_registrar_.prefs()->GetList(prefs::kCTExcludedHosts)); | 
|  | 269 } | 
|  | 270 | 
|  | 271 }  // namespace certificate_transparency | 
| OLD | NEW | 
|---|