summaryrefslogtreecommitdiffstats
path: root/net/base
diff options
context:
space:
mode:
authoragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-14 14:30:58 +0000
committeragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-14 14:30:58 +0000
commit381e88561fbeba7d1d98fad78ceddfef2871959b (patch)
treebbdaef7584f7e66ec777eadaed036b2976b9b1bb /net/base
parente3ff0f76a65fdae99aa892b8471061d70dcd2809 (diff)
downloadchromium_src-381e88561fbeba7d1d98fad78ceddfef2871959b.zip
chromium_src-381e88561fbeba7d1d98fad78ceddfef2871959b.tar.gz
chromium_src-381e88561fbeba7d1d98fad78ceddfef2871959b.tar.bz2
net: implement the beginnings of HSTS pinning
(Based on a patch by Chris Evans.) Doesn't yet actually get the information from the HSTS header, but all the infrastructure is in place. BUG=none TEST=none Review URL: http://codereview.chromium.org/6835033 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@81584 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r--net/base/transport_security_state.cc78
-rw-r--r--net/base/transport_security_state.h10
-rw-r--r--net/base/transport_security_state_unittest.cc33
3 files changed, 120 insertions, 1 deletions
diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc
index 44dce0e..8f578c2 100644
--- a/net/base/transport_security_state.cc
+++ b/net/base/transport_security_state.cc
@@ -9,6 +9,7 @@
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
+#include "base/sha1.h"
#include "base/sha2.h"
#include "base/string_number_conversions.h"
#include "base/string_tokenizer.h"
@@ -32,6 +33,9 @@ void TransportSecurityState::EnableHost(const std::string& host,
if (canonicalized_host.empty())
return;
+ // TODO(cevans) -- we likely want to permit a host to override a built-in,
+ // for at least the case where the override is stricter (i.e. includes
+ // subdomains, or includes certificate pinning).
bool temp;
if (IsPreloadedSTS(canonicalized_host, true, &temp))
return;
@@ -313,6 +317,18 @@ bool TransportSecurityState::Serialise(std::string* output) {
continue;
}
+ ListValue* pins = new ListValue;
+ for (std::vector<SHA1Fingerprint>::const_iterator
+ j = i->second.public_key_hashes.begin();
+ j != i->second.public_key_hashes.end(); ++j) {
+ std::string hash_str(reinterpret_cast<const char*>(j->data),
+ sizeof(j->data));
+ std::string b64;
+ base::Base64Encode(hash_str, &b64);
+ pins->Append(new StringValue("sha1/" + b64));
+ }
+ state->Set("public_key_hashes", pins);
+
toplevel.Set(HashedDomainToExternalString(i->first), state);
}
@@ -350,6 +366,26 @@ bool TransportSecurityState::Deserialise(const std::string& input,
continue;
}
+ ListValue* pins_list = NULL;
+ std::vector<SHA1Fingerprint> public_key_hashes;
+ if (state->GetList("public_key_hashes", &pins_list)) {
+ size_t num_pins = pins_list->GetSize();
+ for (size_t i = 0; i < num_pins; ++i) {
+ std::string type_and_base64;
+ std::string hash_str;
+ SHA1Fingerprint hash;
+ if (pins_list->GetString(i, &type_and_base64) &&
+ type_and_base64.find("sha1/") == 0 &&
+ base::Base64Decode(
+ type_and_base64.substr(5, type_and_base64.size() - 5),
+ &hash_str) &&
+ hash_str.size() == base::SHA1_LENGTH) {
+ memcpy(hash.data, hash_str.data(), sizeof(hash.data));
+ public_key_hashes.push_back(hash);
+ }
+ }
+ }
+
DomainState::Mode mode;
if (mode_string == "strict") {
mode = DomainState::MODE_STRICT;
@@ -381,14 +417,17 @@ bool TransportSecurityState::Deserialise(const std::string& input,
}
std::string hashed = ExternalStringToHashedDomain(*i);
- if (hashed.empty())
+ if (hashed.empty()) {
+ dirtied = true;
continue;
+ }
DomainState new_state;
new_state.mode = mode;
new_state.created = created_time;
new_state.expiry = expiry_time;
new_state.include_subdomains = include_subdomains;
+ new_state.public_key_hashes = public_key_hashes;
enabled_hosts_[hashed] = new_state;
}
@@ -524,4 +563,41 @@ bool TransportSecurityState::IsPreloadedSTS(
return false;
}
+static std::string HashesToBase64String(
+ const std::vector<net::SHA1Fingerprint>& hashes) {
+ std::vector<std::string> hashes_strs;
+ for (std::vector<net::SHA1Fingerprint>::const_iterator
+ i = hashes.begin(); i != hashes.end(); i++) {
+ std::string s;
+ const std::string hash_str(reinterpret_cast<const char*>(i->data),
+ sizeof(i->data));
+ base::Base64Encode(hash_str, &s);
+ hashes_strs.push_back(s);
+ }
+
+ return JoinString(hashes_strs, ',');
+}
+
+bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted(
+ const std::vector<net::SHA1Fingerprint>& hashes) {
+ if (public_key_hashes.empty())
+ return true;
+
+ for (std::vector<net::SHA1Fingerprint>::const_iterator
+ i = hashes.begin(); i != hashes.end(); ++i) {
+ for (std::vector<net::SHA1Fingerprint>::const_iterator
+ j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) {
+ if (i->Equals(*j))
+ return true;
+ }
+ }
+
+
+ LOG(ERROR) << "Rejecting public key chain for domain " << domain
+ << ". Validated chain: " << HashesToBase64String(hashes)
+ << ", expected: " << HashesToBase64String(public_key_hashes);
+
+ return false;
+}
+
} // namespace
diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h
index e7705f5..33bae61 100644
--- a/net/base/transport_security_state.h
+++ b/net/base/transport_security_state.h
@@ -8,11 +8,13 @@
#include <map>
#include <string>
+#include <vector>
#include "base/basictypes.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/time.h"
+#include "net/base/x509_cert_types.h"
namespace net {
@@ -50,10 +52,18 @@ class TransportSecurityState :
include_subdomains(false),
preloaded(false) { }
+ // IsChainOfPublicKeysPermitted takes a set of public key hashes and
+ // returns true if:
+ // 1) |public_key_hashes| is empty, i.e. no public keys have been pinned.
+ // 2) |hashes| and |public_key_hashes| are not disjoint.
+ bool IsChainOfPublicKeysPermitted(
+ const std::vector<SHA1Fingerprint>& hashes);
+
Mode mode;
base::Time created; // when this host entry was first created
base::Time expiry; // the absolute time (UTC) when this record expires
bool include_subdomains; // subdomains included?
+ std::vector<SHA1Fingerprint> public_key_hashes; // optional; permitted keys
// The follow members are not valid when stored in |enabled_hosts_|.
bool preloaded; // is this a preloaded entry?
diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc
index 9823072..d2db7f9 100644
--- a/net/base/transport_security_state_unittest.cc
+++ b/net/base/transport_security_state_unittest.cc
@@ -501,4 +501,37 @@ TEST_F(TransportSecurityStateTest, LongNames) {
EXPECT_FALSE(state->IsEnabledForHost(&domain_state, kLongName, true));
}
+TEST_F(TransportSecurityStateTest, PublicKeyHashes) {
+ scoped_refptr<TransportSecurityState> state(
+ new TransportSecurityState);
+
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "example.com", false));
+ std::vector<SHA1Fingerprint> hashes;
+ EXPECT_TRUE(domain_state.IsChainOfPublicKeysPermitted(hashes));
+
+ SHA1Fingerprint hash;
+ memset(hash.data, '1', sizeof(hash.data));
+ domain_state.public_key_hashes.push_back(hash);
+
+ EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(hashes));
+ hashes.push_back(hash);
+ EXPECT_TRUE(domain_state.IsChainOfPublicKeysPermitted(hashes));
+ hashes[0].data[0] = '2';
+ EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(hashes));
+
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ domain_state.expiry = expiry;
+ state->EnableHost("example.com", domain_state);
+ std::string ser;
+ EXPECT_TRUE(state->Serialise(&ser));
+ bool dirty;
+ EXPECT_TRUE(state->Deserialise(ser, &dirty));
+ EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "example.com", false));
+ EXPECT_EQ(1u, domain_state.public_key_hashes.size());
+ EXPECT_TRUE(0 == memcmp(domain_state.public_key_hashes[0].data, hash.data,
+ sizeof(hash.data)));
+}
+
} // namespace net