summaryrefslogtreecommitdiffstats
path: root/remoting/host/security_key
diff options
context:
space:
mode:
authorjoedow <joedow@chromium.org>2016-02-08 13:18:13 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-08 21:19:16 +0000
commit91151047466065d41b4e1a8ee8cae7653dbcceb5 (patch)
treefdf245aa5c864a59667f92a29e24da596e4f8ec1 /remoting/host/security_key
parent77e1d01e1d15ebe163796d7742f177df24b1f089 (diff)
downloadchromium_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.h45
-rw-r--r--remoting/host/security_key/gnubby_auth_handler_linux.cc302
-rw-r--r--remoting/host/security_key/gnubby_auth_handler_linux.h101
-rw-r--r--remoting/host/security_key/gnubby_auth_handler_linux_unittest.cc234
-rw-r--r--remoting/host/security_key/gnubby_auth_handler_mac.cc48
-rw-r--r--remoting/host/security_key/gnubby_auth_handler_win.cc48
-rw-r--r--remoting/host/security_key/gnubby_socket.cc188
-rw-r--r--remoting/host/security_key/gnubby_socket.h110
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_