Implement cryptex header extension negotiation Implements https://datatracker.ietf.org/doc/rfc9335/ with the necessary APIs for the W3C extension API described in https://w3c.github.io/webrtc-extensions/#rtp-header-extension-encryption cryptex is declarative per https://datatracker.ietf.org/doc/html/rfc9335#section-4 cryptex is used as a session-level attribute by default with the exception of matching media-level-only offers at media level. The per-transceiver flag from the W3C spec is split to a separate CL. Bug: webrtc:455813732 Change-Id: I4a17bad40300112300c5d98d3d4d80cb3b6df848 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/451800 Commit-Queue: Philipp Hancke <philipp.hancke@googlemail.com> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Reviewed-by: Erik Språng <sprang@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47813}
diff --git a/api/crypto/crypto_options.cc b/api/crypto/crypto_options.cc index 6464728..1549e69 100644 --- a/api/crypto/crypto_options.cc +++ b/api/crypto/crypto_options.cc
@@ -82,6 +82,7 @@ bool enable_aes128_sha1_32_crypto_cipher; bool enable_aes128_sha1_80_crypto_cipher; bool enable_encrypted_rtp_header_extensions; + CryptoOptions::Srtp::CryptexPolicy cryptex_policy; } srtp; struct SFrame { bool require_frame_encryption; @@ -100,6 +101,7 @@ other.srtp.enable_aes128_sha1_80_crypto_cipher && srtp.enable_encrypted_rtp_header_extensions == other.srtp.enable_encrypted_rtp_header_extensions && + srtp.cryptex_policy == other.srtp.cryptex_policy && sframe.require_frame_encryption == other.sframe.require_frame_encryption && ephemeral_key_exchange_cipher_groups ==
diff --git a/api/crypto/crypto_options.h b/api/crypto/crypto_options.h index 5308d59..5c0c15f 100644 --- a/api/crypto/crypto_options.h +++ b/api/crypto/crypto_options.h
@@ -69,6 +69,13 @@ // requested. For this to work the Chromium field trial // `kWebRtcEncryptedRtpHeaderExtensions` must be enabled. bool enable_encrypted_rtp_header_extensions = true; + + // Enable cryptex, a modern take on encrypting header extensions: + // https://www.rfc-editor.org/rfc/rfc9335.html + // Supported as of libsrtp v2.8.0. + enum class CryptexPolicy { kDisabled, kNegotiate, kRequire }; + // TODO: bugs.webrtc.org/455813732 - the default should be `negotiate`. + CryptexPolicy cryptex_policy = CryptexPolicy::kDisabled; } srtp; // Options to be used when the FrameEncryptor / FrameDecryptor APIs are used.
diff --git a/api/uma_metrics.h b/api/uma_metrics.h index dfe7f1d..981736d 100644 --- a/api/uma_metrics.h +++ b/api/uma_metrics.h
@@ -175,6 +175,17 @@ kRtcpMuxPolicyUsageMax }; +// Metrics for RTCRtpHeaderEncryptionPolicy (which enables cryptex). +// https://w3c.github.io/webrtc-extensions/#rtp-header-extension-encryption +enum CryptexPolicyUsage { + kCryptexPolicyUsageDisabled = 0, // Non-spec. + kCryptexPolicyUsageNegotiate = 1, + // Policy was `negotiate` and it was negotiated at session level. + kCryptexPolicyUsageNegotiated = 2, + kCryptexPolicyUsageRequire = 3, + kCryptexPolicyUsageMax +}; + // Metrics for SDP munging. // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. Keep in (loose) sync with @@ -210,6 +221,7 @@ kRtpHeaderExtensionRemoved = 40, kRtpHeaderExtensionAdded = 41, kRtpHeaderExtensionModified = 42, + kCryptex = 43, // Audio-related munging. kAudioCodecsRemoved = 60, kAudioCodecsAdded = 61,
diff --git a/api/webrtc_sdp.cc b/api/webrtc_sdp.cc index f4ba9a9..5647314 100644 --- a/api/webrtc_sdp.cc +++ b/api/webrtc_sdp.cc
@@ -143,6 +143,9 @@ constexpr absl::string_view kAttributeRid = "rid"; const char kAttributePacketization[] = "packetization"; +// https://www.rfc-editor.org/rfc/rfc9335.html +const char kAttributeCryptex[] = "cryptex"; + // Experimental flags const char kAttributeXGoogleFlag[] = "x-google-flag"; const char kValueConference[] = "conference"; @@ -1268,6 +1271,11 @@ InitAttrLine(kAttributeExtmapAllowMixed, &os); AddLine(os.str(), message); } + if (media_desc->cryptex_level() == + MediaContentDescription::AttributeLevel::kMedia) { + InitAttrLine(kAttributeCryptex, &os); + AddLine(os.str(), message); + } BuildRtpHeaderExtensions(media_desc->rtp_header_extensions(), message); // RFC 3264 @@ -1659,6 +1667,8 @@ desc->set_msid_signaling(kMsidSignalingNotUsed); desc->set_extmap_allow_mixed(false); + desc->set_cryptex(false); + // RFC 4566 // v= (protocol version) line = GetLineWithType(message, pos, kLineTypeVersion); @@ -1810,6 +1820,8 @@ return false; } session_extmaps->push_back(extmap); + } else if (HasAttribute(*aline, kAttributeCryptex)) { + desc->set_cryptex(true); } } return true; @@ -2871,6 +2883,9 @@ // Ignore and do not log a=rtcp line. // JSEP section 5.8.2 (media section parsing) says to ignore it. continue; + } else if (HasAttribute(*line, kAttributeCryptex)) { + media_desc->set_cryptex_level( + MediaContentDescription::AttributeLevel::kMedia); } else { // Unrecognized attribute in RTP protocol. RTC_LOG(LS_VERBOSE) << "Ignored line: " << *line; @@ -3015,6 +3030,7 @@ media_desc->set_extmap_allow_mixed_level( MediaContentDescription::AttributeLevel::kNone); + media_desc->set_cryptex_level(MediaContentDescription::AttributeLevel::kNone); if (!ParseContent(message, media_type, mline_index, protocol, payload_types, pos, content_name, bundle_only, msid_signaling, media_desc.get(), transport, candidates, error)) { @@ -3326,6 +3342,10 @@ InitAttrLine(kAttributeExtmapAllowMixed, &os); AddLine(os.str(), &message); } + if (desc->cryptex()) { + InitAttrLine(kAttributeCryptex, &os); + AddLine(os.str(), &message); + } // MediaStream semantics. // TODO(bugs.webrtc.org/10421): Change to & kMsidSignalingSemantic
diff --git a/p2p/base/transport_description.cc b/p2p/base/transport_description.cc index 8758633..8d5ed4c 100644 --- a/p2p/base/transport_description.cc +++ b/p2p/base/transport_description.cc
@@ -151,7 +151,9 @@ } TransportDescription::TransportDescription() - : ice_mode(ICEMODE_FULL), connection_role(CONNECTIONROLE_NONE) {} + : ice_mode(ICEMODE_FULL), + connection_role(CONNECTIONROLE_NONE), + cryptex(false) {} TransportDescription::TransportDescription( const std::vector<std::string>& transport_options, @@ -165,14 +167,16 @@ ice_pwd(ice_pwd), ice_mode(ice_mode), connection_role(role), - identity_fingerprint(CopyFingerprint(identity_fingerprint)) {} + identity_fingerprint(CopyFingerprint(identity_fingerprint)), + cryptex(false) {} TransportDescription::TransportDescription(absl::string_view ice_ufrag, absl::string_view ice_pwd) : ice_ufrag(ice_ufrag), ice_pwd(ice_pwd), ice_mode(ICEMODE_FULL), - connection_role(CONNECTIONROLE_NONE) {} + connection_role(CONNECTIONROLE_NONE), + cryptex(false) {} TransportDescription::TransportDescription(const TransportDescription& from) : transport_options(from.transport_options), @@ -180,7 +184,8 @@ ice_pwd(from.ice_pwd), ice_mode(from.ice_mode), connection_role(from.connection_role), - identity_fingerprint(CopyFingerprint(from.identity_fingerprint.get())) {} + identity_fingerprint(CopyFingerprint(from.identity_fingerprint.get())), + cryptex(from.cryptex) {} TransportDescription::~TransportDescription() = default; @@ -197,6 +202,7 @@ connection_role = from.connection_role; identity_fingerprint.reset(CopyFingerprint(from.identity_fingerprint.get())); + cryptex = from.cryptex; return *this; }
diff --git a/p2p/base/transport_description.h b/p2p/base/transport_description.h index d5592ef..b3c2fbf 100644 --- a/p2p/base/transport_description.h +++ b/p2p/base/transport_description.h
@@ -147,6 +147,8 @@ ConnectionRole connection_role; std::unique_ptr<SSLFingerprint> identity_fingerprint; + + bool cryptex; }; } // namespace webrtc
diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 85f01b0..7325d7c 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn
@@ -211,6 +211,7 @@ "../api:rtc_error", "../api:scoped_refptr", "../api:sequence_checker", + "../api/crypto:options", "../api/transport:datagram_transport_interface", "../media:rtc_data_sctp_transport_internal", "../p2p:dtls_transport_internal", @@ -353,6 +354,7 @@ "../api:rtp_parameters", "../api:rtp_transceiver_direction", "../api:sctp_transport_interface", + "../api/crypto:options", "../api/environment", "../api/transport:sctp_transport_factory_interface", "../call:payload_type", @@ -1141,6 +1143,7 @@ ":jsep_transport_controller", ":legacy_stats_collector", ":media_options", + ":media_protocol_names", ":media_session", ":media_stream", ":media_stream_observer", @@ -3610,6 +3613,7 @@ "../api/audio_codecs:audio_codecs_api", "../api/audio_codecs:builtin_audio_decoder_factory", "../api/audio_codecs:builtin_audio_encoder_factory", + "../api/crypto:options", "../api/video_codecs:video_codecs_api", "../api/video_codecs:video_decoder_factory_template", "../api/video_codecs:video_decoder_factory_template_dav1d_adapter", @@ -3657,6 +3661,7 @@ "../api:scoped_refptr", "../api/audio_codecs:builtin_audio_decoder_factory", "../api/audio_codecs:builtin_audio_encoder_factory", + "../api/crypto:options", "../api/video_codecs:video_codecs_api", "../api/video_codecs:video_decoder_factory_template", "../api/video_codecs:video_decoder_factory_template_dav1d_adapter", @@ -3809,6 +3814,7 @@ "../api:rtc_stats_api", "../api:scoped_refptr", "../api:sctp_transport_interface", + "../api/crypto:options", "../api/units:time_delta", "../p2p:p2p_test_utils", "../p2p:transport_description",
diff --git a/pc/jsep_transport.cc b/pc/jsep_transport.cc index 4681431..0e4355e 100644 --- a/pc/jsep_transport.cc +++ b/pc/jsep_transport.cc
@@ -21,6 +21,7 @@ #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "api/candidate.h" +#include "api/crypto/crypto_options.h" #include "api/dtls_transport_interface.h" #include "api/ice_transport_interface.h" #include "api/jsep.h" @@ -87,7 +88,8 @@ std::unique_ptr<RtpTransport> rtp_transport, scoped_refptr<DtlsTransport> rtp_dtls_transport, std::unique_ptr<SctpTransportInternal> sctp_transport, - absl::AnyInvocable<void()> rtcp_mux_active_callback) + absl::AnyInvocable<void()> rtcp_mux_active_callback, + CryptoOptions::Srtp::CryptexPolicy cryptex_policy) : local_certificate_(local_certificate), rtp_transport_(std::move(rtp_transport)), rtp_dtls_transport_(std::move(rtp_dtls_transport)), @@ -96,7 +98,8 @@ std::move(sctp_transport), rtp_dtls_transport_) : nullptr), - rtcp_mux_active_callback_(std::move(rtcp_mux_active_callback)) { + rtcp_mux_active_callback_(std::move(rtcp_mux_active_callback)), + cryptex_policy_(cryptex_policy) { TRACE_EVENT0("webrtc", "JsepTransport::JsepTransport"); RTC_DCHECK(rtp_dtls_transport_); RTC_DCHECK(rtp_transport_); @@ -146,6 +149,11 @@ if (auto* dtls_srtp_transport = rtp_transport_->AsDtlsSrtpTransport()) { dtls_srtp_transport->UpdateRecvEncryptedHeaderExtensionIds( jsep_description.encrypted_header_extension_ids); + dtls_srtp_transport->UseCryptex( + (cryptex_policy_ != CryptoOptions::Srtp::CryptexPolicy::kDisabled) && + (remote_description_ != nullptr && + remote_description_->transport_desc.cryptex), + cryptex_policy_ == CryptoOptions::Srtp::CryptexPolicy::kRequire); } bool ice_restarting = local_description_ != nullptr && @@ -218,6 +226,10 @@ if (auto* dtls_srtp_transport = rtp_transport_->AsDtlsSrtpTransport()) { dtls_srtp_transport->UpdateSendEncryptedHeaderExtensionIds( jsep_description.encrypted_header_extension_ids); + dtls_srtp_transport->UseCryptex( + (cryptex_policy_ != CryptoOptions::Srtp::CryptexPolicy::kDisabled) && + jsep_description.transport_desc.cryptex, + cryptex_policy_ == CryptoOptions::Srtp::CryptexPolicy::kRequire); } remote_description_.reset(new JsepTransportDescription(jsep_description));
diff --git a/pc/jsep_transport.h b/pc/jsep_transport.h index 9ec919b..b87a072 100644 --- a/pc/jsep_transport.h +++ b/pc/jsep_transport.h
@@ -17,6 +17,7 @@ #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" +#include "api/crypto/crypto_options.h" #include "api/ice_transport_interface.h" #include "api/jsep.h" #include "api/rtc_error.h" @@ -77,7 +78,8 @@ std::unique_ptr<RtpTransport> rtp_transport, scoped_refptr<DtlsTransport> rtp_dtls_transport, std::unique_ptr<SctpTransportInternal> sctp_transport, - absl::AnyInvocable<void()> rtcp_mux_active_callback); + absl::AnyInvocable<void()> rtcp_mux_active_callback, + CryptoOptions::Srtp::CryptexPolicy cryptex_policy); ~JsepTransport(); @@ -260,6 +262,8 @@ // `rtcp_dtls_transport_` is destroyed. The JsepTransportController will // receive the callback and update the aggregate transport states. absl::AnyInvocable<void()> rtcp_mux_active_callback_; + + const CryptoOptions::Srtp::CryptexPolicy cryptex_policy_; }; } // namespace webrtc
diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc index e15a03c..0421f56 100644 --- a/pc/jsep_transport_controller.cc +++ b/pc/jsep_transport_controller.cc
@@ -782,6 +782,8 @@ JsepTransportDescription jsep_description = CreateJsepTransportDescription( content_info, transport_info, extension_ids); + jsep_description.transport_desc.cryptex = + content_info.media_description()->cryptex(); if (local) { error = transport->SetLocalJsepTransportDescription(jsep_description, type); @@ -1199,12 +1201,14 @@ make_ref_counted<DtlsTransport>(rtp_dtls_transport_ptr); std::unique_ptr<JsepTransport> jsep_transport = - std::make_unique<JsepTransport>(certificate_, std::move(rtp_transport), - std::move(dtls_transport), - std::move(sctp_transport), [&]() { - RTC_DCHECK_RUN_ON(network_thread_); - UpdateAggregateStates_n(); - }); + std::make_unique<JsepTransport>( + certificate_, std::move(rtp_transport), std::move(dtls_transport), + std::move(sctp_transport), + [&]() { + RTC_DCHECK_RUN_ON(network_thread_); + UpdateAggregateStates_n(); + }, + config_.crypto_options.srtp.cryptex_policy); // Object hierarchy for objects injected into JsepTransport: //
diff --git a/pc/jsep_transport_unittest.cc b/pc/jsep_transport_unittest.cc index a154b97..94882ad 100644 --- a/pc/jsep_transport_unittest.cc +++ b/pc/jsep_transport_unittest.cc
@@ -22,6 +22,7 @@ #include <vector> #include "api/candidate.h" +#include "api/crypto/crypto_options.h" #include "api/field_trials.h" #include "api/ice_transport_interface.h" #include "api/jsep.h" @@ -150,7 +151,8 @@ /*local_certificate=*/nullptr, std::move(rtp_transport), std::move(rtp_dtls_transport_wrapper), /*sctp_transport=*/nullptr, - /*rtcp_mux_active_callback=*/[&]() { OnRtcpMuxActive(); }); + /*rtcp_mux_active_callback=*/[&]() { OnRtcpMuxActive(); }, + /*cryptex_policy=*/CryptoOptions::Srtp::CryptexPolicy::kDisabled); signal_rtcp_mux_active_received_ = false; return jsep_transport;
diff --git a/pc/media_session.cc b/pc/media_session.cc index bfa645d..9f62643 100644 --- a/pc/media_session.cc +++ b/pc/media_session.cc
@@ -21,6 +21,7 @@ #include "absl/algorithm/container.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" +#include "api/crypto/crypto_options.h" #include "api/environment/environment.h" #include "api/field_trials_view.h" #include "api/media_types.h" @@ -630,6 +631,16 @@ extensions_filter, &negotiated_rtp_extensions, suggester, media_description_options.mid, id_domain); answer->set_rtp_header_extensions(negotiated_rtp_extensions); + // Cryptex is declarative, i.e. does not depend on the offer. + // If present in the offer we match the level (session/media) + // and put it at session level otherwise. + if (session_options.crypto_options.srtp.cryptex_policy != + CryptoOptions::Srtp::CryptexPolicy::kDisabled) { + answer->set_cryptex_level( + offer->cryptex_level() != MediaContentDescription::AttributeLevel::kNone + ? offer->cryptex_level() + : MediaContentDescription::AttributeLevel::kSession); + } answer->set_rtcp_mux(session_options.rtcp_mux_enabled && offer->rtcp_mux()); answer->set_rtcp_reduced_size(offer->rtcp_reduced_size()); @@ -864,6 +875,8 @@ } offer->set_extmap_allow_mixed(session_options.offer_extmap_allow_mixed); + offer->set_cryptex(session_options.crypto_options.srtp.cryptex_policy != + CryptoOptions::Srtp::CryptexPolicy::kDisabled); return offer; } @@ -938,6 +951,17 @@ } answer->set_extmap_allow_mixed(offer->extmap_allow_mixed()); + // Cryptex is declarative: advertise support when policy allows. If the + // offer used cryptex at media level, answer at media level. + bool use_session_level_cryptex = + offer->cryptex() || + absl::c_find_if(offer->contents(), [](const ContentInfo& content) { + return content.media_description()->cryptex_level() == + MediaContentDescription::AttributeLevel::kMedia; + }) == offer->contents().end(); + answer->set_cryptex(session_options.crypto_options.srtp.cryptex_policy != + CryptoOptions::Srtp::CryptexPolicy::kDisabled && + use_session_level_cryptex); // Iterate through the media description options, matching with existing // media descriptions in `current_description`.
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc index fa5108e..f36189d 100644 --- a/pc/peer_connection.cc +++ b/pc/peer_connection.cc
@@ -2043,7 +2043,9 @@ // Rollback does not have SDP so can not be munged. break; } - bool negotiated_sctp_snap = false; + + // Below this point are features where we check what was negotiated in + // SDP. `desc` will contain the answer, i.e. what was negotiated. const SessionDescription* desc = nullptr; if (local_description()->GetType() == SdpType::kAnswer || local_description()->GetType() == SdpType::kPrAnswer) { @@ -2058,7 +2060,8 @@ << ", remote=" << remote_description()->GetType(); return; } - // Below this point, we assume that we have an answer in `desc` + + bool negotiated_sctp_snap = false; const ContentInfo* sctp_content = GetFirstDataContent(desc); if (sctp_content && !sctp_content->rejected) { const SctpDataContentDescription* sctp_desc = @@ -2069,8 +2072,9 @@ } RTC_HISTOGRAM_BOOLEAN("WebRTC.PeerConnection.NegotiatedSctpSnap", negotiated_sctp_snap); + // Record congestion control mechanism in use, if any. - // The information is taken from the last seen Answer SDP. + // The information is taken from the last seen answer SDP. std::optional<RtcpFeedbackType> feedback_type; for (const auto& content : desc->contents()) { std::optional<RtcpFeedbackType> this_feedback_type = @@ -2088,6 +2092,25 @@ RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.NegotiatedFeedbackType", static_cast<int>(*feedback_type), static_cast<int>(RtcpFeedbackType::MAX)); + + CryptexPolicyUsage cryptex = kCryptexPolicyUsageMax; + switch (configuration_.crypto_options.srtp.cryptex_policy) { + case CryptoOptions::Srtp::CryptexPolicy::kDisabled: + cryptex = kCryptexPolicyUsageDisabled; + break; + case CryptoOptions::Srtp::CryptexPolicy::kNegotiate: + cryptex = kCryptexPolicyUsageNegotiate; + // Special-case for when cryptex was not required but negotiated. + if (desc->cryptex()) { + cryptex = kCryptexPolicyUsageNegotiated; + } + break; + case CryptoOptions::Srtp::CryptexPolicy::kRequire: + cryptex = kCryptexPolicyUsageRequire; + break; + } + RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CryptexUsage", cryptex, + kCryptexPolicyUsageMax); } void PeerConnection::ReportCloseUsageMetrics() {
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc index be7934e..bebbd0c 100644 --- a/pc/peer_connection_integrationtest.cc +++ b/pc/peer_connection_integrationtest.cc
@@ -5137,6 +5137,50 @@ } } +TEST_P(PeerConnectionIntegrationTest, CryptexRenegotiation) { + CryptoOptions crypto_options; + crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + PeerConnectionInterface::RTCConfiguration config; + config.crypto_options = crypto_options; + + const bool create_media_engine = true; + SetCallerPcWrapperAndReturnCurrent(CreatePeerConnectionWrapper( + "caller", nullptr, &config, PeerConnectionDependencies(nullptr), + /* event_log_factory= */ nullptr, + /* reset_encoder_factory= */ false, + /* reset_decoder_factory= */ false, create_media_engine)); + SetCalleePcWrapperAndReturnCurrent(CreatePeerConnectionWrapper( + "callee", nullptr, &config, PeerConnectionDependencies(nullptr), + /* event_log_factory= */ nullptr, + /* reset_encoder_factory= */ false, + /* reset_decoder_factory= */ false, create_media_engine)); + + ConnectFakeSignaling(); + + caller()->AddAudioVideoTracks(); + // Strip cryptex from the answer the caller receives so the caller observes + // a peer that does not negotiate cryptex. + caller()->SetReceivedSdpMunger( + [](std::unique_ptr<SessionDescriptionInterface>& sdp) { + sdp->description()->set_cryptex(false); + }); + + caller()->CreateAndSetAndSignalOffer(); + ASSERT_THAT( + WaitUntil( + [&] { + return PeerConnectionStateIs( + PeerConnectionInterface::PeerConnectionState::kConnected); + }, + IsTrue()), + IsRtcOk()); + caller()->SetReceivedSdpMunger(nullptr); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()), + IsRtcOk()); +} + #ifdef WEBRTC_HAVE_SCTP class DowngradeLogSink : public LogSink {
diff --git a/pc/peer_connection_interface_unittest.cc b/pc/peer_connection_interface_unittest.cc index b06416b..c46ee38 100644 --- a/pc/peer_connection_interface_unittest.cc +++ b/pc/peer_connection_interface_unittest.cc
@@ -2605,6 +2605,26 @@ RTCError error = pc_->SetConfiguration(config); EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, error.type()); } + { + RTCConfiguration config; + config.sdp_semantics = sdp_semantics_; + CryptoOptions options; + options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + config.crypto_options = options; + + CreatePeerConnection(config); + + std::unique_ptr<SessionDescriptionInterface> offer; + ASSERT_TRUE(DoCreateOffer(&offer, nullptr)); + EXPECT_TRUE(DoSetLocalDescription(std::move(offer))); + + options.srtp.cryptex_policy = CryptoOptions::Srtp::CryptexPolicy::kRequire; + config.crypto_options = options; + + RTCError error = pc_->SetConfiguration(config); + EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, error.type()); + } } // Test that PeerConnection::Close changes the states to closed and all remote
diff --git a/pc/sdp_munging_detector.cc b/pc/sdp_munging_detector.cc index d4e52c5..83d8edb 100644 --- a/pc/sdp_munging_detector.cc +++ b/pc/sdp_munging_detector.cc
@@ -601,6 +601,13 @@ } } + // Validate media level cryptex. + if (last_created_media_description->cryptex() != + media_description_to_set->cryptex()) { + RTC_LOG(LS_WARNING) << "SDP munging: cryptex changed at media level."; + return SdpMungingType::kCryptex; + } + // Validate b= (which does not have an effect in the local description). if (last_created_media_description->bandwidth() != media_description_to_set->bandwidth()) { @@ -700,6 +707,13 @@ } } + // Validate cryptex setting. + if (sdesc->description()->cryptex() != + last_created_desc->description()->cryptex()) { + RTC_LOG(LS_WARNING) << "SDP munging: cryptex changed at session level."; + return SdpMungingType::kCryptex; + } + // TODO: crbug.com/40567530 - this serializes the descriptions back to a SDP // string which is very complex and we not should be be forced to rely on // string equality. @@ -757,6 +771,8 @@ return false; case SdpMungingType::kDataChannelSctpInit: return false; + case SdpMungingType::kCryptex: + return false; default: // Handled below. break;
diff --git a/pc/sdp_munging_detector_unittest.cc b/pc/sdp_munging_detector_unittest.cc index 813a6ad..d63fa74 100644 --- a/pc/sdp_munging_detector_unittest.cc +++ b/pc/sdp_munging_detector_unittest.cc
@@ -27,6 +27,7 @@ #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/candidate.h" #include "api/create_peerconnection_factory.h" +#include "api/crypto/crypto_options.h" #include "api/jsep.h" #include "api/media_types.h" #include "api/peer_connection_interface.h" @@ -1209,6 +1210,49 @@ ElementsAre(Pair(SdpMungingType::kRtpHeaderExtensionModified, 1))); } +TEST_F(SdpMungingTest, CryptexModifiedSession) { + RTCConfiguration config; + config.sdp_semantics = SdpSemantics::kUnifiedPlan; + config.crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + auto pc = CreatePeerConnection(config, ""); + pc->AddVideoTrack("video_track", {}); + + std::unique_ptr<SessionDescriptionInterface> offer = pc->CreateOffer(); + EXPECT_TRUE(offer->description()->cryptex()); + offer->description()->set_cryptex(false); + + RTCError error; + EXPECT_FALSE(pc->SetLocalDescription(std::move(offer), &error)); + EXPECT_THAT( + metrics::Samples("WebRTC.PeerConnection.SdpMunging.Offer.Initial"), + ElementsAre(Pair(SdpMungingType::kCryptex, 1))); +} + +TEST_F(SdpMungingTest, CryptexModifiedMedia) { + RTCConfiguration config; + config.sdp_semantics = SdpSemantics::kUnifiedPlan; + config.crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + auto pc = CreatePeerConnection(config, ""); + pc->AddVideoTrack("video_track", {}); + + std::unique_ptr<SessionDescriptionInterface> offer = pc->CreateOffer(); + auto& contents = offer->description()->contents(); + ASSERT_THAT(contents, SizeIs(1)); + auto* media_description = contents[0].media_description(); + ASSERT_THAT(media_description, Not(IsNull())); + EXPECT_TRUE(media_description->cryptex()); + media_description->set_cryptex_level( + MediaContentDescription::AttributeLevel::kNone); + + RTCError error; + EXPECT_FALSE(pc->SetLocalDescription(std::move(offer), &error)); + EXPECT_THAT( + metrics::Samples("WebRTC.PeerConnection.SdpMunging.Offer.Initial"), + ElementsAre(Pair(SdpMungingType::kCryptex, 1))); +} + TEST_F(SdpMungingTest, PayloadTypeChanged) { auto pc = CreatePeerConnection(); pc->AddAudioTrack("audio_track", {});
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc index 41550e5f..41686f2 100644 --- a/pc/sdp_offer_answer.cc +++ b/pc/sdp_offer_answer.cc
@@ -75,6 +75,7 @@ #include "pc/jsep_transport_controller.h" #include "pc/legacy_stats_collector.h" #include "pc/media_options.h" +#include "pc/media_protocol_names.h" #include "pc/media_session.h" #include "pc/media_stream.h" #include "pc/media_stream_observer.h" @@ -720,6 +721,68 @@ return RTCError::OK(); } +// Check that cryptex is supported (if required) and consistent within the +// bundle groups. +RTCError ValidateCryptex( + const SessionDescription* description, + const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid, + bool required_to_negotiate) { + RTC_DCHECK(description); + + bool session_level_support = description->cryptex(); + for (const ContentInfo& content_info : description->contents()) { + if (content_info.rejected || + !IsRtpProtocol(content_info.media_description()->protocol())) { + continue; + } + const MediaContentDescription* media = content_info.media_description(); + if (required_to_negotiate && !session_level_support && !media->cryptex()) { + return LOG_ERROR(RTCError::InvalidParameter() + << "cryptex required but not negotiated."); + } + + if (media->cryptex_level() == + MediaContentDescription::AttributeLevel::kSession) { + // Session level attributes are implicitly consistent. + continue; + } + + const auto mid = content_info.mid(); + auto it = bundle_groups_by_mid.find(mid); + if (it == bundle_groups_by_mid.end() || it->second == nullptr) { + continue; + } + const ContentGroup* bundle = it->second; + + // Find and compare cryptex at media level of the current content to + // cryptex of the first non-datachannel media content. + const ContentInfo* first_media_content = nullptr; + for (const std::string& name : bundle->content_names()) { + const ContentInfo* content = description->GetContentByName(name); + if (content && content->media_description()->type() != MediaType::DATA) { + first_media_content = content; + break; + } + } + if (!first_media_content) { + return LOG_ERROR(RTCError::InvalidParameter() << "Inconsistent bundle"); + } + // If this content is the first non-datachannel media content in the + // bundle, this is implicitly consistent. + if (first_media_content->mid() == mid) { + continue; + } + if (first_media_content->media_description()->cryptex_level() != + media->cryptex_level()) { + return LOG_ERROR( + RTCError::InvalidParameter() + << "The media section with MID='" << content_info.mid() + << "' is not consistent with the cryptex level of its bundle"); + } + } + return RTCError::OK(); +} + RTCError ValidatePayloadTypes(const SessionDescription& description) { for (const ContentInfo& content : description.contents()) { if (content.type != MediaProtocolType::kRtp) { @@ -1046,7 +1109,7 @@ } // From `rtc_options`, fill parts of `session_options` shared by all generated -// m= sectionss (in other words, nothing that involves a map/array). +// m= sections (in other words, nothing that involves a map/array). void ExtractSharedMediaSessionOptions( const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, MediaSessionOptions* session_options) { @@ -4065,6 +4128,13 @@ } } + error = ValidateCryptex(sdesc->description(), bundle_groups_by_mid, + pc_->GetCryptoOptions().srtp.cryptex_policy == + CryptoOptions::Srtp::CryptexPolicy::kRequire); + if (!error.ok()) { + return error; + } + // Verify ice-ufrag and ice-pwd. if (!VerifyIceUfragPwdPresent(sdesc->description(), bundle_groups_by_mid)) { return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
diff --git a/pc/sdp_offer_answer_unittest.cc b/pc/sdp_offer_answer_unittest.cc index dc3765c..ff06d90 100644 --- a/pc/sdp_offer_answer_unittest.cc +++ b/pc/sdp_offer_answer_unittest.cc
@@ -25,6 +25,7 @@ #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/create_peerconnection_factory.h" +#include "api/crypto/crypto_options.h" #include "api/jsep.h" #include "api/media_types.h" #include "api/peer_connection_interface.h" @@ -3164,4 +3165,310 @@ EXPECT_TRUE(caller_transceiver->stopped()); } +TEST_F(SdpOfferAnswerTest, CryptexOffInOffer) { + CryptoOptions crypto_options; + crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kDisabled; + PeerConnectionInterface::RTCConfiguration config; + config.crypto_options = crypto_options; + auto pc = CreatePeerConnection(config, ""); + + auto offer = pc->CreateOfferAndSetAsLocal(); + EXPECT_FALSE(offer->description()->cryptex()); +} + +TEST_F(SdpOfferAnswerTest, CryptexOffInAnswer) { + CryptoOptions crypto_options; + crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kDisabled; + PeerConnectionInterface::RTCConfiguration config; + config.crypto_options = crypto_options; + auto pc = CreatePeerConnection(config, ""); + + std::string sdp_offer = + "v=0\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=group:BUNDLE 0\r\n" + "a=ice-ufrag:TESTUFRAG\r\n" + "a=ice-pwd:ThisIsATestIcePasswordThatIsLongEnough\r\n" + "a=fingerprint:sha-256 " + "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" + "B5:27:3E:30:B1:7D:69:42\r\n" + "a=cryptex\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:0\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + "a=rtcp-fb:111 transport-cc\r\n"; + auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer); + ASSERT_THAT(offer, NotNull()); + EXPECT_TRUE(pc->SetRemoteDescription(std::move(offer))); + + auto answer = pc->CreateAnswerAndSetAsLocal(); + ASSERT_THAT(answer, NotNull()); + auto* desc = answer->description(); + EXPECT_FALSE(desc->cryptex()); +} + +TEST_F(SdpOfferAnswerTest, CryptexNotOfferedAnswerDeclaresSupport) { + CryptoOptions crypto_options; + crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + PeerConnectionInterface::RTCConfiguration config; + config.crypto_options = crypto_options; + auto pc = CreatePeerConnection(config, ""); + + std::string sdp_offer = + "v=0\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=group:BUNDLE 0\r\n" + "a=ice-ufrag:TESTUFRAG\r\n" + "a=ice-pwd:ThisIsATestIcePasswordThatIsLongEnough\r\n" + "a=fingerprint:sha-256 " + "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" + "B5:27:3E:30:B1:7D:69:42\r\n" + // No a=cryptex at session level. + "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:0\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + // No a=cryptex at media level. + "a=rtpmap:111 opus/48000/2\r\n" + "a=rtcp-fb:111 transport-cc\r\n"; + auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer); + ASSERT_THAT(offer, NotNull()); + EXPECT_TRUE(pc->SetRemoteDescription(std::move(offer))); + + auto answer = pc->CreateAnswerAndSetAsLocal(); + ASSERT_THAT(answer, NotNull()); + auto* desc = answer->description(); + EXPECT_TRUE(desc->cryptex()); + + auto* content = desc->GetContentByName("0"); + ASSERT_THAT(content, NotNull()); + EXPECT_EQ(content->media_description()->cryptex_level(), + MediaContentDescription::AttributeLevel::kSession); +} + +TEST_F(SdpOfferAnswerTest, CryptexSessionLevel) { + CryptoOptions crypto_options; + crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + PeerConnectionInterface::RTCConfiguration config; + config.crypto_options = crypto_options; + auto pc = CreatePeerConnection(config, ""); + + std::string sdp_offer = + "v=0\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=group:BUNDLE 0\r\n" + "a=ice-ufrag:TESTUFRAG\r\n" + "a=ice-pwd:ThisIsATestIcePasswordThatIsLongEnough\r\n" + "a=fingerprint:sha-256 " + "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" + "B5:27:3E:30:B1:7D:69:42\r\n" + "a=cryptex\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:0\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + "a=rtcp-fb:111 transport-cc\r\n"; + + auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer); + ASSERT_THAT(offer, NotNull()); + EXPECT_TRUE(pc->SetRemoteDescription(std::move(offer))); + + auto answer = pc->CreateAnswerAndSetAsLocal(); + ASSERT_THAT(answer, NotNull()); + auto* desc = answer->description(); + EXPECT_TRUE(desc->cryptex()); + + auto* content = desc->GetContentByName("0"); + ASSERT_THAT(content, NotNull()); + EXPECT_TRUE(content->media_description()->cryptex()); + EXPECT_EQ(content->media_description()->cryptex_level(), + MediaContentDescription::AttributeLevel::kSession); +} + +TEST_F(SdpOfferAnswerTest, CryptexMediaLevel) { + CryptoOptions crypto_options; + crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + PeerConnectionInterface::RTCConfiguration config; + config.crypto_options = crypto_options; + auto pc = CreatePeerConnection(config, ""); + + std::string sdp_offer = + "v=0\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=group:BUNDLE 0 1\r\n" + "a=ice-ufrag:TESTUFRAG\r\n" + "a=ice-pwd:ThisIsATestIcePasswordThatIsLongEnough\r\n" + "a=fingerprint:sha-256 " + "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" + "B5:27:3E:30:B1:7D:69:42\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:0\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + "a=cryptex\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + "a=rtcp-fb:111 transport-cc\r\n" + // cryptex is not required for non-rtp content. + "m=application 0 UDP/DTLS/SCTP webrtc-datachannel\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=sctp-port:5000\r\n" + "a=max-message-size:262144\r\n" + "a=mid:1\r\n"; + + auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer); + ASSERT_THAT(offer, NotNull()); + EXPECT_TRUE(pc->SetRemoteDescription(std::move(offer))); + + auto answer = pc->CreateAnswerAndSetAsLocal(); + ASSERT_THAT(answer, NotNull()); + auto* desc = answer->description(); + EXPECT_FALSE(desc->cryptex()); + + auto* content = desc->GetContentByName("0"); + ASSERT_THAT(content, NotNull()); + EXPECT_TRUE(content->media_description()->cryptex()); + EXPECT_EQ(content->media_description()->cryptex_level(), + MediaContentDescription::AttributeLevel::kMedia); +} + +TEST_F(SdpOfferAnswerTest, CryptexBundleInconsistency) { + CryptoOptions crypto_options; + crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + PeerConnectionInterface::RTCConfiguration config; + config.crypto_options = crypto_options; + auto pc = CreatePeerConnection(config, ""); + + // First m-line in the BUNDLE has cryptex, second does not. + std::string sdp_offer = + "v=0\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=group:BUNDLE 0 1\r\n" + "a=ice-ufrag:TESTUFRAG\r\n" + "a=ice-pwd:ThisIsATestIcePasswordThatIsLongEnough\r\n" + "a=fingerprint:sha-256 " + "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" + "B5:27:3E:30:B1:7D:69:42\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:0\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + "a=cryptex\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + "a=rtcp-fb:111 transport-cc\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:1\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + // "a=cryptex\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + "a=rtcp-fb:111 transport-cc\r\n"; + + auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer); + ASSERT_NE(offer, nullptr); + RTCError error; + pc->SetRemoteDescription(std::move(offer), &error); + EXPECT_FALSE(error.ok()); + EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); +} + +TEST_F(SdpOfferAnswerTest, CryptexNegotiated) { + CryptoOptions crypto_options; + crypto_options.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kNegotiate; + PeerConnectionInterface::RTCConfiguration config; + config.crypto_options = crypto_options; + auto pc1 = CreatePeerConnection(config, ""); + pc1->AddAudioTrack("audio_track", {}); + auto pc2 = CreatePeerConnection(config, ""); + + auto offer = pc1->CreateOfferAndSetAsLocal(); + ASSERT_EQ(offer->description()->contents().size(), 1u); + EXPECT_TRUE(pc2->SetRemoteDescription(std::move(offer))); + + auto answer = pc2->CreateAnswerAndSetAsLocal(); + EXPECT_TRUE(answer->description()->cryptex()); +} + +TEST_F(SdpOfferAnswerTest, CryptexRequired) { + CryptoOptions crypto_options1; + crypto_options1.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kRequire; + PeerConnectionInterface::RTCConfiguration config1; + config1.crypto_options = crypto_options1; + auto pc1 = CreatePeerConnection(config1, ""); + pc1->AddAudioTrack("audio_track", {}); + + CryptoOptions crypto_options2; + crypto_options2.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kDisabled; + PeerConnectionInterface::RTCConfiguration config2; + config2.crypto_options = crypto_options2; + auto pc2 = CreatePeerConnection(config2, ""); + + auto offer = pc1->CreateOfferAndSetAsLocal(); + EXPECT_TRUE(pc2->SetRemoteDescription(std::move(offer))); + + auto answer = pc2->CreateAnswerAndSetAsLocal(); + EXPECT_FALSE(answer->description()->cryptex()); + + RTCError error; + pc1->SetRemoteDescription(std::move(answer), &error); + EXPECT_FALSE(error.ok()); + EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); +} + +#ifdef WEBRTC_HAVE_SCTP +TEST_F(SdpOfferAnswerTest, CryptexRequiredDatachannelOnly) { + CryptoOptions crypto_options1; + crypto_options1.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kRequire; + PeerConnectionInterface::RTCConfiguration config1; + config1.crypto_options = crypto_options1; + auto pc1 = CreatePeerConnection(config1, ""); + EXPECT_TRUE(pc1->pc()->CreateDataChannelOrError("dc", nullptr).ok()); + + CryptoOptions crypto_options2; + crypto_options2.srtp.cryptex_policy = + CryptoOptions::Srtp::CryptexPolicy::kDisabled; + PeerConnectionInterface::RTCConfiguration config2; + config2.crypto_options = crypto_options2; + auto pc2 = CreatePeerConnection(config2, ""); + + auto offer = pc1->CreateOfferAndSetAsLocal(); + EXPECT_TRUE(pc2->SetRemoteDescription(std::move(offer))); + + auto answer = pc2->CreateAnswerAndSetAsLocal(); + EXPECT_FALSE(answer->description()->cryptex()); + + RTCError error; + pc1->SetRemoteDescription(std::move(answer), &error); + EXPECT_TRUE(error.ok()); +} +#endif // WEBRTC_HAVE_SCTP + } // namespace webrtc
diff --git a/pc/session_description.cc b/pc/session_description.cc index 784c177..1dcb78f 100644 --- a/pc/session_description.cc +++ b/pc/session_description.cc
@@ -177,11 +177,15 @@ } void SessionDescription::AddContent(ContentInfo&& content) { + // Mixed support on session level overrides setting on media level. if (extmap_allow_mixed()) { - // Mixed support on session level overrides setting on media level. content.media_description()->set_extmap_allow_mixed_level( MediaContentDescription::AttributeLevel::kSession); } + if (cryptex()) { + content.media_description()->set_cryptex_level( + MediaContentDescription::AttributeLevel::kSession); + } contents_.push_back(std::move(content)); }
diff --git a/pc/session_description.h b/pc/session_description.h index bb37f3a..f8c44ec 100644 --- a/pc/session_description.h +++ b/pc/session_description.h
@@ -321,6 +321,18 @@ } } + // Determines if cryptex header extension encryption is supported. + void set_cryptex_level(AttributeLevel level) { + if (level == AttributeLevel::kMedia && + cryptex_level_ == AttributeLevel::kSession) { + // Do not downgrade from session level to media level. + return; + } + cryptex_level_ = level; + } + AttributeLevel cryptex_level() const { return cryptex_level_; } + bool cryptex() const { return cryptex_level_ != AttributeLevel::kNone; } + protected: // TODO(bugs.webrtc.org/15214): move all RTP related things to // RtpMediaDescription that the SCTP content description does @@ -352,6 +364,8 @@ virtual MediaContentDescription* CloneInternal() const = 0; std::vector<Codec> codecs_; + + AttributeLevel cryptex_level_ = AttributeLevel::kNone; }; class RtpMediaContentDescription : public MediaContentDescription {}; @@ -660,6 +674,21 @@ } bool extmap_allow_mixed() const { return extmap_allow_mixed_; } + void set_cryptex(bool supported) { + cryptex_ = supported; + MediaContentDescription::AttributeLevel media_level_setting = + supported ? MediaContentDescription::AttributeLevel::kSession + : MediaContentDescription::AttributeLevel::kNone; + for (auto& content : contents_) { + // Do not set to kNone if the current setting is kMedia. + if (supported || content.media_description()->cryptex_level() != + MediaContentDescription::AttributeLevel::kMedia) { + content.media_description()->set_cryptex_level(media_level_setting); + } + } + } + bool cryptex() const { return cryptex_; } + private: SessionDescription(const SessionDescription&); @@ -668,6 +697,7 @@ ContentGroups content_groups_; int msid_signaling_ = kMsidSignalingMediaSection | kMsidSignalingSemantic; bool extmap_allow_mixed_ = true; + bool cryptex_ = false; }; // Indicates whether a session description was sent by the local client or