/* * Copyright 2012 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 "p2p/base/transport_description_factory.h" #include #include #include #include #include "absl/strings/string_view.h" #include "p2p/base/p2p_constants.h" #include "p2p/base/transport_description.h" #include "rtc_base/copy_on_write_buffer.h" #include "rtc_base/fake_ssl_identity.h" #include "rtc_base/ssl_certificate.h" #include "rtc_base/ssl_fingerprint.h" #include "rtc_base/ssl_identity.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/scoped_key_value_config.h" using cricket::TransportDescription; using cricket::TransportDescriptionFactory; using cricket::TransportOptions; using ::testing::Contains; using ::testing::Not; class TransportDescriptionFactoryTest : public ::testing::Test { public: TransportDescriptionFactoryTest() : ice_credentials_({}), f1_(field_trials_), f2_(field_trials_), cert1_(rtc::RTCCertificate::Create(std::unique_ptr( new rtc::FakeSSLIdentity("User1")))), cert2_(rtc::RTCCertificate::Create(std::unique_ptr( new rtc::FakeSSLIdentity("User2")))) {} void CheckDesc(const TransportDescription* desc, absl::string_view opt, absl::string_view ice_ufrag, absl::string_view ice_pwd, absl::string_view dtls_alg) { ASSERT_TRUE(desc != NULL); EXPECT_EQ(!opt.empty(), desc->HasOption(opt)); if (ice_ufrag.empty() && ice_pwd.empty()) { EXPECT_EQ(static_cast(cricket::ICE_UFRAG_LENGTH), desc->ice_ufrag.size()); EXPECT_EQ(static_cast(cricket::ICE_PWD_LENGTH), desc->ice_pwd.size()); } else { EXPECT_EQ(ice_ufrag, desc->ice_ufrag); EXPECT_EQ(ice_pwd, desc->ice_pwd); } if (dtls_alg.empty()) { EXPECT_TRUE(desc->identity_fingerprint.get() == NULL); } else { ASSERT_TRUE(desc->identity_fingerprint.get() != NULL); EXPECT_EQ(desc->identity_fingerprint->algorithm, dtls_alg); EXPECT_GT(desc->identity_fingerprint->digest.size(), 0U); } } // This test ice restart by doing two offer answer exchanges. On the second // exchange ice is restarted. The test verifies that the ufrag and password // in the offer and answer is changed. // If `dtls` is true, the test verifies that the finger print is not changed. void TestIceRestart(bool dtls) { SetDtls(dtls); cricket::TransportOptions options; // The initial offer / answer exchange. std::unique_ptr offer = f1_.CreateOffer(options, NULL, &ice_credentials_); std::unique_ptr answer = f2_.CreateAnswer(offer.get(), options, true, NULL, &ice_credentials_); // Create an updated offer where we restart ice. options.ice_restart = true; std::unique_ptr restart_offer = f1_.CreateOffer(options, offer.get(), &ice_credentials_); VerifyUfragAndPasswordChanged(dtls, offer.get(), restart_offer.get()); // Create a new answer. The transport ufrag and password is changed since // |options.ice_restart == true| std::unique_ptr restart_answer = f2_.CreateAnswer( restart_offer.get(), options, true, answer.get(), &ice_credentials_); ASSERT_TRUE(restart_answer.get() != NULL); VerifyUfragAndPasswordChanged(dtls, answer.get(), restart_answer.get()); } void VerifyUfragAndPasswordChanged(bool dtls, const TransportDescription* org_desc, const TransportDescription* restart_desc) { EXPECT_NE(org_desc->ice_pwd, restart_desc->ice_pwd); EXPECT_NE(org_desc->ice_ufrag, restart_desc->ice_ufrag); EXPECT_EQ(static_cast(cricket::ICE_UFRAG_LENGTH), restart_desc->ice_ufrag.size()); EXPECT_EQ(static_cast(cricket::ICE_PWD_LENGTH), restart_desc->ice_pwd.size()); // If DTLS is enabled, make sure the finger print is unchanged. if (dtls) { EXPECT_FALSE( org_desc->identity_fingerprint->GetRfc4572Fingerprint().empty()); EXPECT_EQ(org_desc->identity_fingerprint->GetRfc4572Fingerprint(), restart_desc->identity_fingerprint->GetRfc4572Fingerprint()); } } void TestIceRenomination(bool dtls) { SetDtls(dtls); cricket::TransportOptions options; // The initial offer / answer exchange. std::unique_ptr offer = f1_.CreateOffer(options, nullptr, &ice_credentials_); ASSERT_TRUE(offer); EXPECT_THAT(offer->transport_options, Not(Contains("renomination"))); std::unique_ptr answer = f2_.CreateAnswer( offer.get(), options, true, nullptr, &ice_credentials_); ASSERT_TRUE(answer); EXPECT_THAT(answer->transport_options, Not(Contains("renomination"))); options.enable_ice_renomination = true; std::unique_ptr renomination_offer = f1_.CreateOffer(options, offer.get(), &ice_credentials_); ASSERT_TRUE(renomination_offer); EXPECT_THAT(renomination_offer->transport_options, Contains("renomination")); std::unique_ptr renomination_answer = f2_.CreateAnswer(renomination_offer.get(), options, true, answer.get(), &ice_credentials_); ASSERT_TRUE(renomination_answer); EXPECT_THAT(renomination_answer->transport_options, Contains("renomination")); } protected: void SetDtls(bool dtls) { if (dtls) { f1_.set_secure(cricket::SEC_ENABLED); f2_.set_secure(cricket::SEC_ENABLED); f1_.set_certificate(cert1_); f2_.set_certificate(cert2_); } else { f1_.set_secure(cricket::SEC_DISABLED); f2_.set_secure(cricket::SEC_DISABLED); } } webrtc::test::ScopedKeyValueConfig field_trials_; cricket::IceCredentialsIterator ice_credentials_; TransportDescriptionFactory f1_; TransportDescriptionFactory f2_; rtc::scoped_refptr cert1_; rtc::scoped_refptr cert2_; }; TEST_F(TransportDescriptionFactoryTest, TestOfferDefault) { std::unique_ptr desc = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", ""); } TEST_F(TransportDescriptionFactoryTest, TestOfferDtls) { f1_.set_secure(cricket::SEC_ENABLED); f1_.set_certificate(cert1_); std::string digest_alg; ASSERT_TRUE( cert1_->GetSSLCertificate().GetSignatureDigestAlgorithm(&digest_alg)); std::unique_ptr desc = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", digest_alg); // Ensure it also works with SEC_REQUIRED. f1_.set_secure(cricket::SEC_REQUIRED); desc = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", digest_alg); } // Test generating an offer with DTLS fails with no identity. TEST_F(TransportDescriptionFactoryTest, TestOfferDtlsWithNoIdentity) { f1_.set_secure(cricket::SEC_ENABLED); std::unique_ptr desc = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); ASSERT_TRUE(desc.get() == NULL); } // Test updating an offer with DTLS to pick ICE. // The ICE credentials should stay the same in the new offer. TEST_F(TransportDescriptionFactoryTest, TestOfferDtlsReofferDtls) { f1_.set_secure(cricket::SEC_ENABLED); f1_.set_certificate(cert1_); std::string digest_alg; ASSERT_TRUE( cert1_->GetSSLCertificate().GetSignatureDigestAlgorithm(&digest_alg)); std::unique_ptr old_desc = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); ASSERT_TRUE(old_desc.get() != NULL); std::unique_ptr desc = f1_.CreateOffer(TransportOptions(), old_desc.get(), &ice_credentials_); CheckDesc(desc.get(), "", old_desc->ice_ufrag, old_desc->ice_pwd, digest_alg); } TEST_F(TransportDescriptionFactoryTest, TestAnswerDefault) { std::unique_ptr offer = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); ASSERT_TRUE(offer.get() != NULL); std::unique_ptr desc = f2_.CreateAnswer( offer.get(), TransportOptions(), true, NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", ""); desc = f2_.CreateAnswer(offer.get(), TransportOptions(), true, NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", ""); } // Test that we can update an answer properly; ICE credentials shouldn't change. TEST_F(TransportDescriptionFactoryTest, TestReanswer) { std::unique_ptr offer = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); ASSERT_TRUE(offer.get() != NULL); std::unique_ptr old_desc = f2_.CreateAnswer( offer.get(), TransportOptions(), true, NULL, &ice_credentials_); ASSERT_TRUE(old_desc.get() != NULL); std::unique_ptr desc = f2_.CreateAnswer( offer.get(), TransportOptions(), true, old_desc.get(), &ice_credentials_); ASSERT_TRUE(desc.get() != NULL); CheckDesc(desc.get(), "", old_desc->ice_ufrag, old_desc->ice_pwd, ""); } // Test that we handle answering an offer with DTLS with no DTLS. TEST_F(TransportDescriptionFactoryTest, TestAnswerDtlsToNoDtls) { f1_.set_secure(cricket::SEC_ENABLED); f1_.set_certificate(cert1_); std::unique_ptr offer = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); ASSERT_TRUE(offer.get() != NULL); std::unique_ptr desc = f2_.CreateAnswer( offer.get(), TransportOptions(), true, NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", ""); } // Test that we handle answering an offer without DTLS if we have DTLS enabled, // but fail if we require DTLS. TEST_F(TransportDescriptionFactoryTest, TestAnswerNoDtlsToDtls) { f2_.set_secure(cricket::SEC_ENABLED); f2_.set_certificate(cert2_); std::unique_ptr offer = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); ASSERT_TRUE(offer.get() != NULL); std::unique_ptr desc = f2_.CreateAnswer( offer.get(), TransportOptions(), true, NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", ""); f2_.set_secure(cricket::SEC_REQUIRED); desc = f2_.CreateAnswer(offer.get(), TransportOptions(), true, NULL, &ice_credentials_); ASSERT_TRUE(desc.get() == NULL); } // Test that we handle answering an DTLS offer with DTLS, both if we have // DTLS enabled and required. TEST_F(TransportDescriptionFactoryTest, TestAnswerDtlsToDtls) { f1_.set_secure(cricket::SEC_ENABLED); f1_.set_certificate(cert1_); f2_.set_secure(cricket::SEC_ENABLED); f2_.set_certificate(cert2_); // f2_ produces the answer that is being checked in this test, so the // answer must contain fingerprint lines with cert2_'s digest algorithm. std::string digest_alg2; ASSERT_TRUE( cert2_->GetSSLCertificate().GetSignatureDigestAlgorithm(&digest_alg2)); std::unique_ptr offer = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); ASSERT_TRUE(offer.get() != NULL); std::unique_ptr desc = f2_.CreateAnswer( offer.get(), TransportOptions(), true, NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", digest_alg2); f2_.set_secure(cricket::SEC_REQUIRED); desc = f2_.CreateAnswer(offer.get(), TransportOptions(), true, NULL, &ice_credentials_); CheckDesc(desc.get(), "", "", "", digest_alg2); } // Test that ice ufrag and password is changed in an updated offer and answer // if `TransportDescriptionOptions::ice_restart` is true. TEST_F(TransportDescriptionFactoryTest, TestIceRestart) { TestIceRestart(false); } // Test that ice ufrag and password is changed in an updated offer and answer // if `TransportDescriptionOptions::ice_restart` is true and DTLS is enabled. TEST_F(TransportDescriptionFactoryTest, TestIceRestartWithDtls) { TestIceRestart(true); } // Test that ice renomination is set in an updated offer and answer // if `TransportDescriptionOptions::enable_ice_renomination` is true. TEST_F(TransportDescriptionFactoryTest, TestIceRenomination) { TestIceRenomination(false); } // Test that ice renomination is set in an updated offer and answer // if `TransportDescriptionOptions::enable_ice_renomination` is true and DTLS // is enabled. TEST_F(TransportDescriptionFactoryTest, TestIceRenominationWithDtls) { TestIceRenomination(true); } // Test that offers and answers have ice-option:trickle. TEST_F(TransportDescriptionFactoryTest, AddsTrickleIceOption) { cricket::TransportOptions options; std::unique_ptr offer = f1_.CreateOffer(options, nullptr, &ice_credentials_); EXPECT_TRUE(offer->HasOption("trickle")); std::unique_ptr answer = f2_.CreateAnswer(offer.get(), options, true, nullptr, &ice_credentials_); EXPECT_TRUE(answer->HasOption("trickle")); } // Test CreateOffer with IceCredentialsIterator. TEST_F(TransportDescriptionFactoryTest, CreateOfferIceCredentialsIterator) { std::vector credentials = { cricket::IceParameters("kalle", "anka", false)}; cricket::IceCredentialsIterator credentialsIterator(credentials); cricket::TransportOptions options; std::unique_ptr offer = f1_.CreateOffer(options, nullptr, &credentialsIterator); EXPECT_EQ(offer->GetIceParameters().ufrag, credentials[0].ufrag); EXPECT_EQ(offer->GetIceParameters().pwd, credentials[0].pwd); } // Test CreateAnswer with IceCredentialsIterator. TEST_F(TransportDescriptionFactoryTest, CreateAnswerIceCredentialsIterator) { cricket::TransportOptions options; std::unique_ptr offer = f1_.CreateOffer(options, nullptr, &ice_credentials_); std::vector credentials = { cricket::IceParameters("kalle", "anka", false)}; cricket::IceCredentialsIterator credentialsIterator(credentials); std::unique_ptr answer = f1_.CreateAnswer( offer.get(), options, false, nullptr, &credentialsIterator); EXPECT_EQ(answer->GetIceParameters().ufrag, credentials[0].ufrag); EXPECT_EQ(answer->GetIceParameters().pwd, credentials[0].pwd); } TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsActpassOffer) { f1_.set_secure(cricket::SEC_ENABLED); f1_.set_certificate(cert1_); f2_.set_secure(cricket::SEC_ENABLED); f2_.set_certificate(cert2_); cricket::TransportOptions options; std::unique_ptr offer = f1_.CreateOffer(options, nullptr, &ice_credentials_); std::unique_ptr answer = f2_.CreateAnswer(offer.get(), options, false, nullptr, &ice_credentials_); EXPECT_EQ(answer->connection_role, cricket::CONNECTIONROLE_ACTIVE); } TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsActiveOffer) { f1_.set_secure(cricket::SEC_ENABLED); f1_.set_certificate(cert1_); f2_.set_secure(cricket::SEC_ENABLED); f2_.set_certificate(cert2_); cricket::TransportOptions options; std::unique_ptr offer = f1_.CreateOffer(options, nullptr, &ice_credentials_); offer->connection_role = cricket::CONNECTIONROLE_ACTIVE; std::unique_ptr answer = f2_.CreateAnswer(offer.get(), options, false, nullptr, &ice_credentials_); EXPECT_EQ(answer->connection_role, cricket::CONNECTIONROLE_PASSIVE); } TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsPassiveOffer) { f1_.set_secure(cricket::SEC_ENABLED); f1_.set_certificate(cert1_); f2_.set_secure(cricket::SEC_ENABLED); f2_.set_certificate(cert2_); cricket::TransportOptions options; std::unique_ptr offer = f1_.CreateOffer(options, nullptr, &ice_credentials_); offer->connection_role = cricket::CONNECTIONROLE_PASSIVE; std::unique_ptr answer = f2_.CreateAnswer(offer.get(), options, false, nullptr, &ice_credentials_); EXPECT_EQ(answer->connection_role, cricket::CONNECTIONROLE_ACTIVE); }