Add support for GCM cipher suites from RFC 7714.

GCM cipher suites are optional (disabled by default) and can be enabled
through "PeerConnectionFactoryInterface::Options".

If compiled with Chromium (i.e. "ENABLE_EXTERNAL_AUTH" is defined), no
GCM ciphers can be used yet (see https://crbug.com/628400).

BUG=webrtc:5222, 628400

Review-Url: https://codereview.webrtc.org/1528843005
Cr-Commit-Position: refs/heads/master@{#13635}
diff --git a/webrtc/api/peerconnection.cc b/webrtc/api/peerconnection.cc
index e217f80..4ccd6e8 100644
--- a/webrtc/api/peerconnection.cc
+++ b/webrtc/api/peerconnection.cc
@@ -1623,6 +1623,7 @@
   }
 
   session_options->rtcp_cname = rtcp_cname_;
+  session_options->crypto_options = factory_->options().crypto_options;
   return true;
 }
 
@@ -1650,6 +1651,7 @@
   if (session_->data_channel_type() == cricket::DCT_SCTP) {
     session_options->data_channel_type = cricket::DCT_SCTP;
   }
+  session_options->crypto_options = factory_->options().crypto_options;
 }
 
 bool PeerConnection::GetOptionsForAnswer(
diff --git a/webrtc/api/peerconnection_unittest.cc b/webrtc/api/peerconnection_unittest.cc
index 4b06b90..18d3606 100644
--- a/webrtc/api/peerconnection_unittest.cc
+++ b/webrtc/api/peerconnection_unittest.cc
@@ -100,6 +100,7 @@
 // SRTP cipher name negotiated by the tests. This must be updated if the
 // default changes.
 static const int kDefaultSrtpCryptoSuite = rtc::SRTP_AES128_CM_SHA1_32;
+static const int kDefaultSrtpCryptoSuiteGcm = rtc::SRTP_AEAD_AES_256_GCM;
 #endif
 
 static void RemoveLinesFromSdp(const std::string& line_start,
@@ -1364,6 +1365,28 @@
     return true;
   }
 
+  void TestGcmNegotiation(bool local_gcm_enabled, bool remote_gcm_enabled,
+      int expected_cipher_suite) {
+    PeerConnectionFactory::Options init_options;
+    init_options.crypto_options.enable_gcm_crypto_suites = local_gcm_enabled;
+    PeerConnectionFactory::Options recv_options;
+    recv_options.crypto_options.enable_gcm_crypto_suites = remote_gcm_enabled;
+    ASSERT_TRUE(
+        CreateTestClients(nullptr, &init_options, nullptr, &recv_options));
+    rtc::scoped_refptr<webrtc::FakeMetricsObserver>
+        init_observer =
+            new rtc::RefCountedObject<webrtc::FakeMetricsObserver>();
+    initializing_client()->pc()->RegisterUMAObserver(init_observer);
+    LocalP2PTest();
+
+    EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(expected_cipher_suite),
+                   initializing_client()->GetSrtpCipherStats(),
+                   kMaxWaitMs);
+    EXPECT_EQ(1,
+              init_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher,
+                                            expected_cipher_suite));
+  }
+
  private:
   // |ss_| is used by |network_thread_| so it must be destroyed later.
   std::unique_ptr<rtc::PhysicalSocketServer> pss_;
@@ -1814,6 +1837,26 @@
                                           kDefaultSrtpCryptoSuite));
 }
 
+// Test that a non-GCM cipher is used if both sides only support non-GCM.
+TEST_F(P2PTestConductor, GetGcmNone) {
+  TestGcmNegotiation(false, false, kDefaultSrtpCryptoSuite);
+}
+
+// Test that a GCM cipher is used if both ends support it.
+TEST_F(P2PTestConductor, GetGcmBoth) {
+  TestGcmNegotiation(true, true, kDefaultSrtpCryptoSuiteGcm);
+}
+
+// Test that GCM isn't used if only the initiator supports it.
+TEST_F(P2PTestConductor, GetGcmInit) {
+  TestGcmNegotiation(true, false, kDefaultSrtpCryptoSuite);
+}
+
+// Test that GCM isn't used if only the receiver supports it.
+TEST_F(P2PTestConductor, GetGcmRecv) {
+  TestGcmNegotiation(false, true, kDefaultSrtpCryptoSuite);
+}
+
 // This test sets up a call between two parties with audio, video and an RTP
 // data channel.
 TEST_F(P2PTestConductor, LocalP2PTestRtpDataChannel) {
diff --git a/webrtc/api/peerconnectionfactory.cc b/webrtc/api/peerconnectionfactory.cc
index 26ca666..82cd5d4 100644
--- a/webrtc/api/peerconnectionfactory.cc
+++ b/webrtc/api/peerconnectionfactory.cc
@@ -164,6 +164,7 @@
       media_engine, worker_thread_, network_thread_));
 
   channel_manager_->SetVideoRtxEnabled(true);
