blob: 56b61eca3b5ddeb5305a9fd85de7ce8218e4a051 [file] [log] [blame]
/*
* Copyright 2011 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include "webrtc/p2p/base/fakedtlstransport.h"
#include "webrtc/p2p/base/fakeicetransport.h"
#include "webrtc/rtc_base/fakesslidentity.h"
#include "webrtc/rtc_base/gunit.h"
#include "webrtc/rtc_base/network.h"
using cricket::JsepTransport;
using cricket::TransportChannel;
using cricket::FakeDtlsTransport;
using cricket::FakeIceTransport;
using cricket::IceRole;
using cricket::TransportDescription;
using rtc::SocketAddress;
static const char kIceUfrag1[] = "TESTICEUFRAG0001";
static const char kIcePwd1[] = "TESTICEPWD00000000000001";
static const char kIceUfrag2[] = "TESTICEUFRAG0002";
static const char kIcePwd2[] = "TESTICEPWD00000000000002";
class JsepTransportTest : public testing::Test, public sigslot::has_slots<> {
public:
JsepTransportTest() { RecreateTransport(); }
bool SetupChannel() {
fake_ice_transport_.reset(new FakeIceTransport(transport_->mid(), 1));
fake_dtls_transport_.reset(
new FakeDtlsTransport(fake_ice_transport_.get()));
return transport_->AddChannel(fake_dtls_transport_.get(), 1);
}
void DestroyChannel() { transport_->RemoveChannel(1); }
void RecreateTransport() {
transport_.reset(new JsepTransport("test content name", nullptr));
}
protected:
std::unique_ptr<FakeDtlsTransport> fake_dtls_transport_;
std::unique_ptr<FakeIceTransport> fake_ice_transport_;
std::unique_ptr<JsepTransport> transport_;
};
// This test verifies channels are created with proper ICE
// ufrag/password after a transport description is applied.
TEST_F(JsepTransportTest, TestChannelIceParameters) {
cricket::TransportDescription local_desc(kIceUfrag1, kIcePwd1);
ASSERT_TRUE(transport_->SetLocalTransportDescription(
local_desc, cricket::CA_OFFER, NULL));
EXPECT_TRUE(SetupChannel());
EXPECT_EQ(cricket::ICEMODE_FULL, fake_ice_transport_->remote_ice_mode());
EXPECT_EQ(kIceUfrag1, fake_ice_transport_->ice_ufrag());
EXPECT_EQ(kIcePwd1, fake_ice_transport_->ice_pwd());
cricket::TransportDescription remote_desc(kIceUfrag1, kIcePwd1);
ASSERT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_ANSWER, NULL));
EXPECT_EQ(cricket::ICEMODE_FULL, fake_ice_transport_->remote_ice_mode());
EXPECT_EQ(kIceUfrag1, fake_ice_transport_->remote_ice_ufrag());
EXPECT_EQ(kIcePwd1, fake_ice_transport_->remote_ice_pwd());
}
// Verifies that IceCredentialsChanged returns true when either ufrag or pwd
// changed, and false in other cases.
TEST_F(JsepTransportTest, TestIceCredentialsChanged) {
EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u2", "p2"));
EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u2", "p1"));
EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u1", "p2"));
EXPECT_FALSE(cricket::IceCredentialsChanged("u1", "p1", "u1", "p1"));
}
// Tests SetNeedsIceRestartFlag and NeedsIceRestart, ensuring NeedsIceRestart
// only starts returning "false" once an ICE restart has been initiated.
TEST_F(JsepTransportTest, NeedsIceRestart) {
// Do initial offer/answer so there's something to restart.
cricket::TransportDescription local_desc(kIceUfrag1, kIcePwd1);
cricket::TransportDescription remote_desc(kIceUfrag1, kIcePwd1);
ASSERT_TRUE(transport_->SetLocalTransportDescription(
local_desc, cricket::CA_OFFER, nullptr));
ASSERT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_ANSWER, nullptr));
// Flag initially should be false.
EXPECT_FALSE(transport_->NeedsIceRestart());
// After setting flag, it should be true.
transport_->SetNeedsIceRestartFlag();
EXPECT_TRUE(transport_->NeedsIceRestart());
// Doing an identical offer/answer shouldn't clear the flag.
ASSERT_TRUE(transport_->SetLocalTransportDescription(
local_desc, cricket::CA_OFFER, nullptr));
ASSERT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_ANSWER, nullptr));
EXPECT_TRUE(transport_->NeedsIceRestart());
// Doing an offer/answer that restarts ICE should clear the flag.
cricket::TransportDescription ice_restart_local_desc(kIceUfrag2, kIcePwd2);
cricket::TransportDescription ice_restart_remote_desc(kIceUfrag2, kIcePwd2);
ASSERT_TRUE(transport_->SetLocalTransportDescription(
ice_restart_local_desc, cricket::CA_OFFER, nullptr));
ASSERT_TRUE(transport_->SetRemoteTransportDescription(
ice_restart_remote_desc, cricket::CA_ANSWER, nullptr));
EXPECT_FALSE(transport_->NeedsIceRestart());
}
TEST_F(JsepTransportTest, TestGetStats) {
EXPECT_TRUE(SetupChannel());
cricket::TransportStats stats;
EXPECT_TRUE(transport_->GetStats(&stats));
// Note that this tests the behavior of a FakeIceTransport.
ASSERT_EQ(1U, stats.channel_stats.size());
EXPECT_EQ(1, stats.channel_stats[0].component);
// Set local transport description for FakeTransport before connecting.
TransportDescription faketransport_desc(
std::vector<std::string>(),
rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH),
rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), cricket::ICEMODE_FULL,
cricket::CONNECTIONROLE_NONE, nullptr);
transport_->SetLocalTransportDescription(faketransport_desc,
cricket::CA_OFFER, nullptr);
EXPECT_TRUE(transport_->GetStats(&stats));
ASSERT_EQ(1U, stats.channel_stats.size());
EXPECT_EQ(1, stats.channel_stats[0].component);
}
// Tests that VerifyCertificateFingerprint only returns true when the
// certificate matches the fingerprint.
TEST_F(JsepTransportTest, TestVerifyCertificateFingerprint) {
std::string error_desc;
EXPECT_FALSE(
transport_->VerifyCertificateFingerprint(nullptr, nullptr, &error_desc));
rtc::KeyType key_types[] = {rtc::KT_RSA, rtc::KT_ECDSA};
for (auto& key_type : key_types) {
rtc::scoped_refptr<rtc::RTCCertificate> certificate =
rtc::RTCCertificate::Create(std::unique_ptr<rtc::SSLIdentity>(
rtc::SSLIdentity::Generate("testing", key_type)));
ASSERT_NE(nullptr, certificate);
std::string digest_algorithm;
ASSERT_TRUE(certificate->ssl_certificate().GetSignatureDigestAlgorithm(
&digest_algorithm));
ASSERT_FALSE(digest_algorithm.empty());
std::unique_ptr<rtc::SSLFingerprint> good_fingerprint(
rtc::SSLFingerprint::Create(digest_algorithm, certificate->identity()));
ASSERT_NE(nullptr, good_fingerprint);
EXPECT_TRUE(transport_->VerifyCertificateFingerprint(
certificate.get(), good_fingerprint.get(), &error_desc));
EXPECT_FALSE(transport_->VerifyCertificateFingerprint(
certificate.get(), nullptr, &error_desc));
EXPECT_FALSE(transport_->VerifyCertificateFingerprint(
nullptr, good_fingerprint.get(), &error_desc));
rtc::SSLFingerprint bad_fingerprint = *good_fingerprint;
bad_fingerprint.digest.AppendData("0", 1);
EXPECT_FALSE(transport_->VerifyCertificateFingerprint(
certificate.get(), &bad_fingerprint, &error_desc));
}
}
// Tests the logic of DTLS role negotiation for an initial offer/answer.
TEST_F(JsepTransportTest, DtlsRoleNegotiation) {
rtc::scoped_refptr<rtc::RTCCertificate> certificate =
rtc::RTCCertificate::Create(std::unique_ptr<rtc::SSLIdentity>(
rtc::SSLIdentity::Generate("testing", rtc::KT_ECDSA)));
std::unique_ptr<rtc::SSLFingerprint> fingerprint(
rtc::SSLFingerprint::CreateFromCertificate(certificate));
TransportDescription local_desc(kIceUfrag1, kIcePwd1);
TransportDescription remote_desc(kIceUfrag2, kIcePwd2);
// Just use the same fingerprint in both descriptions; the remote fingerprint
// doesn't matter in a non end-to-end test.
local_desc.identity_fingerprint.reset(
TransportDescription::CopyFingerprint(fingerprint.get()));
remote_desc.identity_fingerprint.reset(
TransportDescription::CopyFingerprint(fingerprint.get()));
struct NegotiateRoleParams {
cricket::ConnectionRole local_role;
cricket::ConnectionRole remote_role;
cricket::ContentAction local_action;
cricket::ContentAction remote_action;
};
std::string error_desc;
// Parameters which set the SSL role to SSL_CLIENT.
NegotiateRoleParams valid_client_params[] = {
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_ANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_PRANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_OFFER, cricket::CA_ANSWER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_OFFER, cricket::CA_PRANSWER}};
for (auto& param : valid_client_params) {
RecreateTransport();
transport_->SetLocalCertificate(certificate);
local_desc.connection_role = param.local_role;
remote_desc.connection_role = param.remote_role;
// Set the offer first.
if (param.local_action == cricket::CA_OFFER) {
EXPECT_TRUE(transport_->SetLocalTransportDescription(
local_desc, param.local_action, nullptr));
EXPECT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, param.remote_action, nullptr));
} else {
EXPECT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, param.remote_action, nullptr));
EXPECT_TRUE(transport_->SetLocalTransportDescription(
local_desc, param.local_action, nullptr));
}
EXPECT_EQ(rtc::SSL_CLIENT, *transport_->GetSslRole());
}
// Parameters which set the SSL role to SSL_SERVER.
NegotiateRoleParams valid_server_params[] = {
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_ANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_PRANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_OFFER, cricket::CA_ANSWER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_OFFER, cricket::CA_PRANSWER}};
for (auto& param : valid_server_params) {
RecreateTransport();
transport_->SetLocalCertificate(certificate);
local_desc.connection_role = param.local_role;
remote_desc.connection_role = param.remote_role;
// Set the offer first.
if (param.local_action == cricket::CA_OFFER) {
EXPECT_TRUE(transport_->SetLocalTransportDescription(
local_desc, param.local_action, nullptr));
EXPECT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, param.remote_action, nullptr));
} else {
EXPECT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, param.remote_action, nullptr));
EXPECT_TRUE(transport_->SetLocalTransportDescription(
local_desc, param.local_action, nullptr));
}
EXPECT_EQ(rtc::SSL_SERVER, *transport_->GetSslRole());
}
// Invalid parameters due to both peers having a duplicate role.
NegotiateRoleParams duplicate_params[] = {
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_ANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_ANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_ANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_PRANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_PRANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_PRANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_OFFER, cricket::CA_ANSWER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_OFFER, cricket::CA_ANSWER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_OFFER, cricket::CA_ANSWER},
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_OFFER, cricket::CA_PRANSWER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_OFFER, cricket::CA_PRANSWER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_OFFER, cricket::CA_PRANSWER}};
for (auto& param : duplicate_params) {
RecreateTransport();
transport_->SetLocalCertificate(certificate);
local_desc.connection_role = param.local_role;
remote_desc.connection_role = param.remote_role;
// Set the offer first.
if (param.local_action == cricket::CA_OFFER) {
EXPECT_TRUE(transport_->SetLocalTransportDescription(
local_desc, param.local_action, nullptr));
EXPECT_FALSE(transport_->SetRemoteTransportDescription(
remote_desc, param.remote_action, nullptr));
} else {
EXPECT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, param.remote_action, nullptr));
EXPECT_FALSE(transport_->SetLocalTransportDescription(
local_desc, param.local_action, nullptr));
}
}
// Invalid parameters due to the offerer not using ACTPASS.
NegotiateRoleParams offerer_without_actpass_params[] = {
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_ANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_ANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_ANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_PRANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_PRANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_PRANSWER, cricket::CA_OFFER},
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_OFFER, cricket::CA_ANSWER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_OFFER, cricket::CA_ANSWER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_OFFER, cricket::CA_ANSWER},
{cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_PASSIVE,
cricket::CA_OFFER, cricket::CA_PRANSWER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTIVE,
cricket::CA_OFFER, cricket::CA_PRANSWER},
{cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS,
cricket::CA_OFFER, cricket::CA_PRANSWER}};
for (auto& param : offerer_without_actpass_params) {
RecreateTransport();
transport_->SetLocalCertificate(certificate);
local_desc.connection_role = param.local_role;
remote_desc.connection_role = param.remote_role;
// Set the offer first.
// TODO(deadbeef): Really this should fail as soon as the offer is
// attempted to be applied, and not when the answer is applied.
if (param.local_action == cricket::CA_OFFER) {
EXPECT_TRUE(transport_->SetLocalTransportDescription(
local_desc, param.local_action, nullptr));
EXPECT_FALSE(transport_->SetRemoteTransportDescription(
remote_desc, param.remote_action, nullptr));
} else {
EXPECT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, param.remote_action, nullptr));
EXPECT_FALSE(transport_->SetLocalTransportDescription(
local_desc, param.local_action, nullptr));
}
}
}
// Test that a remote offer with the current negotiated role can be accepted.
// This is allowed by dtls-sdp, though we'll never generate such an offer,
// since JSEP requires generating "actpass".
TEST_F(JsepTransportTest, RemoteOfferWithCurrentNegotiatedDtlsRole) {
rtc::scoped_refptr<rtc::RTCCertificate> certificate =
rtc::RTCCertificate::Create(std::unique_ptr<rtc::SSLIdentity>(
rtc::SSLIdentity::Generate("testing", rtc::KT_ECDSA)));
std::unique_ptr<rtc::SSLFingerprint> fingerprint(
rtc::SSLFingerprint::CreateFromCertificate(certificate));
transport_->SetLocalCertificate(certificate);
TransportDescription local_desc(kIceUfrag1, kIcePwd1);
TransportDescription remote_desc(kIceUfrag2, kIcePwd2);
// Just use the same fingerprint in both descriptions; the remote fingerprint
// doesn't matter in a non end-to-end test.
local_desc.identity_fingerprint.reset(
TransportDescription::CopyFingerprint(fingerprint.get()));
remote_desc.identity_fingerprint.reset(
TransportDescription::CopyFingerprint(fingerprint.get()));
remote_desc.connection_role = cricket::CONNECTIONROLE_ACTPASS;
local_desc.connection_role = cricket::CONNECTIONROLE_ACTIVE;
// Normal initial offer/answer with "actpass" in the offer and "active" in
// the answer.
ASSERT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_OFFER, nullptr));
ASSERT_TRUE(transport_->SetLocalTransportDescription(
local_desc, cricket::CA_ANSWER, nullptr));
// Sanity check that role was actually negotiated.
rtc::Optional<rtc::SSLRole> role = transport_->GetSslRole();
ASSERT_TRUE(role);
EXPECT_EQ(rtc::SSL_CLIENT, *role);
// Subsequent offer with current negotiated role of "passive".
remote_desc.connection_role = cricket::CONNECTIONROLE_PASSIVE;
EXPECT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_OFFER, nullptr));
EXPECT_TRUE(transport_->SetLocalTransportDescription(
local_desc, cricket::CA_ANSWER, nullptr));
}
// Test that a remote offer with the inverse of the current negotiated DTLS
// role is rejected.
TEST_F(JsepTransportTest, RemoteOfferThatChangesNegotiatedDtlsRole) {
rtc::scoped_refptr<rtc::RTCCertificate> certificate =
rtc::RTCCertificate::Create(std::unique_ptr<rtc::SSLIdentity>(
rtc::SSLIdentity::Generate("testing", rtc::KT_ECDSA)));
std::unique_ptr<rtc::SSLFingerprint> fingerprint(
rtc::SSLFingerprint::CreateFromCertificate(certificate));
transport_->SetLocalCertificate(certificate);
TransportDescription local_desc(kIceUfrag1, kIcePwd1);
TransportDescription remote_desc(kIceUfrag2, kIcePwd2);
// Just use the same fingerprint in both descriptions; the remote fingerprint
// doesn't matter in a non end-to-end test.
local_desc.identity_fingerprint.reset(
TransportDescription::CopyFingerprint(fingerprint.get()));
remote_desc.identity_fingerprint.reset(
TransportDescription::CopyFingerprint(fingerprint.get()));
remote_desc.connection_role = cricket::CONNECTIONROLE_ACTPASS;
local_desc.connection_role = cricket::CONNECTIONROLE_ACTIVE;
// Normal initial offer/answer with "actpass" in the offer and "active" in
// the answer.
ASSERT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_OFFER, nullptr));
ASSERT_TRUE(transport_->SetLocalTransportDescription(
local_desc, cricket::CA_ANSWER, nullptr));
// Sanity check that role was actually negotiated.
rtc::Optional<rtc::SSLRole> role = transport_->GetSslRole();
ASSERT_TRUE(role);
EXPECT_EQ(rtc::SSL_CLIENT, *role);
// Subsequent offer with "active", which is the opposite of the remote
// endpoint's negotiated role.
// TODO(deadbeef): Really this should fail as soon as the offer is
// attempted to be applied, and not when the answer is applied.
remote_desc.connection_role = cricket::CONNECTIONROLE_ACTIVE;
EXPECT_TRUE(transport_->SetRemoteTransportDescription(
remote_desc, cricket::CA_OFFER, nullptr));
EXPECT_FALSE(transport_->SetLocalTransportDescription(
local_desc, cricket::CA_ANSWER, nullptr));
}