summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chromeos/web_socket_proxy.cc53
-rw-r--r--chrome/browser/chromeos/web_socket_proxy.h5
-rw-r--r--chrome/browser/chromeos/web_socket_proxy_controller.cc102
-rw-r--r--chrome/browser/chromeos/web_socket_proxy_controller.h13
-rw-r--r--chrome/browser/extensions/extension_apitest.h2
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.cc4
-rw-r--r--chrome/browser/extensions/extension_web_socket_proxy_private_api.cc78
-rw-r--r--chrome/browser/extensions/extension_web_socket_proxy_private_api.h36
-rw-r--r--chrome/browser/extensions/extension_web_socket_proxy_private_apitest.cc19
-rw-r--r--chrome/browser/internal_auth.cc480
-rw-r--r--chrome/browser/internal_auth.h74
-rw-r--r--chrome/browser/internal_auth_unittest.cc199
-rw-r--r--chrome/chrome_browser.gypi4
-rw-r--r--chrome/chrome_tests.gypi7
-rw-r--r--chrome/common/chrome_switches.cc4
-rw-r--r--chrome/common/chrome_switches.h1
-rw-r--r--chrome/common/extensions/api/extension_api.json38
-rw-r--r--chrome/common/extensions/extension.cc55
-rw-r--r--chrome/common/extensions/extension.h3
-rw-r--r--chrome/common/extensions/extension_unittest.cc5
-rw-r--r--chrome/renderer/resources/renderer_extension_bindings.js4
-rw-r--r--chrome/test/data/extensions/api_test/web_socket_proxy_private/background.html32
-rw-r--r--chrome/test/data/extensions/api_test/web_socket_proxy_private/manifest.json8
-rw-r--r--crypto/hmac.h4
-rw-r--r--crypto/hmac_mac.cc2
-rw-r--r--crypto/hmac_nss.cc2
-rw-r--r--crypto/hmac_openssl.cc2
-rw-r--r--crypto/hmac_win.cc2
-rw-r--r--crypto/symmetric_key_mac.cc18
29 files changed, 1174 insertions, 82 deletions
diff --git a/chrome/browser/chromeos/web_socket_proxy.cc b/chrome/browser/chromeos/web_socket_proxy.cc
index 8a52746..d84ee8d 100644
--- a/chrome/browser/chromeos/web_socket_proxy.cc
+++ b/chrome/browser/chromeos/web_socket_proxy.cc
@@ -30,13 +30,12 @@
#include "base/memory/scoped_ptr.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
+#include "chrome/browser/internal_auth.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_service.h"
#include "content/common/notification_type.h"
-// TODO(dilmah): enable this once webSocketProxyPrivate.getToken is wired.
-#if 0
-#include "chrome/browser/internal_auth.h"
-#endif
+#include "content/common/url_constants.h"
+#include "googleurl/src/gurl.h"
#include "third_party/libevent/evdns.h"
#include "third_party/libevent/event.h"
@@ -105,10 +104,10 @@ bool FetchDecimalDigits(const std::string& s, uint32* result) {
return got_something;
}
-// Parses "token:hostname:port:" string. Returns true on success.
-bool FetchTokenNamePort(
+// Parses "passport:hostname:port:" string. Returns true on success.
+bool FetchPassportNamePort(
uint8* begin, uint8* end,
- std::string* token, std::string* name, uint32* port) {
+ std::string* passport, std::string* name, uint32* port) {
std::string input(begin, end);
if (input[input.size() - 1] != ':')
return false;
@@ -134,14 +133,17 @@ bool FetchTokenNamePort(
pos = input.find_first_of(':');
if (pos == std::string::npos)
return false;
- token->assign(input, 0, pos);
+ passport->assign(input, 0, pos);
name->assign(input, pos + 1, std::string::npos);
return !name->empty();
}
-std::string FetchExtensionIdFromOrigin(const std::string origin) {
- // Origin of extension looks like "chrome-extension://EXTENSION_ID".
- return origin.substr(origin.find_last_of('/'));
+std::string FetchExtensionIdFromOrigin(const std::string &origin) {
+ GURL url(origin);
+ if (url.SchemeIs(chrome::kExtensionScheme))
+ return url.host();
+ else
+ return std::string();
}
inline size_t strlen(const uint8* s) {
@@ -733,16 +735,22 @@ Conn::Status Conn::ConsumeHeader(struct evbuffer* evb) {
return STATUS_ABORT;
}
- if (!master_->IsOriginAllowed(header_fields_["origin"]))
+ // Normalize origin (e.g. leading slash).
+ GURL origin = GURL(header_fields_["origin"]).GetOrigin();
+ if (!origin.is_valid())
+ return STATUS_ABORT;
+ // Here we check origin. This check may seem redundant because we verify
+ // passport token later. However the earlier we can reject connection the
+ // better. We receive origin field in websocket header way before receiving
+ // passport string.
+ if (!master_->IsOriginAllowed(origin.spec()))
return STATUS_ABORT;
static const std::string kSecKey1 = "sec-websocket-key1";
static const std::string kSecKey2 = "sec-websocket-key2";
uint32 key_number1, key_number2;
- if (!FetchDecimalDigits(header_fields_[kSecKey1],
- &key_number1) ||
- !FetchDecimalDigits(header_fields_[kSecKey2],
- &key_number2)) {
+ if (!FetchDecimalDigits(header_fields_[kSecKey1], &key_number1) ||
+ !FetchDecimalDigits(header_fields_[kSecKey2], &key_number2)) {
return STATUS_ABORT;
}
@@ -828,20 +836,19 @@ Conn::Status Conn::ConsumeDestframe(struct evbuffer* evb) {
return STATUS_INCOMPLETE;
}
- std::string token;
- if (!FetchTokenNamePort(buf + 1, term_pos, &token, &destname_, &destport_))
+ std::string passport;
+ if (!FetchPassportNamePort(
+ buf + 1, term_pos, &passport, &destname_, &destport_)) {
return STATUS_ABORT;
- // TODO(dilmah): enable this once webSocketProxyPrivate.getToken is wired.
-#if 0
+ }
std::map<std::string, std::string> map;
map["hostname"] = destname_;
map["port"] = base::IntToString(destport_);
map["extension_id"] = FetchExtensionIdFromOrigin(header_fields_["origin"]);
- if (!browser::InternalAuthVerification::VerifyToken(
- "web_socket_proxy", token, map)) {
+ if (!browser::InternalAuthVerification::VerifyPassport(
+ passport, "web_socket_proxy", map)) {
return STATUS_ABORT;
}
-#endif
evbuffer_drain(evb, term_pos - buf + 1);
return STATUS_OK;
diff --git a/chrome/browser/chromeos/web_socket_proxy.h b/chrome/browser/chromeos/web_socket_proxy.h
index 9f15cbc..09e783a 100644
--- a/chrome/browser/chromeos/web_socket_proxy.h
+++ b/chrome/browser/chromeos/web_socket_proxy.h
@@ -16,14 +16,15 @@ namespace chromeos {
class WebSocketProxy {
public:
- static const size_t kReadBufferLimit = 6 * 1024 * 1024;
+ static const size_t kReadBufferLimit = 12 * 1024 * 1024;
// Limits incoming websocket headers in initial stage of connection.
static const size_t kHeaderLimit = 32 * 1024;
// Limits number of simultaneously open connections.
- static const size_t kConnPoolLimit = 1000;
+ static const size_t kConnPoolLimit = 40;
+ // Empty |allowed_origins| vector disables check for origin.
WebSocketProxy(
const std::vector<std::string>& allowed_origins,
struct sockaddr* addr, int addr_len);
diff --git a/chrome/browser/chromeos/web_socket_proxy_controller.cc b/chrome/browser/chromeos/web_socket_proxy_controller.cc
index 3387071..b6cb61f 100644
--- a/chrome/browser/chromeos/web_socket_proxy_controller.cc
+++ b/chrome/browser/chromeos/web_socket_proxy_controller.cc
@@ -4,19 +4,95 @@
#include "web_socket_proxy_controller.h"
+#include <algorithm>
+
#include <netinet/in.h>
#include <sys/wait.h>
#include <unistd.h>
+#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/message_loop.h"
+#include "base/string_tokenizer.h"
#include "base/threading/thread.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/web_socket_proxy.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
#include "content/browser/browser_thread.h"
+#include "content/common/url_constants.h"
+#include "googleurl/src/gurl.h"
namespace {
+const char* kWebAppId = "haiffjcadagjlijoggckpgfnoeiflnem";
+
+const char* kAllowedIds[] = {
+ kWebAppId
+};
+
+class OriginValidator {
+ public:
+ OriginValidator()
+ : allowed_ids_(kAllowedIds, kAllowedIds + arraysize(kAllowedIds)) {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ DCHECK(command_line);
+ std::string allowed_list =
+ command_line->GetSwitchValueASCII(switches::kAllowWebSocketProxy);
+ if (!allowed_list.empty()) {
+ StringTokenizer t(allowed_list, ",");
+ while (t.GetNext()) {
+ // It must be either extension id or origin.
+ if (Extension::IdIsValid(t.token())) {
+ allowed_ids_.push_back(t.token());
+ } else {
+ // It is not extension id, check if it is an origin.
+ GURL origin = GURL(t.token()).GetOrigin();
+ if (!origin.is_valid()) {
+ LOG(ERROR) << "Invalid extension id or origin specified via "
+ << switches::kAllowWebSocketProxy << " switch";
+ break;
+ }
+ allowed_origins_.push_back(origin.spec());
+ if (origin.SchemeIs(chrome::kExtensionScheme))
+ allowed_ids_.push_back(origin.host());
+ }
+ }
+ }
+ for (size_t i = 0; i < allowed_ids_.size(); ++i) {
+ allowed_origins_.push_back(Extension::GetBaseURLFromExtensionId(
+ allowed_ids_[i]).GetOrigin().spec());
+ }
+ std::sort(allowed_ids_.begin(), allowed_ids_.end());
+ allowed_ids_.resize(std::unique(
+ allowed_ids_.begin(), allowed_ids_.end()) - allowed_ids_.begin());
+ std::sort(allowed_origins_.begin(), allowed_origins_.end());
+ allowed_origins_.resize(std::unique(allowed_origins_.begin(),
+ allowed_origins_.end()) - allowed_origins_.begin());
+ }
+
+ bool CheckCredentials(
+ const std::string& extension_id,
+ const std::string& hostname,
+ unsigned short port,
+ chromeos::WebSocketProxyController::ConnectionFlags flags) {
+ if (flags & chromeos::WebSocketProxyController::TLS_OVER_TCP) {
+ NOTIMPLEMENTED();
+ return false;
+ }
+ return std::binary_search(
+ allowed_ids_.begin(), allowed_ids_.end(), extension_id);
+ }
+
+ const std::vector<std::string>& allowed_origins() { return allowed_origins_; }
+
+ private:
+ std::vector<std::string> allowed_ids_;
+ std::vector<std::string> allowed_origins_;
+};
+
+base::LazyInstance<OriginValidator> g_validator(base::LINKER_INITIALIZED);
+
class ProxyTask : public Task {
virtual void Run() OVERRIDE;
};
@@ -34,7 +110,7 @@ struct ProxyLifetime {
volatile bool shutdown_requested;
- base::Lock shutdown_lock;
+ base::Lock lock;
};
base::LazyInstance<ProxyLifetime> g_proxy_lifetime(base::LINKER_INITIALIZED);
@@ -43,11 +119,6 @@ void ProxyTask::Run() {
LOG(INFO) << "Attempt to run web socket proxy task";
const int kPort = 10101;
- // Configure allowed origins. Empty vector allows any origin.
- std::vector<std::string> allowed_origins;
- allowed_origins.push_back(
- "chrome-extension://haiffjcadagjlijoggckpgfnoeiflnem");
-
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
@@ -55,9 +126,10 @@ void ProxyTask::Run() {
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
chromeos::WebSocketProxy* server = new chromeos::WebSocketProxy(
- allowed_origins, reinterpret_cast<sockaddr*>(&sa), sizeof(sa));
+ g_validator.Get().allowed_origins(),
+ reinterpret_cast<sockaddr*>(&sa), sizeof(sa));
{
- base::AutoLock alk(g_proxy_lifetime.Get().shutdown_lock);
+ base::AutoLock alk(g_proxy_lifetime.Get().lock);
if (g_proxy_lifetime.Get().shutdown_requested)
return;
delete g_proxy_lifetime.Get().server;
@@ -65,7 +137,7 @@ void ProxyTask::Run() {
}
server->Run();
{
- base::AutoLock alk(g_proxy_lifetime.Get().shutdown_lock);
+ base::AutoLock alk(g_proxy_lifetime.Get().lock);
delete server;
g_proxy_lifetime.Get().server = NULL;
if (!g_proxy_lifetime.Get().shutdown_requested) {
@@ -99,12 +171,22 @@ bool WebSocketProxyController::IsInitiated() {
void WebSocketProxyController::Shutdown() {
if (IsInitiated()) {
LOG(INFO) << "WebSocketProxyController shutdown";
- base::AutoLock alk(g_proxy_lifetime.Get().shutdown_lock);
+ base::AutoLock alk(g_proxy_lifetime.Get().lock);
g_proxy_lifetime.Get().shutdown_requested = true;
if (g_proxy_lifetime.Get().server)
g_proxy_lifetime.Get().server->Shutdown();
}
}
+// static
+bool WebSocketProxyController::CheckCredentials(
+ const std::string& extension_id,
+ const std::string& hostname,
+ unsigned short port,
+ ConnectionFlags flags) {
+ return g_validator.Get().CheckCredentials(
+ extension_id, hostname, port, flags);
+}
+
} // namespace chromeos
diff --git a/chrome/browser/chromeos/web_socket_proxy_controller.h b/chrome/browser/chromeos/web_socket_proxy_controller.h
index e5749e7..f96fedc 100644
--- a/chrome/browser/chromeos/web_socket_proxy_controller.h
+++ b/chrome/browser/chromeos/web_socket_proxy_controller.h
@@ -6,17 +6,30 @@
#define CHROME_BROWSER_CHROMEOS_WEB_SOCKET_PROXY_CONTROLLER_H_
#pragma once
+#include <string>
+
namespace chromeos {
// Controls webproxy to TCP service.
class WebSocketProxyController {
public:
+ enum ConnectionFlags {
+ PLAIN_TCP = 0,
+ TLS_OVER_TCP = 1 << 0
+ };
+
// Can be called on any thread. Subsequent calls are cheap and do nothing.
static void Initiate();
// All methods can be called on any thread.
static void Shutdown();
static bool IsInitiated();
+
+ static bool CheckCredentials(
+ const std::string& extension_id,
+ const std::string& hostname,
+ unsigned short port,
+ ConnectionFlags);
};
} // namespace chromeos
diff --git a/chrome/browser/extensions/extension_apitest.h b/chrome/browser/extensions/extension_apitest.h
index 52b492e..1bab742 100644
--- a/chrome/browser/extensions/extension_apitest.h
+++ b/chrome/browser/extensions/extension_apitest.h
@@ -118,7 +118,7 @@ class ExtensionApiTest : public ExtensionBrowserTest {
private:
bool RunExtensionTestImpl(const char* extension_name,
const std::string& test_page,
- bool enable_incogntio,
+ bool enable_incognito,
bool enable_fileaccess,
bool load_as_component);
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 9a6b372..a64d6a1 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -38,6 +38,7 @@
#include "chrome/browser/extensions/extension_tabs_module.h"
#include "chrome/browser/extensions/extension_test_api.h"
#include "chrome/browser/extensions/extension_tts_api.h"
+#include "chrome/browser/extensions/extension_web_socket_proxy_private_api.h"
#include "chrome/browser/extensions/extension_web_ui.h"
#include "chrome/browser/extensions/extension_webrequest_api.h"
#include "chrome/browser/extensions/extension_webstore_private_api.h"
@@ -337,6 +338,9 @@ void FactoryRegistry::ResetFunctions() {
#endif
#endif
+ // Websocket to TCP proxy. Currently noop on anything other than ChromeOS.
+ RegisterFunction<WebSocketProxyPrivateGetPassportForTCPFunction>();
+
// Debugger
RegisterFunction<AttachDebuggerFunction>();
RegisterFunction<DetachDebuggerFunction>();
diff --git a/chrome/browser/extensions/extension_web_socket_proxy_private_api.cc b/chrome/browser/extensions/extension_web_socket_proxy_private_api.cc
new file mode 100644
index 0000000..5acbfbe
--- /dev/null
+++ b/chrome/browser/extensions/extension_web_socket_proxy_private_api.cc
@@ -0,0 +1,78 @@
+// 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.
+
+#include "chrome/browser/extensions/extension_web_socket_proxy_private_api.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "base/string_number_conversions.h"
+#include "chrome/browser/internal_auth.h"
+#include "chrome/common/extensions/extension.h"
+#include "content/common/notification_service.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/web_socket_proxy_controller.h"
+#endif
+
+bool WebSocketProxyPrivateGetPassportForTCPFunction::RunImpl() {
+ bool delay_response = false;
+ result_.reset(Value::CreateStringValue(""));
+
+#if defined(OS_CHROMEOS)
+ std::string hostname;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &hostname));
+ int port = -1;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(1, &port));
+
+ if (chromeos::WebSocketProxyController::CheckCredentials(
+ extension_id(), hostname, port,
+ chromeos::WebSocketProxyController::PLAIN_TCP)) {
+ if (!chromeos::WebSocketProxyController::IsInitiated()) {
+ delay_response = true;
+ registrar_.Add(
+ this, NotificationType::WEB_SOCKET_PROXY_STARTED,
+ NotificationService::AllSources());
+ chromeos::WebSocketProxyController::Initiate();
+ }
+
+ std::map<std::string, std::string> map;
+ map["hostname"] = hostname;
+ map["port"] = base::IntToString(port);
+ map["extension_id"] = extension_id();
+ StringValue* passport = Value::CreateStringValue(
+ browser::InternalAuthGeneration::GeneratePassport(
+ "web_socket_proxy", map));
+ result_.reset(passport);
+ }
+#endif // defined(OS_CHROMEOS)
+
+ // TODO(dilmah): get idea why notification is not observed and timer callback
+ // is not called and remove this line.
+ delay_response = false;
+
+ if (delay_response) {
+ timer_.Start(base::TimeDelta::FromSeconds(3),
+ this, &WebSocketProxyPrivateGetPassportForTCPFunction::Finalize);
+ } else {
+ Finalize();
+ }
+ return true;
+}
+
+void WebSocketProxyPrivateGetPassportForTCPFunction::Observe(
+ NotificationType type, const NotificationSource& source,
+ const NotificationDetails& details) {
+#if defined(OS_CHROMEOS)
+ DCHECK(type.value == NotificationType::WEB_SOCKET_PROXY_STARTED);
+#else
+ NOTREACHED();
+#endif
+ timer_.Stop(); // Cancel timeout timer.
+ Finalize();
+}
+
+void WebSocketProxyPrivateGetPassportForTCPFunction::Finalize() {
+ SendResponse(true);
+}
+
diff --git a/chrome/browser/extensions/extension_web_socket_proxy_private_api.h b/chrome/browser/extensions/extension_web_socket_proxy_private_api.h
new file mode 100644
index 0000000..bb32262
--- /dev/null
+++ b/chrome/browser/extensions/extension_web_socket_proxy_private_api.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_WEB_SOCKET_PROXY_PRIVATE_API_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_WEB_SOCKET_PROXY_PRIVATE_API_H_
+#pragma once
+
+#include "base/timer.h"
+#include "content/common/notification_observer.h"
+#include "content/common/notification_registrar.h"
+#include "chrome/browser/extensions/extension_function.h"
+
+class WebSocketProxyPrivateGetPassportForTCPFunction
+ : public AsyncExtensionFunction, public NotificationObserver {
+ private:
+ // ExtensionFunction implementation.
+ virtual bool RunImpl() OVERRIDE;
+
+ // NotificationObserver implementation.
+ virtual void Observe(
+ NotificationType type, const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Finalizes async operation.
+ void Finalize();
+
+ // Used to signal timeout (when waiting for proxy initial launch).
+ base::OneShotTimer<WebSocketProxyPrivateGetPassportForTCPFunction> timer_;
+
+ NotificationRegistrar registrar_;
+
+ DECLARE_EXTENSION_FUNCTION_NAME("webSocketProxyPrivate.getPassportForTCP")
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_WEB_SOCKET_PROXY_PRIVATE_API_H_
diff --git a/chrome/browser/extensions/extension_web_socket_proxy_private_apitest.cc b/chrome/browser/extensions/extension_web_socket_proxy_private_apitest.cc
new file mode 100644
index 0000000..ef2afd6
--- /dev/null
+++ b/chrome/browser/extensions/extension_web_socket_proxy_private_apitest.cc
@@ -0,0 +1,19 @@
+// 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.
+
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/common/chrome_switches.h"
+
+class ExtensionWebSocketProxyPrivateApiTest : public ExtensionApiTest {
+ void SetUpCommandLine(CommandLine* command_line) {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitchASCII(
+ switches::kAllowWebSocketProxy, "mknjjldhaihcdajjbihghhiehamnpcak");
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(ExtensionWebSocketProxyPrivateApiTest, Pass) {
+ ASSERT_TRUE(RunExtensionTest("web_socket_proxy_private")) << message_;
+}
+
diff --git a/chrome/browser/internal_auth.cc b/chrome/browser/internal_auth.cc
new file mode 100644
index 0000000..268ee1b
--- /dev/null
+++ b/chrome/browser/internal_auth.cc
@@ -0,0 +1,480 @@
+// 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.
+
+#include "chrome/browser/internal_auth.h"
+
+#include <algorithm>
+#include <deque>
+
+#include "base/base64.h"
+#include "base/lazy_instance.h"
+#include "base/rand_util.h"
+#include "base/synchronization/lock.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/threading/thread_checker.h"
+#include "base/time.h"
+#include "base/timer.h"
+#include "base/values.h"
+#include "content/browser/browser_thread.h"
+#include "crypto/hmac.h"
+
+namespace {
+
+typedef std::map<std::string, std::string> VarValueMap;
+
+// Size of a tick in microseconds. This determines upper bound for average
+// number of passports generated per time unit. This bound equals to
+// (kMicrosecondsPerSecond / TickUs) calls per second.
+const int64 kTickUs = 10000;
+
+// Verification window size in ticks; that means any passport expires in
+// (kVerificationWindowTicks * TickUs / kMicrosecondsPerSecond) seconds.
+const int kVerificationWindowTicks = 2000;
+
+// Generation window determines how well we are able to cope with bursts of
+// GeneratePassport calls those exceed upper bound on average speed.
+const int kGenerationWindowTicks = 20;
+
+// Makes no sense to compare other way round.
+COMPILE_ASSERT(kGenerationWindowTicks <= kVerificationWindowTicks,
+ makes_no_sense_to_have_generation_window_larger_than_verification_one);
+// We are not optimized for high value of kGenerationWindowTicks.
+COMPILE_ASSERT(kGenerationWindowTicks < 30, too_large_generation_window);
+
+// Regenerate key after this number of ticks.
+const int kKeyRegenerationSoftTicks = 500000;
+// Reject passports if key has not been regenerated in that number of ticks.
+const int kKeyRegenerationHardTicks = kKeyRegenerationSoftTicks * 2;
+
+// Limit for number of accepted var=value pairs. Feel free to bump this limit
+// higher once needed.
+const size_t kVarsLimit = 16;
+
+// Limit for length of caller-supplied strings. Feel free to bump this limit
+// higher once needed.
+const size_t kStringLengthLimit = 512;
+
+// Character used as a separator for construction of message to take HMAC of.
+// It is critical to validate all caller-supplied data (used to construct
+// message) to be clear of this separator because it could allow attacks.
+const char kItemSeparator = '\n';
+
+// Character used for var=value separation.
+const char kVarValueSeparator = '=';
+
+const size_t kKeySizeInBytes = 128 / 8;
+const int kHMACSizeInBytes = 256 / 8;
+
+// Length of base64 string required to encode given number of raw octets.
+#define BASE64_PER_RAW(X) (X > 0 ? ((X - 1) / 3 + 1) * 4 : 0)
+
+// Size of decimal string representing 64-bit tick.
+const size_t kTickStringLength = 20;
+
+// A passport consists of 2 parts: HMAC and tick.
+const size_t kPassportSize =
+ BASE64_PER_RAW(kHMACSizeInBytes) + kTickStringLength;
+
+int64 GetCurrentTick() {
+ int64 tick = base::Time::Now().ToInternalValue() / kTickUs;
+ if (tick < kVerificationWindowTicks ||
+ tick < kKeyRegenerationHardTicks ||
+ tick > kint64max - kKeyRegenerationHardTicks) {
+ return 0;
+ }
+ return tick;
+}
+
+bool IsDomainSane(const std::string& domain) {
+ return !domain.empty() &&
+ domain.size() <= kStringLengthLimit &&
+ IsStringUTF8(domain) &&
+ domain.find_first_of(kItemSeparator) == std::string::npos;
+}
+
+bool IsVarSane(const std::string& var) {
+ static const char kAllowedChars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "_";
+ COMPILE_ASSERT(
+ sizeof(kAllowedChars) == 26 + 26 + 10 + 1 + 1, some_mess_with_chars);
+ // We must not allow kItemSeparator in anything used as an input to construct
+ // message to sign.
+ DCHECK(std::find(kAllowedChars, kAllowedChars + arraysize(kAllowedChars),
+ kItemSeparator) == kAllowedChars + arraysize(kAllowedChars));
+ DCHECK(std::find(kAllowedChars, kAllowedChars + arraysize(kAllowedChars),
+ kVarValueSeparator) == kAllowedChars + arraysize(kAllowedChars));
+ return !var.empty() &&
+ var.size() <= kStringLengthLimit &&
+ IsStringASCII(var) &&
+ var.find_first_not_of(kAllowedChars) == std::string::npos &&
+ !IsAsciiDigit(var[0]);
+}
+
+bool IsValueSane(const std::string& value) {
+ return value.size() <= kStringLengthLimit &&
+ IsStringUTF8(value) &&
+ value.find_first_of(kItemSeparator) == std::string::npos;
+}
+
+bool IsVarValueMapSane(const VarValueMap& map) {
+ if (map.size() > kVarsLimit)
+ return false;
+ for (VarValueMap::const_iterator it = map.begin(); it != map.end(); ++it) {
+ const std::string& var = it->first;
+ const std::string& value = it->second;
+ if (!IsVarSane(var) || !IsValueSane(value))
+ return false;
+ }
+ return true;
+}
+
+void ConvertVarValueMapToBlob(const VarValueMap& map, std::string* out) {
+ out->clear();
+ DCHECK(IsVarValueMapSane(map));
+ for (VarValueMap::const_iterator it = map.begin(); it != map.end(); ++it)
+ *out += it->first + kVarValueSeparator + it->second + kItemSeparator;
+}
+
+void CreatePassport(
+ const std::string& domain,
+ const VarValueMap& map,
+ int64 tick,
+ const crypto::HMAC* engine,
+ std::string* out) {
+ DCHECK(engine);
+ DCHECK(out);
+ DCHECK(IsDomainSane(domain));
+ DCHECK(IsVarValueMapSane(map));
+
+ out->clear();
+ std::string result(kPassportSize, '0');
+
+ std::string blob;
+ blob = domain + kItemSeparator;
+ std::string tmp;
+ ConvertVarValueMapToBlob(map, &tmp);
+ blob += tmp + kItemSeparator + base::Uint64ToString(tick);
+
+ std::string hmac;
+ unsigned char* hmac_data = reinterpret_cast<unsigned char*>(
+ WriteInto(&hmac, kHMACSizeInBytes + 1));
+ if (!engine->Sign(blob, hmac_data, kHMACSizeInBytes)) {
+ NOTREACHED();
+ return;
+ }
+ std::string hmac_base64;
+ if (!base::Base64Encode(hmac, &hmac_base64)) {
+ NOTREACHED();
+ return;
+ }
+ if (hmac_base64.size() != BASE64_PER_RAW(kHMACSizeInBytes)) {
+ NOTREACHED();
+ return;
+ }
+ DCHECK(hmac_base64.size() < result.size());
+ std::copy(hmac_base64.begin(), hmac_base64.end(), result.begin());
+
+ std::string tick_decimal = base::Uint64ToString(tick);
+ DCHECK(tick_decimal.size() <= kTickStringLength);
+ std::copy(
+ tick_decimal.begin(),
+ tick_decimal.end(),
+ result.begin() + kPassportSize - tick_decimal.size());
+
+ out->swap(result);
+}
+
+} // namespace
+
+namespace browser {
+
+class InternalAuthVerificationService {
+ public:
+ InternalAuthVerificationService()
+ : key_change_tick_(0),
+ dark_tick_(0) {
+ }
+
+ bool VerifyPassport(
+ const std::string& passport,
+ const std::string& domain,
+ const VarValueMap& map) {
+ int64 current_tick = GetCurrentTick();
+ int64 tick = PreVerifyPassport(passport, domain, current_tick);
+ if (tick == 0)
+ return false;
+ if (!IsVarValueMapSane(map))
+ return false;
+ std::string reference_passport;
+ CreatePassport(domain, map, tick, engine_.get(), &reference_passport);
+ if (passport != reference_passport) {
+ // Consider old key.
+ if (key_change_tick_ + get_verification_window_ticks() < tick) {
+ return false;
+ }
+ if (old_key_.empty() || old_engine_ == NULL)
+ return false;
+ CreatePassport(domain, map, tick, old_engine_.get(), &reference_passport);
+ if (passport != reference_passport)
+ return false;
+ }
+
+ // Record used tick to prevent reuse.
+ std::deque<int64>::iterator it = std::lower_bound(
+ used_ticks_.begin(), used_ticks_.end(), tick);
+ DCHECK(it == used_ticks_.end() || *it != tick);
+ used_ticks_.insert(it, tick);
+
+ // Consider pruning |used_ticks_|.
+ if (used_ticks_.size() > 50) {
+ dark_tick_ = std::max(dark_tick_,
+ current_tick - get_verification_window_ticks());
+ used_ticks_.erase(
+ used_ticks_.begin(),
+ std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
+ dark_tick_ + 1));
+ }
+ return true;
+ }
+
+ void ChangeKey(const std::string& key) {
+ old_key_.swap(key_);
+ key_.clear();
+ old_engine_.swap(engine_);
+ engine_.reset(NULL);
+
+ if (key.size() != kKeySizeInBytes)
+ return;
+ engine_.reset(new crypto::HMAC(crypto::HMAC::SHA256));
+ engine_->Init(key);
+ key_ = key;
+ key_change_tick_ = GetCurrentTick();
+ }
+
+ private:
+ static int get_verification_window_ticks() {
+ return InternalAuthVerification::get_verification_window_ticks();
+ }
+
+ // Returns tick bound to given passport on success or zero on failure.
+ int64 PreVerifyPassport(
+ const std::string& passport,
+ const std::string& domain,
+ int64 current_tick) {
+ if (passport.size() != kPassportSize ||
+ !IsStringASCII(passport) ||
+ !IsDomainSane(domain) ||
+ current_tick <= dark_tick_ ||
+ current_tick > key_change_tick_ + kKeyRegenerationHardTicks ||
+ key_.empty() ||
+ engine_ == NULL) {
+ return 0;
+ }
+
+ // Passport consists of 2 parts: first hmac and then tick.
+ std::string tick_decimal =
+ passport.substr(BASE64_PER_RAW(kHMACSizeInBytes));
+ DCHECK(tick_decimal.size() == kTickStringLength);
+ int64 tick = 0;
+ if (!base::StringToInt64(tick_decimal, &tick) ||
+ tick <= dark_tick_ ||
+ tick > key_change_tick_ + kKeyRegenerationHardTicks ||
+ tick < current_tick - get_verification_window_ticks() ||
+ std::binary_search(used_ticks_.begin(), used_ticks_.end(), tick)) {
+ return 0;
+ }
+ return tick;
+ }
+
+ // Current key.
+ std::string key_;
+
+ // We keep previous key in order to be able to verify passports during
+ // regeneration time. Keys are regenerated on a regular basis.
+ std::string old_key_;
+
+ // Corresponding HMAC engines.
+ scoped_ptr<crypto::HMAC> engine_;
+ scoped_ptr<crypto::HMAC> old_engine_;
+
+ // Tick at a time of recent key regeneration.
+ int64 key_change_tick_;
+
+ // Keeps track of ticks of successfully verified passports to prevent their
+ // reuse. Size of this container is kept reasonably low by purging outdated
+ // ticks.
+ std::deque<int64> used_ticks_;
+
+ // Some ticks before |dark_tick_| were purged from |used_ticks_| container.
+ // That means that we must not trust any tick less than or equal to dark tick.
+ int64 dark_tick_;
+
+ DISALLOW_COPY_AND_ASSIGN(InternalAuthVerificationService);
+};
+
+} // namespace browser
+
+namespace {
+
+static base::LazyInstance<browser::InternalAuthVerificationService>
+ g_verification_service(base::LINKER_INITIALIZED);
+static base::LazyInstance<base::Lock> g_verification_service_lock(
+ base::LINKER_INITIALIZED);
+
+} // namespace
+
+namespace browser {
+
+class InternalAuthGenerationService : public base::ThreadChecker {
+ public:
+ InternalAuthGenerationService() : key_regeneration_tick_(0) {
+ GenerateNewKey();
+ }
+
+ void GenerateNewKey() {
+ DCHECK(CalledOnValidThread());
+ if (!timer_.IsRunning()) {
+ timer_.Start(
+ base::TimeDelta::FromMicroseconds(
+ kKeyRegenerationSoftTicks * kTickUs),
+ this,
+ &InternalAuthGenerationService::GenerateNewKey);
+ }
+
+ engine_.reset(new crypto::HMAC(crypto::HMAC::SHA256));
+ std::string key = base::RandBytesAsString(kKeySizeInBytes);
+ engine_->Init(key);
+ key_regeneration_tick_ = GetCurrentTick();
+ g_verification_service.Get().ChangeKey(key);
+ std::fill(key.begin(), key.end(), 0);
+ }
+
+ // Returns zero on failure.
+ int64 GetUnusedTick(const std::string& domain) {
+ DCHECK(CalledOnValidThread());
+ if (engine_ == NULL) {
+ NOTREACHED();
+ return 0;
+ }
+ if (!IsDomainSane(domain))
+ return 0;
+
+ int64 current_tick = GetCurrentTick();
+ if (!used_ticks_.empty() && used_ticks_.back() > current_tick)
+ current_tick = used_ticks_.back();
+ if (current_tick > key_regeneration_tick_ + kKeyRegenerationHardTicks)
+ return 0;
+
+ // Forget outdated ticks if any.
+ used_ticks_.erase(
+ used_ticks_.begin(),
+ std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
+ current_tick - kGenerationWindowTicks + 1));
+ DCHECK(used_ticks_.size() <= kGenerationWindowTicks + 0u);
+ if (used_ticks_.size() >= kGenerationWindowTicks + 0u) {
+ // Average speed of GeneratePassport calls exceeds limit.
+ return 0;
+ }
+ for (int64 tick = current_tick;
+ tick > current_tick - kGenerationWindowTicks;
+ --tick) {
+ int idx = static_cast<int>(used_ticks_.size()) -
+ static_cast<int>(current_tick - tick + 1);
+ if (idx < 0 || used_ticks_[idx] != tick) {
+ DCHECK(used_ticks_.end() ==
+ std::find(used_ticks_.begin(), used_ticks_.end(), tick));
+ return tick;
+ }
+ }
+ NOTREACHED();
+ return 0;
+ }
+
+ std::string GeneratePassport(
+ const std::string& domain, const VarValueMap& map, int64 tick) {
+ DCHECK(CalledOnValidThread());
+ if (tick == 0) {
+ tick = GetUnusedTick(domain);
+ if (tick == 0)
+ return std::string();
+ }
+ if (!IsVarValueMapSane(map))
+ return std::string();
+
+ std::string result;
+ CreatePassport(domain, map, tick, engine_.get(), &result);
+ used_ticks_.insert(
+ std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick), tick);
+ return result;
+ }
+
+ private:
+ static int get_verification_window_ticks() {
+ return InternalAuthVerification::get_verification_window_ticks();
+ }
+
+ scoped_ptr<crypto::HMAC> engine_;
+ base::RepeatingTimer<InternalAuthGenerationService> timer_;
+ int64 key_regeneration_tick_;
+ std::deque<int64> used_ticks_;
+
+ DISALLOW_COPY_AND_ASSIGN(InternalAuthGenerationService);
+};
+
+} // namespace browser
+
+namespace {
+
+static base::LazyInstance<browser::InternalAuthGenerationService>
+ g_generation_service(base::LINKER_INITIALIZED);
+
+} // namespace
+
+namespace browser {
+
+// static
+bool InternalAuthVerification::VerifyPassport(
+ const std::string& passport,
+ const std::string& domain,
+ const VarValueMap& var_value_map) {
+ base::AutoLock alk(g_verification_service_lock.Get());
+ return g_verification_service.Get().VerifyPassport(
+ passport, domain, var_value_map);
+}
+
+// static
+void InternalAuthVerification::ChangeKey(const std::string& key) {
+ base::AutoLock alk(g_verification_service_lock.Get());
+ g_verification_service.Get().ChangeKey(key);
+};
+
+// static
+int InternalAuthVerification::get_verification_window_ticks() {
+ int candidate = kVerificationWindowTicks;
+ if (verification_window_seconds_ > 0)
+ candidate = verification_window_seconds_ *
+ base::Time::kMicrosecondsPerSecond / kTickUs;
+ return std::max(1, std::min(candidate, kVerificationWindowTicks));
+}
+
+int InternalAuthVerification::verification_window_seconds_ = 0;
+
+// static
+std::string InternalAuthGeneration::GeneratePassport(
+ const std::string& domain, const VarValueMap& var_value_map) {
+ return g_generation_service.Get().GeneratePassport(domain, var_value_map, 0);
+}
+
+// static
+void InternalAuthGeneration::GenerateNewKey() {
+ g_generation_service.Get().GenerateNewKey();
+}
+
+} // namespace browser
+
diff --git a/chrome/browser/internal_auth.h b/chrome/browser/internal_auth.h
new file mode 100644
index 0000000..008559b
--- /dev/null
+++ b/chrome/browser/internal_auth.h
@@ -0,0 +1,74 @@
+// 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.
+
+#ifndef CHROME_BROWSER_INTERNAL_AUTH_H_
+#define CHROME_BROWSER_INTERNAL_AUTH_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+
+class WebSocketProxyPrivateGetPassportForTCPFunction;
+
+namespace browser {
+
+// Call InternalAuthVerification methods on any thread.
+class InternalAuthVerification {
+ public:
+ // Used by consumer of passport in order to verify credentials.
+ static bool VerifyPassport(
+ const std::string& passport,
+ const std::string& domain,
+ const std::map<std::string, std::string>& var_value_map);
+
+ private:
+ // We allow for easy separation of InternalAuthVerification and
+ // InternalAuthGeneration so the only thing they share (besides time) is
+ // a key (regenerated infrequently).
+ static void ChangeKey(const std::string& key);
+
+#ifdef UNIT_TEST
+ static void set_verification_window_seconds(int seconds) {
+ verification_window_seconds_ = seconds;
+ }
+#endif
+
+ static int get_verification_window_ticks();
+
+ static int verification_window_seconds_;
+
+ friend class InternalAuthGeneration;
+ friend class InternalAuthVerificationService;
+ friend class InternalAuthGenerationService;
+
+ FRIEND_TEST_ALL_PREFIXES(InternalAuthTest, ExpirationAndBruteForce);
+};
+
+// Not thread-safe. Make all calls on the same thread (UI thread).
+class InternalAuthGeneration {
+ private:
+ // Generates passport; do this only after successful check of credentials.
+ static std::string GeneratePassport(
+ const std::string& domain,
+ const std::map<std::string, std::string>& var_value_map);
+
+ // Used only by tests.
+ static void GenerateNewKey();
+
+ friend class ::WebSocketProxyPrivateGetPassportForTCPFunction;
+
+ FRIEND_TEST_ALL_PREFIXES(InternalAuthTest, BasicGeneration);
+ FRIEND_TEST_ALL_PREFIXES(InternalAuthTest, DoubleGeneration);
+ FRIEND_TEST_ALL_PREFIXES(InternalAuthTest, BadGeneration);
+ FRIEND_TEST_ALL_PREFIXES(InternalAuthTest, BasicVerification);
+ FRIEND_TEST_ALL_PREFIXES(InternalAuthTest, BruteForce);
+ FRIEND_TEST_ALL_PREFIXES(InternalAuthTest, ExpirationAndBruteForce);
+ FRIEND_TEST_ALL_PREFIXES(InternalAuthTest, ChangeKey);
+};
+
+} // namespace browser
+
+#endif // CHROME_BROWSER_INTERNAL_AUTH_H_
diff --git a/chrome/browser/internal_auth_unittest.cc b/chrome/browser/internal_auth_unittest.cc
new file mode 100644
index 0000000..1e81692
--- /dev/null
+++ b/chrome/browser/internal_auth_unittest.cc
@@ -0,0 +1,199 @@
+// 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.
+
+#include "chrome/browser/internal_auth.h"
+
+#include <algorithm>
+
+#include "base/lazy_instance.h"
+#include "base/time.h"
+#include "content/browser/browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace browser {
+
+class InternalAuthTest : public ::testing::Test {
+ public:
+ InternalAuthTest() {
+ long_string_ = "seed";
+ for (int i = 20; i--;)
+ long_string_ += long_string_;
+ }
+ virtual ~InternalAuthTest() {}
+
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ }
+
+ MessageLoop message_loop_;
+ std::string long_string_;
+};
+
+TEST_F(InternalAuthTest, BasicGeneration) {
+ std::map<std::string, std::string> map;
+ map["key"] = "value";
+ std::string token = browser::InternalAuthGeneration::GeneratePassport(
+ "zapata", map);
+ ASSERT_GT(token.size(), 10u); // short token is insecure.
+
+ map["key2"] = "value2";
+ token = browser::InternalAuthGeneration::GeneratePassport("zapata", map);
+ ASSERT_GT(token.size(), 10u);
+}
+
+TEST_F(InternalAuthTest, DoubleGeneration) {
+ std::map<std::string, std::string> map;
+ map["key"] = "value";
+ std::string token1 = browser::InternalAuthGeneration::GeneratePassport(
+ "zapata", map);
+ ASSERT_GT(token1.size(), 10u);
+
+ std::string token2 = browser::InternalAuthGeneration::GeneratePassport(
+ "zapata", map);
+ ASSERT_GT(token2.size(), 10u);
+ // tokens are different even if credentials coincide.
+ ASSERT_NE(token1, token2);
+}
+
+TEST_F(InternalAuthTest, BadGeneration) {
+ std::map<std::string, std::string> map;
+ map["key"] = "value";
+ // Trying huge domain.
+ std::string token = browser::InternalAuthGeneration::GeneratePassport(
+ long_string_, map);
+ ASSERT_TRUE(token.empty());
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ token, long_string_, map));
+
+ // Trying empty domain.
+ token = browser::InternalAuthGeneration::GeneratePassport("", map);
+ ASSERT_TRUE(token.empty());
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ token, "", map));
+
+ std::string dummy("abcdefghij");
+ for (size_t i = 1000; i--;) {
+ std::string key = dummy;
+ std::next_permutation(dummy.begin(), dummy.end());
+ std::string value = dummy;
+ std::next_permutation(dummy.begin(), dummy.end());
+ map[key] = value;
+ }
+ // Trying huge var=value map.
+ token = browser::InternalAuthGeneration::GeneratePassport("zapata", map);
+ ASSERT_TRUE(token.empty());
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ token, "zapata", map));
+
+ map.clear();
+ map[""] = "value";
+ // Trying empty key.
+ token = browser::InternalAuthGeneration::GeneratePassport("zapata", map);
+ ASSERT_TRUE(token.empty());
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ token, "zapata", map));
+}
+
+TEST_F(InternalAuthTest, BasicVerification) {
+ std::map<std::string, std::string> map;
+ map["key"] = "value";
+ std::string token = browser::InternalAuthGeneration::GeneratePassport(
+ "zapata", map);
+ ASSERT_GT(token.size(), 10u);
+ ASSERT_TRUE(browser::InternalAuthVerification::VerifyPassport(
+ token, "zapata", map));
+ // Passport can not be reused.
+ for (int i = 10000; i--;) {
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ token, "zapata", map));
+ }
+}
+
+TEST_F(InternalAuthTest, BruteForce) {
+ std::map<std::string, std::string> map;
+ map["key"] = "value";
+ std::string token = browser::InternalAuthGeneration::GeneratePassport(
+ "zapata", map);
+ ASSERT_GT(token.size(), 10u);
+
+ // Trying bruteforce.
+ std::string dummy = token;
+ for (size_t i = 10000; i--;) {
+ std::next_permutation(dummy.begin(), dummy.end());
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ dummy, "zapata", map));
+ }
+ dummy = token;
+ for (size_t i = 10000; i--;) {
+ std::next_permutation(dummy.begin(), dummy.begin() + dummy.size() / 2);
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ dummy, "zapata", map));
+ }
+ // We brute forced just too little, so original token must not expire yet.
+ ASSERT_TRUE(browser::InternalAuthVerification::VerifyPassport(
+ token, "zapata", map));
+}
+
+TEST_F(InternalAuthTest, ExpirationAndBruteForce) {
+ int kCustomVerificationWindow = 2;
+ browser::InternalAuthVerification::set_verification_window_seconds(
+ kCustomVerificationWindow);
+
+ std::map<std::string, std::string> map;
+ map["key"] = "value";
+ std::string token = browser::InternalAuthGeneration::GeneratePassport(
+ "zapata", map);
+ ASSERT_GT(token.size(), 10u);
+
+ // We want to test token expiration, so we need to wait some amount of time,
+ // so we are brute-forcing during this time.
+ base::Time timestamp = base::Time::Now();
+ std::string dummy1 = token;
+ std::string dummy2 = token;
+ for (;;) {
+ for (size_t i = 10000; i--;) {
+ std::next_permutation(dummy1.begin(), dummy1.end());
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ dummy1, "zapata", map));
+ }
+ for (size_t i = 10000; i--;) {
+ std::next_permutation(dummy2.begin(), dummy2.begin() + dummy2.size() / 2);
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ dummy2, "zapata", map));
+ }
+ if (base::Time::Now() - timestamp > base::TimeDelta::FromSeconds(
+ kCustomVerificationWindow + 1)) {
+ break;
+ }
+ }
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ token, "zapata", map));
+ // Reset verification window to default.
+ browser::InternalAuthVerification::set_verification_window_seconds(0);
+}
+
+TEST_F(InternalAuthTest, ChangeKey) {
+ std::map<std::string, std::string> map;
+ map["key"] = "value";
+ std::string token = browser::InternalAuthGeneration::GeneratePassport(
+ "zapata", map);
+ ASSERT_GT(token.size(), 10u);
+
+ browser::InternalAuthGeneration::GenerateNewKey();
+ // Passport should survive key change.
+ ASSERT_TRUE(browser::InternalAuthVerification::VerifyPassport(
+ token, "zapata", map));
+
+ token = browser::InternalAuthGeneration::GeneratePassport("zapata", map);
+ ASSERT_GT(token.size(), 10u);
+ for (int i = 20; i--;)
+ browser::InternalAuthGeneration::GenerateNewKey();
+ // Passport should not survive series of key changes.
+ ASSERT_FALSE(browser::InternalAuthVerification::VerifyPassport(
+ token, "zapata", map));
+}
+
+} // namespace browser
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index fcac008..3879ffe 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1001,6 +1001,8 @@
'browser/extensions/extension_uninstall_dialog.h',
'browser/extensions/extension_updater.cc',
'browser/extensions/extension_updater.h',
+ 'browser/extensions/extension_web_socket_proxy_private_api.cc',
+ 'browser/extensions/extension_web_socket_proxy_private_api.h',
'browser/extensions/extension_web_ui.cc',
'browser/extensions/extension_web_ui.h',
'browser/extensions/extension_webrequest_api.cc',
@@ -1265,6 +1267,8 @@
'browser/instant/instant_unload_handler.h',
'browser/instant/promo_counter.cc',
'browser/instant/promo_counter.h',
+ 'browser/internal_auth.cc',
+ 'browser/internal_auth.h',
'browser/intranet_redirect_detector.cc',
'browser/intranet_redirect_detector.h',
'browser/io_thread.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 6ebfcc5..d3b1927 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1176,12 +1176,12 @@
'../jingle/jingle.gyp:jingle_glue_test_util',
'../media/media.gyp:media_test_support',
'../net/net.gyp:net_test_support',
+ '../testing/gmock.gyp:gmock',
+ '../testing/gtest.gyp:gtest',
'test_support_common',
'test_support_sync',
'test_support_syncapi',
'test_support_unit',
- '../testing/gmock.gyp:gmock',
- '../testing/gtest.gyp:gtest',
# 3) anything tests directly depend on
'../app/app.gyp:app_resources',
'../skia/skia.gyp:skia',
@@ -1415,6 +1415,8 @@
'browser/importer/toolbar_importer_unittest.cc',
'browser/instant/instant_loader_manager_unittest.cc',
'browser/instant/promo_counter_unittest.cc',
+ 'browser/internal_auth_unittest.cc',
+ 'browser/internal_auth_unittest.h',
'browser/mach_broker_mac_unittest.cc',
'browser/metrics/metrics_log_unittest.cc',
'browser/metrics/metrics_response_unittest.cc',
@@ -2365,6 +2367,7 @@
'browser/extensions/extension_toolbar_model_browsertest.cc',
'browser/extensions/extension_tts_apitest.cc',
'browser/extensions/extension_url_rewrite_browsertest.cc',
+ 'browser/extensions/extension_web_socket_proxy_private_apitest.cc',
'browser/extensions/extension_webglbackground_apitest.cc',
'browser/extensions/extension_webnavigation_apitest.cc',
'browser/extensions/extension_webrequest_apitest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 4afe3a8..169c2a1 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -37,6 +37,10 @@ const char kAllowOutdatedPlugins[] = "allow-outdated-plugins";
// useful for automation testing of the gallery.
const char kAllowScriptingGallery[] = "allow-scripting-gallery";
+// Specifies comma separated list of extension ids to grant access to local
+// websocket proxy.
+const char kAllowWebSocketProxy[] = "allow-websocket-proxy";
+
// This prevents Chrome from requiring authorization to run certain widely
// installed but less commonly used plug-ins.
const char kAlwaysAuthorizePlugins[] = "always-authorize-plugins";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index aa4cb99..a9c6d8e 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -29,6 +29,7 @@ extern const char kAllowFileAccess[];
extern const char kAllowOutdatedPlugins[];
extern const char kAllowHTTPBackgroundPage[];
extern const char kAllowScriptingGallery[];
+extern const char kAllowWebSocketProxy[];
extern const char kAlwaysAuthorizePlugins[];
extern const char kAlwaysEnableDevTools[];
extern const char kApp[];
diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json
index 8ae8075..00febf8 100644
--- a/chrome/common/extensions/api/extension_api.json
+++ b/chrome/common/extensions/api/extension_api.json
@@ -5240,6 +5240,44 @@
]
},
{
+ "namespace": "webSocketProxyPrivate",
+ "nodoc": true,
+ "types": [],
+ "functions": [
+ {
+ "name": "getPassportForTCP",
+ "description": "requests authorization token for websocket to TCP proxy.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "hostname",
+ "minLength": 1,
+ "description": "hostname to which TCP connection is requested."
+ },
+ {
+ "type": "integer",
+ "name": "port",
+ "minimum": 1,
+ "maximum": 65535,
+ "description": "TCP port number."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "passport",
+ "description": "Passport for passing to proxy."
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": []
+ },
+ {
"namespace": "experimental.extension",
"types": [
{
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index 7f472c1..5914bec 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -268,7 +268,7 @@ const char Extension::kClipboardWritePermission[] = "clipboardWrite";
const char Extension::kContextMenusPermission[] = "contextMenus";
const char Extension::kContentSettingsPermission[] = "contentSettings";
const char Extension::kCookiePermission[] = "cookies";
-const char Extension::kChromeosInfoPrivatePermissions[] = "chromeosInfoPrivate";
+const char Extension::kChromeosInfoPrivatePermission[] = "chromeosInfoPrivate";
const char Extension::kDebuggerPermission[] = "debugger";
const char Extension::kExperimentalPermission[] = "experimental";
const char Extension::kFileBrowserHandlerPermission[] = "fileBrowserHandler";
@@ -282,35 +282,37 @@ const char Extension::kProxyPermission[] = "proxy";
const char Extension::kTabPermission[] = "tabs";
const char Extension::kUnlimitedStoragePermission[] = "unlimitedStorage";
const char Extension::kWebstorePrivatePermission[] = "webstorePrivate";
+const char Extension::kWebSocketProxyPrivatePermission[] =
+ "webSocketProxyPrivate";
// In general, all permissions should have an install message.
// See ExtensionsTest.PermissionMessages for an explanation of each
// exception.
const Extension::Permission Extension::kPermissions[] = {
- { kBackgroundPermission, PermissionMessage::ID_NONE },
- { kBookmarkPermission, PermissionMessage::ID_BOOKMARKS },
- { kChromeosInfoPrivatePermissions, PermissionMessage::ID_NONE },
- { kClipboardReadPermission, PermissionMessage::ID_CLIPBOARD },
- { kClipboardWritePermission, PermissionMessage::ID_NONE },
- { kContentSettingsPermission, PermissionMessage::ID_NONE },
- { kContextMenusPermission, PermissionMessage::ID_NONE },
- { kCookiePermission, PermissionMessage::ID_NONE },
- { kDebuggerPermission, PermissionMessage::ID_DEBUGGER },
- { kExperimentalPermission, PermissionMessage::ID_NONE },
- { kFileBrowserHandlerPermission, PermissionMessage::ID_NONE },
- { kFileBrowserPrivatePermission, PermissionMessage::ID_NONE },
- { kGeolocationPermission, PermissionMessage::ID_GEOLOCATION },
- { kIdlePermission, PermissionMessage::ID_NONE },
- { kHistoryPermission, PermissionMessage::ID_BROWSING_HISTORY },
- { kManagementPermission, PermissionMessage::ID_MANAGEMENT },
- { kNotificationPermission, PermissionMessage::ID_NONE },
- { kProxyPermission, PermissionMessage::ID_NONE },
- { kTabPermission, PermissionMessage::ID_TABS },
- { kUnlimitedStoragePermission, PermissionMessage::ID_NONE },
- { kWebstorePrivatePermission, PermissionMessage::ID_NONE }
+ { kBackgroundPermission, PermissionMessage::ID_NONE },
+ { kBookmarkPermission, PermissionMessage::ID_BOOKMARKS },
+ { kChromeosInfoPrivatePermission, PermissionMessage::ID_NONE },
+ { kClipboardReadPermission, PermissionMessage::ID_CLIPBOARD },
+ { kClipboardWritePermission, PermissionMessage::ID_NONE },
+ { kContentSettingsPermission, PermissionMessage::ID_NONE },
+ { kContextMenusPermission, PermissionMessage::ID_NONE },
+ { kCookiePermission, PermissionMessage::ID_NONE },
+ { kDebuggerPermission, PermissionMessage::ID_DEBUGGER },
+ { kExperimentalPermission, PermissionMessage::ID_NONE },
+ { kFileBrowserHandlerPermission, PermissionMessage::ID_NONE },
+ { kFileBrowserPrivatePermission, PermissionMessage::ID_NONE },
+ { kGeolocationPermission, PermissionMessage::ID_GEOLOCATION },
+ { kIdlePermission, PermissionMessage::ID_NONE },
+ { kHistoryPermission, PermissionMessage::ID_BROWSING_HISTORY },
+ { kManagementPermission, PermissionMessage::ID_MANAGEMENT },
+ { kNotificationPermission, PermissionMessage::ID_NONE },
+ { kProxyPermission, PermissionMessage::ID_NONE },
+ { kTabPermission, PermissionMessage::ID_TABS },
+ { kUnlimitedStoragePermission, PermissionMessage::ID_NONE },
+ { kWebSocketProxyPrivatePermission, PermissionMessage::ID_NONE },
+ { kWebstorePrivatePermission, PermissionMessage::ID_NONE },
};
-const size_t Extension::kNumPermissions =
- arraysize(Extension::kPermissions);
+const size_t Extension::kNumPermissions = arraysize(Extension::kPermissions);
const char* const Extension::kHostedAppPermissionNames[] = {
Extension::kBackgroundPermission,
@@ -325,7 +327,7 @@ const size_t Extension::kNumHostedAppPermissions =
const char* const Extension::kComponentPrivatePermissionNames[] = {
Extension::kFileBrowserPrivatePermission,
Extension::kWebstorePrivatePermission,
- Extension::kChromeosInfoPrivatePermissions,
+ Extension::kChromeosInfoPrivatePermission,
};
const size_t Extension::kNumComponentPrivatePermissions =
arraysize(Extension::kComponentPrivatePermissionNames);
@@ -2765,8 +2767,7 @@ void Extension::InitEffectiveHostPermissions() {
}
}
-bool Extension::IsComponentOnlyPermission
- (const std::string& permission) const {
+bool Extension::IsComponentOnlyPermission(const std::string& permission) const {
if (location() == Extension::COMPONENT)
return true;
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index bb60afc..8fc1641 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -283,7 +283,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
static const char kContentSettingsPermission[];
static const char kContextMenusPermission[];
static const char kCookiePermission[];
- static const char kChromeosInfoPrivatePermissions[];
+ static const char kChromeosInfoPrivatePermission[];
static const char kDebuggerPermission[];
static const char kExperimentalPermission[];
static const char kFileBrowserHandlerPermission[];
@@ -297,6 +297,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
static const char kTabPermission[];
static const char kUnlimitedStoragePermission[];
static const char kWebstorePrivatePermission[];
+ static const char kWebSocketProxyPrivatePermission[];
static const Permission kPermissions[];
static const size_t kNumPermissions;
diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc
index 8d432c9..8488984 100644
--- a/chrome/common/extensions/extension_unittest.cc
+++ b/chrome/common/extensions/extension_unittest.cc
@@ -1031,10 +1031,11 @@ TEST(ExtensionTest, PermissionMessages) {
// to warn you further.
skip.insert(Extension::kExperimentalPermission);
- // These are only usable by component extensions.
+ // These are private.
skip.insert(Extension::kWebstorePrivatePermission);
skip.insert(Extension::kFileBrowserPrivatePermission);
- skip.insert(Extension::kChromeosInfoPrivatePermissions);
+ skip.insert(Extension::kChromeosInfoPrivatePermission);
+ skip.insert(Extension::kWebSocketProxyPrivatePermission);
const Extension::PermissionMessage::MessageId ID_NONE =
Extension::PermissionMessage::ID_NONE;
diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js
index fb98466..4bf6e4b 100644
--- a/chrome/renderer/resources/renderer_extension_bindings.js
+++ b/chrome/renderer/resources/renderer_extension_bindings.js
@@ -6,8 +6,7 @@
// extensions. It is loaded by any extension-related context, such as content
// scripts or toolstrips.
// See user_script_slave.cc for script that is loaded by content scripts only.
-// TODO(mpcomplete): we also load this in regular web pages, but don't need
-// to.
+// TODO(mpcomplete): we also load this in regular web pages, but don't need to.
var chrome = chrome || {};
(function () {
@@ -328,6 +327,7 @@ var chrome = chrome || {};
"tabs",
"test",
"toolstrip",
+ "webSocketProxyPrivate",
"webstorePrivate",
"windows",
diff --git a/chrome/test/data/extensions/api_test/web_socket_proxy_private/background.html b/chrome/test/data/extensions/api_test/web_socket_proxy_private/background.html
new file mode 100644
index 0000000..9b2034e
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/web_socket_proxy_private/background.html
@@ -0,0 +1,32 @@
+<script>
+ var hostname = '127.0.0.1';
+ var port = 20202;
+ var proxy = "ws://127.0.0.1:10101/tcpproxy";
+
+ function gotMessage(msg) {
+ chrome.test.assertEq(msg.data, window.btoa("aloha\n"));
+ }
+
+ function gotPassport(passport) {
+ ws = new WebSocket(proxy);
+
+ /* TODO(dilmah): envelope gotMessage into chrome.test.callbackPass after
+ setting up testserver */
+ ws.onmessage = gotMessage;
+
+ ws.onopen = function() {
+ var request = passport + ':' + hostname + ':' + port + ':';
+ ws.send(request);
+
+ /* Further on we can send base64-encoded data */
+ ws.send(window.btoa("HELO localhost\n"));
+ };
+ }
+
+ function test_connect() {
+ chrome.webSocketProxyPrivate.getPassportForTCP(
+ hostname, port, chrome.test.callbackPass(gotPassport));
+ }
+
+ chrome.test.runTests([test_connect]);
+</script>
diff --git a/chrome/test/data/extensions/api_test/web_socket_proxy_private/manifest.json b/chrome/test/data/extensions/api_test/web_socket_proxy_private/manifest.json
new file mode 100644
index 0000000..adac703
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/web_socket_proxy_private/manifest.json
@@ -0,0 +1,8 @@
+{
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzxp8WFAeJGgwQFdaktyst1HW/8yqbgFeD6z5yKK6MOhGn0HSsiDSU0cjEkBbqbyr4WJFDpAB3uLhUhUOszLtjKJt0c8XdqITKlSXKfa16e4P6z4lA+cVmcCuTvQtop2iXaJrTon7dL6BFPq8xKuaCX8yb+uZo91KmkNpd3OEF6QIDAQAB",
+ "name": "chrome.webSocketProxyPrivate",
+ "version": "0.1",
+ "description": "Test for webSocketProxyPrivate.getPassportForTCP",
+ "background_page": "background.html",
+ "permissions": ["webSocketProxyPrivate", "chromeosInfoPrivate"]
+}
diff --git a/crypto/hmac.h b/crypto/hmac.h
index 7852797..c0706d8 100644
--- a/crypto/hmac.h
+++ b/crypto/hmac.h
@@ -51,7 +51,9 @@ class HMAC {
// to the constructor and the key supplied to the Init method. The HMAC is
// returned in |digest|, which has |digest_length| bytes of storage available.
// TODO(abarth): digest_length should be a size_t.
- bool Sign(const std::string& data, unsigned char* digest, int digest_length);
+ bool Sign(const std::string& data,
+ unsigned char* digest,
+ int digest_length) const;
// TODO(albertb): Add a Verify method.
diff --git a/crypto/hmac_mac.cc b/crypto/hmac_mac.cc
index d7cec61..fefd6e7 100644
--- a/crypto/hmac_mac.cc
+++ b/crypto/hmac_mac.cc
@@ -41,7 +41,7 @@ HMAC::~HMAC() {
bool HMAC::Sign(const std::string& data,
unsigned char* digest,
- int digest_length) {
+ int digest_length) const {
CCHmacAlgorithm algorithm;
int algorithm_digest_length;
switch (hash_alg_) {
diff --git a/crypto/hmac_nss.cc b/crypto/hmac_nss.cc
index 957f9db..722fcf1 100644
--- a/crypto/hmac_nss.cc
+++ b/crypto/hmac_nss.cc
@@ -75,7 +75,7 @@ bool HMAC::Init(const unsigned char *key, int key_length) {
bool HMAC::Sign(const std::string& data,
unsigned char* digest,
- int digest_length) {
+ int digest_length) const {
if (!plat_->sym_key_.get()) {
// Init has not been called before Sign.
NOTREACHED();
diff --git a/crypto/hmac_openssl.cc b/crypto/hmac_openssl.cc
index 6fbc437..8b7b96d 100644
--- a/crypto/hmac_openssl.cc
+++ b/crypto/hmac_openssl.cc
@@ -42,7 +42,7 @@ HMAC::~HMAC() {
bool HMAC::Sign(const std::string& data,
unsigned char* digest,
- int digest_length) {
+ int digest_length) const {
DCHECK_GE(digest_length, 0);
DCHECK(!plat_->key.empty()); // Init must be called before Sign.
diff --git a/crypto/hmac_win.cc b/crypto/hmac_win.cc
index e5511e0..1e6954a 100644
--- a/crypto/hmac_win.cc
+++ b/crypto/hmac_win.cc
@@ -156,7 +156,7 @@ HMAC::~HMAC() {
bool HMAC::Sign(const std::string& data,
unsigned char* digest,
- int digest_length) {
+ int digest_length) const {
if (hash_alg_ == SHA256) {
if (plat_->raw_key_.empty())
return false;
diff --git a/crypto/symmetric_key_mac.cc b/crypto/symmetric_key_mac.cc
index 47193a08..a92c43a 100644
--- a/crypto/symmetric_key_mac.cc
+++ b/crypto/symmetric_key_mac.cc
@@ -32,7 +32,7 @@ CSSM_KEY_TYPE CheckKeyParams(crypto::SymmetricKey::Algorithm algorithm,
}
}
-void* CreateRandomBytes(size_t size) {
+uint8_t* CreateRandomBytes(size_t size) {
CSSM_RETURN err;
CSSM_CC_HANDLE ctx;
err = CSSM_CSP_CreateRandomGenContext(crypto::GetSharedCSPHandle(),
@@ -50,7 +50,7 @@ void* CreateRandomBytes(size_t size) {
random_data.Data = NULL;
}
CSSM_DeleteContext(ctx);
- return random_data.Data; // Caller responsible for freeing this
+ return random_data.Data; // Caller responsible for freeing this.
}
inline CSSM_DATA StringToData(const std::string& str) {
@@ -65,16 +65,20 @@ inline CSSM_DATA StringToData(const std::string& str) {
namespace crypto {
-SymmetricKey::~SymmetricKey() {}
+SymmetricKey::~SymmetricKey() {
+ std::fill(key_.begin(), key_.end(), 0);
+}
// static
SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm,
size_t key_size_in_bits) {
CheckKeyParams(algorithm, key_size_in_bits);
- void* random_bytes = CreateRandomBytes((key_size_in_bits + 7) / 8);
+ size_t key_size_in_bytes = (key_size_in_bits + 7) / 8;
+ uint8_t* random_bytes = CreateRandomBytes(key_size_in_bytes);
if (!random_bytes)
return NULL;
SymmetricKey *key = new SymmetricKey(random_bytes, key_size_in_bits);
+ std::fill(random_bytes, random_bytes + key_size_in_bytes, 0);
free(random_bytes);
return key;
}
@@ -139,9 +143,9 @@ SymmetricKey* SymmetricKey::Import(Algorithm algorithm,
return new SymmetricKey(raw_key.data(), raw_key.size() * 8);
}
-SymmetricKey::SymmetricKey(const void *key_data, size_t key_size_in_bits)
- : key_(reinterpret_cast<const char*>(key_data),
- key_size_in_bits / 8) {}
+SymmetricKey::SymmetricKey(const void* key_data, size_t key_size_in_bits)
+ : key_(reinterpret_cast<const char*>(key_data), key_size_in_bits / 8) {
+}
bool SymmetricKey::GetRawKey(std::string* raw_key) {
*raw_key = key_;