+  channel_manager_->SetCryptoOptions(options_.crypto_options);
   if (!channel_manager_->Init()) {
     return false;
   }
@@ -171,6 +172,13 @@
   return true;
 }
 
+void PeerConnectionFactory::SetOptions(const Options& options) {
+  options_ = options;
+  if (channel_manager_) {
+    channel_manager_->SetCryptoOptions(options.crypto_options);
+  }
+}
+
 rtc::scoped_refptr<AudioSourceInterface>
 PeerConnectionFactory::CreateAudioSource(
     const MediaConstraintsInterface* constraints) {
diff --git a/webrtc/api/peerconnectionfactory.h b/webrtc/api/peerconnectionfactory.h
index c209fdb..377ad73 100644
--- a/webrtc/api/peerconnectionfactory.h
+++ b/webrtc/api/peerconnectionfactory.h
@@ -31,9 +31,7 @@
 
 class PeerConnectionFactory : public PeerConnectionFactoryInterface {
  public:
-  void SetOptions(const Options& options) override {
-    options_ = options;
-  }
+  void SetOptions(const Options& options) override;
 
   // Deprecated, use version without constraints.
   rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
diff --git a/webrtc/api/peerconnectioninterface.h b/webrtc/api/peerconnectioninterface.h
index 39c4856..e0eb1a4 100644
--- a/webrtc/api/peerconnectioninterface.h
+++ b/webrtc/api/peerconnectioninterface.h
@@ -597,7 +597,8 @@
           disable_sctp_data_channels(false),
           disable_network_monitor(false),
           network_ignore_mask(rtc::kDefaultNetworkIgnoreMask),
-          ssl_max_version(rtc::SSL_PROTOCOL_DTLS_12) {}
+          ssl_max_version(rtc::SSL_PROTOCOL_DTLS_12),
+          crypto_options(rtc::CryptoOptions::NoGcm()) {}
     bool disable_encryption;
     bool disable_sctp_data_channels;
     bool disable_network_monitor;
@@ -611,6 +612,9 @@
     // supported by both ends will be used for the connection, i.e. if one
     // party supports DTLS 1.0 and the other DTLS 1.2, DTLS 1.0 will be used.
     rtc::SSLProtocolVersion ssl_max_version;
+
+    // Sets crypto related options, e.g. enabled cipher suites.
+    rtc::CryptoOptions crypto_options;
   };
 
   virtual void SetOptions(const Options& options) = 0;
diff --git a/webrtc/api/webrtcsession_unittest.cc b/webrtc/api/webrtcsession_unittest.cc
index b96236d..1aff50f 100644
--- a/webrtc/api/webrtcsession_unittest.cc
+++ b/webrtc/api/webrtcsession_unittest.cc
@@ -461,6 +461,14 @@
     Init();
   }
 
+  void InitWithGcm() {
+    rtc::CryptoOptions crypto_options;
+    crypto_options.enable_gcm_crypto_suites = true;
+    channel_manager_->SetCryptoOptions(crypto_options);
+    with_gcm_ = true;
+    Init();
+  }
+
   void SendAudioVideoStream1() {
     send_stream_1_ = true;
     send_stream_2_ = false;
@@ -551,6 +559,10 @@
     if (session_->data_channel_type() == cricket::DCT_SCTP && data_channel_) {
       session_options->data_channel_type = cricket::DCT_SCTP;
     }
+
+    if (with_gcm_) {
+      session_options->crypto_options.enable_gcm_crypto_suites = true;
+    }
   }
 
   void GetOptionsForAnswer(cricket::MediaSessionOptions* session_options) {
@@ -566,6 +578,10 @@
     if (session_->data_channel_type() == cricket::DCT_SCTP) {
       session_options->data_channel_type = cricket::DCT_SCTP;
     }
+
+    if (with_gcm_) {
+      session_options->crypto_options.enable_gcm_crypto_suites = true;
+    }
   }
 
   // Creates a local offer and applies it. Starts ICE.
