Support encrypted RTP extensions (RFC 6904)

Can be enabled by setting "enable_encrypted_rtp_header_extensions" in
"crypto_options" of "PeerConnectionFactoryInterface::Options" and will
only be used if both peers support it.

BUG=webrtc:3411

Review-Url: https://codereview.webrtc.org/2761143002
Cr-Commit-Position: refs/heads/master@{#18842}
This commit is contained in:
jbauch 2017-06-29 12:31:36 -07:00 committed by Commit Bot
parent c9be3d5e8b
commit 5869f50f7a
27 changed files with 1404 additions and 116 deletions

View File

@ -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",

View File

@ -9,6 +9,7 @@
*/
#include "webrtc/config.h"
#include <algorithm>
#include <sstream>
#include <string>
@ -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<RtpExtension>& extensions,
const std::string& uri) {
for (const auto& extension : extensions) {
if (extension.uri == uri) {
return &extension;
}
}
return nullptr;
}
std::vector<RtpExtension> RtpExtension::FilterDuplicateNonEncrypted(
const std::vector<RtpExtension>& extensions) {
std::vector<RtpExtension> 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),

View File

@ -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<RtpExtension>& 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<RtpExtension> FilterDuplicateNonEncrypted(
const std::vector<RtpExtension>& 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 {

49
webrtc/config_unittest.cc Normal file
View File

@ -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 <vector>
#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<RtpExtension> extensions;
std::vector<RtpExtension> filtered;
extensions.push_back(kExtension1);
extensions.push_back(kExtension1Encrypted);
filtered = RtpExtension::FilterDuplicateNonEncrypted(extensions);
EXPECT_EQ(1u, filtered.size());
EXPECT_EQ(std::vector<RtpExtension>{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<RtpExtension>{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);
}

View File

@ -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",

View File

@ -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 <algorithm>
#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<int> 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++;
}
}
}

View File

@ -13,6 +13,8 @@
#ifndef WEBRTC_MEDIA_BASE_FAKERTP_H_
#define WEBRTC_MEDIA_BASE_FAKERTP_H_
#include <vector>
// 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<int> encrypted_headers, bool expect_equal);
#endif // WEBRTC_MEDIA_BASE_FAKERTP_H_

View File

@ -208,18 +208,22 @@ std::vector<webrtc::RtpExtension> 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());

View File

@ -145,6 +145,38 @@ TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundant) {
EXPECT_NE(filtered[0].uri, filtered[1].uri);
}
TEST(WebRtcMediaEngineTest, FilterRtpExtensions_RemoveRedundantEncrypted_1) {
std::vector<RtpExtension> 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<webrtc::RtpExtension> 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<RtpExtension> 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<webrtc::RtpExtension> 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<RtpExtension> 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<RtpExtension> 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<webrtc::RtpExtension> 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<RtpExtension> extensions;
extensions.push_back(RtpExtension(RtpExtension::kTimestampOffsetUri, 1));

View File

@ -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);

View File

@ -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<rtc::RTCCertificate> 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_;

View File

@ -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;

View File

