Commit 8d60aa54 authored by rch@chromium.org's avatar rch@chromium.org

Centralize the logic for checking public key pins from ClientSocketNSS

and ProofVerifierChromium to TransportSecurityState::CheckPublicKeyPins.
This required adding an is_issued_by_known_root argument to this method.

In addition, CheckPublicKeyPins now only checks static pins if the
TransportSecurityState's enable_static_pins_ member is true. This defaults
to true only for official desktop builds. This also means that dynamic
pins are now checked on mobile and on non-official builds.

BUG=398925,391033

Review URL: https://codereview.chromium.org/433123003

Cr-Commit-Position: refs/heads/master@{#288435}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@288435 0039d316-1c4b-4281-b951-d872f2087c98
parent eb7b4aac
...@@ -506,6 +506,7 @@ TEST_F(HttpSecurityHeadersTest, UpdateDynamicPKPOnly) { ...@@ -506,6 +506,7 @@ TEST_F(HttpSecurityHeadersTest, UpdateDynamicPKPOnly) {
// docs.google.com has preloaded pins. // docs.google.com has preloaded pins.
const bool sni_enabled = true; const bool sni_enabled = true;
std::string domain = "docs.google.com"; std::string domain = "docs.google.com";
state.enable_static_pins_ = true;
EXPECT_TRUE( EXPECT_TRUE(
state.GetStaticDomainState(domain, sni_enabled, &static_domain_state)); state.GetStaticDomainState(domain, sni_enabled, &static_domain_state));
EXPECT_GT(static_domain_state.pkp.spki_hashes.size(), 1UL); EXPECT_GT(static_domain_state.pkp.spki_hashes.size(), 1UL);
...@@ -554,8 +555,9 @@ TEST_F(HttpSecurityHeadersTest, UpdateDynamicPKPOnly) { ...@@ -554,8 +555,9 @@ TEST_F(HttpSecurityHeadersTest, UpdateDynamicPKPOnly) {
HashValueVector hashes; HashValueVector hashes;
hashes.push_back(good_hash); hashes.push_back(good_hash);
std::string failure_log; std::string failure_log;
EXPECT_TRUE( const bool is_issued_by_known_root = true;
state.CheckPublicKeyPins(domain, sni_enabled, hashes, &failure_log)); EXPECT_TRUE(state.CheckPublicKeyPins(
domain, sni_enabled, is_issued_by_known_root, hashes, &failure_log));
TransportSecurityState::DomainState new_dynamic_domain_state; TransportSecurityState::DomainState new_dynamic_domain_state;
EXPECT_TRUE(state.GetDynamicDomainState(domain, &new_dynamic_domain_state)); EXPECT_TRUE(state.GetDynamicDomainState(domain, &new_dynamic_domain_state));
...@@ -585,6 +587,7 @@ TEST_F(HttpSecurityHeadersTest, MAYBE_UpdateDynamicPKPMaxAge0) { ...@@ -585,6 +587,7 @@ TEST_F(HttpSecurityHeadersTest, MAYBE_UpdateDynamicPKPMaxAge0) {
// docs.google.com has preloaded pins. // docs.google.com has preloaded pins.
const bool sni_enabled = true; const bool sni_enabled = true;
std::string domain = "docs.google.com"; std::string domain = "docs.google.com";
state.enable_static_pins_ = true;
ASSERT_TRUE( ASSERT_TRUE(
state.GetStaticDomainState(domain, sni_enabled, &static_domain_state)); state.GetStaticDomainState(domain, sni_enabled, &static_domain_state));
EXPECT_GT(static_domain_state.pkp.spki_hashes.size(), 1UL); EXPECT_GT(static_domain_state.pkp.spki_hashes.size(), 1UL);
...@@ -648,8 +651,13 @@ TEST_F(HttpSecurityHeadersTest, MAYBE_UpdateDynamicPKPMaxAge0) { ...@@ -648,8 +651,13 @@ TEST_F(HttpSecurityHeadersTest, MAYBE_UpdateDynamicPKPMaxAge0) {
// Damage the hashes to cause a pin validation failure. // Damage the hashes to cause a pin validation failure.
new_static_domain_state2.pkp.spki_hashes[0].data()[0] ^= 0x80; new_static_domain_state2.pkp.spki_hashes[0].data()[0] ^= 0x80;
new_static_domain_state2.pkp.spki_hashes[1].data()[0] ^= 0x80; new_static_domain_state2.pkp.spki_hashes[1].data()[0] ^= 0x80;
EXPECT_FALSE(state.CheckPublicKeyPins( const bool is_issued_by_known_root = true;
domain, true, new_static_domain_state2.pkp.spki_hashes, &failure_log)); EXPECT_FALSE(
state.CheckPublicKeyPins(domain,
true,
is_issued_by_known_root,
new_static_domain_state2.pkp.spki_hashes,
&failure_log));
EXPECT_NE(0UL, failure_log.length()); EXPECT_NE(0UL, failure_log.length());
} }
#undef MAYBE_UpdateDynamicPKPMaxAge0 #undef MAYBE_UpdateDynamicPKPMaxAge0
...@@ -663,6 +671,7 @@ TEST_F(HttpSecurityHeadersTest, NoClobberPins) { ...@@ -663,6 +671,7 @@ TEST_F(HttpSecurityHeadersTest, NoClobberPins) {
// accounts.google.com has preloaded pins. // accounts.google.com has preloaded pins.
std::string domain = "accounts.google.com"; std::string domain = "accounts.google.com";
state.enable_static_pins_ = true;
// Retrieve the DomainState as it is by default, including its known good // Retrieve the DomainState as it is by default, including its known good
// pins. // pins.
...@@ -680,8 +689,12 @@ TEST_F(HttpSecurityHeadersTest, NoClobberPins) { ...@@ -680,8 +689,12 @@ TEST_F(HttpSecurityHeadersTest, NoClobberPins) {
EXPECT_TRUE(state.AddHSTSHeader(domain, "includesubdomains; max-age=10000")); EXPECT_TRUE(state.AddHSTSHeader(domain, "includesubdomains; max-age=10000"));
EXPECT_TRUE(state.ShouldUpgradeToSSL(domain, sni_enabled)); EXPECT_TRUE(state.ShouldUpgradeToSSL(domain, sni_enabled));
std::string failure_log; std::string failure_log;
EXPECT_TRUE(state.CheckPublicKeyPins( const bool is_issued_by_known_root = true;
domain, sni_enabled, saved_hashes, &failure_log)); EXPECT_TRUE(state.CheckPublicKeyPins(domain,
sni_enabled,
is_issued_by_known_root,
saved_hashes,
&failure_log));
// Add an HPKP header, which should only update the dynamic state. // Add an HPKP header, which should only update the dynamic state.
HashValue good_hash = GetTestHashValue(1, HASH_VALUE_SHA1); HashValue good_hash = GetTestHashValue(1, HASH_VALUE_SHA1);
...@@ -701,8 +714,11 @@ TEST_F(HttpSecurityHeadersTest, NoClobberPins) { ...@@ -701,8 +714,11 @@ TEST_F(HttpSecurityHeadersTest, NoClobberPins) {
EXPECT_TRUE(state.ShouldUpgradeToSSL(domain, sni_enabled)); EXPECT_TRUE(state.ShouldUpgradeToSSL(domain, sni_enabled));
// The dynamic pins, which do not match |saved_hashes|, should take // The dynamic pins, which do not match |saved_hashes|, should take
// precedence over the static pins and cause the check to fail. // precedence over the static pins and cause the check to fail.
EXPECT_FALSE(state.CheckPublicKeyPins( EXPECT_FALSE(state.CheckPublicKeyPins(domain,
domain, sni_enabled, saved_hashes, &failure_log)); sni_enabled,
is_issued_by_known_root,
saved_hashes,
&failure_log));
} }
}; // namespace net }; // namespace net
...@@ -84,7 +84,12 @@ bool AddHash(const char* sha1_hash, ...@@ -84,7 +84,12 @@ bool AddHash(const char* sha1_hash,
} // namespace } // namespace
TransportSecurityState::TransportSecurityState() TransportSecurityState::TransportSecurityState()
: delegate_(NULL) { : delegate_(NULL), enable_static_pins_(true) {
// Static pinning is only enabled for official builds to make sure that
// others don't end up with pins that cannot be easily updated.
#if !defined(OFFICIAL_BUILD) || defined(OS_ANDROID) || defined(OS_IOS)
enable_static_pins_ = false;
#endif
DCHECK(CalledOnValidThread()); DCHECK(CalledOnValidThread());
} }
...@@ -118,21 +123,36 @@ bool TransportSecurityState::ShouldUpgradeToSSL(const std::string& host, ...@@ -118,21 +123,36 @@ bool TransportSecurityState::ShouldUpgradeToSSL(const std::string& host,
return false; return false;
} }
bool TransportSecurityState::CheckPublicKeyPins(const std::string& host, bool TransportSecurityState::CheckPublicKeyPins(
bool sni_enabled, const std::string& host,
const HashValueVector& hashes, bool sni_available,
std::string* failure_log) { bool is_issued_by_known_root,
DomainState dynamic_state; const HashValueVector& public_key_hashes,
if (GetDynamicDomainState(host, &dynamic_state)) std::string* pinning_failure_log) {
return dynamic_state.CheckPublicKeyPins(hashes, failure_log); // Perform pin validation if, and only if, all these conditions obtain:
//
DomainState static_state; // * the server's certificate chain chains up to a known root (i.e. not a
if (GetStaticDomainState(host, sni_enabled, &static_state) && // user-installed trust anchor); and
static_state.CheckPublicKeyPins(hashes, failure_log)) { // * the build is recent (very old builds should fail open so that users
// have some chance to recover).
// * the server actually has public key pins.
//
// TODO(rsleevi): http://crbug.com/391032 - Only disable static HPKP if the
// build is not timely.
if (!is_issued_by_known_root || !IsBuildTimely() ||
!HasPublicKeyPins(host, sni_available)) {
return true; return true;
} }
return false; bool pins_are_valid = CheckPublicKeyPinsImpl(
host, sni_available, public_key_hashes, pinning_failure_log);
if (!pins_are_valid) {
LOG(ERROR) << *pinning_failure_log;
ReportUMAOnPinFailure(host);
}
UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", pins_are_valid);
return pins_are_valid;
} }
bool TransportSecurityState::HasPublicKeyPins(const std::string& host, bool TransportSecurityState::HasPublicKeyPins(const std::string& host,
...@@ -549,9 +569,13 @@ struct HSTSPreload { ...@@ -549,9 +569,13 @@ struct HSTSPreload {
SecondLevelDomainName second_level_domain_name; SecondLevelDomainName second_level_domain_name;
}; };
static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, static bool HasPreload(const struct HSTSPreload* entries,
const std::string& canonicalized_host, size_t i, size_t num_entries,
TransportSecurityState::DomainState* out, bool* ret) { const std::string& canonicalized_host,
size_t i,
bool enable_static_pins,
TransportSecurityState::DomainState* out,
bool* ret) {
for (size_t j = 0; j < num_entries; j++) { for (size_t j = 0; j < num_entries; j++) {
if (entries[j].length == canonicalized_host.size() - i && if (entries[j].length == canonicalized_host.size() - i &&
memcmp(entries[j].dns_name, &canonicalized_host[i], memcmp(entries[j].dns_name, &canonicalized_host[i],
...@@ -561,14 +585,16 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, ...@@ -561,14 +585,16 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries,
} else { } else {
out->sts.include_subdomains = entries[j].include_subdomains; out->sts.include_subdomains = entries[j].include_subdomains;
out->sts.last_observed = base::GetBuildTime(); out->sts.last_observed = base::GetBuildTime();
out->pkp.include_subdomains = entries[j].include_subdomains;
out->pkp.last_observed = base::GetBuildTime();
*ret = true; *ret = true;
out->sts.upgrade_mode = out->sts.upgrade_mode =
TransportSecurityState::DomainState::MODE_FORCE_HTTPS; TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
if (!entries[j].https_required) if (!entries[j].https_required)
out->sts.upgrade_mode = out->sts.upgrade_mode =
TransportSecurityState::DomainState::MODE_DEFAULT; TransportSecurityState::DomainState::MODE_DEFAULT;
if (enable_static_pins) {
out->pkp.include_subdomains = entries[j].include_subdomains;
out->pkp.last_observed = base::GetBuildTime();
if (entries[j].pins.required_hashes) { if (entries[j].pins.required_hashes) {
const char* const* sha1_hash = entries[j].pins.required_hashes; const char* const* sha1_hash = entries[j].pins.required_hashes;
while (*sha1_hash) { while (*sha1_hash) {
...@@ -584,6 +610,7 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, ...@@ -584,6 +610,7 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries,
} }
} }
} }
}
return true; return true;
} }
} }
...@@ -763,6 +790,24 @@ bool TransportSecurityState::IsBuildTimely() { ...@@ -763,6 +790,24 @@ bool TransportSecurityState::IsBuildTimely() {
return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */; return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */;
} }
bool TransportSecurityState::CheckPublicKeyPinsImpl(
const std::string& host,
bool sni_enabled,
const HashValueVector& hashes,
std::string* failure_log) {
DomainState dynamic_state;
if (GetDynamicDomainState(host, &dynamic_state))
return dynamic_state.CheckPublicKeyPins(hashes, failure_log);
DomainState static_state;
if (GetStaticDomainState(host, sni_enabled, &static_state))
return static_state.CheckPublicKeyPins(hashes, failure_log);
// HasPublicKeyPins should have returned true in order for this method
// to have been called, so if we fall through to here, it's an error.
return false;
}
bool TransportSecurityState::GetStaticDomainState(const std::string& host, bool TransportSecurityState::GetStaticDomainState(const std::string& host,
bool sni_enabled, bool sni_enabled,
DomainState* out) const { DomainState* out) const {
...@@ -781,15 +826,22 @@ bool TransportSecurityState::GetStaticDomainState(const std::string& host, ...@@ -781,15 +826,22 @@ bool TransportSecurityState::GetStaticDomainState(const std::string& host,
canonicalized_host.size() - i); canonicalized_host.size() - i);
out->domain = DNSDomainToString(host_sub_chunk); out->domain = DNSDomainToString(host_sub_chunk);
bool ret; bool ret;
if (is_build_timely && if (is_build_timely && HasPreload(kPreloadedSTS,
HasPreload(kPreloadedSTS, kNumPreloadedSTS, canonicalized_host, i, out, kNumPreloadedSTS,
canonicalized_host,
i,
enable_static_pins_,
out,
&ret)) { &ret)) {
return ret; return ret;
} }
if (sni_enabled && if (sni_enabled && is_build_timely && HasPreload(kPreloadedSNISTS,
is_build_timely && kNumPreloadedSNISTS,
HasPreload(kPreloadedSNISTS, kNumPreloadedSNISTS, canonicalized_host, i, canonicalized_host,
out, &ret)) { i,
enable_static_pins_,
out,
&ret)) {
return ret; return ret;
} }
} }
......
...@@ -163,6 +163,7 @@ class NET_EXPORT TransportSecurityState ...@@ -163,6 +163,7 @@ class NET_EXPORT TransportSecurityState
bool ShouldUpgradeToSSL(const std::string& host, bool sni_enabled); bool ShouldUpgradeToSSL(const std::string& host, bool sni_enabled);
bool CheckPublicKeyPins(const std::string& host, bool CheckPublicKeyPins(const std::string& host,
bool sni_enabled, bool sni_enabled,
bool is_issued_by_known_root,
const HashValueVector& hashes, const HashValueVector& hashes,
std::string* failure_log); std::string* failure_log);
bool HasPublicKeyPins(const std::string& host, bool sni_enabled); bool HasPublicKeyPins(const std::string& host, bool sni_enabled);
...@@ -267,6 +268,14 @@ class NET_EXPORT TransportSecurityState ...@@ -267,6 +268,14 @@ class NET_EXPORT TransportSecurityState
// The maximum number of seconds for which we'll cache an HSTS request. // The maximum number of seconds for which we'll cache an HSTS request.
static const long int kMaxHSTSAgeSecs; static const long int kMaxHSTSAgeSecs;
private:
friend class TransportSecurityStateTest;
FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest, UpdateDynamicPKPOnly);
FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest, UpdateDynamicPKPMaxAge0);
FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest, NoClobberPins);
typedef std::map<std::string, DomainState> DomainStateMap;
// Send an UMA report on pin validation failure, if the host is in a // Send an UMA report on pin validation failure, if the host is in a
// statically-defined list of domains. // statically-defined list of domains.
// //
...@@ -282,12 +291,11 @@ class NET_EXPORT TransportSecurityState ...@@ -282,12 +291,11 @@ class NET_EXPORT TransportSecurityState
// information) is timely. // information) is timely.
static bool IsBuildTimely(); static bool IsBuildTimely();
private: // Helper method for actually checking pins.
friend class TransportSecurityStateTest; bool CheckPublicKeyPinsImpl(const std::string& host,
FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest, bool sni_enabled,
UpdateDynamicPKPOnly); const HashValueVector& hashes,
std::string* failure_log);
typedef std::map<std::string, DomainState> DomainStateMap;
// If a Delegate is present, notify it that the internal state has // If a Delegate is present, notify it that the internal state has
// changed. // changed.
...@@ -309,6 +317,9 @@ class NET_EXPORT TransportSecurityState ...@@ -309,6 +317,9 @@ class NET_EXPORT TransportSecurityState
Delegate* delegate_; Delegate* delegate_;
// True if static pins should be used.
bool enable_static_pins_;
DISALLOW_COPY_AND_ASSIGN(TransportSecurityState); DISALLOW_COPY_AND_ASSIGN(TransportSecurityState);
}; };
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
namespace net { namespace net {
class TransportSecurityStateTest : public testing::Test { class TransportSecurityStateTest : public testing::Test {
public:
virtual void SetUp() { virtual void SetUp() {
#if defined(USE_OPENSSL) #if defined(USE_OPENSSL)
crypto::EnsureOpenSSLInit(); crypto::EnsureOpenSSLInit();
...@@ -45,6 +46,14 @@ class TransportSecurityStateTest : public testing::Test { ...@@ -45,6 +46,14 @@ class TransportSecurityStateTest : public testing::Test {
#endif #endif
} }
static void DisableStaticPins(TransportSecurityState* state) {
state->enable_static_pins_ = false;
}
static void EnableStaticPins(TransportSecurityState* state) {
state->enable_static_pins_ = true;
}
protected: protected:
bool GetStaticDomainState(TransportSecurityState* state, bool GetStaticDomainState(TransportSecurityState* state,
const std::string& host, const std::string& host,
...@@ -162,6 +171,27 @@ TEST_F(TransportSecurityStateTest, DeleteDynamicDataForHost) { ...@@ -162,6 +171,27 @@ TEST_F(TransportSecurityStateTest, DeleteDynamicDataForHost) {
EXPECT_FALSE(state.GetDynamicDomainState("yahoo.com", &domain_state)); EXPECT_FALSE(state.GetDynamicDomainState("yahoo.com", &domain_state));
} }
TEST_F(TransportSecurityStateTest, EnableStaticPins) {
TransportSecurityState state;
TransportSecurityState::DomainState domain_state;
EnableStaticPins(&state);
EXPECT_TRUE(
state.GetStaticDomainState("chrome.google.com", true, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
}
TEST_F(TransportSecurityStateTest, DisableStaticPins) {
TransportSecurityState state;
TransportSecurityState::DomainState domain_state;
DisableStaticPins(&state);
EXPECT_TRUE(
state.GetStaticDomainState("chrome.google.com", true, &domain_state));
EXPECT_TRUE(domain_state.pkp.spki_hashes.empty());
}
TEST_F(TransportSecurityStateTest, IsPreloaded) { TEST_F(TransportSecurityStateTest, IsPreloaded) {
const std::string paypal = "paypal.com"; const std::string paypal = "paypal.com";
const std::string www_paypal = "www.paypal.com"; const std::string www_paypal = "www.paypal.com";
...@@ -177,7 +207,6 @@ TEST_F(TransportSecurityStateTest, IsPreloaded) { ...@@ -177,7 +207,6 @@ TEST_F(TransportSecurityStateTest, IsPreloaded) {
EXPECT_TRUE(GetStaticDomainState(&state, paypal, true, &domain_state)); EXPECT_TRUE(GetStaticDomainState(&state, paypal, true, &domain_state));
EXPECT_TRUE(GetStaticDomainState(&state, www_paypal, true, &domain_state)); EXPECT_TRUE(GetStaticDomainState(&state, www_paypal, true, &domain_state));
EXPECT_FALSE(domain_state.sts.include_subdomains); EXPECT_FALSE(domain_state.sts.include_subdomains);
EXPECT_FALSE(domain_state.pkp.include_subdomains);
EXPECT_FALSE(GetStaticDomainState(&state, a_www_paypal, true, &domain_state)); EXPECT_FALSE(GetStaticDomainState(&state, a_www_paypal, true, &domain_state));
EXPECT_FALSE(GetStaticDomainState(&state, abc_paypal, true, &domain_state)); EXPECT_FALSE(GetStaticDomainState(&state, abc_paypal, true, &domain_state));
EXPECT_FALSE(GetStaticDomainState(&state, example, true, &domain_state)); EXPECT_FALSE(GetStaticDomainState(&state, example, true, &domain_state));
...@@ -214,6 +243,7 @@ static bool HasStaticState(const char* hostname) { ...@@ -214,6 +243,7 @@ static bool HasStaticState(const char* hostname) {
static bool HasStaticPublicKeyPins(const char* hostname, bool sni_enabled) { static bool HasStaticPublicKeyPins(const char* hostname, bool sni_enabled) {
TransportSecurityState state; TransportSecurityState state;
TransportSecurityStateTest::EnableStaticPins(&state);
TransportSecurityState::DomainState domain_state; TransportSecurityState::DomainState domain_state;
if (!state.GetStaticDomainState(hostname, sni_enabled, &domain_state)) if (!state.GetStaticDomainState(hostname, sni_enabled, &domain_state))
return false; return false;
...@@ -227,6 +257,7 @@ static bool HasStaticPublicKeyPins(const char* hostname) { ...@@ -227,6 +257,7 @@ static bool HasStaticPublicKeyPins(const char* hostname) {
static bool OnlyPinningInStaticState(const char* hostname) { static bool OnlyPinningInStaticState(const char* hostname) {
TransportSecurityState state; TransportSecurityState state;
TransportSecurityStateTest::EnableStaticPins(&state);
TransportSecurityState::DomainState domain_state; TransportSecurityState::DomainState domain_state;
if (!state.GetStaticDomainState(hostname, true /* SNI ok */, &domain_state)) if (!state.GetStaticDomainState(hostname, true /* SNI ok */, &domain_state))
return false; return false;
...@@ -285,24 +316,6 @@ TEST_F(TransportSecurityStateTest, Preloaded) { ...@@ -285,24 +316,6 @@ TEST_F(TransportSecurityStateTest, Preloaded) {
EXPECT_FALSE(HasStaticState("m.gmail.com")); EXPECT_FALSE(HasStaticState("m.gmail.com"));
EXPECT_FALSE(HasStaticState("m.googlemail.com")); EXPECT_FALSE(HasStaticState("m.googlemail.com"));
EXPECT_TRUE(OnlyPinningInStaticState("www.google.com"));
EXPECT_TRUE(OnlyPinningInStaticState("foo.google.com"));
EXPECT_TRUE(OnlyPinningInStaticState("google.com"));
EXPECT_TRUE(OnlyPinningInStaticState("www.youtube.com"));
EXPECT_TRUE(OnlyPinningInStaticState("youtube.com"));
EXPECT_TRUE(OnlyPinningInStaticState("i.ytimg.com"));
EXPECT_TRUE(OnlyPinningInStaticState("ytimg.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googleusercontent.com"));
EXPECT_TRUE(OnlyPinningInStaticState("www.googleusercontent.com"));
EXPECT_TRUE(OnlyPinningInStaticState("www.google-analytics.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googleapis.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googleadservices.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googlecode.com"));
EXPECT_TRUE(OnlyPinningInStaticState("appspot.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googlesyndication.com"));
EXPECT_TRUE(OnlyPinningInStaticState("doubleclick.net"));
EXPECT_TRUE(OnlyPinningInStaticState("googlegroups.com"));
// Tests for domains that don't work without SNI. // Tests for domains that don't work without SNI.
EXPECT_FALSE(state.GetStaticDomainState("gmail.com", false, &domain_state)); EXPECT_FALSE(state.GetStaticDomainState("gmail.com", false, &domain_state));
EXPECT_FALSE( EXPECT_FALSE(
...@@ -407,28 +420,12 @@ TEST_F(TransportSecurityStateTest, Preloaded) { ...@@ -407,28 +420,12 @@ TEST_F(TransportSecurityStateTest, Preloaded) {
EXPECT_TRUE(StaticShouldRedirect("www.dropcam.com")); EXPECT_TRUE(StaticShouldRedirect("www.dropcam.com"));
EXPECT_FALSE(HasStaticState("foo.dropcam.com")); EXPECT_FALSE(HasStaticState("foo.dropcam.com"));
EXPECT_TRUE(
state.GetStaticDomainState("torproject.org", false, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
EXPECT_TRUE(
state.GetStaticDomainState("www.torproject.org", false, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
EXPECT_TRUE(
state.GetStaticDomainState("check.torproject.org", false, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
EXPECT_TRUE(
state.GetStaticDomainState("blog.torproject.org", false, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
EXPECT_TRUE(StaticShouldRedirect("ebanking.indovinabank.com.vn")); EXPECT_TRUE(StaticShouldRedirect("ebanking.indovinabank.com.vn"));
EXPECT_TRUE(StaticShouldRedirect("foo.ebanking.indovinabank.com.vn")); EXPECT_TRUE(StaticShouldRedirect("foo.ebanking.indovinabank.com.vn"));
EXPECT_TRUE(StaticShouldRedirect("epoxate.com")); EXPECT_TRUE(StaticShouldRedirect("epoxate.com"));
EXPECT_FALSE(HasStaticState("foo.epoxate.com")); EXPECT_FALSE(HasStaticState("foo.epoxate.com"));
EXPECT_TRUE(HasStaticPublicKeyPins("torproject.org"));
EXPECT_TRUE(HasStaticPublicKeyPins("www.torproject.org"));
EXPECT_TRUE(HasStaticPublicKeyPins("check.torproject.org"));
EXPECT_TRUE(HasStaticPublicKeyPins("blog.torproject.org"));
EXPECT_FALSE(HasStaticState("foo.torproject.org")); EXPECT_FALSE(HasStaticState("foo.torproject.org"));
EXPECT_TRUE(StaticShouldRedirect("www.moneybookers.com")); EXPECT_TRUE(StaticShouldRedirect("www.moneybookers.com"));
...@@ -478,6 +475,57 @@ TEST_F(TransportSecurityStateTest, Preloaded) { ...@@ -478,6 +475,57 @@ TEST_F(TransportSecurityStateTest, Preloaded) {
EXPECT_TRUE(StaticShouldRedirect("crate.io")); EXPECT_TRUE(StaticShouldRedirect("crate.io"));
EXPECT_TRUE(StaticShouldRedirect("foo.crate.io")); EXPECT_TRUE(StaticShouldRedirect("foo.crate.io"));
}
TEST_F(TransportSecurityStateTest, PreloadedPins) {
TransportSecurityState state;
EnableStaticPins(&state);
TransportSecurityState::DomainState domain_state;
// We do more extensive checks for the first domain.
EXPECT_TRUE(
state.GetStaticDomainState("www.paypal.com", true, &domain_state));
EXPECT_EQ(domain_state.sts.upgrade_mode,
TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
EXPECT_FALSE(domain_state.sts.include_subdomains);
EXPECT_FALSE(domain_state.pkp.include_subdomains);
EXPECT_TRUE(OnlyPinningInStaticState("www.google.com"));
EXPECT_TRUE(OnlyPinningInStaticState("foo.google.com"));
EXPECT_TRUE(OnlyPinningInStaticState("google.com"));
EXPECT_TRUE(OnlyPinningInStaticState("www.youtube.com"));
EXPECT_TRUE(OnlyPinningInStaticState("youtube.com"));
EXPECT_TRUE(OnlyPinningInStaticState("i.ytimg.com"));
EXPECT_TRUE(OnlyPinningInStaticState("ytimg.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googleusercontent.com"));
EXPECT_TRUE(OnlyPinningInStaticState("www.googleusercontent.com"));
EXPECT_TRUE(OnlyPinningInStaticState("www.google-analytics.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googleapis.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googleadservices.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googlecode.com"));
EXPECT_TRUE(OnlyPinningInStaticState("appspot.com"));
EXPECT_TRUE(OnlyPinningInStaticState("googlesyndication.com"));
EXPECT_TRUE(OnlyPinningInStaticState("doubleclick.net"));
EXPECT_TRUE(OnlyPinningInStaticState("googlegroups.com"));
EXPECT_TRUE(HasStaticPublicKeyPins("torproject.org"));
EXPECT_TRUE(HasStaticPublicKeyPins("www.torproject.org"));
EXPECT_TRUE(HasStaticPublicKeyPins("check.torproject.org"));
EXPECT_TRUE(HasStaticPublicKeyPins("blog.torproject.org"));
EXPECT_FALSE(HasStaticState("foo.torproject.org"));
EXPECT_TRUE(
state.GetStaticDomainState("torproject.org", false, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
EXPECT_TRUE(
state.GetStaticDomainState("www.torproject.org", false, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
EXPECT_TRUE(
state.GetStaticDomainState("check.torproject.org", false, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
EXPECT_TRUE(
state.GetStaticDomainState("blog.torproject.org", false, &domain_state));
EXPECT_FALSE(domain_state.pkp.spki_hashes.empty());
EXPECT_TRUE(HasStaticPublicKeyPins("www.twitter.com")); EXPECT_TRUE(HasStaticPublicKeyPins("www.twitter.com"));
} }
...@@ -495,6 +543,7 @@ TEST_F(TransportSecurityStateTest, LongNames) { ...@@ -495,6 +543,7 @@ TEST_F(TransportSecurityStateTest, LongNames) {
TEST_F(TransportSecurityStateTest, BuiltinCertPins) { TEST_F(TransportSecurityStateTest, BuiltinCertPins) {
TransportSecurityState state; TransportSecurityState state;
EnableStaticPins(&state);
TransportSecurityState::DomainState domain_state; TransportSecurityState::DomainState domain_state;
EXPECT_TRUE( EXPECT_TRUE(
...@@ -534,7 +583,6 @@ TEST_F(TransportSecurityStateTest, BuiltinCertPins) { ...@@ -534,7 +583,6 @@ TEST_F(TransportSecurityStateTest, BuiltinCertPins) {
EXPECT_TRUE(HasStaticPublicKeyPins("ssl.google-analytics.com")); EXPECT_TRUE(HasStaticPublicKeyPins("ssl.google-analytics.com"));
EXPECT_TRUE(HasStaticPublicKeyPins("www.googleplex.com")); EXPECT_TRUE(HasStaticPublicKeyPins("www.googleplex.com"));
// Disabled in order to help track down pinning failures --agl
EXPECT_TRUE(HasStaticPublicKeyPins("twitter.com")); EXPECT_TRUE(HasStaticPublicKeyPins("twitter.com"));
EXPECT_FALSE(HasStaticPublicKeyPins("foo.twitter.com")); EXPECT_FALSE(HasStaticPublicKeyPins("foo.twitter.com"));
EXPECT_TRUE(HasStaticPublicKeyPins("www.twitter.com")); EXPECT_TRUE(HasStaticPublicKeyPins("www.twitter.com"));
...@@ -585,6 +633,8 @@ TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) { ...@@ -585,6 +633,8 @@ TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) {
} }
TransportSecurityState state; TransportSecurityState state;
EnableStaticPins(&state);
TransportSecurityState::DomainState domain_state; TransportSecurityState::DomainState domain_state;
EXPECT_TRUE( EXPECT_TRUE(
state.GetStaticDomainState("blog.torproject.org", true, &domain_state)); state.GetStaticDomainState("blog.torproject.org", true, &domain_state));
...@@ -597,6 +647,7 @@ TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) { ...@@ -597,6 +647,7 @@ TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) {
TEST_F(TransportSecurityStateTest, OptionalHSTSCertPins) { TEST_F(TransportSecurityStateTest, OptionalHSTSCertPins) {
TransportSecurityState state; TransportSecurityState state;
EnableStaticPins(&state);
TransportSecurityState::DomainState domain_state; TransportSecurityState::DomainState domain_state;
EXPECT_FALSE(StaticShouldRedirect("www.google-analytics.com")); EXPECT_FALSE(StaticShouldRedirect("www.google-analytics.com"));
......
...@@ -236,58 +236,20 @@ int ProofVerifierChromium::Job::DoVerifyCert(int result) { ...@@ -236,58 +236,20 @@ int ProofVerifierChromium::Job::DoVerifyCert(int result) {
int ProofVerifierChromium::Job::DoVerifyCertComplete(int result) { int ProofVerifierChromium::Job::DoVerifyCertComplete(int result) {
verifier_.reset(); verifier_.reset();
#if defined(OFFICIAL_BUILD) && !defined(OS_ANDROID) && !defined(OS_IOS)
// TODO(wtc): The following code was copied from ssl_client_socket_nss.cc.
// Convert it to a new function that can be called by both files. These
// variables simulate the arguments to the new function.
const CertVerifyResult& cert_verify_result = const CertVerifyResult& cert_verify_result =
verify_details_->cert_verify_result; verify_details_->cert_verify_result;
bool sni_available = true;
const std::string& host = hostname_;
TransportSecurityState* transport_security_state = transport_security_state_;
std::string* pinning_failure_log = &verify_details_->pinning_failure_log;
// Take care of any mandates for public key pinning.
//
// Pinning is only enabled for official builds to make sure that others don't
// end up with pins that cannot be easily updated.
//
// TODO(agl): We might have an issue here where a request for foo.example.com
// merges into a SPDY connection to www.example.com, and gets a different
// certificate.
// Perform pin validation if, and only if, all these conditions obtain:
//
// * a TransportSecurityState object is available;
// * the server's certificate chain is valid (or suffers from only a minor
// error);
// * the server's certificate chain chains up to a known root (i.e. not a
// user-installed trust anchor); and
// * the build is recent (very old builds should fail open so that users
// have some chance to recover).
//
const CertStatus cert_status = cert_verify_result.cert_status; const CertStatus cert_status = cert_verify_result.cert_status;
if (transport_security_state && if (transport_security_state_ &&
(result == OK || (result == OK ||
(IsCertificateError(result) && IsCertStatusMinorError(cert_status))) && (IsCertificateError(result) && IsCertStatusMinorError(cert_status))) &&
cert_verify_result.is_issued_by_known_root && !transport_security_state_->CheckPublicKeyPins(
TransportSecurityState::IsBuildTimely()) { hostname_,
if (transport_security_state->HasPublicKeyPins(host, sni_available)) { true, /* sni_available */
if (!transport_security_state->CheckPublicKeyPins( cert_verify_result.is_issued_by_known_root,
host,
sni_available,
cert_verify_result.public_key_hashes, cert_verify_result.public_key_hashes,
pinning_failure_log)) { &verify_details_->pinning_failure_log)) {
LOG(ERROR) << *pinning_failure_log;
result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN; result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN;
UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", false);
TransportSecurityState::ReportUMAOnPinFailure(host);
} else {
UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", true);
}
}
} }
#endif
if (result != OK) { if (result != OK) {
std::string error_string = ErrorToString(result); std::string error_string = ErrorToString(result);
......
...@@ -3427,53 +3427,20 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) { ...@@ -3427,53 +3427,20 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) {
if (result == OK) if (result == OK)
LogConnectionTypeMetrics(); LogConnectionTypeMetrics();
#if defined(OFFICIAL_BUILD) && !defined(OS_ANDROID) && !defined(OS_IOS) bool sni_available = ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1 ||
// Take care of any mandates for public key pinning. ssl_config_.version_fallback;
//
// Pinning is only enabled for official builds to make sure that others don't
// end up with pins that cannot be easily updated.
//
// TODO(agl): We might have an issue here where a request for foo.example.com
// merges into a SPDY connection to www.example.com, and gets a different
// certificate.
// Perform pin validation if, and only if, all these conditions obtain:
//
// * a TransportSecurityState object is available;
// * the server's certificate chain is valid (or suffers from only a minor
// error);
// * the server's certificate chain chains up to a known root (i.e. not a
// user-installed trust anchor); and
// * the build is recent (very old builds should fail open so that users
// have some chance to recover).
//
const CertStatus cert_status = server_cert_verify_result_.cert_status; const CertStatus cert_status = server_cert_verify_result_.cert_status;
if (transport_security_state_ && if (transport_security_state_ &&
(result == OK || (result == OK ||
(IsCertificateError(result) && IsCertStatusMinorError(cert_status))) && (IsCertificateError(result) && IsCertStatusMinorError(cert_status))) &&
server_cert_verify_result_.is_issued_by_known_root && !transport_security_state_->CheckPublicKeyPins(
TransportSecurityState::IsBuildTimely()) { host_and_port_.host(),
bool sni_available =
ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1 ||
ssl_config_.version_fallback;
const std::string& host = host_and_port_.host();
if (transport_security_state_->HasPublicKeyPins(host, sni_available)) {
if (!transport_security_state_->CheckPublicKeyPins(
host,
sni_available, sni_available,
server_cert_verify_result_.is_issued_by_known_root,
server_cert_verify_result_.public_key_hashes, server_cert_verify_result_.public_key_hashes,
&pinning_failure_log_)) { &pinning_failure_log_)) {
LOG(ERROR) << pinning_failure_log_;
result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN; result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN;
UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", false);
TransportSecurityState::ReportUMAOnPinFailure(host);
} else {
UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", true);
} }
}
}
#endif
if (result == OK) { if (result == OK) {
// Only check Certificate Transparency if there were no other errors with // Only check Certificate Transparency if there were no other errors with
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment