Add a flag to actively reset the SRTP parameters

Add a new flag to RtcConfiguration. By setting that flag to true, the
SRTP parameters will be reset whenever the DTLS transports are reset
after every offer/answer negotiation.

The flag is added to Android and Objc wrapper as well.

This should only be used as a workaround for the linked bug, if the
application knows that the other party is affected (for instance,
using a version number).

TBR=sakal@webrtc.org, denicija@webrtc.org

Bug: chromium:835958
Change-Id: I6db025e1c69bf83e1b1908f7df4627430db9920c
Reviewed-on: https://webrtc-review.googlesource.com/83101
Commit-Queue: Zhi Huang <zhihuang@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23587}
diff --git a/api/peerconnectioninterface.h b/api/peerconnectioninterface.h
index ab9f458..757811e 100644
--- a/api/peerconnectioninterface.h
+++ b/api/peerconnectioninterface.h
@@ -577,6 +577,13 @@
     // For all other users, specify kUnifiedPlan.
     SdpSemantics sdp_semantics = SdpSemantics::kPlanB;
 
+    // Actively reset the SRTP parameters whenever the DTLS transports
+    // underneath are reset for every offer/answer negotiation.
+    // This is only intended to be a workaround for crbug.com/835958
+    // WARNING: This would cause RTP/RTCP packets decryption failure if not used
+    // correctly. This flag will be deprecated soon. Do not rely on it.
+    bool active_reset_srtp_params = false;
+
     //
     // Don't forget to update operator== if adding something.
     //
diff --git a/pc/dtlssrtptransport.cc b/pc/dtlssrtptransport.cc
index a452d97..e31b2f5 100644
--- a/pc/dtlssrtptransport.cc
+++ b/pc/dtlssrtptransport.cc
@@ -39,7 +39,10 @@
   // When using DTLS-SRTP, we must reset the SrtpTransport every time the
   // DtlsTransport changes and wait until the DTLS handshake is complete to set
   // the newly negotiated parameters.
-  if (IsSrtpActive() && rtp_dtls_transport != rtp_dtls_transport_) {
+  // If |active_reset_srtp_params_| is true, intentionally reset the SRTP
+  // parameter even though the DtlsTransport may not change.
+  if (IsSrtpActive() && (rtp_dtls_transport != rtp_dtls_transport_ ||
+                         active_reset_srtp_params_)) {
     ResetParams();
   }
 
diff --git a/pc/dtlssrtptransport.h b/pc/dtlssrtptransport.h
index c24034a..a2d7aad 100644
--- a/pc/dtlssrtptransport.h
+++ b/pc/dtlssrtptransport.h
@@ -53,6 +53,12 @@
                     "Set SRTP keys for DTLS-SRTP is not supported.");
   }
 
+  // If |active_reset_srtp_params_| is set to be true, the SRTP parameters will
+  // be reset whenever the DtlsTransports are reset.
+  void SetActiveResetSrtpParams(bool active_reset_srtp_params) {
+    active_reset_srtp_params_ = active_reset_srtp_params;
+  }
+
  private:
   bool IsDtlsActive();
   bool IsDtlsConnected();
@@ -84,6 +90,8 @@
   // The encrypted header extension IDs.
   rtc::Optional<std::vector<int>> send_extension_ids_;
   rtc::Optional<std::vector<int>> recv_extension_ids_;
+
+  bool active_reset_srtp_params_ = false;
 };
 
 }  // namespace webrtc
diff --git a/pc/dtlssrtptransport_unittest.cc b/pc/dtlssrtptransport_unittest.cc
index e1da462..cfe817f 100644
--- a/pc/dtlssrtptransport_unittest.cc
+++ b/pc/dtlssrtptransport_unittest.cc
@@ -510,3 +510,51 @@
   // errors when a packet with a duplicate SRTCP index is received.
   SendRecvRtcpPackets();
 }
+
+// Tests that RTCP packets can be sent and received if both sides actively reset
+// the SRTP parameters with the |active_reset_srtp_params_| flag.
+TEST_F(DtlsSrtpTransportTest, ActivelyResetSrtpParams) {
+  auto rtp_dtls1 = rtc::MakeUnique<FakeDtlsTransport>(
+      "audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
+  auto rtcp_dtls1 = rtc::MakeUnique<FakeDtlsTransport>(
+      "audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
+  auto rtp_dtls2 = rtc::MakeUnique<FakeDtlsTransport>(
+      "audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
+  auto rtcp_dtls2 = rtc::MakeUnique<FakeDtlsTransport>(
+      "audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
+
+  MakeDtlsSrtpTransports(rtp_dtls1.get(), rtcp_dtls1.get(), rtp_dtls2.get(),
+                         rtcp_dtls2.get(), /*rtcp_mux_enabled=*/true);
+  CompleteDtlsHandshake(rtp_dtls1.get(), rtp_dtls2.get());
+  CompleteDtlsHandshake(rtcp_dtls1.get(), rtcp_dtls2.get());
+
+  // Send some RTCP packets, causing the SRTCP index to be incremented.
+  SendRecvRtcpPackets();
+
+  // Only set the |active_reset_srtp_params_| flag to be true one side.
+  dtls_srtp_transport1_->SetActiveResetSrtpParams(true);
+  // Set RTCP transport to null to trigger the SRTP parameters update.
+  dtls_srtp_transport1_->SetDtlsTransports(rtp_dtls1.get(), nullptr);
+  dtls_srtp_transport2_->SetDtlsTransports(rtp_dtls2.get(), nullptr);
+
+  // Sending some RTCP packets.
+  size_t rtcp_len = sizeof(kRtcpReport);
+  size_t packet_size = rtcp_len + 4 + kRtpAuthTagLen;
+  rtc::Buffer rtcp_packet_buffer(packet_size);
+  rtc::CopyOnWriteBuffer rtcp_packet(kRtcpReport, rtcp_len, packet_size);
+  int prev_received_packets = transport_observer2_.rtcp_count();
+  ASSERT_TRUE(dtls_srtp_transport1_->SendRtcpPacket(
+      &rtcp_packet, rtc::PacketOptions(), cricket::PF_SRTP_BYPASS));
+  // The RTCP packet is not exepected to be received because the SRTP parameters
+  // are only reset on one side and the SRTCP index is out of sync.
+  EXPECT_EQ(prev_received_packets, transport_observer2_.rtcp_count());
+
+  // Set the flag to be true on the other side.
+  dtls_srtp_transport2_->SetActiveResetSrtpParams(true);
+  // Set RTCP transport to null to trigger the SRTP parameters update.
+  dtls_srtp_transport1_->SetDtlsTransports(rtp_dtls1.get(), nullptr);
+  dtls_srtp_transport2_->SetDtlsTransports(rtp_dtls2.get(), nullptr);
+
+  // RTCP packets flow is expected to work just fine.
+  SendRecvRtcpPackets();
+}
diff --git a/pc/jseptransport.cc b/pc/jseptransport.cc
index ab785af..adce4af 100644
--- a/pc/jseptransport.cc
+++ b/pc/jseptransport.cc
@@ -330,6 +330,15 @@
                           std::string(desc.str()));
 }
 
+void JsepTransport::SetActiveResetSrtpParams(bool active_reset_srtp_params) {
+  if (dtls_srtp_transport_) {
+    RTC_LOG(INFO)
+        << "Setting active_reset_srtp_params of DtlsSrtpTransport to: "
+        << active_reset_srtp_params;
+    dtls_srtp_transport_->SetActiveResetSrtpParams(active_reset_srtp_params);
+  }
+}
+
 void JsepTransport::SetLocalIceParameters(IceTransportInternal* ice_transport) {
   RTC_DCHECK(ice_transport);
   RTC_DCHECK(local_description_);
diff --git a/pc/jseptransport.h b/pc/jseptransport.h
index 290ac4a..392f861 100644
--- a/pc/jseptransport.h
+++ b/pc/jseptransport.h
@@ -173,6 +173,8 @@
       const rtc::RTCCertificate* certificate,
       const rtc::SSLFingerprint* fingerprint) const;
 
+  void SetActiveResetSrtpParams(bool active_reset_srtp_params);
+
  private:
   bool SetRtcpMux(bool enable, webrtc::SdpType type, ContentSource source);
 
diff --git a/pc/jseptransportcontroller.cc b/pc/jseptransportcontroller.cc
index 2d3eeb9..52519c0 100644
--- a/pc/jseptransportcontroller.cc
+++ b/pc/jseptransportcontroller.cc
@@ -379,6 +379,24 @@
   }
 }
 
+void JsepTransportController::SetActiveResetSrtpParams(
+    bool active_reset_srtp_params) {
+  if (!network_thread_->IsCurrent()) {
+    network_thread_->Invoke<void>(RTC_FROM_HERE, [=] {
+      SetActiveResetSrtpParams(active_reset_srtp_params);
+    });
+    return;
+  }
+
+  RTC_LOG(INFO)
+      << "Updating the active_reset_srtp_params for JsepTransportController: "
+      << active_reset_srtp_params;
+  config_.active_reset_srtp_params = active_reset_srtp_params;
+  for (auto& kv : jsep_transports_by_name_) {
+    kv.second->SetActiveResetSrtpParams(active_reset_srtp_params);
+  }
+}
+
 std::unique_ptr<cricket::DtlsTransportInternal>
 JsepTransportController::CreateDtlsTransport(const std::string& transport_name,
                                              bool rtcp) {
@@ -478,6 +496,8 @@
 
   dtls_srtp_transport->SetDtlsTransports(rtp_dtls_transport,
                                          rtcp_dtls_transport);
+  dtls_srtp_transport->SetActiveResetSrtpParams(
+      config_.active_reset_srtp_params);
   return dtls_srtp_transport;
 }
 
diff --git a/pc/jseptransportcontroller.h b/pc/jseptransportcontroller.h
index dee05fa..b678a09 100644
--- a/pc/jseptransportcontroller.h
+++ b/pc/jseptransportcontroller.h
@@ -78,6 +78,7 @@
     // Used to inject the ICE/DTLS transports created externally.
     cricket::TransportFactoryInterface* external_transport_factory = nullptr;
     Observer* transport_observer = nullptr;
+    bool active_reset_srtp_params = false;
     RtcEventLog* event_log = nullptr;
   };
 
@@ -155,6 +156,8 @@
 
   bool initial_offerer() const { return initial_offerer_ && *initial_offerer_; }
 
+  void SetActiveResetSrtpParams(bool active_reset_srtp_params);
+
   // All of these signals are fired on the signaling thread.
 
   // If any transport failed => failed,
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 093e72d..cf56c35 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -693,6 +693,7 @@
     webrtc::TurnCustomizer* turn_customizer;
     SdpSemantics sdp_semantics;
     rtc::Optional<rtc::AdapterType> network_preference;
+    bool active_reset_srtp_params;
   };
   static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this),
                 "Did you add something to RTCConfiguration and forget to "
@@ -739,7 +740,8 @@
          ice_regather_interval_range == o.ice_regather_interval_range &&
          turn_customizer == o.turn_customizer &&
          sdp_semantics == o.sdp_semantics &&
-         network_preference == o.network_preference;
+         network_preference == o.network_preference &&
+         active_reset_srtp_params == o.active_reset_srtp_params;
 }
 
 bool PeerConnectionInterface::RTCConfiguration::operator!=(
@@ -938,6 +940,7 @@
 #if defined(ENABLE_EXTERNAL_AUTH)
   config.enable_external_auth = true;
 #endif
+  config.active_reset_srtp_params = configuration.active_reset_srtp_params;
   transport_controller_.reset(new JsepTransportController(
       signaling_thread(), network_thread(), port_allocator_.get(), config));
   transport_controller_->SignalIceConnectionState.connect(
@@ -2841,7 +2844,6 @@
 bool PeerConnection::SetConfiguration(const RTCConfiguration& configuration,
                                       RTCError* error) {
   TRACE_EVENT0("webrtc", "PeerConnection::SetConfiguration");
-
   if (IsClosed()) {
     RTC_LOG(LS_ERROR) << "SetConfiguration: PeerConnection is closed.";
     return SafeSetError(RTCErrorType::INVALID_STATE, error);
@@ -2879,6 +2881,8 @@
       configuration.stun_candidate_keepalive_interval;
   modified_config.turn_customizer = configuration.turn_customizer;
   modified_config.network_preference = configuration.network_preference;
+  modified_config.active_reset_srtp_params =
+      configuration.active_reset_srtp_params;
   if (configuration != modified_config) {
     RTC_LOG(LS_ERROR) << "Modifying the configuration in an unsupported way.";
     return SafeSetError(RTCErrorType::INVALID_MODIFICATION, error);
@@ -2937,6 +2941,12 @@
 
   transport_controller_->SetIceConfig(ParseIceConfig(modified_config));
 
+  if (configuration_.active_reset_srtp_params !=
+      modified_config.active_reset_srtp_params) {
+    transport_controller_->SetActiveResetSrtpParams(
+        modified_config.active_reset_srtp_params);
+  }
+
   configuration_ = modified_config;
   return SafeSetError(RTCErrorType::NONE, error);
 }
diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java
index b9e31e1..2f9adcf 100644
--- a/sdk/android/api/org/webrtc/PeerConnection.java
+++ b/sdk/android/api/org/webrtc/PeerConnection.java
@@ -457,6 +457,10 @@
     // This is an optional wrapper for the C++ webrtc::TurnCustomizer.
     @Nullable public TurnCustomizer turnCustomizer;
 
+    // Actively reset the SRTP parameters whenever the DTLS transports underneath are reset for
+    // every offer/answer negotiation.This is only intended to be a workaround for crbug.com/835958
+    public boolean activeResetSrtpParams;
+
     // TODO(deadbeef): Instead of duplicating the defaults here, we should do
     // something to pick up the defaults from C++. The Objective-C equivalent
     // of RTCConfiguration does that.
@@ -495,6 +499,7 @@
       enableDtlsSrtp = null;
       networkPreference = AdapterType.UNKNOWN;
       sdpSemantics = SdpSemantics.PLAN_B;
+      activeResetSrtpParams = false;
     }
 
     @CalledByNative("RTCConfiguration")
@@ -682,6 +687,11 @@
     SdpSemantics getSdpSemantics() {
       return sdpSemantics;
     }
+
+    @CalledByNative("RTCConfiguration")
+    boolean getActiveResetSrtpParams() {
+      return activeResetSrtpParams;
+    }
   };
 
   private final List<MediaStream> localStreams = new ArrayList<>();
diff --git a/sdk/android/src/jni/pc/peerconnection.cc b/sdk/android/src/jni/pc/peerconnection.cc
index 225443f..722a785 100644
--- a/sdk/android/src/jni/pc/peerconnection.cc
+++ b/sdk/android/src/jni/pc/peerconnection.cc
@@ -232,6 +232,8 @@
   rtc_config->network_preference =
       JavaToNativeNetworkPreference(jni, j_network_preference);
   rtc_config->sdp_semantics = JavaToNativeSdpSemantics(jni, j_sdp_semantics);
+  rtc_config->active_reset_srtp_params =
+      Java_RTCConfiguration_getActiveResetSrtpParams(jni, j_rtc_config);
 }
 
 rtc::KeyType GetRtcConfigKeyType(JNIEnv* env,
diff --git a/sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm b/sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm
index e318a9f..4bd0450 100644
--- a/sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm
+++ b/sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm
@@ -45,6 +45,7 @@
 @synthesize iceRegatherIntervalRange = _iceRegatherIntervalRange;
 @synthesize sdpSemantics = _sdpSemantics;
 @synthesize turnCustomizer = _turnCustomizer;
+@synthesize activeResetSrtpParams = _activeResetSrtpParams;
 
 - (instancetype)init {
   // Copy defaults.
@@ -99,6 +100,7 @@
     }
     _sdpSemantics = [[self class] sdpSemanticsForNativeSdpSemantics:config.sdp_semantics];
     _turnCustomizer = config.turn_customizer;
+    _activeResetSrtpParams = config.active_reset_srtp_params;
   }
   return self;
 }
