/* * libjingle * Copyright 2011, Google Inc. * 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 #include "talk/base/common.h" #include "talk/base/dscp.h" #include "talk/base/gunit.h" #include "talk/base/helpers.h" #include "talk/base/scoped_ptr.h" #include "talk/base/stringutils.h" #include "talk/base/thread.h" #include "talk/p2p/base/fakesession.h" #include "talk/base/ssladapter.h" #include "talk/base/sslidentity.h" #include "talk/base/sslstreamadapter.h" #include "talk/p2p/base/dtlstransport.h" #define MAYBE_SKIP_TEST(feature) \ if (!(talk_base::SSLStreamAdapter::feature())) { \ LOG(LS_INFO) << "Feature disabled... skipping"; \ return; \ } static const char AES_CM_128_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80"; static const char kIceUfrag1[] = "TESTICEUFRAG0001"; static const char kIcePwd1[] = "TESTICEPWD00000000000001"; static const size_t kPacketNumOffset = 8; static const size_t kPacketHeaderLen = 12; static bool IsRtpLeadByte(uint8 b) { return ((b & 0xC0) == 0x80); } using cricket::ConnectionRole; enum Flags { NF_REOFFER = 0x1, NF_EXPECT_FAILURE = 0x2 }; class DtlsTestClient : public sigslot::has_slots<> { public: DtlsTestClient(const std::string& name, talk_base::Thread* signaling_thread, talk_base::Thread* worker_thread) : name_(name), signaling_thread_(signaling_thread), worker_thread_(worker_thread), protocol_(cricket::ICEPROTO_GOOGLE), packet_size_(0), use_dtls_srtp_(false), negotiated_dtls_(false), received_dtls_client_hello_(false), received_dtls_server_hello_(false) { } void SetIceProtocol(cricket::TransportProtocol proto) { protocol_ = proto; } void CreateIdentity() { identity_.reset(talk_base::SSLIdentity::Generate(name_)); } talk_base::SSLIdentity* identity() { return identity_.get(); } void SetupSrtp() { ASSERT(identity_.get() != NULL); use_dtls_srtp_ = true; } void SetupChannels(int count, cricket::IceRole role) { transport_.reset(new cricket::DtlsTransport( signaling_thread_, worker_thread_, "dtls content name", NULL, identity_.get())); transport_->SetAsync(true); transport_->SetIceRole(role); transport_->SetIceTiebreaker( (role == cricket::ICEROLE_CONTROLLING) ? 1 : 2); transport_->SignalWritableState.connect(this, &DtlsTestClient::OnTransportWritableState); for (int i = 0; i < count; ++i) { cricket::DtlsTransportChannelWrapper* channel = static_cast( transport_->CreateChannel(i)); ASSERT_TRUE(channel != NULL); channel->SignalWritableState.connect(this, &DtlsTestClient::OnTransportChannelWritableState); channel->SignalReadPacket.connect(this, &DtlsTestClient::OnTransportChannelReadPacket); channels_.push_back(channel); // Hook the raw packets so that we can verify they are encrypted. channel->channel()->SignalReadPacket.connect( this, &DtlsTestClient::OnFakeTransportChannelReadPacket); } } cricket::Transport* transport() { return transport_.get(); } cricket::FakeTransportChannel* GetFakeChannel(int component) { cricket::TransportChannelImpl* ch = transport_->GetChannel(component); cricket::DtlsTransportChannelWrapper* wrapper = static_cast(ch); return (wrapper) ? static_cast(wrapper->channel()) : NULL; } // Offer DTLS if we have an identity; pass in a remote fingerprint only if // both sides support DTLS. void Negotiate(DtlsTestClient* peer, cricket::ContentAction action, ConnectionRole local_role, ConnectionRole remote_role, int flags) { Negotiate(identity_.get(), (identity_) ? peer->identity_.get() : NULL, action, local_role, remote_role, flags); } // Allow any DTLS configuration to be specified (including invalid ones). void Negotiate(talk_base::SSLIdentity* local_identity, talk_base::SSLIdentity* remote_identity, cricket::ContentAction action, ConnectionRole local_role, ConnectionRole remote_role, int flags) { talk_base::scoped_ptr local_fingerprint; talk_base::scoped_ptr remote_fingerprint; if (local_identity) { local_fingerprint.reset(talk_base::SSLFingerprint::Create( talk_base::DIGEST_SHA_1, local_identity)); ASSERT_TRUE(local_fingerprint.get() != NULL); } if (remote_identity) { remote_fingerprint.reset(talk_base::SSLFingerprint::Create( talk_base::DIGEST_SHA_1, remote_identity)); ASSERT_TRUE(remote_fingerprint.get() != NULL); } if (use_dtls_srtp_ && !(flags & NF_REOFFER)) { // SRTP ciphers will be set only in the beginning. for (std::vector::iterator it = channels_.begin(); it != channels_.end(); ++it) { std::vector ciphers; ciphers.push_back(AES_CM_128_HMAC_SHA1_80); ASSERT_TRUE((*it)->SetSrtpCiphers(ciphers)); } } std::string transport_type = (protocol_ == cricket::ICEPROTO_GOOGLE) ? cricket::NS_GINGLE_P2P : cricket::NS_JINGLE_ICE_UDP; cricket::TransportDescription local_desc( transport_type, std::vector(), kIceUfrag1, kIcePwd1, cricket::ICEMODE_FULL, local_role, // If remote if the offerer and has no DTLS support, answer will be // without any fingerprint. (action == cricket::CA_ANSWER && !remote_identity) ? NULL : local_fingerprint.get(), cricket::Candidates()); cricket::TransportDescription remote_desc( transport_type, std::vector(), kIceUfrag1, kIcePwd1, cricket::ICEMODE_FULL, remote_role, remote_fingerprint.get(), cricket::Candidates()); bool expect_success = (flags & NF_EXPECT_FAILURE) ? false : true; // If |expect_success| is false, expect SRTD or SLTD to fail when // content action is CA_ANSWER. if (action == cricket::CA_OFFER) { ASSERT_TRUE(transport_->SetLocalTransportDescription( local_desc, cricket::CA_OFFER, NULL)); ASSERT_EQ(expect_success, transport_->SetRemoteTransportDescription( remote_desc, cricket::CA_ANSWER, NULL)); } else { ASSERT_TRUE(transport_->SetRemoteTransportDescription( remote_desc, cricket::CA_OFFER, NULL)); ASSERT_EQ(expect_success, transport_->SetLocalTransportDescription( local_desc, cricket::CA_ANSWER, NULL)); } negotiated_dtls_ = (local_identity && remote_identity); } bool Connect(DtlsTestClient* peer) { transport_->ConnectChannels(); transport_->SetDestination(peer->transport_.get()); return true; } bool writable() const { return transport_->writable(); } void CheckRole(talk_base::SSLRole role) { if (role == talk_base::SSL_CLIENT) { ASSERT_FALSE(received_dtls_client_hello_); ASSERT_TRUE(received_dtls_server_hello_); } else { ASSERT_TRUE(received_dtls_client_hello_); ASSERT_FALSE(received_dtls_server_hello_); } } void CheckSrtp(const std::string& expected_cipher) { for (std::vector::iterator it = channels_.begin(); it != channels_.end(); ++it) { std::string cipher; bool rv = (*it)->GetSrtpCipher(&cipher); if (negotiated_dtls_ && !expected_cipher.empty()) { ASSERT_TRUE(rv); ASSERT_EQ(cipher, expected_cipher); } else { ASSERT_FALSE(rv); } } } void SendPackets(size_t channel, size_t size, size_t count, bool srtp) { ASSERT(channel < channels_.size()); talk_base::scoped_ptr packet(new char[size]); size_t sent = 0; do { // Fill the packet with a known value and a sequence number to check // against, and make sure that it doesn't look like DTLS. memset(packet.get(), sent & 0xff, size); packet[0] = (srtp) ? 0x80 : 0x00; talk_base::SetBE32(packet.get() + kPacketNumOffset, static_cast(sent)); // Only set the bypass flag if we've activated DTLS. int flags = (identity_.get() && srtp) ? cricket::PF_SRTP_BYPASS : 0; talk_base::PacketOptions packet_options; int rv = channels_[channel]->SendPacket( packet.get(), size, packet_options, flags); ASSERT_GT(rv, 0); ASSERT_EQ(size, static_cast(rv)); ++sent; } while (sent < count); } int SendInvalidSrtpPacket(size_t channel, size_t size) { ASSERT(channel < channels_.size()); talk_base::scoped_ptr packet(new char[size]); // Fill the packet with 0 to form an invalid SRTP packet. memset(packet.get(), 0, size); talk_base::PacketOptions packet_options; return channels_[channel]->SendPacket( packet.get(), size, packet_options, cricket::PF_SRTP_BYPASS); } void ExpectPackets(size_t channel, size_t size) { packet_size_ = size; received_.clear(); } size_t NumPacketsReceived() { return received_.size(); } bool VerifyPacket(const char* data, size_t size, uint32* out_num) { if (size != packet_size_ || (data[0] != 0 && static_cast(data[0]) != 0x80)) { return false; } uint32 packet_num = talk_base::GetBE32(data + kPacketNumOffset); for (size_t i = kPacketHeaderLen; i < size; ++i) { if (static_cast(data[i]) != (packet_num & 0xff)) { return false; } } if (out_num) { *out_num = packet_num; } return true; } bool VerifyEncryptedPacket(const char* data, size_t size) { // This is an encrypted data packet; let's make sure it's mostly random; // less than 10% of the bytes should be equal to the cleartext packet. if (size <= packet_size_) { return false; } uint32 packet_num = talk_base::GetBE32(data + kPacketNumOffset); int num_matches = 0; for (size_t i = kPacketNumOffset; i < size; ++i) { if (static_cast(data[i]) == (packet_num & 0xff)) { ++num_matches; } } return (num_matches < ((static_cast(size) - 5) / 10)); } // Transport callbacks void OnTransportWritableState(cricket::Transport* transport) { LOG(LS_INFO) << name_ << ": is writable"; } // Transport channel callbacks void OnTransportChannelWritableState(cricket::TransportChannel* channel) { LOG(LS_INFO) << name_ << ": Channel '" << channel->component() << "' is writable"; } void OnTransportChannelReadPacket(cricket::TransportChannel* channel, const char* data, size_t size, const talk_base::PacketTime& packet_time, int flags) { uint32 packet_num = 0; ASSERT_TRUE(VerifyPacket(data, size, &packet_num)); received_.insert(packet_num); // Only DTLS-SRTP packets should have the bypass flag set. int expected_flags = (identity_.get() && IsRtpLeadByte(data[0])) ? cricket::PF_SRTP_BYPASS : 0; ASSERT_EQ(expected_flags, flags); } // Hook into the raw packet stream to make sure DTLS packets are encrypted. void OnFakeTransportChannelReadPacket(cricket::TransportChannel* channel, const char* data, size_t size, const talk_base::PacketTime& time, int flags) { // Flags shouldn't be set on the underlying TransportChannel packets. ASSERT_EQ(0, flags); // Look at the handshake packets to see what role we played. // Check that non-handshake packets are DTLS data or SRTP bypass. if (negotiated_dtls_) { if (data[0] == 22 && size > 17) { if (data[13] == 1) { received_dtls_client_hello_ = true; } else if (data[13] == 2) { received_dtls_server_hello_ = true; } } else if (!(data[0] >= 20 && data[0] <= 22)) { ASSERT_TRUE(data[0] == 23 || IsRtpLeadByte(data[0])); if (data[0] == 23) { ASSERT_TRUE(VerifyEncryptedPacket(data, size)); } else if (IsRtpLeadByte(data[0])) { ASSERT_TRUE(VerifyPacket(data, size, NULL)); } } } } private: std::string name_; talk_base::Thread* signaling_thread_; talk_base::Thread* worker_thread_; cricket::TransportProtocol protocol_; talk_base::scoped_ptr identity_; talk_base::scoped_ptr transport_; std::vector channels_; size_t packet_size_; std::set received_; bool use_dtls_srtp_; bool negotiated_dtls_; bool received_dtls_client_hello_; bool received_dtls_server_hello_; }; class DtlsTransportChannelTest : public testing::Test { public: static void SetUpTestCase() { talk_base::InitializeSSL(); } static void TearDownTestCase() { talk_base::CleanupSSL(); } DtlsTransportChannelTest() : client1_("P1", talk_base::Thread::Current(), talk_base::Thread::Current()), client2_("P2", talk_base::Thread::Current(), talk_base::Thread::Current()), channel_ct_(1), use_dtls_(false), use_dtls_srtp_(false) { } void SetChannelCount(size_t channel_ct) { channel_ct_ = static_cast(channel_ct); } void PrepareDtls(bool c1, bool c2) { if (c1) { client1_.CreateIdentity(); } if (c2) { client2_.CreateIdentity(); } if (c1 && c2) use_dtls_ = true; } void PrepareDtlsSrtp(bool c1, bool c2) { if (!use_dtls_) return; if (c1) client1_.SetupSrtp(); if (c2) client2_.SetupSrtp(); if (c1 && c2) use_dtls_srtp_ = true; } bool Connect(ConnectionRole client1_role, ConnectionRole client2_role) { Negotiate(client1_role, client2_role); bool rv = client1_.Connect(&client2_); EXPECT_TRUE(rv); if (!rv) return false; EXPECT_TRUE_WAIT(client1_.writable() && client2_.writable(), 10000); if (!client1_.writable() || !client2_.writable()) return false; // Check that we used the right roles. if (use_dtls_) { talk_base::SSLRole client1_ssl_role = (client1_role == cricket::CONNECTIONROLE_ACTIVE || (client2_role == cricket::CONNECTIONROLE_PASSIVE && client1_role == cricket::CONNECTIONROLE_ACTPASS)) ? talk_base::SSL_CLIENT : talk_base::SSL_SERVER; talk_base::SSLRole client2_ssl_role = (client2_role == cricket::CONNECTIONROLE_ACTIVE || (client1_role == cricket::CONNECTIONROLE_PASSIVE && client2_role == cricket::CONNECTIONROLE_ACTPASS)) ? talk_base::SSL_CLIENT : talk_base::SSL_SERVER; client1_.CheckRole(client1_ssl_role); client2_.CheckRole(client2_ssl_role); } // Check that we negotiated the right ciphers. if (use_dtls_srtp_) { client1_.CheckSrtp(AES_CM_128_HMAC_SHA1_80); client2_.CheckSrtp(AES_CM_128_HMAC_SHA1_80); } else { client1_.CheckSrtp(""); client2_.CheckSrtp(""); } return true; } bool Connect() { // By default, Client1 will be Server and Client2 will be Client. return Connect(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE); } void Negotiate() { Negotiate(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE); } void Negotiate(ConnectionRole client1_role, ConnectionRole client2_role) { client1_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLING); client2_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLED); // Expect success from SLTD and SRTD. client1_.Negotiate(&client2_, cricket::CA_OFFER, client1_role, client2_role, 0); client2_.Negotiate(&client1_, cricket::CA_ANSWER, client2_role, client1_role, 0); } // Negotiate with legacy client |client2|. Legacy client doesn't use setup // attributes, except NONE. void NegotiateWithLegacy() { client1_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLING); client2_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLED); // Expect success from SLTD and SRTD. client1_.Negotiate(&client2_, cricket::CA_OFFER, cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_NONE, 0); client2_.Negotiate(&client1_, cricket::CA_ANSWER, cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_NONE, 0); } void Renegotiate(DtlsTestClient* reoffer_initiator, ConnectionRole client1_role, ConnectionRole client2_role, int flags) { if (reoffer_initiator == &client1_) { client1_.Negotiate(&client2_, cricket::CA_OFFER, client1_role, client2_role, flags); client2_.Negotiate(&client1_, cricket::CA_ANSWER, client2_role, client1_role, flags); } else { client2_.Negotiate(&client1_, cricket::CA_OFFER, client2_role, client1_role, flags); client1_.Negotiate(&client2_, cricket::CA_ANSWER, client1_role, client2_role, flags); } } void TestTransfer(size_t channel, size_t size, size_t count, bool srtp) { LOG(LS_INFO) << "Expect packets, size=" << size; client2_.ExpectPackets(channel, size); client1_.SendPackets(channel, size, count, srtp); EXPECT_EQ_WAIT(count, client2_.NumPacketsReceived(), 10000); } protected: DtlsTestClient client1_; DtlsTestClient client2_; int channel_ct_; bool use_dtls_; bool use_dtls_srtp_; }; // Test that transport negotiation of ICE, no DTLS works properly. TEST_F(DtlsTransportChannelTest, TestChannelSetupIce) { client1_.SetIceProtocol(cricket::ICEPROTO_RFC5245); client2_.SetIceProtocol(cricket::ICEPROTO_RFC5245); Negotiate(); cricket::FakeTransportChannel* channel1 = client1_.GetFakeChannel(0); cricket::FakeTransportChannel* channel2 = client2_.GetFakeChannel(0); ASSERT_TRUE(channel1 != NULL); ASSERT_TRUE(channel2 != NULL); EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel1->GetIceRole()); EXPECT_EQ(1U, channel1->IceTiebreaker()); EXPECT_EQ(cricket::ICEPROTO_RFC5245, channel1->protocol()); EXPECT_EQ(kIceUfrag1, channel1->ice_ufrag()); EXPECT_EQ(kIcePwd1, channel1->ice_pwd()); EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel2->GetIceRole()); EXPECT_EQ(2U, channel2->IceTiebreaker()); EXPECT_EQ(cricket::ICEPROTO_RFC5245, channel2->protocol()); } // Test that transport negotiation of GICE, no DTLS works properly. TEST_F(DtlsTransportChannelTest, TestChannelSetupGice) { client1_.SetIceProtocol(cricket::ICEPROTO_GOOGLE); client2_.SetIceProtocol(cricket::ICEPROTO_GOOGLE); Negotiate(); cricket::FakeTransportChannel* channel1 = client1_.GetFakeChannel(0); cricket::FakeTransportChannel* channel2 = client2_.GetFakeChannel(0); ASSERT_TRUE(channel1 != NULL); ASSERT_TRUE(channel2 != NULL); EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel1->GetIceRole()); EXPECT_EQ(1U, channel1->IceTiebreaker()); EXPECT_EQ(cricket::ICEPROTO_GOOGLE, channel1->protocol()); EXPECT_EQ(kIceUfrag1, channel1->ice_ufrag()); EXPECT_EQ(kIcePwd1, channel1->ice_pwd()); EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel2->GetIceRole()); EXPECT_EQ(2U, channel2->IceTiebreaker()); EXPECT_EQ(cricket::ICEPROTO_GOOGLE, channel2->protocol()); } // Connect without DTLS, and transfer some data. TEST_F(DtlsTransportChannelTest, TestTransfer) { ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, false); } // Create two channels without DTLS, and transfer some data. TEST_F(DtlsTransportChannelTest, TestTransferTwoChannels) { SetChannelCount(2); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, false); TestTransfer(1, 1000, 100, false); } // Connect without DTLS, and transfer SRTP data. TEST_F(DtlsTransportChannelTest, TestTransferSrtp) { ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, true); } // Create two channels without DTLS, and transfer SRTP data. TEST_F(DtlsTransportChannelTest, TestTransferSrtpTwoChannels) { SetChannelCount(2); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); } // Connect with DTLS, and transfer some data. TEST_F(DtlsTransportChannelTest, TestTransferDtls) { MAYBE_SKIP_TEST(HaveDtls); PrepareDtls(true, true); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, false); } // Create two channels with DTLS, and transfer some data. TEST_F(DtlsTransportChannelTest, TestTransferDtlsTwoChannels) { MAYBE_SKIP_TEST(HaveDtls); SetChannelCount(2); PrepareDtls(true, true); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, false); TestTransfer(1, 1000, 100, false); } // Connect with A doing DTLS and B not, and transfer some data. TEST_F(DtlsTransportChannelTest, TestTransferDtlsRejected) { PrepareDtls(true, false); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, false); } // Connect with B doing DTLS and A not, and transfer some data. TEST_F(DtlsTransportChannelTest, TestTransferDtlsNotOffered) { PrepareDtls(false, true); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, false); } // Connect with DTLS, negotiate DTLS-SRTP, and transfer SRTP using bypass. TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtp) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, true); } // Connect with DTLS-SRTP, transfer an invalid SRTP packet, and expects -1 // returned. TEST_F(DtlsTransportChannelTest, TestTransferDtlsInvalidSrtpPacket) { MAYBE_SKIP_TEST(HaveDtls); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); ASSERT_TRUE(Connect()); int result = client1_.SendInvalidSrtpPacket(0, 100); ASSERT_EQ(-1, result); } // Connect with DTLS. A does DTLS-SRTP but B does not. TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpRejected) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true); PrepareDtlsSrtp(true, false); ASSERT_TRUE(Connect()); } // Connect with DTLS. B does DTLS-SRTP but A does not. TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpNotOffered) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true); PrepareDtlsSrtp(false, true); ASSERT_TRUE(Connect()); } // Create two channels with DTLS, negotiate DTLS-SRTP, and transfer bypass SRTP. TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpTwoChannels) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); } // Create a single channel with DTLS, and send normal data and SRTP data on it. TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpDemux) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, false); TestTransfer(0, 1000, 100, true); } // Testing when the remote is passive. TEST_F(DtlsTransportChannelTest, TestTransferDtlsAnswererIsPassive) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE)); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); } // Testing with the legacy DTLS client which doesn't use setup attribute. // In this case legacy is the answerer. TEST_F(DtlsTransportChannelTest, TestDtlsSetupWithLegacyAsAnswerer) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true); NegotiateWithLegacy(); talk_base::SSLRole channel1_role; talk_base::SSLRole channel2_role; EXPECT_TRUE(client1_.transport()->GetSslRole(&channel1_role)); EXPECT_TRUE(client2_.transport()->GetSslRole(&channel2_role)); EXPECT_EQ(talk_base::SSL_SERVER, channel1_role); EXPECT_EQ(talk_base::SSL_CLIENT, channel2_role); } // Testing re offer/answer after the session is estbalished. Roles will be // kept same as of the previous negotiation. TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromOfferer) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); // Initial role for client1 is ACTPASS and client2 is ACTIVE. ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE)); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); // Using input roles for the re-offer. Renegotiate(&client1_, cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE, NF_REOFFER); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); } TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromAnswerer) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); // Initial role for client1 is ACTPASS and client2 is ACTIVE. ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE)); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); // Using input roles for the re-offer. Renegotiate(&client2_, cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS, NF_REOFFER); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); } // Test that any change in role after the intial setup will result in failure. TEST_F(DtlsTransportChannelTest, TestDtlsRoleReversal) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE)); // Renegotiate from client2 with actpass and client1 as active. Renegotiate(&client2_, cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE, NF_REOFFER | NF_EXPECT_FAILURE); } // Test that using different setup attributes which results in similar ssl // role as the initial negotiation will result in success. TEST_F(DtlsTransportChannelTest, TestDtlsReOfferWithDifferentSetupAttr) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE)); // Renegotiate from client2 with actpass and client1 as active. Renegotiate(&client2_, cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTPASS, NF_REOFFER); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); } // Test that re-negotiation can be started before the clients become connected // in the first negotiation. TEST_F(DtlsTransportChannelTest, TestRenegotiateBeforeConnect) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true); PrepareDtlsSrtp(true, true); Negotiate(); Renegotiate(&client1_, cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE, NF_REOFFER); bool rv = client1_.Connect(&client2_); EXPECT_TRUE(rv); EXPECT_TRUE_WAIT(client1_.writable() && client2_.writable(), 10000); TestTransfer(0, 1000, 100, true); TestTransfer(1, 1000, 100, true); } // Test Certificates state after negotiation but before connection. TEST_F(DtlsTransportChannelTest, TestCertificatesBeforeConnect) { MAYBE_SKIP_TEST(HaveDtls); PrepareDtls(true, true); Negotiate(); talk_base::scoped_ptr identity1; talk_base::scoped_ptr identity2; talk_base::scoped_ptr remote_cert1; talk_base::scoped_ptr remote_cert2; // After negotiation, each side has a distinct local certificate, but still no // remote certificate, because connection has not yet occurred. ASSERT_TRUE(client1_.transport()->GetIdentity(identity1.accept())); ASSERT_TRUE(client2_.transport()->GetIdentity(identity2.accept())); ASSERT_NE(identity1->certificate().ToPEMString(), identity2->certificate().ToPEMString()); ASSERT_FALSE( client1_.transport()->GetRemoteCertificate(remote_cert1.accept())); ASSERT_FALSE(remote_cert1 != NULL); ASSERT_FALSE( client2_.transport()->GetRemoteCertificate(remote_cert2.accept())); ASSERT_FALSE(remote_cert2 != NULL); } // Test Certificates state after connection. TEST_F(DtlsTransportChannelTest, TestCertificatesAfterConnect) { MAYBE_SKIP_TEST(HaveDtls); PrepareDtls(true, true); ASSERT_TRUE(Connect()); talk_base::scoped_ptr identity1; talk_base::scoped_ptr identity2; talk_base::scoped_ptr remote_cert1; talk_base::scoped_ptr remote_cert2; // After connection, each side has a distinct local certificate. ASSERT_TRUE(client1_.transport()->GetIdentity(identity1.accept())); ASSERT_TRUE(client2_.transport()->GetIdentity(identity2.accept())); ASSERT_NE(identity1->certificate().ToPEMString(), identity2->certificate().ToPEMString()); // Each side's remote certificate is the other side's local certificate. ASSERT_TRUE( client1_.transport()->GetRemoteCertificate(remote_cert1.accept())); ASSERT_EQ(remote_cert1->ToPEMString(), identity2->certificate().ToPEMString()); ASSERT_TRUE( client2_.transport()->GetRemoteCertificate(remote_cert2.accept())); ASSERT_EQ(remote_cert2->ToPEMString(), identity1->certificate().ToPEMString()); }