diff options
author | joedow <joedow@chromium.org> | 2016-02-08 13:18:13 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-08 21:19:16 +0000 |
commit | 91151047466065d41b4e1a8ee8cae7653dbcceb5 (patch) | |
tree | fdf245aa5c864a59667f92a29e24da596e4f8ec1 /remoting/host/security_key | |
parent | 77e1d01e1d15ebe163796d7742f177df24b1f089 (diff) | |
download | chromium_src-91151047466065d41b4e1a8ee8cae7653dbcceb5.zip chromium_src-91151047466065d41b4e1a8ee8cae7653dbcceb5.tar.gz chromium_src-91151047466065d41b4e1a8ee8cae7653dbcceb5.tar.bz2 |
Moving Gnubby* files to a new directory.
This CL moves the existing Gnubby files into a new security_key
directory. Since I will be refactoring this and expanding the number of
files, I thought it would be better to keep them orgainzed in a
sub-directory vs. adding all of th files to the host root directory.
Also needed to update the Presubmit file for the AllowScopedIO
exception.
BUG=584463
Review URL: https://codereview.chromium.org/1668393002
Cr-Commit-Position: refs/heads/master@{#374177}
Diffstat (limited to 'remoting/host/security_key')
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler.h | 45 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_linux.cc | 302 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_linux.h | 101 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc | 234 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_mac.cc | 48 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_auth_handler_win.cc | 48 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_socket.cc | 188 | ||||
-rw-r--r-- | remoting/host/security_key/gnubby_socket.h | 110 |
8 files changed, 1076 insertions, 0 deletions
diff --git a/remoting/host/security_key/gnubby_auth_handler.h b/remoting/host/security_key/gnubby_auth_handler.h new file mode 100644 index 0000000..606742b --- /dev/null +++ b/remoting/host/security_key/gnubby_auth_handler.h @@ -0,0 +1,45 @@ +// 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_AUTH_HANDLER_H_ +#define REMOTING_HOST_SECURITY_KEY_GNUBBY_AUTH_HANDLER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" + +namespace base { +class FilePath; +} // namespace base + +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() {} + + // Creates a platform-specific GnubbyAuthHandler. + static scoped_ptr<GnubbyAuthHandler> Create( + protocol::ClientStub* client_stub); + + // Specify the name of the socket to listen to gnubby requests on. + static void SetGnubbySocketName(const base::FilePath& gnubby_socket_name); + + // A message was received from the client. + virtual void DeliverClientMessage(const std::string& message) = 0; + + // Send data to client. + virtual void DeliverHostDataMessage(int connection_id, + const std::string& data) const = 0; +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SECURITY_KEY_GNUBBY_AUTH_HANDLER_H_ diff --git a/remoting/host/security_key/gnubby_auth_handler_linux.cc b/remoting/host/security_key/gnubby_auth_handler_linux.cc new file mode 100644 index 0000000..5bed3c9 --- /dev/null +++ b/remoting/host/security_key/gnubby_auth_handler_linux.cc @@ -0,0 +1,302 @@ +// 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_auth_handler_linux.h" + +#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" +#include "base/threading/thread_restrictions.h" +#include "base/values.h" +#include "net/base/net_errors.h" +#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. +base::LazyInstance<base::FilePath>::Leaky g_gnubby_socket_name = + LAZY_INSTANCE_INITIALIZER; + +// Socket authentication function that only allows connections from callers with +// the current uid. +bool MatchUid(const net::UnixDomainServerSocket::Credentials& credentials) { + bool allowed = credentials.user_id == getuid(); + if (!allowed) + HOST_LOG << "Refused socket connection from uid " << credentials.user_id; + return allowed; +} + +// 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 + +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_); +} + +// static +scoped_ptr<GnubbyAuthHandler> GnubbyAuthHandler::Create( + protocol::ClientStub* client_stub) { + return make_scoped_ptr(new GnubbyAuthHandlerLinux(client_stub)); +} + +// 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; + } + } +} + +void GnubbyAuthHandlerLinux::DeliverHostDataMessage( + int connection_id, + const std::string& data) const { + DCHECK(CalledOnValidThread()); + + base::DictionaryValue request; + request.SetString(kMessageType, kDataMessage); + request.SetInteger(kConnectionId, connection_id); + + 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); + + std::string request_json; + if (!base::JSONWriter::Write(request, &request_json)) { + LOG(ERROR) << "Failed to create request json"; + return; + } + + protocol::ExtensionMessage message; + message.set_type(kGnubbyAuthMessage); + message.set_data(request_json); + + client_stub_->DeliverHostMessage(message); +} + +size_t GnubbyAuthHandlerLinux::GetActiveSocketsMapSizeForTest() const { + return active_sockets_.size(); +} + +void GnubbyAuthHandlerLinux::SetRequestTimeoutForTest( + const base::TimeDelta& timeout) { + request_timeout_ = timeout; +} + +void GnubbyAuthHandlerLinux::DoAccept() { + int result = auth_socket_->Accept( + &accept_socket_, + base::Bind(&GnubbyAuthHandlerLinux::OnAccepted, base::Unretained(this))); + if (result != net::ERR_IO_PENDING) + OnAccepted(result); +} + +void GnubbyAuthHandlerLinux::OnAccepted(int result) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(net::ERR_IO_PENDING, result); + + if (result < 0) { + LOG(ERROR) << "Error in accepting a new connection"; + 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; + socket->StartReadingRequest( + base::Bind(&GnubbyAuthHandlerLinux::OnReadComplete, + base::Unretained(this), connection_id)); + + // Continue accepting new connections. + DoAccept(); +} + +void GnubbyAuthHandlerLinux::OnReadComplete(int connection_id) { + DCHECK(CalledOnValidThread()); + + ActiveSockets::iterator iter = active_sockets_.find(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); +} + +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(); +} + +void GnubbyAuthHandlerLinux::SendErrorAndCloseActiveSocket( + const ActiveSockets::iterator& iter) { + iter->second->SendSshError(); + delete iter->second; + active_sockets_.erase(iter); +} + +void GnubbyAuthHandlerLinux::RequestTimedOut(int connection_id) { + HOST_LOG << "Gnubby request timed out"; + ActiveSockets::iterator iter = active_sockets_.find(connection_id); + if (iter != active_sockets_.end()) + SendErrorAndCloseActiveSocket(iter); +} + +} // namespace remoting diff --git a/remoting/host/security_key/gnubby_auth_handler_linux.h b/remoting/host/security_key/gnubby_auth_handler_linux.h new file mode 100644 index 0000000..a3c324e --- /dev/null +++ b/remoting/host/security_key/gnubby_auth_handler_linux.h @@ -0,0 +1,101 @@ +// 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_AUTH_HANDLER_LINUX_H_ +#define REMOTING_HOST_SECURITY_KEY_GNUBBY_AUTH_HANDLER_LINUX_H_ + +#include <stddef.h> + +#include <map> +#include <string> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.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 { + public: + explicit GnubbyAuthHandlerLinux(protocol::ClientStub* client_stub); + ~GnubbyAuthHandlerLinux() override; + + size_t GetActiveSocketsMapSizeForTest() const; + + void SetRequestTimeoutForTest(const base::TimeDelta& timeout); + + private: + 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; + + // Starts listening for connection. + void DoAccept(); + + // Called when a connection is accepted. + void OnAccepted(int result); + + // Called when a GnubbySocket has done reading. + void OnReadComplete(int connection_id); + + // Create socket for authorization. + void CreateAuthorizationSocket(); + + // 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); + + // Send an error and closes an active socket. + void SendErrorAndCloseActiveSocket(const ActiveSockets::iterator& iter); + + // A request timed out. + void RequestTimedOut(int connection_id); + + // Interface through which communication with the client occurs. + protocol::ClientStub* client_stub_; + + // Socket used to listen for authorization requests. + scoped_ptr<net::UnixDomainServerSocket> auth_socket_; + + // A temporary holder for an accepted connection. + scoped_ptr<net::StreamSocket> accept_socket_; + + // The last assigned gnubby connection id. + int last_connection_id_; + + // Sockets by connection id used to process gnubbyd requests. + ActiveSockets active_sockets_; + + // Timeout used for a request. + base::TimeDelta request_timeout_; + + DISALLOW_COPY_AND_ASSIGN(GnubbyAuthHandlerLinux); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SECURITY_KEY_GNUBBY_AUTH_HANDLER_LINUX_H_ diff --git a/remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc b/remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc new file mode 100644 index 0000000..2b73ffc --- /dev/null +++ b/remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc @@ -0,0 +1,234 @@ +// 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/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/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 { + +namespace { + +const char kSocketFilename[] = "socket_for_testing"; + +// 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() : 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; + 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() { + loop_->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> 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 WriteRequestData(net::UnixDomainClientSocket* client_socket) { + int request_len = sizeof(kRequestData); + scoped_refptr<net::DrainableIOBuffer> request_buffer( + new net::DrainableIOBuffer( + new net::WrappedIOBuffer( + reinterpret_cast<const char*>(kRequestData)), + request_len)); + net::TestCompletionCallback write_callback; + int bytes_written = 0; + while (bytes_written < request_len) { + int write_result = client_socket->Write(request_buffer.get(), + request_buffer->BytesRemaining(), + write_callback.callback()); + write_result = write_callback.GetResult(write_result); + ASSERT_GT(write_result, 0); + bytes_written += write_result; + ASSERT_LE(bytes_written, request_len); + request_buffer->DidConsume(write_result); + } + ASSERT_EQ(request_len, bytes_written); + } + + void WaitForAndVerifyHostMessage() { + client_stub_.WaitForDeliverHostMessage(); + 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); + } + + protected: + // Object under test. + scoped_ptr<GnubbyAuthHandlerLinux> auth_handler_posix_; + + // GnubbyAuthHandler interface for |auth_handler_posix_|. + GnubbyAuthHandler* auth_handler_; + + base::MessageLoopForIO message_loop_; + TestClientStub client_stub_; + 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]"); +} + +TEST_F(GnubbyAuthHandlerLinuxTest, NotClosedAfterRequest) { + ASSERT_EQ(0u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); + + const char message_json[] = "{\"type\":\"control\",\"option\":\"auth-v1\"}"; + 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)); + + // Write the request and verify the response. + WriteRequestData(&client_socket); + WaitForAndVerifyHostMessage(); + + // Verify that completing a request/response cycle didn't close the socket. + ASSERT_EQ(1u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); +} + +TEST_F(GnubbyAuthHandlerLinuxTest, HandleTwoRequests) { + ASSERT_EQ(0u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); + + const char message_json[] = "{\"type\":\"control\",\"option\":\"auth-v1\"}"; + 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)); + + // Write the request and verify the response. + WriteRequestData(&client_socket); + WaitForAndVerifyHostMessage(); + + // Repeat the request/response cycle. + WriteRequestData(&client_socket); + WaitForAndVerifyHostMessage(); + + // Verify that completing two request/response cycles didn't close the socket. + ASSERT_EQ(1u, auth_handler_posix_->GetActiveSocketsMapSizeForTest()); +} + +TEST_F(GnubbyAuthHandlerLinuxTest, DidReadTimeout) { + std::string message_json = "{\"type\":\"control\",\"option\":\"auth-v1\"}"; + + 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()); +} + +TEST_F(GnubbyAuthHandlerLinuxTest, ClientErrorMessageDelivered) { + std::string message_json = "{\"type\":\"control\",\"option\":\"auth-v1\"}"; + + 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()); +} + +} // namespace remoting diff --git a/remoting/host/security_key/gnubby_auth_handler_mac.cc b/remoting/host/security_key/gnubby_auth_handler_mac.cc new file mode 100644 index 0000000..b76d77c --- /dev/null +++ b/remoting/host/security_key/gnubby_auth_handler_mac.cc @@ -0,0 +1,48 @@ +// 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_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) { + 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 new file mode 100644 index 0000000..83351ab --- /dev/null +++ b/remoting/host/security_key/gnubby_auth_handler_win.cc @@ -0,0 +1,48 @@ +// 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_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) { + 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_socket.cc b/remoting/host/security_key/gnubby_socket.cc new file mode 100644 index 0000000..cc54a22 --- /dev/null +++ b/remoting/host/security_key/gnubby_socket.cc @@ -0,0 +1,188 @@ +// 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_socket.h" + +#include <utility> + +#include "base/callback_helpers.h" +#include "base/macros.h" +#include "base/timer/timer.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/socket/stream_socket.h" + +namespace remoting { + +namespace { + +const size_t kRequestSizeBytes = 4; +const size_t kMaxRequestLength = 16384; +const size_t kRequestReadBufferLength = kRequestSizeBytes + kMaxRequestLength; + +// SSH Failure Code +const char kSshError[] = {0x05}; + +} // namespace + +GnubbySocket::GnubbySocket(scoped_ptr<net::StreamSocket> socket, + const base::TimeDelta& timeout, + const base::Closure& timeout_callback) + : socket_(std::move(socket)), + read_completed_(false), + read_buffer_(new net::IOBufferWithSize(kRequestReadBufferLength)) { + timer_.reset(new base::Timer(false, false)); + timer_->Start(FROM_HERE, timeout, timeout_callback); +} + +GnubbySocket::~GnubbySocket() {} + +bool GnubbySocket::GetAndClearRequestData(std::string* data_out) { + DCHECK(CalledOnValidThread()); + DCHECK(read_completed_); + + if (!read_completed_) + return false; + if (!IsRequestComplete() || IsRequestTooLarge()) + return false; + // The request size is not part of the data; don't send it. + data_out->assign(request_data_.begin() + kRequestSizeBytes, + request_data_.end()); + request_data_.clear(); + return true; +} + +void GnubbySocket::SendResponse(const std::string& response_data) { + DCHECK(CalledOnValidThread()); + DCHECK(!write_buffer_); + + std::string response_length_string = GetResponseLengthAsBytes(response_data); + int response_len = response_length_string.size() + response_data.size(); + scoped_ptr<std::string> response( + new std::string(response_length_string + response_data)); + write_buffer_ = new net::DrainableIOBuffer( + new net::StringIOBuffer(std::move(response)), response_len); + DoWrite(); +} + +void GnubbySocket::SendSshError() { + DCHECK(CalledOnValidThread()); + + SendResponse(std::string(kSshError, arraysize(kSshError))); +} + +void GnubbySocket::StartReadingRequest( + const base::Closure& request_received_callback) { + DCHECK(CalledOnValidThread()); + DCHECK(request_received_callback_.is_null()); + + request_received_callback_ = request_received_callback; + DoRead(); +} + +void GnubbySocket::OnDataWritten(int result) { + DCHECK(CalledOnValidThread()); + DCHECK(write_buffer_); + + if (result < 0) { + LOG(ERROR) << "Error sending response: " << result; + return; + } + ResetTimer(); + write_buffer_->DidConsume(result); + DoWrite(); +} + +void GnubbySocket::DoWrite() { + DCHECK(CalledOnValidThread()); + DCHECK(write_buffer_); + + if (!write_buffer_->BytesRemaining()) { + write_buffer_ = nullptr; + return; + } + int result = socket_->Write( + write_buffer_.get(), write_buffer_->BytesRemaining(), + base::Bind(&GnubbySocket::OnDataWritten, base::Unretained(this))); + if (result != net::ERR_IO_PENDING) + OnDataWritten(result); +} + +void GnubbySocket::OnDataRead(int result) { + DCHECK(CalledOnValidThread()); + + if (result <= 0) { + if (result < 0) + LOG(ERROR) << "Error reading request: " << result; + read_completed_ = true; + base::ResetAndReturn(&request_received_callback_).Run(); + return; + } + + ResetTimer(); + request_data_.insert(request_data_.end(), read_buffer_->data(), + read_buffer_->data() + result); + if (IsRequestComplete()) { + read_completed_ = true; + base::ResetAndReturn(&request_received_callback_).Run(); + return; + } + + DoRead(); +} + +void GnubbySocket::DoRead() { + DCHECK(CalledOnValidThread()); + + int result = socket_->Read( + read_buffer_.get(), kRequestReadBufferLength, + base::Bind(&GnubbySocket::OnDataRead, base::Unretained(this))); + if (result != net::ERR_IO_PENDING) + OnDataRead(result); +} + +bool GnubbySocket::IsRequestComplete() const { + DCHECK(CalledOnValidThread()); + + if (request_data_.size() < kRequestSizeBytes) + return false; + return GetRequestLength() <= request_data_.size(); +} + +bool GnubbySocket::IsRequestTooLarge() const { + DCHECK(CalledOnValidThread()); + + if (request_data_.size() < kRequestSizeBytes) + return false; + return GetRequestLength() > kMaxRequestLength; +} + +size_t GnubbySocket::GetRequestLength() const { + DCHECK(request_data_.size() >= kRequestSizeBytes); + + return ((request_data_[0] & 255) << 24) + ((request_data_[1] & 255) << 16) + + ((request_data_[2] & 255) << 8) + (request_data_[3] & 255) + + kRequestSizeBytes; +} + +std::string GnubbySocket::GetResponseLengthAsBytes( + const std::string& response) const { + std::string response_len; + response_len.reserve(kRequestSizeBytes); + int len = response.size(); + + response_len.push_back((len >> 24) & 255); + response_len.push_back((len >> 16) & 255); + response_len.push_back((len >> 8) & 255); + response_len.push_back(len & 255); + + return response_len; +} + +void GnubbySocket::ResetTimer() { + if (timer_->IsRunning()) + timer_->Reset(); +} + +} // namespace remoting diff --git a/remoting/host/security_key/gnubby_socket.h b/remoting/host/security_key/gnubby_socket.h new file mode 100644 index 0000000..e37f64f --- /dev/null +++ b/remoting/host/security_key/gnubby_socket.h @@ -0,0 +1,110 @@ +// 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_SOCKET_H_ +#define REMOTING_HOST_SECURITY_KEY_GNUBBY_SOCKET_H_ + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" + +namespace base { +class Timer; +} // namespace base + +namespace net { +class DrainableIOBuffer; +class IOBufferWithSize; +class StreamSocket; +} // namespace net + +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 { + public: + GnubbySocket(scoped_ptr<net::StreamSocket> socket, + const base::TimeDelta& timeout, + const base::Closure& timeout_callback); + ~GnubbySocket(); + + // Returns false if the request has not yet completed, or is too large to be + // processed. Otherwise, the cached request data is copied into |data_out| and + // the internal buffer resets and is ready for the next request. + bool GetAndClearRequestData(std::string* data_out); + + // Sends response data to the socket. + void SendResponse(const std::string& data); + + // Sends an SSH error code to the socket. + void SendSshError(); + + // |request_received_callback| is used to notify the caller that request data + // has been fully read, and caller is to use GetAndClearRequestData method to + // get the request data. + void StartReadingRequest(const base::Closure& request_received_callback); + + private: + // Called when bytes are written to |socket_|. + void OnDataWritten(int result); + + // Continues writing to |socket_| if needed. + void DoWrite(); + + // Called when bytes are read from |socket_|. + void OnDataRead(int bytes_read); + + // Continues to read. + void DoRead(); + + // Returns true if the current request is complete. + bool IsRequestComplete() const; + + // Returns true if the stated request size is larger than the allowed maximum. + bool IsRequestTooLarge() const; + + // Returns the stated request length. + size_t GetRequestLength() const; + + // Returns the response length bytes. + std::string GetResponseLengthAsBytes(const std::string& response) const; + + // Resets the socket activity timer. + void ResetTimer(); + + // The socket. + scoped_ptr<net::StreamSocket> socket_; + + // Invoked when request data has been read. + base::Closure request_received_callback_; + + // Indicates whether read has completed and |request_received_callback_| is + // about to be run. + bool read_completed_; + + // Request data. + std::vector<char> request_data_; + + scoped_refptr<net::DrainableIOBuffer> write_buffer_; + + scoped_refptr<net::IOBufferWithSize> read_buffer_; + + // The activity timer. + scoped_ptr<base::Timer> timer_; + + DISALLOW_COPY_AND_ASSIGN(GnubbySocket); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SECURITY_KEY_GNUBBY_SOCKET_H_ |