blob: 6a4e5ade90e3e71328826ec5b6329c94235586fc [file] [log] [blame]
/*
* 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 <set>
#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<cricket::FakeTransport>(
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<cricket::DtlsTransportChannelWrapper*>(
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<cricket::DtlsTransportChannelWrapper*>(ch);
return (wrapper) ?
static_cast<cricket::FakeTransportChannel*>(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<talk_base::SSLFingerprint> local_fingerprint;
talk_base::scoped_ptr<talk_base::SSLFingerprint> 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<cricket::DtlsTransportChannelWrapper*>::iterator it =
channels_.begin(); it != channels_.end(); ++it) {
std::vector<std::string> 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<std::string>(), 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<std::string>(), 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));
ASSERT_EQ(expect_success, transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_ANSWER));
} else {
ASSERT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_OFFER));
ASSERT_EQ(expect_success, transport_->SetLocalTransportDescription(
local_desc, cricket::CA_ANSWER));
}
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<cricket::DtlsTransportChannelWrapper*>::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_array<char> 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<uint32>(sent));
// Only set the bypass flag if we've activated DTLS.
int flags = (identity_.get() && srtp) ? cricket::PF_SRTP_BYPASS : 0;
int rv = channels_[channel]->SendPacket(
packet.get(), size, talk_base::DSCP_NO_CHANGE, flags);
ASSERT_GT(rv, 0);
ASSERT_EQ(size, static_cast<size_t>(rv));
++sent;
} while (sent < count);
}
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<uint8>(data[0]) != 0x80)) {
return false;
}
uint32 packet_num = talk_base::GetBE32(data + kPacketNumOffset);
for (size_t i = kPacketHeaderLen; i < size; ++i) {
if (static_cast<uint8>(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<uint8>(data[i]) == (packet_num & 0xff)) {
++num_matches;
}
}
return (num_matches < ((static_cast<int>(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,
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,
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<talk_base::SSLIdentity> identity_;
talk_base::scoped_ptr<cricket::FakeTransport> transport_;
std::vector<cricket::DtlsTransportChannelWrapper*> channels_;
size_t packet_size_;
std::set<int> 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<int>(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. 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 Certificates state after negotiation but before connection.
TEST_F(DtlsTransportChannelTest, TestCertificatesBeforeConnect) {
MAYBE_SKIP_TEST(HaveDtls);
PrepareDtls(true, true);
Negotiate();
talk_base::scoped_ptr<talk_base::SSLIdentity> identity1;
talk_base::scoped_ptr<talk_base::SSLIdentity> identity2;
talk_base::scoped_ptr<talk_base::SSLCertificate> remote_cert1;
talk_base::scoped_ptr<talk_base::SSLCertificate> 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<talk_base::SSLIdentity> identity1;
talk_base::scoped_ptr<talk_base::SSLIdentity> identity2;
talk_base::scoped_ptr<talk_base::SSLCertificate> remote_cert1;
talk_base::scoped_ptr<talk_base::SSLCertificate> 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());
}