summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc')
-rw-r--r--third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc796
1 files changed, 796 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc b/third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc
new file mode 100644
index 0000000000..fae74681fc
--- /dev/null
+++ b/third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc
@@ -0,0 +1,796 @@
+/*
+ * Copyright 2017 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 <stddef.h>
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "api/audio/audio_mixer.h"
+#include "api/audio_codecs/builtin_audio_decoder_factory.h"
+#include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "api/create_peerconnection_factory.h"
+#include "api/crypto/crypto_options.h"
+#include "api/crypto_params.h"
+#include "api/jsep.h"
+#include "api/peer_connection_interface.h"
+#include "api/scoped_refptr.h"
+#include "api/video_codecs/builtin_video_decoder_factory.h"
+#include "api/video_codecs/builtin_video_encoder_factory.h"
+#include "modules/audio_device/include/audio_device.h"
+#include "modules/audio_processing/include/audio_processing.h"
+#include "p2p/base/fake_port_allocator.h"
+#include "p2p/base/port_allocator.h"
+#include "p2p/base/transport_description.h"
+#include "p2p/base/transport_info.h"
+#include "pc/media_protocol_names.h"
+#include "pc/media_session.h"
+#include "pc/peer_connection_wrapper.h"
+#include "pc/sdp_utils.h"
+#include "pc/session_description.h"
+#include "pc/test/mock_peer_connection_observers.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/rtc_certificate.h"
+#include "rtc_base/rtc_certificate_generator.h"
+#include "rtc_base/ssl_fingerprint.h"
+#include "rtc_base/thread.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+#ifdef WEBRTC_ANDROID
+#include "pc/test/android_test_initializer.h"
+#endif
+#include "pc/test/fake_audio_capture_module.h"
+#include "pc/test/fake_rtc_certificate_generator.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/virtual_socket_server.h"
+
+namespace webrtc {
+
+using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
+using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
+using ::testing::Combine;
+using ::testing::Values;
+
+constexpr int kGenerateCertTimeout = 1000;
+
+class PeerConnectionCryptoBaseTest : public ::testing::Test {
+ protected:
+ typedef std::unique_ptr<PeerConnectionWrapper> WrapperPtr;
+
+ explicit PeerConnectionCryptoBaseTest(SdpSemantics sdp_semantics)
+ : vss_(new rtc::VirtualSocketServer()),
+ main_(vss_.get()),
+ sdp_semantics_(sdp_semantics) {
+#ifdef WEBRTC_ANDROID
+ InitializeAndroidObjects();
+#endif
+ pc_factory_ = CreatePeerConnectionFactory(
+ rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
+ FakeAudioCaptureModule::Create(), CreateBuiltinAudioEncoderFactory(),
+ CreateBuiltinAudioDecoderFactory(), CreateBuiltinVideoEncoderFactory(),
+ CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,
+ nullptr /* audio_processing */);
+ }
+
+ WrapperPtr CreatePeerConnection() {
+ return CreatePeerConnection(RTCConfiguration());
+ }
+
+ WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
+ return CreatePeerConnection(config, nullptr);
+ }
+
+ WrapperPtr CreatePeerConnection(
+ const RTCConfiguration& config,
+ std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_gen) {
+ auto fake_port_allocator = std::make_unique<cricket::FakePortAllocator>(
+ rtc::Thread::Current(),
+ std::make_unique<rtc::BasicPacketSocketFactory>(vss_.get()),
+ &field_trials_);
+ auto observer = std::make_unique<MockPeerConnectionObserver>();
+ RTCConfiguration modified_config = config;
+ modified_config.sdp_semantics = sdp_semantics_;
+ PeerConnectionDependencies pc_dependencies(observer.get());
+ pc_dependencies.allocator = std::move(fake_port_allocator);
+ pc_dependencies.cert_generator = std::move(cert_gen);
+ auto result = pc_factory_->CreatePeerConnectionOrError(
+ modified_config, std::move(pc_dependencies));
+ if (!result.ok()) {
+ return nullptr;
+ }
+
+ observer->SetPeerConnectionInterface(result.value().get());
+ return std::make_unique<PeerConnectionWrapper>(
+ pc_factory_, result.MoveValue(), std::move(observer));
+ }
+
+ // Accepts the same arguments as CreatePeerConnection and adds default audio
+ // and video tracks.
+ template <typename... Args>
+ WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) {
+ auto wrapper = CreatePeerConnection(std::forward<Args>(args)...);
+ if (!wrapper) {
+ return nullptr;
+ }
+ wrapper->AddAudioTrack("a");
+ wrapper->AddVideoTrack("v");
+ return wrapper;
+ }
+
+ cricket::ConnectionRole& AudioConnectionRole(
+ cricket::SessionDescription* desc) {
+ return ConnectionRoleFromContent(desc, cricket::GetFirstAudioContent(desc));
+ }
+
+ cricket::ConnectionRole& VideoConnectionRole(
+ cricket::SessionDescription* desc) {
+ return ConnectionRoleFromContent(desc, cricket::GetFirstVideoContent(desc));
+ }
+
+ cricket::ConnectionRole& ConnectionRoleFromContent(
+ cricket::SessionDescription* desc,
+ cricket::ContentInfo* content) {
+ RTC_DCHECK(content);
+ auto* transport_info = desc->GetTransportInfoByName(content->name);
+ RTC_DCHECK(transport_info);
+ return transport_info->description.connection_role;
+ }
+
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+ std::unique_ptr<rtc::VirtualSocketServer> vss_;
+ rtc::AutoSocketServerThread main_;
+ rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
+ const SdpSemantics sdp_semantics_;
+};
+
+SdpContentPredicate HaveDtlsFingerprint() {
+ return [](const cricket::ContentInfo* content,
+ const cricket::TransportInfo* transport) {
+ return transport->description.identity_fingerprint != nullptr;
+ };
+}
+
+SdpContentPredicate HaveSdesCryptos() {
+ return [](const cricket::ContentInfo* content,
+ const cricket::TransportInfo* transport) {
+ return !content->media_description()->cryptos().empty();
+ };
+}
+
+SdpContentPredicate HaveProtocol(const std::string& protocol) {
+ return [protocol](const cricket::ContentInfo* content,
+ const cricket::TransportInfo* transport) {
+ return content->media_description()->protocol() == protocol;
+ };
+}
+
+SdpContentPredicate HaveSdesGcmCryptos(size_t num_crypto_suites) {
+ return [num_crypto_suites](const cricket::ContentInfo* content,
+ const cricket::TransportInfo* transport) {
+ const auto& cryptos = content->media_description()->cryptos();
+ if (cryptos.size() != num_crypto_suites) {
+ return false;
+ }
+ for (size_t i = 0; i < cryptos.size(); ++i) {
+ if (cryptos[i].key_params.size() == 67U &&
+ cryptos[i].cipher_suite == "AEAD_AES_256_GCM")
+ return true;
+ }
+ return false;
+ };
+}
+
+class PeerConnectionCryptoTest
+ : public PeerConnectionCryptoBaseTest,
+ public ::testing::WithParamInterface<SdpSemantics> {
+ protected:
+ PeerConnectionCryptoTest() : PeerConnectionCryptoBaseTest(GetParam()) {}
+};
+
+SdpContentMutator RemoveSdesCryptos() {
+ return [](cricket::ContentInfo* content, cricket::TransportInfo* transport) {
+ content->media_description()->set_cryptos({});
+ };
+}
+
+SdpContentMutator RemoveDtlsFingerprint() {
+ return [](cricket::ContentInfo* content, cricket::TransportInfo* transport) {
+ transport->description.identity_fingerprint.reset();
+ };
+}
+
+// When DTLS is enabled, the SDP offer/answer should have a DTLS fingerprint and
+// no SDES cryptos.
+TEST_P(PeerConnectionCryptoTest, CorrectCryptoInOfferWhenDtlsEnabled) {
+ RTCConfiguration config;
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ ASSERT_TRUE(offer);
+
+ ASSERT_FALSE(offer->description()->contents().empty());
+ EXPECT_TRUE(SdpContentsAll(HaveDtlsFingerprint(), offer->description()));
+ EXPECT_TRUE(SdpContentsNone(HaveSdesCryptos(), offer->description()));
+ EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolDtlsSavpf),
+ offer->description()));
+}
+TEST_P(PeerConnectionCryptoTest, CorrectCryptoInAnswerWhenDtlsEnabled) {
+ RTCConfiguration config;
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ callee->SetRemoteDescription(caller->CreateOffer());
+ auto answer = callee->CreateAnswer();
+ ASSERT_TRUE(answer);
+
+ ASSERT_FALSE(answer->description()->contents().empty());
+ EXPECT_TRUE(SdpContentsAll(HaveDtlsFingerprint(), answer->description()));
+ EXPECT_TRUE(SdpContentsNone(HaveSdesCryptos(), answer->description()));
+ EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolDtlsSavpf),
+ answer->description()));
+}
+
+#if defined(WEBRTC_FUCHSIA)
+// When DTLS is disabled, the SDP offer/answer should include SDES cryptos and
+// should not have a DTLS fingerprint.
+TEST_P(PeerConnectionCryptoTest, CorrectCryptoInOfferWhenDtlsDisabled) {
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ ASSERT_TRUE(offer);
+
+ ASSERT_FALSE(offer->description()->contents().empty());
+ EXPECT_TRUE(SdpContentsAll(HaveSdesCryptos(), offer->description()));
+ EXPECT_TRUE(SdpContentsNone(HaveDtlsFingerprint(), offer->description()));
+ EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolSavpf),
+ offer->description()));
+}
+
+TEST_P(PeerConnectionCryptoTest, CorrectCryptoInAnswerWhenDtlsDisabled) {
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ callee->SetRemoteDescription(caller->CreateOffer());
+ auto answer = callee->CreateAnswer();
+ ASSERT_TRUE(answer);
+
+ ASSERT_FALSE(answer->description()->contents().empty());
+ EXPECT_TRUE(SdpContentsAll(HaveSdesCryptos(), answer->description()));
+ EXPECT_TRUE(SdpContentsNone(HaveDtlsFingerprint(), answer->description()));
+ EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolSavpf),
+ answer->description()));
+}
+
+// When encryption is disabled, the SDP offer/answer should have neither a DTLS
+// fingerprint nor any SDES crypto options.
+TEST_P(PeerConnectionCryptoTest, CorrectCryptoInOfferWhenEncryptionDisabled) {
+ PeerConnectionFactoryInterface::Options options;
+ options.disable_encryption = true;
+ pc_factory_->SetOptions(options);
+
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ ASSERT_TRUE(offer);
+
+ ASSERT_FALSE(offer->description()->contents().empty());
+ EXPECT_TRUE(SdpContentsNone(HaveSdesCryptos(), offer->description()));
+ EXPECT_TRUE(SdpContentsNone(HaveDtlsFingerprint(), offer->description()));
+ EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolAvpf),
+ offer->description()));
+}
+
+TEST_P(PeerConnectionCryptoTest, CorrectCryptoInAnswerWhenEncryptionDisabled) {
+ PeerConnectionFactoryInterface::Options options;
+ options.disable_encryption = true;
+ pc_factory_->SetOptions(options);
+
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ callee->SetRemoteDescription(caller->CreateOffer());
+ auto answer = callee->CreateAnswer();
+ ASSERT_TRUE(answer);
+
+ ASSERT_FALSE(answer->description()->contents().empty());
+ EXPECT_TRUE(SdpContentsNone(HaveSdesCryptos(), answer->description()));
+ EXPECT_TRUE(SdpContentsNone(HaveDtlsFingerprint(), answer->description()));
+ EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolAvpf),
+ answer->description()));
+}
+
+// CryptoOptions has been promoted to RTCConfiguration. As such if it is ever
+// set in the configuration it should overrite the settings set in the factory.
+TEST_P(PeerConnectionCryptoTest, RTCConfigurationCryptoOptionOverridesFactory) {
+ PeerConnectionFactoryInterface::Options options;
+ options.crypto_options.srtp.enable_gcm_crypto_suites = true;
+ pc_factory_->SetOptions(options);
+
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ CryptoOptions crypto_options;
+ crypto_options.srtp.enable_gcm_crypto_suites = false;
+ config.crypto_options = crypto_options;
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ ASSERT_TRUE(offer);
+
+ ASSERT_FALSE(offer->description()->contents().empty());
+ // This should exist if GCM is enabled see CorrectCryptoInOfferWithSdesAndGcm
+ EXPECT_FALSE(SdpContentsAll(HaveSdesGcmCryptos(3), offer->description()));
+}
+
+// When DTLS is disabled and GCM cipher suites are enabled, the SDP offer/answer
+// should have the correct ciphers in the SDES crypto options.
+// With GCM cipher suites enabled, there will be 3 cryptos in the offer and 1
+// in the answer.
+TEST_P(PeerConnectionCryptoTest, CorrectCryptoInOfferWithSdesAndGcm) {
+ PeerConnectionFactoryInterface::Options options;
+ options.crypto_options.srtp.enable_gcm_crypto_suites = true;
+ pc_factory_->SetOptions(options);
+
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ ASSERT_TRUE(offer);
+
+ ASSERT_FALSE(offer->description()->contents().empty());
+ EXPECT_TRUE(SdpContentsAll(HaveSdesGcmCryptos(3), offer->description()));
+}
+
+TEST_P(PeerConnectionCryptoTest, CorrectCryptoInAnswerWithSdesAndGcm) {
+ PeerConnectionFactoryInterface::Options options;
+ options.crypto_options.srtp.enable_gcm_crypto_suites = true;
+ pc_factory_->SetOptions(options);
+
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ for (cricket::ContentInfo& content : offer->description()->contents()) {
+ auto cryptos = content.media_description()->cryptos();
+ cryptos.erase(cryptos.begin()); // Assumes that non-GCM is the default.
+ content.media_description()->set_cryptos(cryptos);
+ }
+
+ callee->SetRemoteDescription(std::move(offer));
+ auto answer = callee->CreateAnswer();
+ ASSERT_TRUE(answer);
+
+ ASSERT_FALSE(answer->description()->contents().empty());
+ EXPECT_TRUE(SdpContentsAll(HaveSdesGcmCryptos(1), answer->description()));
+}
+
+TEST_P(PeerConnectionCryptoTest, CanSetSdesGcmRemoteOfferAndLocalAnswer) {
+ PeerConnectionFactoryInterface::Options options;
+ options.crypto_options.srtp.enable_gcm_crypto_suites = true;
+ pc_factory_->SetOptions(options);
+
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ ASSERT_TRUE(offer);
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+
+ auto answer = callee->CreateAnswer();
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(callee->SetLocalDescription(std::move(answer)));
+}
+
+// The following group tests that two PeerConnections can successfully exchange
+// an offer/answer when DTLS is off and that they will refuse any offer/answer
+// applied locally/remotely if it does not include SDES cryptos.
+TEST_P(PeerConnectionCryptoTest, ExchangeOfferAnswerWhenSdesOn) {
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOfferAndSetAsLocal();
+ ASSERT_TRUE(offer);
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+
+ auto answer = callee->CreateAnswerAndSetAsLocal();
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
+}
+TEST_P(PeerConnectionCryptoTest, FailToSetLocalOfferWithNoCryptosWhenSdesOn) {
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ SdpContentsForEach(RemoveSdesCryptos(), offer->description());
+
+ EXPECT_FALSE(caller->SetLocalDescription(std::move(offer)));
+}
+TEST_P(PeerConnectionCryptoTest, FailToSetRemoteOfferWithNoCryptosWhenSdesOn) {
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ SdpContentsForEach(RemoveSdesCryptos(), offer->description());
+
+ EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer)));
+}
+TEST_P(PeerConnectionCryptoTest, FailToSetLocalAnswerWithNoCryptosWhenSdesOn) {
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal());
+ auto answer = callee->CreateAnswer();
+ SdpContentsForEach(RemoveSdesCryptos(), answer->description());
+
+ EXPECT_FALSE(callee->SetLocalDescription(std::move(answer)));
+}
+TEST_P(PeerConnectionCryptoTest, FailToSetRemoteAnswerWithNoCryptosWhenSdesOn) {
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal());
+ auto answer = callee->CreateAnswerAndSetAsLocal();
+ SdpContentsForEach(RemoveSdesCryptos(), answer->description());
+
+ EXPECT_FALSE(caller->SetRemoteDescription(std::move(answer)));
+}
+#endif
+
+// The following group tests that two PeerConnections can successfully exchange
+// an offer/answer when DTLS is on and that they will refuse any offer/answer
+// applied locally/remotely if it does not include a DTLS fingerprint.
+TEST_P(PeerConnectionCryptoTest, ExchangeOfferAnswerWhenDtlsOn) {
+ RTCConfiguration config;
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOfferAndSetAsLocal();
+ ASSERT_TRUE(offer);
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+
+ auto answer = callee->CreateAnswerAndSetAsLocal();
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
+}
+TEST_P(PeerConnectionCryptoTest,
+ FailToSetLocalOfferWithNoFingerprintWhenDtlsOn) {
+ RTCConfiguration config;
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ SdpContentsForEach(RemoveDtlsFingerprint(), offer->description());
+
+ EXPECT_FALSE(caller->SetLocalDescription(std::move(offer)));
+}
+TEST_P(PeerConnectionCryptoTest,
+ FailToSetRemoteOfferWithNoFingerprintWhenDtlsOn) {
+ RTCConfiguration config;
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOffer();
+ SdpContentsForEach(RemoveDtlsFingerprint(), offer->description());
+
+ EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer)));
+}
+TEST_P(PeerConnectionCryptoTest,
+ FailToSetLocalAnswerWithNoFingerprintWhenDtlsOn) {
+ RTCConfiguration config;
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal());
+ auto answer = callee->CreateAnswer();
+ SdpContentsForEach(RemoveDtlsFingerprint(), answer->description());
+}
+TEST_P(PeerConnectionCryptoTest,
+ FailToSetRemoteAnswerWithNoFingerprintWhenDtlsOn) {
+ RTCConfiguration config;
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal());
+ auto answer = callee->CreateAnswerAndSetAsLocal();
+ SdpContentsForEach(RemoveDtlsFingerprint(), answer->description());
+
+ EXPECT_FALSE(caller->SetRemoteDescription(std::move(answer)));
+}
+
+#if defined(WEBRTC_FUCHSIA)
+// Test that an offer/answer can be exchanged when encryption is disabled.
+TEST_P(PeerConnectionCryptoTest, ExchangeOfferAnswerWhenNoEncryption) {
+ PeerConnectionFactoryInterface::Options options;
+ options.disable_encryption = true;
+ pc_factory_->SetOptions(options);
+
+ RTCConfiguration config;
+ config.enable_dtls_srtp.emplace(false);
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ auto callee = CreatePeerConnectionWithAudioVideo(config);
+
+ auto offer = caller->CreateOfferAndSetAsLocal();
+ ASSERT_TRUE(offer);
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+
+ auto answer = callee->CreateAnswerAndSetAsLocal();
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
+}
+#endif
+
+// Tests that a DTLS call can be established when the certificate is specified
+// in the PeerConnection config and no certificate generator is specified.
+TEST_P(PeerConnectionCryptoTest,
+ ExchangeOfferAnswerWhenDtlsCertificateInConfig) {
+ RTCConfiguration caller_config;
+ caller_config.certificates.push_back(
+ FakeRTCCertificateGenerator::GenerateCertificate());
+ auto caller = CreatePeerConnectionWithAudioVideo(caller_config);
+
+ RTCConfiguration callee_config;
+ callee_config.certificates.push_back(
+ FakeRTCCertificateGenerator::GenerateCertificate());
+ auto callee = CreatePeerConnectionWithAudioVideo(callee_config);
+
+ auto offer = caller->CreateOfferAndSetAsLocal();
+ ASSERT_TRUE(offer);
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+
+ auto answer = callee->CreateAnswerAndSetAsLocal();
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
+}
+
+// The following parameterized test verifies that CreateOffer/CreateAnswer
+// returns successfully (or with failure if the underlying certificate generator
+// fails) no matter when the DTLS certificate is generated. If multiple
+// CreateOffer/CreateAnswer calls are made while waiting for the certificate,
+// they all finish after the certificate is generated.
+
+// Whether the certificate will be generated before calling CreateOffer or
+// while CreateOffer is executing.
+enum class CertGenTime { kBefore, kDuring };
+std::ostream& operator<<(std::ostream& out, CertGenTime value) {
+ switch (value) {
+ case CertGenTime::kBefore:
+ return out << "before";
+ case CertGenTime::kDuring:
+ return out << "during";
+ default:
+ return out << "unknown";
+ }
+}
+
+// Whether the fake certificate generator will produce a certificate or fail.
+enum class CertGenResult { kSucceed, kFail };
+std::ostream& operator<<(std::ostream& out, CertGenResult value) {
+ switch (value) {
+ case CertGenResult::kSucceed:
+ return out << "succeed";
+ case CertGenResult::kFail:
+ return out << "fail";
+ default:
+ return out << "unknown";
+ }
+}
+
+class PeerConnectionCryptoDtlsCertGenTest
+ : public PeerConnectionCryptoBaseTest,
+ public ::testing::WithParamInterface<std::tuple<SdpSemantics,
+ SdpType,
+ CertGenTime,
+ CertGenResult,
+ size_t>> {
+ protected:
+ PeerConnectionCryptoDtlsCertGenTest()
+ : PeerConnectionCryptoBaseTest(std::get<0>(GetParam())) {
+ sdp_type_ = std::get<1>(GetParam());
+ cert_gen_time_ = std::get<2>(GetParam());
+ cert_gen_result_ = std::get<3>(GetParam());
+ concurrent_calls_ = std::get<4>(GetParam());
+ }
+
+ SdpType sdp_type_;
+ CertGenTime cert_gen_time_;
+ CertGenResult cert_gen_result_;
+ size_t concurrent_calls_;
+};
+
+TEST_P(PeerConnectionCryptoDtlsCertGenTest, TestCertificateGeneration) {
+ RTCConfiguration config;
+ auto owned_fake_certificate_generator =
+ std::make_unique<FakeRTCCertificateGenerator>();
+ auto* fake_certificate_generator = owned_fake_certificate_generator.get();
+ fake_certificate_generator->set_should_fail(cert_gen_result_ ==
+ CertGenResult::kFail);
+ fake_certificate_generator->set_should_wait(cert_gen_time_ ==
+ CertGenTime::kDuring);
+ WrapperPtr pc;
+ if (sdp_type_ == SdpType::kOffer) {
+ pc = CreatePeerConnectionWithAudioVideo(
+ config, std::move(owned_fake_certificate_generator));
+ } else {
+ auto caller = CreatePeerConnectionWithAudioVideo(config);
+ pc = CreatePeerConnectionWithAudioVideo(
+ config, std::move(owned_fake_certificate_generator));
+ pc->SetRemoteDescription(caller->CreateOfferAndSetAsLocal());
+ }
+ if (cert_gen_time_ == CertGenTime::kBefore) {
+ ASSERT_TRUE_WAIT(fake_certificate_generator->generated_certificates() +
+ fake_certificate_generator->generated_failures() >
+ 0,
+ kGenerateCertTimeout);
+ } else {
+ ASSERT_EQ(fake_certificate_generator->generated_certificates(), 0);
+ fake_certificate_generator->set_should_wait(false);
+ }
+ std::vector<rtc::scoped_refptr<MockCreateSessionDescriptionObserver>>
+ observers;
+ for (size_t i = 0; i < concurrent_calls_; i++) {
+ rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer =
+ rtc::make_ref_counted<MockCreateSessionDescriptionObserver>();
+ observers.push_back(observer);
+ if (sdp_type_ == SdpType::kOffer) {
+ pc->pc()->CreateOffer(observer.get(),
+ PeerConnectionInterface::RTCOfferAnswerOptions());
+ } else {
+ pc->pc()->CreateAnswer(observer.get(),
+ PeerConnectionInterface::RTCOfferAnswerOptions());
+ }
+ }
+ for (auto& observer : observers) {
+ EXPECT_TRUE_WAIT(observer->called(), 1000);
+ if (cert_gen_result_ == CertGenResult::kSucceed) {
+ EXPECT_TRUE(observer->result());
+ } else {
+ EXPECT_FALSE(observer->result());
+ }
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ PeerConnectionCryptoTest,
+ PeerConnectionCryptoDtlsCertGenTest,
+ Combine(Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan),
+ Values(SdpType::kOffer, SdpType::kAnswer),
+ Values(CertGenTime::kBefore, CertGenTime::kDuring),
+ Values(CertGenResult::kSucceed, CertGenResult::kFail),
+ Values(1, 3)));
+
+// Test that we can create and set an answer correctly when different
+// SSL roles have been negotiated for different transports.
+// See: https://bugs.chromium.org/p/webrtc/issues/detail?id=4525
+TEST_P(PeerConnectionCryptoTest, CreateAnswerWithDifferentSslRoles) {
+ auto caller = CreatePeerConnectionWithAudioVideo();
+ auto callee = CreatePeerConnectionWithAudioVideo();
+
+ RTCOfferAnswerOptions options_no_bundle;
+ options_no_bundle.use_rtp_mux = false;
+
+ // First, negotiate different SSL roles for audio and video.
+ ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
+ auto answer = callee->CreateAnswer(options_no_bundle);
+
+ AudioConnectionRole(answer->description()) = cricket::CONNECTIONROLE_ACTIVE;
+ VideoConnectionRole(answer->description()) = cricket::CONNECTIONROLE_PASSIVE;
+
+ ASSERT_TRUE(
+ callee->SetLocalDescription(CloneSessionDescription(answer.get())));
+ ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
+
+ // Now create an offer in the reverse direction, and ensure the initial
+ // offerer responds with an answer with the correct SSL roles.
+ ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateOfferAndSetAsLocal()));
+ answer = caller->CreateAnswer(options_no_bundle);
+
+ EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
+ AudioConnectionRole(answer->description()));
+ EXPECT_EQ(cricket::CONNECTIONROLE_ACTIVE,
+ VideoConnectionRole(answer->description()));
+
+ ASSERT_TRUE(
+ caller->SetLocalDescription(CloneSessionDescription(answer.get())));
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(answer)));
+
+ // Lastly, start BUNDLE-ing on "audio", expecting that the "passive" role of
+ // audio is transferred over to video in the answer that completes the BUNDLE
+ // negotiation.
+ RTCOfferAnswerOptions options_bundle;
+ options_bundle.use_rtp_mux = true;
+
+ ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateOfferAndSetAsLocal()));
+ answer = caller->CreateAnswer(options_bundle);
+
+ EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
+ AudioConnectionRole(answer->description()));
+ EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
+ VideoConnectionRole(answer->description()));
+
+ ASSERT_TRUE(
+ caller->SetLocalDescription(CloneSessionDescription(answer.get())));
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(answer)));
+}
+
+// Tests that if the DTLS fingerprint is invalid then all future calls to
+// SetLocalDescription and SetRemoteDescription will fail due to a session
+// error.
+// This is a regression test for crbug.com/800775
+TEST_P(PeerConnectionCryptoTest, SessionErrorIfFingerprintInvalid) {
+ auto callee_certificate = rtc::RTCCertificate::FromPEM(kRsaPems[0]);
+ auto other_certificate = rtc::RTCCertificate::FromPEM(kRsaPems[1]);
+
+ auto caller = CreatePeerConnectionWithAudioVideo();
+ RTCConfiguration callee_config;
+ callee_config.certificates.push_back(callee_certificate);
+ auto callee = CreatePeerConnectionWithAudioVideo(callee_config);
+
+ ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
+
+ // Create an invalid answer with the other certificate's fingerprint.
+ auto valid_answer = callee->CreateAnswer();
+ auto invalid_answer = CloneSessionDescription(valid_answer.get());
+ auto* audio_content =
+ cricket::GetFirstAudioContent(invalid_answer->description());
+ ASSERT_TRUE(audio_content);
+ auto* audio_transport_info =
+ invalid_answer->description()->GetTransportInfoByName(
+ audio_content->name);
+ ASSERT_TRUE(audio_transport_info);
+ audio_transport_info->description.identity_fingerprint =
+ rtc::SSLFingerprint::CreateFromCertificate(*other_certificate);
+
+ // Set the invalid answer and expect a fingerprint error.
+ std::string error;
+ ASSERT_FALSE(callee->SetLocalDescription(std::move(invalid_answer), &error));
+ EXPECT_PRED_FORMAT2(AssertStringContains, error,
+ "Local fingerprint does not match identity.");
+
+ // Make sure that setting a valid remote offer or local answer also fails now.
+ ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error));
+ EXPECT_PRED_FORMAT2(AssertStringContains, error,
+ "Session error code: ERROR_CONTENT.");
+ ASSERT_FALSE(callee->SetLocalDescription(std::move(valid_answer), &error));
+ EXPECT_PRED_FORMAT2(AssertStringContains, error,
+ "Session error code: ERROR_CONTENT.");
+}
+
+INSTANTIATE_TEST_SUITE_P(PeerConnectionCryptoTest,
+ PeerConnectionCryptoTest,
+ Values(SdpSemantics::kPlanB_DEPRECATED,
+ SdpSemantics::kUnifiedPlan));
+
+} // namespace webrtc