diff options
author | joedow <joedow@chromium.org> | 2016-02-16 21:30:02 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-17 05:30:58 +0000 |
commit | 709e40ef1af725648b40fe28e06801b3a56f7e14 (patch) | |
tree | 93c8d61e2c7bab3093b03c1b555404c10d1487be /remoting/host/security_key | |
parent | cf186a3887a562701a0f52b216a4b05ac136d0f0 (diff) | |
download | chromium_src-709e40ef1af725648b40fe28e06801b3a56f7e14.zip chromium_src-709e40ef1af725648b40fe28e06801b3a56f7e14.tar.gz chromium_src-709e40ef1af725648b40fe28e06801b3a56f7e14.tar.bz2 |
Refactoring platform-agnostic Gnubby Auth Handler logic into a Host Extension.
This change breaks the logic in the GnubbyAuthHandlerLinux class into
platform-specific and platform-agnostic chunks. The platform-specific
logic has remained in the GnubbyAuthHandlerLinux class whereas the
platform-agnostic code was moved into a new HostExtension class
(GnubbyHostExtensionSession). This allows us to add a Gnubby host
extension if it is supported by policy and remove the logic and plumbing
for gnubby message handling from ClientSession and the many
DesktopEnvironment classes.
BUG=584463
Review URL: https://codereview.chromium.org/1689003002
Cr-Commit-Position: refs/heads/master@{#375797}
Diffstat (limited to 'remoting/host/security_key')
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler.h | 34 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_linux.cc | 271 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_linux.h | 48 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc | 196 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_mac.cc | 29 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_win.cc | 29 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_extension.cc | 34 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_extension.h | 34 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_extension_session.cc | 207 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_extension_session.h | 65 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_extension_session_unittest.cc | 377 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_socket.cc | 20 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_socket.h | 7 |
13 files changed, 978 insertions, 373 deletions
diff --git a/remoting/host/security_key/gnubby_auth_handler.h b/remoting/host/security_key/gnubby_auth_handler.h index 606742b..8070398 100644 --- a/remoting/host/security_key/gnubby_auth_handler.h +++ b/remoting/host/security_key/gnubby_auth_handler.h @@ -7,6 +7,7 @@ #include <string> +#include "base/callback_forward.h" #include "base/memory/scoped_ptr.h" namespace base { @@ -15,29 +16,42 @@ class FilePath; namespace remoting { -namespace protocol { -class ClientStub; -} // namespace protocol - // Class responsible for proxying authentication data between a local gnubbyd // and the client. class GnubbyAuthHandler { public: virtual ~GnubbyAuthHandler() {} + // Used to send gnubby extension messages to the client. + typedef base::Callback<void(int connection_id, const std::string& data)> + SendMessageCallback; + // Creates a platform-specific GnubbyAuthHandler. + // All invocations of |callback| are guaranteed to occur before the underlying + // GnubbyAuthHandler object is destroyed. It is not safe to destroy the + // GnubbyAuthHandler object within the callback. static scoped_ptr<GnubbyAuthHandler> Create( - protocol::ClientStub* client_stub); + const SendMessageCallback& callback); // Specify the name of the socket to listen to gnubby requests on. + // TODO(joedow): Move this to a linux specific class. see: crbug.com/587298 static void SetGnubbySocketName(const base::FilePath& gnubby_socket_name); - // A message was received from the client. - virtual void DeliverClientMessage(const std::string& message) = 0; + // Sets the callback used to send messages to the client. + virtual void SetSendMessageCallback(const SendMessageCallback& callback) = 0; + + // Creates the platform specific connection to handle gnubby requests. + virtual void CreateGnubbyConnection() = 0; + + // Returns true if |gnubby_connection_id| represents a valid connection. + virtual bool IsValidConnectionId(int gnubby_connection_id) const = 0; + + // Sends the gnubby response from the client to the local gnubby agent. + virtual void SendClientResponse(int gnubby_connection_id, + const std::string& response) = 0; - // Send data to client. - virtual void DeliverHostDataMessage(int connection_id, - const std::string& data) const = 0; + // Closes the gnubby connection represented by |gnubby_connection_id|. + virtual void SendErrorAndCloseConnection(int gnubby_connection_id) = 0; }; } // namespace remoting diff --git a/remoting/host/security_key/gnubby_auth_handler_linux.cc b/remoting/host/security_key/gnubby_auth_handler_linux.cc index 5bed3c9..8a50686 100644 --- a/remoting/host/security_key/gnubby_auth_handler_linux.cc +++ b/remoting/host/security_key/gnubby_auth_handler_linux.cc @@ -6,12 +6,9 @@ #include <stdint.h> #include <unistd.h> -#include <utility> #include "base/bind.h" #include "base/files/file_util.h" -#include "base/json/json_reader.h" -#include "base/json/json_writer.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/stl_util.h" @@ -21,23 +18,9 @@ #include "net/socket/unix_domain_server_socket_posix.h" #include "remoting/base/logging.h" #include "remoting/host/security_key/gnubby_socket.h" -#include "remoting/proto/control.pb.h" -#include "remoting/protocol/client_stub.h" - -namespace remoting { namespace { -const char kConnectionId[] = "connectionId"; -const char kControlMessage[] = "control"; -const char kControlOption[] = "option"; -const char kDataMessage[] = "data"; -const char kDataPayload[] = "data"; -const char kErrorMessage[] = "error"; -const char kGnubbyAuthMessage[] = "gnubby-auth"; -const char kGnubbyAuthV1[] = "auth-v1"; -const char kMessageType[] = "type"; - const int64_t kDefaultRequestTimeoutSeconds = 60; // The name of the socket to listen for gnubby requests on. @@ -59,127 +42,95 @@ unsigned int GetCommandCode(const std::string& data) { return data.empty() ? -1 : static_cast<unsigned int>(data[0]); } -// Creates a string of byte data from a ListValue of numbers. Returns true if -// all of the list elements are numbers. -bool ConvertListValueToString(base::ListValue* bytes, std::string* out) { - out->clear(); - - unsigned int byte_count = bytes->GetSize(); - if (byte_count != 0) { - out->reserve(byte_count); - for (unsigned int i = 0; i < byte_count; i++) { - int value; - if (!bytes->GetInteger(i, &value)) - return false; - out->push_back(static_cast<char>(value)); - } - } - return true; -} - } // namespace -GnubbyAuthHandlerLinux::GnubbyAuthHandlerLinux( - protocol::ClientStub* client_stub) - : client_stub_(client_stub), - last_connection_id_(0), - request_timeout_( - base::TimeDelta::FromSeconds(kDefaultRequestTimeoutSeconds)) { - DCHECK(client_stub_); -} - -GnubbyAuthHandlerLinux::~GnubbyAuthHandlerLinux() { - STLDeleteValues(&active_sockets_); -} +namespace remoting { -// static scoped_ptr<GnubbyAuthHandler> GnubbyAuthHandler::Create( - protocol::ClientStub* client_stub) { - return make_scoped_ptr(new GnubbyAuthHandlerLinux(client_stub)); + const SendMessageCallback& callback) { + scoped_ptr<GnubbyAuthHandler> auth_handler(new GnubbyAuthHandlerLinux()); + auth_handler->SetSendMessageCallback(callback); + return auth_handler; } -// static void GnubbyAuthHandler::SetGnubbySocketName( const base::FilePath& gnubby_socket_name) { g_gnubby_socket_name.Get() = gnubby_socket_name; } -void GnubbyAuthHandlerLinux::DeliverClientMessage(const std::string& message) { - DCHECK(CalledOnValidThread()); - - scoped_ptr<base::Value> value = base::JSONReader::Read(message); - base::DictionaryValue* client_message; - if (value && value->GetAsDictionary(&client_message)) { - std::string type; - if (!client_message->GetString(kMessageType, &type)) { - LOG(ERROR) << "Invalid gnubby-auth message"; - return; - } - - if (type == kControlMessage) { - std::string option; - if (client_message->GetString(kControlOption, &option) && - option == kGnubbyAuthV1) { - CreateAuthorizationSocket(); - } else { - LOG(ERROR) << "Invalid gnubby-auth control option"; - } - } else if (type == kDataMessage) { - ActiveSockets::iterator iter = GetSocketForMessage(client_message); - if (iter != active_sockets_.end()) { - base::ListValue* bytes; - std::string response; - if (client_message->GetList(kDataPayload, &bytes) && - ConvertListValueToString(bytes, &response)) { - HOST_LOG << "Sending gnubby response: " << GetCommandCode(response); - iter->second->SendResponse(response); - } else { - LOG(ERROR) << "Invalid gnubby data"; - SendErrorAndCloseActiveSocket(iter); - } - } else { - LOG(ERROR) << "Unknown gnubby-auth data connection"; - } - } else if (type == kErrorMessage) { - ActiveSockets::iterator iter = GetSocketForMessage(client_message); - if (iter != active_sockets_.end()) { - HOST_LOG << "Sending gnubby error"; - SendErrorAndCloseActiveSocket(iter); - } else { - LOG(ERROR) << "Unknown gnubby-auth error connection"; - } - } else { - LOG(ERROR) << "Unknown gnubby-auth message type: " << type; - } - } +GnubbyAuthHandlerLinux::GnubbyAuthHandlerLinux() + : last_connection_id_(0), + request_timeout_( + base::TimeDelta::FromSeconds(kDefaultRequestTimeoutSeconds)) {} + +GnubbyAuthHandlerLinux::~GnubbyAuthHandlerLinux() { + STLDeleteValues(&active_sockets_); } -void GnubbyAuthHandlerLinux::DeliverHostDataMessage( - int connection_id, - const std::string& data) const { - DCHECK(CalledOnValidThread()); +void GnubbyAuthHandlerLinux::CreateGnubbyConnection() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!g_gnubby_socket_name.Get().empty()); - base::DictionaryValue request; - request.SetString(kMessageType, kDataMessage); - request.SetInteger(kConnectionId, connection_id); + { + // DeleteFile() is a blocking operation, but so is creation of the unix + // socket below. Consider moving this class to a different thread if this + // causes any problems. See crbug.com/509807. + // TODO(joedow): Since this code now runs as a host extension, we should + // perform our IO on a separate thread. + base::ThreadRestrictions::ScopedAllowIO allow_io; - base::ListValue* bytes = new base::ListValue(); - for (std::string::const_iterator i = data.begin(); i != data.end(); ++i) { - bytes->AppendInteger(static_cast<unsigned char>(*i)); + // If the file already exists, a socket in use error is returned. + base::DeleteFile(g_gnubby_socket_name.Get(), false); } - request.Set(kDataPayload, bytes); - std::string request_json; - if (!base::JSONWriter::Write(request, &request_json)) { - LOG(ERROR) << "Failed to create request json"; + HOST_LOG << "Listening for gnubby requests on " + << g_gnubby_socket_name.Get().value(); + + auth_socket_.reset( + new net::UnixDomainServerSocket(base::Bind(MatchUid), false)); + int rv = auth_socket_->BindAndListen(g_gnubby_socket_name.Get().value(), + /*backlog=*/1); + if (rv != net::OK) { + LOG(ERROR) << "Failed to open socket for gnubby requests: '" << rv << "'"; return; } + DoAccept(); +} + +bool GnubbyAuthHandlerLinux::IsValidConnectionId( + int gnubby_connection_id) const { + return GetSocketForConnectionId(gnubby_connection_id) != + active_sockets_.end(); +} + +void GnubbyAuthHandlerLinux::SendClientResponse(int gnubby_connection_id, + const std::string& response) { + ActiveSockets::const_iterator iter = + GetSocketForConnectionId(gnubby_connection_id); + if (iter != active_sockets_.end()) { + iter->second->SendResponse(response); + } else { + LOG(WARNING) << "Unknown gnubby-auth data connection: '" + << gnubby_connection_id << "'"; + } +} - protocol::ExtensionMessage message; - message.set_type(kGnubbyAuthMessage); - message.set_data(request_json); +void GnubbyAuthHandlerLinux::SendErrorAndCloseConnection( + int gnubby_connection_id) { + ActiveSockets::const_iterator iter = + GetSocketForConnectionId(gnubby_connection_id); + if (iter != active_sockets_.end()) { + HOST_LOG << "Sending gnubby error"; + SendErrorAndCloseActiveSocket(iter); + } else { + LOG(WARNING) << "Unknown gnubby-auth data connection: '" + << gnubby_connection_id << "'"; + } +} - client_stub_->DeliverHostMessage(message); +void GnubbyAuthHandlerLinux::SetSendMessageCallback( + const SendMessageCallback& callback) { + send_message_callback_ = callback; } size_t GnubbyAuthHandlerLinux::GetActiveSocketsMapSizeForTest() const { @@ -200,7 +151,7 @@ void GnubbyAuthHandlerLinux::DoAccept() { } void GnubbyAuthHandlerLinux::OnAccepted(int result) { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_NE(net::ERR_IO_PENDING, result); if (result < 0) { @@ -208,93 +159,57 @@ void GnubbyAuthHandlerLinux::OnAccepted(int result) { return; } - int connection_id = ++last_connection_id_; - GnubbySocket* socket = - new GnubbySocket(std::move(accept_socket_), request_timeout_, - base::Bind(&GnubbyAuthHandlerLinux::RequestTimedOut, - base::Unretained(this), connection_id)); - active_sockets_[connection_id] = socket; + int gnubby_connection_id = ++last_connection_id_; + GnubbySocket* socket = new GnubbySocket( + std::move(accept_socket_), request_timeout_, + base::Bind(&GnubbyAuthHandlerLinux::RequestTimedOut, + base::Unretained(this), gnubby_connection_id)); + active_sockets_[gnubby_connection_id] = socket; socket->StartReadingRequest( base::Bind(&GnubbyAuthHandlerLinux::OnReadComplete, - base::Unretained(this), connection_id)); + base::Unretained(this), gnubby_connection_id)); // Continue accepting new connections. DoAccept(); } -void GnubbyAuthHandlerLinux::OnReadComplete(int connection_id) { - DCHECK(CalledOnValidThread()); +void GnubbyAuthHandlerLinux::OnReadComplete(int gnubby_connection_id) { + DCHECK(thread_checker_.CalledOnValidThread()); - ActiveSockets::iterator iter = active_sockets_.find(connection_id); + ActiveSockets::const_iterator iter = + active_sockets_.find(gnubby_connection_id); DCHECK(iter != active_sockets_.end()); std::string request_data; if (!iter->second->GetAndClearRequestData(&request_data)) { SendErrorAndCloseActiveSocket(iter); return; } - ProcessGnubbyRequest(connection_id, request_data); - iter->second->StartReadingRequest( - base::Bind(&GnubbyAuthHandlerLinux::OnReadComplete, - base::Unretained(this), connection_id)); -} - -void GnubbyAuthHandlerLinux::CreateAuthorizationSocket() { - DCHECK(CalledOnValidThread()); - - if (!g_gnubby_socket_name.Get().empty()) { - { - // DeleteFile() is a blocking operation, but so is creation of the unix - // socket below. Consider moving this class to a different thread if this - // causes any problems. See crbug.com/509807 . - base::ThreadRestrictions::ScopedAllowIO allow_io; - - // If the file already exists, a socket in use error is returned. - base::DeleteFile(g_gnubby_socket_name.Get(), false); - } - - HOST_LOG << "Listening for gnubby requests on " - << g_gnubby_socket_name.Get().value(); - - auth_socket_.reset( - new net::UnixDomainServerSocket(base::Bind(MatchUid), false)); - int rv = auth_socket_->BindAndListen(g_gnubby_socket_name.Get().value(), - /*backlog=*/1); - if (rv != net::OK) { - LOG(ERROR) << "Failed to open socket for gnubby requests"; - return; - } - DoAccept(); - } else { - HOST_LOG << "No gnubby socket name specified"; - } -} -void GnubbyAuthHandlerLinux::ProcessGnubbyRequest( - int connection_id, - const std::string& request_data) { HOST_LOG << "Received gnubby request: " << GetCommandCode(request_data); - DeliverHostDataMessage(connection_id, request_data); + send_message_callback_.Run(gnubby_connection_id, request_data); + + iter->second->StartReadingRequest( + base::Bind(&GnubbyAuthHandlerLinux::OnReadComplete, + base::Unretained(this), gnubby_connection_id)); } -GnubbyAuthHandlerLinux::ActiveSockets::iterator -GnubbyAuthHandlerLinux::GetSocketForMessage(base::DictionaryValue* message) { - int connection_id; - if (message->GetInteger(kConnectionId, &connection_id)) { - return active_sockets_.find(connection_id); - } - return active_sockets_.end(); +GnubbyAuthHandlerLinux::ActiveSockets::const_iterator +GnubbyAuthHandlerLinux::GetSocketForConnectionId( + int gnubby_connection_id) const { + return active_sockets_.find(gnubby_connection_id); } void GnubbyAuthHandlerLinux::SendErrorAndCloseActiveSocket( - const ActiveSockets::iterator& iter) { + const ActiveSockets::const_iterator& iter) { iter->second->SendSshError(); delete iter->second; active_sockets_.erase(iter); } -void GnubbyAuthHandlerLinux::RequestTimedOut(int connection_id) { +void GnubbyAuthHandlerLinux::RequestTimedOut(int gnubby_connection_id) { HOST_LOG << "Gnubby request timed out"; - ActiveSockets::iterator iter = active_sockets_.find(connection_id); + ActiveSockets::const_iterator iter = + active_sockets_.find(gnubby_connection_id); if (iter != active_sockets_.end()) SendErrorAndCloseActiveSocket(iter); } diff --git a/remoting/host/security_key/gnubby_auth_handler_linux.h b/remoting/host/security_key/gnubby_auth_handler_linux.h index a3c324e..ff27c20 100644 --- a/remoting/host/security_key/gnubby_auth_handler_linux.h +++ b/remoting/host/security_key/gnubby_auth_handler_linux.h @@ -12,31 +12,22 @@ #include "base/macros.h" #include "base/memory/scoped_ptr.h" -#include "base/threading/non_thread_safe.h" +#include "base/threading/thread_checker.h" #include "net/base/completion_callback.h" #include "net/socket/stream_socket.h" #include "remoting/host/security_key/gnubby_auth_handler.h" -namespace base { -class DictionaryValue; -} // namespace base - namespace net { class UnixDomainServerSocket; } // namespace net namespace remoting { -namespace protocol { -class ClientStub; -} // namespace protocol - class GnubbySocket; -class GnubbyAuthHandlerLinux : public GnubbyAuthHandler, - public base::NonThreadSafe { +class GnubbyAuthHandlerLinux : public GnubbyAuthHandler { public: - explicit GnubbyAuthHandlerLinux(protocol::ClientStub* client_stub); + GnubbyAuthHandlerLinux(); ~GnubbyAuthHandlerLinux() override; size_t GetActiveSocketsMapSizeForTest() const; @@ -47,9 +38,12 @@ class GnubbyAuthHandlerLinux : public GnubbyAuthHandler, typedef std::map<int, GnubbySocket*> ActiveSockets; // GnubbyAuthHandler interface. - void DeliverClientMessage(const std::string& message) override; - void DeliverHostDataMessage(int connection_id, - const std::string& data) const override; + void CreateGnubbyConnection() override; + bool IsValidConnectionId(int gnubby_connection_id) const override; + void SendClientResponse(int gnubby_connection_id, + const std::string& response) override; + void SendErrorAndCloseConnection(int gnubby_connection_id) override; + void SetSendMessageCallback(const SendMessageCallback& callback) override; // Starts listening for connection. void DoAccept(); @@ -58,25 +52,20 @@ class GnubbyAuthHandlerLinux : public GnubbyAuthHandler, void OnAccepted(int result); // Called when a GnubbySocket has done reading. - void OnReadComplete(int connection_id); - - // Create socket for authorization. - void CreateAuthorizationSocket(); + void OnReadComplete(int gnubby_connection_id); - // Process a gnubby request. - void ProcessGnubbyRequest(int connection_id, const std::string& request_data); - - // Gets an active socket iterator for the connection id in |message|. - ActiveSockets::iterator GetSocketForMessage(base::DictionaryValue* message); + // Gets an active socket iterator for |gnubby_connection_id|. + ActiveSockets::const_iterator GetSocketForConnectionId( + int gnubby_connection_id) const; // Send an error and closes an active socket. - void SendErrorAndCloseActiveSocket(const ActiveSockets::iterator& iter); + void SendErrorAndCloseActiveSocket(const ActiveSockets::const_iterator& iter); // A request timed out. - void RequestTimedOut(int connection_id); + void RequestTimedOut(int gnubby_connection_id); - // Interface through which communication with the client occurs. - protocol::ClientStub* client_stub_; + // Ensures GnubbyAuthHandlerLinux methods are called on the same thread. + base::ThreadChecker thread_checker_; // Socket used to listen for authorization requests. scoped_ptr<net::UnixDomainServerSocket> auth_socket_; @@ -84,6 +73,9 @@ class GnubbyAuthHandlerLinux : public GnubbyAuthHandler, // A temporary holder for an accepted connection. scoped_ptr<net::StreamSocket> accept_socket_; + // Used to pass gnubby extension messages to the client. + SendMessageCallback send_message_callback_; + // The last assigned gnubby connection id. int last_connection_id_; diff --git a/remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc b/remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc index 2b73ffc..6a20326 100644 --- a/remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc +++ b/remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc @@ -6,7 +6,6 @@ #include "base/files/file_path.h" #include "base/files/scoped_temp_dir.h" -#include "base/json/json_writer.h" #include "base/macros.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" @@ -20,7 +19,6 @@ #include "remoting/host/security_key/gnubby_auth_handler_linux.h" #include "remoting/host/security_key/gnubby_socket.h" #include "remoting/proto/internal.pb.h" -#include "remoting/protocol/client_stub.h" #include "testing/gtest/include/gtest/gtest.h" namespace remoting { @@ -48,59 +46,37 @@ const unsigned char kRequestData[] = { } // namespace -class TestClientStub : public protocol::ClientStub { +class GnubbyAuthHandlerLinuxTest : public testing::Test { public: - TestClientStub() : loop_(new base::RunLoop) {} - ~TestClientStub() override {} - - // protocol::ClientStub implementation. - void SetCapabilities(const protocol::Capabilities& capabilities) override {} - - void SetPairingResponse( - const protocol::PairingResponse& pairing_response) override {} + GnubbyAuthHandlerLinuxTest() + : run_loop_(new base::RunLoop()), last_connection_id_received_(-1) { + EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); + socket_path_ = temp_dir_.path().Append(kSocketFilename); - void DeliverHostMessage(const protocol::ExtensionMessage& message) override { - message_ = message; - loop_->Quit(); + send_message_callback_ = + base::Bind(&GnubbyAuthHandlerLinuxTest::SendMessageToClient, + base::Unretained(this)); + auth_handler_linux_.reset(new GnubbyAuthHandlerLinux()); + auth_handler_ = auth_handler_linux_.get(); + auth_handler_->SetSendMessageCallback(send_message_callback_); + auth_handler_->SetGnubbySocketName(socket_path_); } - // protocol::ClipboardStub implementation. - void InjectClipboardEvent(const protocol::ClipboardEvent& event) override {} - - // protocol::CursorShapeStub implementation. - void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override {} - - void WaitForDeliverHostMessage() { - loop_->Run(); - loop_.reset(new base::RunLoop); + void WaitForSendMessageToClient() { + run_loop_->Run(); + run_loop_.reset(new base::RunLoop); } - void CheckHostDataMessage(int id, const std::string& data) { - std::string connection_id = base::StringPrintf("\"connectionId\":%d", id); - std::string data_message = base::StringPrintf("\"data\":%s", data.c_str()); - - ASSERT_TRUE(message_.type() == "gnubby-auth" || - message_.type() == "auth-v1"); - ASSERT_NE(message_.data().find("\"type\":\"data\""), std::string::npos); - ASSERT_NE(message_.data().find(connection_id), std::string::npos); - ASSERT_NE(message_.data().find(data_message), std::string::npos); + void SendMessageToClient(int connection_id, const std::string& data) { + last_connection_id_received_ = connection_id; + last_message_received_ = data; + run_loop_->Quit(); } - private: - protocol::ExtensionMessage message_; - scoped_ptr<base::RunLoop> loop_; - - DISALLOW_COPY_AND_ASSIGN(TestClientStub); -}; - -class GnubbyAuthHandlerLinuxTest : public testing::Test { - public: - GnubbyAuthHandlerLinuxTest() { - EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); - socket_path_ = temp_dir_.path().Append(kSocketFilename); - auth_handler_posix_.reset(new GnubbyAuthHandlerLinux(&client_stub_)); - auth_handler_ = auth_handler_posix_.get(); - auth_handler_->SetGnubbySocketName(socket_path_); + void CheckHostDataMessage(int id, const std::string& expected_data) { + ASSERT_EQ(id, last_connection_id_received_); + ASSERT_EQ(expected_data.length(), last_message_received_.length()); + ASSERT_EQ(expected_data, last_message_received_); } void WriteRequestData(net::UnixDomainClientSocket* client_socket) { @@ -125,45 +101,46 @@ class GnubbyAuthHandlerLinuxTest : public testing::Test { ASSERT_EQ(request_len, bytes_written); } - void WaitForAndVerifyHostMessage() { - client_stub_.WaitForDeliverHostMessage(); - base::ListValue expected_data; - // Skip first four bytes. + void WaitForAndVerifyHostMessage(int connection_id) { + WaitForSendMessageToClient(); + std::string expected_data; + expected_data.reserve(sizeof(kRequestData) - 4); + + // Skip first four bytes and build up the response string. for (size_t i = 4; i < sizeof(kRequestData); ++i) { - expected_data.AppendInteger(kRequestData[i]); + expected_data.append(1, static_cast<unsigned char>(kRequestData[i])); } - std::string expected_data_json; - base::JSONWriter::Write(expected_data, &expected_data_json); - client_stub_.CheckHostDataMessage(1, expected_data_json); + CheckHostDataMessage(connection_id, expected_data); } protected: + base::MessageLoopForIO message_loop_; + scoped_ptr<base::RunLoop> run_loop_; + // Object under test. - scoped_ptr<GnubbyAuthHandlerLinux> auth_handler_posix_; + scoped_ptr<GnubbyAuthHandlerLinux> auth_handler_linux_; - // GnubbyAuthHandler interface for |auth_handler_posix_|. + // GnubbyAuthHandler interface for |auth_handler_linux_|. GnubbyAuthHandler* auth_handler_; - base::MessageLoopForIO message_loop_; - TestClientStub client_stub_; + GnubbyAuthHandler::SendMessageCallback send_message_callback_; + + int last_connection_id_received_; + std::string last_message_received_; + base::ScopedTempDir temp_dir_; base::FilePath socket_path_; base::Closure accept_callback_; -}; -TEST_F(GnubbyAuthHandlerLinuxTest, HostDataMessageDelivered) { - auth_handler_->DeliverHostDataMessage(42, "test_msg"); - client_stub_.WaitForDeliverHostMessage(); - // Expects a JSON array of the ASCII character codes for "test_msg". - client_stub_.CheckHostDataMessage(42, "[116,101,115,116,95,109,115,103]"); -} + private: + DISALLOW_COPY_AND_ASSIGN(GnubbyAuthHandlerLinuxTest); +}; TEST_F(GnubbyAuthHandlerLinuxTest, NotClosedAfterRequest) { - ASSERT_EQ(0u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); + ASSERT_EQ(0u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); - const char message_json[] = "{\"type\":\"control\",\"option\":\"auth-v1\"}"; - auth_handler_->DeliverClientMessage(message_json); + auth_handler_->CreateGnubbyConnection(); net::UnixDomainClientSocket client_socket(socket_path_.value(), false); net::TestCompletionCallback connect_callback; @@ -173,17 +150,19 @@ TEST_F(GnubbyAuthHandlerLinuxTest, NotClosedAfterRequest) { // Write the request and verify the response. WriteRequestData(&client_socket); - WaitForAndVerifyHostMessage(); + WaitForAndVerifyHostMessage(1); + + // Verify the connection is now valid. + ASSERT_TRUE(auth_handler_->IsValidConnectionId(1)); // Verify that completing a request/response cycle didn't close the socket. - ASSERT_EQ(1u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); + ASSERT_EQ(1u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); } TEST_F(GnubbyAuthHandlerLinuxTest, HandleTwoRequests) { - ASSERT_EQ(0u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); + ASSERT_EQ(0u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); - const char message_json[] = "{\"type\":\"control\",\"option\":\"auth-v1\"}"; - auth_handler_->DeliverClientMessage(message_json); + auth_handler_->CreateGnubbyConnection(); net::UnixDomainClientSocket client_socket(socket_path_.value(), false); net::TestCompletionCallback connect_callback; @@ -193,42 +172,81 @@ TEST_F(GnubbyAuthHandlerLinuxTest, HandleTwoRequests) { // Write the request and verify the response. WriteRequestData(&client_socket); - WaitForAndVerifyHostMessage(); + WaitForAndVerifyHostMessage(1); + + // Verify the connection is now valid. + ASSERT_TRUE(auth_handler_->IsValidConnectionId(1)); // Repeat the request/response cycle. WriteRequestData(&client_socket); - WaitForAndVerifyHostMessage(); + WaitForAndVerifyHostMessage(1); + + // Verify the connection is still valid. + ASSERT_TRUE(auth_handler_->IsValidConnectionId(1)); + + // Verify that completing two request/response cycles didn't close the + // socket. + ASSERT_EQ(1u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); +} + +TEST_F(GnubbyAuthHandlerLinuxTest, HandleTwoIndependentRequests) { + ASSERT_EQ(0u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); + + auth_handler_->CreateGnubbyConnection(); + + net::UnixDomainClientSocket client_socket(socket_path_.value(), false); + net::TestCompletionCallback connect_callback; + + int rv = client_socket.Connect(connect_callback.callback()); + ASSERT_EQ(net::OK, connect_callback.GetResult(rv)); + + // Write the request and verify the response. + WriteRequestData(&client_socket); + WaitForAndVerifyHostMessage(1); + + // Verify the first connection is now valid. + ASSERT_TRUE(auth_handler_->IsValidConnectionId(1)); + + // Disconnect and establish a new connection. + client_socket.Disconnect(); + rv = client_socket.Connect(connect_callback.callback()); + ASSERT_EQ(net::OK, connect_callback.GetResult(rv)); + + // Repeat the request/response cycle. + WriteRequestData(&client_socket); + WaitForAndVerifyHostMessage(2); + + // Verify the second connection is valid and the first is not. + ASSERT_TRUE(auth_handler_->IsValidConnectionId(2)); + ASSERT_FALSE(auth_handler_->IsValidConnectionId(1)); - // Verify that completing two request/response cycles didn't close the socket. - ASSERT_EQ(1u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); + // Verify that the initial socket was released properly. + ASSERT_EQ(1u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); } TEST_F(GnubbyAuthHandlerLinuxTest, DidReadTimeout) { - std::string message_json = "{\"type\":\"control\",\"option\":\"auth-v1\"}"; + ASSERT_EQ(0u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); + auth_handler_->CreateGnubbyConnection(); - ASSERT_EQ(0u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); - auth_handler_->DeliverClientMessage(message_json); net::UnixDomainClientSocket client_socket(socket_path_.value(), false); net::TestCompletionCallback connect_callback; int rv = client_socket.Connect(connect_callback.callback()); ASSERT_EQ(net::OK, connect_callback.GetResult(rv)); - auth_handler_posix_->SetRequestTimeoutForTest(base::TimeDelta()); - ASSERT_EQ(0u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); + auth_handler_linux_->SetRequestTimeoutForTest(base::TimeDelta()); + ASSERT_EQ(0u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); } TEST_F(GnubbyAuthHandlerLinuxTest, ClientErrorMessageDelivered) { - std::string message_json = "{\"type\":\"control\",\"option\":\"auth-v1\"}"; + ASSERT_EQ(0u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); + auth_handler_->CreateGnubbyConnection(); - ASSERT_EQ(0u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); - auth_handler_->DeliverClientMessage(message_json); net::UnixDomainClientSocket client_socket(socket_path_.value(), false); net::TestCompletionCallback connect_callback; int rv = client_socket.Connect(connect_callback.callback()); ASSERT_EQ(net::OK, connect_callback.GetResult(rv)); - std::string error_json = "{\"type\":\"error\",\"connectionId\":1}"; - auth_handler_->DeliverClientMessage(error_json); - ASSERT_EQ(0u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); + auth_handler_->SendErrorAndCloseConnection(1); + ASSERT_EQ(0u, auth_handler_linux_->GetActiveSocketsMapSizeForTest()); } } // namespace remoting diff --git a/remoting/host/security_key/gnubby_auth_handler_mac.cc b/remoting/host/security_key/gnubby_auth_handler_mac.cc index b76d77c..74e44aa 100644 --- a/remoting/host/security_key/gnubby_auth_handler_mac.cc +++ b/remoting/host/security_key/gnubby_auth_handler_mac.cc @@ -5,44 +5,17 @@ #include "remoting/host/security_key/gnubby_auth_handler.h" #include "base/logging.h" -#include "base/macros.h" namespace remoting { -namespace { - -class GnubbyAuthHandlerMac : public GnubbyAuthHandler { - private: - // GnubbyAuthHandler interface. - void DeliverClientMessage(const std::string& message) override; - void DeliverHostDataMessage(int connection_id, - const std::string& data) const override; - - DISALLOW_COPY_AND_ASSIGN(GnubbyAuthHandlerMac); -}; - -} // namespace - -// static scoped_ptr<GnubbyAuthHandler> GnubbyAuthHandler::Create( - protocol::ClientStub* client_stub) { + const SendMessageCallback& callback) { return nullptr; } -// static void GnubbyAuthHandler::SetGnubbySocketName( const base::FilePath& gnubby_socket_name) { NOTIMPLEMENTED(); } -void GnubbyAuthHandlerMac::DeliverClientMessage(const std::string& message) { - NOTIMPLEMENTED(); -} - -void GnubbyAuthHandlerMac::DeliverHostDataMessage( - int connection_id, - const std::string& data) const { - NOTIMPLEMENTED(); -} - } // namespace remoting diff --git a/remoting/host/security_key/gnubby_auth_handler_win.cc b/remoting/host/security_key/gnubby_auth_handler_win.cc index 83351ab..886b899 100644 --- a/remoting/host/security_key/gnubby_auth_handler_win.cc +++ b/remoting/host/security_key/gnubby_auth_handler_win.cc @@ -5,44 +5,17 @@ #include "remoting/host/security_key/gnubby_auth_handler.h" #include "base/logging.h" -#include "base/macros.h" namespace remoting { -namespace { - -class GnubbyAuthHandlerWin : public GnubbyAuthHandler { - private: - // GnubbyAuthHandler interface. - void DeliverClientMessage(const std::string& message) override; - void DeliverHostDataMessage(int connection_id, - const std::string& data) const override; - - DISALLOW_COPY_AND_ASSIGN(GnubbyAuthHandlerWin); -}; - -} // namespace - -// static scoped_ptr<GnubbyAuthHandler> GnubbyAuthHandler::Create( - protocol::ClientStub* client_stub) { + const GnubbyAuthHandler::SendMessageCallback& callback) { return nullptr; } -// static void GnubbyAuthHandler::SetGnubbySocketName( const base::FilePath& gnubby_socket_name) { NOTIMPLEMENTED(); } -void GnubbyAuthHandlerWin::DeliverClientMessage(const std::string& message) { - NOTIMPLEMENTED(); -} - -void GnubbyAuthHandlerWin::DeliverHostDataMessage( - int connection_id, - const std::string& data) const { - NOTIMPLEMENTED(); -} - } // namespace remoting diff --git a/remoting/host/security_key/gnubby_extension.cc b/remoting/host/security_key/gnubby_extension.cc new file mode 100644 index 0000000..d225f31 --- /dev/null +++ b/remoting/host/security_key/gnubby_extension.cc @@ -0,0 +1,34 @@ +// Copyright 2016 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 "remoting/host/security_key/gnubby_extension.h" + +#include "remoting/host/security_key/gnubby_extension_session.h" + +namespace { +// TODO(joedow): Update this once clients support sending a gnubby capabililty. +// Tracked via: crbug.com/585835 +const char kCapability[] = ""; +} + +namespace remoting { + +GnubbyExtension::GnubbyExtension() {} + +GnubbyExtension::~GnubbyExtension() {} + +std::string GnubbyExtension::capability() const { + return kCapability; +} + +scoped_ptr<HostExtensionSession> GnubbyExtension::CreateExtensionSession( + ClientSessionControl* client_session_control, + protocol::ClientStub* client_stub) { + // TODO(joedow): Update this mechanism to allow for multiple sessions. The + // extension will only send messages through the initial + // |client_stub| with the current design. + return make_scoped_ptr(new GnubbyExtensionSession(client_stub)); +} + +} // namespace remoting diff --git a/remoting/host/security_key/gnubby_extension.h b/remoting/host/security_key/gnubby_extension.h new file mode 100644 index 0000000..7cad095 --- /dev/null +++ b/remoting/host/security_key/gnubby_extension.h @@ -0,0 +1,34 @@ +// Copyright 2016 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 REMOTING_HOST_SECURITY_KEY_GNUBBY_EXTENSION_H_ +#define REMOTING_HOST_SECURITY_KEY_GNUBBY_EXTENSION_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "remoting/host/host_extension.h" + +namespace remoting { + +// GnubbyExtension extends HostExtension to enable Security Key support. +class GnubbyExtension : public HostExtension { + public: + GnubbyExtension(); + ~GnubbyExtension() override; + + // HostExtension interface. + std::string capability() const override; + scoped_ptr<HostExtensionSession> CreateExtensionSession( + ClientSessionControl* client_session_control, + protocol::ClientStub* client_stub) override; + + private: + DISALLOW_COPY_AND_ASSIGN(GnubbyExtension); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SECURITY_KEY_GNUBBY_EXTENSION_H_ diff --git a/remoting/host/security_key/gnubby_extension_session.cc b/remoting/host/security_key/gnubby_extension_session.cc new file mode 100644 index 0000000..d53338c --- /dev/null +++ b/remoting/host/security_key/gnubby_extension_session.cc @@ -0,0 +1,207 @@ +// Copyright 2016 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 "remoting/host/security_key/gnubby_extension_session.h" + +#include "base/bind.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/values.h" +#include "remoting/base/logging.h" +#include "remoting/host/security_key/gnubby_auth_handler.h" +#include "remoting/proto/control.pb.h" +#include "remoting/protocol/client_stub.h" + +namespace { + +// Used as the type attribute of all Security Key protocol::ExtensionMessages. +const char kExtensionMessageType[] = "gnubby-auth"; + +// Gnubby extension message data members. +const char kConnectionId[] = "connectionId"; +const char kControlMessage[] = "control"; +const char kControlOption[] = "option"; +const char kDataMessage[] = "data"; +const char kDataPayload[] = "data"; +const char kErrorMessage[] = "error"; +const char kGnubbyAuthV1[] = "auth-v1"; +const char kMessageType[] = "type"; + +// Returns the command code (the first byte of the data) if it exists, or -1 if +// the data is empty. +unsigned int GetCommandCode(const std::string& data) { + return data.empty() ? -1 : static_cast<unsigned int>(data[0]); +} + +// Creates a string of byte data from a ListValue of numbers. Returns true if +// all of the list elements are numbers. +bool ConvertListValueToString(base::ListValue* bytes, std::string* out) { + out->clear(); + + unsigned int byte_count = bytes->GetSize(); + if (byte_count != 0) { + out->reserve(byte_count); + for (unsigned int i = 0; i < byte_count; i++) { + int value; + if (!bytes->GetInteger(i, &value)) { + return false; + } + out->push_back(static_cast<char>(value)); + } + } + return true; +} + +} // namespace + +namespace remoting { + +GnubbyExtensionSession::GnubbyExtensionSession( + protocol::ClientStub* client_stub) + : client_stub_(client_stub) { + DCHECK(client_stub_); + + gnubby_auth_handler_ = remoting::GnubbyAuthHandler::Create(base::Bind( + &GnubbyExtensionSession::SendMessageToClient, base::Unretained(this))); +} + +GnubbyExtensionSession::~GnubbyExtensionSession() {} + +// Returns true if the |message| is a Security Key ExtensionMessage. +// This is done so the host does not pass |message| to other HostExtensions. +// TODO(joedow): Use |client_session_control| to disconnect the session if we +// receive an invalid extension message. +bool GnubbyExtensionSession::OnExtensionMessage( + ClientSessionControl* client_session_control, + protocol::ClientStub* client_stub, + const protocol::ExtensionMessage& message) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (message.type() != kExtensionMessageType) { + return false; + } + + scoped_ptr<base::Value> value = base::JSONReader::Read(message.data()); + base::DictionaryValue* client_message; + if (!value || !value->GetAsDictionary(&client_message)) { + LOG(WARNING) << "Failed to retrieve data from gnubby-auth message."; + return true; + } + + std::string type; + if (!client_message->GetString(kMessageType, &type)) { + LOG(WARNING) << "Invalid gnubby-auth message format."; + return true; + } + + if (type == kControlMessage) { + ProcessControlMessage(client_message); + } else if (type == kDataMessage) { + ProcessDataMessage(client_message); + } else if (type == kErrorMessage) { + ProcessErrorMessage(client_message); + } else { + VLOG(2) << "Unknown gnubby-auth message type: " << type; + } + + return true; +} + +void GnubbyExtensionSession::ProcessControlMessage( + base::DictionaryValue* message_data) const { + std::string option; + if (!message_data->GetString(kControlOption, &option)) { + LOG(WARNING) << "Could not extract control option from message."; + return; + } + + if (option == kGnubbyAuthV1) { + gnubby_auth_handler_->CreateGnubbyConnection(); + } else { + VLOG(2) << "Invalid gnubby-auth control option: " << option; + } +} + +void GnubbyExtensionSession::ProcessDataMessage( + base::DictionaryValue* message_data) const { + int connection_id; + if (!message_data->GetInteger(kConnectionId, &connection_id)) { + LOG(WARNING) << "Could not extract connection id from message."; + return; + } + + if (!gnubby_auth_handler_->IsValidConnectionId(connection_id)) { + LOG(WARNING) << "Unknown gnubby-auth data connection: '" << connection_id + << "'"; + return; + } + + base::ListValue* bytes; + std::string response; + if (message_data->GetList(kDataPayload, &bytes) && + ConvertListValueToString(bytes, &response)) { + HOST_LOG << "Sending gnubby response: " << GetCommandCode(response); + gnubby_auth_handler_->SendClientResponse(connection_id, response); + } else { + LOG(WARNING) << "Could not extract response data from message."; + gnubby_auth_handler_->SendErrorAndCloseConnection(connection_id); + return; + } +} + +void GnubbyExtensionSession::ProcessErrorMessage( + base::DictionaryValue* message_data) const { + int connection_id; + if (!message_data->GetInteger(kConnectionId, &connection_id)) { + LOG(WARNING) << "Could not extract connection id from message."; + return; + } + + if (gnubby_auth_handler_->IsValidConnectionId(connection_id)) { + HOST_LOG << "Sending gnubby error"; + gnubby_auth_handler_->SendErrorAndCloseConnection(connection_id); + } else { + LOG(WARNING) << "Unknown gnubby-auth data connection: '" << connection_id + << "'"; + } +} + +void GnubbyExtensionSession::SendMessageToClient( + int connection_id, + const std::string& data) const { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(client_stub_); + + base::DictionaryValue request; + request.SetString(kMessageType, kDataMessage); + request.SetInteger(kConnectionId, connection_id); + + scoped_ptr<base::ListValue> bytes(new base::ListValue()); + for (std::string::const_iterator i = data.begin(); i != data.end(); ++i) { + bytes->AppendInteger(static_cast<unsigned char>(*i)); + } + request.Set(kDataPayload, bytes.release()); + + std::string request_json; + CHECK(base::JSONWriter::Write(request, &request_json)); + + protocol::ExtensionMessage message; + message.set_type(kExtensionMessageType); + message.set_data(request_json); + + client_stub_->DeliverHostMessage(message); +} + +void GnubbyExtensionSession::SetGnubbyAuthHandlerForTesting( + scoped_ptr<GnubbyAuthHandler> gnubby_auth_handler) { + DCHECK(gnubby_auth_handler); + + gnubby_auth_handler_ = std::move(gnubby_auth_handler); + gnubby_auth_handler_->SetSendMessageCallback(base::Bind( + &GnubbyExtensionSession::SendMessageToClient, base::Unretained(this))); +} + +} // namespace remoting diff --git a/remoting/host/security_key/gnubby_extension_session.h b/remoting/host/security_key/gnubby_extension_session.h new file mode 100644 index 0000000..82b6703 --- /dev/null +++ b/remoting/host/security_key/gnubby_extension_session.h @@ -0,0 +1,65 @@ +// Copyright 2016 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 REMOTING_HOST_SECURITY_KEY_GNUBBY_EXTENSION_SESSION_H_ +#define REMOTING_HOST_SECURITY_KEY_GNUBBY_EXTENSION_SESSION_H_ + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "remoting/host/host_extension_session.h" + +namespace base { +class DictionaryValue; +} + +namespace remoting { + +class GnubbyAuthHandler; + +namespace protocol { +class ClientStub; +} + +// A HostExtensionSession implementation that enables Security Key support. +class GnubbyExtensionSession : public HostExtensionSession { + public: + explicit GnubbyExtensionSession(protocol::ClientStub* client_stub); + ~GnubbyExtensionSession() override; + + // HostExtensionSession interface. + bool OnExtensionMessage(ClientSessionControl* client_session_control, + protocol::ClientStub* client_stub, + const protocol::ExtensionMessage& message) override; + + // Allows the underlying GnubbyAuthHandler to be overridden for unit testing. + void SetGnubbyAuthHandlerForTesting( + scoped_ptr<GnubbyAuthHandler> gnubby_auth_handler); + + private: + // These methods process specific gnubby extension message types. + void ProcessControlMessage(base::DictionaryValue* message_data) const; + void ProcessDataMessage(base::DictionaryValue* message_data) const; + void ProcessErrorMessage(base::DictionaryValue* message_data) const; + + void SendMessageToClient(int connection_id, const std::string& data) const; + + // Ensures GnubbyExtensionSession methods are called on the same thread. + base::ThreadChecker thread_checker_; + + // Interface through which messages can be sent to the client. + protocol::ClientStub* client_stub_ = nullptr; + + // Handles platform specific gnubby operations. + scoped_ptr<GnubbyAuthHandler> gnubby_auth_handler_; + + DISALLOW_COPY_AND_ASSIGN(GnubbyExtensionSession); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SECURITY_KEY_GNUBBY_EXTENSION_SESSION_H_ diff --git a/remoting/host/security_key/gnubby_extension_session_unittest.cc b/remoting/host/security_key/gnubby_extension_session_unittest.cc new file mode 100644 index 0000000..f763670 --- /dev/null +++ b/remoting/host/security_key/gnubby_extension_session_unittest.cc @@ -0,0 +1,377 @@ +// Copyright 2016 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 <stddef.h> + +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/json/json_writer.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/thread_task_runner_handle.h" +#include "base/timer/mock_timer.h" +#include "base/values.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/socket/unix_domain_client_socket_posix.h" +#include "remoting/host/host_mock_objects.h" +#include "remoting/host/security_key/gnubby_auth_handler.h" +#include "remoting/host/security_key/gnubby_extension_session.h" +#include "remoting/proto/internal.pb.h" +#include "remoting/protocol/client_stub.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +namespace { + +// Test gnubby request data. +const unsigned char kRequestData[] = { + 0x00, 0x00, 0x00, 0x9a, 0x65, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x60, 0x90, + 0x24, 0x71, 0xf8, 0xf2, 0xe5, 0xdf, 0x7f, 0x81, 0xc7, 0x49, 0xc4, 0xa3, + 0x58, 0x5c, 0xf6, 0xcc, 0x40, 0x14, 0x28, 0x0c, 0xa0, 0xfa, 0x03, 0x18, + 0x38, 0xd8, 0x7d, 0x77, 0x2b, 0x3a, 0x00, 0x00, 0x00, 0x20, 0x64, 0x46, + 0x47, 0x2f, 0xdf, 0x6e, 0xed, 0x7b, 0xf3, 0xc3, 0x37, 0x20, 0xf2, 0x36, + 0x67, 0x6c, 0x36, 0xe1, 0xb4, 0x5e, 0xbe, 0x04, 0x85, 0xdb, 0x89, 0xa3, + 0xcd, 0xfd, 0xd2, 0x4b, 0xd6, 0x9f, 0x00, 0x00, 0x00, 0x40, 0x38, 0x35, + 0x05, 0x75, 0x1d, 0x13, 0x6e, 0xb3, 0x6b, 0x1d, 0x29, 0xae, 0xd3, 0x43, + 0xe6, 0x84, 0x8f, 0xa3, 0x9d, 0x65, 0x4e, 0x2f, 0x57, 0xe3, 0xf6, 0xe6, + 0x20, 0x3c, 0x00, 0xc6, 0xe1, 0x73, 0x34, 0xe2, 0x23, 0x99, 0xc4, 0xfa, + 0x91, 0xc2, 0xd5, 0x97, 0xc1, 0x8b, 0xd0, 0x3c, 0x13, 0xba, 0xf0, 0xd7, + 0x5e, 0xa3, 0xbc, 0x02, 0x5b, 0xec, 0xe4, 0x4b, 0xae, 0x0e, 0xf2, 0xbd, + 0xc8, 0xaa}; + +} // namespace + +class TestClientStub : public protocol::ClientStub { + public: + TestClientStub() : run_loop_(new base::RunLoop) {} + ~TestClientStub() override {} + + // protocol::ClientStub implementation. + void SetCapabilities(const protocol::Capabilities& capabilities) override {} + + void SetPairingResponse( + const protocol::PairingResponse& pairing_response) override {} + + void DeliverHostMessage(const protocol::ExtensionMessage& message) override { + message_ = message; + run_loop_->Quit(); + } + + // protocol::ClipboardStub implementation. + void InjectClipboardEvent(const protocol::ClipboardEvent& event) override {} + + // protocol::CursorShapeStub implementation. + void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override {} + + void WaitForDeliverHostMessage(const base::TimeDelta& max_timeout) { + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop_->QuitClosure(), max_timeout); + run_loop_->Run(); + run_loop_.reset(new base::RunLoop); + } + + void CheckHostDataMessage(int id, const std::string& data) { + std::string connection_id = base::StringPrintf("\"connectionId\":%d", id); + std::string data_message = base::StringPrintf("\"data\":%s", data.c_str()); + + ASSERT_TRUE(message_.type() == "gnubby-auth" || + message_.type() == "auth-v1"); + ASSERT_NE(message_.data().find("\"type\":\"data\""), std::string::npos); + ASSERT_NE(message_.data().find(connection_id), std::string::npos); + ASSERT_NE(message_.data().find(data_message), std::string::npos); + } + + private: + protocol::ExtensionMessage message_; + scoped_ptr<base::RunLoop> run_loop_; + + DISALLOW_COPY_AND_ASSIGN(TestClientStub); +}; + +class GnubbyExtensionSessionTest : public testing::Test { + public: + GnubbyExtensionSessionTest() + : gnubby_extension_session_(new GnubbyExtensionSession(&client_stub_)) { + // We want to retain ownership of mock object so we can use it to inject + // events into the extension session. The mock object should not be used + // once |gnubby_extension_session_| is destroyed. + mock_gnubby_auth_handler_ = new MockGnubbyAuthHandler(); + gnubby_extension_session_->SetGnubbyAuthHandlerForTesting( + make_scoped_ptr(mock_gnubby_auth_handler_)); + } + + void WaitForAndVerifyHostMessage() { + client_stub_.WaitForDeliverHostMessage( + base::TimeDelta::FromMilliseconds(500)); + base::ListValue expected_data; + + // Skip first four bytes. + for (size_t i = 4; i < sizeof(kRequestData); ++i) { + expected_data.AppendInteger(kRequestData[i]); + } + + std::string expected_data_json; + base::JSONWriter::Write(expected_data, &expected_data_json); + client_stub_.CheckHostDataMessage(1, expected_data_json); + } + + void CreateGnubbyConnection() { + EXPECT_CALL(*mock_gnubby_auth_handler_, CreateGnubbyConnection()).Times(1); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"control\",\"option\":\"auth-v1\"}"); + + ASSERT_TRUE(gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, + message)); + } + + protected: + base::MessageLoopForIO message_loop_; + + // Object under test. + scoped_ptr<GnubbyExtensionSession> gnubby_extension_session_; + + MockGnubbyAuthHandler* mock_gnubby_auth_handler_ = nullptr; + + TestClientStub client_stub_; + + private: + DISALLOW_COPY_AND_ASSIGN(GnubbyExtensionSessionTest); +}; + +TEST_F(GnubbyExtensionSessionTest, GnubbyConnectionCreated_ValidMessage) { + CreateGnubbyConnection(); +} + +TEST_F(GnubbyExtensionSessionTest, NoGnubbyConnectionCreated_WrongMessageType) { + EXPECT_CALL(*mock_gnubby_auth_handler_, CreateGnubbyConnection()).Times(0); + + protocol::ExtensionMessage message; + message.set_type("unsupported-gnubby-auth"); + message.set_data("{\"type\":\"control\",\"option\":\"auth-v1\"}"); + + ASSERT_FALSE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, + NoGnubbyConnectionCreated_InvalidMessageData) { + EXPECT_CALL(*mock_gnubby_auth_handler_, CreateGnubbyConnection()).Times(0); + + // First try invalid JSON. + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"control\",\"option\":}"); + // handled should still be true, even if the message payload is invalid. + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); + + // Now try an invalid message type. + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"control\",\"option\":\"auth-v0\"}"); + // handled should still be true, even if the message payload is invalid. + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); + + // Now try a message that is missing the option and auth type. + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"control\"}"); + // handled should still be true, even if the message payload is invalid. + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, DataMessageProcessing_MissingConnectionId) { + CreateGnubbyConnection(); + + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendClientResponse(testing::_, testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendErrorAndCloseConnection(testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(testing::_)) + .Times(0); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"data\"}"); + + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, DataMessageProcessing_InvalidConnectionId) { + CreateGnubbyConnection(); + + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendClientResponse(testing::_, testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendErrorAndCloseConnection(testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(1)).Times(1); + + ON_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(testing::_)) + .WillByDefault(testing::Return(false)); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"data\",\"connectionId\":1}"); + + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, DataMessageProcessing_MissingPayload) { + CreateGnubbyConnection(); + + EXPECT_CALL(*mock_gnubby_auth_handler_, SendErrorAndCloseConnection(1)) + .Times(1); + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendClientResponse(testing::_, testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(1)).Times(1); + + ON_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(testing::_)) + .WillByDefault(testing::Return(true)); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"data\",\"connectionId\":1}"); + + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, DataMessageProcessing_InvalidPayload) { + CreateGnubbyConnection(); + + EXPECT_CALL(*mock_gnubby_auth_handler_, SendErrorAndCloseConnection(1)) + .Times(1); + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendClientResponse(testing::_, testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(1)).Times(1); + + ON_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(testing::_)) + .WillByDefault(testing::Return(true)); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data( + "{\"type\":\"data\",\"connectionId\":1,\"data\":[\"a\",\"-\",\"z\"]}"); + + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, DataMessageProcessing_ValidData) { + CreateGnubbyConnection(); + + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendClientResponse(1, "\x1\x2\x3\x4\x5")) + .Times(1); + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendErrorAndCloseConnection(testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(1)).Times(1); + + ON_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(testing::_)) + .WillByDefault(testing::Return(true)); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data( + "{\"type\":\"data\",\"connectionId\":1,\"data\":[1,2,3,4,5]}"); + + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, ErrorMessageProcessing_MissingConnectionId) { + CreateGnubbyConnection(); + + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendErrorAndCloseConnection(testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendClientResponse(testing::_, testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(testing::_)) + .Times(0); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"error\"}"); + + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, ErrorMessageProcessing_InvalidConnectionId) { + CreateGnubbyConnection(); + + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendErrorAndCloseConnection(testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendClientResponse(testing::_, testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(1)).Times(1); + + ON_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(testing::_)) + .WillByDefault(testing::Return(false)); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"error\",\"connectionId\":1}"); + + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, ErrorMessageProcessing_ValidData) { + CreateGnubbyConnection(); + + EXPECT_CALL(*mock_gnubby_auth_handler_, SendErrorAndCloseConnection(1)) + .Times(1); + EXPECT_CALL(*mock_gnubby_auth_handler_, + SendClientResponse(testing::_, testing::_)) + .Times(0); + EXPECT_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(1)).Times(1); + + ON_CALL(*mock_gnubby_auth_handler_, IsValidConnectionId(testing::_)) + .WillByDefault(testing::Return(true)); + + protocol::ExtensionMessage message; + message.set_type("gnubby-auth"); + message.set_data("{\"type\":\"error\",\"connectionId\":1}"); + + ASSERT_TRUE( + gnubby_extension_session_->OnExtensionMessage(nullptr, nullptr, message)); +} + +TEST_F(GnubbyExtensionSessionTest, SendMessageToClient_ValidData) { + CreateGnubbyConnection(); + + // Inject data into the SendMessageCallback to simulate a gnubby request. + mock_gnubby_auth_handler_->GetSendMessageCallback().Run(42, "test_msg"); + + client_stub_.WaitForDeliverHostMessage( + base::TimeDelta::FromMilliseconds(500)); + + // Expects a JSON array of the ASCII character codes for "test_msg". + client_stub_.CheckHostDataMessage(42, "[116,101,115,116,95,109,115,103]"); +} + +} // namespace remoting diff --git a/remoting/host/security_key/gnubby_socket.cc b/remoting/host/security_key/gnubby_socket.cc index cc54a22..0a24688 100644 --- a/remoting/host/security_key/gnubby_socket.cc +++ b/remoting/host/security_key/gnubby_socket.cc @@ -39,7 +39,7 @@ GnubbySocket::GnubbySocket(scoped_ptr<net::StreamSocket> socket, GnubbySocket::~GnubbySocket() {} bool GnubbySocket::GetAndClearRequestData(std::string* data_out) { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(read_completed_); if (!read_completed_) @@ -54,7 +54,7 @@ bool GnubbySocket::GetAndClearRequestData(std::string* data_out) { } void GnubbySocket::SendResponse(const std::string& response_data) { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!write_buffer_); std::string response_length_string = GetResponseLengthAsBytes(response_data); @@ -67,14 +67,14 @@ void GnubbySocket::SendResponse(const std::string& response_data) { } void GnubbySocket::SendSshError() { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); SendResponse(std::string(kSshError, arraysize(kSshError))); } void GnubbySocket::StartReadingRequest( const base::Closure& request_received_callback) { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(request_received_callback_.is_null()); request_received_callback_ = request_received_callback; @@ -82,7 +82,7 @@ void GnubbySocket::StartReadingRequest( } void GnubbySocket::OnDataWritten(int result) { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(write_buffer_); if (result < 0) { @@ -95,7 +95,7 @@ void GnubbySocket::OnDataWritten(int result) { } void GnubbySocket::DoWrite() { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(write_buffer_); if (!write_buffer_->BytesRemaining()) { @@ -110,7 +110,7 @@ void GnubbySocket::DoWrite() { } void GnubbySocket::OnDataRead(int result) { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); if (result <= 0) { if (result < 0) @@ -133,7 +133,7 @@ void GnubbySocket::OnDataRead(int result) { } void GnubbySocket::DoRead() { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); int result = socket_->Read( read_buffer_.get(), kRequestReadBufferLength, @@ -143,7 +143,7 @@ void GnubbySocket::DoRead() { } bool GnubbySocket::IsRequestComplete() const { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); if (request_data_.size() < kRequestSizeBytes) return false; @@ -151,7 +151,7 @@ bool GnubbySocket::IsRequestComplete() const { } bool GnubbySocket::IsRequestTooLarge() const { - DCHECK(CalledOnValidThread()); + DCHECK(thread_checker_.CalledOnValidThread()); if (request_data_.size() < kRequestSizeBytes) return false; diff --git a/remoting/host/security_key/gnubby_socket.h b/remoting/host/security_key/gnubby_socket.h index e37f64f..39d5d05 100644 --- a/remoting/host/security_key/gnubby_socket.h +++ b/remoting/host/security_key/gnubby_socket.h @@ -14,7 +14,7 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "base/threading/non_thread_safe.h" +#include "base/threading/thread_checker.h" namespace base { class Timer; @@ -31,7 +31,7 @@ namespace remoting { // Class that manages reading requests and sending responses. The socket can // only handle receiving one request at a time. It expects to receive no extra // bytes over the wire, which is checked by IsRequestTooLarge method. -class GnubbySocket : public base::NonThreadSafe { +class GnubbySocket { public: GnubbySocket(scoped_ptr<net::StreamSocket> socket, const base::TimeDelta& timeout, @@ -82,6 +82,9 @@ class GnubbySocket : public base::NonThreadSafe { // Resets the socket activity timer. void ResetTimer(); + // Ensures GnubbySocket methods are called on the same thread. + base::ThreadChecker thread_checker_; + // The socket. scoped_ptr<net::StreamSocket> socket_; |