@ -91,6 +91,7 @@ class FakeDtlsTransport : public DtlsTransportInternal {
if (!asymmetric) {
dest->SetDestination(this, true);
}
dtls_state_ = DTLS_TRANSPORT_CONNECTED;
ice_transport_->SetDestination(
static_cast<FakeIceTransport*>(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<rtc::RTCCertificate>& 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<rtc::RTCCertificate> 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;

View File

@ -8,6 +8,8 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include <iterator>
#include <utility>
#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<webrtc::RtpExtension>& 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 <class Codec>
void RtpParametersFromMediaDescription(
const MediaContentDescriptionImpl<Codec>* desc,
const RtpHeaderExtensions& extensions,
RtpParameters<Codec>* 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 <class Codec>
void RtpSendParametersFromMediaDescription(
const MediaContentDescriptionImpl<Codec>* desc,
const RtpHeaderExtensions& extensions,
RtpSendParameters<Codec>* 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<int>(send_key->size()),
selected_crypto_suite, &(*recv_key)[0],
static_cast<int>(recv_key->size()));
if (!srtp_filter_.IsActive()) {
if (rtcp) {
ret = srtp_filter_.SetRtcpParams(selected_crypto_suite, &(*send_key)[0],
static_cast<int>(send_key->size()),
selected_crypto_suite, &(*recv_key)[0],
static_cast<int>(recv_key->size()));
} else {
ret = srtp_filter_.SetRtpParams(selected_crypto_suite, &(*send_key)[0],
static_cast<int>(send_key->size()),
selected_crypto_suite, &(*recv_key)[0],
static_cast<int>(recv_key->size()));
}
} else {
ret = srtp_filter_.SetRtpParams(selected_crypto_suite, &(*send_key)[0],
static_cast<int>(send_key->size()),
selected_crypto_suite, &(*recv_key)[0],
static_cast<int>(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<int>(send_key->size()),
selected_crypto_suite,
&(*recv_key)[0], static_cast<int>(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<int> 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<bool>(
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<int>& 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<CryptoParams>& cryptos,
bool BaseChannel::SetSrtp_n(const std::vector<CryptoParams>& cryptos,
ContentAction action,
ContentSource src,
const std::vector<int>& 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<CryptoParams>& 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<CryptoParams>& 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<webrtc::RtpExtension>& 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<void>(
@ -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<int>(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<DataCodec>(data, &send_params);
RtpSendParametersFromMediaDescription<DataCodec>(data, rtp_header_extensions,
&send_params);
if (!media_channel()->SetSendParameters(send_params)) {
SafeSetError("Failed to set remote data description send parameters.",
error_desc);

View File

@ -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<int>& 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<CryptoParams>& params,
ContentAction action,
ContentSource src,
const std::vector<int>& encrypted_extension_ids,
std::string* error_desc);
bool SetRtcpMux_n(bool enable,
ContentAction action,

View File

@ -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<const char*>(kPcmuFrameWithExtensions),
sizeof(kPcmuFrameWithExtensions), data, size, encrypted_headers,
false);
}
std::vector<int> 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<VoiceTraits> {
: Base(true, kPcmuFrame, kRtcpReport, NetworkIsWorker::No) {}
};
class VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest
: public ChannelTest<VoiceTraits> {
public:
typedef ChannelTest<VoiceTraits> Base;
VoiceChannelWithEncryptedRtpHeaderExtensionsSingleThreadTest()
: Base(true, kPcmuFrameWithExtensions, kRtcpReport,
NetworkIsWorker::Yes) {}
};
class VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest
: public ChannelTest<VoiceTraits> {
public:
typedef ChannelTest<VoiceTraits> Base;
VoiceChannelWithEncryptedRtpHeaderExtensionsDoubleThreadTest()
: Base(true, kPcmuFrameWithExtensions, kRtcpReport,
NetworkIsWorker::No) {}
};
// override to add NULL parameter
template <>
cricket::VideoChannel* ChannelTest<VideoTraits>::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();
}

View File

@ -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,

View File

@ -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_;
};

View File

@ -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<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
ASSERT_TRUE(offer.get() != NULL);
std::unique_ptr<SessionDescription> 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<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
ASSERT_TRUE(offer.get() != NULL);
std::unique_ptr<SessionDescription> 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<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
ASSERT_TRUE(offer.get() != NULL);
std::unique_ptr<SessionDescription> 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<SessionDescription> 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<SessionDescription> 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);

View File

@ -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<int>& 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<size_t>(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<int*>(&encrypted_header_extension_ids_[0]);
policy.enc_xtn_hdr_count =
static_cast<int>(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<int>& encrypted_header_extension_ids) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
encrypted_header_extension_ids_ = encrypted_header_extension_ids;
}
bool SrtpSession::Init() {
rtc::GlobalLockScope ls(&lock_);

View File

@ -76,6 +76,10 @@ class SrtpFilter {
bool SetAnswer(const std::vector<CryptoParams>& answer_params,
ContentSource source);
// Set the header extension ids that should be encrypted for the given source.
void SetEncryptedHeaderExtensionIds(ContentSource source,
const std::vector<int>& 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<SrtpSession> recv_rtcp_session_;
CryptoParams applied_send_params_;
CryptoParams applied_recv_params_;
std::vector<int> send_encrypted_header_extension_ids_;
std::vector<int> 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<int>& 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<int>& 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<int> encrypted_header_extension_ids_;
RTC_DISALLOW_COPY_AND_ASSIGN(SrtpSession);
};

View File

@ -8,6 +8,8 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#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<int>& encrypted_header_ids) {
rtc::Buffer rtp_buffer(sizeof(kPcmuFrameWithExtensions) +
rtp_auth_tag_len(cs1));
char* rtp_packet = rtp_buffer.data<char>();
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<uint8_t*>(rtp_packet) + 2,
++sequence_number_);
memcpy(original_rtp_packet, rtp_packet, rtp_len);
EXPECT_TRUE(f1_.ProtectRtp(rtp_packet, rtp_len,
static_cast<int>(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<int>(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<int> 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,

View File

@ -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:<value["/"<direction>] urn:ietf:params:rtp-hdrext:encrypt \
// <URI> <extensionattributes>
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);
}

View File

@ -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<AudioContentDescription*>(
audio_desc_->Copy());
video_desc_ = static_cast<VideoContentDescription*>(
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<bool> {
};
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;

View File

@ -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;
}

View File

@ -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<const rtc::scoped_refptr<rtc::RTCCertificate>&>
SignalCertificateReady;

View File

@ -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|.