webrtc_m130/webrtc/p2p/base/jseptransport_unittest.cc
deadbeef d8cfa1af38 Accept remote offers with current DTLS role, rather than "actpass".
JSEP implementations are required to always generate offers with
"actpass", but remote endpoints are not. So we should accept remote
offers with the current negotiated DTLS role.

This was recently clarified in dtls-sdp; it was somewhat ambiguous
before.

Also doing a bit of refactoring of JsepTransport (making a method
private that should have been private, fixing unit tests that were
directly calling said method).

BUG=webrtc:7072

Review-Url: https://codereview.webrtc.org/2770903003
Cr-Commit-Position: refs/heads/master@{#17396}
2017-03-27 17:33:26 +00:00

453 lines
20 KiB
C++

/*
* 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/base/fakesslidentity.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/network.h"
#include "webrtc/p2p/base/fakedtlstransport.h"
#include "webrtc/p2p/base/fakeicetransport.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));
}