summaryrefslogtreecommitdiff
path: root/chromium/components/certificate_transparency
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:20:33 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:28:57 +0000
commitd17ea114e5ef69ad5d5d7413280a13e6428098aa (patch)
tree2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/components/certificate_transparency
parent8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff)
downloadqtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7 Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/components/certificate_transparency')
-rw-r--r--chromium/components/certificate_transparency/BUILD.gn7
-rw-r--r--chromium/components/certificate_transparency/ct_policy_manager.cc416
-rw-r--r--chromium/components/certificate_transparency/ct_policy_manager.h54
-rw-r--r--chromium/components/certificate_transparency/ct_policy_manager_unittest.cc405
-rw-r--r--chromium/components/certificate_transparency/features.cc13
-rw-r--r--chromium/components/certificate_transparency/features.h16
-rw-r--r--chromium/components/certificate_transparency/log_dns_client.cc195
-rw-r--r--chromium/components/certificate_transparency/log_dns_client.h49
-rw-r--r--chromium/components/certificate_transparency/log_dns_client_unittest.cc189
-rw-r--r--chromium/components/certificate_transparency/pref_names.cc14
-rw-r--r--chromium/components/certificate_transparency/pref_names.h30
-rw-r--r--chromium/components/certificate_transparency/single_tree_tracker.cc66
-rw-r--r--chromium/components/certificate_transparency/single_tree_tracker.h41
-rw-r--r--chromium/components/certificate_transparency/single_tree_tracker_unittest.cc94
-rw-r--r--chromium/components/certificate_transparency/sth_distributor.cc65
-rw-r--r--chromium/components/certificate_transparency/sth_distributor.h56
-rw-r--r--chromium/components/certificate_transparency/sth_distributor_unittest.cc133
-rw-r--r--chromium/components/certificate_transparency/sth_observer.h29
-rw-r--r--chromium/components/certificate_transparency/sth_reporter.h25
-rw-r--r--chromium/components/certificate_transparency/tree_state_tracker.cc12
-rw-r--r--chromium/components/certificate_transparency/tree_state_tracker.h7
-rw-r--r--chromium/components/certificate_transparency/tree_state_tracker_unittest.cc9
22 files changed, 1400 insertions, 525 deletions
diff --git a/chromium/components/certificate_transparency/BUILD.gn b/chromium/components/certificate_transparency/BUILD.gn
index f55ed3d7ec6..a2dae9e98c7 100644
--- a/chromium/components/certificate_transparency/BUILD.gn
+++ b/chromium/components/certificate_transparency/BUILD.gn
@@ -6,12 +6,18 @@ static_library("certificate_transparency") {
sources = [
"ct_policy_manager.cc",
"ct_policy_manager.h",
+ "features.cc",
+ "features.h",
"log_dns_client.cc",
"log_dns_client.h",
"pref_names.cc",
"pref_names.h",
"single_tree_tracker.cc",
"single_tree_tracker.h",
+ "sth_distributor.cc",
+ "sth_distributor.h",
+ "sth_observer.h",
+ "sth_reporter.h",
"tree_state_tracker.cc",
"tree_state_tracker.h",
]
@@ -35,6 +41,7 @@ source_set("unit_tests") {
"mock_log_dns_traffic.cc",
"mock_log_dns_traffic.h",
"single_tree_tracker_unittest.cc",
+ "sth_distributor_unittest.cc",
"tree_state_tracker_unittest.cc",
]
deps = [
diff --git a/chromium/components/certificate_transparency/ct_policy_manager.cc b/chromium/components/certificate_transparency/ct_policy_manager.cc
index 833014cf817..d896d336254 100644
--- a/chromium/components/certificate_transparency/ct_policy_manager.cc
+++ b/chromium/components/certificate_transparency/ct_policy_manager.cc
@@ -4,41 +4,182 @@
#include "components/certificate_transparency/ct_policy_manager.h"
+#include <algorithm>
+#include <iterator>
#include <map>
#include <set>
#include <string>
+#include <utility>
+#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/location.h"
+#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/values.h"
-#include "components/certificate_transparency/pref_names.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/pref_service.h"
#include "components/url_formatter/url_fixer.h"
#include "components/url_matcher/url_matcher.h"
+#include "crypto/sha2.h"
+#include "net/base/hash_value.h"
#include "net/base/host_port_pair.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/internal/name_constraints.h"
+#include "net/cert/internal/parse_name.h"
+#include "net/cert/internal/parsed_certificate.h"
+#include "net/cert/known_roots.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
namespace certificate_transparency {
+namespace {
+
+// Helper that takes a given net::RDNSequence and returns only the
+// organizationName net::X509NameAttributes.
+class OrgAttributeFilter {
+ public:
+ // Creates a new OrgAttributeFilter for |sequence| that begins iterating at
+ // |head|. Note that |head| can be equal to |sequence.end()|, in which case,
+ // there are no organizationName attributes.
+ explicit OrgAttributeFilter(const net::RDNSequence& sequence)
+ : sequence_head_(sequence.begin()), sequence_end_(sequence.end()) {
+ if (sequence_head_ != sequence_end_) {
+ rdn_it_ = sequence_head_->begin();
+ AdvanceIfNecessary();
+ }
+ }
+
+ bool IsValid() const { return sequence_head_ != sequence_end_; }
+
+ const net::X509NameAttribute& GetAttribute() const {
+ DCHECK(IsValid());
+ return *rdn_it_;
+ }
+
+ void Advance() {
+ DCHECK(IsValid());
+ ++rdn_it_;
+ AdvanceIfNecessary();
+ }
+
+ private:
+ // If the current field is an organization field, does nothing, otherwise,
+ // advances the state to the next organization field, or, if no more are
+ // present, the end of the sequence.
+ void AdvanceIfNecessary() {
+ while (sequence_head_ != sequence_end_) {
+ while (rdn_it_ != sequence_head_->end()) {
+ if (rdn_it_->type == net::TypeOrganizationNameOid())
+ return;
+ ++rdn_it_;
+ }
+ ++sequence_head_;
+ if (sequence_head_ != sequence_end_) {
+ rdn_it_ = sequence_head_->begin();
+ }
+ }
+ }
+
+ net::RDNSequence::const_iterator sequence_head_;
+ net::RDNSequence::const_iterator sequence_end_;
+ net::RelativeDistinguishedName::const_iterator rdn_it_;
+};
+
+// Returns true if |dn_without_sequence| identifies an
+// organizationally-validated certificate, per the CA/Browser Forum's Baseline
+// Requirements, storing the parsed RDNSequence in |*out|.
+bool ParseOrganizationBoundName(net::der::Input dn_without_sequence,
+ net::RDNSequence* out) {
+ if (!net::ParseNameValue(dn_without_sequence, out))
+ return false;
+ for (const auto& rdn : *out) {
+ for (const auto& attribute_type_and_value : rdn) {
+ if (attribute_type_and_value.type == net::TypeOrganizationNameOid())
+ return true;
+ }
+ }
+ return false;
+}
+
+// Returns true if the certificate identified by |leaf_rdn_sequence| is
+// considered to be issued under the same organizational authority as
+// |org_cert|.
+bool AreCertsSameOrganization(const net::RDNSequence& leaf_rdn_sequence,
+ CRYPTO_BUFFER* org_cert) {
+ scoped_refptr<net::ParsedCertificate> parsed_org =
+ net::ParsedCertificate::Create(net::x509_util::DupCryptoBuffer(org_cert),
+ net::ParseCertificateOptions(), nullptr);
+ if (!parsed_org)
+ return false;
+
+ // If the candidate cert has nameConstraints, see if it has a
+ // permittedSubtrees nameConstraint over a DirectoryName that is
+ // organizationally-bound. If so, the enforcement of nameConstraints is
+ // sufficient to consider |org_cert| a match.
+ if (parsed_org->has_name_constraints()) {
+ const net::NameConstraints& nc = parsed_org->name_constraints();
+ for (const auto& permitted_name : nc.permitted_subtrees().directory_names) {
+ net::RDNSequence tmp;
+ if (ParseOrganizationBoundName(permitted_name, &tmp))
+ return true;
+ }
+ }
+
+ net::RDNSequence org_rdn_sequence;
+ if (!net::ParseNameValue(parsed_org->normalized_subject(), &org_rdn_sequence))
+ return false;
+
+ // Finally, try to match the organization fields within |leaf_rdn_sequence|
+ // to |org_rdn_sequence|. As |leaf_rdn_sequence| has already been checked
+ // for all the necessary fields, it's not necessary to check
+ // |org_rdn_sequence|. Iterate through all of the organization fields in
+ // each, doing a byte-for-byte equality check.
+ // Note that this does permit differences in the SET encapsulations between
+ // RelativeDistinguishedNames, although it does still require that the same
+ // number of organization fields appear, and with the same overall ordering.
+ // This is simply as an implementation simplification, and not done for
+ // semantic or technical reasons.
+ OrgAttributeFilter leaf_filter(leaf_rdn_sequence);
+ OrgAttributeFilter org_filter(org_rdn_sequence);
+ while (leaf_filter.IsValid() && org_filter.IsValid()) {
+ if (leaf_filter.GetAttribute().type != org_filter.GetAttribute().type ||
+ leaf_filter.GetAttribute().value_tag !=
+ org_filter.GetAttribute().value_tag ||
+ leaf_filter.GetAttribute().value != org_filter.GetAttribute().value) {
+ return false;
+ }
+ leaf_filter.Advance();
+ org_filter.Advance();
+ }
+
+ // Ensure all attributes were fully consumed.
+ return !leaf_filter.IsValid() && !org_filter.IsValid();
+}
+
+} // namespace
+
class CTPolicyManager::CTDelegate
: public net::TransportSecurityState::RequireCTDelegate {
public:
- explicit CTDelegate(
- scoped_refptr<base::SequencedTaskRunner> network_task_runner);
+ explicit CTDelegate();
~CTDelegate() override = default;
- // Called on the prefs task runner. Updates the CTDelegate to require CT
+ // Updates the CTDelegate to require CT
// for |required_hosts|, and exclude |excluded_hosts| from CT policies.
- void UpdateFromPrefs(const base::ListValue* required_hosts,
- const base::ListValue* excluded_hosts);
+ void UpdateCTPolicies(const std::vector<std::string>& required_hosts,
+ const std::vector<std::string>& excluded_hosts,
+ const std::vector<std::string>& excluded_spkis,
+ const std::vector<std::string>& excluded_legacy_spkis);
// RequireCTDelegate implementation
// Called on the network task runner.
- CTRequirementLevel IsCTRequiredForHost(const std::string& hostname) override;
+ CTRequirementLevel IsCTRequiredForHost(
+ const std::string& hostname,
+ const net::X509Certificate* chain,
+ const net::HashValueVector& hashes) override;
private:
struct Filter {
@@ -47,49 +188,86 @@ class CTPolicyManager::CTDelegate
size_t host_length = 0;
};
- // Called on the |network_task_runner_|, updates the |url_matcher_| to
+ // Returns true if a policy for |hostname| is found, setting
+ // |*ct_required| to indicate whether or not Certificate Transparency is
+ // required for the host.
+ bool MatchHostname(const std::string& hostname, bool* ct_required) const;
+
+ // Returns true if a policy for |chain|, which contains the SPKI hashes
+ // |hashes|, is found, setting |*ct_required| to indicate whether or not
+ // Certificate Transparency is required for the certificate.
+ bool MatchSPKI(const net::X509Certificate* chain,
+ const net::HashValueVector& hashes,
+ bool* ct_required) const;
+
+ // Updates the |url_matcher_| to
// require CT for |required_hosts| and exclude |excluded_hosts|, both
- // of which are Lists of Strings which are URLBlacklist filters.
- void Update(base::ListValue* required_hosts, base::ListValue* excluded_hosts);
+ // of which are Lists of Strings which are URLBlacklist filters, and
+ // updates |excluded_spkis| and |excluded_legacy_spkis| to exclude CT for
+ // those SPKIs, which are encoded as strings using net::HashValue::ToString.
+ void Update(const std::vector<std::string>& required_hosts,
+ const std::vector<std::string>& excluded_hosts,
+ const std::vector<std::string>& excluded_spkis,
+ const std::vector<std::string>& excluded_legacy_spkis);
// Parses the filters from |host_patterns|, adding them as filters to
// |filters_| (with |ct_required| indicating whether or not CT is required
// for that host), and updating |*conditions| with the corresponding
// URLMatcher::Conditions to match the host.
void AddFilters(bool ct_required,
- base::ListValue* host_patterns,
+ const std::vector<std::string>& host_patterns,
url_matcher::URLMatcherConditionSet::Vector* conditions);
+ // Parses the SPKIs from |list|, setting |*hashes| to the sorted set of all
+ // valid SPKIs.
+ void ParseSpkiHashes(const std::vector<std::string> list,
+ net::HashValueVector* hashes) const;
+
// Returns true if |lhs| has greater precedence than |rhs|.
bool FilterTakesPrecedence(const Filter& lhs, const Filter& rhs) const;
- scoped_refptr<base::SequencedTaskRunner> network_task_runner_;
std::unique_ptr<url_matcher::URLMatcher> url_matcher_;
url_matcher::URLMatcherConditionSet::ID next_id_;
std::map<url_matcher::URLMatcherConditionSet::ID, Filter> filters_;
+ // Both SPKI lists are sorted.
+ net::HashValueVector spkis_;
+ net::HashValueVector legacy_spkis_;
+
DISALLOW_COPY_AND_ASSIGN(CTDelegate);
};
-CTPolicyManager::CTDelegate::CTDelegate(
- scoped_refptr<base::SequencedTaskRunner> network_task_runner)
- : network_task_runner_(std::move(network_task_runner)),
- url_matcher_(new url_matcher::URLMatcher),
- next_id_(0) {}
-
-void CTPolicyManager::CTDelegate::UpdateFromPrefs(
- const base::ListValue* required_hosts,
- const base::ListValue* excluded_hosts) {
- network_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&CTDelegate::Update, base::Unretained(this),
- base::Owned(required_hosts->CreateDeepCopy().release()),
- base::Owned(excluded_hosts->CreateDeepCopy().release())));
+CTPolicyManager::CTDelegate::CTDelegate()
+ : url_matcher_(new url_matcher::URLMatcher), next_id_(0) {}
+
+void CTPolicyManager::CTDelegate::UpdateCTPolicies(
+ const std::vector<std::string>& required_hosts,
+ const std::vector<std::string>& excluded_hosts,
+ const std::vector<std::string>& excluded_spkis,
+ const std::vector<std::string>& excluded_legacy_spkis) {
+ Update(required_hosts, excluded_hosts, excluded_spkis, excluded_legacy_spkis);
}
net::TransportSecurityState::RequireCTDelegate::CTRequirementLevel
-CTPolicyManager::CTDelegate::IsCTRequiredForHost(const std::string& hostname) {
- DCHECK(network_task_runner_->RunsTasksInCurrentSequence());
+CTPolicyManager::CTDelegate::IsCTRequiredForHost(
+ const std::string& hostname,
+ const net::X509Certificate* chain,
+ const net::HashValueVector& hashes) {
+
+ bool ct_required = false;
+ if (MatchHostname(hostname, &ct_required) ||
+ MatchSPKI(chain, hashes, &ct_required)) {
+ return ct_required ? CTRequirementLevel::REQUIRED
+ : CTRequirementLevel::NOT_REQUIRED;
+ }
+
+ return CTRequirementLevel::DEFAULT;
+}
+
+bool CTPolicyManager::CTDelegate::MatchHostname(const std::string& hostname,
+ bool* ct_required) const {
+ if (url_matcher_->IsEmpty())
+ return false;
// Scheme and port are ignored by the policy, so it's OK to construct a
// new GURL here. However, |hostname| is in network form, not URL form,
@@ -98,7 +276,7 @@ CTPolicyManager::CTDelegate::IsCTRequiredForHost(const std::string& hostname) {
url_matcher_->MatchURL(
GURL("https://" + net::HostPortPair(hostname, 443).HostForURL()));
if (matching_ids.empty())
- return CTRequirementLevel::DEFAULT;
+ return false;
// Determine the overall policy by determining the most specific policy.
std::map<url_matcher::URLMatcherConditionSet::ID, Filter>::const_iterator it =
@@ -119,14 +297,93 @@ CTPolicyManager::CTDelegate::IsCTRequiredForHost(const std::string& hostname) {
}
CHECK(active_filter);
- return active_filter->ct_required ? CTRequirementLevel::REQUIRED
- : CTRequirementLevel::NOT_REQUIRED;
+ *ct_required = active_filter->ct_required;
+ return true;
}
-void CTPolicyManager::CTDelegate::Update(base::ListValue* required_hosts,
- base::ListValue* excluded_hosts) {
- DCHECK(network_task_runner_->RunsTasksInCurrentSequence());
+bool CTPolicyManager::CTDelegate::MatchSPKI(const net::X509Certificate* chain,
+ const net::HashValueVector& hashes,
+ bool* ct_required) const {
+ // Try to scan legacy SPKIs first, if any, since they will only require
+ // comparing hash values.
+ if (!legacy_spkis_.empty()) {
+ for (const auto& hash : hashes) {
+ if (std::binary_search(legacy_spkis_.begin(), legacy_spkis_.end(),
+ hash)) {
+ *ct_required = false;
+ return true;
+ }
+ }
+ }
+
+ if (spkis_.empty())
+ return false;
+
+ // Scan the constrained SPKIs via |hashes| first, as an optimization. If
+ // there are matches, the SPKI hash will have to be recomputed anyways to
+ // find the matching certificate, but avoid recomputing all the hashes for
+ // the case where there is no match.
+ net::HashValueVector matches;
+ for (const auto& hash : hashes) {
+ if (std::binary_search(spkis_.begin(), spkis_.end(), hash)) {
+ matches.push_back(hash);
+ }
+ }
+ if (matches.empty())
+ return false;
+
+ CRYPTO_BUFFER* leaf_cert = chain->cert_buffer();
+
+ // As an optimization, since the leaf is allowed to be listed as an SPKI,
+ // a match on the leaf's SPKI hash can return early, without comparing
+ // the organization information to itself.
+ net::HashValue hash;
+ if (net::x509_util::CalculateSha256SpkiHash(leaf_cert, &hash) &&
+ std::find(matches.begin(), matches.end(), hash) != matches.end()) {
+ *ct_required = false;
+ return true;
+ }
+
+ // If there was a match (or multiple matches), it's necessary to recompute
+ // the hashes to find the associated certificate.
+ std::vector<CRYPTO_BUFFER*> candidates;
+ for (const auto& buffer : chain->intermediate_buffers()) {
+ if (net::x509_util::CalculateSha256SpkiHash(buffer.get(), &hash) &&
+ std::find(matches.begin(), matches.end(), hash) != matches.end()) {
+ candidates.push_back(buffer.get());
+ }
+ }
+
+ if (candidates.empty())
+ return false;
+
+ scoped_refptr<net::ParsedCertificate> parsed_leaf =
+ net::ParsedCertificate::Create(net::x509_util::DupCryptoBuffer(leaf_cert),
+ net::ParseCertificateOptions(), nullptr);
+ if (!parsed_leaf)
+ return false;
+ // If the leaf is not organizationally-bound, it's not a match.
+ net::RDNSequence leaf_rdn_sequence;
+ if (!ParseOrganizationBoundName(parsed_leaf->normalized_subject(),
+ &leaf_rdn_sequence)) {
+ return false;
+ }
+
+ for (auto* cert : candidates) {
+ if (AreCertsSameOrganization(leaf_rdn_sequence, cert)) {
+ *ct_required = false;
+ return true;
+ }
+ }
+ return false;
+}
+
+void CTPolicyManager::CTDelegate::Update(
+ const std::vector<std::string>& required_hosts,
+ const std::vector<std::string>& excluded_hosts,
+ const std::vector<std::string>& excluded_spkis,
+ const std::vector<std::string>& excluded_legacy_spkis) {
url_matcher_.reset(new url_matcher::URLMatcher);
filters_.clear();
next_id_ = 0;
@@ -136,17 +393,29 @@ void CTPolicyManager::CTDelegate::Update(base::ListValue* required_hosts,
AddFilters(false, excluded_hosts, &all_conditions);
url_matcher_->AddConditionSets(all_conditions);
+
+ ParseSpkiHashes(excluded_spkis, &spkis_);
+ ParseSpkiHashes(excluded_legacy_spkis, &legacy_spkis_);
+
+ // Filter out SPKIs that aren't for legacy CAs.
+ legacy_spkis_.erase(
+ std::remove_if(legacy_spkis_.begin(), legacy_spkis_.end(),
+ [](const net::HashValue& hash) {
+ if (!net::IsLegacyPubliclyTrustedCA(hash)) {
+ LOG(ERROR) << "Non-legacy SPKI configured "
+ << hash.ToString();
+ return true;
+ }
+ return false;
+ }),
+ legacy_spkis_.end());
}
void CTPolicyManager::CTDelegate::AddFilters(
bool ct_required,
- base::ListValue* hosts,
+ const std::vector<std::string>& hosts,
url_matcher::URLMatcherConditionSet::Vector* conditions) {
- for (size_t i = 0; i < hosts->GetSize(); ++i) {
- std::string pattern;
- if (!hosts->GetString(i, &pattern))
- continue;
-
+ for (const auto& pattern : hosts) {
Filter filter;
filter.ct_required = ct_required;
@@ -203,6 +472,20 @@ void CTPolicyManager::CTDelegate::AddFilters(
}
}
+void CTPolicyManager::CTDelegate::ParseSpkiHashes(
+ const std::vector<std::string> list,
+ net::HashValueVector* hashes) const {
+ hashes->clear();
+ for (const auto& value : list) {
+ net::HashValue hash;
+ if (!hash.FromString(value)) {
+ continue;
+ }
+ hashes->push_back(std::move(hash));
+ }
+ std::sort(hashes->begin(), hashes->end());
+}
+
bool CTPolicyManager::CTDelegate::FilterTakesPrecedence(
const Filter& lhs,
const Filter& rhs) const {
@@ -218,54 +501,21 @@ bool CTPolicyManager::CTDelegate::FilterTakesPrecedence(
return false;
}
-// static
-void CTPolicyManager::RegisterPrefs(PrefRegistrySimple* registry) {
- registry->RegisterListPref(prefs::kCTRequiredHosts);
- registry->RegisterListPref(prefs::kCTExcludedHosts);
-}
-
-CTPolicyManager::CTPolicyManager(
- PrefService* pref_service,
- scoped_refptr<base::SequencedTaskRunner> network_task_runner)
- : delegate_(new CTDelegate(std::move(network_task_runner))),
- weak_factory_(this) {
- pref_change_registrar_.Init(pref_service);
- pref_change_registrar_.Add(
- prefs::kCTRequiredHosts,
- base::Bind(&CTPolicyManager::ScheduleUpdate, base::Unretained(this)));
- pref_change_registrar_.Add(
- prefs::kCTExcludedHosts,
- base::Bind(&CTPolicyManager::ScheduleUpdate, base::Unretained(this)));
-
- ScheduleUpdate();
-}
+CTPolicyManager::CTPolicyManager() : delegate_(new CTDelegate()) {}
CTPolicyManager::~CTPolicyManager() {}
-void CTPolicyManager::Shutdown() {
- // Cancel any pending updates, since the preferences are going away.
- weak_factory_.InvalidateWeakPtrs();
- pref_change_registrar_.RemoveAll();
+void CTPolicyManager::UpdateCTPolicies(
+ const std::vector<std::string>& required_hosts,
+ const std::vector<std::string>& excluded_hosts,
+ const std::vector<std::string>& excluded_spkis,
+ const std::vector<std::string>& excluded_legacy_spkis) {
+ delegate_->UpdateCTPolicies(required_hosts, excluded_hosts, excluded_spkis,
+ excluded_legacy_spkis);
}
net::TransportSecurityState::RequireCTDelegate* CTPolicyManager::GetDelegate() {
return delegate_.get();
}
-void CTPolicyManager::ScheduleUpdate() {
- // Cancel any pending updates, and schedule a new update. If this method
- // is called again, this pending update will be cancelled because the weak
- // pointer is invalidated, and the new update will take precedence.
- weak_factory_.InvalidateWeakPtrs();
- base::SequencedTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(&CTPolicyManager::Update, weak_factory_.GetWeakPtr()));
-}
-
-void CTPolicyManager::Update() {
- delegate_->UpdateFromPrefs(
- pref_change_registrar_.prefs()->GetList(prefs::kCTRequiredHosts),
- pref_change_registrar_.prefs()->GetList(prefs::kCTExcludedHosts));
-}
-
} // namespace certificate_transparency
diff --git a/chromium/components/certificate_transparency/ct_policy_manager.h b/chromium/components/certificate_transparency/ct_policy_manager.h
index 15b09a0ab32..88f3b977b3e 100644
--- a/chromium/components/certificate_transparency/ct_policy_manager.h
+++ b/chromium/components/certificate_transparency/ct_policy_manager.h
@@ -9,16 +9,8 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "components/prefs/pref_change_registrar.h"
#include "net/http/transport_security_state.h"
-namespace base {
-class SequencedTaskRunner;
-} // base
-
-class PrefRegistrySimple;
-
namespace certificate_transparency {
// CTPolicyManager serves as the bridge between the Certificate Transparency
@@ -27,26 +19,10 @@ namespace certificate_transparency {
// CT-related policies.
class CTPolicyManager {
public:
- // Registers the preferences related to Certificate Transparency policy
- // in the given pref registry.
- static void RegisterPrefs(PrefRegistrySimple* registry);
-
- // Creates a CTPolicyManager that will monitor the preferences on
- // |pref_service| and make them available to a RequireCTDelegate that
- // can be used on |network_task_runner|.
- //
- // The CTPolicyManager should be constructed on the same task runner
- // associated with the |pref_service|, but can be destructed on any
- // task runner, provided that Shutdown() has been called.
- CTPolicyManager(PrefService* pref_service,
- scoped_refptr<base::SequencedTaskRunner> network_task_runner);
+ // Creates a CTPolicyManager that will provide a RequireCTDelegate delegate.
+ CTPolicyManager();
~CTPolicyManager();
- // Unregisters the CTPolicyManager from the preference subsystem. This
- // should be called on the same task runner that the pref service
- // it was constructed with lives on.
- void Shutdown();
-
// Returns a RequireCTDelegate that responds based on the policies set via
// preferences.
//
@@ -55,29 +31,21 @@ class CTPolicyManager {
// - The most specific host is preferred.
// - Requiring CT is preferred over excluding CT
//
- // This object MUST only be used on the network task runner supplied during
- // construction, MAY be used after Shutdown() is called (at which point,
- // it will reflect the last status before Shutdown() was called), and
- // MUST NOT be used after this object has been deleted.
net::TransportSecurityState::RequireCTDelegate* GetDelegate();
+ // Updates the CTDelegate to require CT for |required_hosts|, and exclude
+ // |excluded_hosts| from CT policies. In addtion, this method updates
+ // |excluded_spkis| and |excluded_legacy_spkis| intended for use within an
+ // Enterprise (see https://crbug.com/824184).
+ void UpdateCTPolicies(const std::vector<std::string>& required_hosts,
+ const std::vector<std::string>& excluded_hosts,
+ const std::vector<std::string>& excluded_spkis,
+ const std::vector<std::string>& excluded_legacy_spkis);
+
private:
class CTDelegate;
-
- // Schedules an update of the CTPolicyDelegate. As it's possible that
- // multiple preferences may be updated at the same time, this exists to
- // schedule only a single update.
- void ScheduleUpdate();
-
- // Performs the actual update of the CTPolicyDelegate once preference
- // changes have quiesced.
- void Update();
-
- PrefChangeRegistrar pref_change_registrar_;
std::unique_ptr<CTDelegate> delegate_;
- base::WeakPtrFactory<CTPolicyManager> weak_factory_;
-
DISALLOW_COPY_AND_ASSIGN(CTPolicyManager);
};
diff --git a/chromium/components/certificate_transparency/ct_policy_manager_unittest.cc b/chromium/components/certificate_transparency/ct_policy_manager_unittest.cc
index 6a2d53b361f..9d3b3b1c5af 100644
--- a/chromium/components/certificate_transparency/ct_policy_manager_unittest.cc
+++ b/chromium/components/certificate_transparency/ct_policy_manager_unittest.cc
@@ -6,6 +6,7 @@
#include <iterator>
+#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
@@ -15,136 +16,130 @@
#include "components/certificate_transparency/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
+#include "net/base/hash_value.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace certificate_transparency {
namespace {
-std::unique_ptr<base::ListValue> ListValueFromStrings(
- const std::vector<const char*>& strings) {
- std::unique_ptr<base::ListValue> result(new base::ListValue);
- for (auto* const str : strings) {
- result->AppendString(str);
- }
- return result;
-}
-
class CTPolicyManagerTest : public ::testing::Test {
public:
CTPolicyManagerTest() : message_loop_(base::MessageLoop::TYPE_IO) {}
+ void SetUp() override {
+ cert_ = net::CreateCertificateChainFromFile(
+ net::GetTestCertsDirectory(), "ok_cert.pem",
+ net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+ ASSERT_TRUE(cert_);
+ hashes_.push_back(net::HashValue(
+ net::X509Certificate::CalculateFingerprint256(cert_->cert_buffer())));
+ }
+
protected:
base::TestMessageLoop message_loop_;
- TestingPrefServiceSimple pref_service_;
+ scoped_refptr<net::X509Certificate> cert_;
+ net::HashValueVector hashes_;
};
// Treat the preferences as a black box as far as naming, but ensure that
// preferences get registered.
TEST_F(CTPolicyManagerTest, RegistersPrefs) {
- auto registered_prefs = std::distance(pref_service_.registry()->begin(),
- pref_service_.registry()->end());
- CTPolicyManager::RegisterPrefs(pref_service_.registry());
- auto newly_registered_prefs = std::distance(pref_service_.registry()->begin(),
- pref_service_.registry()->end());
+ TestingPrefServiceSimple pref_service;
+ auto registered_prefs = std::distance(pref_service.registry()->begin(),
+ pref_service.registry()->end());
+ certificate_transparency::prefs::RegisterPrefs(pref_service.registry());
+ auto newly_registered_prefs = std::distance(pref_service.registry()->begin(),
+ pref_service.registry()->end());
EXPECT_NE(registered_prefs, newly_registered_prefs);
}
TEST_F(CTPolicyManagerTest, DelegateChecksRequired) {
using CTRequirementLevel =
net::TransportSecurityState::RequireCTDelegate::CTRequirementLevel;
- // Register preferences and set up initial state
- CTPolicyManager::RegisterPrefs(pref_service_.registry());
- CTPolicyManager manager(&pref_service_, message_loop_.task_runner());
- base::RunLoop().RunUntilIdle();
+ CTPolicyManager manager;
net::TransportSecurityState::RequireCTDelegate* delegate =
manager.GetDelegate();
ASSERT_TRUE(delegate);
- // No preferences should yield the default results.
+ // No required host set yet.
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("google.com"));
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
- // Now set a preference, pump the message loop, and ensure things are now
- // reflected.
- pref_service_.SetManagedPref(
- prefs::kCTRequiredHosts,
- ListValueFromStrings(std::vector<const char*>{"google.com"}));
- base::RunLoop().RunUntilIdle();
+ // Add a required host
+ manager.UpdateCTPolicies(
+ std::vector<std::string>{"google.com"}, std::vector<std::string>(),
+ std::vector<std::string>(), std::vector<std::string>());
- // The new preferences should take effect.
+ // The new setting should take effect.
EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("google.com"));
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
}
TEST_F(CTPolicyManagerTest, DelegateChecksExcluded) {
using CTRequirementLevel =
net::TransportSecurityState::RequireCTDelegate::CTRequirementLevel;
- // Register preferences and set up initial state
- CTPolicyManager::RegisterPrefs(pref_service_.registry());
- CTPolicyManager manager(&pref_service_, message_loop_.task_runner());
- base::RunLoop().RunUntilIdle();
+ CTPolicyManager manager;
net::TransportSecurityState::RequireCTDelegate* delegate =
manager.GetDelegate();
ASSERT_TRUE(delegate);
- // No preferences should yield the default results.
+ // No setting should yield the default results.
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("google.com"));
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
- // Now set a preference, pump the message loop, and ensure things are now
- // reflected.
- pref_service_.SetManagedPref(
- prefs::kCTExcludedHosts,
- ListValueFromStrings(std::vector<const char*>{"google.com"}));
- base::RunLoop().RunUntilIdle();
+ // Add a excluded host
+ manager.UpdateCTPolicies(
+ std::vector<std::string>(), std::vector<std::string>{"google.com"},
+ std::vector<std::string>(), std::vector<std::string>());
- // The new preferences should take effect.
+ // The new setting should take effect.
EXPECT_EQ(CTRequirementLevel::NOT_REQUIRED,
- delegate->IsCTRequiredForHost("google.com"));
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
}
TEST_F(CTPolicyManagerTest, IgnoresInvalidEntries) {
using CTRequirementLevel =
net::TransportSecurityState::RequireCTDelegate::CTRequirementLevel;
- // Register preferences and set up initial state
- CTPolicyManager::RegisterPrefs(pref_service_.registry());
- CTPolicyManager manager(&pref_service_, message_loop_.task_runner());
- base::RunLoop().RunUntilIdle();
+ CTPolicyManager manager;
net::TransportSecurityState::RequireCTDelegate* delegate =
manager.GetDelegate();
ASSERT_TRUE(delegate);
- // No preferences should yield the default results.
+ // No setting should yield the default results.
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("google.com"));
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
- // Now setup invalid preferences (that is, that fail to be parsable as
+ // Now setup invalid state (that is, that fail to be parsable as
// URLs).
- pref_service_.SetManagedPref(
- prefs::kCTRequiredHosts,
- ListValueFromStrings(std::vector<const char*>{
+ manager.UpdateCTPolicies(
+ std::vector<std::string>{
"file:///etc/fstab", "file://withahost/etc/fstab",
"file:///c|/Windows", "*", "https://*", "example.com",
- "https://example.test:invalid_port",
- }));
- base::RunLoop().RunUntilIdle();
+ "https://example.test:invalid_port"},
+ std::vector<std::string>(), std::vector<std::string>(),
+ std::vector<std::string>());
// Wildcards are ignored (both * and https://*).
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("google.com"));
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
// File URL hosts are ignored.
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("withahost"));
+ delegate->IsCTRequiredForHost("withahost", cert_.get(), hashes_));
// While the partially parsed hosts should take effect.
+ EXPECT_EQ(
+ CTRequirementLevel::REQUIRED,
+ delegate->IsCTRequiredForHost("example.test", cert_.get(), hashes_));
EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("example.test"));
- EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("example.com"));
+ delegate->IsCTRequiredForHost("example.com", cert_.get(), hashes_));
}
// Make sure the various 'undocumented' priorities apply:
@@ -154,122 +149,260 @@ TEST_F(CTPolicyManagerTest, IgnoresInvalidEntries) {
TEST_F(CTPolicyManagerTest, AppliesPriority) {
using CTRequirementLevel =
net::TransportSecurityState::RequireCTDelegate::CTRequirementLevel;
- // Register preferences and set up initial state
- CTPolicyManager::RegisterPrefs(pref_service_.registry());
- CTPolicyManager manager(&pref_service_, message_loop_.task_runner());
- base::RunLoop().RunUntilIdle();
+ CTPolicyManager manager;
net::TransportSecurityState::RequireCTDelegate* delegate =
manager.GetDelegate();
ASSERT_TRUE(delegate);
- // No preferences should yield the default results.
- EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("example.com"));
- EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("sub.example.com"));
+ // No setting should yield the default results.
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("accounts.example.com"));
+ delegate->IsCTRequiredForHost("example.com", cert_.get(), hashes_));
+ EXPECT_EQ(
+ CTRequirementLevel::DEFAULT,
+ delegate->IsCTRequiredForHost("sub.example.com", cert_.get(), hashes_));
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("login.accounts.example.com"));
+ delegate->IsCTRequiredForHost("accounts.example.com", cert_.get(),
+ hashes_));
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("sub.accounts.example.com"));
+ delegate->IsCTRequiredForHost("login.accounts.example.com",
+ cert_.get(), hashes_));
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("login.sub.accounts.example.com"));
+ delegate->IsCTRequiredForHost("sub.accounts.example.com",
+ cert_.get(), hashes_));
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("test.example.com"));
+ delegate->IsCTRequiredForHost("login.sub.accounts.example.com",
+ cert_.get(), hashes_));
+ EXPECT_EQ(
+ CTRequirementLevel::DEFAULT,
+ delegate->IsCTRequiredForHost("test.example.com", cert_.get(), hashes_));
// Set up policies that exclude it for a domain and all of its subdomains,
// but then require it for a specific host.
- pref_service_.SetManagedPref(
- prefs::kCTExcludedHosts,
- ListValueFromStrings(std::vector<const char*>{
- "example.com", ".sub.example.com", ".sub.accounts.example.com",
- "test.example.com"}));
- pref_service_.SetManagedPref(
- prefs::kCTRequiredHosts,
- ListValueFromStrings(std::vector<const char*>{
- "sub.example.com", "accounts.example.com", "test.example.com"}));
- base::RunLoop().RunUntilIdle();
+ manager.UpdateCTPolicies(
+ std::vector<std::string>{"sub.example.com", "accounts.example.com",
+ "test.example.com"},
+ std::vector<std::string>{"example.com", ".sub.example.com",
+ ".sub.accounts.example.com", "test.example.com"},
+ std::vector<std::string>(), std::vector<std::string>());
EXPECT_EQ(CTRequirementLevel::NOT_REQUIRED,
- delegate->IsCTRequiredForHost("example.com"));
+ delegate->IsCTRequiredForHost("example.com", cert_.get(), hashes_));
// Non-wildcarding (.sub.example.com) beats wildcarding (sub.example.com).
- EXPECT_EQ(CTRequirementLevel::NOT_REQUIRED,
- delegate->IsCTRequiredForHost("sub.example.com"));
+ EXPECT_EQ(
+ CTRequirementLevel::NOT_REQUIRED,
+ delegate->IsCTRequiredForHost("sub.example.com", cert_.get(), hashes_));
// More specific hosts (accounts.example.com) beat less specific hosts
// (example.com + wildcard).
EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("accounts.example.com"));
+ delegate->IsCTRequiredForHost("accounts.example.com", cert_.get(),
+ hashes_));
// More specific hosts (accounts.example.com) beat less specific hosts
// (example.com).
EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("login.accounts.example.com"));
+ delegate->IsCTRequiredForHost("login.accounts.example.com",
+ cert_.get(), hashes_));
EXPECT_EQ(CTRequirementLevel::NOT_REQUIRED,
- delegate->IsCTRequiredForHost("sub.accounts.example.com"));
+ delegate->IsCTRequiredForHost("sub.accounts.example.com",
+ cert_.get(), hashes_));
EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("login.sub.accounts.example.com"));
+ delegate->IsCTRequiredForHost("login.sub.accounts.example.com",
+ cert_.get(), hashes_));
// Requiring beats excluding.
- EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("test.example.com"));
+ EXPECT_EQ(
+ CTRequirementLevel::REQUIRED,
+ delegate->IsCTRequiredForHost("test.example.com", cert_.get(), hashes_));
}
-// Ensure that the RequireCTDelegate is still valid and usable after Shutdown
-// has been called. Preferences should no longer sync, but the old results
-// should still be returned.
-TEST_F(CTPolicyManagerTest, UsableAfterShutdown) {
+TEST_F(CTPolicyManagerTest, SupportsOrgRestrictions) {
using CTRequirementLevel =
net::TransportSecurityState::RequireCTDelegate::CTRequirementLevel;
- // Register preferences and set up initial state
- CTPolicyManager::RegisterPrefs(pref_service_.registry());
- CTPolicyManager manager(&pref_service_, message_loop_.task_runner());
- base::RunLoop().RunUntilIdle();
+
+ CTPolicyManager manager;
net::TransportSecurityState::RequireCTDelegate* delegate =
manager.GetDelegate();
ASSERT_TRUE(delegate);
- // No preferences should yield the default results.
- EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("google.com"));
+ base::FilePath test_directory = net::GetTestNetDataDirectory().Append(
+ FILE_PATH_LITERAL("ov_name_constraints"));
+
+ // As all the leaves and intermediates share SPKIs in their classes, load
+ // known-good answers for the remaining test config.
+ scoped_refptr<net::X509Certificate> tmp =
+ net::ImportCertFromFile(test_directory, "leaf-o1.pem");
+ ASSERT_TRUE(tmp);
+ net::HashValue leaf_spki;
+ ASSERT_TRUE(
+ net::x509_util::CalculateSha256SpkiHash(tmp->cert_buffer(), &leaf_spki));
+ tmp = net::ImportCertFromFile(test_directory, "int-o3.pem");
+ ASSERT_TRUE(tmp);
+ net::HashValue intermediate_spki;
+ ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(tmp->cert_buffer(),
+ &intermediate_spki));
+
+ struct {
+ const char* const leaf_file;
+ const char* const intermediate_file;
+ const net::HashValue spki;
+ CTRequirementLevel expected;
+ } kTestCases[] = {
+ // Positive cases
+ //
+ // Exact match on the leaf SPKI (leaf has O)
+ {"leaf-o1.pem", nullptr, leaf_spki, CTRequirementLevel::NOT_REQUIRED},
+ // Exact match on the leaf SPKI (leaf does not have O)
+ {"leaf-no-o.pem", nullptr, leaf_spki, CTRequirementLevel::NOT_REQUIRED},
+ // Exact match on the leaf SPKI (leaf has O), even when the
+ // intermediate does not
+ {"leaf-o1.pem", "int-cn.pem", leaf_spki,
+ CTRequirementLevel::NOT_REQUIRED},
+ // Matches (multiple) organization values in two SEQUENCEs+SETs
+ {"leaf-o1-o2.pem", "int-o1-o2.pem", intermediate_spki,
+ CTRequirementLevel::NOT_REQUIRED},
+ // Matches (multiple) organization values in a single SEQUENCE+SET
+ {"leaf-o1-o2.pem", "int-o1-plus-o2.pem", intermediate_spki,
+ CTRequirementLevel::NOT_REQUIRED},
+ // Matches nameConstrained O
+ {"leaf-o1.pem", "nc-int-permit-o1.pem", intermediate_spki,
+ CTRequirementLevel::NOT_REQUIRED},
+ // Matches the second nameConstraint on the O, out of 3
+ {"leaf-o1.pem", "nc-int-permit-o2-o1-o3.pem", intermediate_spki,
+ CTRequirementLevel::NOT_REQUIRED},
+ // Leaf is in different string type than issuer (BMPString), but it is
+ // in the issuer O field, not the nameConstraint
+ // TODO(rsleevi): Make this fail, because it's not byte-for-byte
+ // identical
+ {"leaf-o1.pem", "int-bmp-o1.pem", intermediate_spki,
+ CTRequirementLevel::NOT_REQUIRED},
+
+ // Negative cases
+ // Leaf is missing O
+ {"leaf-no-o.pem", "int-o1-o2.pem", intermediate_spki,
+ CTRequirementLevel::DEFAULT},
+ // Leaf is missing O
+ {"leaf-no-o.pem", "int-cn.pem", intermediate_spki,
+ CTRequirementLevel::DEFAULT},
+ // Leaf doesn't match issuer O
+ {"leaf-o1.pem", "int-o3.pem", intermediate_spki,
+ CTRequirementLevel::DEFAULT},
+ // Multiple identical organization values, but in different orders.
+ {"leaf-o1-o2.pem", "int-o2-o1.pem", intermediate_spki,
+ CTRequirementLevel::DEFAULT},
+ // Intermediate is nameConstrained, with a dirName, but not an O
+ {"leaf-o1.pem", "nc-int-permit-cn.pem", intermediate_spki,
+ CTRequirementLevel::DEFAULT},
+ // Intermediate is nameConstrained, but with a dNSName
+ {"leaf-o1.pem", "nc-int-permit-dns.pem", intermediate_spki,
+ CTRequirementLevel::DEFAULT},
+ // Intermediate is nameConstrained, but with an excludedSubtrees that
+ // has a dirName that matches the O.
+ {"leaf-o1.pem", "nc-int-exclude-o1.pem", intermediate_spki,
+ CTRequirementLevel::DEFAULT},
+ // Intermediate is nameConstrained, but the encoding of the
+ // nameConstraint is different from the encoding of the leaf
+ {"leaf-o1.pem", "nc-int-permit-bmp-o1.pem", intermediate_spki,
+ CTRequirementLevel::DEFAULT},
+ };
+
+ for (const auto& test : kTestCases) {
+ SCOPED_TRACE(::testing::Message()
+ << "leaf=" << test.leaf_file
+ << ",intermediate=" << test.intermediate_file);
+
+ scoped_refptr<net::X509Certificate> leaf =
+ net::ImportCertFromFile(test_directory, test.leaf_file);
+ ASSERT_TRUE(leaf);
+
+ net::HashValueVector hashes;
+ net::HashValue leaf_hash;
+ ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(leaf->cert_buffer(),
+ &leaf_hash));
+ hashes.push_back(std::move(leaf_hash));
+
+ // Append the intermediate to |leaf|, if any.
+ if (test.intermediate_file) {
+ scoped_refptr<net::X509Certificate> intermediate =
+ net::ImportCertFromFile(test_directory, test.intermediate_file);
+ ASSERT_TRUE(intermediate);
+
+ net::HashValue intermediate_hash;
+ ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(
+ intermediate->cert_buffer(), &intermediate_hash));
+ hashes.push_back(std::move(intermediate_hash));
+
+ std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
+ intermediates.push_back(
+ net::x509_util::DupCryptoBuffer(intermediate->cert_buffer()));
+
+ leaf = net::X509Certificate::CreateFromBuffer(
+ net::x509_util::DupCryptoBuffer(leaf->cert_buffer()),
+ std::move(intermediates));
+ }
+ manager.UpdateCTPolicies(
+ std::vector<std::string>(), std::vector<std::string>(),
+ std::vector<std::string>(), std::vector<std::string>());
+
+ // There should be no existing settings.
+ EXPECT_EQ(CTRequirementLevel::DEFAULT,
+ delegate->IsCTRequiredForHost("google.com", leaf.get(), hashes));
+
+ manager.UpdateCTPolicies(std::vector<std::string>(),
+ std::vector<std::string>(),
+ std::vector<std::string>{test.spki.ToString()},
+ std::vector<std::string>());
+
+ // The new setting should take effect.
+ EXPECT_EQ(test.expected,
+ delegate->IsCTRequiredForHost("google.com", leaf.get(), hashes));
+ }
+}
+
+TEST_F(CTPolicyManagerTest, SupportsLegacyCaRestrictions) {
+ using CTRequirementLevel =
+ net::TransportSecurityState::RequireCTDelegate::CTRequirementLevel;
+ CTPolicyManager manager;
- // Now set a preference, pump the message loop, and ensure things are now
- // reflected.
- pref_service_.SetManagedPref(
- prefs::kCTRequiredHosts,
- ListValueFromStrings(std::vector<const char*>{"google.com"}));
- base::RunLoop().RunUntilIdle();
+ net::TransportSecurityState::RequireCTDelegate* delegate =
+ manager.GetDelegate();
+ ASSERT_TRUE(delegate);
- // The new preferences should take effect.
- EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("google.com"));
+ // The hash of a known legacy CA. See
+ // //net/cert/root_cert_list_generated.h
+ net::SHA256HashValue legacy_spki = {{
+ 0x00, 0x6C, 0xB2, 0x26, 0xA7, 0x72, 0xC7, 0x18, 0x2D, 0x77, 0x72,
+ 0x38, 0x3E, 0x37, 0x3F, 0x0F, 0x22, 0x9E, 0x7D, 0xFE, 0x34, 0x44,
+ 0x81, 0x0A, 0x8D, 0x6E, 0x50, 0x90, 0x5D, 0x20, 0xD6, 0x61,
+ }};
- // Shut down the preferences, which should unregister any observers.
- manager.Shutdown();
- base::RunLoop().RunUntilIdle();
+ hashes_.push_back(net::HashValue(legacy_spki));
- // Update the preferences again, which should do nothing; the
- // RequireCTDelegate should continue to be valid and return the old results.
- EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("google.com"));
- EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("example.com"));
+ // No setting should yield the default results.
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("sub.example.com"));
- pref_service_.SetManagedPref(
- prefs::kCTRequiredHosts,
- ListValueFromStrings(std::vector<const char*>{"sub.example.com"}));
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(CTRequirementLevel::REQUIRED,
- delegate->IsCTRequiredForHost("google.com"));
- EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("example.com"));
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
+
+ // Setting to a non-legacy CA should not work.
+ std::string leaf_hash_string = hashes_.front().ToString();
+ manager.UpdateCTPolicies(
+ std::vector<std::string>(), std::vector<std::string>(),
+ std::vector<std::string>(), std::vector<std::string>{leaf_hash_string});
+
+ // This setting should have no effect, because the hash for |cert_|
+ // is not a legacy CA hash.
EXPECT_EQ(CTRequirementLevel::DEFAULT,
- delegate->IsCTRequiredForHost("sub.example.com"));
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
- // And it should still be possible to get the delegate, even after calling
- // Shutdown().
- EXPECT_TRUE(manager.GetDelegate());
+ // Now set to a truly legacy CA, and create a chain that
+ // contains that legacy CA hash.
+ std::string legacy_ca_hash_string = hashes_.back().ToString();
+
+ manager.UpdateCTPolicies(std::vector<std::string>(),
+ std::vector<std::string>(),
+ std::vector<std::string>(),
+ std::vector<std::string>{legacy_ca_hash_string});
+
+ EXPECT_EQ(CTRequirementLevel::NOT_REQUIRED,
+ delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
}
} // namespace
diff --git a/chromium/components/certificate_transparency/features.cc b/chromium/components/certificate_transparency/features.cc
new file mode 100644
index 00000000000..949ef35bf73
--- /dev/null
+++ b/chromium/components/certificate_transparency/features.cc
@@ -0,0 +1,13 @@
+// Copyright 2017 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 "components/certificate_transparency/features.h"
+
+namespace certificate_transparency {
+
+// Enables or disables auditing Certificate Transparency logs over DNS.
+const base::Feature kCTLogAuditing = {"CertificateTransparencyLogAuditing",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+} // namespace certificate_transparency
diff --git a/chromium/components/certificate_transparency/features.h b/chromium/components/certificate_transparency/features.h
new file mode 100644
index 00000000000..835cdab9d1a
--- /dev/null
+++ b/chromium/components/certificate_transparency/features.h
@@ -0,0 +1,16 @@
+// Copyright 2017 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.
+
+#ifndef COMPONENTS_CERTIFICATE_TRANSPARENCY_FEATURES_H_
+#define COMPONENTS_CERTIFICATE_TRANSPARENCY_FEATURES_H_
+
+#include "base/feature_list.h"
+
+namespace certificate_transparency {
+
+extern const base::Feature kCTLogAuditing;
+
+} // namespace certificate_transparency
+
+#endif // COMPONENTS_CERTIFICATE_TRANSPARENCY_FEATURES_H_
diff --git a/chromium/components/certificate_transparency/log_dns_client.cc b/chromium/components/certificate_transparency/log_dns_client.cc
index afa4c5b8ead..2ed85e0c22d 100644
--- a/chromium/components/certificate_transparency/log_dns_client.cc
+++ b/chromium/components/certificate_transparency/log_dns_client.cc
@@ -6,11 +6,9 @@
#include "base/big_endian.h"
#include "base/bind.h"
-#include "base/callback_helpers.h"
#include "base/format_macros.h"
#include "base/location.h"
#include "base/logging.h"
-#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
@@ -19,6 +17,7 @@
#include "base/time/time.h"
#include "components/base32/base32.h"
#include "crypto/sha2.h"
+#include "net/base/completion_once_callback.h"
#include "net/base/sys_addrinfo.h"
#include "net/cert/merkle_audit_proof.h"
#include "net/dns/dns_client.h"
@@ -150,24 +149,32 @@ bool ParseAuditPath(const net::DnsResponse& response,
// Encapsulates the state machine required to get an audit proof from a Merkle
// leaf hash. This requires a DNS request to obtain the leaf index, then a
// series of DNS requests to get the nodes of the proof.
-class LogDnsClient::AuditProofQuery {
+class AuditProofQueryImpl : public LogDnsClient::AuditProofQuery {
public:
- // The LogDnsClient is guaranteed to outlive the AuditProofQuery, so it's safe
- // to leave ownership of |dns_client| with LogDnsClient.
- AuditProofQuery(net::DnsClient* dns_client,
- const std::string& domain_for_log,
- const net::NetLogWithSource& net_log);
+ // The API contract of LogDnsClient requires that callers make sure the
+ // AuditProofQuery does not outlive LogDnsClient, so it's safe to leave
+ // ownership of |dns_client| with LogDnsClient.
+ AuditProofQueryImpl(net::DnsClient* dns_client,
+ const std::string& domain_for_log,
+ const net::NetLogWithSource& net_log);
+
+ ~AuditProofQueryImpl() override;
// Begins the process of getting an audit proof for the CT log entry with a
// leaf hash of |leaf_hash|. The proof will be for a tree of size |tree_size|.
// If it cannot be obtained synchronously, net::ERR_IO_PENDING will be
// returned and |callback| will be invoked when the operation has completed
- // asynchronously. Ownership of |proof| remains with the caller, and it must
- // not be deleted until the operation is complete.
+ // asynchronously. If the operation is cancelled (by deleting the
+ // AuditProofQueryImpl), |cancellation_callback| will be invoked.
net::Error Start(std::string leaf_hash,
uint64_t tree_size,
- const net::CompletionCallback& callback,
- net::ct::MerkleAuditProof* out_proof);
+ net::CompletionOnceCallback callback,
+ base::OnceClosure cancellation_callback);
+
+ // Returns the proof that is being obtained by this query.
+ // It is only guaranteed to be populated once either Start() returns net::OK
+ // or the completion callback is invoked with net::OK.
+ const net::ct::MerkleAuditProof& GetProof() const override;
private:
enum class State {
@@ -228,9 +235,11 @@ class LogDnsClient::AuditProofQuery {
// The Merkle leaf hash of the CT log entry an audit proof is required for.
std::string leaf_hash_;
// The audit proof to populate.
- net::ct::MerkleAuditProof* proof_;
+ net::ct::MerkleAuditProof proof_;
// The callback to invoke when the query is complete.
- net::CompletionCallback callback_;
+ net::CompletionOnceCallback callback_;
+ // The callback to invoke when the query is cancelled.
+ base::OnceClosure cancellation_callback_;
// The DnsClient to use for sending DNS requests to the CT log.
net::DnsClient* dns_client_;
// The most recent DNS request. Null if no DNS requests have been made.
@@ -243,13 +252,12 @@ class LogDnsClient::AuditProofQuery {
// The time that Start() was last called. Used to measure query duration.
base::TimeTicks start_time_;
// Produces WeakPtrs to |this| for binding callbacks.
- base::WeakPtrFactory<AuditProofQuery> weak_ptr_factory_;
+ base::WeakPtrFactory<AuditProofQueryImpl> weak_ptr_factory_;
};
-LogDnsClient::AuditProofQuery::AuditProofQuery(
- net::DnsClient* dns_client,
- const std::string& domain_for_log,
- const net::NetLogWithSource& net_log)
+AuditProofQueryImpl::AuditProofQueryImpl(net::DnsClient* dns_client,
+ const std::string& domain_for_log,
+ const net::NetLogWithSource& net_log)
: next_state_(State::NONE),
domain_for_log_(domain_for_log),
dns_client_(dns_client),
@@ -259,20 +267,24 @@ LogDnsClient::AuditProofQuery::AuditProofQuery(
DCHECK(!domain_for_log_.empty());
}
+AuditProofQueryImpl::~AuditProofQueryImpl() {
+ if (next_state_ != State::NONE)
+ std::move(cancellation_callback_).Run();
+}
+
// |leaf_hash| is not a const-ref to allow callers to std::move that string into
// the method, avoiding the need to make a copy.
-net::Error LogDnsClient::AuditProofQuery::Start(
- std::string leaf_hash,
- uint64_t tree_size,
- const net::CompletionCallback& callback,
- net::ct::MerkleAuditProof* proof) {
+net::Error AuditProofQueryImpl::Start(std::string leaf_hash,
+ uint64_t tree_size,
+ net::CompletionOnceCallback callback,
+ base::OnceClosure cancellation_callback) {
// It should not already be in progress.
DCHECK_EQ(State::NONE, next_state_);
start_time_ = base::TimeTicks::Now();
- proof_ = proof;
- proof_->tree_size = tree_size;
+ proof_.tree_size = tree_size;
leaf_hash_ = std::move(leaf_hash);
- callback_ = callback;
+ callback_ = std::move(callback);
+ cancellation_callback_ = std::move(cancellation_callback);
// The first step in the query is to request the leaf index corresponding to
// |leaf_hash| from the CT log.
next_state_ = State::REQUEST_LEAF_INDEX;
@@ -280,7 +292,11 @@ net::Error LogDnsClient::AuditProofQuery::Start(
return DoLoop(net::OK);
}
-net::Error LogDnsClient::AuditProofQuery::DoLoop(net::Error result) {
+const net::ct::MerkleAuditProof& AuditProofQueryImpl::GetProof() const {
+ return proof_;
+}
+
+net::Error AuditProofQueryImpl::DoLoop(net::Error result) {
CHECK_NE(State::NONE, next_state_);
State state;
do {
@@ -339,7 +355,7 @@ net::Error LogDnsClient::AuditProofQuery::DoLoop(net::Error result) {
return result;
}
-void LogDnsClient::AuditProofQuery::OnDnsTransactionComplete(
+void AuditProofQueryImpl::OnDnsTransactionComplete(
net::DnsTransaction* transaction,
int net_error,
const net::DnsResponse* response) {
@@ -351,14 +367,14 @@ void LogDnsClient::AuditProofQuery::OnDnsTransactionComplete(
// callback. OnDnsTransactionComplete() will be invoked again once the I/O
// is complete, and can invoke the completion callback then if appropriate.
if (result != net::ERR_IO_PENDING) {
- // The callback will delete this query (now that it has finished), so copy
+ // The callback may delete this query (now that it has finished), so copy
// |callback_| before running it so that it is not deleted along with the
// query, mid-callback-execution (which would result in a crash).
- base::ResetAndReturn(&callback_).Run(result);
+ std::move(callback_).Run(result);
}
}
-net::Error LogDnsClient::AuditProofQuery::RequestLeafIndex() {
+net::Error AuditProofQueryImpl::RequestLeafIndex() {
std::string encoded_leaf_hash = base32::Base32Encode(
leaf_hash_, base32::Base32EncodePolicy::OMIT_PADDING);
DCHECK_EQ(encoded_leaf_hash.size(), 52u);
@@ -376,14 +392,13 @@ net::Error LogDnsClient::AuditProofQuery::RequestLeafIndex() {
// Stores the received leaf index in |proof_->leaf_index|.
// If successful, the audit proof nodes will be requested next.
-net::Error LogDnsClient::AuditProofQuery::RequestLeafIndexComplete(
- net::Error result) {
+net::Error AuditProofQueryImpl::RequestLeafIndexComplete(net::Error result) {
if (result != net::OK) {
return result;
}
DCHECK(last_dns_response_);
- if (!ParseLeafIndex(*last_dns_response_, &proof_->leaf_index)) {
+ if (!ParseLeafIndex(*last_dns_response_, &proof_.leaf_index)) {
return net::ERR_DNS_MALFORMED_RESPONSE;
}
@@ -393,7 +408,7 @@ net::Error LogDnsClient::AuditProofQuery::RequestLeafIndexComplete(
// b) the wrong leaf hash was provided.
// c) there is a bug server-side.
// The first two are more likely, so return ERR_INVALID_ARGUMENT.
- if (proof_->leaf_index >= proof_->tree_size) {
+ if (proof_.leaf_index >= proof_.tree_size) {
return net::ERR_INVALID_ARGUMENT;
}
@@ -401,17 +416,17 @@ net::Error LogDnsClient::AuditProofQuery::RequestLeafIndexComplete(
return net::OK;
}
-net::Error LogDnsClient::AuditProofQuery::RequestAuditProofNodes() {
+net::Error AuditProofQueryImpl::RequestAuditProofNodes() {
// Test pre-conditions (should be guaranteed by DNS response validation).
- if (proof_->leaf_index >= proof_->tree_size ||
- proof_->nodes.size() >= net::ct::CalculateAuditPathLength(
- proof_->leaf_index, proof_->tree_size)) {
+ if (proof_.leaf_index >= proof_.tree_size ||
+ proof_.nodes.size() >= net::ct::CalculateAuditPathLength(
+ proof_.leaf_index, proof_.tree_size)) {
return net::ERR_UNEXPECTED;
}
std::string qname = base::StringPrintf(
- "%zu.%" PRIu64 ".%" PRIu64 ".tree.%s.", proof_->nodes.size(),
- proof_->leaf_index, proof_->tree_size, domain_for_log_.c_str());
+ "%zu.%" PRIu64 ".%" PRIu64 ".tree.%s.", proof_.nodes.size(),
+ proof_.leaf_index, proof_.tree_size, domain_for_log_.c_str());
if (!StartDnsTransaction(qname)) {
return net::ERR_NAME_RESOLUTION_FAILED;
@@ -421,35 +436,34 @@ net::Error LogDnsClient::AuditProofQuery::RequestAuditProofNodes() {
return net::ERR_IO_PENDING;
}
-net::Error LogDnsClient::AuditProofQuery::RequestAuditProofNodesComplete(
+net::Error AuditProofQueryImpl::RequestAuditProofNodesComplete(
net::Error result) {
if (result != net::OK) {
return result;
}
const uint64_t audit_path_length =
- net::ct::CalculateAuditPathLength(proof_->leaf_index, proof_->tree_size);
+ net::ct::CalculateAuditPathLength(proof_.leaf_index, proof_.tree_size);
// The calculated |audit_path_length| can't ever be greater than 64, so
// deriving the amount of memory to reserve from the untrusted |leaf_index|
// is safe.
- proof_->nodes.reserve(audit_path_length);
+ proof_.nodes.reserve(audit_path_length);
DCHECK(last_dns_response_);
- if (!ParseAuditPath(*last_dns_response_, proof_)) {
+ if (!ParseAuditPath(*last_dns_response_, &proof_)) {
return net::ERR_DNS_MALFORMED_RESPONSE;
}
// Keep requesting more proof nodes until all of them are received.
- if (proof_->nodes.size() < audit_path_length) {
+ if (proof_.nodes.size() < audit_path_length) {
next_state_ = State::REQUEST_AUDIT_PROOF_NODES;
}
return net::OK;
}
-bool LogDnsClient::AuditProofQuery::StartDnsTransaction(
- const std::string& qname) {
+bool AuditProofQueryImpl::StartDnsTransaction(const std::string& qname) {
net::DnsTransactionFactory* factory = dns_client_->GetTransactionFactory();
if (!factory) {
return false;
@@ -457,8 +471,8 @@ bool LogDnsClient::AuditProofQuery::StartDnsTransaction(
current_dns_transaction_ = factory->CreateTransaction(
qname, net::dns_protocol::kTypeTXT,
- base::Bind(&LogDnsClient::AuditProofQuery::OnDnsTransactionComplete,
- weak_ptr_factory_.GetWeakPtr()),
+ base::BindOnce(&AuditProofQueryImpl::OnDnsTransactionComplete,
+ weak_ptr_factory_.GetWeakPtr()),
net_log_);
current_dns_transaction_->Start();
@@ -467,11 +481,11 @@ bool LogDnsClient::AuditProofQuery::StartDnsTransaction(
LogDnsClient::LogDnsClient(std::unique_ptr<net::DnsClient> dns_client,
const net::NetLogWithSource& net_log,
- size_t max_concurrent_queries)
+ size_t max_in_flight_queries)
: dns_client_(std::move(dns_client)),
net_log_(net_log),
- max_concurrent_queries_(max_concurrent_queries),
- weak_ptr_factory_(this) {
+ in_flight_queries_(0),
+ max_in_flight_queries_(max_in_flight_queries) {
CHECK(dns_client_);
net::NetworkChangeNotifier::AddDNSObserver(this);
UpdateDnsConfig();
@@ -489,9 +503,9 @@ void LogDnsClient::OnInitialDNSConfigRead() {
UpdateDnsConfig();
}
-void LogDnsClient::NotifyWhenNotThrottled(const base::Closure& callback) {
- DCHECK(HasMaxConcurrentQueriesInProgress());
- not_throttled_callbacks_.push_back(callback);
+void LogDnsClient::NotifyWhenNotThrottled(base::OnceClosure callback) {
+ DCHECK(HasMaxQueriesInFlight());
+ not_throttled_callbacks_.emplace_back(std::move(callback));
}
// |leaf_hash| is not a const-ref to allow callers to std::move that string into
@@ -500,57 +514,66 @@ net::Error LogDnsClient::QueryAuditProof(
base::StringPiece domain_for_log,
std::string leaf_hash,
uint64_t tree_size,
- net::ct::MerkleAuditProof* proof,
+ std::unique_ptr<AuditProofQuery>* out_query,
const net::CompletionCallback& callback) {
- DCHECK(proof);
+ DCHECK(out_query);
if (domain_for_log.empty() || leaf_hash.size() != crypto::kSHA256Length) {
return net::ERR_INVALID_ARGUMENT;
}
- if (HasMaxConcurrentQueriesInProgress()) {
+ if (HasMaxQueriesInFlight()) {
return net::ERR_TEMPORARILY_THROTTLED;
}
- AuditProofQuery* query = new AuditProofQuery(
- dns_client_.get(), domain_for_log.as_string(), net_log_);
- // Transfers ownership of |query| to |audit_proof_queries_|.
- audit_proof_queries_.emplace_back(query);
+ auto* query = new AuditProofQueryImpl(dns_client_.get(),
+ domain_for_log.as_string(), net_log_);
+ out_query->reset(query);
+
+ ++in_flight_queries_;
return query->Start(std::move(leaf_hash), tree_size,
- base::Bind(&LogDnsClient::QueryAuditProofComplete,
- weak_ptr_factory_.GetWeakPtr(),
- base::Unretained(query), callback),
- proof);
+ base::BindOnce(&LogDnsClient::QueryAuditProofComplete,
+ base::Unretained(this), callback),
+ base::BindOnce(&LogDnsClient::QueryAuditProofCancelled,
+ base::Unretained(this)));
}
void LogDnsClient::QueryAuditProofComplete(
- AuditProofQuery* query,
- const net::CompletionCallback& callback,
+ const net::CompletionCallback& completion_callback,
int net_error) {
- DCHECK(query);
+ --in_flight_queries_;
+
+ // Move the "not throttled" callbacks to a local variable, just in case one of
+ // the callbacks deletes this LogDnsClient.
+ std::list<base::OnceClosure> not_throttled_callbacks =
+ std::move(not_throttled_callbacks_);
+
+ completion_callback.Run(net_error);
+
+ // Notify interested parties that the next query will not be throttled.
+ for (auto& callback : not_throttled_callbacks) {
+ std::move(callback).Run();
+ }
+}
- // Finished with the query - destroy it.
- auto query_iterator =
- std::find_if(audit_proof_queries_.begin(), audit_proof_queries_.end(),
- [query](const std::unique_ptr<AuditProofQuery>& p) {
- return p.get() == query;
- });
- DCHECK(query_iterator != audit_proof_queries_.end());
- audit_proof_queries_.erase(query_iterator);
+void LogDnsClient::QueryAuditProofCancelled() {
+ --in_flight_queries_;
- callback.Run(net_error);
+ // Move not_throttled_callbacks_ to a local variable, just in case one of the
+ // callbacks deletes this LogDnsClient.
+ std::list<base::OnceClosure> not_throttled_callbacks =
+ std::move(not_throttled_callbacks_);
// Notify interested parties that the next query will not be throttled.
- std::list<base::Closure> callbacks = std::move(not_throttled_callbacks_);
- for (const base::Closure& callback : callbacks) {
- callback.Run();
+ for (auto& callback : not_throttled_callbacks) {
+ std::move(callback).Run();
}
}
-bool LogDnsClient::HasMaxConcurrentQueriesInProgress() const {
- return max_concurrent_queries_ != 0 &&
- audit_proof_queries_.size() >= max_concurrent_queries_;
+bool LogDnsClient::HasMaxQueriesInFlight() const {
+ return max_in_flight_queries_ != 0 &&
+ in_flight_queries_ >= max_in_flight_queries_;
}
void LogDnsClient::UpdateDnsConfig() {
diff --git a/chromium/components/certificate_transparency/log_dns_client.h b/chromium/components/certificate_transparency/log_dns_client.h
index f3966a8d2b2..1a280f383f1 100644
--- a/chromium/components/certificate_transparency/log_dns_client.h
+++ b/chromium/components/certificate_transparency/log_dns_client.h
@@ -11,6 +11,7 @@
#include "base/callback.h"
#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
#include "base/strings/string_piece.h"
#include "net/base/completion_callback.h"
#include "net/base/net_errors.h"
@@ -33,6 +34,12 @@ namespace certificate_transparency {
// It must be created and deleted on the same thread. It is not thread-safe.
class LogDnsClient : public net::NetworkChangeNotifier::DNSObserver {
public:
+ class AuditProofQuery {
+ public:
+ virtual ~AuditProofQuery() = default;
+ virtual const net::ct::MerkleAuditProof& GetProof() const = 0;
+ };
+
// Creates a log client that will take ownership of |dns_client| and use it
// to perform DNS queries. Queries will be logged to |net_log|.
// The |dns_client| does not need to be configured first - this will be done
@@ -60,7 +67,8 @@ class LogDnsClient : public net::NetworkChangeNotifier::DNSObserver {
// constructor of LogDnsClient). This callback will fire once and then be
// unregistered. Should only be used if QueryAuditProof() returns
// net::ERR_TEMPORARILY_THROTTLED.
- void NotifyWhenNotThrottled(const base::Closure& callback);
+ // The callback will be run on the same thread that created the LogDnsClient.
+ void NotifyWhenNotThrottled(base::OnceClosure callback);
// Queries a CT log to retrieve an audit proof for the leaf with |leaf_hash|.
// The log is identified by |domain_for_log|, which is the DNS name used as a
@@ -68,10 +76,12 @@ class LogDnsClient : public net::NetworkChangeNotifier::DNSObserver {
// The |leaf_hash| is the SHA-256 Merkle leaf hash (see RFC6962, section 2.1).
// The size of the CT log tree, for which the proof is requested, must be
// provided in |tree_size|.
- // The leaf index and audit proof obtained from the CT log will be placed in
- // |out_proof|.
+ // A handle to the query will be placed in |out_query|. The audit proof can be
+ // obtained from that once the query completes. Deleting this handle before
+ // the query completes will cancel it. It must not outlive the LogDnsClient.
// If the proof cannot be obtained synchronously, this method will return
// net::ERR_IO_PENDING and invoke |callback| once the query is complete.
+ // The callback will be run on the same thread that created the LogDnsClient.
// Returns:
// - net::OK if the query was successful.
// - net::ERR_IO_PENDING if the query was successfully started and is
@@ -85,24 +95,23 @@ class LogDnsClient : public net::NetworkChangeNotifier::DNSObserver {
net::Error QueryAuditProof(base::StringPiece domain_for_log,
std::string leaf_hash,
uint64_t tree_size,
- net::ct::MerkleAuditProof* out_proof,
+ std::unique_ptr<AuditProofQuery>* out_query,
const net::CompletionCallback& callback);
private:
- class AuditProofQuery;
-
// Invoked when an audit proof query completes.
- // |query| is the query that has completed.
// |callback| is the user-provided callback that should be notified.
// |net_error| is a net::Error indicating success or failure.
- void QueryAuditProofComplete(AuditProofQuery* query,
- const net::CompletionCallback& callback,
+ void QueryAuditProofComplete(const net::CompletionCallback& callback,
int net_error);
- // Returns true if the maximum number of queries are currently in flight.
- // If the maximum number of concurrency queries is set to 0, this will always
+ // Invoked when an audit proof query is cancelled.
+ void QueryAuditProofCancelled();
+
+ // Returns true if the maximum number of queries are currently in-flight.
+ // If the maximum number of in-flight queries is set to 0, this will always
// return false.
- bool HasMaxConcurrentQueriesInProgress() const;
+ bool HasMaxQueriesInFlight() const;
// Updates the |dns_client_| config using NetworkChangeNotifier.
void UpdateDnsConfig();
@@ -111,16 +120,12 @@ class LogDnsClient : public net::NetworkChangeNotifier::DNSObserver {
std::unique_ptr<net::DnsClient> dns_client_;
// Passed to the DNS client for logging.
net::NetLogWithSource net_log_;
- // A FIFO queue of ongoing queries. Since entries will always be appended to
- // the end and lookups will typically yield entries at the beginning,
- // std::list is an efficient choice.
- std::list<std::unique_ptr<AuditProofQuery>> audit_proof_queries_;
- // The maximum number of queries that can be in flight at one time.
- size_t max_concurrent_queries_;
- // Callbacks to invoke when the number of concurrent queries is at its limit.
- std::list<base::Closure> not_throttled_callbacks_;
- // Creates weak_ptrs to this, for callback purposes.
- base::WeakPtrFactory<LogDnsClient> weak_ptr_factory_;
+ // The number of queries that are currently in-flight.
+ size_t in_flight_queries_;
+ // The maximum number of queries that can be in-flight at one time.
+ size_t max_in_flight_queries_;
+ // Callbacks to invoke when the number of in-flight queries is at its limit.
+ std::list<base::OnceClosure> not_throttled_callbacks_;
DISALLOW_COPY_AND_ASSIGN(LogDnsClient);
};
diff --git a/chromium/components/certificate_transparency/log_dns_client_unittest.cc b/chromium/components/certificate_transparency/log_dns_client_unittest.cc
index d971560199f..85f50bda694 100644
--- a/chromium/components/certificate_transparency/log_dns_client_unittest.cc
+++ b/chromium/components/certificate_transparency/log_dns_client_unittest.cc
@@ -133,8 +133,8 @@ TEST_P(LogDnsClientTest, QueryAuditProofReportsThatLogDomainDoesNotExist) {
ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
kLeafIndexQnames[0], net::dns_protocol::kRcodeNXDOMAIN));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_NAME_NOT_RESOLVED));
}
@@ -143,8 +143,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
kLeafIndexQnames[0], net::dns_protocol::kRcodeSERVFAIL));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_SERVER_FAILED));
}
@@ -153,8 +153,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
kLeafIndexQnames[0], net::dns_protocol::kRcodeREFUSED));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_SERVER_FAILED));
}
@@ -164,8 +164,8 @@ TEST_P(
ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(
kLeafIndexQnames[0], std::vector<base::StringPiece>()));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -175,8 +175,8 @@ TEST_P(
ASSERT_TRUE(
mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"123456", "7"}));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -184,8 +184,8 @@ TEST_P(LogDnsClientTest,
QueryAuditProofReportsMalformedResponseIfLeafIndexIsNotNumeric) {
ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"foo"}));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -194,8 +194,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(
mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"123456.0"}));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -203,8 +203,8 @@ TEST_P(LogDnsClientTest,
QueryAuditProofReportsMalformedResponseIfLeafIndexIsEmpty) {
ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {""}));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -213,8 +213,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(
mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"foo123456"}));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -223,26 +223,26 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(
mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"123456foo"}));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLogDomainIsEmpty) {
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_INVALID_ARGUMENT));
}
TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLeafHashIsInvalid) {
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", "foo", kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", "foo", kTreeSizes[0], &query),
IsError(net::ERR_INVALID_ARGUMENT));
}
TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLeafHashIsEmpty) {
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", "", kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", "", kTreeSizes[0], &query),
IsError(net::ERR_INVALID_ARGUMENT));
}
@@ -251,8 +251,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(mock_dns_.ExpectRequestAndSocketError(
kLeafIndexQnames[0], net::ERR_CONNECTION_REFUSED));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_CONNECTION_REFUSED));
}
@@ -260,8 +260,8 @@ TEST_P(LogDnsClientTest,
QueryAuditProofReportsTimeoutsDuringLeafIndexRequests) {
ASSERT_TRUE(mock_dns_.ExpectRequestAndTimeout(kLeafIndexQnames[0]));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &query),
IsError(net::ERR_DNS_TIMED_OUT));
}
@@ -284,9 +284,10 @@ TEST_P(LogDnsClientTest, QueryAuditProof) {
audit_proof.begin() + nodes_begin, audit_proof.begin() + nodes_end));
}
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsOk());
+ const net::ct::MerkleAuditProof& proof = query->GetProof();
EXPECT_THAT(proof.leaf_index, Eq(123456u));
EXPECT_THAT(proof.tree_size, Eq(999999u));
EXPECT_THAT(proof.nodes, Eq(audit_proof));
@@ -319,9 +320,10 @@ TEST_P(LogDnsClientTest, QueryAuditProofHandlesResponsesWithShortAuditPaths) {
"13.123456.999999.tree.ct.test.", audit_proof.begin() + 13,
audit_proof.end()));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsOk());
+ const net::ct::MerkleAuditProof& proof = query->GetProof();
EXPECT_THAT(proof.leaf_index, Eq(123456u));
EXPECT_THAT(proof.tree_size, Eq(999999u));
EXPECT_THAT(proof.nodes, Eq(audit_proof));
@@ -334,8 +336,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
"0.123456.999999.tree.ct.test.", net::dns_protocol::kRcodeNXDOMAIN));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_NAME_NOT_RESOLVED));
}
@@ -346,8 +348,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
"0.123456.999999.tree.ct.test.", net::dns_protocol::kRcodeSERVFAIL));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_DNS_SERVER_FAILED));
}
@@ -358,8 +360,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
"0.123456.999999.tree.ct.test.", net::dns_protocol::kRcodeREFUSED));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_DNS_SERVER_FAILED));
}
@@ -373,8 +375,8 @@ TEST_P(
ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(
"0.123456.999999.tree.ct.test.", std::vector<base::StringPiece>()));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -398,8 +400,8 @@ TEST_P(
"0.123456.999999.tree.ct.test.",
{first_chunk_of_proof, second_chunk_of_proof}));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -413,8 +415,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
"0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end()));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -427,8 +429,8 @@ TEST_P(LogDnsClientTest, QueryAuditProofReportsResponseMalformedIfNodeTooLong) {
ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
"0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end()));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -440,8 +442,8 @@ TEST_P(LogDnsClientTest, QueryAuditProofReportsResponseMalformedIfEmpty) {
ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
"0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end()));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
@@ -450,8 +452,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(
mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 123456, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 123456, &query),
IsError(net::ERR_INVALID_ARGUMENT));
}
@@ -460,8 +462,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(
mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 999999));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 123456, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 123456, &query),
IsError(net::ERR_INVALID_ARGUMENT));
}
@@ -472,8 +474,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(mock_dns_.ExpectRequestAndSocketError(
"0.123456.999999.tree.ct.test.", net::ERR_CONNECTION_REFUSED));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_CONNECTION_REFUSED));
}
@@ -484,8 +486,8 @@ TEST_P(LogDnsClientTest,
ASSERT_TRUE(
mock_dns_.ExpectRequestAndTimeout("0.123456.999999.tree.ct.test."));
- net::ct::MerkleAuditProof proof;
- ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &query),
IsError(net::ERR_DNS_TIMED_OUT));
}
@@ -547,10 +549,10 @@ TEST_P(LogDnsClientTest, AdoptsLatestDnsConfigMidQuery) {
LogDnsClient log_client(std::move(tmp), net::NetLogWithSource(), 0);
// Start query.
- net::ct::MerkleAuditProof proof;
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
net::TestCompletionCallback callback;
ASSERT_THAT(log_client.QueryAuditProof("ct.test", kLeafHashes[0], 999999,
- &proof, callback.callback()),
+ &query, callback.callback()),
IsError(net::ERR_IO_PENDING));
// Get the current DNS config, modify it and publish the update.
@@ -566,6 +568,7 @@ TEST_P(LogDnsClientTest, AdoptsLatestDnsConfigMidQuery) {
// Wait for the query to complete, then check that it was successful.
// The DNS config should be updated during this time.
ASSERT_THAT(callback.WaitForResult(), IsOk());
+ const net::ct::MerkleAuditProof& proof = query->GetProof();
EXPECT_THAT(proof.leaf_index, Eq(123456u));
EXPECT_THAT(proof.tree_size, Eq(999999u));
EXPECT_THAT(proof.nodes, Eq(audit_proof));
@@ -632,13 +635,13 @@ TEST_P(LogDnsClientTest, CanPerformQueriesInParallel) {
}
}
- net::ct::MerkleAuditProof proofs[kNumOfParallelQueries];
+ std::unique_ptr<LogDnsClient::AuditProofQuery> queries[kNumOfParallelQueries];
// Start the queries.
for (size_t i = 0; i < kNumOfParallelQueries; ++i) {
ASSERT_THAT(
log_client->QueryAuditProof("ct.test", kLeafHashes[i], kTreeSizes[i],
- &proofs[i], callbacks[i].callback()),
+ &queries[i], callbacks[i].callback()),
IsError(net::ERR_IO_PENDING))
<< "query #" << i;
}
@@ -649,9 +652,10 @@ TEST_P(LogDnsClientTest, CanPerformQueriesInParallel) {
SCOPED_TRACE(testing::Message() << "callbacks[" << i << "]");
EXPECT_THAT(callback.WaitForResult(), IsOk());
- EXPECT_THAT(proofs[i].leaf_index, Eq(kLeafIndices[i]));
- EXPECT_THAT(proofs[i].tree_size, Eq(kTreeSizes[i]));
- EXPECT_THAT(proofs[i].nodes, Eq(audit_proofs[i]));
+ const net::ct::MerkleAuditProof& proof = queries[i]->GetProof();
+ EXPECT_THAT(proof.leaf_index, Eq(kLeafIndices[i]));
+ EXPECT_THAT(proof.tree_size, Eq(kTreeSizes[i]));
+ EXPECT_THAT(proof.nodes, Eq(audit_proofs[i]));
}
}
@@ -684,20 +688,21 @@ TEST_P(LogDnsClientTest, CanBeThrottledToOneQueryAtATime) {
CreateLogDnsClient(kMaxConcurrentQueries);
// Try to start the queries.
- net::ct::MerkleAuditProof proof1;
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query1;
net::TestCompletionCallback callback1;
ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], 999999,
- &proof1, callback1.callback()),
+ &query1, callback1.callback()),
IsError(net::ERR_IO_PENDING));
- net::ct::MerkleAuditProof proof2;
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query2;
net::TestCompletionCallback callback2;
ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[1], 999999,
- &proof2, callback2.callback()),
+ &query2, callback2.callback()),
IsError(net::ERR_TEMPORARILY_THROTTLED));
// Check that the first query succeeded.
EXPECT_THAT(callback1.WaitForResult(), IsOk());
+ const net::ct::MerkleAuditProof& proof1 = query1->GetProof();
EXPECT_THAT(proof1.leaf_index, Eq(123456u));
EXPECT_THAT(proof1.tree_size, Eq(999999u));
EXPECT_THAT(proof1.nodes, Eq(audit_proof));
@@ -715,14 +720,15 @@ TEST_P(LogDnsClientTest, CanBeThrottledToOneQueryAtATime) {
"14.666.999999.tree.ct.test.", audit_proof.begin() + 14,
audit_proof.end()));
- net::ct::MerkleAuditProof proof3;
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query3;
net::TestCompletionCallback callback3;
ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[2], 999999,
- &proof3, callback3.callback()),
+ &query3, callback3.callback()),
IsError(net::ERR_IO_PENDING));
// Check that the third query succeeded.
EXPECT_THAT(callback3.WaitForResult(), IsOk());
+ const net::ct::MerkleAuditProof& proof3 = query3->GetProof();
EXPECT_THAT(proof3.leaf_index, Eq(666u));
EXPECT_THAT(proof3.tree_size, Eq(999999u));
EXPECT_THAT(proof3.nodes, Eq(audit_proof));
@@ -748,16 +754,16 @@ TEST_P(LogDnsClientTest, NotifiesWhenNoLongerThrottled) {
CreateLogDnsClient(kMaxConcurrentQueries);
// Start a query.
- net::ct::MerkleAuditProof proof1;
- net::TestCompletionCallback proof_callback1;
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query1;
+ net::TestCompletionCallback query_callback1;
ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], 999999,
- &proof1, proof_callback1.callback()),
+ &query1, query_callback1.callback()),
IsError(net::ERR_IO_PENDING));
net::TestClosure not_throttled_callback;
log_client->NotifyWhenNotThrottled(not_throttled_callback.closure());
- ASSERT_THAT(proof_callback1.WaitForResult(), IsOk());
+ ASSERT_THAT(query_callback1.WaitForResult(), IsOk());
not_throttled_callback.WaitForResult();
// Start another query to check |not_throttled_callback| doesn't fire again.
@@ -773,19 +779,44 @@ TEST_P(LogDnsClientTest, NotifiesWhenNoLongerThrottled) {
"14.666.999999.tree.ct.test.", audit_proof.begin() + 14,
audit_proof.end()));
- net::ct::MerkleAuditProof proof2;
- net::TestCompletionCallback proof_callback2;
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query2;
+ net::TestCompletionCallback query_callback2;
ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[1], 999999,
- &proof2, proof_callback2.callback()),
+ &query2, query_callback2.callback()),
IsError(net::ERR_IO_PENDING));
// Give the query a chance to run.
- ASSERT_THAT(proof_callback2.WaitForResult(), IsOk());
+ ASSERT_THAT(query_callback2.WaitForResult(), IsOk());
// Give |not_throttled_callback| a chance to run - it shouldn't though.
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(not_throttled_callback.have_result());
}
+TEST_P(LogDnsClientTest, CanCancelQueries) {
+ const size_t kMaxConcurrentQueries = 1;
+ std::unique_ptr<LogDnsClient> log_client =
+ CreateLogDnsClient(kMaxConcurrentQueries);
+
+ // Expect the first request of the query to be sent, but not the rest because
+ // it'll be cancelled before it gets that far.
+ ASSERT_TRUE(
+ mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456));
+
+ // Start query.
+ std::unique_ptr<LogDnsClient::AuditProofQuery> query;
+ net::TestCompletionCallback callback;
+ ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], 999999,
+ &query, callback.callback()),
+ IsError(net::ERR_IO_PENDING));
+
+ // Cancel the query.
+ query.reset();
+
+ // Give |callback| a chance to run - it shouldn't though.
+ base::RunLoop().RunUntilIdle();
+ ASSERT_FALSE(callback.have_result());
+}
+
INSTANTIATE_TEST_CASE_P(ReadMode,
LogDnsClientTest,
::testing::Values(net::IoMode::ASYNC,
diff --git a/chromium/components/certificate_transparency/pref_names.cc b/chromium/components/certificate_transparency/pref_names.cc
index 0ffcb430238..776a51876da 100644
--- a/chromium/components/certificate_transparency/pref_names.cc
+++ b/chromium/components/certificate_transparency/pref_names.cc
@@ -3,13 +3,27 @@
// found in the LICENSE file.
#include "components/certificate_transparency/pref_names.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_registry_simple.h"
namespace certificate_transparency {
namespace prefs {
+void RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterListPref(prefs::kCTRequiredHosts);
+ registry->RegisterListPref(prefs::kCTExcludedHosts);
+ registry->RegisterListPref(prefs::kCTExcludedSPKIs);
+ registry->RegisterListPref(prefs::kCTExcludedLegacySPKIs);
+}
+
const char kCTRequiredHosts[] = "certificate_transparency.required_hosts";
const char kCTExcludedHosts[] = "certificate_transparency.excluded_hosts";
+const char kCTExcludedSPKIs[] = "certificate_transparency.excluded_spkis";
+
+const char kCTExcludedLegacySPKIs[] =
+ "certificate_transparency.excluded_legacy_spkis";
+
} // namespace prefs
} // namespace certificate_transparency
diff --git a/chromium/components/certificate_transparency/pref_names.h b/chromium/components/certificate_transparency/pref_names.h
index f27716d15b6..b7153c5fb10 100644
--- a/chromium/components/certificate_transparency/pref_names.h
+++ b/chromium/components/certificate_transparency/pref_names.h
@@ -5,9 +5,15 @@
#ifndef COMPONENTS_CERTIFICATE_TRANSPARENCY_PREF_NAMES_H_
#define COMPONENTS_CERTIFICATE_TRANSPARENCY_PREF_NAMES_H_
+class PrefRegistrySimple;
+
namespace certificate_transparency {
namespace prefs {
+// Registers the preferences related to Certificate Transparency policy
+// in the given pref registry.
+void RegisterPrefs(PrefRegistrySimple* registry);
+
// The set of hosts (as URLBlacklist-syntax filters) for which Certificate
// Transparency is required to be present.
extern const char kCTRequiredHosts[];
@@ -17,6 +23,30 @@ extern const char kCTRequiredHosts[];
// otherwise be required (e.g. as part of security policy).
extern const char kCTExcludedHosts[];
+// The set of subjectPublicKeyInfo hashes in the form of
+// <hash-name>"/"<base64-hash-value>. If a certificate matches this SPKI, then
+// Certificate Transparency information is allowed to be absent if one of the
+// following conditions are met:
+// 1) The matching certificate is a CA certificate (basicConstraints CA:TRUE)
+// that has a nameConstraints extension with a permittedSubtrees that
+// contains one or more directoryName entries, the directoryName has
+// one or more organizationName attributes, and the leaf certificate also
+// contains one or more organizationName attributes in the Subject.
+// 2) The matching certificate contains one or more organizationName
+// attributes in the Subject, and those attributes are identical in
+// ordering, number of values, and byte-for-byte equality of values.
+extern const char kCTExcludedSPKIs[];
+
+// The set of subjectPublicKeyInfo hashes in the form of
+// <hash-name>"/"<base64-hash-value>. If a certificate matches this SPKI, then
+// Certificate Transparency information is allowed to be absent if:
+// 1) The SPKI listed is a known as a publicly trusted root
+// (see //net/data/ssl/root_stores)
+// 2) The SPKI listed is not actively trusted in the current version of the
+// ChromiumOS or Android root stores.
+// (see '"legacy": true' in root_stores.json)
+extern const char kCTExcludedLegacySPKIs[];
+
} // namespace prefs
} // namespace certificate_transparency
diff --git a/chromium/components/certificate_transparency/single_tree_tracker.cc b/chromium/components/certificate_transparency/single_tree_tracker.cc
index c4a61fe7cf7..68cc2ced96d 100644
--- a/chromium/components/certificate_transparency/single_tree_tracker.cc
+++ b/chromium/components/certificate_transparency/single_tree_tracker.cc
@@ -55,38 +55,6 @@ namespace certificate_transparency {
namespace {
-// Enum indicating whether an SCT can be checked for inclusion and if not,
-// the reason it cannot.
-//
-// Note: The numeric values are used within a histogram and should not change
-// or be re-assigned.
-enum SCTCanBeCheckedForInclusion {
- // If the SingleTreeTracker does not have a valid STH, then a valid STH is
- // first required to evaluate whether the SCT can be checked for inclusion
- // or not.
- VALID_STH_REQUIRED = 0,
-
- // If the STH does not cover the SCT (the timestamp in the SCT is greater than
- // MMD + timestamp in the STH), then a newer STH is needed.
- NEWER_STH_REQUIRED = 1,
-
- // When an SCT is observed, if the SingleTreeTracker instance has a valid STH
- // and the STH covers the SCT (the timestamp in the SCT is less than MMD +
- // timestamp in the STH), then it can be checked for inclusion.
- CAN_BE_CHECKED = 2,
-
- // This SCT was not audited because the queue of pending entries was
- // full.
- NOT_AUDITED_QUEUE_FULL = 3,
-
- // This SCT was not audited because no DNS lookup was done when first
- // visiting the website that supplied it. It could compromise the user's
- // privacy to do an inclusion check over DNS in this scenario.
- NOT_AUDITED_NO_DNS_LOOKUP = 4,
-
- SCT_CAN_BE_CHECKED_MAX
-};
-
// Measure how often clients encounter very new SCTs, by measuring whether an
// SCT can be checked for inclusion upon first observation.
void LogCanBeCheckedForInclusionToUMA(
@@ -202,8 +170,9 @@ struct SingleTreeTracker::EntryAuditState {
// Current phase of inclusion check.
AuditState state;
- // The proof to be filled in by the LogDnsClient
- MerkleAuditProof proof;
+ // The audit proof query performed by LogDnsClient.
+ // It is null unless a query has been started.
+ std::unique_ptr<LogDnsClient::AuditProofQuery> audit_proof_query;
// The root hash of the tree for which an inclusion proof was requested.
// The root hash is needed after the inclusion proof is fetched for validating
@@ -268,7 +237,9 @@ SingleTreeTracker::SingleTreeTracker(
&SingleTreeTracker::OnMemoryPressure, base::Unretained(this))));
}
-SingleTreeTracker::~SingleTreeTracker() = default;
+SingleTreeTracker::~SingleTreeTracker() {
+ ResetPendingQueue();
+}
void SingleTreeTracker::OnSCTVerified(base::StringPiece hostname,
net::X509Certificate* cert,
@@ -387,7 +358,12 @@ void SingleTreeTracker::NewSTHObserved(const SignedTreeHead& sth) {
}
void SingleTreeTracker::ResetPendingQueue() {
- pending_entries_.clear();
+ // Move entries out of pending_entries_ prior to deleting them, in case any
+ // have inclusion checks in progress. Cancelling those checks would invoke the
+ // cancellation callback (ProcessPendingEntries()), which would attempt to
+ // access pending_entries_ while it was in the process of being deleted.
+ std::map<EntryToAudit, EntryAuditState, OrderByTimestamp> pending_entries;
+ pending_entries_.swap(pending_entries);
}
SingleTreeTracker::SCTInclusionStatus
@@ -414,9 +390,9 @@ void SingleTreeTracker::ProcessPendingEntries() {
crypto::kSHA256Length);
net::Error result = dns_client_->QueryAuditProof(
ct_log_->dns_domain(), leaf_hash, verified_sth_.tree_size,
- &(it->second.proof),
+ &(it->second.audit_proof_query),
base::Bind(&SingleTreeTracker::OnAuditProofObtained,
- weak_factory_.GetWeakPtr(), it->first));
+ base::Unretained(this), it->first));
// Handling proofs returned synchronously is not implemeted.
DCHECK_NE(result, net::OK);
if (result == net::ERR_IO_PENDING) {
@@ -424,9 +400,12 @@ void SingleTreeTracker::ProcessPendingEntries() {
// and continue to the next one.
it->second.state = INCLUSION_PROOF_REQUESTED;
} else if (result == net::ERR_TEMPORARILY_THROTTLED) {
+ // Need to use a weak pointer here, as this callback could be triggered
+ // when the SingleTreeTracker is deleted (and pending queries are
+ // cancelled).
dns_client_->NotifyWhenNotThrottled(
- base::Bind(&SingleTreeTracker::ProcessPendingEntries,
- weak_factory_.GetWeakPtr()));
+ base::BindOnce(&SingleTreeTracker::ProcessPendingEntries,
+ weak_factory_.GetWeakPtr()));
// Exit the loop since all subsequent calls to QueryAuditProof
// will be throttled.
break;
@@ -494,8 +473,9 @@ void SingleTreeTracker::OnAuditProofObtained(const EntryToAudit& entry,
std::string leaf_hash(reinterpret_cast<const char*>(entry.leaf_hash.data),
crypto::kSHA256Length);
- bool verified = ct_log_->VerifyAuditProof(it->second.proof,
- it->second.root_hash, leaf_hash);
+ bool verified =
+ ct_log_->VerifyAuditProof(it->second.audit_proof_query->GetProof(),
+ it->second.root_hash, leaf_hash);
LogAuditResultToNetLog(entry, verified);
if (!verified) {
@@ -514,7 +494,7 @@ void SingleTreeTracker::OnMemoryPressure(
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
break;
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
- pending_entries_.clear();
+ ResetPendingQueue();
// Fall through to clearing the other cache.
FALLTHROUGH;
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
diff --git a/chromium/components/certificate_transparency/single_tree_tracker.h b/chromium/components/certificate_transparency/single_tree_tracker.h
index dfe49c30502..be36b276095 100644
--- a/chromium/components/certificate_transparency/single_tree_tracker.h
+++ b/chromium/components/certificate_transparency/single_tree_tracker.h
@@ -13,11 +13,11 @@
#include "base/memory/memory_pressure_monitor.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
+#include "components/certificate_transparency/sth_observer.h"
#include "net/base/hash_value.h"
#include "net/base/network_change_notifier.h"
#include "net/cert/ct_verifier.h"
#include "net/cert/signed_tree_head.h"
-#include "net/cert/sth_observer.h"
#include "net/log/net_log_with_source.h"
namespace net {
@@ -39,6 +39,38 @@ namespace certificate_transparency {
class LogDnsClient;
+// Enum indicating whether an SCT can be checked for inclusion and if not,
+// the reason it cannot.
+//
+// Note: The numeric values are used within a histogram and should not change
+// or be re-assigned.
+enum SCTCanBeCheckedForInclusion {
+ // If the SingleTreeTracker does not have a valid STH, then a valid STH is
+ // first required to evaluate whether the SCT can be checked for inclusion
+ // or not.
+ VALID_STH_REQUIRED = 0,
+
+ // If the STH does not cover the SCT (the timestamp in the SCT is greater than
+ // MMD + timestamp in the STH), then a newer STH is needed.
+ NEWER_STH_REQUIRED = 1,
+
+ // When an SCT is observed, if the SingleTreeTracker instance has a valid STH
+ // and the STH covers the SCT (the timestamp in the SCT is less than MMD +
+ // timestamp in the STH), then it can be checked for inclusion.
+ CAN_BE_CHECKED = 2,
+
+ // This SCT was not audited because the queue of pending entries was
+ // full.
+ NOT_AUDITED_QUEUE_FULL = 3,
+
+ // This SCT was not audited because no DNS lookup was done when first
+ // visiting the website that supplied it. It could compromise the user's
+ // privacy to do an inclusion check over DNS in this scenario.
+ NOT_AUDITED_NO_DNS_LOOKUP = 4,
+
+ SCT_CAN_BE_CHECKED_MAX
+};
+
// Tracks the state of an individual Certificate Transparency Log's Merkle Tree.
// A CT Log constantly issues Signed Tree Heads, for which every older STH must
// be incorporated into the current/newer STH. As new certificates are logged,
@@ -55,11 +87,10 @@ class LogDnsClient;
//
// To accomplish this, this class needs to be notified of when new SCTs are
// observed (which it does by implementing net::CTVerifier::Observer) and when
-// new STHs are observed (which it does by implementing net::ct::STHObserver).
+// new STHs are observed (which it does by implementing STHObserver).
// Once connected to sources providing that data, the status for a given SCT
// can be queried by calling GetLogEntryInclusionCheck.
-class SingleTreeTracker : public net::CTVerifier::Observer,
- public net::ct::STHObserver {
+class SingleTreeTracker : public net::CTVerifier::Observer, public STHObserver {
public:
enum SCTInclusionStatus {
// SCT was not observed by this class and is not currently pending
@@ -111,7 +142,7 @@ class SingleTreeTracker : public net::CTVerifier::Observer,
net::X509Certificate* cert,
const net::ct::SignedCertificateTimestamp* sct) override;
- // net::ct::STHObserver implementation.
+ // STHObserver implementation.
// After verification of the signature over the |sth|, uses this
// STH for future inclusion checks.
// Must only be called for STHs issued by the log this instance tracks.
diff --git a/chromium/components/certificate_transparency/single_tree_tracker_unittest.cc b/chromium/components/certificate_transparency/single_tree_tracker_unittest.cc
index d1fb0b1247a..a84a78d1ce3 100644
--- a/chromium/components/certificate_transparency/single_tree_tracker_unittest.cc
+++ b/chromium/components/certificate_transparency/single_tree_tracker_unittest.cc
@@ -185,9 +185,8 @@ void AddCacheEntry(net::HostCache* cache,
class SingleTreeTrackerTest : public ::testing::Test {
void SetUp() override {
- log_ =
- net::CTLogVerifier::Create(GetTestPublicKey(), "testlog",
- "https://ct.example.com", kDNSRequestSuffix);
+ log_ = net::CTLogVerifier::Create(GetTestPublicKey(), "testlog",
+ kDNSRequestSuffix);
ASSERT_TRUE(log_);
ASSERT_EQ(log_->key_id(), GetTestPublicKeyId());
@@ -655,6 +654,45 @@ TEST_F(SingleTreeTrackerTest, TestEntryIncludedAfterInclusionCheckSuccess) {
net_log_, LeafHash(chain_.get(), cert_sct_.get()), true));
}
+// Tests that inclusion checks are aborted and SCTs discarded if under critical
+// memory pressure.
+TEST_F(SingleTreeTrackerTest,
+ TestInclusionCheckCancelledIfUnderMemoryPressure) {
+ CreateTreeTracker();
+ AddCacheEntry(host_resolver_.GetHostCache(), kHostname,
+ net::HostCache::Entry::SOURCE_DNS, kZeroTTL);
+
+ tree_tracker_->OnSCTVerified(kHostname, chain_.get(), cert_sct_.get());
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_PENDING_NEWER_STH,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+
+ // Provide with a fresh STH, which is for a tree of size 2.
+ SignedTreeHead sth;
+ ASSERT_TRUE(GetSignedTreeHeadForTreeOfSize2(&sth));
+ ASSERT_TRUE(log_->VerifySignedTreeHead(sth));
+
+ // Make the first event that is processed a critical memory pressure
+ // notification. This should be handled before the response to the first DNS
+ // request, so no requests after the first one should be sent (the leaf index
+ // request).
+ base::MemoryPressureListener::NotifyMemoryPressure(
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
+
+ ASSERT_TRUE(mock_dns_.ExpectLeafIndexRequestAndResponse(
+ Base32LeafHash(chain_.get(), cert_sct_.get()) + ".hash." +
+ kDNSRequestSuffix,
+ 0));
+
+ tree_tracker_->NewSTHObserved(sth);
+ base::RunLoop().RunUntilIdle();
+
+ // Expect the SCT to have been discarded.
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_NOT_OBSERVED,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+}
+
// Test that pending entries transition states correctly according to the
// STHs provided:
// * Start without an STH.
@@ -880,23 +918,59 @@ TEST_F(SingleTreeTrackerTest,
// Test that entries are no longer pending after a network state
// change.
TEST_F(SingleTreeTrackerTest, DiscardsPendingEntriesAfterNetworkChange) {
- CreateTreeTrackerWithDefaultDnsExpectation();
+ // Setup expectations for 2 SCTs to pass inclusion checking.
+ // However, the first should be cancelled half way through (when the network
+ // change occurs) and the second should be throttled (and then cancelled) so,
+ // by the end of test, neither should actually have passed the checks.
+ std::vector<std::string> audit_proof;
+ FillVectorWithValidAuditProofForTreeOfSize2(&audit_proof);
+
+ ASSERT_TRUE(mock_dns_.ExpectLeafIndexRequestAndResponse(
+ Base32LeafHash(chain_.get(), cert_sct_.get()) + ".hash." +
+ kDNSRequestSuffix,
+ 0));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ std::string("0.0.2.tree.") + kDNSRequestSuffix, audit_proof.begin(),
+ audit_proof.begin() + 1));
+
+ scoped_refptr<SignedCertificateTimestamp> second_sct(GetSCT());
+ second_sct->timestamp -= base::TimeDelta::FromHours(1);
+
+ ASSERT_TRUE(mock_dns_.ExpectLeafIndexRequestAndResponse(
+ Base32LeafHash(chain_.get(), second_sct.get()) + ".hash." +
+ kDNSRequestSuffix,
+ 1));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ std::string("0.1.2.tree.") + kDNSRequestSuffix, audit_proof.begin(),
+ audit_proof.begin() + 1));
+
+ CreateTreeTracker();
AddCacheEntry(host_resolver_.GetHostCache(), kHostname,
net::HostCache::Entry::SOURCE_DNS, kZeroTTL);
+ // Provide an STH to the tree_tracker_.
+ SignedTreeHead sth;
+ GetSignedTreeHeadForTreeOfSize2(&sth);
+ tree_tracker_->NewSTHObserved(sth);
+
tree_tracker_->OnSCTVerified(kHostname, chain_.get(), cert_sct_.get());
+ tree_tracker_->OnSCTVerified(kHostname, chain_.get(), second_sct.get());
- EXPECT_EQ(
- SingleTreeTracker::SCT_PENDING_NEWER_STH,
- tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+ for (auto sct : {cert_sct_, second_sct}) {
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_PENDING_INCLUSION_CHECK,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), sct.get()));
+ }
net_change_notifier_->NotifyObserversOfNetworkChangeForTests(
net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
base::RunLoop().RunUntilIdle();
- EXPECT_EQ(
- SingleTreeTracker::SCT_NOT_OBSERVED,
- tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+ for (auto sct : {cert_sct_, second_sct}) {
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_NOT_OBSERVED,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), sct.get()));
+ }
}
} // namespace certificate_transparency
diff --git a/chromium/components/certificate_transparency/sth_distributor.cc b/chromium/components/certificate_transparency/sth_distributor.cc
new file mode 100644
index 00000000000..33820950294
--- /dev/null
+++ b/chromium/components/certificate_transparency/sth_distributor.cc
@@ -0,0 +1,65 @@
+// Copyright 2016 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 "components/certificate_transparency/sth_distributor.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/time/time.h"
+#include "net/cert/signed_tree_head.h"
+
+namespace {
+const uint8_t kPilotLogID[] = {0xa4, 0xb9, 0x09, 0x90, 0xb4, 0x18, 0x58, 0x14,
+ 0x87, 0xbb, 0x13, 0xa2, 0xcc, 0x67, 0x70, 0x0a,
+ 0x3c, 0x35, 0x98, 0x04, 0xf9, 0x1b, 0xdf, 0xb8,
+ 0xe3, 0x77, 0xcd, 0x0e, 0xc8, 0x0d, 0xdc, 0x10};
+}
+
+namespace certificate_transparency {
+
+STHDistributor::STHDistributor()
+ : observer_list_(base::ObserverListPolicy::EXISTING_ONLY) {}
+
+STHDistributor::~STHDistributor() = default;
+
+void STHDistributor::NewSTHObserved(const net::ct::SignedTreeHead& sth) {
+ auto it = std::find_if(observed_sths_.begin(), observed_sths_.end(),
+ [&sth](const net::ct::SignedTreeHead& other) {
+ return sth.log_id == other.log_id;
+ });
+
+ if (it == observed_sths_.end())
+ observed_sths_.push_back(sth);
+ else
+ *it = sth;
+
+ for (auto& observer : observer_list_)
+ observer.NewSTHObserved(sth);
+
+ if (sth.log_id.compare(0, sth.log_id.size(),
+ reinterpret_cast<const char*>(kPilotLogID),
+ sizeof(kPilotLogID)) != 0)
+ return;
+
+ const base::TimeDelta sth_age = base::Time::Now() - sth.timestamp;
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertificateTransparency.PilotSTHAge", sth_age,
+ base::TimeDelta::FromHours(1),
+ base::TimeDelta::FromDays(4), 100);
+}
+
+void STHDistributor::RegisterObserver(STHObserver* observer) {
+ observer_list_.AddObserver(observer);
+ // Make a local copy, because notifying the |observer| of a
+ // new STH may result in this class being notified of a
+ // (different) new STH, thus invalidating the iterator.
+ std::vector<net::ct::SignedTreeHead> local_sths(observed_sths_);
+
+ for (const auto& sth : local_sths)
+ observer->NewSTHObserved(sth);
+}
+
+void STHDistributor::UnregisterObserver(STHObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+} // namespace certificate_transparency
diff --git a/chromium/components/certificate_transparency/sth_distributor.h b/chromium/components/certificate_transparency/sth_distributor.h
new file mode 100644
index 00000000000..37711c17fa9
--- /dev/null
+++ b/chromium/components/certificate_transparency/sth_distributor.h
@@ -0,0 +1,56 @@
+// Copyright 2016 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.
+
+#ifndef COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_DISTRIBUTOR_H_
+#define COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_DISTRIBUTOR_H_
+
+#include <vector>
+
+#include "base/observer_list.h"
+#include "components/certificate_transparency/sth_observer.h"
+#include "components/certificate_transparency/sth_reporter.h"
+#include "net/base/net_export.h"
+
+namespace net {
+namespace ct {
+struct SignedTreeHead;
+} // namespace ct
+} // namespace net
+
+namespace certificate_transparency {
+
+// A proxy for delegating new STH notifications to all registered
+// observers.
+// For each |observer| registered with RegisterObserver, the
+// NewSTHObserved method will be called whenever the STHDistributor's
+// NewSTHObserved method is invoked.
+class STHDistributor : public STHObserver, public STHReporter {
+ public:
+ STHDistributor();
+ ~STHDistributor() override;
+
+ // STHObserver implementation.
+ void NewSTHObserved(const net::ct::SignedTreeHead& sth) override;
+
+ // STHReporter implementation
+ // Registers |observer| for new STH notifications. On registration,
+ // the |observer| will be notified of the latest STH for each log tha the
+ // STHDistributor has observed.
+ void RegisterObserver(STHObserver* observer) override;
+
+ // Unregisters |observer|, which must have been previously
+ // registered via RegisterObserver()
+ void UnregisterObserver(STHObserver* observer) override;
+
+ private:
+ // STHs from logs, one for each log.
+ std::vector<net::ct::SignedTreeHead> observed_sths_;
+
+ // The observers for new STH notifications.
+ base::ObserverList<STHObserver, true> observer_list_;
+};
+
+} // namespace certificate_transparency
+
+#endif // COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_DISTRIBUTOR_H_
diff --git a/chromium/components/certificate_transparency/sth_distributor_unittest.cc b/chromium/components/certificate_transparency/sth_distributor_unittest.cc
new file mode 100644
index 00000000000..6e59cf761f8
--- /dev/null
+++ b/chromium/components/certificate_transparency/sth_distributor_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright 2016 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 "components/certificate_transparency/sth_distributor.h"
+
+#include <map>
+#include <string>
+
+#include "base/test/histogram_tester.h"
+#include "components/certificate_transparency/sth_observer.h"
+#include "crypto/sha2.h"
+#include "net/cert/signed_tree_head.h"
+#include "net/test/ct_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace certificate_transparency {
+
+namespace {
+
+// An STHObserver implementation that simply stores all
+// observed STHs, keyed by log ID.
+class StoringSTHObserver : public STHObserver {
+ public:
+ void NewSTHObserved(const net::ct::SignedTreeHead& sth) override {
+ sths[sth.log_id] = sth;
+ }
+
+ std::map<std::string, net::ct::SignedTreeHead> sths;
+};
+
+class STHDistributorTest : public ::testing::Test {
+ public:
+ STHDistributorTest() = default;
+
+ void SetUp() override {
+ ASSERT_TRUE(GetSampleSignedTreeHead(&sample_sth_));
+ sample_sth_.log_id = net::ct::GetTestPublicKeyId();
+ }
+
+ protected:
+ STHDistributor distributor_;
+ net::ct::SignedTreeHead sample_sth_;
+};
+
+// Test that when a new observer is registered, the STHDistributor notifies it
+// of all the observed STHs it received so far.
+// This test makes sure that all observed STHs are reported to the observer.
+TEST_F(STHDistributorTest, NotifiesOfExistingSTHs) {
+ // Create an STH that differs from the |sample_sth_| by belonging to a
+ // different log.
+ const std::string other_log = "another log";
+ net::ct::SignedTreeHead second_sth(sample_sth_);
+ second_sth.log_id = other_log;
+
+ // Notify |distributor_| of both STHs.
+ distributor_.NewSTHObserved(sample_sth_);
+ distributor_.NewSTHObserved(second_sth);
+
+ StoringSTHObserver observer;
+ distributor_.RegisterObserver(&observer);
+
+ // Check that two STHs from different logs received prior to observer
+ // registration were reported to the observer once registered.
+ EXPECT_EQ(2u, observer.sths.size());
+ EXPECT_EQ(1u, observer.sths.count(other_log));
+ distributor_.UnregisterObserver(&observer);
+}
+
+// Test that histograms are properly recorded for the STH age when an STH
+// from Google's Pilot log is observed.
+TEST_F(STHDistributorTest, LogsUMAForPilotSTH) {
+ const char kPilotSTHAgeHistogram[] =
+ "Net.CertificateTransparency.PilotSTHAge";
+ base::HistogramTester histograms;
+ histograms.ExpectTotalCount(kPilotSTHAgeHistogram, 0);
+
+ const uint8_t kPilotLogID[] = {
+ 0xa4, 0xb9, 0x09, 0x90, 0xb4, 0x18, 0x58, 0x14, 0x87, 0xbb, 0x13,
+ 0xa2, 0xcc, 0x67, 0x70, 0x0a, 0x3c, 0x35, 0x98, 0x04, 0xf9, 0x1b,
+ 0xdf, 0xb8, 0xe3, 0x77, 0xcd, 0x0e, 0xc8, 0x0d, 0xdc, 0x10};
+ sample_sth_.log_id = std::string(reinterpret_cast<const char*>(kPilotLogID),
+ crypto::kSHA256Length);
+
+ distributor_.NewSTHObserved(sample_sth_);
+ histograms.ExpectTotalCount(kPilotSTHAgeHistogram, 1);
+}
+
+// Test that the STHDistributor updates, rather than accumulates, STHs
+// coming from the same log.
+// This is tested by notifying the STHDistributor of an STH, modifying that
+// STH, notifying the STHDistributor of the modified STH, then registering
+// an observer which should get notified only once, with the modified STH.
+TEST_F(STHDistributorTest, UpdatesObservedSTHData) {
+ // Observe an initial STH
+ StoringSTHObserver observer;
+ distributor_.RegisterObserver(&observer);
+
+ distributor_.NewSTHObserved(sample_sth_);
+
+ EXPECT_EQ(1u, observer.sths.size());
+ EXPECT_EQ(sample_sth_, observer.sths[net::ct::GetTestPublicKeyId()]);
+
+ // Observe a new STH. "new" simply means that it is a more recently observed
+ // SignedTreeHead for the given log ID, not necessarily that it's newer
+ // chronologically (the timestamp) or the log state (the tree size).
+ // To make sure the more recently observed SignedTreeHead is returned, just
+ // modify some fields.
+ net::ct::SignedTreeHead new_sth = sample_sth_;
+ new_sth.tree_size++;
+ new_sth.timestamp -= base::TimeDelta::FromSeconds(3);
+
+ distributor_.NewSTHObserved(new_sth);
+ // The STH should have been broadcast to existing observers.
+ EXPECT_EQ(1u, observer.sths.size());
+ EXPECT_NE(sample_sth_, observer.sths[net::ct::GetTestPublicKeyId()]);
+ EXPECT_EQ(new_sth, observer.sths[net::ct::GetTestPublicKeyId()]);
+
+ // Registering a new observer should only receive the most recently observed
+ // STH.
+ StoringSTHObserver new_observer;
+ distributor_.RegisterObserver(&new_observer);
+ EXPECT_EQ(1u, new_observer.sths.size());
+ EXPECT_NE(sample_sth_, new_observer.sths[net::ct::GetTestPublicKeyId()]);
+ EXPECT_EQ(new_sth, new_observer.sths[net::ct::GetTestPublicKeyId()]);
+
+ distributor_.UnregisterObserver(&new_observer);
+ distributor_.UnregisterObserver(&observer);
+}
+
+} // namespace
+
+} // namespace certificate_transparency
diff --git a/chromium/components/certificate_transparency/sth_observer.h b/chromium/components/certificate_transparency/sth_observer.h
new file mode 100644
index 00000000000..62742ea5ad4
--- /dev/null
+++ b/chromium/components/certificate_transparency/sth_observer.h
@@ -0,0 +1,29 @@
+// Copyright 2016 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.
+
+#ifndef COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_OBSERVER_H_
+#define COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_OBSERVER_H_
+
+#include <set>
+
+namespace net {
+namespace ct {
+struct SignedTreeHead;
+} // namespace ct
+} // namespace net
+
+namespace certificate_transparency {
+
+// Interface for receiving notifications of new STHs observed.
+class STHObserver {
+ public:
+ virtual ~STHObserver() {}
+
+ // Called with a new |sth| when one is observed.
+ virtual void NewSTHObserved(const net::ct::SignedTreeHead& sth) = 0;
+};
+
+} // namespace certificate_transparency
+
+#endif // COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_OBSERVER_H_
diff --git a/chromium/components/certificate_transparency/sth_reporter.h b/chromium/components/certificate_transparency/sth_reporter.h
new file mode 100644
index 00000000000..c4eea96c0a0
--- /dev/null
+++ b/chromium/components/certificate_transparency/sth_reporter.h
@@ -0,0 +1,25 @@
+// Copyright 2016 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.
+
+#ifndef COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_REPORTER_H_
+#define COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_REPORTER_H_
+
+#include <set>
+
+namespace certificate_transparency {
+
+class STHObserver;
+
+// Interface for registering/unregistering observers.
+class STHReporter {
+ public:
+ virtual ~STHReporter() {}
+
+ virtual void RegisterObserver(STHObserver* observer) = 0;
+ virtual void UnregisterObserver(STHObserver* observer) = 0;
+};
+
+} // namespace certificate_transparency
+
+#endif // COMPONENTS_CERTIFICATE_TRANSPARENCY_STH_REPORTER_H_
diff --git a/chromium/components/certificate_transparency/tree_state_tracker.cc b/chromium/components/certificate_transparency/tree_state_tracker.cc
index 958769c694c..25dad103c01 100644
--- a/chromium/components/certificate_transparency/tree_state_tracker.cc
+++ b/chromium/components/certificate_transparency/tree_state_tracker.cc
@@ -5,8 +5,10 @@
#include "components/certificate_transparency/tree_state_tracker.h"
#include <memory>
+#include <utility>
#include "base/feature_list.h"
+#include "components/certificate_transparency/features.h"
#include "components/certificate_transparency/log_dns_client.h"
#include "components/certificate_transparency/single_tree_tracker.h"
#include "net/base/network_change_notifier.h"
@@ -29,17 +31,10 @@ const size_t kMaxConcurrentDnsQueries = 1;
namespace certificate_transparency {
-// Enables or disables auditing Certificate Transparency logs over DNS.
-const base::Feature kCTLogAuditing = {"CertificateTransparencyLogAuditing",
- base::FEATURE_DISABLED_BY_DEFAULT};
-
TreeStateTracker::TreeStateTracker(
std::vector<scoped_refptr<const CTLogVerifier>> ct_logs,
net::HostResolver* host_resolver,
net::NetLog* net_log) {
- if (!base::FeatureList::IsEnabled(kCTLogAuditing))
- return;
-
std::unique_ptr<net::DnsClient> dns_client =
net::DnsClient::CreateClient(net_log);
dns_client_ = std::make_unique<LogDnsClient>(
@@ -59,6 +54,9 @@ TreeStateTracker::~TreeStateTracker() {}
void TreeStateTracker::OnSCTVerified(base::StringPiece hostname,
X509Certificate* cert,
const SignedCertificateTimestamp* sct) {
+ if (!base::FeatureList::IsEnabled(kCTLogAuditing))
+ return;
+
auto it = tree_trackers_.find(sct->log_id);
// Ignore if the SCT is from an unknown log.
if (it == tree_trackers_.end())
diff --git a/chromium/components/certificate_transparency/tree_state_tracker.h b/chromium/components/certificate_transparency/tree_state_tracker.h
index 606efbeffeb..90dc7953c5f 100644
--- a/chromium/components/certificate_transparency/tree_state_tracker.h
+++ b/chromium/components/certificate_transparency/tree_state_tracker.h
@@ -11,8 +11,8 @@
#include <vector>
#include "base/memory/ref_counted.h"
+#include "components/certificate_transparency/sth_observer.h"
#include "net/cert/ct_verifier.h"
-#include "net/cert/sth_observer.h"
namespace net {
class NetLog;
@@ -37,8 +37,7 @@ class SingleTreeTracker;
// TODO(eranm): Export the inclusion check status of SCTs+Certs so it can
// be used in the DevTools Security panel, for example. See
// https://crbug.com/506227#c22.
-class TreeStateTracker : public net::CTVerifier::Observer,
- public net::ct::STHObserver {
+class TreeStateTracker : public net::CTVerifier::Observer, public STHObserver {
public:
// Tracks the state of the logs provided in |ct_logs|. An instance of this
// class only tracks the logs provided in the constructor. The implementation
@@ -56,7 +55,7 @@ class TreeStateTracker : public net::CTVerifier::Observer,
net::X509Certificate* cert,
const net::ct::SignedCertificateTimestamp* sct) override;
- // net::ct::STHObserver implementation.
+ // STHObserver implementation.
// Delegates to the tree tracker corresponding to the log that issued the STH.
void NewSTHObserved(const net::ct::SignedTreeHead& sth) override;
diff --git a/chromium/components/certificate_transparency/tree_state_tracker_unittest.cc b/chromium/components/certificate_transparency/tree_state_tracker_unittest.cc
index c8f61ce1190..5b138af2cd8 100644
--- a/chromium/components/certificate_transparency/tree_state_tracker_unittest.cc
+++ b/chromium/components/certificate_transparency/tree_state_tracker_unittest.cc
@@ -4,16 +4,15 @@
#include "components/certificate_transparency/tree_state_tracker.h"
+#include <memory>
#include <string>
#include <utility>
-#include <memory>
-
-#include "base/feature_list.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
+#include "components/certificate_transparency/features.h"
#include "net/base/net_errors.h"
#include "net/cert/ct_log_verifier.h"
#include "net/cert/ct_serialization.h"
@@ -35,9 +34,6 @@ using net::ct::GetTestPublicKey;
using net::ct::kSthRootHashLength;
using net::ct::GetX509CertSCT;
-const base::Feature kCTLogAuditing = {"CertificateTransparencyLogAuditing",
- base::FEATURE_DISABLED_BY_DEFAULT};
-
constexpr char kHostname[] = "example.test";
constexpr base::TimeDelta kZeroTTL;
@@ -46,7 +42,6 @@ namespace certificate_transparency {
class TreeStateTrackerTest : public ::testing::Test {
void SetUp() override {
log_ = net::CTLogVerifier::Create(GetTestPublicKey(), "testlog",
- "https://ct.example.com",
"unresolvable.invalid");
ASSERT_TRUE(log_);