diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-17 17:20:28 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-17 17:20:28 +0000 |
commit | f060be3754a92a80e2f4402bde51763663f444d9 (patch) | |
tree | d2805ea3f4600fcdad167345501e1f17111ec2f5 | |
parent | f2e430a18f74fcb5972945c05cc08aa321ca6953 (diff) | |
download | chromium_src-f060be3754a92a80e2f4402bde51763663f444d9.zip chromium_src-f060be3754a92a80e2f4402bde51763663f444d9.tar.gz chromium_src-f060be3754a92a80e2f4402bde51763663f444d9.tar.bz2 |
HSTS: add net-internals UI.
This change adds a simple DOMUI interface to the HSTS list. Since the
list is stored, hashed in memory and on disk, there's no list of
entries. But the set can be queried and we can provide insertion and
deletion.
BUG=none
TEST=Open about:net-internals, goto HSTS tab.
Review URL: http://codereview.chromium.org/6500010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75282 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/dom_ui/net_internals_ui.cc | 87 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/hstsview.js | 118 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/index.html | 45 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/main.css | 3 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/main.js | 36 | ||||
-rw-r--r-- | net/base/dns_util.cc | 18 | ||||
-rw-r--r-- | net/base/dns_util.h | 3 | ||||
-rw-r--r-- | net/base/dns_util_unittest.cc | 15 | ||||
-rw-r--r-- | net/base/transport_security_state.cc | 58 | ||||
-rw-r--r-- | net/base/transport_security_state.h | 15 | ||||
-rw-r--r-- | net/base/transport_security_state_unittest.cc | 34 |
11 files changed, 407 insertions, 25 deletions
diff --git a/chrome/browser/dom_ui/net_internals_ui.cc b/chrome/browser/dom_ui/net_internals_ui.cc index a9b661d..6808367 100644 --- a/chrome/browser/dom_ui/net_internals_ui.cc +++ b/chrome/browser/dom_ui/net_internals_ui.cc @@ -16,6 +16,7 @@ #include "base/singleton.h" #include "base/string_number_conversions.h" #include "base/string_piece.h" +#include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/browser_process.h" @@ -255,6 +256,9 @@ class NetInternalsMessageHandler::IOThreadImpl void OnClearHostResolverCache(const ListValue* list); void OnEnableIPv6(const ListValue* list); void OnStartConnectionTests(const ListValue* list); + void OnHSTSQuery(const ListValue* list); + void OnHSTSAdd(const ListValue* list); + void OnHSTSDelete(const ListValue* list); void OnGetHttpCacheInfo(const ListValue* list); void OnGetSocketPoolInfo(const ListValue* list); void OnGetSpdySessionInfo(const ListValue* list); @@ -507,6 +511,15 @@ void NetInternalsMessageHandler::RegisterMessages() { "startConnectionTests", proxy_->CreateCallback(&IOThreadImpl::OnStartConnectionTests)); web_ui_->RegisterMessageCallback( + "hstsQuery", + proxy_->CreateCallback(&IOThreadImpl::OnHSTSQuery)); + web_ui_->RegisterMessageCallback( + "hstsAdd", + proxy_->CreateCallback(&IOThreadImpl::OnHSTSAdd)); + web_ui_->RegisterMessageCallback( + "hstsDelete", + proxy_->CreateCallback(&IOThreadImpl::OnHSTSDelete)); + web_ui_->RegisterMessageCallback( "getHttpCacheInfo", proxy_->CreateCallback(&IOThreadImpl::OnGetHttpCacheInfo)); web_ui_->RegisterMessageCallback( @@ -954,6 +967,80 @@ void NetInternalsMessageHandler::IOThreadImpl::OnStartConnectionTests( connection_tester_->RunAllTests(url); } +void NetInternalsMessageHandler::IOThreadImpl::OnHSTSQuery( + const ListValue* list) { + // |list| should be: [<domain to query>]. + std::string domain; + CHECK(list->GetString(0, &domain)); + DictionaryValue* result = new(DictionaryValue); + + if (!IsStringASCII(domain)) { + result->SetString("error", "non-ASCII domain name"); + } else { + net::TransportSecurityState* transport_security_state = + context_getter_->GetURLRequestContext()->transport_security_state(); + if (!transport_security_state) { + result->SetString("error", "no TransportSecurityState active"); + } else { + net::TransportSecurityState::DomainState state; + const bool found = transport_security_state->IsEnabledForHost( + &state, domain); + + result->SetBoolean("result", found); + if (found) { + result->SetInteger("mode", static_cast<int>(state.mode)); + result->SetBoolean("subdomains", state.include_subdomains); + result->SetBoolean("preloaded", state.preloaded); + result->SetString("domain", state.domain); + } + } + } + + CallJavascriptFunction(L"g_browser.receivedHSTSResult", result); +} + +void NetInternalsMessageHandler::IOThreadImpl::OnHSTSAdd( + const ListValue* list) { + // |list| should be: [<domain to query>, <include subdomains>]. + std::string domain; + CHECK(list->GetString(0, &domain)); + if (!IsStringASCII(domain)) { + // Silently fail. The user will get a helpful error if they query for the + // name. + return; + } + bool include_subdomains; + CHECK(list->GetBoolean(1, &include_subdomains)); + + net::TransportSecurityState* transport_security_state = + context_getter_->GetURLRequestContext()->transport_security_state(); + if (!transport_security_state) + return; + + net::TransportSecurityState::DomainState state; + state.expiry = state.created + base::TimeDelta::FromDays(1000); + state.include_subdomains = include_subdomains; + + transport_security_state->EnableHost(domain, state); +} + +void NetInternalsMessageHandler::IOThreadImpl::OnHSTSDelete( + const ListValue* list) { + // |list| should be: [<domain to query>]. + std::string domain; + CHECK(list->GetString(0, &domain)); + if (!IsStringASCII(domain)) { + // There cannot be a unicode entry in the HSTS set. + return; + } + net::TransportSecurityState* transport_security_state = + context_getter_->GetURLRequestContext()->transport_security_state(); + if (!transport_security_state) + return; + + transport_security_state->DeleteHost(domain); +} + void NetInternalsMessageHandler::IOThreadImpl::OnGetHttpCacheInfo( const ListValue* list) { DictionaryValue* info_dict = new DictionaryValue(); diff --git a/chrome/browser/resources/net_internals/hstsview.js b/chrome/browser/resources/net_internals/hstsview.js new file mode 100644 index 0000000..1194a7a --- /dev/null +++ b/chrome/browser/resources/net_internals/hstsview.js @@ -0,0 +1,118 @@ +// Copyright (c) 2011 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. + +/** + * HSTS is HTTPS Strict Transport Security: a way for sites to elect to always + * use HTTPS. See http://dev.chromium.org/sts + * + * This UI allows a user to query and update the browser's list of HSTS domains. + + * @constructor + */ +function HSTSView(mainBoxId, queryInputId, formId, queryOutputDivId, + addInputId, addFormId, addCheckId, + deleteInputId, deleteFormId) { + DivView.call(this, mainBoxId); + + this.queryInput_ = document.getElementById(queryInputId); + this.addCheck_ = document.getElementById(addCheckId); + this.addInput_ = document.getElementById(addInputId); + this.deleteInput_ = document.getElementById(deleteInputId); + this.queryOutputDiv_ = document.getElementById(queryOutputDivId); + + var form = document.getElementById(formId); + form.addEventListener('submit', this.onSubmitQuery_.bind(this), false); + form = document.getElementById(addFormId); + form.addEventListener('submit', this.onSubmitAdd_.bind(this), false); + form = document.getElementById(deleteFormId); + form.addEventListener('submit', this.onSubmitDelete_.bind(this), false); + + g_browser.addHSTSObserver(this); +} + +inherits(HSTSView, DivView); + +HSTSView.prototype.onSubmitQuery_ = function(event) { + g_browser.sendHSTSQuery(this.queryInput_.value); + event.preventDefault(); +}; + +HSTSView.prototype.onSubmitAdd_ = function(event) { + g_browser.sendHSTSAdd(this.addInput_.value, this.addCheck_.checked); + g_browser.sendHSTSQuery(this.addInput_.value); + this.queryInput_.value = this.addInput_.value; + this.addCheck_.checked = false; + this.addInput_.value = ''; + event.preventDefault(); +}; + +HSTSView.prototype.onSubmitDelete_ = function(event) { + g_browser.sendHSTSDelete(this.deleteInput_.value); + this.deleteInput_.value = ''; + event.preventDefault(); +}; + +function hstsModeToString(m) { + if (m == 0) { + return 'STRICT'; + } else if (m == 1) { + return 'OPPORTUNISTIC'; + } else if (m == 2) { + return 'SPDY'; + } else { + return 'UNKNOWN'; + } +} + +function yellowFade(element) { + element.style.webkitTransitionProperty = 'background-color'; + element.style.webkitTransitionDuration = '0'; + element.style.backgroundColor = '#fffccf'; + setTimeout(function() { + element.style.webkitTransitionDuration = '1000ms'; + element.style.backgroundColor = '#fff'; + }, 0); +} + +HSTSView.prototype.onHSTSQueryResult = function(result) { + if (result.error != undefined) { + this.queryOutputDiv_.innerHTML = ''; + s = addNode(this.queryOutputDiv_, 'span'); + s.innerText = result.error; + s.style.color = 'red'; + yellowFade(this.queryOutputDiv_); + return; + } + + if (result.result == false) { + this.queryOutputDiv_.innerHTML = '<b>Not found</b>'; + yellowFade(this.queryOutputDiv_); + return; + } + + this.queryOutputDiv_.innerHTML = ''; + + s = addNode(this.queryOutputDiv_, 'span'); + s.innerHTML = '<b>Found</b>: mode: '; + + t = addNode(this.queryOutputDiv_, 'tt'); + t.innerText = hstsModeToString(result.mode); + + addTextNode(this.queryOutputDiv_, ' include_subdomains:'); + + t = addNode(this.queryOutputDiv_, 'tt'); + t.innerText = result.subdomains; + + addTextNode(this.queryOutputDiv_, ' domain:'); + + t = addNode(this.queryOutputDiv_, 'tt'); + t.innerText = result.domain; + + addTextNode(this.queryOutputDiv_, ' is_preloaded:'); + + t = addNode(this.queryOutputDiv_, 'tt'); + t.innerText = result.preloaded; + + yellowFade(this.queryOutputDiv_); +} diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html index d67957c..c18da47 100644 --- a/chrome/browser/resources/net_internals/index.html +++ b/chrome/browser/resources/net_internals/index.html @@ -14,6 +14,7 @@ found in the LICENSE file. <script src="dataview.js"></script> <script src="httpcacheview.js"></script> <script src="testview.js"></script> + <script src="hstsview.js"></script> <script src="main.js"></script> <script src="dnsview.js"></script> <script src="eventsview.js"></script> @@ -44,6 +45,7 @@ found in the LICENSE file. <!-- Tab is only shown on Windows --> <li><a href="#serviceProviders" id=serviceProvidersTab style="display: none;">SPIs</a></li> <li><a href="#tests" id=testTab>Tests</a></li> + <li><a href="#hsts" id=hstsTab>HSTS</a></li> </ul> <div style="clear: both;"></div> </div> @@ -349,6 +351,49 @@ function displayHelpForBugDump() { <div id=testSummary></div> </div> + <!-- HSTS tab --> + <div id=hstsTabContent> + <p> + HSTS is HTTPS Strict Transport Security: a way for sites to elect to + always use HTTPS. See <a href="http://dev.chromium.org/sts"> + http://dev.chromium.org/sts</a>.</p> + + <!-- This UI allows a user to query and update the browser's list of + HSTS domains. --> + + <h4>Add domain</h4> + + <p>Input a domain name to add it to the HSTS set:</p> + <form id=hstsAddForm> + Domain: <input type=text id=hstsAddInput type="url" + placeholder="example.com"/><br/> + Include subdomains: <input type="checkbox" id=hstsCheckInput /> + <input type=submit value="Add" /> + </form> + + <h4>Delete domain</h4> + + <p> + Input a domain name to delete it from the HSTS set + (<i>you cannot delete preloaded entries</i>): + </p> + <form id=hstsDeleteForm> + Domain: <input type=text id=hstsDeleteInput type="url" + placeholder="example.com"/> + <input type=submit value="Delete" /> + </form> + + <h4>Query domain</h4> + + <p>Input a domain name to query the current HSTS set:</p> + <form id=hstsQueryForm> + Domain: <input type=text id=hstsQueryInput type="url" + placeholder="example.com"/> + <input type=submit value="Query" /> + </form> + <div style="margin-top: 1em; margin-left: 2em;" id=hstsQueryOutput></div> + </div> + <!-- ================= Events view =================== --> <!-- Filter Box: This the top bar which contains the search box. --> diff --git a/chrome/browser/resources/net_internals/main.css b/chrome/browser/resources/net_internals/main.css index 8c6982c..5ca6d75 100644 --- a/chrome/browser/resources/net_internals/main.css +++ b/chrome/browser/resources/net_internals/main.css @@ -160,7 +160,8 @@ body { #socketsTabContent, #spdyTabContent, #serviceProvidersTabContent, -#testTabContent { +#testTabContent, +#hstsTabContent { overflow: auto; padding: 10px; } diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js index ed803c5..561770b 100644 --- a/chrome/browser/resources/net_internals/main.js +++ b/chrome/browser/resources/net_internals/main.js @@ -87,6 +87,13 @@ function onLoaded() { var testView = new TestView('testTabContent', 'testUrlInput', 'connectionTestsForm', 'testSummary'); + // Create a view which allows the user to query and alter the HSTS database. + var hstsView = new HSTSView('hstsTabContent', + 'hstsQueryInput', 'hstsQueryForm', + 'hstsQueryOutput', + 'hstsAddInput', 'hstsAddForm', 'hstsCheckInput', + 'hstsDeleteInput', 'hstsDeleteForm'); + var httpCacheView = new HttpCacheView('httpCacheTabContent', 'httpCacheStats'); @@ -128,6 +135,7 @@ function onLoaded() { if (g_browser.isPlatformWindows()) categoryTabSwitcher.addTab('serviceProvidersTab', serviceView, false); categoryTabSwitcher.addTab('testTab', testView, false); + categoryTabSwitcher.addTab('hstsTab', hstsView, false); // Build a map from the anchor name of each tab handle to its "tab ID". // We will consider navigations to the #hash as a switch tab request. @@ -169,6 +177,7 @@ function BrowserBridge() { // List of observers for various bits of browser state. this.logObservers_ = []; this.connectionTestsObservers_ = []; + this.hstsObservers_ = []; this.pollableDataHelpers_ = {}; this.pollableDataHelpers_.proxySettings = @@ -301,6 +310,18 @@ BrowserBridge.prototype.sendStartConnectionTests = function(url) { chrome.send('startConnectionTests', [url]); }; +BrowserBridge.prototype.sendHSTSQuery = function(domain) { + chrome.send('hstsQuery', [domain]); +}; + +BrowserBridge.prototype.sendHSTSAdd = function(domain, include_subdomains) { + chrome.send('hstsAdd', [domain, include_subdomains]); +}; + +BrowserBridge.prototype.sendHSTSDelete = function(domain) { + chrome.send('hstsDelete', [domain]); +}; + BrowserBridge.prototype.sendGetHttpCacheInfo = function() { chrome.send('getHttpCacheInfo'); }; @@ -472,6 +493,11 @@ BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() { this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); }; +BrowserBridge.prototype.receivedHSTSResult = function(info) { + for (var i = 0; i < this.hstsObservers_.length; ++i) + this.hstsObservers_[i].onHSTSQueryResult(info); +}; + BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { this.pollableDataHelpers_.httpCacheInfo.update(info); }; @@ -694,6 +720,16 @@ BrowserBridge.prototype.addHttpCacheInfoObserver = function(observer) { }; /** + * Adds a listener for the results of HSTS (HTTPS Strict Transport Security) + * queries. The observer will be called back with: + * + * observer.onHSTSQueryResult(result); + */ +BrowserBridge.prototype.addHSTSObserver = function(observer) { + this.hstsObservers_.push(observer); +}; + +/** * The browser gives us times in terms of "time ticks" in milliseconds. * This function converts the tick count to a Date() object. * diff --git a/net/base/dns_util.cc b/net/base/dns_util.cc index f1e0de4..d97d3d2 100644 --- a/net/base/dns_util.cc +++ b/net/base/dns_util.cc @@ -56,6 +56,24 @@ bool DNSDomainFromDot(const std::string& dotted, std::string* out) { return true; } +std::string DNSDomainToString(const std::string& domain) { + std::string ret; + + for (unsigned i = 0; i < domain.size() && domain[i]; i += domain[i] + 1) { + if (domain[i] < 0 || domain[i] > 63) + return ""; + + if (i) + ret += "."; + + if (static_cast<unsigned>(domain[i]) + i + 1 > domain.size()) + return ""; + + ret += domain.substr(i + 1, domain[i]); + } + return ret; +} + bool IsSTD3ASCIIValidCharacter(char c) { if (c <= 0x2c) return false; diff --git a/net/base/dns_util.h b/net/base/dns_util.h index d86fcb9..40e8736 100644 --- a/net/base/dns_util.h +++ b/net/base/dns_util.h @@ -19,6 +19,9 @@ namespace net { // out: a result in DNS form: "\x03www\x06google\x03com\x00" bool DNSDomainFromDot(const std::string& dotted, std::string* out); +// DNSDomainToString coverts a domain in DNS format to a dotted string. +std::string DNSDomainToString(const std::string& domain); + // Returns true iff the given character is in the set of valid DNS label // characters as given in RFC 3490, 4.1, 3(a) bool IsSTD3ASCIIValidCharacter(char c); diff --git a/net/base/dns_util_unittest.cc b/net/base/dns_util_unittest.cc index feeba55..103e9a7 100644 --- a/net/base/dns_util_unittest.cc +++ b/net/base/dns_util_unittest.cc @@ -53,6 +53,21 @@ TEST_F(DNSUtilTest, DNSDomainFromDot) { EXPECT_EQ(out, IncludeNUL("\003www\006google\003com")); } +TEST_F(DNSUtilTest, DNSDomainToString) { + EXPECT_EQ("", DNSDomainToString(IncludeNUL(""))); + EXPECT_EQ("foo", DNSDomainToString(IncludeNUL("\003foo"))); + EXPECT_EQ("foo.bar", DNSDomainToString(IncludeNUL("\003foo\003bar"))); + EXPECT_EQ("foo.bar.uk", + DNSDomainToString(IncludeNUL("\003foo\003bar\002uk"))); + + // It should cope with a lack of root label. + EXPECT_EQ("foo.bar", DNSDomainToString("\003foo\003bar")); + + // Invalid inputs should return an empty string. + EXPECT_EQ("", DNSDomainToString(IncludeNUL("\x80"))); + EXPECT_EQ("", DNSDomainToString("\x06")); +} + TEST_F(DNSUtilTest, STD3ASCII) { EXPECT_TRUE(IsSTD3ASCIIValidCharacter('a')); EXPECT_TRUE(IsSTD3ASCIIValidCharacter('b')); diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc index d4cea15..d2ec579 100644 --- a/net/base/transport_security_state.cc +++ b/net/base/transport_security_state.cc @@ -28,16 +28,16 @@ TransportSecurityState::TransportSecurityState() void TransportSecurityState::EnableHost(const std::string& host, const DomainState& state) { - const std::string canonicalised_host = CanonicaliseHost(host); - if (canonicalised_host.empty()) + const std::string canonicalized_host = CanonicalizeHost(host); + if (canonicalized_host.empty()) return; bool temp; - if (IsPreloadedSTS(canonicalised_host, &temp)) + if (IsPreloadedSTS(canonicalized_host, &temp)) return; char hashed[base::SHA256_LENGTH]; - base::SHA256HashString(canonicalised_host, hashed, sizeof(hashed)); + base::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); // Use the original creation date if we already have this host. DomainState state_copy(state); @@ -45,10 +45,32 @@ void TransportSecurityState::EnableHost(const std::string& host, if (IsEnabledForHost(&existing_state, host)) state_copy.created = existing_state.created; + // We don't store these values. + state_copy.preloaded = false; + state_copy.domain.clear(); + enabled_hosts_[std::string(hashed, sizeof(hashed))] = state_copy; DirtyNotify(); } +bool TransportSecurityState::DeleteHost(const std::string& host) { + const std::string canonicalized_host = CanonicalizeHost(host); + if (canonicalized_host.empty()) + return false; + + char hashed[base::SHA256_LENGTH]; + base::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); + + std::map<std::string, DomainState>::iterator i = enabled_hosts_.find( + std::string(hashed, sizeof(hashed))); + if (i != enabled_hosts_.end()) { + enabled_hosts_.erase(i); + DirtyNotify(); + return true; + } + return false; +} + // IncludeNUL converts a char* to a std::string and includes the terminating // NUL in the result. static std::string IncludeNUL(const char* in) { @@ -57,24 +79,28 @@ static std::string IncludeNUL(const char* in) { bool TransportSecurityState::IsEnabledForHost(DomainState* result, const std::string& host) { - const std::string canonicalised_host = CanonicaliseHost(host); - if (canonicalised_host.empty()) + *result = DomainState(); + + const std::string canonicalized_host = CanonicalizeHost(host); + if (canonicalized_host.empty()) return false; bool include_subdomains; - if (IsPreloadedSTS(canonicalised_host, &include_subdomains)) { + if (IsPreloadedSTS(canonicalized_host, &include_subdomains)) { result->created = result->expiry = base::Time::FromTimeT(0); result->mode = DomainState::MODE_STRICT; result->include_subdomains = include_subdomains; + result->preloaded = true; return true; } + result->preloaded = false; base::Time current_time(base::Time::Now()); - for (size_t i = 0; canonicalised_host[i]; i += canonicalised_host[i] + 1) { + for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { char hashed_domain[base::SHA256_LENGTH]; - base::SHA256HashString(IncludeNUL(&canonicalised_host[i]), &hashed_domain, + base::SHA256HashString(IncludeNUL(&canonicalized_host[i]), &hashed_domain, sizeof(hashed_domain)); std::map<std::string, DomainState>::iterator j = enabled_hosts_.find(std::string(hashed_domain, sizeof(hashed_domain))); @@ -88,6 +114,8 @@ bool TransportSecurityState::IsEnabledForHost(DomainState* result, } *result = j->second; + result->domain = DNSDomainToString( + canonicalized_host.substr(i, canonicalized_host.size() - i)); // If we matched the domain exactly, it doesn't matter what the value of // include_subdomains is. @@ -376,7 +404,7 @@ void TransportSecurityState::DirtyNotify() { } // static -std::string TransportSecurityState::CanonicaliseHost(const std::string& host) { +std::string TransportSecurityState::CanonicalizeHost(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. @@ -411,11 +439,11 @@ std::string TransportSecurityState::CanonicaliseHost(const std::string& host) { return new_host; } -// IsPreloadedSTS returns true if the canonicalised hostname should always be +// IsPreloadedSTS returns true if the canonicalized hostname should always be // considered to have STS enabled. // static bool TransportSecurityState::IsPreloadedSTS( - const std::string& canonicalised_host, bool *include_subdomains) { + const std::string& canonicalized_host, bool *include_subdomains) { // In the medium term this list is likely to just be hardcoded here. This, // slightly odd, form removes the need for additional relocations records. static const struct { @@ -442,11 +470,11 @@ bool TransportSecurityState::IsPreloadedSTS( }; static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS); - for (size_t i = 0; canonicalised_host[i]; i += canonicalised_host[i] + 1) { + for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { for (size_t j = 0; j < kNumPreloadedSTS; j++) { - if (kPreloadedSTS[j].length == canonicalised_host.size() - i && + if (kPreloadedSTS[j].length == canonicalized_host.size() - i && (kPreloadedSTS[j].include_subdomains || i == 0) && - memcmp(kPreloadedSTS[j].dns_name, &canonicalised_host[i], + memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i], kPreloadedSTS[j].length) == 0) { *include_subdomains = kPreloadedSTS[j].include_subdomains; return true; diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h index 768ccbb..05061ca 100644 --- a/net/base/transport_security_state.h +++ b/net/base/transport_security_state.h @@ -47,17 +47,26 @@ class TransportSecurityState : DomainState() : mode(MODE_STRICT), created(base::Time::Now()), - include_subdomains(false) { } + include_subdomains(false), + preloaded(false) { } 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? + + // The follow members are not valid when stored in |enabled_hosts_|. + bool preloaded; // is this a preloaded entry? + std::string domain; // the domain which matched }; // Enable TransportSecurity for |host|. void EnableHost(const std::string& host, const DomainState& state); + // Delete any entry for |host|. If |host| doesn't have an exact entry then no + // action is taken. Returns true iff an entry was deleted. + bool DeleteHost(const std::string& host); + // Returns true if |host| has TransportSecurity enabled. If that case, // *result is filled out. bool IsEnabledForHost(DomainState* result, const std::string& host); @@ -101,8 +110,8 @@ class TransportSecurityState : // our state is dirty. void DirtyNotify(); - static std::string CanonicaliseHost(const std::string& host); - static bool IsPreloadedSTS(const std::string& canonicalised_host, + static std::string CanonicalizeHost(const std::string& host); + static bool IsPreloadedSTS(const std::string& canonicalized_host, bool* out_include_subdomains); // The set of hosts that have enabled TransportSecurity. The keys here diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc index 3c81c69..3eabc06 100644 --- a/net/base/transport_security_state_unittest.cc +++ b/net/base/transport_security_state_unittest.cc @@ -281,6 +281,23 @@ TEST_F(TransportSecurityStateTest, DeleteSince) { EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com")); } +TEST_F(TransportSecurityStateTest, DeleteHost) { + scoped_refptr<TransportSecurityState> state( + new TransportSecurityState); + + TransportSecurityState::DomainState domain_state; + const base::Time current_time(base::Time::Now()); + const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); + domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT; + domain_state.expiry = expiry; + state->EnableHost("google.com", domain_state); + + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com")); + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "example.com")); + EXPECT_TRUE(state->DeleteHost("google.com")); + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com")); +} + TEST_F(TransportSecurityStateTest, SerialiseOld) { scoped_refptr<TransportSecurityState> state( new TransportSecurityState); @@ -301,17 +318,17 @@ TEST_F(TransportSecurityStateTest, SerialiseOld) { TEST_F(TransportSecurityStateTest, IsPreloaded) { const std::string paypal = - TransportSecurityState::CanonicaliseHost("paypal.com"); + TransportSecurityState::CanonicalizeHost("paypal.com"); const std::string www_paypal = - TransportSecurityState::CanonicaliseHost("www.paypal.com"); + TransportSecurityState::CanonicalizeHost("www.paypal.com"); const std::string a_www_paypal = - TransportSecurityState::CanonicaliseHost("a.www.paypal.com"); + TransportSecurityState::CanonicalizeHost("a.www.paypal.com"); const std::string abc_paypal = - TransportSecurityState::CanonicaliseHost("a.b.c.paypal.com"); + TransportSecurityState::CanonicalizeHost("a.b.c.paypal.com"); const std::string example = - TransportSecurityState::CanonicaliseHost("example.com"); + TransportSecurityState::CanonicalizeHost("example.com"); const std::string aypal = - TransportSecurityState::CanonicaliseHost("aypal.com"); + TransportSecurityState::CanonicalizeHost("aypal.com"); bool b; EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS(paypal, &b)); @@ -331,6 +348,7 @@ TEST_F(TransportSecurityStateTest, Preloaded) { EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "www.paypal.com")); EXPECT_EQ(domain_state.mode, TransportSecurityState::DomainState::MODE_STRICT); + EXPECT_TRUE(domain_state.preloaded); EXPECT_FALSE(domain_state.include_subdomains); EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "www2.paypal.com")); EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "a.www.paypal.com")); @@ -370,6 +388,10 @@ TEST_F(TransportSecurityStateTest, Preloaded) { EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "splendidbacon.com")); EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "www.splendidbacon.com")); EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "foo.splendidbacon.com")); + + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "chrome.google.com")); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "checkout.google.com")); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "health.google.com")); } TEST_F(TransportSecurityStateTest, LongNames) { |