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