summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--remoting/host/chromoting_host.cc3
-rw-r--r--remoting/host/chromoting_host.h5
-rw-r--r--remoting/host/plugin/host_script_object.cc3
-rw-r--r--remoting/protocol/connection_to_host.cc3
-rw-r--r--remoting/protocol/fake_session.cc10
-rw-r--r--remoting/protocol/fake_session.h5
-rw-r--r--remoting/protocol/jingle_datagram_connector.cc3
-rw-r--r--remoting/protocol/jingle_session.cc12
-rw-r--r--remoting/protocol/jingle_session.h9
-rw-r--r--remoting/protocol/jingle_session_unittest.cc119
-rw-r--r--remoting/protocol/jingle_stream_connector.cc193
-rw-r--r--remoting/protocol/jingle_stream_connector.h29
-rw-r--r--remoting/protocol/protocol_mock_objects.h2
-rw-r--r--remoting/protocol/session.h4
14 files changed, 338 insertions, 62 deletions
diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc
index c7c3af8..0ae98a7 100644
--- a/remoting/host/chromoting_host.cc
+++ b/remoting/host/chromoting_host.cc
@@ -305,6 +305,9 @@ void ChromotingHost::OnIncomingSession(
session->set_receiver_token(
GenerateHostAuthToken(session->initiator_token()));
+ // Provide the Access Code as shared secret for SSL channel authentication.
+ session->set_shared_secret(access_code_);
+
*response = protocol::SessionManager::ACCEPT;
logger_->Log(logging::LOG_INFO, "Client connected: %s",
diff --git a/remoting/host/chromoting_host.h b/remoting/host/chromoting_host.h
index 5908432..2938b6c 100644
--- a/remoting/host/chromoting_host.h
+++ b/remoting/host/chromoting_host.h
@@ -135,6 +135,9 @@ class ChromotingHost : public base::RefCountedThreadSafe<ChromotingHost>,
void set_it2me(bool is_it2me) {
is_it2me_ = is_it2me;
}
+ void set_access_code(const std::string& access_code) {
+ access_code_ = access_code;
+ }
// Notify all active client sessions that local input has been detected, and
// that remote input should be ignored for a short time.
@@ -226,6 +229,8 @@ class ChromotingHost : public base::RefCountedThreadSafe<ChromotingHost>,
// are pre-authenticated, and hence the local login challenge can be bypassed.
bool is_it2me_;
+ std::string access_code_;
+
// Stores list of tasks that should be executed when we finish
// shutdown. Used only while |state_| is set to kStopping.
std::vector<Task*> shutdown_tasks_;
diff --git a/remoting/host/plugin/host_script_object.cc b/remoting/host/plugin/host_script_object.cc
index cfb3b9c..460655a 100644
--- a/remoting/host/plugin/host_script_object.cc
+++ b/remoting/host/plugin/host_script_object.cc
@@ -458,6 +458,9 @@ void HostNPScriptObject::OnReceivedSupportID(
access_code_ = support_id + access_verifier->host_secret();
access_code_lifetime_ = lifetime;
+ // Tell the ChromotingHost the access code, to use as shared-secret.
+ host_->set_access_code(access_code_);
+
// Let the caller know that life is good.
OnStateChanged(kReceivedAccessCode);
}
diff --git a/remoting/protocol/connection_to_host.cc b/remoting/protocol/connection_to_host.cc
index 5fbe969..fe75cfe 100644
--- a/remoting/protocol/connection_to_host.cc
+++ b/remoting/protocol/connection_to_host.cc
@@ -115,6 +115,9 @@ void ConnectionToHost::InitSession() {
session_manager_.reset(session_manager);
session_manager_->Init(
local_jid_, signal_strategy_.get(), this, NULL, "", allow_nat_traversal_);
+
+ // Set the shared-secret for securing SSL channels.
+ session_->set_shared_secret(access_code_);
}
const SessionConfig* ConnectionToHost::config() {
diff --git a/remoting/protocol/fake_session.cc b/remoting/protocol/fake_session.cc
index 2403a20..b23dbb1 100644
--- a/remoting/protocol/fake_session.cc
+++ b/remoting/protocol/fake_session.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -205,6 +205,14 @@ void FakeSession::set_receiver_token(const std::string& receiver_token) {
receiver_token_ = receiver_token;
}
+void FakeSession::set_shared_secret(const std::string& shared_secret) {
+ shared_secret_ = shared_secret;
+}
+
+const std::string& FakeSession::shared_secret() {
+ return shared_secret_;
+}
+
void FakeSession::Close() {
closed_ = true;
}
diff --git a/remoting/protocol/fake_session.h b/remoting/protocol/fake_session.h
index d892ede..912cfb0 100644
--- a/remoting/protocol/fake_session.h
+++ b/remoting/protocol/fake_session.h
@@ -128,6 +128,9 @@ class FakeSession : public Session {
virtual const std::string& receiver_token();
virtual void set_receiver_token(const std::string& receiver_token);
+ virtual void set_shared_secret(const std::string& secret);
+ virtual const std::string& shared_secret();
+
virtual void Close();
public:
@@ -144,6 +147,8 @@ class FakeSession : public Session {
std::string initiator_token_;
std::string receiver_token_;
+ std::string shared_secret_;
+
std::string jid_;
bool closed_;
};
diff --git a/remoting/protocol/jingle_datagram_connector.cc b/remoting/protocol/jingle_datagram_connector.cc
index 0a45425..5a789d1 100644
--- a/remoting/protocol/jingle_datagram_connector.cc
+++ b/remoting/protocol/jingle_datagram_connector.cc
@@ -35,8 +35,9 @@ void JingleDatagramConnector::Connect(
// TODO(sergeyu): Implement encryption for datagram channels.
- callback_.Run(name_, socket);
session_->OnChannelConnectorFinished(name_, this);
+ callback_.Run(name_, socket);
+ delete this;
}
} // namespace protocol
diff --git a/remoting/protocol/jingle_session.cc b/remoting/protocol/jingle_session.cc
index 3e08dca..8485336 100644
--- a/remoting/protocol/jingle_session.cc
+++ b/remoting/protocol/jingle_session.cc
@@ -303,6 +303,17 @@ void JingleSession::set_receiver_token(const std::string& receiver_token) {
receiver_token_ = receiver_token;
}
+void JingleSession::set_shared_secret(const std::string& secret) {
+ DCHECK(CalledOnValidThread());
+ shared_secret_ = secret;
+}
+
+const std::string& JingleSession::shared_secret() {
+ DCHECK(CalledOnValidThread());
+ return shared_secret_;
+}
+
+
void JingleSession::Close() {
DCHECK(CalledOnValidThread());
@@ -492,7 +503,6 @@ void JingleSession::OnChannelConnectorFinished(
DCHECK(CalledOnValidThread());
DCHECK_EQ(channel_connectors_[name], connector);
channel_connectors_[name] = NULL;
- delete connector;
}
void JingleSession::CreateChannels() {
diff --git a/remoting/protocol/jingle_session.h b/remoting/protocol/jingle_session.h
index cc40660..e91a45c 100644
--- a/remoting/protocol/jingle_session.h
+++ b/remoting/protocol/jingle_session.h
@@ -49,6 +49,8 @@ class JingleSession : public protocol::Session,
virtual void set_initiator_token(const std::string& initiator_token) OVERRIDE;
virtual const std::string& receiver_token() OVERRIDE;
virtual void set_receiver_token(const std::string& receiver_token) OVERRIDE;
+ virtual void set_shared_secret(const std::string& secret) OVERRIDE;
+ virtual const std::string& shared_secret() OVERRIDE;
virtual void Close() OVERRIDE;
private:
@@ -113,7 +115,8 @@ class JingleSession : public protocol::Session,
JingleChannelConnector* connector);
// Called by JingleChannelConnector when it has finished connecting
- // the channel and needs to be destroyed.
+ // the channel, to remove itself from the table of pending connectors. The
+ // connector assumes responsibility for destroying itself after this call.
void OnChannelConnectorFinished(const std::string& name,
JingleChannelConnector* connector);
@@ -152,6 +155,10 @@ class JingleSession : public protocol::Session,
// session-initiate message (encrypted with the host's key).
std::string master_key_;
+ // Shared secret to use in channel authentication. This is currently only
+ // used in IT2Me.
+ std::string shared_secret_;
+
State state_;
scoped_ptr<StateChangeCallback> state_change_callback_;
diff --git a/remoting/protocol/jingle_session_unittest.cc b/remoting/protocol/jingle_session_unittest.cc
index 122151a..139acd3 100644
--- a/remoting/protocol/jingle_session_unittest.cc
+++ b/remoting/protocol/jingle_session_unittest.cc
@@ -22,8 +22,10 @@
#include "third_party/libjingle/source/talk/p2p/client/basicportallocator.h"
using testing::_;
+using testing::AnyNumber;
using testing::DeleteArg;
using testing::DoAll;
+using testing::InSequence;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::Return;
@@ -63,6 +65,9 @@ const char kTestHostPublicKey[] =
"A78Oi+IbcJf/jJUZO119VNnRKGiPsf5GZIoHyXX8O5OUQk5soKdQPeK1FwWkeZu6fuXl"
"QoU12I6podD6xMFa/PA/xefMwcpmuWTRhcso9bp10zVFGQIDAQAB";
+const char kTestSharedSecret[] = "1234-1234-5678";
+const char kTestSharedSecretBad[] = "0000-0000-0001";
+
void QuitCurrentThread() {
MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
}
@@ -117,6 +122,7 @@ class JingleSessionTest : public testing::Test {
&MockSessionCallback::OnStateChange));
session->set_config(SessionConfig::CreateDefault());
+ session->set_shared_secret(kTestSharedSecret);
}
protected:
@@ -128,11 +134,6 @@ class JingleSessionTest : public testing::Test {
CloseSessionManager();
}
- void CreateServerPair() {
- // Sessions must be initialized on the jingle thread.
- DoCreateServerPair();
- }
-
void CloseSessions() {
if (host_session_.get()) {
host_session_->Close();
@@ -144,7 +145,7 @@ class JingleSessionTest : public testing::Test {
}
}
- void DoCreateServerPair() {
+ void CreateServerPair() {
FilePath certs_dir;
PathService::Get(base::DIR_SOURCE_ROOT, &certs_dir);
certs_dir = certs_dir.AppendASCII("net");
@@ -201,7 +202,7 @@ class JingleSessionTest : public testing::Test {
client_signal_strategy_.reset();
}
- bool InitiateConnection() {
+ bool InitiateConnection(const char* shared_secret) {
int not_connected_peers = 2;
EXPECT_CALL(host_server_listener_, OnIncomingSession(_, _))
@@ -210,31 +211,49 @@ class JingleSessionTest : public testing::Test {
this, &JingleSessionTest::SetHostSession)),
SetArgumentPointee<1>(protocol::SessionManager::ACCEPT)));
- base::WaitableEvent host_connected_event(true, false);
- EXPECT_CALL(host_connection_callback_,
- OnStateChange(Session::CONNECTING))
- .Times(1);
- EXPECT_CALL(host_connection_callback_,
- OnStateChange(Session::CONNECTED))
- .Times(1)
- .WillOnce(QuitThreadOnCounter(&not_connected_peers));
- // Expect that the connection will be closed eventually.
- EXPECT_CALL(host_connection_callback_,
- OnStateChange(Session::CLOSED))
- .Times(1);
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(host_connection_callback_,
+ OnStateChange(Session::CONNECTING))
+ .Times(1);
+ if (shared_secret == kTestSharedSecret) {
+ EXPECT_CALL(host_connection_callback_,
+ OnStateChange(Session::CONNECTED))
+ .Times(1)
+ .WillOnce(QuitThreadOnCounter(&not_connected_peers));
+ // Expect that the connection will be closed eventually.
+ EXPECT_CALL(host_connection_callback_,
+ OnStateChange(Session::CLOSED))
+ .Times(1);
+ } else {
+ // Might pass through the CONNECTED state.
+ EXPECT_CALL(host_connection_callback_,
+ OnStateChange(Session::CONNECTED))
+ .Times(AnyNumber());
+ // Expect that the connection will be closed eventually.
+ EXPECT_CALL(host_connection_callback_,
+ OnStateChange(Session::FAILED))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(&QuitCurrentThread));
+ }
+ }
- base::WaitableEvent client_connected_event(true, false);
- EXPECT_CALL(client_connection_callback_,
- OnStateChange(Session::CONNECTING))
- .Times(1);
- EXPECT_CALL(client_connection_callback_,
- OnStateChange(Session::CONNECTED))
- .Times(1)
- .WillOnce(QuitThreadOnCounter(&not_connected_peers));
- // Expect that the connection will be closed eventually.
- EXPECT_CALL(client_connection_callback_,
- OnStateChange(Session::CLOSED))
- .Times(1);
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(client_connection_callback_,
+ OnStateChange(Session::CONNECTING))
+ .Times(1);
+ EXPECT_CALL(client_connection_callback_,
+ OnStateChange(Session::CONNECTED))
+ .Times(1)
+ .WillOnce(QuitThreadOnCounter(&not_connected_peers));
+ // Expect that the connection will be closed eventually.
+ EXPECT_CALL(client_connection_callback_,
+ OnStateChange(Session::CLOSED))
+ .Times(1);
+ }
client_session_.reset(client_server_->Connect(
kHostJid, kTestHostPublicKey, kTestToken,
@@ -242,6 +261,8 @@ class JingleSessionTest : public testing::Test {
NewCallback(&client_connection_callback_,
&MockSessionCallback::OnStateChange)));
+ client_session_->set_shared_secret(shared_secret);
+
return RunMessageLoopWithTimeout(TestTimeouts::action_max_timeout_ms());
}
@@ -617,13 +638,17 @@ TEST_F(JingleSessionTest, RejectConnection) {
EXPECT_CALL(host_server_listener_, OnIncomingSession(_, _))
.WillOnce(SetArgumentPointee<1>(protocol::SessionManager::DECLINE));
- EXPECT_CALL(client_connection_callback_,
- OnStateChange(Session::CONNECTING))
- .Times(1);
- EXPECT_CALL(client_connection_callback_,
- OnStateChange(Session::CLOSED))
- .Times(1)
- .WillOnce(InvokeWithoutArgs(&QuitCurrentThread));
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(client_connection_callback_,
+ OnStateChange(Session::CONNECTING))
+ .Times(1);
+ EXPECT_CALL(client_connection_callback_,
+ OnStateChange(Session::CLOSED))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(&QuitCurrentThread));
+ }
client_session_.reset(client_server_->Connect(
kHostJid, kTestHostPublicKey, kTestToken,
@@ -637,13 +662,19 @@ TEST_F(JingleSessionTest, RejectConnection) {
// Verify that we can connect two endpoints.
TEST_F(JingleSessionTest, Connect) {
CreateServerPair();
- ASSERT_TRUE(InitiateConnection());
+ ASSERT_TRUE(InitiateConnection(kTestSharedSecret));
+}
+
+// Verify that we can't connect two endpoints with mismatched secrets.
+TEST_F(JingleSessionTest, ConnectBadChannelAuth) {
+ CreateServerPair();
+ ASSERT_TRUE(InitiateConnection(kTestSharedSecretBad));
}
// Verify that data can be transmitted over the event channel.
TEST_F(JingleSessionTest, TestControlChannel) {
CreateServerPair();
- ASSERT_TRUE(InitiateConnection());
+ ASSERT_TRUE(InitiateConnection(kTestSharedSecret));
scoped_refptr<TCPChannelTester> tester(
new TCPChannelTester(host_session_.get(), client_session_.get(),
kMessageSize, kMessages));
@@ -658,7 +689,7 @@ TEST_F(JingleSessionTest, TestControlChannel) {
// Verify that data can be transmitted over the video channel.
TEST_F(JingleSessionTest, TestVideoChannel) {
CreateServerPair();
- ASSERT_TRUE(InitiateConnection());
+ ASSERT_TRUE(InitiateConnection(kTestSharedSecret));
scoped_refptr<TCPChannelTester> tester(
new TCPChannelTester(host_session_.get(), client_session_.get(),
kMessageSize, kMessageSize));
@@ -673,7 +704,7 @@ TEST_F(JingleSessionTest, TestVideoChannel) {
// Verify that data can be transmitted over the event channel.
TEST_F(JingleSessionTest, TestEventChannel) {
CreateServerPair();
- ASSERT_TRUE(InitiateConnection());
+ ASSERT_TRUE(InitiateConnection(kTestSharedSecret));
scoped_refptr<TCPChannelTester> tester(
new TCPChannelTester(host_session_.get(), client_session_.get(),
kMessageSize, kMessageSize));
@@ -688,7 +719,7 @@ TEST_F(JingleSessionTest, TestEventChannel) {
// Verify that data can be transmitted over the video RTP channel.
TEST_F(JingleSessionTest, TestVideoRtpChannel) {
CreateServerPair();
- ASSERT_TRUE(InitiateConnection());
+ ASSERT_TRUE(InitiateConnection(kTestSharedSecret));
scoped_refptr<UDPChannelTester> tester(
new UDPChannelTester(host_session_.get(), client_session_.get()));
tester->Start(ChannelTesterBase::VIDEO_RTP);
@@ -703,7 +734,7 @@ TEST_F(JingleSessionTest, TestVideoRtpChannel) {
// using sockets from JingleSession.
TEST_F(JingleSessionTest, TestSpeed) {
CreateServerPair();
- ASSERT_TRUE(InitiateConnection());
+ ASSERT_TRUE(InitiateConnection(kTestSharedSecret));
scoped_refptr<ChannelSpeedTester> tester;
tester = new ChannelSpeedTester(host_session_.get(),
diff --git a/remoting/protocol/jingle_stream_connector.cc b/remoting/protocol/jingle_stream_connector.cc
index 650c119..cc671e2 100644
--- a/remoting/protocol/jingle_stream_connector.cc
+++ b/remoting/protocol/jingle_stream_connector.cc
@@ -4,6 +4,7 @@
#include "remoting/protocol/jingle_stream_connector.h"
+#include "crypto/hmac.h"
#include "jingle/glue/channel_socket_adapter.h"
#include "jingle/glue/pseudotcp_adapter.h"
#include "net/base/cert_status_flags.h"
@@ -21,6 +22,12 @@ namespace protocol {
namespace {
+// Size of the HMAC-SHA-1 authentication digest.
+const int kAuthDigestLength = 20;
+
+// Labels for use when exporting the SSL master keys.
+const char kClientSslExporterLabel[] = "EXPORTER-remoting-channel-auth-client";
+
// Value is choosen to balance the extra latency against the reduced
// load due to ACK traffic.
const int kTcpAckDelayMilliseconds = 10;
@@ -30,6 +37,9 @@ net::SSLClientSocket* CreateSSLClientSocket(
net::StreamSocket* socket, const std::string& der_cert,
net::CertVerifier* cert_verifier) {
net::SSLConfig ssl_config;
+ // If False Start is enabled then the Connect callback will fire before
+ // the cipher has been set up, and ExportKeyingMaterial will fail.
+ ssl_config.false_start_enabled = false;
// Certificate provided by the host doesn't need authority.
net::SSLConfig::CertAndStatus cert_and_status;
@@ -59,10 +69,16 @@ JingleStreamConnector::JingleStreamConnector(
initiator_(false),
local_private_key_(NULL),
raw_channel_(NULL),
+ ssl_client_socket_(NULL),
+ ssl_server_socket_(NULL),
ALLOW_THIS_IN_INITIALIZER_LIST(tcp_connect_callback_(
this, &JingleStreamConnector::OnTCPConnect)),
ALLOW_THIS_IN_INITIALIZER_LIST(ssl_connect_callback_(
- this, &JingleStreamConnector::OnSSLConnect)) {
+ this, &JingleStreamConnector::OnSSLConnect)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(auth_write_callback_(
+ this, &JingleStreamConnector::OnAuthBytesWritten)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(auth_read_callback_(
+ this, &JingleStreamConnector::OnAuthBytesRead)) {
}
JingleStreamConnector::~JingleStreamConnector() {
@@ -115,11 +131,11 @@ bool JingleStreamConnector::EstablishSSLConnection() {
cert_verifier_.reset(new net::CertVerifier());
// Create client SSL socket.
- net::SSLClientSocket* ssl_client_socket = CreateSSLClientSocket(
+ ssl_client_socket_ = CreateSSLClientSocket(
socket_.release(), remote_cert_, cert_verifier_.get());
- socket_.reset(ssl_client_socket);
+ socket_.reset(ssl_client_socket_);
- result = ssl_client_socket->Connect(&ssl_connect_callback_);
+ result = ssl_client_socket_->Connect(&ssl_connect_callback_);
} else {
scoped_refptr<net::X509Certificate> cert =
net::X509Certificate::CreateFromBytes(
@@ -131,13 +147,13 @@ bool JingleStreamConnector::EstablishSSLConnection() {
// Create server SSL socket.
net::SSLConfig ssl_config;
+ ssl_config.false_start_enabled = false;
- net::SSLServerSocket* ssl_server_socket =
- net::CreateSSLServerSocket(socket_.release(), cert,
- local_private_key_, ssl_config);
- socket_.reset(ssl_server_socket);
+ ssl_server_socket_ = net::CreateSSLServerSocket(
+ socket_.release(), cert, local_private_key_, ssl_config);
+ socket_.reset(ssl_server_socket_);
- result = ssl_server_socket->Handshake(&ssl_connect_callback_);
+ result = ssl_server_socket_->Handshake(&ssl_connect_callback_);
}
if (result == net::ERR_IO_PENDING) {
@@ -175,18 +191,171 @@ void JingleStreamConnector::OnSSLConnect(int result) {
}
DCHECK(socket_->IsConnected());
+ AuthenticateChannel();
+}
+
+void JingleStreamConnector::AuthenticateChannel() {
+ DCHECK(CalledOnValidThread());
+ if (initiator_) {
+ // Allocate a buffer to write the authentication digest.
+ scoped_refptr<net::IOBuffer> write_buf =
+ new net::IOBuffer(kAuthDigestLength);
+ auth_write_buf_ = new net::DrainableIOBuffer(write_buf,
+ kAuthDigestLength);
+
+ // Generate the auth digest to send.
+ if (!GetAuthBytes(kClientSslExporterLabel, auth_write_buf_->data())) {
+ NotifyError();
+ return;
+ }
+
+ DoAuthWrite();
+ } else {
+ // Read an authentication digest.
+ auth_read_buf_ = new net::GrowableIOBuffer();
+ auth_read_buf_->SetCapacity(kAuthDigestLength);
+ DoAuthRead();
+ }
+}
+
+void JingleStreamConnector::DoAuthWrite() {
+ while (true) {
+ int result = socket_->Write(auth_write_buf_,
+ auth_write_buf_->BytesRemaining(),
+ &auth_write_callback_);
+ if (result == net::ERR_IO_PENDING)
+ break;
+ if (!HandleAuthBytesWritten(result))
+ break;
+ }
+}
+
+void JingleStreamConnector::DoAuthRead() {
+ while (true) {
+ int result = socket_->Read(auth_read_buf_,
+ auth_read_buf_->RemainingCapacity(),
+ &auth_read_callback_);
+ if (result == net::ERR_IO_PENDING)
+ break;
+ if (!HandleAuthBytesRead(result))
+ break;
+ }
+}
+
+void JingleStreamConnector::OnAuthBytesWritten(int result) {
+ if (HandleAuthBytesWritten(result))
+ DoAuthWrite();
+}
+
+void JingleStreamConnector::OnAuthBytesRead(int result) {
+ if (HandleAuthBytesRead(result))
+ DoAuthRead();
+}
+
+bool JingleStreamConnector::HandleAuthBytesWritten(int result) {
+ DCHECK(CalledOnValidThread());
+
+ if (result <= 0) {
+ LOG(ERROR) << "Error writing authentication: " << result;
+ NotifyError();
+ return false;
+ }
+
+ auth_write_buf_->DidConsume(result);
+ if (auth_write_buf_->BytesRemaining() > 0)
+ return true;
+
NotifyDone(socket_.release());
+ return false;
+}
+
+bool JingleStreamConnector::HandleAuthBytesRead(int read_result) {
+ DCHECK(CalledOnValidThread());
+
+ if (read_result <= 0) {
+ LOG(ERROR) << "Error reading authentication: " << read_result;
+ NotifyError();
+ return false;
+ }
+
+ auth_read_buf_->set_offset(auth_read_buf_->offset() + read_result);
+ if (auth_read_buf_->RemainingCapacity() > 0)
+ return true;
+
+ if (!VerifyAuthBytes(
+ kClientSslExporterLabel,
+ auth_read_buf_->StartOfBuffer())) {
+ NotifyError();
+ return false;
+ }
+
+ NotifyDone(socket_.release());
+ return false;
+}
+
+bool JingleStreamConnector::VerifyAuthBytes(const char* label,
+ const char* auth_bytes) {
+ char expected[kAuthDigestLength];
+ if (!GetAuthBytes(label, expected))
+ return false;
+ // Compare the received and expected digests in fixed time, to limit the
+ // scope for timing attacks.
+ uint8 result = 0;
+ for (unsigned i = 0; i < sizeof(expected); i++) {
+ result |= auth_bytes[i] ^ expected[i];
+ }
+ if (result != 0) {
+ LOG(ERROR) << "Mismatched authentication";
+ return false;
+ }
+ return true;
+}
+
+bool JingleStreamConnector::GetAuthBytes(const char* label,
+ char* out_bytes) {
+ // Fetch keying material from the socket.
+ unsigned char key_material[kAuthDigestLength];
+ int result;
+ if (initiator_) {
+ result = ssl_client_socket_->ExportKeyingMaterial(
+ kClientSslExporterLabel, "", key_material, sizeof(key_material));
+ } else {
+ result = ssl_server_socket_->ExportKeyingMaterial(
+ kClientSslExporterLabel, "", key_material, sizeof(key_material));
+ }
+ if (result != net::OK) {
+ LOG(ERROR) << "Error fetching keying material: " << result;
+ return false;
+ }
+
+ // Generate auth digest based on the keying material and shared secret.
+ crypto::HMAC response(crypto::HMAC::SHA1);
+ if (!response.Init(session_->shared_secret())) {
+ NOTREACHED() << "HMAC::Init failed";
+ return false;
+ }
+ base::StringPiece message(reinterpret_cast<const char*>(key_material),
+ sizeof(key_material));
+ if (!response.Sign(
+ message,
+ reinterpret_cast<unsigned char*>(out_bytes),
+ kAuthDigestLength)) {
+ NOTREACHED() << "HMAC::Sign failed";
+ return false;
+ }
+
+ return true;
}
void JingleStreamConnector::NotifyDone(net::StreamSocket* socket) {
- callback_.Run(name_, socket);
session_->OnChannelConnectorFinished(name_, this);
+ callback_.Run(name_, socket);
+ delete this;
}
void JingleStreamConnector::NotifyError() {
socket_.reset();
- callback_.Run(name_, NULL);
- session_->OnChannelConnectorFinished(name_, this);
+ NotifyDone(NULL);
}
} // namespace protocol
diff --git a/remoting/protocol/jingle_stream_connector.h b/remoting/protocol/jingle_stream_connector.h
index 9abfc63..df003e3 100644
--- a/remoting/protocol/jingle_stream_connector.h
+++ b/remoting/protocol/jingle_stream_connector.h
@@ -22,6 +22,8 @@ class TransportChannelSocketAdapter;
namespace net {
class CertVerifier;
class StreamSocket;
+class SSLClientSocket;
+class SSLServerSocket;
} // namespace net
namespace remoting {
@@ -29,6 +31,12 @@ namespace protocol {
class JingleSession;
+// JingleStreamConnector creates the named datagram channel in the supplied
+// JingleSession, and uses PseudoTcp to turn it into a stream channel. Within
+// the stream channel SSL is used to secure the protocol stream. Finally, the
+// initiator authenticates the channel to the recipient by sending a digest
+// based on a secret shared by the two parties, and keying material derived
+// from the SSL session's master secret and nonces.
class JingleStreamConnector : public JingleChannelConnector {
public:
JingleStreamConnector(JingleSession* session,
@@ -52,13 +60,21 @@ class JingleStreamConnector : public JingleChannelConnector {
bool EstablishSSLConnection();
void OnSSLConnect(int result);
+ void AuthenticateChannel();
+ void DoAuthWrite();
+ void DoAuthRead();
+ void OnAuthBytesWritten(int result);
+ void OnAuthBytesRead(int result);
+ bool HandleAuthBytesWritten(int result);
+ bool HandleAuthBytesRead(int result);
+ bool VerifyAuthBytes(const char* label, const char* auth_bytes);
+ bool GetAuthBytes(const char* label, char* out_bytes);
+
void NotifyDone(net::StreamSocket* socket);
void NotifyError();
JingleSession* session_;
-
std::string name_;
-
Session::StreamChannelCallback callback_;
bool initiator_;
@@ -66,15 +82,24 @@ class JingleStreamConnector : public JingleChannelConnector {
std::string remote_cert_;
crypto::RSAPrivateKey* local_private_key_;
+ scoped_refptr<net::DrainableIOBuffer> auth_write_buf_;
+ scoped_refptr<net::GrowableIOBuffer> auth_read_buf_;
+
cricket::TransportChannel* raw_channel_;
scoped_ptr<net::StreamSocket> socket_;
+ // TODO(wez): Ugly up-casts needed so we can fetch SSL keying material.
+ net::SSLClientSocket* ssl_client_socket_;
+ net::SSLServerSocket* ssl_server_socket_;
+
// Used to verify the certificate received in SSLClientSocket.
scoped_ptr<net::CertVerifier> cert_verifier_;
// Callback called by the TCP and SSL layers.
net::CompletionCallbackImpl<JingleStreamConnector> tcp_connect_callback_;
net::CompletionCallbackImpl<JingleStreamConnector> ssl_connect_callback_;
+ net::CompletionCallbackImpl<JingleStreamConnector> auth_write_callback_;
+ net::CompletionCallbackImpl<JingleStreamConnector> auth_read_callback_;
DISALLOW_COPY_AND_ASSIGN(JingleStreamConnector);
};
diff --git a/remoting/protocol/protocol_mock_objects.h b/remoting/protocol/protocol_mock_objects.h
index 199dd2d..716a3b4 100644
--- a/remoting/protocol/protocol_mock_objects.h
+++ b/remoting/protocol/protocol_mock_objects.h
@@ -128,6 +128,8 @@ class MockSession : public Session {
MOCK_METHOD1(set_initiator_token, void(const std::string& initiator_token));
MOCK_METHOD0(receiver_token, const std::string&());
MOCK_METHOD1(set_receiver_token, void(const std::string& receiver_token));
+ MOCK_METHOD1(set_shared_secret, void(const std::string& secret));
+ MOCK_METHOD0(shared_secret, const std::string&());
MOCK_METHOD0(Close, void());
private:
diff --git a/remoting/protocol/session.h b/remoting/protocol/session.h
index 059a21f..fc40a6d 100644
--- a/remoting/protocol/session.h
+++ b/remoting/protocol/session.h
@@ -92,6 +92,10 @@ class Session : public base::NonThreadSafe {
virtual const std::string& receiver_token() = 0;
virtual void set_receiver_token(const std::string& receiver_token) = 0;
+ // A shared secret to use to mutually-authenticate the SSL channels.
+ virtual void set_shared_secret(const std::string& secret) = 0;
+ virtual const std::string& shared_secret() = 0;
+
// Closes connection. Callbacks are guaranteed not to be called
// after this method returns. Must be called before the object is
// destroyed, unless the state is set to FAILED or CLOSED.