diff options
-rw-r--r-- | remoting/host/chromoting_host.cc | 3 | ||||
-rw-r--r-- | remoting/host/chromoting_host.h | 5 | ||||
-rw-r--r-- | remoting/host/plugin/host_script_object.cc | 3 | ||||
-rw-r--r-- | remoting/protocol/connection_to_host.cc | 3 | ||||
-rw-r--r-- | remoting/protocol/fake_session.cc | 10 | ||||
-rw-r--r-- | remoting/protocol/fake_session.h | 5 | ||||
-rw-r--r-- | remoting/protocol/jingle_datagram_connector.cc | 3 | ||||
-rw-r--r-- | remoting/protocol/jingle_session.cc | 12 | ||||
-rw-r--r-- | remoting/protocol/jingle_session.h | 9 | ||||
-rw-r--r-- | remoting/protocol/jingle_session_unittest.cc | 119 | ||||
-rw-r--r-- | remoting/protocol/jingle_stream_connector.cc | 193 | ||||
-rw-r--r-- | remoting/protocol/jingle_stream_connector.h | 29 | ||||
-rw-r--r-- | remoting/protocol/protocol_mock_objects.h | 2 | ||||
-rw-r--r-- | remoting/protocol/session.h | 4 |
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(¬_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(¬_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(¬_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(¬_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. |