@@ -106,7 +108,7 @@
 - (NSString *)description {
   static NSString *formatString =
       @"RTCConfiguration: "
-      @"{\n%@\n%@\n%@\n%@\n%@\n%@\n%@\n%@\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n%@\n%@\n%d\n%d\n}\n";
+      @"{\n%@\n%@\n%@\n%@\n%@\n%@\n%@\n%@\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n%@\n%@\n%d\n%d\n%d\n}\n";
 
   return [NSString
       stringWithFormat:formatString,
@@ -128,7 +130,8 @@
                        _iceCheckMinInterval,
                        _iceRegatherIntervalRange,
                        _disableLinkLocalNetworks,
-                       _maxIPv6Networks];
+                       _maxIPv6Networks,
+                       _activeResetSrtpParams];
 }
 
 #pragma mark - Private
@@ -194,6 +197,7 @@
   if (_turnCustomizer) {
     nativeConfig->turn_customizer = _turnCustomizer;
   }
+  nativeConfig->active_reset_srtp_params = _activeResetSrtpParams ? true : false;
   return nativeConfig.release();
 }
 
diff --git a/sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h b/sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h
index dd8f364..b203b28 100644
--- a/sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h
+++ b/sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h
@@ -162,6 +162,12 @@
  */
 @property(nonatomic, assign) RTCSdpSemantics sdpSemantics;
 
+/** Actively reset the SRTP parameters when the DTLS transports underneath are
+ *  changed after offer/answer negotiation. This is only intended to be a
+ *  workaround for crbug.com/835958
+ */
+@property(nonatomic, assign) BOOL activeResetSrtpParams;
+
 - (instancetype)init;
 
 @end
diff --git a/sdk/objc/Framework/UnitTests/RTCPeerConnectionTest.mm b/sdk/objc/Framework/UnitTests/RTCPeerConnectionTest.mm
index 686bf2d..63617cb 100644
--- a/sdk/objc/Framework/UnitTests/RTCPeerConnectionTest.mm
+++ b/sdk/objc/Framework/UnitTests/RTCPeerConnectionTest.mm
@@ -49,6 +49,7 @@
   config.continualGatheringPolicy =
       RTCContinualGatheringPolicyGatherContinually;
   config.shouldPruneTurnPorts = YES;
+  config.activeResetSrtpParams = YES;
 
   RTCMediaConstraints *contraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@{}
       optionalConstraints:nil];
@@ -87,6 +88,7 @@
             newConfig.iceBackupCandidatePairPingInterval);
   EXPECT_EQ(config.continualGatheringPolicy, newConfig.continualGatheringPolicy);
   EXPECT_EQ(config.shouldPruneTurnPorts, newConfig.shouldPruneTurnPorts);
+  EXPECT_EQ(config.activeResetSrtpParams, newConfig.activeResetSrtpParams);
 }
 
 @end