diff options
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_; |