/* * 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 #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 fake_dtls_transport_; std::unique_ptr fake_ice_transport_; std::unique_ptr 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(), 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 certificate = rtc::RTCCertificate::Create(std::unique_ptr( 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 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 certificate = rtc::RTCCertificate::Create(std::unique_ptr( rtc::SSLIdentity::Generate("testing", rtc::KT_ECDSA))); std::unique_ptr 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 certificate = rtc::RTCCertificate::Create(std::unique_ptr( rtc::SSLIdentity::Generate("testing", rtc::KT_ECDSA))); std::unique_ptr 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 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 certificate = rtc::RTCCertificate::Create(std::unique_ptr( rtc::SSLIdentity::Generate("testing", rtc::KT_ECDSA))); std::unique_ptr 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 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)); }