@@ -628,7 +644,8 @@
             session_->video_channel() != NULL);
   }
 
-  void VerifyCryptoParams(const cricket::SessionDescription* sdp) {
+  void VerifyCryptoParams(const cricket::SessionDescription* sdp,
+      bool gcm_enabled = false) {
     ASSERT_TRUE(session_.get() != NULL);
     const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp);
     ASSERT_TRUE(content != NULL);
@@ -636,12 +653,24 @@
         static_cast<const cricket::AudioContentDescription*>(
             content->description);
     ASSERT_TRUE(audio_content != NULL);
-    ASSERT_EQ(1U, audio_content->cryptos().size());
-    ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
-    ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
-              audio_content->cryptos()[0].cipher_suite);
-    EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
-              audio_content->protocol());
+    if (!gcm_enabled) {
+      ASSERT_EQ(1U, audio_content->cryptos().size());
+      ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+                audio_content->cryptos()[0].cipher_suite);
+      EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+                audio_content->protocol());
+    } else {
+      // The offer contains 3 possible crypto suites, the answer 1.
+      EXPECT_LE(1U, audio_content->cryptos().size());
+      EXPECT_NE(2U, audio_content->cryptos().size());
+      EXPECT_GE(3U, audio_content->cryptos().size());
+      ASSERT_EQ(67U, audio_content->cryptos()[0].key_params.size());
+      ASSERT_EQ("AEAD_AES_256_GCM",
+                audio_content->cryptos()[0].cipher_suite);
+      EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+                audio_content->protocol());
+    }
 
     content = cricket::GetFirstVideoContent(sdp);
     ASSERT_TRUE(content != NULL);
@@ -649,12 +678,24 @@
         static_cast<const cricket::VideoContentDescription*>(
             content->description);
     ASSERT_TRUE(video_content != NULL);
-    ASSERT_EQ(1U, video_content->cryptos().size());
-    ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
-              video_content->cryptos()[0].cipher_suite);
-    ASSERT_EQ(47U, video_content->cryptos()[0].key_params.size());
-    EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
-              video_content->protocol());
+    if (!gcm_enabled) {
+      ASSERT_EQ(1U, video_content->cryptos().size());
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+                video_content->cryptos()[0].cipher_suite);
+      ASSERT_EQ(47U, video_content->cryptos()[0].key_params.size());
+      EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+                video_content->protocol());
+    } else {
+      // The offer contains 3 possible crypto suites, the answer 1.
+      EXPECT_LE(1U, video_content->cryptos().size());
+      EXPECT_NE(2U, video_content->cryptos().size());
+      EXPECT_GE(3U, video_content->cryptos().size());
+      ASSERT_EQ("AEAD_AES_256_GCM",
+                video_content->cryptos()[0].cipher_suite);
+      ASSERT_EQ(67U, video_content->cryptos()[0].key_params.size());
+      EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+                video_content->protocol());
+    }
   }
 
   void VerifyNoCryptoParams(const cricket::SessionDescription* sdp, bool dtls) {
@@ -1470,6 +1511,7 @@
   std::string last_data_channel_label_;
   InternalDataChannelInit last_data_channel_config_;
   bool session_destroyed_ = false;
+  bool with_gcm_ = false;
 };
 
 TEST_P(WebRtcSessionTest, TestInitializeWithDtls) {
@@ -2770,6 +2812,16 @@
   VerifyCryptoParams(answer->description());
 }
 
+TEST_F(WebRtcSessionTest, VerifyCryptoParamsInSDPGcm) {
+  InitWithGcm();
+  SendAudioVideoStream1();
+  std::unique_ptr<SessionDescriptionInterface> offer(CreateOffer());
+  VerifyCryptoParams(offer->description(), true);
+  SetRemoteDescriptionWithoutError(offer.release());
+  std::unique_ptr<SessionDescriptionInterface> answer(CreateAnswer());
+  VerifyCryptoParams(answer->description(), true);
+}
+
 TEST_F(WebRtcSessionTest, VerifyNoCryptoParamsInSDP) {
   options_.disable_encryption = true;
   Init();
@@ -3395,6 +3447,12 @@
   SetLocalDescriptionWithoutError(offer);
 }
 
+TEST_F(WebRtcSessionTest, SetSetupGcm) {
+  InitWithGcm();
+  SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+}
+
 TEST_F(WebRtcSessionTest, CanNotInsertDtmf) {
   TestCanInsertDtmf(false);
 }