diff options
-rw-r--r-- | remoting/protocol/secure_p2p_socket.cc | 307 | ||||
-rw-r--r-- | remoting/protocol/secure_p2p_socket.h | 84 | ||||
-rw-r--r-- | remoting/protocol/secure_p2p_socket_unittest.cc | 127 | ||||
-rw-r--r-- | remoting/remoting.gyp | 4 |
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', { |