Index: net/base/strict_transport_security_state.cc |
diff --git a/net/base/strict_transport_security_state.cc b/net/base/strict_transport_security_state.cc |
index 58f9f25bd4e4286701d5cfeb583fc384fa8f3be2..59b8949463651f660b7bd87c53cdef5f4289142a 100644 |
--- a/net/base/strict_transport_security_state.cc |
+++ b/net/base/strict_transport_security_state.cc |
@@ -8,11 +8,13 @@ |
#include "base/json_writer.h" |
#include "base/logging.h" |
#include "base/scoped_ptr.h" |
+#include "base/sha2.h" |
#include "base/string_tokenizer.h" |
#include "base/string_util.h" |
#include "base/values.h" |
#include "googleurl/src/gurl.h" |
-#include "net/base/registry_controlled_domain.h" |
+#include "net/base/base64.h" |
+#include "net/base/dns_util.h" |
namespace net { |
@@ -36,33 +38,54 @@ void StrictTransportSecurityState::DidReceiveHeader(const GURL& url, |
} |
void StrictTransportSecurityState::EnableHost(const std::string& host, |
- base::Time expiry, |
- bool include_subdomains) { |
- // TODO(abarth): Canonicalize host. |
+ base::Time expiry, |
+ bool include_subdomains) { |
+ const std::string canonicalised_host = CanonicaliseHost(host); |
+ if (canonicalised_host.empty()) |
+ return; |
+ char hashed[base::SHA256_LENGTH]; |
+ base::SHA256HashString(canonicalised_host, hashed, sizeof(hashed)); |
+ |
AutoLock lock(lock_); |
State state = {expiry, include_subdomains}; |
- enabled_hosts_[host] = state; |
+ enabled_hosts_[std::string(hashed, sizeof(hashed))] = state; |
DirtyNotify(); |
} |
bool StrictTransportSecurityState::IsEnabledForHost(const std::string& host) { |
- // TODO(abarth): Canonicalize host. |
- // TODO: check for subdomains too. |
- |
- AutoLock lock(lock_); |
- std::map<std::string, State>::iterator i = enabled_hosts_.find(host); |
- if (i == enabled_hosts_.end()) |
+ const std::string canonicalised_host = CanonicaliseHost(host); |
+ if (canonicalised_host.empty()) |
return false; |
base::Time current_time(base::Time::Now()); |
- if (current_time > i->second.expiry) { |
- enabled_hosts_.erase(i); |
- DirtyNotify(); |
- return false; |
+ AutoLock lock(lock_); |
+ |
+ for (size_t i = 0; canonicalised_host[i]; i += canonicalised_host[i] + 1) { |
+ char hashed_domain[base::SHA256_LENGTH]; |
+ |
+ base::SHA256HashString(&canonicalised_host[i], &hashed_domain, |
+ sizeof(hashed_domain)); |
+ std::map<std::string, State>::iterator j = |
+ enabled_hosts_.find(std::string(hashed_domain, sizeof(hashed_domain))); |
+ if (j == enabled_hosts_.end()) |
+ continue; |
+ |
+ if (current_time > j->second.expiry) { |
+ enabled_hosts_.erase(j); |
+ DirtyNotify(); |
+ continue; |
+ } |
+ |
+ // If we matched the domain exactly, it doesn't matter what the value of |
+ // include_subdomains is. |
+ if (i == 0) |
+ return true; |
+ |
+ return j->second.include_subdomains; |
} |
- return true; |
+ return false; |
} |
// "X-Force-TLS" ":" "max-age" "=" delta-seconds *1INCLUDESUBDOMAINS |
@@ -169,6 +192,27 @@ void StrictTransportSecurityState::SetDelegate( |
delegate_ = delegate; |
} |
+// This function converts the binary hashes, which we store in |
+// |enabled_hosts_|, to a base64 string which we can include in a JSON file. |
+static std::wstring HashedDomainToExternalString(const std::string& hashed) { |
+ std::string out; |
+ CHECK(Base64Encode(hashed, &out)); |
+ return ASCIIToWide(out); |
+} |
+ |
+// This inverts |HashedDomainToExternalString|, above. It turns an external |
+// string (from a JSON file) into an internal (binary) string. |
+static std::string ExternalStringToHashedDomain(const std::wstring& external) { |
+ std::string external_ascii = WideToASCII(external); |
+ std::string out; |
+ if (!Base64Decode(external_ascii, &out) || |
+ out.size() != base::SHA256_LENGTH) { |
+ return std::string(); |
+ } |
+ |
+ return out; |
+} |
+ |
bool StrictTransportSecurityState::Serialise(std::string* output) { |
AutoLock lock(lock_); |
@@ -179,7 +223,7 @@ bool StrictTransportSecurityState::Serialise(std::string* output) { |
state->SetBoolean(L"include_subdomains", i->second.include_subdomains); |
state->SetReal(L"expiry", i->second.expiry.ToDoubleT()); |
- toplevel.Set(ASCIIToWide(i->first), state); |
+ toplevel.Set(HashedDomainToExternalString(i->first), state); |
} |
JSONWriter::Write(&toplevel, true /* pretty print */, output); |
@@ -205,7 +249,6 @@ bool StrictTransportSecurityState::Deserialise(const std::string& input) { |
if (!dict_value->GetDictionary(*i, &state)) |
continue; |
- const std::string host = WideToASCII(*i); |
bool include_subdomains; |
double expiry; |
@@ -218,11 +261,15 @@ bool StrictTransportSecurityState::Deserialise(const std::string& input) { |
if (expiry_time <= current_time) |
continue; |
+ std::string hashed = ExternalStringToHashedDomain(*i); |
+ if (hashed.empty()) |
+ continue; |
+ |
State new_state = { expiry_time, include_subdomains }; |
- enabled_hosts_[host] = new_state; |
+ enabled_hosts_[hashed] = new_state; |
} |
- return enabled_hosts_.size() > 0; |
+ return true; |
} |
void StrictTransportSecurityState::DirtyNotify() { |
@@ -230,4 +277,40 @@ void StrictTransportSecurityState::DirtyNotify() { |
delegate_->StateIsDirty(this); |
} |
+// static |
+std::string StrictTransportSecurityState::CanonicaliseHost( |
+ const std::string& host) { |
+ // We cannot perform the operations as detailed in the spec here as |host| |
+ // has already undergone IDN processing before it reached us. Thus, we check |
+ // that there are no invalid characters in the host and lowercase the result. |
+ |
+ std::string new_host; |
+ if (!DNSDomainFromDot(host, &new_host)) { |
+ NOTREACHED(); |
+ return std::string(); |
+ } |
+ |
+ for (size_t i = 0; new_host[i]; i += new_host[i] + 1) { |
+ const unsigned label_length = static_cast<unsigned>(new_host[i]); |
+ if (!label_length) |
+ break; |
+ |
+ for (size_t j = 0; j < label_length; ++j) { |
+ // RFC 3490, 4.1, step 3 |
+ if (!IsSTD3ASCIIValidCharacter(new_host[i + 1 + j])) |
+ return std::string(); |
+ |
+ new_host[i + 1 + j] = tolower(new_host[i + 1 + j]); |
+ } |
+ |
+ // step 3(b) |
+ if (new_host[i + 1] == '-' || |
+ new_host[i + label_length] == '-') { |
+ return std::string(); |
+ } |
+ } |
+ |
+ return new_host; |
+} |
+ |
} // namespace |