| /* |
| * libjingle |
| * Copyright 2011, Google Inc. |
| * Portions Copyright 2011, RTFM, Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| |
| #include <string> |
| #include <set> |
| |
| #include "talk/base/gunit.h" |
| #include "talk/base/helpers.h" |
| #include "talk/base/ssladapter.h" |
| #include "talk/base/sslidentity.h" |
| #include "talk/base/sslstreamadapter.h" |
| #include "talk/base/stream.h" |
| |
| static const int kBlockSize = 4096; |
| |
| class SSLStreamAdapterTestBase; |
| |
| class SSLDummyStream : public talk_base::StreamInterface, |
| public sigslot::has_slots<> { |
| public: |
| explicit SSLDummyStream(SSLStreamAdapterTestBase *test, |
| const std::string &side, |
| talk_base::FifoBuffer *in, |
| talk_base::FifoBuffer *out) : |
| test_(test), |
| side_(side), |
| in_(in), |
| out_(out), |
| first_packet_(true) { |
| in_->SignalEvent.connect(this, &SSLDummyStream::OnEventIn); |
| out_->SignalEvent.connect(this, &SSLDummyStream::OnEventOut); |
| } |
| |
| virtual talk_base::StreamState GetState() const { return talk_base::SS_OPEN; } |
| |
| virtual talk_base::StreamResult Read(void* buffer, size_t buffer_len, |
| size_t* read, int* error) { |
| talk_base::StreamResult r; |
| |
| r = in_->Read(buffer, buffer_len, read, error); |
| if (r == talk_base::SR_BLOCK) |
| return talk_base::SR_BLOCK; |
| if (r == talk_base::SR_EOS) |
| return talk_base::SR_EOS; |
| |
| if (r != talk_base::SR_SUCCESS) { |
| ADD_FAILURE(); |
| return talk_base::SR_ERROR; |
| } |
| |
| return talk_base::SR_SUCCESS; |
| } |
| |
| // Catch readability events on in and pass them up. |
| virtual void OnEventIn(talk_base::StreamInterface *stream, int sig, |
| int err) { |
| int mask = (talk_base::SE_READ | talk_base::SE_CLOSE); |
| |
| if (sig & mask) { |
| LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ << " sig=" |
| << sig << " forwarding upward"; |
| PostEvent(sig & mask, 0); |
| } |
| } |
| |
| // Catch writeability events on out and pass them up. |
| virtual void OnEventOut(talk_base::StreamInterface *stream, int sig, |
| int err) { |
| if (sig & talk_base::SE_WRITE) { |
| LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ << " sig=" |
| << sig << " forwarding upward"; |
| |
| PostEvent(sig & talk_base::SE_WRITE, 0); |
| } |
| } |
| |
| // Write to the outgoing FifoBuffer |
| talk_base::StreamResult WriteData(const void* data, size_t data_len, |
| size_t* written, int* error) { |
| return out_->Write(data, data_len, written, error); |
| } |
| |
| // Defined later |
| virtual talk_base::StreamResult Write(const void* data, size_t data_len, |
| size_t* written, int* error); |
| |
| virtual void Close() { |
| LOG(LS_INFO) << "Closing outbound stream"; |
| out_->Close(); |
| } |
| |
| private: |
| SSLStreamAdapterTestBase *test_; |
| const std::string side_; |
| talk_base::FifoBuffer *in_; |
| talk_base::FifoBuffer *out_; |
| bool first_packet_; |
| }; |
| |
| class SSLStreamAdapterTestBase : public testing::Test, |
| public sigslot::has_slots<> { |
| public: |
| explicit SSLStreamAdapterTestBase(bool dtls) : |
| client_buffer_(4096), server_buffer_(4096), |
| client_stream_( |
| new SSLDummyStream(this, "c2s", &client_buffer_, &server_buffer_)), |
| server_stream_( |
| new SSLDummyStream(this, "s2c", &server_buffer_, &client_buffer_)), |
| client_ssl_(talk_base::SSLStreamAdapter::Create(client_stream_)), |
| server_ssl_(talk_base::SSLStreamAdapter::Create(server_stream_)), |
| client_identity_(NULL), server_identity_(NULL), |
| delay_(0), mtu_(1460), loss_(0), lose_first_packet_(false), dtls_(dtls), |
| handshake_wait_(5000), identities_set_(false) { |
| // Set use of the test RNG to get predictable loss patterns. |
| talk_base::SetRandomTestMode(true); |
| |
| LOG(LS_INFO) << "Setup"; |
| talk_base::InitializeSSL(); |
| |
| // Set up the slots |
| client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); |
| server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); |
| |
| client_identity_ = talk_base::SSLIdentity::Generate("client"); |
| server_identity_ = talk_base::SSLIdentity::Generate("server"); |
| |
| client_ssl_->SetIdentity(client_identity_); |
| server_ssl_->SetIdentity(server_identity_); |
| } |
| |
| ~SSLStreamAdapterTestBase() { |
| // Put it back for the next test. |
| talk_base::SetRandomTestMode(false); |
| } |
| |
| virtual void OnEvent(talk_base::StreamInterface *stream, int sig, int err) { |
| LOG(LS_INFO) << "SSLStreamAdapterTestBase::OnEvent sig=" << sig; |
| |
| if (sig & talk_base::SE_READ) { |
| ReadData(stream); |
| } |
| |
| if ((stream == client_ssl_.get()) && (sig & talk_base::SE_WRITE)) { |
| WriteData(); |
| } |
| } |
| |
| void SetPeerIdentitiesByCertificate(bool correct) { |
| LOG(LS_INFO) << "Setting peer identities by certificate"; |
| |
| if (correct) { |
| client_ssl_->SetPeerCertificate(server_identity_->certificate(). |
| GetReference()); |
| server_ssl_->SetPeerCertificate(client_identity_->certificate(). |
| GetReference()); |
| } else { |
| // If incorrect, set up to expect our own certificate at the peer |
| client_ssl_->SetPeerCertificate(client_identity_->certificate(). |
| GetReference()); |
| server_ssl_->SetPeerCertificate(server_identity_->certificate(). |
| GetReference()); |
| } |
| identities_set_ = true; |
| } |
| |
| void SetPeerIdentitiesByDigest(bool correct) { |
| unsigned char digest[20]; |
| size_t digest_len; |
| bool rv; |
| |
| LOG(LS_INFO) << "Setting peer identities by digest"; |
| |
| rv = server_identity_->certificate().ComputeDigest(talk_base::DIGEST_SHA_1, |
| digest, 20, |
| &digest_len); |
| ASSERT_TRUE(rv); |
| if (!correct) { |
| LOG(LS_INFO) << "Setting bogus digest for server cert"; |
| digest[0]++; |
| } |
| rv = client_ssl_->SetPeerCertificateDigest(talk_base::DIGEST_SHA_1, digest, |
| digest_len); |
| ASSERT_TRUE(rv); |
| |
| |
| rv = client_identity_->certificate().ComputeDigest(talk_base::DIGEST_SHA_1, |
| digest, 20, &digest_len); |
| ASSERT_TRUE(rv); |
| if (!correct) { |
| LOG(LS_INFO) << "Setting bogus digest for client cert"; |
| digest[0]++; |
| } |
| rv = server_ssl_->SetPeerCertificateDigest(talk_base::DIGEST_SHA_1, digest, |
| digest_len); |
| ASSERT_TRUE(rv); |
| |
| identities_set_ = true; |
| } |
| |
| void TestHandshake(bool expect_success = true) { |
| server_ssl_->SetMode(dtls_ ? talk_base::SSL_MODE_DTLS : |
| talk_base::SSL_MODE_TLS); |
| client_ssl_->SetMode(dtls_ ? talk_base::SSL_MODE_DTLS : |
| talk_base::SSL_MODE_TLS); |
| |
| if (!dtls_) { |
| // Make sure we simulate a reliable network for TLS. |
| // This is just a check to make sure that people don't write wrong |
| // tests. |
| ASSERT((mtu_ == 1460) && (loss_ == 0) && (lose_first_packet_ == 0)); |
| } |
| |
| if (!identities_set_) |
| SetPeerIdentitiesByDigest(true); |
| |
| // Start the handshake |
| int rv; |
| |
| server_ssl_->SetServerRole(); |
| rv = server_ssl_->StartSSLWithPeer(); |
| ASSERT_EQ(0, rv); |
| |
| rv = client_ssl_->StartSSLWithPeer(); |
| ASSERT_EQ(0, rv); |
| |
| // Now run the handshake |
| if (expect_success) { |
| EXPECT_TRUE_WAIT((client_ssl_->GetState() == talk_base::SS_OPEN) |
| && (server_ssl_->GetState() == talk_base::SS_OPEN), |
| handshake_wait_); |
| } else { |
| EXPECT_TRUE_WAIT(client_ssl_->GetState() == talk_base::SS_CLOSED, |
| handshake_wait_); |
| } |
| } |
| |
| talk_base::StreamResult DataWritten(SSLDummyStream *from, const void *data, |
| size_t data_len, size_t *written, |
| int *error) { |
| // Randomly drop loss_ percent of packets |
| if (talk_base::CreateRandomId() % 100 < static_cast<uint32>(loss_)) { |
| LOG(LS_INFO) << "Randomly dropping packet, size=" << data_len; |
| *written = data_len; |
| return talk_base::SR_SUCCESS; |
| } |
| if (dtls_ && (data_len > mtu_)) { |
| LOG(LS_INFO) << "Dropping packet > mtu, size=" << data_len; |
| *written = data_len; |
| return talk_base::SR_SUCCESS; |
| } |
| |
| return from->WriteData(data, data_len, written, error); |
| } |
| |
| void SetDelay(int delay) { |
| delay_ = delay; |
| } |
| int GetDelay() { return delay_; } |
| |
| void SetLoseFirstPacket(bool lose) { |
| lose_first_packet_ = lose; |
| } |
| bool GetLoseFirstPacket() { return lose_first_packet_; } |
| |
| void SetLoss(int percent) { |
| loss_ = percent; |
| } |
| |
| void SetMtu(size_t mtu) { |
| mtu_ = mtu; |
| } |
| |
| void SetHandshakeWait(int wait) { |
| handshake_wait_ = wait; |
| } |
| |
| // To be implemented by subclasses. |
| virtual void WriteData() = 0; |
| virtual void ReadData(talk_base::StreamInterface *stream) = 0; |
| virtual void TestTransfer(int size) = 0; |
| |
| protected: |
| talk_base::FifoBuffer client_buffer_; |
| talk_base::FifoBuffer server_buffer_; |
| SSLDummyStream *client_stream_; // freed by client_ssl_ destructor |
| SSLDummyStream *server_stream_; // freed by server_ssl_ destructor |
| talk_base::scoped_ptr<talk_base::SSLStreamAdapter> client_ssl_; |
| talk_base::scoped_ptr<talk_base::SSLStreamAdapter> server_ssl_; |
| talk_base::SSLIdentity *client_identity_; // freed by client_ssl_ destructor |
| talk_base::SSLIdentity *server_identity_; // freed by server_ssl_ destructor |
| int delay_; |
| size_t mtu_; |
| int loss_; |
| bool lose_first_packet_; |
| bool dtls_; |
| int handshake_wait_; |
| bool identities_set_; |
| }; |
| |
| |
| class SSLStreamAdapterTestTLS : public SSLStreamAdapterTestBase { |
| public: |
| SSLStreamAdapterTestTLS() : |
| SSLStreamAdapterTestBase(false) { |
| }; |
| |
| // Test data transfer for TLS |
| virtual void TestTransfer(int size) { |
| LOG(LS_INFO) << "Starting transfer test with " << size << " bytes"; |
| // Create some dummy data to send. |
| size_t received; |
| |
| send_stream_.ReserveSize(size); |
| for (int i = 0; i < size; ++i) { |
| char ch = static_cast<char>(i); |
| send_stream_.Write(&ch, 1, NULL, NULL); |
| } |
| send_stream_.Rewind(); |
| |
| // Prepare the receive stream. |
| recv_stream_.ReserveSize(size); |
| |
| // Start sending |
| WriteData(); |
| |
| // Wait for the client to close |
| EXPECT_TRUE_WAIT(server_ssl_->GetState() == talk_base::SS_CLOSED, 10000); |
| |
| // Now check the data |
| recv_stream_.GetSize(&received); |
| EXPECT_EQ(static_cast<size_t>(size), received); |
| EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(), |
| recv_stream_.GetBuffer(), size)); |
| } |
| |
| void WriteData() { |
| size_t position, tosend, size; |
| talk_base::StreamResult rv; |
| size_t sent; |
| char block[kBlockSize]; |
| |
| send_stream_.GetSize(&size); |
| if (!size) |
| return; |
| |
| for (;;) { |
| send_stream_.GetPosition(&position); |
| if (send_stream_.Read(block, sizeof(block), &tosend, NULL) != |
| talk_base::SR_EOS) { |
| rv = client_ssl_->Write(block, tosend, &sent, 0); |
| |
| if (rv == talk_base::SR_SUCCESS) { |
| send_stream_.SetPosition(position + sent); |
| LOG(LS_VERBOSE) << "Sent: " << position + sent; |
| } else if (rv == talk_base::SR_BLOCK) { |
| LOG(LS_VERBOSE) << "Blocked..."; |
| send_stream_.SetPosition(position); |
| break; |
| } else { |
| ADD_FAILURE(); |
| break; |
| } |
| } else { |
| // Now close |
| LOG(LS_INFO) << "Wrote " << position << " bytes. Closing"; |
| client_ssl_->Close(); |
| break; |
| } |
| } |
| }; |
| |
| virtual void ReadData(talk_base::StreamInterface *stream) { |
| char buffer[1600]; |
| size_t bread; |
| int err2; |
| talk_base::StreamResult r; |
| |
| for (;;) { |
| r = stream->Read(buffer, sizeof(buffer), &bread, &err2); |
| |
| if (r == talk_base::SR_ERROR) { |
| // Unfortunately, errors are the way that the stream adapter |
| // signals close right now |
| stream->Close(); |
| return; |
| } |
| |
| if (r == talk_base::SR_BLOCK) |
| break; |
| |
| ASSERT_EQ(talk_base::SR_SUCCESS, r); |
| LOG(LS_INFO) << "Read " << bread; |
| |
| recv_stream_.Write(buffer, bread, NULL, NULL); |
| } |
| } |
| |
| private: |
| talk_base::MemoryStream send_stream_; |
| talk_base::MemoryStream recv_stream_; |
| }; |
| |
| |
| class SSLStreamAdapterTestDTLS : public SSLStreamAdapterTestBase { |
| public: |
| SSLStreamAdapterTestDTLS() : |
| SSLStreamAdapterTestBase(true), |
| packet_size_(1000), count_(0), sent_(0) { |
| } |
| |
| virtual void WriteData() { |
| unsigned char *packet = new unsigned char[1600]; |
| |
| do { |
| memset(packet, sent_ & 0xff, sizeof(packet)); |
| *(reinterpret_cast<uint32_t *>(packet)) = sent_; |
| |
| size_t sent; |
| int rv = client_ssl_->Write(packet, packet_size_, &sent, 0); |
| if (rv == talk_base::SR_SUCCESS) { |
| LOG(LS_VERBOSE) << "Sent: " << sent_; |
| sent_++; |
| } else if (rv == talk_base::SR_BLOCK) { |
| LOG(LS_VERBOSE) << "Blocked..."; |
| break; |
| } else { |
| ADD_FAILURE(); |
| break; |
| } |
| } while (sent_ < count_); |
| |
| delete [] packet; |
| } |
| |
| virtual void ReadData(talk_base::StreamInterface *stream) { |
| unsigned char *buffer = new unsigned char[1600]; |
| size_t bread; |
| int err2; |
| talk_base::StreamResult r; |
| |
| for (;;) { |
| r = stream->Read(buffer, sizeof(buffer), |
| &bread, &err2); |
| |
| if (r == talk_base::SR_ERROR) { |
| // Unfortunately, errors are the way that the stream adapter |
| // signals close right now |
| stream->Close(); |
| return; |
| } |
| |
| if (r == talk_base::SR_BLOCK) |
| break; |
| |
| ASSERT_EQ(talk_base::SR_SUCCESS, r); |
| LOG(LS_INFO) << "Read " << bread; |
| |
| // Now parse the datagram |
| ASSERT_EQ(packet_size_, bread); |
| uint32_t packet_num = *(reinterpret_cast<uint32_t *>(buffer)); |
| |
| for (size_t i = 4; i < packet_size_; i++) { |
| ASSERT_EQ((packet_num & 0xff), buffer[i]); |
| } |
| received_.insert(packet_num); |
| } |
| } |
| |
| virtual void TestTransfer(int count) { |
| count_ = count; |
| |
| WriteData(); |
| |
| EXPECT_TRUE_WAIT(sent_ == count_, 10000); |
| LOG(LS_INFO) << "sent_ == " << sent_; |
| |
| if (loss_ == 0) { |
| EXPECT_TRUE_WAIT(static_cast<size_t>(sent_) == received_.size(), 1000); |
| } else { |
| LOG(LS_INFO) << "Sent " << sent_ << " packets; received " << |
| received_.size(); |
| } |
| }; |
| |
| private: |
| size_t packet_size_; |
| int count_; |
| int sent_; |
| std::set<int> received_; |
| }; |
| |
| |
| talk_base::StreamResult SSLDummyStream::Write(const void* data, size_t data_len, |
| size_t* written, int* error) { |
| *written = data_len; |
| |
| LOG(LS_INFO) << "Writing to loopback " << data_len; |
| |
| if (first_packet_) { |
| first_packet_ = false; |
| if (test_->GetLoseFirstPacket()) { |
| LOG(LS_INFO) << "Losing initial packet of length " << data_len; |
| return talk_base::SR_SUCCESS; |
| } |
| } |
| |
| return test_->DataWritten(this, data, data_len, written, error); |
| |
| return talk_base::SR_SUCCESS; |
| }; |
| |
| |
| // Basic tests: TLS |
| // Test that we can make a handshake work |
| TEST_F(SSLStreamAdapterTestTLS, TestTLSConnect) { |
| TestHandshake(); |
| }; |
| |
| // Test transfer -- trivial |
| TEST_F(SSLStreamAdapterTestTLS, TestTLSTransfer) { |
| TestHandshake(); |
| TestTransfer(100000); |
| }; |
| |
| // Test a handshake with a bogus peer digest |
| TEST_F(SSLStreamAdapterTestTLS, TestTLSBogusDigest) { |
| SetPeerIdentitiesByDigest(false); |
| TestHandshake(false); |
| }; |
| |
| // Test a handshake with a peer certificate |
| TEST_F(SSLStreamAdapterTestTLS, TestTLSPeerCertificate) { |
| SetPeerIdentitiesByCertificate(true); |
| TestHandshake(); |
| }; |
| |
| // Test a handshake with a bogus peer certificate |
| TEST_F(SSLStreamAdapterTestTLS, TestTLSBogusPeerCertificate) { |
| SetPeerIdentitiesByCertificate(false); |
| TestHandshake(false); |
| }; |
| // Test moving a bunch of data |
| |
| // Basic tests: DTLS |
| // Test that we can make a handshake work |
| TEST_F(SSLStreamAdapterTestDTLS, DISABLED_TestDTLSConnect) { |
| TestHandshake(); |
| }; |
| |
| // Test that we can make a handshake work if the first packet in |
| // each direction is lost. This gives us predictable loss |
| // rather than having to tune random |
| TEST_F(SSLStreamAdapterTestDTLS, DISABLED_TestDTLSConnectWithLostFirstPacket) { |
| SetLoseFirstPacket(true); |
| TestHandshake(); |
| }; |
| |
| // Test a handshake with loss and delay |
| TEST_F(SSLStreamAdapterTestDTLS, |
| DISABLED_TestDTLSConnectWithLostFirstPacketDelay2s) { |
| SetLoseFirstPacket(true); |
| SetDelay(2000); |
| SetHandshakeWait(20000); |
| TestHandshake(); |
| }; |
| |
| // Test a handshake with small MTU |
| TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithSmallMtu) { |
| SetMtu(700); |
| TestHandshake(); |
| }; |
| |
| // Test transfer -- trivial |
| TEST_F(SSLStreamAdapterTestDTLS, DISABLED_TestDTLSTransfer) { |
| TestHandshake(); |
| TestTransfer(100); |
| }; |
| |
| TEST_F(SSLStreamAdapterTestDTLS, DISABLED_TestDTLSTransferWithLoss) { |
| TestHandshake(); |
| SetLoss(10); |
| TestTransfer(100); |
| }; |
| |