diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:20:33 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:28:57 +0000 |
commit | d17ea114e5ef69ad5d5d7413280a13e6428098aa (patch) | |
tree | 2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/components/certificate_transparency | |
parent | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff) | |
download | qtwebengine-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')
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_); |