summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--remoting/protocol/secure_p2p_socket.cc307
-rw-r--r--remoting/protocol/secure_p2p_socket.h84
-rw-r--r--remoting/protocol/secure_p2p_socket_unittest.cc127
-rw-r--r--remoting/remoting.gyp4
4 files changed, 522 insertions, 0 deletions
diff --git a/remoting/protocol/secure_p2p_socket.cc b/remoting/protocol/secure_p2p_socket.cc
new file mode 100644
index 0000000..d73912c
--- /dev/null
+++ b/remoting/protocol/secure_p2p_socket.cc
@@ -0,0 +1,307 @@
+// 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.
+
+#include "remoting/protocol/secure_p2p_socket.h"
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "crypto/symmetric_key.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+using net::CompletionCallback;
+using net::IOBuffer;
+
+namespace remoting {
+namespace protocol {
+
+namespace {
+const uint8 kMaskSalt[16] = {0xDB, 0x68, 0xB5, 0xFD, 0x17, 0x0E, 0x15, 0x77,
+ 0x56, 0xAF, 0x7A, 0x3A, 0x1A, 0x57, 0x75, 0x02};
+const uint8 kHashSalt[16] = {0x4E, 0x2F, 0x96, 0xAB, 0x0A, 0x39, 0x92, 0xA2,
+ 0x56, 0x94, 0x91, 0xF5, 0x7E, 0x58, 0x2E, 0xFA};
+const uint8 kFrameType[4] = {0x0, 0x0, 0x0, 0x1};
+const int kFrameTypeSize = sizeof(kFrameType);
+const size_t kKeySize = 16;
+const int kHeaderSize = 44;
+const int kSeqNumberSize = 8;
+const int kHashPosition = 0;
+const int kNoncePosition = kKeySize;
+const int kRawMessagePosition = kNoncePosition + kKeySize;
+const int kSeqNumberPosition = kRawMessagePosition;
+const int kFrameTypePosition = kSeqNumberPosition + kSeqNumberSize;
+const int kMessagePosition = kFrameTypePosition + kFrameTypeSize;
+const int kReadBufferSize = 65536;
+const std::string kMaskSaltStr(
+ reinterpret_cast<const char*>(kMaskSalt), kKeySize);
+const std::string kHashSaltStr(
+ reinterpret_cast<const char*>(kHashSalt), kKeySize);
+
+inline void SetBE64(void* memory, uint64 v) {
+ uint8* mem_ptr = reinterpret_cast<uint8*>(memory);
+
+ mem_ptr[0] = static_cast<uint8>(v >> 56);
+ mem_ptr[1] = static_cast<uint8>(v >> 48);
+ mem_ptr[2] = static_cast<uint8>(v >> 40);
+ mem_ptr[3] = static_cast<uint8>(v >> 32);
+ mem_ptr[4] = static_cast<uint8>(v >> 24);
+ mem_ptr[5] = static_cast<uint8>(v >> 16);
+ mem_ptr[6] = static_cast<uint8>(v >> 8);
+ mem_ptr[7] = static_cast<uint8>(v >> 0);
+}
+
+inline uint64 GetBE64(const void* memory) {
+ const uint8* mem_ptr = reinterpret_cast<const uint8*>(memory);
+
+ return (static_cast<uint64>(mem_ptr[0]) << 56) |
+ (static_cast<uint64>(mem_ptr[1]) << 48) |
+ (static_cast<uint64>(mem_ptr[2]) << 40) |
+ (static_cast<uint64>(mem_ptr[3]) << 32) |
+ (static_cast<uint64>(mem_ptr[4]) << 24) |
+ (static_cast<uint64>(mem_ptr[5]) << 16) |
+ (static_cast<uint64>(mem_ptr[6]) << 8) |
+ (static_cast<uint64>(mem_ptr[7]) << 0);
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////
+// SecureP2PSocket Implementation.
+SecureP2PSocket::SecureP2PSocket(Socket* socket, const std::string& ice_key)
+ : socket_(socket),
+ write_seq_(0),
+ read_seq_(0),
+ user_read_callback_(NULL),
+ user_read_buf_len_(0),
+ user_write_callback_(NULL),
+ user_write_buf_len_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ read_callback_(NewCallback(this, &SecureP2PSocket::ReadDone))),
+ read_buf_(new net::IOBufferWithSize(kReadBufferSize)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ write_callback_(NewCallback(this, &SecureP2PSocket::WriteDone))),
+ msg_hasher_(crypto::HMAC::SHA1) {
+ // Make sure the key is valid.
+ CHECK(ice_key.size() == kKeySize);
+
+ // Create the mask key from ice key.
+ crypto::HMAC mask_hasher(crypto::HMAC::SHA1);
+ bool ret = mask_hasher.Init(
+ reinterpret_cast<const unsigned char*>(ice_key.data()), kKeySize);
+ DCHECK(ret) << "Initialize HMAC-SHA1 for mask failed.";
+ scoped_array<uint8> mask_digest(new uint8[mask_hasher.DigestLength()]);
+ mask_hasher.Sign(kMaskSaltStr, mask_digest.get(),
+ mask_hasher.DigestLength());
+ mask_key_.reset(crypto::SymmetricKey::Import(
+ crypto::SymmetricKey::AES,
+ std::string(mask_digest.get(), mask_digest.get() + kKeySize)));
+ DCHECK(mask_key_.get()) << "Import symmetric key failed.";
+
+ // Initialize the encryptor with mask key.
+ encryptor_.Init(mask_key_.get(), crypto::Encryptor::CTR, "");
+
+ // Create the hash key from ice key.
+ crypto::HMAC hash_hasher(crypto::HMAC::SHA1);
+ ret = hash_hasher.Init(
+ reinterpret_cast<const unsigned char*>(ice_key.data()), kKeySize);
+ DCHECK(ret) << "Initialize HMAC-SHA1 for hash failed.";
+ scoped_array<uint8> hash_key(new uint8[hash_hasher.DigestLength()]);
+ hash_hasher.Sign(kHashSaltStr, hash_key.get(), hash_hasher.DigestLength());
+
+ // Create a hasher for message.
+ ret = msg_hasher_.Init(hash_key.get(), kKeySize);
+ DCHECK(ret) << "Initialize HMAC-SHA1 for message failed.";
+}
+
+int SecureP2PSocket::Read(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(!user_read_buf_);
+ DCHECK(!user_read_buf_len_);
+ DCHECK(!user_read_callback_);
+
+ user_read_buf_ = buf;
+ user_read_buf_len_ = buf_len;
+ user_read_callback_ = callback;
+ return ReadInternal();
+}
+
+int SecureP2PSocket::Write(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ // See the spec for the steps taken in this method:
+ // http://www.whatwg.org/specs/web-apps/current-work/complete/video-conferencing-and-peer-to-peer-communication.html#peer-to-peer-connections
+ // 4. Increment sequence number by one.
+ ++write_seq_;
+
+ const int encrypted_buffer_size = kHeaderSize + buf_len;
+ scoped_refptr<net::IOBuffer> encrypted_buf =
+ new net::IOBuffer(encrypted_buffer_size);
+
+ // 6. Concatenate to form the raw message.
+ const int kRawMessageSize = kSeqNumberSize + kFrameTypeSize + buf_len;
+ std::string raw_message;
+ raw_message.resize(kRawMessageSize);
+ char* raw_message_buf = const_cast<char*>(raw_message.data());
+ SetBE64(raw_message_buf, write_seq_);
+ memcpy(raw_message_buf + kSeqNumberSize, kFrameType,
+ kFrameTypeSize);
+ memcpy(raw_message_buf + kSeqNumberSize + kFrameTypeSize,
+ buf->data(), buf_len);
+
+ // 7. Encrypt the message.
+ std::string nonce = base::RandBytesAsString(kKeySize);
+ CHECK(encryptor_.SetCounter(nonce));
+ std::string encrypted_message;
+ CHECK(encryptor_.Encrypt(raw_message, &encrypted_message));
+ memcpy(encrypted_buf->data() + kRawMessagePosition,
+ encrypted_message.data(), encrypted_message.size());
+
+ // 8. Concatenate nonce and encrypted message to form masked message.
+ memcpy(encrypted_buf->data() + kNoncePosition, nonce.data(), kKeySize);
+
+ // 10. Create hash from masked message with nonce.
+ scoped_array<uint8> msg_digest(new uint8[msg_hasher_.DigestLength()]);
+ msg_hasher_.Sign(
+ base::StringPiece(encrypted_buf->data() + kNoncePosition,
+ kRawMessageSize + kKeySize),
+ msg_digest.get(), msg_hasher_.DigestLength());
+ memcpy(encrypted_buf->data() + kHashPosition, msg_digest.get(), kKeySize);
+
+ // Write to the socket.
+ int ret = socket_->Write(encrypted_buf, encrypted_buffer_size,
+ write_callback_.get());
+ if (ret == net::ERR_IO_PENDING) {
+ DCHECK(callback);
+ user_write_callback_ = callback;
+ user_write_buf_len_ = buf_len;
+ return ret;
+ } else if (ret < 0) {
+ return ret;
+ }
+ DCHECK_EQ(buf_len + kHeaderSize, ret);
+ return buf_len;
+}
+
+bool SecureP2PSocket::SetReceiveBufferSize(int32 size) {
+ return true;
+}
+
+bool SecureP2PSocket::SetSendBufferSize(int32 size) {
+ return true;
+}
+
+int SecureP2PSocket::ReadInternal() {
+ while (true) {
+ int ret = socket_->Read(read_buf_, kReadBufferSize, read_callback_.get());
+ if (ret == net::ERR_IO_PENDING || ret < 0)
+ return ret;
+
+ ret = DecryptBuffer(ret);
+
+ // Can't decrypt the message so try again.
+ if (ret == net::ERR_INVALID_RESPONSE)
+ continue;
+
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ user_read_callback_ = NULL;
+ return ret;
+ }
+}
+
+void SecureP2PSocket::ReadDone(int err) {
+ net::CompletionCallback* callback = user_read_callback_;
+ user_read_callback_ = NULL;
+
+ if (err < 0) {
+ user_read_buf_len_ = 0;
+ user_read_buf_ = NULL;
+ callback->Run(err);
+ return;
+ }
+
+ int ret = DecryptBuffer(err);
+ if (ret == net::ERR_INVALID_RESPONSE)
+ ret = ReadInternal();
+ if (ret == net::ERR_IO_PENDING)
+ return;
+
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ callback->Run(ret);
+}
+
+void SecureP2PSocket::WriteDone(int err) {
+ net::CompletionCallback* callback = user_write_callback_;
+ int buf_len = user_write_buf_len_;
+
+ user_write_callback_ = NULL;
+ user_write_buf_len_ = 0;
+
+ if (err >= 0) {
+ DCHECK_EQ(buf_len + kHeaderSize, err);
+ callback->Run(buf_len);
+ return;
+ }
+ callback->Run(err);
+}
+
+int SecureP2PSocket::DecryptBuffer(int size) {
+ if (size < kRawMessagePosition)
+ return net::ERR_INVALID_RESPONSE;
+
+ // See the spec for the steps taken in this method:
+ // http://www.whatwg.org/specs/web-apps/current-work/complete/video-conferencing-and-peer-to-peer-communication.html#peer-to-peer-connections
+ // 5. Compute hash of the message.
+ scoped_array<uint8> msg_digest(new uint8[msg_hasher_.DigestLength()]);
+ msg_hasher_.Sign(
+ base::StringPiece(read_buf_->data() + kNoncePosition,
+ size - kNoncePosition),
+ msg_digest.get(), msg_hasher_.DigestLength());
+
+ // 6. Compare the hash values.
+ int ret = memcmp(read_buf_->data(), msg_digest.get(), kKeySize);
+ if (ret)
+ return net::ERR_INVALID_RESPONSE;
+
+ // 7. Decrypt the message.
+ std::string nonce = std::string(
+ read_buf_->data() + kNoncePosition, kKeySize);
+ CHECK(encryptor_.SetCounter(nonce));
+ const int raw_message_size = size - kRawMessagePosition;
+
+ // TODO(hclam): Change Encryptor API to trim this memcpy.
+ std::string encrypted_message(read_buf_->data() + kRawMessagePosition,
+ raw_message_size);
+ std::string raw_message;
+ CHECK(encryptor_.Decrypt(encrypted_message, &raw_message));
+
+ if (raw_message_size < kSeqNumberSize)
+ return net::ERR_INVALID_RESPONSE;
+
+ // 12. Read the sequence number.
+ uint64 seq_number = GetBE64(raw_message.data());
+
+ // The spec says we reject the packet if it is out of order. We don't do
+ // this so allow upper levels to do reordering.
+
+ // 14. Save the most recent sequence number.
+ read_seq_ = seq_number;
+
+ // 15. Parse the frame type.
+ if (raw_message_size < kSeqNumberSize + kFrameTypeSize)
+ return net::ERR_INVALID_RESPONSE;
+ ret = memcmp(raw_message.data() + kSeqNumberSize, kFrameType,
+ kFrameTypeSize);
+ if (ret)
+ return net::ERR_INVALID_RESPONSE;
+
+ // 16. Read the message.
+ const int kMessageSize = raw_message_size - kSeqNumberSize - kFrameTypeSize;
+ memcpy(user_read_buf_->data(),
+ raw_message.data() + kSeqNumberSize + kFrameTypeSize, kMessageSize);
+ return kMessageSize;
+}
+
+} // namespace protocol
+} // namespace remoting
diff --git a/remoting/protocol/secure_p2p_socket.h b/remoting/protocol/secure_p2p_socket.h
new file mode 100644
index 0000000..19ba1a1
--- /dev/null
+++ b/remoting/protocol/secure_p2p_socket.h
@@ -0,0 +1,84 @@
+// 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.
+
+// Implement a secure P2P socket according to the W3C spec
+//
+// "Video conferencing and peer-to-peer communication"
+// http://www.whatwg.org/specs/web-apps/current-work/complete/video-conferencing-and-peer-to-peer-communication.html#peer-to-peer-connections
+//
+// This class operates on an establish socket to perform encryption for P2P
+// connection. This class does not perform chunking for outgoing buffers, all
+// outgoing buffers have to be 44 bytes smaller than MTU to allow space for
+// header to support encryption.
+
+#ifndef REMOTING_PROTOCOL_SECURE_P2P_SOCKET_H_
+#define REMOTING_PROTOCOL_SOCKET_P2P_SOCKET_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/encryptor.h"
+#include "crypto/hmac.h"
+#include "net/socket/socket.h"
+
+namespace crypto {
+class SymmetricKey;
+} // namespace crypto
+
+namespace net {
+class IOBufferWithSize;
+} // namespace net
+
+namespace remoting {
+namespace protocol {
+
+class SecureP2PSocket : public net::Socket {
+ public:
+ // Construct a secured P2P socket using |socket| as the underlying
+ // socket. Ownership of |socket| is transfered to this object.
+ SecureP2PSocket(net::Socket* socket, const std::string& ice_key);
+
+ // Socket implementation.
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback);
+ virtual int Write(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback);
+ virtual bool SetReceiveBufferSize(int32 size);
+ virtual bool SetSendBufferSize(int32 size);
+
+ private:
+ int ReadInternal();
+ void ReadDone(int err);
+ void WriteDone(int err);
+ int DecryptBuffer(int size);
+
+ scoped_ptr<net::Socket> socket_;
+
+ uint64 write_seq_;
+ uint64 read_seq_;
+
+ net::CompletionCallback* user_read_callback_;
+ scoped_refptr<net::IOBuffer> user_read_buf_;
+ int user_read_buf_len_;
+
+ net::CompletionCallback* user_write_callback_;
+ int user_write_buf_len_;
+
+ scoped_ptr<net::CompletionCallback> read_callback_;
+ scoped_refptr<net::IOBufferWithSize> read_buf_;
+
+ scoped_ptr<net::CompletionCallback> write_callback_;
+
+ scoped_ptr<crypto::SymmetricKey> mask_key_;
+ crypto::HMAC msg_hasher_;
+ crypto::Encryptor encryptor_;
+
+ DISALLOW_COPY_AND_ASSIGN(SecureP2PSocket);
+};
+
+} // namespace protocol
+} // namespace remoting
+
+#endif // REMOTING_PROTOCOL_SOCKET_P2P_SOCKET_H_
diff --git a/remoting/protocol/secure_p2p_socket_unittest.cc b/remoting/protocol/secure_p2p_socket_unittest.cc
new file mode 100644
index 0000000..2d46794
--- /dev/null
+++ b/remoting/protocol/secure_p2p_socket_unittest.cc
@@ -0,0 +1,127 @@
+// 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.
+
+#include <queue>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stringprintf.h"
+#include "crypto/symmetric_key.h"
+#include "net/base/io_buffer.h"
+#include "remoting/protocol/secure_p2p_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+namespace protocol {
+
+namespace {
+class TestSocket : public net::Socket {
+ public:
+ TestSocket() {}
+
+ // Socket implementation.
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ std::string buffer = buffer_.front();
+ buffer_.pop();
+
+ memcpy(buf->data(), buffer.data(), buffer.length());
+ int size = buffer.length();
+ return size;
+ }
+
+ virtual int Write(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ buffer_.push(std::string(buf->data(), buf_len));
+ return buf_len;
+ }
+
+ virtual bool SetReceiveBufferSize(int32 size) {
+ return true;
+ }
+
+ virtual bool SetSendBufferSize(int32 size) {
+ return true;
+ }
+
+ std::string GetFrontBuffer() const {
+ return buffer_.front();
+ }
+
+ void PushBuffer(std::string buffer) {
+ buffer_.push(buffer);
+ }
+
+ private:
+ std::queue<std::string> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSocket);
+};
+} // namespace
+
+// AES-CTR is only implemented with NSS.
+#if defined(USE_NSS)
+
+TEST(SecureP2PSocketTest, WriteAndRead) {
+ TestSocket* test_socket = new TestSocket();
+ SecureP2PSocket secure_socket(test_socket, "1234567890123456");
+
+ const std::string kWritePattern = "Hello world! This is a nice day.";
+ scoped_refptr<net::IOBuffer> write_buf =
+ new net::StringIOBuffer(kWritePattern);
+ scoped_refptr<net::IOBuffer> read_buf = new net::IOBufferWithSize(2048);
+
+ for (int i = 0; i < 5; ++i) {
+ size_t written = secure_socket.Write(write_buf,
+ kWritePattern.length(), NULL);
+ EXPECT_EQ(kWritePattern.length(), written);
+ EXPECT_EQ(kWritePattern.length() + 44,
+ test_socket->GetFrontBuffer().length());
+
+ std::string hex_packet;
+ for (size_t j = 0; j < test_socket->GetFrontBuffer().length(); ++j) {
+ base::StringAppendF(&hex_packet, "%02x",
+ (uint8)test_socket->GetFrontBuffer()[j]);
+ }
+ LOG(INFO) << hex_packet;
+
+ size_t read = secure_socket.Read(read_buf, 2048, NULL);
+ EXPECT_EQ(kWritePattern.length(), read);
+ EXPECT_EQ(0, memcmp(kWritePattern.data(), read_buf->data(),
+ kWritePattern.length()));
+ }
+}
+
+TEST(SecureP2PSocketTest, ReadRetry) {
+ TestSocket* test_socket = new TestSocket();
+ SecureP2PSocket secure_socket(test_socket, "1234567890123456");
+
+ const std::string kWritePattern = "Hello world! This is a nice day.";
+ scoped_refptr<net::IOBuffer> write_buf =
+ new net::StringIOBuffer(kWritePattern);
+ scoped_refptr<net::IOBuffer> read_buf = new net::IOBufferWithSize(2048);
+
+ // Push some garbage to the socket.
+ test_socket->PushBuffer("0");
+ test_socket->PushBuffer("00");
+ test_socket->PushBuffer("0000");
+ test_socket->PushBuffer("00000000");
+ test_socket->PushBuffer("0000000000000000");
+ test_socket->PushBuffer("00000000000000000000000000000000");
+
+ // Then write some real stuff.
+ size_t written = secure_socket.Write(write_buf,
+ kWritePattern.length(), NULL);
+ EXPECT_EQ(kWritePattern.length(), written);
+
+ size_t read = secure_socket.Read(read_buf, 2048, NULL);
+ EXPECT_EQ(kWritePattern.length(), read);
+ EXPECT_EQ(0, memcmp(kWritePattern.data(), read_buf->data(),
+ kWritePattern.length()));
+}
+
+#endif
+
+} // namespace protocol
+} // namespace remoting
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 0be1a37..ade5d75 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -594,6 +594,8 @@
'protocol/rtp_video_writer.h',
'protocol/rtp_writer.cc',
'protocol/rtp_writer.h',
+ 'protocol/secure_p2p_socket.cc',
+ 'protocol/secure_p2p_socket.h',
'protocol/session.h',
'protocol/session_config.cc',
'protocol/session_config.h',
@@ -733,6 +735,7 @@
'protocol/protocol_mock_objects.h',
'protocol/rtp_video_reader_unittest.cc',
'protocol/rtp_video_writer_unittest.cc',
+ 'protocol/secure_p2p_socket_unittest.cc',
'protocol/session_manager_pair.cc',
'protocol/session_manager_pair.h',
'run_all_unittests.cc',
@@ -746,6 +749,7 @@
# ../base/test_suite.h
# gtk/gtk.h
'../build/linux/system.gyp:gtk',
+ '../build/linux/system.gyp:ssl',
],
'conditions': [
[ 'linux_use_tcmalloc==1', {