diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn index f359dc09ab..2821556e67 100644 --- a/webrtc/BUILD.gn +++ b/webrtc/BUILD.gn @@ -385,10 +385,17 @@ if (rtc_include_tests) { rtc_test("rtc_unittests") { testonly = true + sources = [ + "config_unittest.cc", + ] + deps = [ + ":webrtc_common", "api:rtc_api_unittests", "api/audio_codecs/test:audio_codecs_api_unittests", "base:rtc_base_approved_unittests", + "base:rtc_base_tests_main", + "base:rtc_base_tests_utils", "base:rtc_base_unittests", "base:rtc_numerics_unittests", "base:rtc_task_queue_unittests", diff --git a/webrtc/config.cc b/webrtc/config.cc index 1a2c13d044..36e9c3ab9a 100644 --- a/webrtc/config.cc +++ b/webrtc/config.cc @@ -9,6 +9,7 @@ */ #include "webrtc/config.h" +#include #include #include @@ -41,6 +42,9 @@ std::string RtpExtension::ToString() const { std::stringstream ss; ss << "{uri: " << uri; ss << ", id: " << id; + if (encrypt) { + ss << ", encrypt"; + } ss << '}'; return ss.str(); } @@ -80,6 +84,9 @@ const char* RtpExtension::kVideoTimingUri = "http://www.webrtc.org/experiments/rtp-hdrext/video-timing"; const int RtpExtension::kVideoTimingDefaultId = 8; +const char* RtpExtension::kEncryptHeaderExtensionsUri = + "urn:ietf:params:rtp-hdrext:encrypt"; + const int RtpExtension::kMinId = 1; const int RtpExtension::kMaxId = 14; @@ -98,6 +105,61 @@ bool RtpExtension::IsSupportedForVideo(const std::string& uri) { uri == webrtc::RtpExtension::kVideoTimingUri; } +bool RtpExtension::IsEncryptionSupported(const std::string& uri) { + return uri == webrtc::RtpExtension::kAudioLevelUri || + uri == webrtc::RtpExtension::kTimestampOffsetUri || +#if !defined(ENABLE_EXTERNAL_AUTH) + // TODO(jbauch): Figure out a way to always allow "kAbsSendTimeUri" + // here and filter out later if external auth is really used in + // srtpfilter. External auth is used by Chromium and replaces the + // extension header value of "kAbsSendTimeUri", so it must not be + // encrypted (which can't be done by Chromium). + uri == webrtc::RtpExtension::kAbsSendTimeUri || +#endif + uri == webrtc::RtpExtension::kVideoRotationUri || + uri == webrtc::RtpExtension::kTransportSequenceNumberUri || + uri == webrtc::RtpExtension::kPlayoutDelayUri || + uri == webrtc::RtpExtension::kVideoContentTypeUri; +} + +const RtpExtension* RtpExtension::FindHeaderExtensionByUri( + const std::vector& extensions, + const std::string& uri) { + for (const auto& extension : extensions) { + if (extension.uri == uri) { + return &extension; + } + } + return nullptr; +} + +std::vector RtpExtension::FilterDuplicateNonEncrypted( + const std::vector& extensions) { + std::vector filtered; + for (auto extension = extensions.begin(); extension != extensions.end(); + ++extension) { + if (extension->encrypt) { + filtered.push_back(*extension); + continue; + } + + // Only add non-encrypted extension if no encrypted with the same URI + // is also present... + if (std::find_if(extension + 1, extensions.end(), + [extension](const RtpExtension& check) { + return extension->uri == check.uri; + }) != extensions.end()) { + continue; + } + + // ...and has not been added before. + if (!FindHeaderExtensionByUri(filtered, extension->uri)) { + filtered.push_back(*extension); + } + } + return filtered; +} + VideoStream::VideoStream() : width(0), height(0), diff --git a/webrtc/config.h b/webrtc/config.h index d5ed266c89..8211a6619a 100644 --- a/webrtc/config.h +++ b/webrtc/config.h @@ -58,14 +58,30 @@ struct UlpfecConfig { // RTP header extension, see RFC 5285. struct RtpExtension { - RtpExtension() : id(0) {} + RtpExtension() {} RtpExtension(const std::string& uri, int id) : uri(uri), id(id) {} + RtpExtension(const std::string& uri, int id, bool encrypt) : uri(uri), + id(id), encrypt(encrypt) {} std::string ToString() const; bool operator==(const RtpExtension& rhs) const { - return uri == rhs.uri && id == rhs.id; + return uri == rhs.uri && id == rhs.id && encrypt == rhs.encrypt; } static bool IsSupportedForAudio(const std::string& uri); static bool IsSupportedForVideo(const std::string& uri); + // Return "true" if the given RTP header extension URI may be encrypted. + static bool IsEncryptionSupported(const std::string& uri); + + // Returns the named header extension if found among all extensions, + // nullptr otherwise. + static const RtpExtension* FindHeaderExtensionByUri( + const std::vector& extensions, + const std::string& uri); + + // Return a list of RTP header extensions with the non-encrypted extensions + // removed if both the encrypted and non-encrypted extension is present for + // the same URI. + static std::vector FilterDuplicateNonEncrypted( + const std::vector& extensions); // Header extension for audio levels, as defined in: // http://tools.ietf.org/html/draft-ietf-avtext-client-to-mixer-audio-level-03 @@ -104,12 +120,17 @@ struct RtpExtension { static const char* kPlayoutDelayUri; static const int kPlayoutDelayDefaultId; + // Encryption of Header Extensions, see RFC 6904 for details: + // https://tools.ietf.org/html/rfc6904 + static const char* kEncryptHeaderExtensionsUri; + // Inclusive min and max IDs for one-byte header extensions, per RFC5285. static const int kMinId; static const int kMaxId; std::string uri; - int id; + int id = 0; + bool encrypt = false; }; struct VideoStream { diff --git a/webrtc/config_unittest.cc b/webrtc/config_unittest.cc new file mode 100644 index 0000000000..6a8e3b7747 --- /dev/null +++ b/webrtc/config_unittest.cc @@ -0,0 +1,49 @@ +/* + * Copyright (c) 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 "webrtc/config.h" + +#include + +#include "webrtc/base/gunit.h" + +using webrtc::RtpExtension; + +static const char kExtensionUri1[] = "extension-uri1"; +static const char kExtensionUri2[] = "extension-uri2"; + +static const RtpExtension kExtension1(kExtensionUri1, 1); +static const RtpExtension kExtension1Encrypted(kExtensionUri1, 10, true); +static const RtpExtension kExtension2(kExtensionUri2, 2); + +TEST(RtpExtensionTest, FilterDuplicateNonEncrypted) { + std::vector extensions; + std::vector filtered; + + extensions.push_back(kExtension1); + extensions.push_back(kExtension1Encrypted); + filtered = RtpExtension::FilterDuplicateNonEncrypted(extensions); + EXPECT_EQ(1u, filtered.size()); + EXPECT_EQ(std::vector{kExtension1Encrypted}, filtered); + + extensions.clear(); + extensions.push_back(kExtension1Encrypted); + extensions.push_back(kExtension1); + filtered = RtpExtension::FilterDuplicateNonEncrypted(extensions); + EXPECT_EQ(1u, filtered.size()); + EXPECT_EQ(std::vector{kExtension1Encrypted}, filtered); + + extensions.clear(); + extensions.push_back(kExtension1); + extensions.push_back(kExtension2); + filtered = RtpExtension::FilterDuplicateNonEncrypted(extensions); + EXPECT_EQ(2u, filtered.size()); + EXPECT_EQ(extensions, filtered); +} diff --git a/webrtc/media/BUILD.gn b/webrtc/media/BUILD.gn index 304383b64c..11a1aa3e4a 100644 --- a/webrtc/media/BUILD.gn +++ b/webrtc/media/BUILD.gn @@ -327,6 +327,7 @@ if (rtc_include_tests) { sources = [ "base/fakemediaengine.h", "base/fakenetworkinterface.h", + "base/fakertp.cc", "base/fakertp.h", "base/fakevideocapturer.h", "base/fakevideorenderer.h", diff --git a/webrtc/media/base/fakertp.cc b/webrtc/media/base/fakertp.cc new file mode 100644 index 0000000000..f9c880f98b --- /dev/null +++ b/webrtc/media/base/fakertp.cc @@ -0,0 +1,66 @@ +/* + * Copyright (c) 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 + +#include "webrtc/base/gunit.h" +#include "webrtc/media/base/fakertp.h" + +void CompareHeaderExtensions(const char* packet1, size_t packet1_size, + const char* packet2, size_t packet2_size, + const std::vector encrypted_headers, bool expect_equal) { + // Sanity check: packets must be large enough to contain the RTP header and + // extensions header. + RTC_CHECK_GE(packet1_size, 12 + 4); + RTC_CHECK_GE(packet2_size, 12 + 4); + // RTP extension headers are the same. + EXPECT_EQ(0, memcmp(packet1 + 12, packet2 + 12, 4)); + // Check for one-byte header extensions. + EXPECT_EQ('\xBE', packet1[12]); + EXPECT_EQ('\xDE', packet1[13]); + // Determine position and size of extension headers. + size_t extension_words = packet1[14] << 8 | packet1[15]; + const char* extension_data1 = packet1 + 12 + 4; + const char* extension_end1 = extension_data1 + extension_words * 4; + const char* extension_data2 = packet2 + 12 + 4; + // Sanity check: packets must be large enough to contain the RTP header + // extensions. + RTC_CHECK_GE(packet1_size, 12 + 4 + extension_words * 4); + RTC_CHECK_GE(packet2_size, 12 + 4 + extension_words * 4); + while (extension_data1 < extension_end1) { + uint8_t id = (*extension_data1 & 0xf0) >> 4; + uint8_t len = (*extension_data1 & 0x0f) +1; + extension_data1++; + extension_data2++; + EXPECT_LE(extension_data1, extension_end1); + if (id == 15) { + // Finished parsing. + break; + } + + // The header extension doesn't get encrypted if the id is not in the + // list of header extensions to encrypt. + if (expect_equal || + std::find(encrypted_headers.begin(), encrypted_headers.end(), id) + == encrypted_headers.end()) { + EXPECT_EQ(0, memcmp(extension_data1, extension_data2, len)); + } else { + EXPECT_NE(0, memcmp(extension_data1, extension_data2, len)); + } + + extension_data1 += len; + extension_data2 += len; + // Skip padding. + while (extension_data1 < extension_end1 && *extension_data1 == 0) { + extension_data1++; + extension_data2++; + } + } +} diff --git a/webrtc/media/base/fakertp.h b/webrtc/media/base/fakertp.h index a9c6fe0163..88419a55e8 100644 --- a/webrtc/media/base/fakertp.h +++ b/webrtc/media/base/fakertp.h @@ -13,6 +13,8 @@ #ifndef WEBRTC_MEDIA_BASE_FAKERTP_H_ #define WEBRTC_MEDIA_BASE_FAKERTP_H_ +#include + // A typical PCMU RTP packet. // PT=0, SN=1, TS=0, SSRC=1 // all data FF @@ -40,6 +42,48 @@ static const unsigned char kPcmuFrame[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; +// A typical PCMU RTP packet with header extensions. +// PT=0, SN=1, TS=0, SSRC=1 +// all data FF +static const unsigned char kPcmuFrameWithExtensions[] = { + 0x90, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // RFC 5285, section 4.2. One-Byte Header. + 0xBE, 0xDE, + // Header extension length 6 * 32 bits. + 0x00, 0x06, + // 8 bytes header id 1. + 0x17, 0x41, 0x42, 0x73, 0xA4, 0x75, 0x26, 0x27, 0x48, + // 3 bytes header id 2. + 0x22, 0x00, 0x00, 0xC8, + // 1 byte header id 3. + 0x30, 0x8E, + // 7 bytes header id 4. + 0x46, 0x55, 0x99, 0x63, 0x86, 0xB3, 0x95, 0xFB, + // 1 byte header padding. + 0x00, + // Payload data. + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + // A typical Receiver Report RTCP packet. // PT=RR, LN=1, SSRC=1 // send SSRC=2, all other fields 0 @@ -84,4 +128,11 @@ static const unsigned char kDataPacket[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }; +// This expects both packets to be based on kPcmuFrameWithExtensions. +// Header extensions with an id in "encrypted_headers" are expected to be +// different in the packets unless "expect_equal" is set to "true". +void CompareHeaderExtensions(const char* packet1, size_t packet1_size, + const char* packet2, size_t packet2_size, + const std::vector encrypted_headers, bool expect_equal); + #endif // WEBRTC_MEDIA_BASE_FAKERTP_H_ diff --git a/webrtc/media/engine/webrtcmediaengine.cc b/webrtc/media/engine/webrtcmediaengine.cc index 9e02f58b84..e4c4c0dc33 100644 --- a/webrtc/media/engine/webrtcmediaengine.cc +++ b/webrtc/media/engine/webrtcmediaengine.cc @@ -208,18 +208,22 @@ std::vector FilterRtpExtensions( } } - // Sort by name, ascending, so that we don't reset extensions if they were - // specified in a different order (also allows us to use std::unique below). + // Sort by name, ascending (prioritise encryption), so that we don't reset + // extensions if they were specified in a different order (also allows us + // to use std::unique below). std::sort(result.begin(), result.end(), [](const webrtc::RtpExtension& rhs, - const webrtc::RtpExtension& lhs) { return rhs.uri < lhs.uri; }); + const webrtc::RtpExtension& lhs) { + return rhs.encrypt == lhs.encrypt ? rhs.uri < lhs.uri + : rhs.encrypt > lhs.encrypt; + }); // Remove unnecessary extensions (used on send side). if (filter_redundant_extensions) { auto it = std::unique( result.begin(), result.end(), [](const webrtc::RtpExtension& rhs, const webrtc::RtpExtension& lhs) { - return rhs.uri == lhs.uri; + return rhs.uri == lhs.uri && rhs.encrypt == lhs.encrypt; }); result.erase(it, result.end()); diff --git a/webrtc/media/engine/webrtcmediaengine_unittest.cc b/webrtc/media/engine/webrtcmediaengine_unittest.cc index 7a97c1a0b9..d22a8da757 100644 --- a/webrtc/media/engine/webrtcmediaengine_unittest.cc +++ b/webrtc/media/engine/webrtcmediaengine_unittest.cc @@ -145,6 +145,38 @@ TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundant) { EXPECT_NE(filtered[0].uri, filtered[1].uri); } +TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundantEncrypted_1) { + std::vector extensions; + extensions.push_back(webrtc::RtpExtension("b", 1)); + extensions.push_back(webrtc::RtpExtension("b", 2, true)); + extensions.push_back(webrtc::RtpExtension("c", 3)); + extensions.push_back(webrtc::RtpExtension("b", 4)); + std::vector filtered = + FilterRtpExtensions(extensions, SupportedExtensions2, true); + EXPECT_EQ(3, filtered.size()); + EXPECT_TRUE(IsSorted(filtered)); + EXPECT_EQ(filtered[0].uri, filtered[1].uri); + EXPECT_NE(filtered[0].encrypt, filtered[1].encrypt); + EXPECT_NE(filtered[0].uri, filtered[2].uri); + EXPECT_NE(filtered[1].uri, filtered[2].uri); +} + +TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundantEncrypted_2) { + std::vector extensions; + extensions.push_back(webrtc::RtpExtension("b", 1, true)); + extensions.push_back(webrtc::RtpExtension("b", 2)); + extensions.push_back(webrtc::RtpExtension("c", 3)); + extensions.push_back(webrtc::RtpExtension("b", 4)); + std::vector filtered = + FilterRtpExtensions(extensions, SupportedExtensions2, true); + EXPECT_EQ(3, filtered.size()); + EXPECT_TRUE(IsSorted(filtered)); + EXPECT_EQ(filtered[0].uri, filtered[1].uri); + EXPECT_NE(filtered[0].encrypt, filtered[1].encrypt); + EXPECT_NE(filtered[0].uri, filtered[2].uri); + EXPECT_NE(filtered[1].uri, filtered[2].uri); +} + TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundantBwe_1) { std::vector extensions; extensions.push_back( @@ -160,6 +192,27 @@ TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundantBwe_1) { EXPECT_EQ(RtpExtension::kTransportSequenceNumberUri, filtered[0].uri); } +TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundantBweEncrypted_1) { + std::vector extensions; + extensions.push_back( + RtpExtension(RtpExtension::kTransportSequenceNumberUri, 3)); + extensions.push_back( + RtpExtension(RtpExtension::kTransportSequenceNumberUri, 4, true)); + extensions.push_back(RtpExtension(RtpExtension::kTimestampOffsetUri, 9)); + extensions.push_back(RtpExtension(RtpExtension::kAbsSendTimeUri, 6)); + extensions.push_back( + RtpExtension(RtpExtension::kTransportSequenceNumberUri, 1)); + extensions.push_back( + RtpExtension(RtpExtension::kTransportSequenceNumberUri, 2, true)); + extensions.push_back(RtpExtension(RtpExtension::kTimestampOffsetUri, 14)); + std::vector filtered = + FilterRtpExtensions(extensions, SupportedExtensions2, true); + EXPECT_EQ(2, filtered.size()); + EXPECT_EQ(RtpExtension::kTransportSequenceNumberUri, filtered[0].uri); + EXPECT_EQ(RtpExtension::kTransportSequenceNumberUri, filtered[1].uri); + EXPECT_NE(filtered[0].encrypt, filtered[1].encrypt); +} + TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundantBwe_2) { std::vector extensions; extensions.push_back(RtpExtension(RtpExtension::kTimestampOffsetUri, 1)); diff --git a/webrtc/p2p/base/dtlstransportchannel.cc b/webrtc/p2p/base/dtlstransportchannel.cc index fc16edff14..f5d658340b 100644 --- a/webrtc/p2p/base/dtlstransportchannel.cc +++ b/webrtc/p2p/base/dtlstransportchannel.cc @@ -117,7 +117,8 @@ DtlsTransport::DtlsTransport(IceTransportInternal* ice_transport, downward_(NULL), srtp_ciphers_(GetSupportedDtlsSrtpCryptoSuites(crypto_options)), ssl_role_(rtc::SSL_CLIENT), - ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_12) { + ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_12), + crypto_options_(crypto_options) { ice_transport_->SignalWritableState.connect(this, &DtlsTransport::OnWritableState); ice_transport_->SignalReadPacket.connect(this, &DtlsTransport::OnReadPacket); diff --git a/webrtc/p2p/base/dtlstransportchannel.h b/webrtc/p2p/base/dtlstransportchannel.h index 65ff4424fb..30a7186b7f 100644 --- a/webrtc/p2p/base/dtlstransportchannel.h +++ b/webrtc/p2p/base/dtlstransportchannel.h @@ -94,6 +94,10 @@ class DtlsTransport : public DtlsTransportInternal { const rtc::CryptoOptions& crypto_options); ~DtlsTransport() override; + const rtc::CryptoOptions& crypto_options() const override { + return crypto_options_; + } + DtlsTransportState dtls_state() const override { return dtls_state_; } const std::string& transport_name() const override { return transport_name_; } @@ -218,6 +222,7 @@ class DtlsTransport : public DtlsTransportInternal { rtc::scoped_refptr local_certificate_; rtc::SSLRole ssl_role_; rtc::SSLProtocolVersion ssl_max_version_; + rtc::CryptoOptions crypto_options_; rtc::Buffer remote_fingerprint_value_; std::string remote_fingerprint_algorithm_; diff --git a/webrtc/p2p/base/dtlstransportinternal.h b/webrtc/p2p/base/dtlstransportinternal.h index 9ec46639d4..448dd49251 100644 --- a/webrtc/p2p/base/dtlstransportinternal.h +++ b/webrtc/p2p/base/dtlstransportinternal.h @@ -39,6 +39,8 @@ class DtlsTransportInternal : public rtc::PacketTransportInternal { public: virtual ~DtlsTransportInternal() {} + virtual const rtc::CryptoOptions& crypto_options() const = 0; + virtual DtlsTransportState dtls_state() const = 0; virtual const std::string& transport_name() const = 0; diff --git a/webrtc/p2p/base/fakedtlstransport.h b/webrtc/p2p/base/fakedtlstransport.h index 9d5859c4dd..0384f00a40 100644 --- a/webrtc/p2p/base/fakedtlstransport.h +++ b/webrtc/p2p/base/fakedtlstransport.h @@ -91,6 +91,7 @@ class FakeDtlsTransport : public DtlsTransportInternal { if (!asymmetric) { dest->SetDestination(this, true); } + dtls_state_ = DTLS_TRANSPORT_CONNECTED; ice_transport_->SetDestination( static_cast(dest->ice_transport()), asymmetric); } else { @@ -122,6 +123,12 @@ class FakeDtlsTransport : public DtlsTransportInternal { *role = ssl_role_; return true; } + const rtc::CryptoOptions& crypto_options() const override { + return crypto_options_; + } + void SetCryptoOptions(const rtc::CryptoOptions& crypto_options) { + crypto_options_ = crypto_options; + } bool SetLocalCertificate( const rtc::scoped_refptr& certificate) override { local_cert_ = certificate; @@ -135,9 +142,12 @@ class FakeDtlsTransport : public DtlsTransportInternal { if (!do_dtls_) { return false; } - *crypto_suite = rtc::SRTP_AES128_CM_SHA1_80; + *crypto_suite = crypto_suite_; return true; } + void SetSrtpCryptoSuite(int crypto_suite) { + crypto_suite_ = crypto_suite; + } bool GetSslCipherSuite(int* cipher_suite) override { return false; } rtc::scoped_refptr GetLocalCertificate() const override { return local_cert_; @@ -230,6 +240,8 @@ class FakeDtlsTransport : public DtlsTransportInternal { rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_12; rtc::SSLFingerprint dtls_fingerprint_; rtc::SSLRole ssl_role_ = rtc::SSL_CLIENT; + int crypto_suite_ = rtc::SRTP_AES128_CM_SHA1_80; + rtc::CryptoOptions crypto_options_; DtlsTransportState dtls_state_ = DTLS_TRANSPORT_NEW; diff --git a/webrtc/pc/channel.cc b/webrtc/pc/channel.cc index 00f78b36c9..e612aaece6 100644 --- a/webrtc/pc/channel.cc +++ b/webrtc/pc/channel.cc @@ -8,6 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include +#include #include #include "webrtc/pc/channel.h" @@ -46,20 +48,6 @@ struct SendPacketMessageData : public rtc::MessageData { rtc::PacketOptions options; }; -#if defined(ENABLE_EXTERNAL_AUTH) -// Returns the named header extension if found among all extensions, -// nullptr otherwise. -const webrtc::RtpExtension* FindHeaderExtension( - const std::vector& extensions, - const std::string& uri) { - for (const auto& extension : extensions) { - if (extension.uri == uri) - return &extension; - } - return nullptr; -} -#endif - } // namespace enum { @@ -130,6 +118,7 @@ static const MediaContentDescription* GetContentDescription( template void RtpParametersFromMediaDescription( const MediaContentDescriptionImpl* desc, + const RtpHeaderExtensions& extensions, RtpParameters* params) { // TODO(pthatcher): Remove this once we're sure no one will give us // a description without codecs (currently a CA_UPDATE with just @@ -140,7 +129,7 @@ void RtpParametersFromMediaDescription( // TODO(pthatcher): See if we really need // rtp_header_extensions_set() and remove it if we don't. if (desc->rtp_header_extensions_set()) { - params->extensions = desc->rtp_header_extensions(); + params->extensions = extensions; } params->rtcp.reduced_size = desc->rtcp_reduced_size(); } @@ -148,8 +137,9 @@ void RtpParametersFromMediaDescription( template void RtpSendParametersFromMediaDescription( const MediaContentDescriptionImpl* desc, + const RtpHeaderExtensions& extensions, RtpSendParameters* send_params) { - RtpParametersFromMediaDescription(desc, send_params); + RtpParametersFromMediaDescription(desc, extensions, send_params); send_params->max_bandwidth_bps = desc->bandwidth(); } @@ -998,16 +988,30 @@ bool BaseChannel::SetupDtlsSrtp_n(bool rtcp) { recv_key = &server_write_key; } - if (rtcp) { - ret = srtp_filter_.SetRtcpParams(selected_crypto_suite, &(*send_key)[0], - static_cast(send_key->size()), - selected_crypto_suite, &(*recv_key)[0], - static_cast(recv_key->size())); + if (!srtp_filter_.IsActive()) { + if (rtcp) { + ret = srtp_filter_.SetRtcpParams(selected_crypto_suite, &(*send_key)[0], + static_cast(send_key->size()), + selected_crypto_suite, &(*recv_key)[0], + static_cast(recv_key->size())); + } else { + ret = srtp_filter_.SetRtpParams(selected_crypto_suite, &(*send_key)[0], + static_cast(send_key->size()), + selected_crypto_suite, &(*recv_key)[0], + static_cast(recv_key->size())); + } } else { - ret = srtp_filter_.SetRtpParams(selected_crypto_suite, &(*send_key)[0], - static_cast(send_key->size()), - selected_crypto_suite, &(*recv_key)[0], - static_cast(recv_key->size())); + if (rtcp) { + // RTCP doesn't need to be updated because UpdateRtpParams is only used + // to update the set of encrypted RTP header extension IDs. + ret = true; + } else { + ret = srtp_filter_.UpdateRtpParams( + selected_crypto_suite, + &(*send_key)[0], static_cast(send_key->size()), + selected_crypto_suite, + &(*recv_key)[0], static_cast(recv_key->size())); + } } if (!ret) { @@ -1055,26 +1059,39 @@ bool BaseChannel::SetRtpTransportParameters( const MediaContentDescription* content, ContentAction action, ContentSource src, + const RtpHeaderExtensions& extensions, std::string* error_desc) { if (action == CA_UPDATE) { // These parameters never get changed by a CA_UDPATE. return true; } + std::vector encrypted_extension_ids; + for (const webrtc::RtpExtension& extension : extensions) { + if (extension.encrypt) { + LOG(LS_INFO) << "Using " << (src == CS_LOCAL ? "local" : "remote") + << " encrypted extension: " << extension.ToString(); + encrypted_extension_ids.push_back(extension.id); + } + } + // Cache srtp_required_ for belt and suspenders check on SendPacket return network_thread_->Invoke( RTC_FROM_HERE, Bind(&BaseChannel::SetRtpTransportParameters_n, this, - content, action, src, error_desc)); + content, action, src, encrypted_extension_ids, + error_desc)); } bool BaseChannel::SetRtpTransportParameters_n( const MediaContentDescription* content, ContentAction action, ContentSource src, + const std::vector& encrypted_extension_ids, std::string* error_desc) { RTC_DCHECK(network_thread_->IsCurrent()); - if (!SetSrtp_n(content->cryptos(), action, src, error_desc)) { + if (!SetSrtp_n(content->cryptos(), action, src, encrypted_extension_ids, + error_desc)) { return false; } @@ -1101,6 +1118,7 @@ bool BaseChannel::CheckSrtpConfig_n(const std::vector& cryptos, bool BaseChannel::SetSrtp_n(const std::vector& cryptos, ContentAction action, ContentSource src, + const std::vector& encrypted_extension_ids, std::string* error_desc) { TRACE_EVENT0("webrtc", "BaseChannel::SetSrtp_w"); if (action == CA_UPDATE) { @@ -1113,6 +1131,7 @@ bool BaseChannel::SetSrtp_n(const std::vector& cryptos, if (!ret) { return false; } + srtp_filter_.SetEncryptedHeaderExtensionIds(src, encrypted_extension_ids); switch (action) { case CA_OFFER: // If DTLS is already active on the channel, we could be renegotiating @@ -1138,6 +1157,14 @@ bool BaseChannel::SetSrtp_n(const std::vector& cryptos, default: break; } + // Only update SRTP filter if using DTLS. SDES is handled internally + // by the SRTP filter. + // TODO(jbauch): Only update if encrypted extension ids have changed. + if (ret && dtls_keyed_ && rtp_dtls_transport_ && + rtp_dtls_transport_->dtls_state() == DTLS_TRANSPORT_CONNECTED) { + bool rtcp = false; + ret = SetupDtlsSrtp_n(rtcp); + } if (!ret) { SafeSetError("Failed to setup SRTP filter.", error_desc); return false; @@ -1369,6 +1396,23 @@ bool BaseChannel::UpdateRemoteStreams_w( return ret; } +RtpHeaderExtensions BaseChannel::GetFilteredRtpHeaderExtensions( + const RtpHeaderExtensions& extensions) { + if (!rtp_dtls_transport_ || + !rtp_dtls_transport_->crypto_options() + .enable_encrypted_rtp_header_extensions) { + RtpHeaderExtensions filtered; + auto pred = [](const webrtc::RtpExtension& extension) { + return !extension.encrypt; + }; + std::copy_if(extensions.begin(), extensions.end(), + std::back_inserter(filtered), pred); + return filtered; + } + + return webrtc::RtpExtension::FilterDuplicateNonEncrypted(extensions); +} + void BaseChannel::MaybeCacheRtpAbsSendTimeHeaderExtension_w( const std::vector& extensions) { // Absolute Send Time extension id is used only with external auth, @@ -1376,7 +1420,8 @@ void BaseChannel::MaybeCacheRtpAbsSendTimeHeaderExtension_w( // something that is not used. #if defined(ENABLE_EXTERNAL_AUTH) const webrtc::RtpExtension* send_time_extension = - FindHeaderExtension(extensions, webrtc::RtpExtension::kAbsSendTimeUri); + webrtc::RtpExtension::FindHeaderExtensionByUri( + extensions, webrtc::RtpExtension::kAbsSendTimeUri); int rtp_abs_sendtime_extn_id = send_time_extension ? send_time_extension->id : -1; invoker_.AsyncInvoke( @@ -1724,12 +1769,16 @@ bool VoiceChannel::SetLocalContent_w(const MediaContentDescription* content, return false; } - if (!SetRtpTransportParameters(content, action, CS_LOCAL, error_desc)) { + RtpHeaderExtensions rtp_header_extensions = + GetFilteredRtpHeaderExtensions(audio->rtp_header_extensions()); + + if (!SetRtpTransportParameters(content, action, CS_LOCAL, + rtp_header_extensions, error_desc)) { return false; } AudioRecvParameters recv_params = last_recv_params_; - RtpParametersFromMediaDescription(audio, &recv_params); + RtpParametersFromMediaDescription(audio, rtp_header_extensions, &recv_params); if (!media_channel()->SetRecvParameters(recv_params)) { SafeSetError("Failed to set local audio description recv parameters.", error_desc); @@ -1769,12 +1818,17 @@ bool VoiceChannel::SetRemoteContent_w(const MediaContentDescription* content, return false; } - if (!SetRtpTransportParameters(content, action, CS_REMOTE, error_desc)) { + RtpHeaderExtensions rtp_header_extensions = + GetFilteredRtpHeaderExtensions(audio->rtp_header_extensions()); + + if (!SetRtpTransportParameters(content, action, CS_REMOTE, + rtp_header_extensions, error_desc)) { return false; } AudioSendParameters send_params = last_send_params_; - RtpSendParametersFromMediaDescription(audio, &send_params); + RtpSendParametersFromMediaDescription(audio, rtp_header_extensions, + &send_params); if (audio->agc_minus_10db()) { send_params.options.adjust_agc_delta = rtc::Optional(kAgcMinus10db); } @@ -1797,7 +1851,7 @@ bool VoiceChannel::SetRemoteContent_w(const MediaContentDescription* content, } if (audio->rtp_header_extensions_set()) { - MaybeCacheRtpAbsSendTimeHeaderExtension_w(audio->rtp_header_extensions()); + MaybeCacheRtpAbsSendTimeHeaderExtension_w(rtp_header_extensions); } set_remote_content_direction(content->direction()); @@ -2002,12 +2056,16 @@ bool VideoChannel::SetLocalContent_w(const MediaContentDescription* content, return false; } - if (!SetRtpTransportParameters(content, action, CS_LOCAL, error_desc)) { + RtpHeaderExtensions rtp_header_extensions = + GetFilteredRtpHeaderExtensions(video->rtp_header_extensions()); + + if (!SetRtpTransportParameters(content, action, CS_LOCAL, + rtp_header_extensions, error_desc)) { return false; } VideoRecvParameters recv_params = last_recv_params_; - RtpParametersFromMediaDescription(video, &recv_params); + RtpParametersFromMediaDescription(video, rtp_header_extensions, &recv_params); if (!media_channel()->SetRecvParameters(recv_params)) { SafeSetError("Failed to set local video description recv parameters.", error_desc); @@ -2047,12 +2105,17 @@ bool VideoChannel::SetRemoteContent_w(const MediaContentDescription* content, return false; } - if (!SetRtpTransportParameters(content, action, CS_REMOTE, error_desc)) { + RtpHeaderExtensions rtp_header_extensions = + GetFilteredRtpHeaderExtensions(video->rtp_header_extensions()); + + if (!SetRtpTransportParameters(content, action, CS_REMOTE, + rtp_header_extensions, error_desc)) { return false; } VideoSendParameters send_params = last_send_params_; - RtpSendParametersFromMediaDescription(video, &send_params); + RtpSendParametersFromMediaDescription(video, rtp_header_extensions, + &send_params); if (video->conference_mode()) { send_params.conference_mode = true; } @@ -2076,7 +2139,7 @@ bool VideoChannel::SetRemoteContent_w(const MediaContentDescription* content, } if (video->rtp_header_extensions_set()) { - MaybeCacheRtpAbsSendTimeHeaderExtension_w(video->rtp_header_extensions()); + MaybeCacheRtpAbsSendTimeHeaderExtension_w(rtp_header_extensions); } set_remote_content_direction(content->direction()); @@ -2197,12 +2260,16 @@ bool RtpDataChannel::SetLocalContent_w(const MediaContentDescription* content, return false; } - if (!SetRtpTransportParameters(content, action, CS_LOCAL, error_desc)) { + RtpHeaderExtensions rtp_header_extensions = + GetFilteredRtpHeaderExtensions(data->rtp_header_extensions()); + + if (!SetRtpTransportParameters(content, action, CS_LOCAL, + rtp_header_extensions, error_desc)) { return false; } DataRecvParameters recv_params = last_recv_params_; - RtpParametersFromMediaDescription(data, &recv_params); + RtpParametersFromMediaDescription(data, rtp_header_extensions, &recv_params); if (!media_channel()->SetRecvParameters(recv_params)) { SafeSetError("Failed to set remote data description recv parameters.", error_desc); @@ -2251,13 +2318,18 @@ bool RtpDataChannel::SetRemoteContent_w(const MediaContentDescription* content, return false; } + RtpHeaderExtensions rtp_header_extensions = + GetFilteredRtpHeaderExtensions(data->rtp_header_extensions()); + LOG(LS_INFO) << "Setting remote data description"; - if (!SetRtpTransportParameters(content, action, CS_REMOTE, error_desc)) { + if (!SetRtpTransportParameters(content, action, CS_REMOTE, + rtp_header_extensions, error_desc)) { return false; } DataSendParameters send_params = last_send_params_; - RtpSendParametersFromMediaDescription(data, &send_params); + RtpSendParametersFromMediaDescription(data, rtp_header_extensions, + &send_params); if (!media_channel()->SetSendParameters(send_params)) { SafeSetError("Failed to set remote data description send parameters.", error_desc); diff --git a/webrtc/pc/channel.h b/webrtc/pc/channel.h index 6a420538d7..f8cc286279 100644 --- a/webrtc/pc/channel.h +++ b/webrtc/pc/channel.h @@ -319,13 +319,18 @@ class BaseChannel ContentAction action, std::string* error_desc) = 0; bool SetRtpTransportParameters(const MediaContentDescription* content, - ContentAction action, - ContentSource src, - std::string* error_desc); + ContentAction action, ContentSource src, + const RtpHeaderExtensions& extensions, std::string* error_desc); bool SetRtpTransportParameters_n(const MediaContentDescription* content, - ContentAction action, - ContentSource src, - std::string* error_desc); + ContentAction action, ContentSource src, + const std::vector& encrypted_extension_ids, + std::string* error_desc); + + // Return a list of RTP header extensions with the non-encrypted extensions + // removed depending on the current crypto_options_ and only if both the + // non-encrypted and encrypted extension is present for the same URI. + RtpHeaderExtensions GetFilteredRtpHeaderExtensions( + const RtpHeaderExtensions& extensions); // Helper method to get RTP Absoulute SendTime extension header id if // present in remote supported extensions list. @@ -338,6 +343,7 @@ class BaseChannel bool SetSrtp_n(const std::vector& params, ContentAction action, ContentSource src, + const std::vector& encrypted_extension_ids, std::string* error_desc); bool SetRtcpMux_n(bool enable, ContentAction action, diff --git a/webrtc/pc/channel_unittest.cc b/webrtc/pc/channel_unittest.cc index cb7d318982..7fe1472e25 100644 --- a/webrtc/pc/channel_unittest.cc +++ b/webrtc/pc/channel_unittest.cc @@ -100,6 +100,8 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { // Use BaseChannel with PacketTransportInternal rather than // DtlsTransportInternal. RAW_PACKET_TRANSPORT = 0x20, + GCM_CIPHER = 0x40, + ENCRYPTED_HEADERS = 0x80, }; ChannelTest(bool verify_playout, @@ -175,6 +177,22 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { fake_rtcp_dtls_transport1_->SetLocalCertificate(cert1); } } + if (flags1 & ENCRYPTED_HEADERS) { + rtc::CryptoOptions crypto_options; + crypto_options.enable_encrypted_rtp_header_extensions = true; + fake_rtp_dtls_transport1_->SetCryptoOptions(crypto_options); + if (fake_rtcp_dtls_transport1_) { + fake_rtcp_dtls_transport1_->SetCryptoOptions(crypto_options); + } + } + if (flags1 & GCM_CIPHER) { + fake_rtp_dtls_transport1_->SetSrtpCryptoSuite( + rtc::SRTP_AEAD_AES_256_GCM); + if (fake_rtcp_dtls_transport1_) { + fake_rtcp_dtls_transport1_->SetSrtpCryptoSuite( + rtc::SRTP_AEAD_AES_256_GCM); + } + } } // Based on flags, create fake DTLS or raw packet transports. if (flags2 & RAW_PACKET_TRANSPORT) { @@ -205,6 +223,22 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { fake_rtcp_dtls_transport2_->SetLocalCertificate(cert2); } } + if (flags2 & ENCRYPTED_HEADERS) { + rtc::CryptoOptions crypto_options; + crypto_options.enable_encrypted_rtp_header_extensions = true; + fake_rtp_dtls_transport2_->SetCryptoOptions(crypto_options); + if (fake_rtcp_dtls_transport2_) { + fake_rtcp_dtls_transport2_->SetCryptoOptions(crypto_options); + } + } + if (flags2 & GCM_CIPHER) { + fake_rtp_dtls_transport2_->SetSrtpCryptoSuite( + rtc::SRTP_AEAD_AES_256_GCM); + if (fake_rtcp_dtls_transport2_) { + fake_rtcp_dtls_transport2_->SetSrtpCryptoSuite( + rtc::SRTP_AEAD_AES_256_GCM); + } + } } channel1_.reset( CreateChannel(worker_thread, network_thread_, &media_engine_, ch1, @@ -869,6 +903,183 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { EXPECT_TRUE(CheckCustomRtp1(kSsrc2, 0)); } + enum EncryptedHeaderTestScenario { + // Offer/Answer are processed before DTLS completes. + DEFAULT, + // DTLS completes before any Offer/Answer have been sent. + DTLS_BEFORE_OFFER_ANSWER, + // DTLS completes after channel 2 has processed (remote) Offer and (local) + // Answer. + DTLS_AFTER_CHANNEL2_READY, + }; + + // Test that encrypted header extensions are working and can be changed when + // sending a new OFFER/ANSWER. + void TestChangeEncryptedHeaderExtensions(int flags, + EncryptedHeaderTestScenario scenario = DEFAULT) { + RTC_CHECK(scenario == 0 || (flags & DTLS)); + struct PacketListener : public sigslot::has_slots<> { + PacketListener() {} + void OnReadPacket(rtc::PacketTransportInternal* transport, + const char* data, size_t size, const rtc::PacketTime& time, + int flags) { + CompareHeaderExtensions( + reinterpret_cast(kPcmuFrameWithExtensions), + sizeof(kPcmuFrameWithExtensions), data, size, encrypted_headers, + false); + } + std::vector encrypted_headers; + } packet_listener1, packet_listener2; + + cricket::StreamParams stream1; + stream1.groupid = "group1"; + stream1.id = "stream1"; + stream1.ssrcs.push_back(kSsrc1); + stream1.cname = "stream1_cname"; + + cricket::StreamParams stream2; + stream2.groupid = "group1"; + stream2.id = "stream2"; + stream2.ssrcs.push_back(kSsrc2); + stream2.cname = "stream2_cname"; + + // Use SRTP when testing encrypted extensions. + int channel_flags = flags | SECURE | ENCRYPTED_HEADERS; + // Enable SDES if channel is not using DTLS. + int content_flags = (channel_flags & DTLS) == 0 ? SECURE : 0; + + // kPcmuFrameWithExtensions contains RTP extension headers with ids 1-4. + // Make sure to use URIs that are supported for encryption. + cricket::RtpHeaderExtensions extensions1; + extensions1.push_back( + RtpExtension(RtpExtension::kAudioLevelUri, 10)); + extensions1.push_back( + RtpExtension(RtpExtension::kAudioLevelUri, 1, true)); + + cricket::RtpHeaderExtensions extensions2; + extensions2.push_back( + RtpExtension(RtpExtension::kAudioLevelUri, 10)); + extensions2.push_back( + RtpExtension(RtpExtension::kAudioLevelUri, 2, true)); + extensions2.push_back( + RtpExtension(RtpExtension::kVideoRotationUri, 3)); + extensions2.push_back( + RtpExtension(RtpExtension::kTimestampOffsetUri, 4, true)); + + // Setup a call where channel 1 send |stream1| to channel 2. + CreateChannels(channel_flags, channel_flags); + fake_rtp_dtls_transport1_->fake_ice_transport()->SignalReadPacket.connect( + &packet_listener1, &PacketListener::OnReadPacket); + fake_rtp_dtls_transport2_->fake_ice_transport()->SignalReadPacket.connect( + &packet_listener2, &PacketListener::OnReadPacket); + + if (scenario == DTLS_BEFORE_OFFER_ANSWER) { + ConnectFakeTransports(); + WaitForThreads(); + } + + typename T::Content content1; + CreateContent(content_flags, kPcmuCodec, kH264Codec, &content1); + content1.AddStream(stream1); + content1.set_rtp_header_extensions(extensions1); + EXPECT_TRUE(channel1_->SetLocalContent(&content1, CA_OFFER, NULL)); + EXPECT_TRUE(channel1_->Enable(true)); + EXPECT_EQ(1u, media_channel1_->send_streams().size()); + packet_listener1.encrypted_headers.push_back(1); + + EXPECT_TRUE(channel2_->SetRemoteContent(&content1, CA_OFFER, NULL)); + EXPECT_EQ(1u, media_channel2_->recv_streams().size()); + + // Channel 2 sends back |stream2|. + typename T::Content content2; + CreateContent(content_flags, kPcmuCodec, kH264Codec, &content2); + content2.AddStream(stream2); + content2.set_rtp_header_extensions(extensions1); + EXPECT_TRUE(channel2_->SetLocalContent(&content2, CA_ANSWER, NULL)); + EXPECT_TRUE(channel2_->Enable(true)); + EXPECT_EQ(1u, media_channel2_->send_streams().size()); + packet_listener2.encrypted_headers.push_back(1); + + if (scenario == DTLS_AFTER_CHANNEL2_READY) { + ConnectFakeTransports(); + WaitForThreads(); + } + + if (scenario == DTLS_BEFORE_OFFER_ANSWER || + scenario == DTLS_AFTER_CHANNEL2_READY) { + // In both scenarios with partially completed Offer/Answer, sending + // packets from Channel 2 to Channel 1 should work. + SendCustomRtp2(kSsrc2, 0); + WaitForThreads(); + EXPECT_TRUE(CheckCustomRtp1(kSsrc2, 0)); + } + + EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_ANSWER, NULL)); + EXPECT_EQ(1u, media_channel1_->recv_streams().size()); + + if (scenario == DEFAULT) { + ConnectFakeTransports(); + WaitForThreads(); + } + + SendCustomRtp1(kSsrc1, 0); + SendCustomRtp2(kSsrc2, 0); + WaitForThreads(); + EXPECT_TRUE(CheckCustomRtp2(kSsrc1, 0)); + EXPECT_TRUE(CheckCustomRtp1(kSsrc2, 0)); + + // Let channel 2 update the encrypted header extensions. + typename T::Content content3; + CreateContent(content_flags, kPcmuCodec, kH264Codec, &content3); + content3.AddStream(stream2); + content3.set_rtp_header_extensions(extensions2); + EXPECT_TRUE(channel2_->SetLocalContent(&content3, CA_OFFER, NULL)); + ASSERT_EQ(1u, media_channel2_->send_streams().size()); + EXPECT_EQ(stream2, media_channel2_->send_streams()[0]); + packet_listener2.encrypted_headers.clear(); + packet_listener2.encrypted_headers.push_back(2); + packet_listener2.encrypted_headers.push_back(4); + + EXPECT_TRUE(channel1_->SetRemoteContent(&content3, CA_OFFER, NULL)); + ASSERT_EQ(1u, media_channel1_->recv_streams().size()); + EXPECT_EQ(stream2, media_channel1_->recv_streams()[0]); + + // Channel 1 is already sending the new encrypted extensions. These + // can be decrypted by channel 2. Channel 2 is still sending the old + // encrypted extensions (which can be decrypted by channel 1). + + if (flags & DTLS) { + // DTLS supports updating the encrypted extensions with only the OFFER + // being processed. For SDES both the OFFER and ANSWER must have been + // processed to update encrypted extensions, so we can't check this case. + SendCustomRtp1(kSsrc1, 0); + SendCustomRtp2(kSsrc2, 0); + WaitForThreads(); + EXPECT_TRUE(CheckCustomRtp2(kSsrc1, 0)); + EXPECT_TRUE(CheckCustomRtp1(kSsrc2, 0)); + } + + // Channel 1 replies with the same extensions. + typename T::Content content4; + CreateContent(content_flags, kPcmuCodec, kH264Codec, &content4); + content4.AddStream(stream1); + content4.set_rtp_header_extensions(extensions2); + EXPECT_TRUE(channel1_->SetLocalContent(&content4, CA_ANSWER, NULL)); + EXPECT_EQ(1u, media_channel1_->send_streams().size()); + packet_listener1.encrypted_headers.clear(); + packet_listener1.encrypted_headers.push_back(2); + packet_listener1.encrypted_headers.push_back(4); + + EXPECT_TRUE(channel2_->SetRemoteContent(&content4, CA_ANSWER, NULL)); + EXPECT_EQ(1u, media_channel2_->recv_streams().size()); + + SendCustomRtp1(kSsrc1, 0); + SendCustomRtp2(kSsrc2, 0); + WaitForThreads(); + EXPECT_TRUE(CheckCustomRtp2(kSsrc1, 0)); + EXPECT_TRUE(CheckCustomRtp1(kSsrc2, 0)); + } + // Test that we only start playout and sending at the right times. void TestPlayoutAndSendingStates() { CreateChannels(0, 0); @@ -1955,6 +2166,24 @@ class VoiceChannelDoubleThreadTest : public ChannelTest { : Base(true, kPcmuFrame, kRtcpReport, NetworkIsWorker::No) {} }; +class VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest + : public ChannelTest { + public: + typedef ChannelTest Base; + VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest() + : Base(true, kPcmuFrameWithExtensions, kRtcpReport, + NetworkIsWorker::Yes) {} +}; + +class VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest + : public ChannelTest { + public: + typedef ChannelTest Base; + VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest() + : Base(true, kPcmuFrameWithExtensions, kRtcpReport, + NetworkIsWorker::No) {} +}; + // override to add NULL parameter template <> cricket::VideoChannel* ChannelTest::CreateChannel( @@ -2091,6 +2320,48 @@ TEST_F(VoiceChannelSingleThreadTest, TestChangeStreamParamsInContent) { Base::TestChangeStreamParamsInContent(); } +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest, + TestChangeEncryptedHeaderExtensionsDtls) { + int flags = DTLS; + Base::TestChangeEncryptedHeaderExtensions(flags); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsScenario1) { + int flags = DTLS; + Base::TestChangeEncryptedHeaderExtensions(flags, DTLS_BEFORE_OFFER_ANSWER); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsScenario2) { + int flags = DTLS; + Base::TestChangeEncryptedHeaderExtensions(flags, DTLS_AFTER_CHANNEL2_READY); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsGcm) { + int flags = DTLS | GCM_CIPHER; + Base::TestChangeEncryptedHeaderExtensions(flags); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsGcmScenario1) { + int flags = DTLS | GCM_CIPHER; + Base::TestChangeEncryptedHeaderExtensions(flags, DTLS_BEFORE_OFFER_ANSWER); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsGcmScenario2) { + int flags = DTLS | GCM_CIPHER; + Base::TestChangeEncryptedHeaderExtensions(flags, DTLS_AFTER_CHANNEL2_READY); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest, + TestChangeEncryptedHeaderExtensionsSDES) { + int flags = 0; + Base::TestChangeEncryptedHeaderExtensions(flags); +} + TEST_F(VoiceChannelSingleThreadTest, TestPlayoutAndSendingStates) { Base::TestPlayoutAndSendingStates(); } @@ -2408,6 +2679,48 @@ TEST_F(VoiceChannelDoubleThreadTest, TestChangeStreamParamsInContent) { Base::TestChangeStreamParamsInContent(); } +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest, + TestChangeEncryptedHeaderExtensionsDtls) { + int flags = DTLS; + Base::TestChangeEncryptedHeaderExtensions(flags); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsScenario1) { + int flags = DTLS; + Base::TestChangeEncryptedHeaderExtensions(flags, DTLS_BEFORE_OFFER_ANSWER); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsScenario2) { + int flags = DTLS; + Base::TestChangeEncryptedHeaderExtensions(flags, DTLS_AFTER_CHANNEL2_READY); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsGcm) { + int flags = DTLS | GCM_CIPHER; + Base::TestChangeEncryptedHeaderExtensions(flags); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsGcmScenario1) { + int flags = DTLS | GCM_CIPHER; + Base::TestChangeEncryptedHeaderExtensions(flags, DTLS_BEFORE_OFFER_ANSWER); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest, + TestChangeEncryptedHeaderExtensionsDtlsGcmScenario2) { + int flags = DTLS | GCM_CIPHER; + Base::TestChangeEncryptedHeaderExtensions(flags, DTLS_AFTER_CHANNEL2_READY); +} + +TEST_F(VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest, + TestChangeEncryptedHeaderExtensionsSDES) { + int flags = 0; + Base::TestChangeEncryptedHeaderExtensions(flags); +} + TEST_F(VoiceChannelDoubleThreadTest, TestPlayoutAndSendingStates) { Base::TestPlayoutAndSendingStates(); } diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc index eb12a71bc2..f6190c2e8f 100644 --- a/webrtc/pc/mediasession.cc +++ b/webrtc/pc/mediasession.cc @@ -956,33 +956,70 @@ static void FindCodecsToOffer( static bool FindByUri(const RtpHeaderExtensions& extensions, const webrtc::RtpExtension& ext_to_match, webrtc::RtpExtension* found_extension) { + // We assume that all URIs are given in a canonical format. + const webrtc::RtpExtension* found = + webrtc::RtpExtension::FindHeaderExtensionByUri(extensions, + ext_to_match.uri); + if (!found) { + return false; + } + if (found_extension) { + *found_extension = *found; + } + return true; +} + +static bool FindByUriWithEncryptionPreference( + const RtpHeaderExtensions& extensions, + const webrtc::RtpExtension& ext_to_match, bool encryption_preference, + webrtc::RtpExtension* found_extension) { + const webrtc::RtpExtension* unencrypted_extension = nullptr; for (RtpHeaderExtensions::const_iterator it = extensions.begin(); it != extensions.end(); ++it) { // We assume that all URIs are given in a canonical format. if (it->uri == ext_to_match.uri) { - if (found_extension != NULL) { - *found_extension = *it; + if (!encryption_preference || it->encrypt) { + if (found_extension) { + *found_extension = *it; + } + return true; } - return true; + unencrypted_extension = &(*it); } } + if (unencrypted_extension) { + if (found_extension) { + *found_extension = *unencrypted_extension; + } + return true; + } return false; } -// Iterates through |offered_extensions|, adding each one to |all_extensions| -// and |used_ids|, and resolving ID conflicts. If an offered extension has the -// same URI as one in |all_extensions|, it will re-use the same ID and won't be -// treated as a conflict. +// Iterates through |offered_extensions|, adding each one to +// |regular_extensions| (or |encrypted_extensions| if encrypted) and |used_ids|, +// and resolving ID conflicts. +// If an offered extension has the same URI as one in |regular_extensions| or +// |encrypted_extensions|, it will re-use the same ID and won't be treated as +// a conflict. static void FindAndSetRtpHdrExtUsed(RtpHeaderExtensions* offered_extensions, - RtpHeaderExtensions* all_extensions, + RtpHeaderExtensions* regular_extensions, + RtpHeaderExtensions* encrypted_extensions, UsedRtpHeaderExtensionIds* used_ids) { for (auto& extension : *offered_extensions) { webrtc::RtpExtension existing; - if (FindByUri(*all_extensions, extension, &existing)) { + if ((extension.encrypt && + FindByUri(*encrypted_extensions, extension, &existing)) || + (!extension.encrypt && + FindByUri(*regular_extensions, extension, &existing))) { extension.id = existing.id; } else { used_ids->FindAndSetIdUsed(&extension); - all_extensions->push_back(extension); + if (extension.encrypt) { + encrypted_extensions->push_back(extension); + } else { + regular_extensions->push_back(extension); + } } } } @@ -1008,15 +1045,47 @@ static void FindRtpHdrExtsToOffer( } } +static void AddEncryptedVersionsOfHdrExts(RtpHeaderExtensions* extensions, + RtpHeaderExtensions* all_extensions, + UsedRtpHeaderExtensionIds* used_ids) { + RtpHeaderExtensions encrypted_extensions; + for (const webrtc::RtpExtension& extension : *extensions) { + webrtc::RtpExtension existing; + // Don't add encrypted extensions again that were already included in a + // previous offer or regular extensions that are also included as encrypted + // extensions. + if (extension.encrypt || + !webrtc::RtpExtension::IsEncryptionSupported(extension.uri) || + (FindByUriWithEncryptionPreference(*extensions, extension, true, + &existing) && existing.encrypt)) { + continue; + } + + if (FindByUri(*all_extensions, extension, &existing)) { + encrypted_extensions.push_back(existing); + } else { + webrtc::RtpExtension encrypted(extension); + encrypted.encrypt = true; + used_ids->FindAndSetIdUsed(&encrypted); + all_extensions->push_back(encrypted); + encrypted_extensions.push_back(encrypted); + } + } + extensions->insert(extensions->end(), encrypted_extensions.begin(), + encrypted_extensions.end()); +} + static void NegotiateRtpHeaderExtensions( const RtpHeaderExtensions& local_extensions, const RtpHeaderExtensions& offered_extensions, + bool enable_encrypted_rtp_header_extensions, RtpHeaderExtensions* negotiated_extenstions) { RtpHeaderExtensions::const_iterator ours; for (ours = local_extensions.begin(); ours != local_extensions.end(); ++ours) { webrtc::RtpExtension theirs; - if (FindByUri(offered_extensions, *ours, &theirs)) { + if (FindByUriWithEncryptionPreference(offered_extensions, *ours, + enable_encrypted_rtp_header_extensions, &theirs)) { // We respond with their RTP header extension id. negotiated_extenstions->push_back(theirs); } @@ -1051,6 +1120,7 @@ static bool CreateMediaContentAnswer( const SecurePolicy& sdes_policy, const CryptoParamsVec* current_cryptos, const RtpHeaderExtensions& local_rtp_extenstions, + bool enable_encrypted_rtp_header_extensions, StreamParamsVec* current_streams, bool add_legacy_stream, bool bundle_enabled, @@ -1062,6 +1132,7 @@ static bool CreateMediaContentAnswer( RtpHeaderExtensions negotiated_rtp_extensions; NegotiateRtpHeaderExtensions(local_rtp_extenstions, offer->rtp_header_extensions(), + enable_encrypted_rtp_header_extensions, &negotiated_rtp_extensions); answer->set_rtp_header_extensions(negotiated_rtp_extensions); @@ -1580,7 +1651,8 @@ void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer( // All header extensions allocated from the same range to avoid potential // issues when using BUNDLE. UsedRtpHeaderExtensionIds used_ids; - RtpHeaderExtensions all_extensions; + RtpHeaderExtensions all_regular_extensions; + RtpHeaderExtensions all_encrypted_extensions; audio_extensions->clear(); video_extensions->clear(); @@ -1593,22 +1665,32 @@ void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer( GetFirstAudioContentDescription(current_description); if (audio) { *audio_extensions = audio->rtp_header_extensions(); - FindAndSetRtpHdrExtUsed(audio_extensions, &all_extensions, &used_ids); + FindAndSetRtpHdrExtUsed(audio_extensions, &all_regular_extensions, + &all_encrypted_extensions, &used_ids); } const VideoContentDescription* video = GetFirstVideoContentDescription(current_description); if (video) { *video_extensions = video->rtp_header_extensions(); - FindAndSetRtpHdrExtUsed(video_extensions, &all_extensions, &used_ids); + FindAndSetRtpHdrExtUsed(video_extensions, &all_regular_extensions, + &all_encrypted_extensions, &used_ids); } } // Add our default RTP header extensions that are not in // |current_description|. FindRtpHdrExtsToOffer(audio_rtp_header_extensions(), audio_extensions, - &all_extensions, &used_ids); + &all_regular_extensions, &used_ids); FindRtpHdrExtsToOffer(video_rtp_header_extensions(), video_extensions, - &all_extensions, &used_ids); + &all_regular_extensions, &used_ids); + // TODO(jbauch): Support adding encrypted header extensions to existing + // sessions. + if (enable_encrypted_rtp_header_extensions_ && !current_description) { + AddEncryptedVersionsOfHdrExts(audio_extensions, &all_encrypted_extensions, + &used_ids); + AddEncryptedVersionsOfHdrExts(video_extensions, &all_encrypted_extensions, + &used_ids); + } } bool MediaSessionDescriptionFactory::AddTransportOffer( @@ -1886,6 +1968,7 @@ bool MediaSessionDescriptionFactory::AddAudioContentForAnswer( sdes_policy, GetCryptos(GetFirstAudioContentDescription(current_description)), audio_rtp_extensions_, + enable_encrypted_rtp_header_extensions_, current_streams, add_legacy_, bundle_enabled, @@ -1942,6 +2025,7 @@ bool MediaSessionDescriptionFactory::AddVideoContentForAnswer( sdes_policy, GetCryptos(GetFirstVideoContentDescription(current_description)), video_rtp_extensions_, + enable_encrypted_rtp_header_extensions_, current_streams, add_legacy_, bundle_enabled, @@ -2003,6 +2087,7 @@ bool MediaSessionDescriptionFactory::AddDataContentForAnswer( sdes_policy, GetCryptos(GetFirstDataContentDescription(current_description)), RtpHeaderExtensions(), + enable_encrypted_rtp_header_extensions_, current_streams, add_legacy_, bundle_enabled, diff --git a/webrtc/pc/mediasession.h b/webrtc/pc/mediasession.h index edf2dd0f40..596bd18ad1 100644 --- a/webrtc/pc/mediasession.h +++ b/webrtc/pc/mediasession.h @@ -475,6 +475,10 @@ class MediaSessionDescriptionFactory { // applications. |add_legacy_| is true per default. void set_add_legacy_streams(bool add_legacy) { add_legacy_ = add_legacy; } + void set_enable_encrypted_rtp_header_extensions(bool enable) { + enable_encrypted_rtp_header_extensions_ = enable; + } + SessionDescription* CreateOffer( const MediaSessionOptions& options, const SessionDescription* current_description) const; @@ -574,6 +578,7 @@ class MediaSessionDescriptionFactory { DataCodecs data_codecs_; SecurePolicy secure_; bool add_legacy_; + bool enable_encrypted_rtp_header_extensions_ = false; std::string lang_; const TransportDescriptionFactory* transport_desc_factory_; }; diff --git a/webrtc/pc/mediasession_unittest.cc b/webrtc/pc/mediasession_unittest.cc index fc2da881f6..670ace2057 100644 --- a/webrtc/pc/mediasession_unittest.cc +++ b/webrtc/pc/mediasession_unittest.cc @@ -112,6 +112,12 @@ static const RtpExtension kAudioRtpExtension1[] = { RtpExtension("http://google.com/testing/audio_something", 10), }; +static const RtpExtension kAudioRtpExtensionEncrypted1[] = { + RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8), + RtpExtension("http://google.com/testing/audio_something", 10), + RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 12, true), +}; + static const RtpExtension kAudioRtpExtension2[] = { RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 2), RtpExtension("http://google.com/testing/audio_something_else", 8), @@ -123,15 +129,37 @@ static const RtpExtension kAudioRtpExtension3[] = { RtpExtension("http://google.com/testing/both_audio_and_video", 3), }; +static const RtpExtension kAudioRtpExtension3ForEncryption[] = { + RtpExtension("http://google.com/testing/audio_something", 2), + // Use RTP extension that supports encryption. + RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 3), +}; + +static const RtpExtension kAudioRtpExtension3ForEncryptionOffer[] = { + RtpExtension("http://google.com/testing/audio_something", 2), + RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 3), + RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14, true), +}; + static const RtpExtension kAudioRtpExtensionAnswer[] = { RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8), }; +static const RtpExtension kAudioRtpExtensionEncryptedAnswer[] = { + RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 12, true), +}; + static const RtpExtension kVideoRtpExtension1[] = { RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14), RtpExtension("http://google.com/testing/video_something", 13), }; +static const RtpExtension kVideoRtpExtensionEncrypted1[] = { + RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14), + RtpExtension("http://google.com/testing/video_something", 13), + RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 11, true), +}; + static const RtpExtension kVideoRtpExtension2[] = { RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 2), RtpExtension("http://google.com/testing/video_something_else", 14), @@ -143,10 +171,20 @@ static const RtpExtension kVideoRtpExtension3[] = { RtpExtension("http://google.com/testing/both_audio_and_video", 5), }; +static const RtpExtension kVideoRtpExtension3ForEncryption[] = { + RtpExtension("http://google.com/testing/video_something", 4), + // Use RTP extension that supports encryption. + RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 5), +}; + static const RtpExtension kVideoRtpExtensionAnswer[] = { RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14), }; +static const RtpExtension kVideoRtpExtensionEncryptedAnswer[] = { + RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 11, true), +}; + static const uint32_t kSimulcastParamsSsrc[] = {10, 11, 20, 21, 30, 31}; static const uint32_t kSimSsrc[] = {10, 20, 30}; static const uint32_t kFec1Ssrc[] = {10, 11}; @@ -1188,6 +1226,112 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithRtpExtensions) { answer.get())->rtp_header_extensions()); } +TEST_F(MediaSessionDescriptionFactoryTest, + TestOfferAnswerWithEncryptedRtpExtensionsBoth) { + MediaSessionOptions opts; + opts.recv_video = true; + + f1_.set_enable_encrypted_rtp_header_extensions(true); + f2_.set_enable_encrypted_rtp_header_extensions(true); + + f1_.set_audio_rtp_header_extensions( + MAKE_VECTOR(kAudioRtpExtension1)); + f1_.set_video_rtp_header_extensions( + MAKE_VECTOR(kVideoRtpExtension1)); + f2_.set_audio_rtp_header_extensions( + MAKE_VECTOR(kAudioRtpExtension2)); + f2_.set_video_rtp_header_extensions( + MAKE_VECTOR(kVideoRtpExtension2)); + + std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), opts, NULL)); + + EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtensionEncrypted1), + GetFirstAudioContentDescription( + offer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtensionEncrypted1), + GetFirstVideoContentDescription( + offer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtensionEncryptedAnswer), + GetFirstAudioContentDescription( + answer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtensionEncryptedAnswer), + GetFirstVideoContentDescription( + answer.get())->rtp_header_extensions()); +} + +TEST_F(MediaSessionDescriptionFactoryTest, + TestOfferAnswerWithEncryptedRtpExtensionsOffer) { + MediaSessionOptions opts; + opts.recv_video = true; + + f1_.set_enable_encrypted_rtp_header_extensions(true); + + f1_.set_audio_rtp_header_extensions( + MAKE_VECTOR(kAudioRtpExtension1)); + f1_.set_video_rtp_header_extensions( + MAKE_VECTOR(kVideoRtpExtension1)); + f2_.set_audio_rtp_header_extensions( + MAKE_VECTOR(kAudioRtpExtension2)); + f2_.set_video_rtp_header_extensions( + MAKE_VECTOR(kVideoRtpExtension2)); + + std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), opts, NULL)); + + EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtensionEncrypted1), + GetFirstAudioContentDescription( + offer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtensionEncrypted1), + GetFirstVideoContentDescription( + offer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtensionAnswer), + GetFirstAudioContentDescription( + answer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtensionAnswer), + GetFirstVideoContentDescription( + answer.get())->rtp_header_extensions()); +} + +TEST_F(MediaSessionDescriptionFactoryTest, + TestOfferAnswerWithEncryptedRtpExtensionsAnswer) { + MediaSessionOptions opts; + opts.recv_video = true; + + f2_.set_enable_encrypted_rtp_header_extensions(true); + + f1_.set_audio_rtp_header_extensions( + MAKE_VECTOR(kAudioRtpExtension1)); + f1_.set_video_rtp_header_extensions( + MAKE_VECTOR(kVideoRtpExtension1)); + f2_.set_audio_rtp_header_extensions( + MAKE_VECTOR(kAudioRtpExtension2)); + f2_.set_video_rtp_header_extensions( + MAKE_VECTOR(kVideoRtpExtension2)); + + std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), opts, NULL)); + + EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtension1), + GetFirstAudioContentDescription( + offer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtension1), + GetFirstVideoContentDescription( + offer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtensionAnswer), + GetFirstAudioContentDescription( + answer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtensionAnswer), + GetFirstVideoContentDescription( + answer.get())->rtp_header_extensions()); +} + // Create an audio, video, data answer without legacy StreamParams. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerWithoutLegacyStreams) { @@ -2262,6 +2406,49 @@ TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReused) { updated_offer.get())->rtp_header_extensions()); } +// Same as "RtpExtensionIdReused" above for encrypted RTP extensions. +TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReusedEncrypted) { + MediaSessionOptions opts; + opts.recv_audio = true; + opts.recv_video = true; + + f1_.set_enable_encrypted_rtp_header_extensions(true); + f2_.set_enable_encrypted_rtp_header_extensions(true); + + f1_.set_audio_rtp_header_extensions( + MAKE_VECTOR(kAudioRtpExtension3ForEncryption)); + f1_.set_video_rtp_header_extensions( + MAKE_VECTOR(kVideoRtpExtension3ForEncryption)); + + std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); + + // The extensions that are shared between audio and video should use the same + // id. + const RtpExtension kExpectedVideoRtpExtension[] = { + kVideoRtpExtension3ForEncryption[0], + kAudioRtpExtension3ForEncryptionOffer[1], + kAudioRtpExtension3ForEncryptionOffer[2], + }; + + EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtension3ForEncryptionOffer), + GetFirstAudioContentDescription( + offer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kExpectedVideoRtpExtension), + GetFirstVideoContentDescription( + offer.get())->rtp_header_extensions()); + + // Nothing should change when creating a new offer + std::unique_ptr updated_offer( + f1_.CreateOffer(opts, offer.get())); + + EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtension3ForEncryptionOffer), + GetFirstAudioContentDescription( + updated_offer.get())->rtp_header_extensions()); + EXPECT_EQ(MAKE_VECTOR(kExpectedVideoRtpExtension), + GetFirstVideoContentDescription( + updated_offer.get())->rtp_header_extensions()); +} + TEST(MediaSessionDescription, CopySessionDescription) { SessionDescription source; cricket::ContentGroup group(cricket::CN_AUDIO); diff --git a/webrtc/pc/srtpfilter.cc b/webrtc/pc/srtpfilter.cc index fa310afdf6..62606e0025 100644 --- a/webrtc/pc/srtpfilter.cc +++ b/webrtc/pc/srtpfilter.cc @@ -77,11 +77,17 @@ bool SrtpFilter::SetRtpParams(int send_cs, return false; } CreateSrtpSessions(); - if (!send_session_->SetSend(send_cs, send_key, send_key_len)) + send_session_->SetEncryptedHeaderExtensionIds( + send_encrypted_header_extension_ids_); + if (!send_session_->SetSend(send_cs, send_key, send_key_len)) { return false; + } - if (!recv_session_->SetRecv(recv_cs, recv_key, recv_key_len)) + recv_session_->SetEncryptedHeaderExtensionIds( + recv_encrypted_header_extension_ids_); + if (!recv_session_->SetRecv(recv_cs, recv_key, recv_key_len)) { return false; + } state_ = ST_ACTIVE; @@ -91,6 +97,34 @@ bool SrtpFilter::SetRtpParams(int send_cs, return true; } +bool SrtpFilter::UpdateRtpParams(int send_cs, + const uint8_t* send_key, + int send_key_len, + int recv_cs, + const uint8_t* recv_key, + int recv_key_len) { + if (!IsActive()) { + LOG(LS_ERROR) << "Tried to update SRTP Params when filter is not active"; + return false; + } + send_session_->SetEncryptedHeaderExtensionIds( + send_encrypted_header_extension_ids_); + if (!send_session_->UpdateSend(send_cs, send_key, send_key_len)) { + return false; + } + + recv_session_->SetEncryptedHeaderExtensionIds( + recv_encrypted_header_extension_ids_); + if (!recv_session_->UpdateRecv(recv_cs, recv_key, recv_key_len)) { + return false; + } + + LOG(LS_INFO) << "SRTP updated with negotiated parameters:" + << " send cipher_suite " << send_cs + << " recv cipher_suite " << recv_cs; + return true; +} + // This function is provided separately because DTLS-SRTP behaves // differently in RTP/RTCP mux and non-mux modes. // @@ -113,12 +147,14 @@ bool SrtpFilter::SetRtcpParams(int send_cs, } send_rtcp_session_.reset(new SrtpSession()); - if (!send_rtcp_session_->SetRecv(send_cs, send_key, send_key_len)) + if (!send_rtcp_session_->SetRecv(send_cs, send_key, send_key_len)) { return false; + } recv_rtcp_session_.reset(new SrtpSession()); - if (!recv_rtcp_session_->SetRecv(recv_cs, recv_key, recv_key_len)) + if (!recv_rtcp_session_->SetRecv(recv_cs, recv_key, recv_key_len)) { return false; + } LOG(LS_INFO) << "SRTCP activated with negotiated parameters:" << " send cipher_suite " << send_cs @@ -224,6 +260,15 @@ bool SrtpFilter::IsExternalAuthActive() const { return send_session_->IsExternalAuthActive(); } +void SrtpFilter::SetEncryptedHeaderExtensionIds(ContentSource source, + const std::vector& extension_ids) { + if (source == CS_LOCAL) { + recv_encrypted_header_extension_ids_ = extension_ids; + } else { + send_encrypted_header_extension_ids_ = extension_ids; + } +} + bool SrtpFilter::ExpectOffer(ContentSource source) { return ((state_ == ST_INIT) || (state_ == ST_ACTIVE) || @@ -384,6 +429,10 @@ bool SrtpFilter::ApplyParams(const CryptoParams& send_params, recv_key.size())); if (ret) { CreateSrtpSessions(); + send_session_->SetEncryptedHeaderExtensionIds( + send_encrypted_header_extension_ids_); + recv_session_->SetEncryptedHeaderExtensionIds( + recv_encrypted_header_extension_ids_); ret = (send_session_->SetSend( rtc::SrtpCryptoSuiteFromName(send_params.cipher_suite), send_key.data(), send_key.size()) && @@ -456,10 +505,18 @@ bool SrtpSession::SetSend(int cs, const uint8_t* key, size_t len) { return SetKey(ssrc_any_outbound, cs, key, len); } +bool SrtpSession::UpdateSend(int cs, const uint8_t* key, size_t len) { + return UpdateKey(ssrc_any_outbound, cs, key, len); +} + bool SrtpSession::SetRecv(int cs, const uint8_t* key, size_t len) { return SetKey(ssrc_any_inbound, cs, key, len); } +bool SrtpSession::UpdateRecv(int cs, const uint8_t* key, size_t len) { + return UpdateKey(ssrc_any_inbound, cs, key, len); +} + bool SrtpSession::ProtectRtp(void* p, int in_len, int max_len, int* out_len) { RTC_DCHECK(thread_checker_.CalledOnValidThread()); if (!session_) { @@ -626,17 +683,9 @@ bool SrtpSession::GetSendStreamPacketIndex(void* p, return true; } -bool SrtpSession::SetKey(int type, int cs, const uint8_t* key, size_t len) { - RTC_DCHECK(thread_checker_.CalledOnValidThread()); - if (session_) { - LOG(LS_ERROR) << "Failed to create SRTP session: " - << "SRTP session already created"; - return false; - } - if (!Init()) { - return false; - } +bool SrtpSession::DoSetKey(int type, int cs, const uint8_t* key, size_t len) { + RTC_DCHECK(thread_checker_.CalledOnValidThread()); srtp_policy_t policy; memset(&policy, 0, sizeof(policy)); @@ -654,8 +703,8 @@ bool SrtpSession::SetKey(int type, int cs, const uint8_t* key, size_t len) { srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtp); srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtcp); } else { - LOG(LS_WARNING) << "Failed to create SRTP session: unsupported" - << " cipher_suite " << cs; + LOG(LS_WARNING) << "Failed to " << (session_ ? "update" : "create") + << " SRTP session: unsupported cipher_suite " << cs; return false; } @@ -664,14 +713,16 @@ bool SrtpSession::SetKey(int type, int cs, const uint8_t* key, size_t len) { if (!rtc::GetSrtpKeyAndSaltLengths(cs, &expected_key_len, &expected_salt_len)) { // This should never happen. - LOG(LS_WARNING) << "Failed to create SRTP session: unsupported" - << " cipher_suite without length information" << cs; + LOG(LS_WARNING) << "Failed to " << (session_ ? "update" : "create") + << " SRTP session: unsupported cipher_suite without length information" + << cs; return false; } if (!key || len != static_cast(expected_key_len + expected_salt_len)) { - LOG(LS_WARNING) << "Failed to create SRTP session: invalid key"; + LOG(LS_WARNING) << "Failed to " << (session_ ? "update" : "create") + << " SRTP session: invalid key"; return false; } @@ -691,22 +742,66 @@ bool SrtpSession::SetKey(int type, int cs, const uint8_t* key, size_t len) { !rtc::IsGcmCryptoSuite(cs)) { policy.rtp.auth_type = EXTERNAL_HMAC_SHA1; } + if (!encrypted_header_extension_ids_.empty()) { + policy.enc_xtn_hdr = const_cast(&encrypted_header_extension_ids_[0]); + policy.enc_xtn_hdr_count = + static_cast(encrypted_header_extension_ids_.size()); + } policy.next = nullptr; - int err = srtp_create(&session_, &policy); - if (err != srtp_err_status_ok) { - session_ = nullptr; - LOG(LS_ERROR) << "Failed to create SRTP session, err=" << err; - return false; + if (!session_) { + int err = srtp_create(&session_, &policy); + if (err != srtp_err_status_ok) { + session_ = nullptr; + LOG(LS_ERROR) << "Failed to create SRTP session, err=" << err; + return false; + } + srtp_set_user_data(session_, this); + } else { + int err = srtp_update(session_, &policy); + if (err != srtp_err_status_ok) { + LOG(LS_ERROR) << "Failed to update SRTP session, err=" << err; + return false; + } } - srtp_set_user_data(session_, this); rtp_auth_tag_len_ = policy.rtp.auth_tag_len; rtcp_auth_tag_len_ = policy.rtcp.auth_tag_len; external_auth_active_ = (policy.rtp.auth_type == EXTERNAL_HMAC_SHA1); return true; } +bool SrtpSession::SetKey(int type, int cs, const uint8_t* key, size_t len) { + RTC_DCHECK(thread_checker_.CalledOnValidThread()); + if (session_) { + LOG(LS_ERROR) << "Failed to create SRTP session: " + << "SRTP session already created"; + return false; + } + + if (!Init()) { + return false; + } + + return DoSetKey(type, cs, key, len); +} + +bool SrtpSession::UpdateKey(int type, int cs, const uint8_t* key, size_t len) { + RTC_DCHECK(thread_checker_.CalledOnValidThread()); + if (!session_) { + LOG(LS_ERROR) << "Failed to update non-existing SRTP session"; + return false; + } + + return DoSetKey(type, cs, key, len); +} + +void SrtpSession::SetEncryptedHeaderExtensionIds( + const std::vector& encrypted_header_extension_ids) { + RTC_DCHECK(thread_checker_.CalledOnValidThread()); + encrypted_header_extension_ids_ = encrypted_header_extension_ids; +} + bool SrtpSession::Init() { rtc::GlobalLockScope ls(&lock_); diff --git a/webrtc/pc/srtpfilter.h b/webrtc/pc/srtpfilter.h index 18a2f6774f..6051e52660 100644 --- a/webrtc/pc/srtpfilter.h +++ b/webrtc/pc/srtpfilter.h @@ -76,6 +76,10 @@ class SrtpFilter { bool SetAnswer(const std::vector& answer_params, ContentSource source); + // Set the header extension ids that should be encrypted for the given source. + void SetEncryptedHeaderExtensionIds(ContentSource source, + const std::vector& extension_ids); + // Just set up both sets of keys directly. // Used with DTLS-SRTP. bool SetRtpParams(int send_cs, @@ -84,6 +88,12 @@ class SrtpFilter { int recv_cs, const uint8_t* recv_key, int recv_key_len); + bool UpdateRtpParams(int send_cs, + const uint8_t* send_key, + int send_key_len, + int recv_cs, + const uint8_t* recv_key, + int recv_key_len); bool SetRtcpParams(int send_cs, const uint8_t* send_key, int send_key_len, @@ -177,6 +187,8 @@ class SrtpFilter { std::unique_ptr recv_rtcp_session_; CryptoParams applied_send_params_; CryptoParams applied_recv_params_; + std::vector send_encrypted_header_extension_ids_; + std::vector recv_encrypted_header_extension_ids_; }; // Class that wraps a libSRTP session. @@ -188,9 +200,15 @@ class SrtpSession { // Configures the session for sending data using the specified // cipher-suite and key. Receiving must be done by a separate session. bool SetSend(int cs, const uint8_t* key, size_t len); + bool UpdateSend(int cs, const uint8_t* key, size_t len); + // Configures the session for receiving data using the specified // cipher-suite and key. Sending must be done by a separate session. bool SetRecv(int cs, const uint8_t* key, size_t len); + bool UpdateRecv(int cs, const uint8_t* key, size_t len); + + void SetEncryptedHeaderExtensionIds( + const std::vector& encrypted_header_extension_ids); // Encrypts/signs an individual RTP/RTCP packet, in-place. // If an HMAC is used, this will increase the packet size. @@ -229,7 +247,11 @@ class SrtpSession { static void Terminate(); private: + bool DoSetKey(int type, int cs, const uint8_t* key, size_t len); bool SetKey(int type, int cs, const uint8_t* key, size_t len); + bool UpdateKey(int type, int cs, const uint8_t* key, size_t len); + bool SetEncryptedHeaderExtensionIds(int type, + const std::vector& encrypted_header_extension_ids); // Returns send stream current packet index from srtp db. bool GetSendStreamPacketIndex(void* data, int in_len, int64_t* index); @@ -246,6 +268,7 @@ class SrtpSession { int last_send_seq_num_ = -1; bool external_auth_active_ = false; bool external_auth_enabled_ = false; + std::vector encrypted_header_extension_ids_; RTC_DISALLOW_COPY_AND_ASSIGN(SrtpSession); }; diff --git a/webrtc/pc/srtpfilter_unittest.cc b/webrtc/pc/srtpfilter_unittest.cc index ec79b90aa1..4d1540e2fd 100644 --- a/webrtc/pc/srtpfilter_unittest.cc +++ b/webrtc/pc/srtpfilter_unittest.cc @@ -8,6 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include + #include "webrtc/pc/srtpfilter.h" #include "third_party/libsrtp/include/srtp.h" @@ -193,6 +195,53 @@ class SrtpFilterTest : public testing::Test { EXPECT_EQ(rtcp_len, out_len); EXPECT_EQ(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len)); } + void TestProtectUnprotectHeaderEncryption(const std::string& cs1, + const std::string& cs2, + const std::vector& encrypted_header_ids) { + rtc::Buffer rtp_buffer(sizeof(kPcmuFrameWithExtensions) + + rtp_auth_tag_len(cs1)); + char* rtp_packet = rtp_buffer.data(); + size_t rtp_packet_size = rtp_buffer.size(); + char original_rtp_packet[sizeof(kPcmuFrameWithExtensions)]; + size_t original_rtp_packet_size = sizeof(original_rtp_packet); + int rtp_len = sizeof(kPcmuFrameWithExtensions), out_len; + memcpy(rtp_packet, kPcmuFrameWithExtensions, rtp_len); + // In order to be able to run this test function multiple times we can not + // use the same sequence number twice. Increase the sequence number by one. + rtc::SetBE16(reinterpret_cast(rtp_packet) + 2, + ++sequence_number_); + memcpy(original_rtp_packet, rtp_packet, rtp_len); + + EXPECT_TRUE(f1_.ProtectRtp(rtp_packet, rtp_len, + static_cast(rtp_buffer.size()), + &out_len)); + EXPECT_EQ(out_len, rtp_len + rtp_auth_tag_len(cs1)); + EXPECT_NE(0, memcmp(rtp_packet, original_rtp_packet, rtp_len)); + CompareHeaderExtensions(rtp_packet, rtp_packet_size, + original_rtp_packet, original_rtp_packet_size, + encrypted_header_ids, false); + EXPECT_TRUE(f2_.UnprotectRtp(rtp_packet, out_len, &out_len)); + EXPECT_EQ(rtp_len, out_len); + EXPECT_EQ(0, memcmp(rtp_packet, original_rtp_packet, rtp_len)); + CompareHeaderExtensions(rtp_packet, rtp_packet_size, + original_rtp_packet, original_rtp_packet_size, + encrypted_header_ids, true); + + EXPECT_TRUE(f2_.ProtectRtp(rtp_packet, rtp_len, + static_cast(rtp_buffer.size()), + &out_len)); + EXPECT_EQ(out_len, rtp_len + rtp_auth_tag_len(cs2)); + EXPECT_NE(0, memcmp(rtp_packet, original_rtp_packet, rtp_len)); + CompareHeaderExtensions(rtp_packet, rtp_packet_size, + original_rtp_packet, original_rtp_packet_size, + encrypted_header_ids, false); + EXPECT_TRUE(f1_.UnprotectRtp(rtp_packet, out_len, &out_len)); + EXPECT_EQ(rtp_len, out_len); + EXPECT_EQ(0, memcmp(rtp_packet, original_rtp_packet, rtp_len)); + CompareHeaderExtensions(rtp_packet, rtp_packet_size, + original_rtp_packet, original_rtp_packet_size, + encrypted_header_ids, true); + } void TestProtectSetParamsDirect(bool enable_external_auth, int cs, const uint8_t* key1, int key1_len, const uint8_t* key2, int key2_len, const std::string& cs_name) { @@ -217,6 +266,27 @@ class SrtpFilterTest : public testing::Test { } TestProtectUnprotect(cs_name, cs_name); } + void TestProtectSetParamsDirectHeaderEncryption(int cs, + const uint8_t* key1, int key1_len, const uint8_t* key2, int key2_len, + const std::string& cs_name) { + std::vector encrypted_headers; + encrypted_headers.push_back(1); + // Don't encrypt header ids 2 and 3. + encrypted_headers.push_back(4); + EXPECT_EQ(key1_len, key2_len); + EXPECT_EQ(cs_name, rtc::SrtpCryptoSuiteToName(cs)); + f1_.SetEncryptedHeaderExtensionIds(CS_LOCAL, encrypted_headers); + f1_.SetEncryptedHeaderExtensionIds(CS_REMOTE, encrypted_headers); + f2_.SetEncryptedHeaderExtensionIds(CS_LOCAL, encrypted_headers); + f2_.SetEncryptedHeaderExtensionIds(CS_REMOTE, encrypted_headers); + EXPECT_TRUE(f1_.SetRtpParams(cs, key1, key1_len, cs, key2, key2_len)); + EXPECT_TRUE(f2_.SetRtpParams(cs, key2, key2_len, cs, key1, key1_len)); + EXPECT_TRUE(f1_.IsActive()); + EXPECT_TRUE(f2_.IsActive()); + EXPECT_FALSE(f1_.IsExternalAuthActive()); + EXPECT_FALSE(f2_.IsExternalAuthActive()); + TestProtectUnprotectHeaderEncryption(cs_name, cs_name, encrypted_headers); + } cricket::SrtpFilter f1_; cricket::SrtpFilter f2_; int sequence_number_; @@ -619,6 +689,13 @@ TEST_P(SrtpFilterProtectSetParamsDirectTest, Test_AES_CM_128_HMAC_SHA1_80) { CS_AES_CM_128_HMAC_SHA1_80); } +TEST_F(SrtpFilterTest, + TestProtectSetParamsDirectHeaderEncryption_AES_CM_128_HMAC_SHA1_80) { + TestProtectSetParamsDirectHeaderEncryption(rtc::SRTP_AES128_CM_SHA1_80, + kTestKey1, kTestKeyLen, kTestKey2, kTestKeyLen, + CS_AES_CM_128_HMAC_SHA1_80); +} + // Test directly setting the params with AES_CM_128_HMAC_SHA1_32. TEST_P(SrtpFilterProtectSetParamsDirectTest, Test_AES_CM_128_HMAC_SHA1_32) { bool enable_external_auth = GetParam(); @@ -627,6 +704,13 @@ TEST_P(SrtpFilterProtectSetParamsDirectTest, Test_AES_CM_128_HMAC_SHA1_32) { CS_AES_CM_128_HMAC_SHA1_32); } +TEST_F(SrtpFilterTest, + TestProtectSetParamsDirectHeaderEncryption_AES_CM_128_HMAC_SHA1_32) { + TestProtectSetParamsDirectHeaderEncryption(rtc::SRTP_AES128_CM_SHA1_32, + kTestKey1, kTestKeyLen, kTestKey2, kTestKeyLen, + CS_AES_CM_128_HMAC_SHA1_32); +} + // Test directly setting the params with SRTP_AEAD_AES_128_GCM. TEST_P(SrtpFilterProtectSetParamsDirectTest, Test_SRTP_AEAD_AES_128_GCM) { bool enable_external_auth = GetParam(); @@ -635,6 +719,13 @@ TEST_P(SrtpFilterProtectSetParamsDirectTest, Test_SRTP_AEAD_AES_128_GCM) { CS_AEAD_AES_128_GCM); } +TEST_F(SrtpFilterTest, + TestProtectSetParamsDirectHeaderEncryption_SRTP_AEAD_AES_128_GCM) { + TestProtectSetParamsDirectHeaderEncryption(rtc::SRTP_AEAD_AES_128_GCM, + kTestKeyGcm128_1, kTestKeyGcm128Len, kTestKeyGcm128_2, kTestKeyGcm128Len, + CS_AEAD_AES_128_GCM); +} + // Test directly setting the params with SRTP_AEAD_AES_256_GCM. TEST_P(SrtpFilterProtectSetParamsDirectTest, Test_SRTP_AEAD_AES_256_GCM) { bool enable_external_auth = GetParam(); @@ -643,6 +734,13 @@ TEST_P(SrtpFilterProtectSetParamsDirectTest, Test_SRTP_AEAD_AES_256_GCM) { CS_AEAD_AES_256_GCM); } +TEST_F(SrtpFilterTest, + TestProtectSetParamsDirectHeaderEncryption_SRTP_AEAD_AES_256_GCM) { + TestProtectSetParamsDirectHeaderEncryption(rtc::SRTP_AEAD_AES_256_GCM, + kTestKeyGcm256_1, kTestKeyGcm256Len, kTestKeyGcm256_2, kTestKeyGcm256Len, + CS_AEAD_AES_256_GCM); +} + // Run all tests both with and without external auth enabled. INSTANTIATE_TEST_CASE_P(ExternalAuth, SrtpFilterProtectSetParamsDirectTest, diff --git a/webrtc/pc/webrtcsdp.cc b/webrtc/pc/webrtcsdp.cc index 3953773d83..49cd0444bf 100644 --- a/webrtc/pc/webrtcsdp.cc +++ b/webrtc/pc/webrtcsdp.cc @@ -1176,7 +1176,25 @@ bool ParseExtmap(const std::string& line, return false; } - *extmap = RtpExtension(uri, value); + bool encrypted = false; + if (uri == RtpExtension::kEncryptHeaderExtensionsUri) { + // RFC 6904 + // a=extmap:] urn:ietf:params:rtp-hdrext:encrypt \ + // + const size_t expected_min_fields_encrypted = expected_min_fields + 1; + if (fields.size() < expected_min_fields_encrypted) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields_encrypted, + error); + } + + encrypted = true; + uri = fields[2]; + if (uri == RtpExtension::kEncryptHeaderExtensionsUri) { + return ParseFailed(line, "Recursive encrypted header.", error); + } + } + + *extmap = RtpExtension(uri, value, encrypted); return true; } @@ -1433,9 +1451,13 @@ void BuildRtpContentAttributes(const MediaContentDescription* media_desc, // The definitions MUST be either all session level or all media level. This // implementation uses all media level. for (size_t i = 0; i < media_desc->rtp_header_extensions().size(); ++i) { + const RtpExtension& extension = media_desc->rtp_header_extensions()[i]; InitAttrLine(kAttributeExtmap, &os); - os << kSdpDelimiterColon << media_desc->rtp_header_extensions()[i].id - << kSdpDelimiterSpace << media_desc->rtp_header_extensions()[i].uri; + os << kSdpDelimiterColon << extension.id; + if (extension.encrypt) { + os << kSdpDelimiterSpace << RtpExtension::kEncryptHeaderExtensionsUri; + } + os << kSdpDelimiterSpace << extension.uri; AddLine(os.str(), message); } diff --git a/webrtc/pc/webrtcsdp_unittest.cc b/webrtc/pc/webrtcsdp_unittest.cc index 57a7bdb0c0..af6f2c91e6 100644 --- a/webrtc/pc/webrtcsdp_unittest.cc +++ b/webrtc/pc/webrtcsdp_unittest.cc @@ -94,6 +94,9 @@ static const char kExtmap[] = "a=extmap:1 http://example.com/082005/ext.htm#ttime\r\n"; static const char kExtmapWithDirectionAndAttribute[] = "a=extmap:1/sendrecv http://example.com/082005/ext.htm#ttime a1 a2\r\n"; +static const char kExtmapWithDirectionAndAttributeEncrypted[] = + "a=extmap:1/sendrecv urn:ietf:params:rtp-hdrext:encrypt " + "http://example.com/082005/ext.htm#ttime a1 a2\r\n"; static const uint8_t kIdentityDigest[] = { 0x4A, 0xAD, 0xB9, 0xB1, 0x3F, 0x82, 0x18, 0x3B, 0x54, 0x02, @@ -1213,6 +1216,7 @@ class WebRtcSdpTest : public testing::Test { const RtpExtension ext2 = cd2->rtp_header_extensions().at(i); EXPECT_EQ(ext1.uri, ext2.uri); EXPECT_EQ(ext1.id, ext2.id); + EXPECT_EQ(ext1.encrypt, ext2.encrypt); } } @@ -1434,13 +1438,15 @@ class WebRtcSdpTest : public testing::Test { cricket::CONNECTIONROLE_NONE, &fingerprint)))); } - void AddExtmap() { + void AddExtmap(bool encrypted) { audio_desc_ = static_cast( audio_desc_->Copy()); video_desc_ = static_cast( video_desc_->Copy()); - audio_desc_->AddRtpHeaderExtension(RtpExtension(kExtmapUri, kExtmapId)); - video_desc_->AddRtpHeaderExtension(RtpExtension(kExtmapUri, kExtmapId)); + audio_desc_->AddRtpHeaderExtension( + RtpExtension(kExtmapUri, kExtmapId, encrypted)); + video_desc_->AddRtpHeaderExtension( + RtpExtension(kExtmapUri, kExtmapId, encrypted)); desc_.RemoveContentByName(kAudioContentName); desc_.RemoveContentByName(kVideoContentName); desc_.AddContent(kAudioContentName, NS_JINGLE_RTP, audio_desc_); @@ -1576,8 +1582,9 @@ class WebRtcSdpTest : public testing::Test { return true; } - void TestDeserializeExtmap(bool session_level, bool media_level) { - AddExtmap(); + void TestDeserializeExtmap(bool session_level, bool media_level, + bool encrypted) { + AddExtmap(encrypted); JsepSessionDescription new_jdesc("dummy"); ASSERT_TRUE(new_jdesc.Initialize(desc_.Copy(), jdesc_.session_id(), @@ -1585,13 +1592,19 @@ class WebRtcSdpTest : public testing::Test { JsepSessionDescription jdesc_with_extmap("dummy"); std::string sdp_with_extmap = kSdpString; if (session_level) { - InjectAfter(kSessionTime, kExtmapWithDirectionAndAttribute, + InjectAfter(kSessionTime, + encrypted ? kExtmapWithDirectionAndAttributeEncrypted + : kExtmapWithDirectionAndAttribute, &sdp_with_extmap); } if (media_level) { - InjectAfter(kAttributeIcePwdVoice, kExtmapWithDirectionAndAttribute, + InjectAfter(kAttributeIcePwdVoice, + encrypted ? kExtmapWithDirectionAndAttributeEncrypted + : kExtmapWithDirectionAndAttribute, &sdp_with_extmap); - InjectAfter(kAttributeIcePwdVideo, kExtmapWithDirectionAndAttribute, + InjectAfter(kAttributeIcePwdVideo, + encrypted ? kExtmapWithDirectionAndAttributeEncrypted + : kExtmapWithDirectionAndAttribute, &sdp_with_extmap); } // The extmap can't be present at the same time in both session level and @@ -2016,7 +2029,8 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithDataChannelAndBandwidth) { } TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithExtmap) { - AddExtmap(); + bool encrypted = false; + AddExtmap(encrypted); JsepSessionDescription desc_with_extmap("dummy"); MakeDescriptionWithoutCandidates(&desc_with_extmap); std::string message = webrtc::SdpSerialize(desc_with_extmap, false); @@ -2030,6 +2044,15 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithExtmap) { EXPECT_EQ(sdp_with_extmap, message); } +TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithExtmapEncrypted) { + bool encrypted = true; + AddExtmap(encrypted); + JsepSessionDescription desc_with_extmap("dummy"); + ASSERT_TRUE(desc_with_extmap.Initialize(desc_.Copy(), + kSessionId, kSessionVersion)); + TestSerialize(desc_with_extmap, false); +} + TEST_F(WebRtcSdpTest, SerializeCandidates) { std::string message = webrtc::SdpSerializeCandidate(*jcandidate_); EXPECT_EQ(std::string(kRawCandidate), message); @@ -2675,18 +2698,32 @@ TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelsAndBandwidth) { EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_with_bandwidth)); } -TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithSessionLevelExtmap) { - TestDeserializeExtmap(true, false); +class WebRtcSdpExtmapTest + : public WebRtcSdpTest, public testing::WithParamInterface { +}; + +TEST_P(WebRtcSdpExtmapTest, + DeserializeSessionDescriptionWithSessionLevelExtmap) { + bool encrypted = GetParam(); + TestDeserializeExtmap(true, false, encrypted); } -TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithMediaLevelExtmap) { - TestDeserializeExtmap(false, true); +TEST_P(WebRtcSdpExtmapTest, + DeserializeSessionDescriptionWithMediaLevelExtmap) { + bool encrypted = GetParam(); + TestDeserializeExtmap(false, true, encrypted); } -TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithInvalidExtmap) { - TestDeserializeExtmap(true, true); +TEST_P(WebRtcSdpExtmapTest, + DeserializeSessionDescriptionWithInvalidExtmap) { + bool encrypted = GetParam(); + TestDeserializeExtmap(true, true, encrypted); } +INSTANTIATE_TEST_CASE_P(Encrypted, + WebRtcSdpExtmapTest, + ::testing::Values(false, true)); + TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutEndLineBreak) { JsepSessionDescription jdesc(kDummyString); std::string sdp = kSdpFullString; diff --git a/webrtc/pc/webrtcsession.cc b/webrtc/pc/webrtcsession.cc index 2c7e9a462b..e0eb72dce0 100644 --- a/webrtc/pc/webrtcsession.cc +++ b/webrtc/pc/webrtcsession.cc @@ -625,6 +625,9 @@ bool WebRtcSession::Initialize( webrtc_session_desc_factory_->SetSdesPolicy(cricket::SEC_DISABLED); } + webrtc_session_desc_factory_->set_enable_encrypted_rtp_header_extensions( + options.crypto_options.enable_encrypted_rtp_header_extensions); + return true; } diff --git a/webrtc/pc/webrtcsessiondescriptionfactory.h b/webrtc/pc/webrtcsessiondescriptionfactory.h index 7572f244ec..d742164999 100644 --- a/webrtc/pc/webrtcsessiondescriptionfactory.h +++ b/webrtc/pc/webrtcsessiondescriptionfactory.h @@ -106,6 +106,10 @@ class WebRtcSessionDescriptionFactory : public rtc::MessageHandler, void SetSdesPolicy(cricket::SecurePolicy secure_policy); cricket::SecurePolicy SdesPolicy() const; + void set_enable_encrypted_rtp_header_extensions(bool enable) { + session_desc_factory_.set_enable_encrypted_rtp_header_extensions(enable); + } + sigslot::signal1&> SignalCertificateReady; diff --git a/webrtc/rtc_base/sslstreamadapter.h b/webrtc/rtc_base/sslstreamadapter.h index 8d85e9270c..889b6349b9 100644 --- a/webrtc/rtc_base/sslstreamadapter.h +++ b/webrtc/rtc_base/sslstreamadapter.h @@ -78,6 +78,10 @@ struct CryptoOptions { // Enable GCM crypto suites from RFC 7714 for SRTP. GCM will only be used // if both sides enable it. bool enable_gcm_crypto_suites = false; + + // If set to true, encrypted RTP header extensions as defined in RFC 6904 + // will be negotiated. They will only be used if both peers support them. + bool enable_encrypted_rtp_header_extensions = false; }; // Returns supported crypto suites, given |crypto_options|.