From cb56065c62a31d83919abcd4e343ea3dbe029e9f Mon Sep 17 00:00:00 2001 From: jbauch Date: Thu, 4 Aug 2016 05:20:32 -0700 Subject: [PATCH] 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} --- webrtc/api/peerconnection.cc | 2 + webrtc/api/peerconnection_unittest.cc | 43 ++++++++ webrtc/api/peerconnectionfactory.cc | 8 ++ webrtc/api/peerconnectionfactory.h | 4 +- webrtc/api/peerconnectioninterface.h | 6 +- webrtc/api/webrtcsession_unittest.cc | 84 ++++++++++++--- webrtc/base/helpers.cc | 7 ++ webrtc/base/helpers.h | 4 + webrtc/base/helpers_unittest.cc | 24 +++++ webrtc/base/opensslstreamadapter.cc | 2 + webrtc/base/sslstreamadapter.cc | 64 +++++++++++- webrtc/base/sslstreamadapter.h | 34 ++++++ webrtc/base/sslstreamadapter_unittest.cc | 102 +++++++++++++++++- webrtc/pc/channel.cc | 48 +++++---- webrtc/pc/channel.h | 7 ++ webrtc/pc/channel_unittest.cc | 88 ++++++++++++++-- webrtc/pc/channelmanager.cc | 28 +++++ webrtc/pc/channelmanager.h | 6 ++ webrtc/pc/mediasession.cc | 88 +++++++++++----- webrtc/pc/mediasession.h | 21 ++-- webrtc/pc/mediasession_unittest.cc | 128 +++++++++++++++++++++++ webrtc/pc/srtpfilter.cc | 82 +++++++++++---- webrtc/pc/srtpfilter.h | 17 ++- webrtc/pc/srtpfilter_unittest.cc | 37 +++++++ 24 files changed, 811 insertions(+), 123 deletions(-) diff --git a/webrtc/api/peerconnection.cc b/webrtc/api/peerconnection.cc index e217f8089f..4ccd6e83ad 100644 --- a/webrtc/api/peerconnection.cc +++ b/webrtc/api/peerconnection.cc @@ -1623,6 +1623,7 @@ bool PeerConnection::GetOptionsForOffer( } session_options->rtcp_cname = rtcp_cname_; + session_options->crypto_options = factory_->options().crypto_options; return true; } @@ -1650,6 +1651,7 @@ void PeerConnection::FinishOptionsForAnswer( 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 4b06b90d01..18d360692f 100644 --- a/webrtc/api/peerconnection_unittest.cc +++ b/webrtc/api/peerconnection_unittest.cc @@ -100,6 +100,7 @@ static const char kDataChannelLabel[] = "data_channel"; // 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 @@ class P2PTestConductor : public testing::Test { 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 + init_observer = + new rtc::RefCountedObject(); + 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 pss_; @@ -1814,6 +1837,26 @@ TEST_F(P2PTestConductor, GetDtls12Recv) { 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 26ca66604c..82cd5d4f4a 100644 --- a/webrtc/api/peerconnectionfactory.cc +++ b/webrtc/api/peerconnectionfactory.cc @@ -164,6 +164,7 @@ bool PeerConnectionFactory::Initialize() { 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 @@ bool PeerConnectionFactory::Initialize() { return true; } +void PeerConnectionFactory::SetOptions(const Options& options) { + options_ = options; + if (channel_manager_) { + channel_manager_->SetCryptoOptions(options.crypto_options); + } +} + rtc::scoped_refptr PeerConnectionFactory::CreateAudioSource( const MediaConstraintsInterface* constraints) { diff --git a/webrtc/api/peerconnectionfactory.h b/webrtc/api/peerconnectionfactory.h index c209fdbba4..377ad73ec8 100644 --- a/webrtc/api/peerconnectionfactory.h +++ b/webrtc/api/peerconnectionfactory.h @@ -31,9 +31,7 @@ namespace webrtc { 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 CreatePeerConnection( diff --git a/webrtc/api/peerconnectioninterface.h b/webrtc/api/peerconnectioninterface.h index 39c48560d2..e0eb1a46d4 100644 --- a/webrtc/api/peerconnectioninterface.h +++ b/webrtc/api/peerconnectioninterface.h @@ -597,7 +597,8 @@ class PeerConnectionFactoryInterface : public rtc::RefCountInterface { 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 @@ class PeerConnectionFactoryInterface : public rtc::RefCountInterface { // 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 b96236d218..1aff50f1ca 100644 --- a/webrtc/api/webrtcsession_unittest.cc +++ b/webrtc/api/webrtcsession_unittest.cc @@ -461,6 +461,14 @@ class WebRtcSessionTest 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 @@ class WebRtcSessionTest 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 @@ class WebRtcSessionTest 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 @@ class WebRtcSessionTest 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 @@ class WebRtcSessionTest static_cast( 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 @@ class WebRtcSessionTest static_cast( 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 @@ class WebRtcSessionTest 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 @@ TEST_F(WebRtcSessionTest, VerifyCryptoParamsInSDP) { VerifyCryptoParams(answer->description()); } +TEST_F(WebRtcSessionTest, VerifyCryptoParamsInSDPGcm) { + InitWithGcm(); + SendAudioVideoStream1(); + std::unique_ptr offer(CreateOffer()); + VerifyCryptoParams(offer->description(), true); + SetRemoteDescriptionWithoutError(offer.release()); + std::unique_ptr answer(CreateAnswer()); + VerifyCryptoParams(answer->description(), true); +} + TEST_F(WebRtcSessionTest, VerifyNoCryptoParamsInSDP) { options_.disable_encryption = true; Init(); @@ -3395,6 +3447,12 @@ TEST_F(WebRtcSessionTest, TestDisabledRtcpMuxWithBundleEnabled) { SetLocalDescriptionWithoutError(offer); } +TEST_F(WebRtcSessionTest, SetSetupGcm) { + InitWithGcm(); + SendAudioVideoStream1(); + CreateAndSetRemoteOfferAndLocalAnswer(); +} + TEST_F(WebRtcSessionTest, CanNotInsertDtmf) { TestCanInsertDtmf(false); } diff --git a/webrtc/base/helpers.cc b/webrtc/base/helpers.cc index 0a39ee923e..b284cd7a73 100644 --- a/webrtc/base/helpers.cc +++ b/webrtc/base/helpers.cc @@ -245,6 +245,13 @@ bool CreateRandomString(size_t len, const std::string& table, static_cast(table.size()), str); } +bool CreateRandomData(size_t length, std::string* data) { + data->resize(length); + // std::string is guaranteed to use contiguous memory in c++11 so we can + // safely write directly to it. + return Rng().Generate(&data->at(0), length); +} + // Version 4 UUID is of the form: // xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx // Where 'x' is a hex digit, and 'y' is 8, 9, a or b. diff --git a/webrtc/base/helpers.h b/webrtc/base/helpers.h index 0e7937362a..c75dba5985 100644 --- a/webrtc/base/helpers.h +++ b/webrtc/base/helpers.h @@ -39,6 +39,10 @@ bool CreateRandomString(size_t length, std::string* str); bool CreateRandomString(size_t length, const std::string& table, std::string* str); +// Generates (cryptographically) random data of the given length. +// Return false if the random number generator failed. +bool CreateRandomData(size_t length, std::string* data); + // Generates a (cryptographically) random UUID version 4 string. std::string CreateRandomUuid(); diff --git a/webrtc/base/helpers_unittest.cc b/webrtc/base/helpers_unittest.cc index 83cc685919..e4903f5869 100644 --- a/webrtc/base/helpers_unittest.cc +++ b/webrtc/base/helpers_unittest.cc @@ -10,6 +10,7 @@ #include +#include "webrtc/base/buffer.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" #include "webrtc/base/ssladapter.h" @@ -43,6 +44,17 @@ TEST_F(RandomTest, TestCreateRandomString) { EXPECT_EQ(256U, random2.size()); } +TEST_F(RandomTest, TestCreateRandomData) { + static size_t kRandomDataLength = 32; + std::string random1; + std::string random2; + EXPECT_TRUE(CreateRandomData(kRandomDataLength, &random1)); + EXPECT_EQ(kRandomDataLength, random1.size()); + EXPECT_TRUE(CreateRandomData(kRandomDataLength, &random2)); + EXPECT_EQ(kRandomDataLength, random2.size()); + EXPECT_NE(0, memcmp(random1.data(), random2.data(), kRandomDataLength)); +} + TEST_F(RandomTest, TestCreateRandomUuid) { std::string random = CreateRandomUuid(); EXPECT_EQ(36U, random.size()); @@ -54,12 +66,24 @@ TEST_F(RandomTest, TestCreateRandomForTest) { EXPECT_EQ(2154761789U, CreateRandomId()); EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16)); EXPECT_EQ("41706e92-cdd3-46d9-a22d-8ff1737ffb11", CreateRandomUuid()); + static size_t kRandomDataLength = 32; + std::string random; + EXPECT_TRUE(CreateRandomData(kRandomDataLength, &random)); + EXPECT_EQ(kRandomDataLength, random.size()); + Buffer expected("\xbd\x52\x2a\x4b\x97\x93\x2f\x1c" + "\xc4\x72\xab\xa2\x88\x68\x3e\xcc" + "\xa3\x8d\xaf\x13\x3b\xbc\x83\xbb" + "\x16\xf1\xcf\x56\x0c\xf5\x4a\x8b", kRandomDataLength); + EXPECT_EQ(0, memcmp(expected.data(), random.data(), kRandomDataLength)); // Reset and make sure we get the same output. SetRandomTestMode(true); EXPECT_EQ(2154761789U, CreateRandomId()); EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16)); EXPECT_EQ("41706e92-cdd3-46d9-a22d-8ff1737ffb11", CreateRandomUuid()); + EXPECT_TRUE(CreateRandomData(kRandomDataLength, &random)); + EXPECT_EQ(kRandomDataLength, random.size()); + EXPECT_EQ(0, memcmp(expected.data(), random.data(), kRandomDataLength)); // Test different character sets. SetRandomTestMode(true); diff --git a/webrtc/base/opensslstreamadapter.cc b/webrtc/base/opensslstreamadapter.cc index e04eb04d67..89f628d3e8 100644 --- a/webrtc/base/opensslstreamadapter.cc +++ b/webrtc/base/opensslstreamadapter.cc @@ -56,6 +56,8 @@ struct SrtpCipherMapEntry { static SrtpCipherMapEntry SrtpCipherMap[] = { {"SRTP_AES128_CM_SHA1_80", SRTP_AES128_CM_SHA1_80}, {"SRTP_AES128_CM_SHA1_32", SRTP_AES128_CM_SHA1_32}, + {"SRTP_AEAD_AES_128_GCM", SRTP_AEAD_AES_128_GCM}, + {"SRTP_AEAD_AES_256_GCM", SRTP_AEAD_AES_256_GCM}, {nullptr, 0}}; #endif diff --git a/webrtc/base/sslstreamadapter.cc b/webrtc/base/sslstreamadapter.cc index 44158d401f..c34fc90bf5 100644 --- a/webrtc/base/sslstreamadapter.cc +++ b/webrtc/base/sslstreamadapter.cc @@ -25,13 +25,22 @@ namespace rtc { // webrtc:5043. const char CS_AES_CM_128_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80"; const char CS_AES_CM_128_HMAC_SHA1_32[] = "AES_CM_128_HMAC_SHA1_32"; +const char CS_AEAD_AES_128_GCM[] = "AEAD_AES_128_GCM"; +const char CS_AEAD_AES_256_GCM[] = "AEAD_AES_256_GCM"; std::string SrtpCryptoSuiteToName(int crypto_suite) { - if (crypto_suite == SRTP_AES128_CM_SHA1_32) + switch (crypto_suite) { + case SRTP_AES128_CM_SHA1_32: return CS_AES_CM_128_HMAC_SHA1_32; - if (crypto_suite == SRTP_AES128_CM_SHA1_80) + case SRTP_AES128_CM_SHA1_80: return CS_AES_CM_128_HMAC_SHA1_80; - return std::string(); + case SRTP_AEAD_AES_128_GCM: + return CS_AEAD_AES_128_GCM; + case SRTP_AEAD_AES_256_GCM: + return CS_AEAD_AES_256_GCM; + default: + return std::string(); + } } int SrtpCryptoSuiteFromName(const std::string& crypto_suite) { @@ -39,9 +48,58 @@ int SrtpCryptoSuiteFromName(const std::string& crypto_suite) { return SRTP_AES128_CM_SHA1_32; if (crypto_suite == CS_AES_CM_128_HMAC_SHA1_80) return SRTP_AES128_CM_SHA1_80; + if (crypto_suite == CS_AEAD_AES_128_GCM) + return SRTP_AEAD_AES_128_GCM; + if (crypto_suite == CS_AEAD_AES_256_GCM) + return SRTP_AEAD_AES_256_GCM; return SRTP_INVALID_CRYPTO_SUITE; } +bool GetSrtpKeyAndSaltLengths(int crypto_suite, int *key_length, + int *salt_length) { + switch (crypto_suite) { + case SRTP_AES128_CM_SHA1_32: + case SRTP_AES128_CM_SHA1_80: + // SRTP_AES128_CM_HMAC_SHA1_32 and SRTP_AES128_CM_HMAC_SHA1_80 are defined + // in RFC 5764 to use a 128 bits key and 112 bits salt for the cipher. + *key_length = 16; + *salt_length = 14; + break; + case SRTP_AEAD_AES_128_GCM: + // SRTP_AEAD_AES_128_GCM is defined in RFC 7714 to use a 128 bits key and + // a 96 bits salt for the cipher. + *key_length = 16; + *salt_length = 12; + break; + case SRTP_AEAD_AES_256_GCM: + // SRTP_AEAD_AES_256_GCM is defined in RFC 7714 to use a 256 bits key and + // a 96 bits salt for the cipher. + *key_length = 32; + *salt_length = 12; + break; + default: + return false; + } + return true; +} + +bool IsGcmCryptoSuite(int crypto_suite) { + return (crypto_suite == SRTP_AEAD_AES_256_GCM || + crypto_suite == SRTP_AEAD_AES_128_GCM); +} + +bool IsGcmCryptoSuiteName(const std::string& crypto_suite) { + return (crypto_suite == CS_AEAD_AES_256_GCM || + crypto_suite == CS_AEAD_AES_128_GCM); +} + +// static +CryptoOptions CryptoOptions::NoGcm() { + CryptoOptions options; + options.enable_gcm_crypto_suites = false; + return options; +} + SSLStreamAdapter* SSLStreamAdapter::Create(StreamInterface* stream) { #if SSL_USE_OPENSSL return new OpenSSLStreamAdapter(stream); diff --git a/webrtc/base/sslstreamadapter.h b/webrtc/base/sslstreamadapter.h index ba60ce3da0..4dbe457eba 100644 --- a/webrtc/base/sslstreamadapter.h +++ b/webrtc/base/sslstreamadapter.h @@ -31,6 +31,12 @@ const int SRTP_AES128_CM_SHA1_80 = 0x0001; #ifndef SRTP_AES128_CM_SHA1_32 const int SRTP_AES128_CM_SHA1_32 = 0x0002; #endif +#ifndef SRTP_AEAD_AES_128_GCM +const int SRTP_AEAD_AES_128_GCM = 0x0007; +#endif +#ifndef SRTP_AEAD_AES_256_GCM +const int SRTP_AEAD_AES_256_GCM = 0x0008; +#endif // Cipher suite to use for SRTP. Typically a 80-bit HMAC will be used, except // in applications (voice) where the additional bandwidth may be significant. @@ -39,6 +45,10 @@ const int SRTP_AES128_CM_SHA1_32 = 0x0002; extern const char CS_AES_CM_128_HMAC_SHA1_80[]; // 128-bit AES with 32-bit SHA-1 HMAC. extern const char CS_AES_CM_128_HMAC_SHA1_32[]; +// 128-bit AES GCM with 16 byte AEAD auth tag. +extern const char CS_AEAD_AES_128_GCM[]; +// 256-bit AES GCM with 16 byte AEAD auth tag. +extern const char CS_AEAD_AES_256_GCM[]; // Given the DTLS-SRTP protection profile ID, as defined in // https://tools.ietf.org/html/rfc4568#section-6.2 , return the SRTP profile @@ -48,6 +58,30 @@ std::string SrtpCryptoSuiteToName(int crypto_suite); // The reverse of above conversion. int SrtpCryptoSuiteFromName(const std::string& crypto_suite); +// Get key length and salt length for given crypto suite. Returns true for +// valid suites, otherwise false. +bool GetSrtpKeyAndSaltLengths(int crypto_suite, int *key_length, + int *salt_length); + +// Returns true if the given crypto suite id uses a GCM cipher. +bool IsGcmCryptoSuite(int crypto_suite); + +// Returns true if the given crypto suite name uses a GCM cipher. +bool IsGcmCryptoSuiteName(const std::string& crypto_suite); + +struct CryptoOptions { + CryptoOptions() {} + + // Helper method to return an instance of the CryptoOptions with GCM crypto + // suites disabled. This method should be used instead of depending on current + // default values set by the constructor. + static CryptoOptions NoGcm(); + + // 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; +}; + // SSLStreamAdapter : A StreamInterfaceAdapter that does SSL/TLS. // After SSL has been started, the stream will only open on successful // SSL verification of certificates, and the communication is diff --git a/webrtc/base/sslstreamadapter_unittest.cc b/webrtc/base/sslstreamadapter_unittest.cc index dc62ac081b..07ee855458 100644 --- a/webrtc/base/sslstreamadapter_unittest.cc +++ b/webrtc/base/sslstreamadapter_unittest.cc @@ -947,7 +947,6 @@ TEST_P(SSLStreamAdapterTestDTLS, TestDTLSSrtpLow) { ASSERT_EQ(client_cipher, rtc::SRTP_AES128_CM_SHA1_32); }; - // Test DTLS-SRTP with a mismatch -- should not converge TEST_P(SSLStreamAdapterTestDTLS, TestDTLSSrtpHighLow) { MAYBE_SKIP_TEST(HaveDtlsSrtp); @@ -984,6 +983,107 @@ TEST_P(SSLStreamAdapterTestDTLS, TestDTLSSrtpMixed) { ASSERT_EQ(client_cipher, rtc::SRTP_AES128_CM_SHA1_80); }; +// Test DTLS-SRTP with all GCM-128 ciphers. +TEST_P(SSLStreamAdapterTestDTLS, TestDTLSSrtpGCM128) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector gcm128; + gcm128.push_back(rtc::SRTP_AEAD_AES_128_GCM); + SetDtlsSrtpCryptoSuites(gcm128, true); + SetDtlsSrtpCryptoSuites(gcm128, false); + TestHandshake(); + + int client_cipher; + ASSERT_TRUE(GetDtlsSrtpCryptoSuite(true, &client_cipher)); + int server_cipher; + ASSERT_TRUE(GetDtlsSrtpCryptoSuite(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, rtc::SRTP_AEAD_AES_128_GCM); +}; + +// Test DTLS-SRTP with all GCM-256 ciphers. +TEST_P(SSLStreamAdapterTestDTLS, TestDTLSSrtpGCM256) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector gcm256; + gcm256.push_back(rtc::SRTP_AEAD_AES_256_GCM); + SetDtlsSrtpCryptoSuites(gcm256, true); + SetDtlsSrtpCryptoSuites(gcm256, false); + TestHandshake(); + + int client_cipher; + ASSERT_TRUE(GetDtlsSrtpCryptoSuite(true, &client_cipher)); + int server_cipher; + ASSERT_TRUE(GetDtlsSrtpCryptoSuite(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, rtc::SRTP_AEAD_AES_256_GCM); +}; + +// Test DTLS-SRTP with mixed GCM-128/-256 ciphers -- should not converge. +TEST_P(SSLStreamAdapterTestDTLS, TestDTLSSrtpGCMMismatch) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector gcm128; + gcm128.push_back(rtc::SRTP_AEAD_AES_128_GCM); + std::vector gcm256; + gcm256.push_back(rtc::SRTP_AEAD_AES_256_GCM); + SetDtlsSrtpCryptoSuites(gcm128, true); + SetDtlsSrtpCryptoSuites(gcm256, false); + TestHandshake(); + + int client_cipher; + ASSERT_FALSE(GetDtlsSrtpCryptoSuite(true, &client_cipher)); + int server_cipher; + ASSERT_FALSE(GetDtlsSrtpCryptoSuite(false, &server_cipher)); +}; + +// Test DTLS-SRTP with both GCM-128/-256 ciphers -- should select GCM-256. +TEST_P(SSLStreamAdapterTestDTLS, TestDTLSSrtpGCMMixed) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector gcmBoth; + gcmBoth.push_back(rtc::SRTP_AEAD_AES_256_GCM); + gcmBoth.push_back(rtc::SRTP_AEAD_AES_128_GCM); + SetDtlsSrtpCryptoSuites(gcmBoth, true); + SetDtlsSrtpCryptoSuites(gcmBoth, false); + TestHandshake(); + + int client_cipher; + ASSERT_TRUE(GetDtlsSrtpCryptoSuite(true, &client_cipher)); + int server_cipher; + ASSERT_TRUE(GetDtlsSrtpCryptoSuite(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, rtc::SRTP_AEAD_AES_256_GCM); +}; + +// Test SRTP cipher suite lengths. +TEST_P(SSLStreamAdapterTestDTLS, TestDTLSSrtpKeyAndSaltLengths) { + int key_len; + int salt_len; + + ASSERT_FALSE(rtc::GetSrtpKeyAndSaltLengths( + rtc::SRTP_INVALID_CRYPTO_SUITE, &key_len, &salt_len)); + + ASSERT_TRUE(rtc::GetSrtpKeyAndSaltLengths( + rtc::SRTP_AES128_CM_SHA1_32, &key_len, &salt_len)); + ASSERT_EQ(128/8, key_len); + ASSERT_EQ(112/8, salt_len); + + ASSERT_TRUE(rtc::GetSrtpKeyAndSaltLengths( + rtc::SRTP_AES128_CM_SHA1_80, &key_len, &salt_len)); + ASSERT_EQ(128/8, key_len); + ASSERT_EQ(112/8, salt_len); + + ASSERT_TRUE(rtc::GetSrtpKeyAndSaltLengths( + rtc::SRTP_AEAD_AES_128_GCM, &key_len, &salt_len)); + ASSERT_EQ(128/8, key_len); + ASSERT_EQ(96/8, salt_len); + + ASSERT_TRUE(rtc::GetSrtpKeyAndSaltLengths( + rtc::SRTP_AEAD_AES_256_GCM, &key_len, &salt_len)); + ASSERT_EQ(256/8, key_len); + ASSERT_EQ(96/8, salt_len); +}; + // Test an exporter TEST_P(SSLStreamAdapterTestDTLS, TestDTLSExporter) { MAYBE_SKIP_TEST(HaveExporter); diff --git a/webrtc/pc/channel.cc b/webrtc/pc/channel.cc index cde1355d23..e464124bd6 100644 --- a/webrtc/pc/channel.cc +++ b/webrtc/pc/channel.cc @@ -555,6 +555,11 @@ int BaseChannel::SetOption_n(SocketType type, return channel ? channel->SetOption(opt, value) : -1; } +bool BaseChannel::SetCryptoOptions(const rtc::CryptoOptions& crypto_options) { + crypto_options_ = crypto_options; + return true; +} + void BaseChannel::OnWritableState(TransportChannel* channel) { RTC_DCHECK(channel == transport_channel_ || channel == rtcp_transport_channel_); @@ -964,7 +969,7 @@ bool BaseChannel::SetDtlsSrtpCryptoSuites_n(TransportChannel* tc, bool rtcp) { if (!rtcp) { GetSrtpCryptoSuites_n(&crypto_suites); } else { - GetDefaultSrtpCryptoSuites(&crypto_suites); + GetDefaultSrtpCryptoSuites(crypto_options(), &crypto_suites); } return tc->SetSrtpCryptoSuites(crypto_suites); } @@ -996,9 +1001,16 @@ bool BaseChannel::SetupDtlsSrtp_n(bool rtcp_channel) { << content_name() << " " << PacketType(rtcp_channel); + int key_len; + int salt_len; + if (!rtc::GetSrtpKeyAndSaltLengths(selected_crypto_suite, &key_len, + &salt_len)) { + LOG(LS_ERROR) << "Unknown DTLS-SRTP crypto suite" << selected_crypto_suite; + return false; + } + // OK, we're now doing DTLS (RFC 5764) - std::vector dtls_buffer(SRTP_MASTER_KEY_KEY_LEN * 2 + - SRTP_MASTER_KEY_SALT_LEN * 2); + std::vector dtls_buffer(key_len * 2 + salt_len * 2); // RFC 5705 exporter using the RFC 5764 parameters if (!channel->ExportKeyingMaterial( @@ -1011,22 +1023,16 @@ bool BaseChannel::SetupDtlsSrtp_n(bool rtcp_channel) { } // Sync up the keys with the DTLS-SRTP interface - std::vector client_write_key(SRTP_MASTER_KEY_KEY_LEN + - SRTP_MASTER_KEY_SALT_LEN); - std::vector server_write_key(SRTP_MASTER_KEY_KEY_LEN + - SRTP_MASTER_KEY_SALT_LEN); + std::vector client_write_key(key_len + salt_len); + std::vector server_write_key(key_len + salt_len); size_t offset = 0; - memcpy(&client_write_key[0], &dtls_buffer[offset], - SRTP_MASTER_KEY_KEY_LEN); - offset += SRTP_MASTER_KEY_KEY_LEN; - memcpy(&server_write_key[0], &dtls_buffer[offset], - SRTP_MASTER_KEY_KEY_LEN); - offset += SRTP_MASTER_KEY_KEY_LEN; - memcpy(&client_write_key[SRTP_MASTER_KEY_KEY_LEN], - &dtls_buffer[offset], SRTP_MASTER_KEY_SALT_LEN); - offset += SRTP_MASTER_KEY_SALT_LEN; - memcpy(&server_write_key[SRTP_MASTER_KEY_KEY_LEN], - &dtls_buffer[offset], SRTP_MASTER_KEY_SALT_LEN); + memcpy(&client_write_key[0], &dtls_buffer[offset], key_len); + offset += key_len; + memcpy(&server_write_key[0], &dtls_buffer[offset], key_len); + offset += key_len; + memcpy(&client_write_key[key_len], &dtls_buffer[offset], salt_len); + offset += salt_len; + memcpy(&server_write_key[key_len], &dtls_buffer[offset], salt_len); std::vector *send_key, *recv_key; rtc::SSLRole role; @@ -1846,7 +1852,7 @@ void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor* monitor, void VoiceChannel::GetSrtpCryptoSuites_n( std::vector* crypto_suites) const { - GetSupportedAudioCryptoSuites(crypto_suites); + GetSupportedAudioCryptoSuites(crypto_options(), crypto_suites); } VideoChannel::VideoChannel(rtc::Thread* worker_thread, @@ -2107,7 +2113,7 @@ void VideoChannel::OnMediaMonitorUpdate( void VideoChannel::GetSrtpCryptoSuites_n( std::vector* crypto_suites) const { - GetSupportedVideoCryptoSuites(crypto_suites); + GetSupportedVideoCryptoSuites(crypto_options(), crypto_suites); } DataChannel::DataChannel(rtc::Thread* worker_thread, @@ -2420,7 +2426,7 @@ void DataChannel::OnDataChannelReadyToSend(bool writable) { } void DataChannel::GetSrtpCryptoSuites_n(std::vector* crypto_suites) const { - GetSupportedDataCryptoSuites(crypto_suites); + GetSupportedDataCryptoSuites(crypto_options(), crypto_suites); } bool DataChannel::ShouldSetupDtlsSrtp_n() const { diff --git a/webrtc/pc/channel.h b/webrtc/pc/channel.h index 37eee47c79..3c00ee3d4a 100644 --- a/webrtc/pc/channel.h +++ b/webrtc/pc/channel.h @@ -170,6 +170,8 @@ class BaseChannel virtual cricket::MediaType media_type() = 0; + bool SetCryptoOptions(const rtc::CryptoOptions& crypto_options); + protected: virtual MediaChannel* media_channel() const { return media_channel_; } // Sets the |transport_channel_| (and |rtcp_transport_channel_|, if |rtcp_| is @@ -303,6 +305,10 @@ class BaseChannel // From MessageHandler void OnMessage(rtc::Message* pmsg) override; + const rtc::CryptoOptions& crypto_options() const { + return crypto_options_; + } + // Handled in derived classes // Get the SRTP crypto suites to use for RTP media virtual void GetSrtpCryptoSuites_n(std::vector* crypto_suites) const = 0; @@ -351,6 +357,7 @@ class BaseChannel bool has_received_packet_; bool dtls_keyed_; bool secure_required_; + rtc::CryptoOptions crypto_options_; int rtp_abs_sendtime_extn_id_; // MediaChannel related members that should be access from worker thread. diff --git a/webrtc/pc/channel_unittest.cc b/webrtc/pc/channel_unittest.cc index b36dcd131a..7b30547a7e 100644 --- a/webrtc/pc/channel_unittest.cc +++ b/webrtc/pc/channel_unittest.cc @@ -15,6 +15,7 @@ #include "webrtc/base/fakeclock.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" +#include "webrtc/base/sslstreamadapter.h" #include "webrtc/media/base/fakemediaengine.h" #include "webrtc/media/base/fakertp.h" #include "webrtc/media/base/mediachannel.h" @@ -94,7 +95,7 @@ template class ChannelTest : public testing::Test, public sigslot::has_slots<> { public: enum Flags { RTCP = 0x1, RTCP_MUX = 0x2, SECURE = 0x4, SSRC_MUX = 0x8, - DTLS = 0x10 }; + DTLS = 0x10, GCM_CIPHER = 0x20 }; ChannelTest(bool verify_playout, rtc::ArrayView rtp_data, @@ -135,10 +136,10 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { media_channel2_ = ch2; channel1_.reset( CreateChannel(worker_thread, network_thread_, &media_engine_, ch1, - transport_controller1_.get(), (flags1 & RTCP) != 0)); + transport_controller1_.get(), flags1)); channel2_.reset( CreateChannel(worker_thread, network_thread_, &media_engine_, ch2, - transport_controller2_.get(), (flags2 & RTCP) != 0)); + transport_controller2_.get(), flags2)); channel1_->SignalMediaMonitor.connect(this, &ChannelTest::OnMediaMonitor1); channel2_->SignalMediaMonitor.connect(this, @@ -187,10 +188,14 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { cricket::MediaEngineInterface* engine, typename T::MediaChannel* ch, cricket::TransportController* transport_controller, - bool rtcp) { + int flags) { typename T::Channel* channel = new typename T::Channel(worker_thread, network_thread, engine, ch, - transport_controller, cricket::CN_AUDIO, rtcp); + transport_controller, cricket::CN_AUDIO, + (flags & RTCP) != 0); + rtc::CryptoOptions crypto_options; + crypto_options.enable_gcm_crypto_suites = (flags & GCM_CIPHER) != 0; + channel->SetCryptoOptions(crypto_options); if (!channel->Init_w(nullptr)) { delete channel; channel = NULL; @@ -369,6 +374,21 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { bool CheckNoRtcp2() { return media_channel2_->CheckNoRtcp(); } + // Checks that the channel is using GCM iff GCM_CIPHER is set in flags. + // Returns true if so. + bool CheckGcmCipher(typename T::Channel* channel, int flags) { + int suite; + if (!channel->transport_channel()->GetSrtpCryptoSuite(&suite)) { + return false; + } + + if (flags & GCM_CIPHER) { + return rtc::IsGcmCryptoSuite(suite); + } else { + return (suite != rtc::SRTP_INVALID_CRYPTO_SUITE && + !rtc::IsGcmCryptoSuite(suite)); + } + } void CreateContent(int flags, const cricket::AudioCodec& audio_codec, @@ -1289,8 +1309,8 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { // Test that we properly send SRTP with RTCP in both directions. // You can pass in DTLS and/or RTCP_MUX as flags. void SendSrtpToSrtp(int flags1_in = 0, int flags2_in = 0) { - ASSERT((flags1_in & ~(RTCP_MUX | DTLS)) == 0); - ASSERT((flags2_in & ~(RTCP_MUX | DTLS)) == 0); + ASSERT((flags1_in & ~(RTCP_MUX | DTLS | GCM_CIPHER)) == 0); + ASSERT((flags2_in & ~(RTCP_MUX | DTLS | GCM_CIPHER)) == 0); int flags1 = RTCP | SECURE | flags1_in; int flags2 = RTCP | SECURE | flags2_in; @@ -1308,6 +1328,14 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { EXPECT_TRUE(channel2_->secure()); EXPECT_EQ(dtls1 && dtls2, channel1_->secure_dtls()); EXPECT_EQ(dtls1 && dtls2, channel2_->secure_dtls()); + // We can only query the negotiated cipher suite for DTLS-SRTP transport + // channels. + if (dtls1 && dtls2) { + // A GCM cipher is only used if both channels support GCM ciphers. + int common_gcm_flags = flags1 & flags2 & GCM_CIPHER; + EXPECT_TRUE(CheckGcmCipher(channel1_.get(), common_gcm_flags)); + EXPECT_TRUE(CheckGcmCipher(channel2_.get(), common_gcm_flags)); + } SendRtp1(); SendRtp2(); SendRtcp1(); @@ -2034,10 +2062,14 @@ cricket::VideoChannel* ChannelTest::CreateChannel( cricket::MediaEngineInterface* engine, cricket::FakeVideoMediaChannel* ch, cricket::TransportController* transport_controller, - bool rtcp) { + int flags) { cricket::VideoChannel* channel = new cricket::VideoChannel(worker_thread, network_thread, ch, - transport_controller, cricket::CN_VIDEO, rtcp); + transport_controller, cricket::CN_VIDEO, + (flags & RTCP) != 0); + rtc::CryptoOptions crypto_options; + crypto_options.enable_gcm_crypto_suites = (flags & GCM_CIPHER) != 0; + channel->SetCryptoOptions(crypto_options); if (!channel->Init_w(nullptr)) { delete channel; channel = NULL; @@ -2265,6 +2297,21 @@ TEST_F(VoiceChannelSingleThreadTest, SendDtlsSrtpToDtlsSrtp) { Base::SendSrtpToSrtp(DTLS, DTLS); } +TEST_F(VoiceChannelSingleThreadTest, SendDtlsSrtpToDtlsSrtpGcmBoth) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + Base::SendSrtpToSrtp(DTLS | GCM_CIPHER, DTLS | GCM_CIPHER); +} + +TEST_F(VoiceChannelSingleThreadTest, SendDtlsSrtpToDtlsSrtpGcmOne) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + Base::SendSrtpToSrtp(DTLS | GCM_CIPHER, DTLS); +} + +TEST_F(VoiceChannelSingleThreadTest, SendDtlsSrtpToDtlsSrtpGcmTwo) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + Base::SendSrtpToSrtp(DTLS, DTLS | GCM_CIPHER); +} + TEST_F(VoiceChannelSingleThreadTest, SendDtlsSrtpToDtlsSrtpRtcpMux) { MAYBE_SKIP_TEST(HaveDtlsSrtp); Base::SendSrtpToSrtp(DTLS | RTCP_MUX, DTLS | RTCP_MUX); @@ -2595,6 +2642,21 @@ TEST_F(VoiceChannelDoubleThreadTest, SendDtlsSrtpToDtlsSrtp) { Base::SendSrtpToSrtp(DTLS, DTLS); } +TEST_F(VoiceChannelDoubleThreadTest, SendDtlsSrtpToDtlsSrtpGcmBoth) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + Base::SendSrtpToSrtp(DTLS | GCM_CIPHER, DTLS | GCM_CIPHER); +} + +TEST_F(VoiceChannelDoubleThreadTest, SendDtlsSrtpToDtlsSrtpGcmOne) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + Base::SendSrtpToSrtp(DTLS | GCM_CIPHER, DTLS); +} + +TEST_F(VoiceChannelDoubleThreadTest, SendDtlsSrtpToDtlsSrtpGcmTwo) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + Base::SendSrtpToSrtp(DTLS, DTLS | GCM_CIPHER); +} + TEST_F(VoiceChannelDoubleThreadTest, SendDtlsSrtpToDtlsSrtpRtcpMux) { MAYBE_SKIP_TEST(HaveDtlsSrtp); Base::SendSrtpToSrtp(DTLS | RTCP_MUX, DTLS | RTCP_MUX); @@ -3274,10 +3336,14 @@ cricket::DataChannel* ChannelTest::CreateChannel( cricket::MediaEngineInterface* engine, cricket::FakeDataMediaChannel* ch, cricket::TransportController* transport_controller, - bool rtcp) { + int flags) { cricket::DataChannel* channel = new cricket::DataChannel(worker_thread, network_thread, ch, - transport_controller, cricket::CN_DATA, rtcp); + transport_controller, cricket::CN_DATA, + (flags & RTCP) != 0); + rtc::CryptoOptions crypto_options; + crypto_options.enable_gcm_crypto_suites = (flags & GCM_CIPHER) != 0; + channel->SetCryptoOptions(crypto_options); if (!channel->Init_w(nullptr)) { delete channel; channel = NULL; diff --git a/webrtc/pc/channelmanager.cc b/webrtc/pc/channelmanager.cc index c2ce1cc604..06475e9eaa 100644 --- a/webrtc/pc/channelmanager.cc +++ b/webrtc/pc/channelmanager.cc @@ -64,6 +64,7 @@ void ChannelManager::Construct(MediaEngineInterface* me, network_thread_ = network_thread; capturing_ = false; enable_rtx_ = false; + crypto_options_ = rtc::CryptoOptions::NoGcm(); } ChannelManager::~ChannelManager() { @@ -97,6 +98,30 @@ bool ChannelManager::SetVideoRtxEnabled(bool enable) { } } +bool ChannelManager::SetCryptoOptions( + const rtc::CryptoOptions& crypto_options) { + return worker_thread_->Invoke(RTC_FROM_HERE, Bind( + &ChannelManager::SetCryptoOptions_w, this, crypto_options)); +} + +bool ChannelManager::SetCryptoOptions_w( + const rtc::CryptoOptions& crypto_options) { + if (!video_channels_.empty() || !voice_channels_.empty() || + !data_channels_.empty()) { + LOG(LS_WARNING) << "Not changing crypto options in existing channels."; + } + crypto_options_ = crypto_options; +#if defined(ENABLE_EXTERNAL_AUTH) + if (crypto_options_.enable_gcm_crypto_suites) { + // TODO(jbauch): Re-enable once https://crbug.com/628400 is resolved. + crypto_options_.enable_gcm_crypto_suites = false; + LOG(LS_WARNING) << "GCM ciphers are not supported with " << + "ENABLE_EXTERNAL_AUTH and will be disabled."; + } +#endif + return true; +} + void ChannelManager::GetSupportedAudioSendCodecs( std::vector* codecs) const { *codecs = media_engine_->audio_send_codecs(); @@ -218,6 +243,7 @@ VoiceChannel* ChannelManager::CreateVoiceChannel_w( VoiceChannel* voice_channel = new VoiceChannel(worker_thread_, network_thread_, media_engine_.get(), media_channel, transport_controller, content_name, rtcp); + voice_channel->SetCryptoOptions(crypto_options_); if (!voice_channel->Init_w(bundle_transport_name)) { delete voice_channel; return nullptr; @@ -281,6 +307,7 @@ VideoChannel* ChannelManager::CreateVideoChannel_w( VideoChannel* video_channel = new VideoChannel(worker_thread_, network_thread_, media_channel, transport_controller, content_name, rtcp); + video_channel->SetCryptoOptions(crypto_options_); if (!video_channel->Init_w(bundle_transport_name)) { delete video_channel; return NULL; @@ -344,6 +371,7 @@ DataChannel* ChannelManager::CreateDataChannel_w( DataChannel* data_channel = new DataChannel(worker_thread_, network_thread_, media_channel, transport_controller, content_name, rtcp); + data_channel->SetCryptoOptions(crypto_options_); if (!data_channel->Init_w(bundle_transport_name)) { LOG(LS_WARNING) << "Failed to init data channel."; delete data_channel; diff --git a/webrtc/pc/channelmanager.h b/webrtc/pc/channelmanager.h index c6a67dfb07..15a3752c44 100644 --- a/webrtc/pc/channelmanager.h +++ b/webrtc/pc/channelmanager.h @@ -125,6 +125,10 @@ class ChannelManager { // engines will start offering an RTX codec. Must be called before Init(). bool SetVideoRtxEnabled(bool enable); + // Define crypto options to set on newly created channels. Doesn't change + // options on already created channels. + bool SetCryptoOptions(const rtc::CryptoOptions& crypto_options); + // Starts/stops the local microphone and enables polling of the input level. bool capturing() const { return capturing_; } @@ -150,6 +154,7 @@ class ChannelManager { bool InitMediaEngine_w(); void DestructorDeletes_w(); void Terminate_w(); + bool SetCryptoOptions_w(const rtc::CryptoOptions& crypto_options); VoiceChannel* CreateVoiceChannel_w( webrtc::MediaControllerInterface* media_controller, TransportController* transport_controller, @@ -185,6 +190,7 @@ class ChannelManager { DataChannels data_channels_; bool enable_rtx_; + rtc::CryptoOptions crypto_options_; bool capturing_; }; diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc index 937a2c11f2..28da7315dd 100644 --- a/webrtc/pc/mediasession.cc +++ b/webrtc/pc/mediasession.cc @@ -18,6 +18,7 @@ #include #include +#include "webrtc/base/base64.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" #include "webrtc/base/stringutils.h" @@ -36,11 +37,13 @@ static const uint32_t kMaxSctpSid = 1023; namespace { const char kInline[] = "inline:"; -void GetSupportedCryptoSuiteNames(void (*func)(std::vector*), +void GetSupportedCryptoSuiteNames(void (*func)(const rtc::CryptoOptions&, + std::vector*), + const rtc::CryptoOptions& crypto_options, std::vector* names) { #ifdef HAVE_SRTP std::vector crypto_suites; - func(&crypto_suites); + func(crypto_options, &crypto_suites); for (const auto crypto : crypto_suites) { names->push_back(rtc::SrtpCryptoSuiteToName(crypto)); } @@ -107,12 +110,22 @@ static bool IsMediaContentOfType(const ContentInfo* content, static bool CreateCryptoParams(int tag, const std::string& cipher, CryptoParams *out) { - std::string key; - key.reserve(SRTP_MASTER_KEY_BASE64_LEN); - - if (!rtc::CreateRandomString(SRTP_MASTER_KEY_BASE64_LEN, &key)) { + int key_len; + int salt_len; + if (!rtc::GetSrtpKeyAndSaltLengths( + rtc::SrtpCryptoSuiteFromName(cipher), &key_len, &salt_len)) { return false; } + + int master_key_len = key_len + salt_len; + std::string master_key; + if (!rtc::CreateRandomData(master_key_len, &master_key)) { + return false; + } + + RTC_CHECK_EQ(static_cast(master_key_len), master_key.size()); + std::string key = rtc::Base64::Encode(master_key); + out->tag = tag; out->cipher_suite = cipher; out->key_params = kInline; @@ -171,63 +184,80 @@ bool FindMatchingCrypto(const CryptoParamsVec& cryptos, return false; } -// For audio, HMAC 32 is prefered because of the low overhead. -void GetSupportedAudioCryptoSuites(std::vector* crypto_suites) { +// For audio, HMAC 32 is prefered over HMAC 80 because of the low overhead. +void GetSupportedAudioCryptoSuites(const rtc::CryptoOptions& crypto_options, + std::vector* crypto_suites) { #ifdef HAVE_SRTP + if (crypto_options.enable_gcm_crypto_suites) { + crypto_suites->push_back(rtc::SRTP_AEAD_AES_256_GCM); + crypto_suites->push_back(rtc::SRTP_AEAD_AES_128_GCM); + } crypto_suites->push_back(rtc::SRTP_AES128_CM_SHA1_32); crypto_suites->push_back(rtc::SRTP_AES128_CM_SHA1_80); #endif } -void GetSupportedAudioCryptoSuiteNames( +void GetSupportedAudioCryptoSuiteNames(const rtc::CryptoOptions& crypto_options, std::vector* crypto_suite_names) { GetSupportedCryptoSuiteNames(GetSupportedAudioCryptoSuites, - crypto_suite_names); + crypto_options, crypto_suite_names); } -void GetSupportedVideoCryptoSuites(std::vector* crypto_suites) { - GetDefaultSrtpCryptoSuites(crypto_suites); +void GetSupportedVideoCryptoSuites(const rtc::CryptoOptions& crypto_options, + std::vector* crypto_suites) { + GetDefaultSrtpCryptoSuites(crypto_options, crypto_suites); } -void GetSupportedVideoCryptoSuiteNames( +void GetSupportedVideoCryptoSuiteNames(const rtc::CryptoOptions& crypto_options, std::vector* crypto_suite_names) { GetSupportedCryptoSuiteNames(GetSupportedVideoCryptoSuites, - crypto_suite_names); + crypto_options, crypto_suite_names); } -void GetSupportedDataCryptoSuites(std::vector* crypto_suites) { - GetDefaultSrtpCryptoSuites(crypto_suites); +void GetSupportedDataCryptoSuites(const rtc::CryptoOptions& crypto_options, + std::vector* crypto_suites) { + GetDefaultSrtpCryptoSuites(crypto_options, crypto_suites); } -void GetSupportedDataCryptoSuiteNames( +void GetSupportedDataCryptoSuiteNames(const rtc::CryptoOptions& crypto_options, std::vector* crypto_suite_names) { GetSupportedCryptoSuiteNames(GetSupportedDataCryptoSuites, - crypto_suite_names); + crypto_options, crypto_suite_names); } -void GetDefaultSrtpCryptoSuites(std::vector* crypto_suites) { +void GetDefaultSrtpCryptoSuites(const rtc::CryptoOptions& crypto_options, + std::vector* crypto_suites) { #ifdef HAVE_SRTP + if (crypto_options.enable_gcm_crypto_suites) { + crypto_suites->push_back(rtc::SRTP_AEAD_AES_256_GCM); + crypto_suites->push_back(rtc::SRTP_AEAD_AES_128_GCM); + } crypto_suites->push_back(rtc::SRTP_AES128_CM_SHA1_80); #endif } -void GetDefaultSrtpCryptoSuiteNames( +void GetDefaultSrtpCryptoSuiteNames(const rtc::CryptoOptions& crypto_options, std::vector* crypto_suite_names) { - GetSupportedCryptoSuiteNames(GetDefaultSrtpCryptoSuites, crypto_suite_names); + GetSupportedCryptoSuiteNames(GetDefaultSrtpCryptoSuites, + crypto_options, crypto_suite_names); } -// For video support only 80-bit SHA1 HMAC. For audio 32-bit HMAC is -// tolerated unless bundle is enabled because it is low overhead. Pick the -// crypto in the list that is supported. +// Support any GCM cipher (if enabled through options). For video support only +// 80-bit SHA1 HMAC. For audio 32-bit HMAC is tolerated unless bundle is enabled +// because it is low overhead. +// Pick the crypto in the list that is supported. static bool SelectCrypto(const MediaContentDescription* offer, bool bundle, + const rtc::CryptoOptions& crypto_options, CryptoParams *crypto) { bool audio = offer->type() == MEDIA_TYPE_AUDIO; const CryptoParamsVec& cryptos = offer->cryptos(); for (CryptoParamsVec::const_iterator i = cryptos.begin(); i != cryptos.end(); ++i) { - if (rtc::CS_AES_CM_128_HMAC_SHA1_80 == i->cipher_suite || + if ((crypto_options.enable_gcm_crypto_suites && + rtc::IsGcmCryptoSuiteName(i->cipher_suite)) || + rtc::CS_AES_CM_128_HMAC_SHA1_80 == i->cipher_suite || (rtc::CS_AES_CM_128_HMAC_SHA1_32 == i->cipher_suite && audio && !bundle)) { return CreateCryptoParams(i->tag, i->cipher_suite, crypto); @@ -1034,7 +1064,7 @@ static bool CreateMediaContentAnswer( if (sdes_policy != SEC_DISABLED) { CryptoParams crypto; - if (SelectCrypto(offer, bundle_enabled, &crypto)) { + if (SelectCrypto(offer, bundle_enabled, options.crypto_options, &crypto)) { if (current_cryptos) { FindMatchingCrypto(*current_cryptos, crypto, &crypto); } @@ -1672,7 +1702,7 @@ bool MediaSessionDescriptionFactory::AddAudioContentForOffer( std::unique_ptr audio(new AudioContentDescription()); std::vector crypto_suites; - GetSupportedAudioCryptoSuiteNames(&crypto_suites); + GetSupportedAudioCryptoSuiteNames(options.crypto_options, &crypto_suites); if (!CreateMediaContentOffer( options, audio_codecs, @@ -1722,7 +1752,7 @@ bool MediaSessionDescriptionFactory::AddVideoContentForOffer( std::unique_ptr video(new VideoContentDescription()); std::vector crypto_suites; - GetSupportedVideoCryptoSuiteNames(&crypto_suites); + GetSupportedVideoCryptoSuiteNames(options.crypto_options, &crypto_suites); if (!CreateMediaContentOffer( options, video_codecs, @@ -1798,7 +1828,7 @@ bool MediaSessionDescriptionFactory::AddDataContentForOffer( data->set_protocol( secure_transport ? kMediaProtocolDtlsSctp : kMediaProtocolSctp); } else { - GetSupportedDataCryptoSuiteNames(&crypto_suites); + GetSupportedDataCryptoSuiteNames(options.crypto_options, &crypto_suites); } if (!CreateMediaContentOffer( diff --git a/webrtc/pc/mediasession.h b/webrtc/pc/mediasession.h index 34354dcd49..b39a8e5499 100644 --- a/webrtc/pc/mediasession.h +++ b/webrtc/pc/mediasession.h @@ -163,6 +163,7 @@ struct MediaSessionOptions { // content name ("mid") => options. std::map transport_options; std::string rtcp_cname; + rtc::CryptoOptions crypto_options; struct Stream { Stream(MediaType type, @@ -594,17 +595,21 @@ VideoContentDescription* GetFirstVideoContentDescription( DataContentDescription* GetFirstDataContentDescription( SessionDescription* sdesc); -void GetSupportedAudioCryptoSuites(std::vector* crypto_suites); -void GetSupportedVideoCryptoSuites(std::vector* crypto_suites); -void GetSupportedDataCryptoSuites(std::vector* crypto_suites); -void GetDefaultSrtpCryptoSuites(std::vector* crypto_suites); -void GetSupportedAudioCryptoSuiteNames( +void GetSupportedAudioCryptoSuites(const rtc::CryptoOptions& crypto_options, + std::vector* crypto_suites); +void GetSupportedVideoCryptoSuites(const rtc::CryptoOptions& crypto_options, + std::vector* crypto_suites); +void GetSupportedDataCryptoSuites(const rtc::CryptoOptions& crypto_options, + std::vector* crypto_suites); +void GetDefaultSrtpCryptoSuites(const rtc::CryptoOptions& crypto_options, + std::vector* crypto_suites); +void GetSupportedAudioCryptoSuiteNames(const rtc::CryptoOptions& crypto_options, std::vector* crypto_suite_names); -void GetSupportedVideoCryptoSuiteNames( +void GetSupportedVideoCryptoSuiteNames(const rtc::CryptoOptions& crypto_options, std::vector* crypto_suite_names); -void GetSupportedDataCryptoSuiteNames( +void GetSupportedDataCryptoSuiteNames(const rtc::CryptoOptions& crypto_options, std::vector* crypto_suite_names); -void GetDefaultSrtpCryptoSuiteNames( +void GetDefaultSrtpCryptoSuiteNames(const rtc::CryptoOptions& crypto_options, std::vector* crypto_suite_names); } // namespace cricket diff --git a/webrtc/pc/mediasession_unittest.cc b/webrtc/pc/mediasession_unittest.cc index 8ad652643e..281d306c05 100644 --- a/webrtc/pc/mediasession_unittest.cc +++ b/webrtc/pc/mediasession_unittest.cc @@ -73,6 +73,8 @@ using cricket::SEC_ENABLED; using cricket::SEC_REQUIRED; using rtc::CS_AES_CM_128_HMAC_SHA1_32; using rtc::CS_AES_CM_128_HMAC_SHA1_80; +using rtc::CS_AEAD_AES_128_GCM; +using rtc::CS_AEAD_AES_256_GCM; using webrtc::RtpExtension; static const AudioCodec kAudioCodecs1[] = { @@ -453,6 +455,52 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { return true; } + void TestVideoGcmCipher(bool gcm_offer, bool gcm_answer) { + MediaSessionOptions offer_opts; + offer_opts.recv_video = true; + offer_opts.crypto_options.enable_gcm_crypto_suites = gcm_offer; + MediaSessionOptions answer_opts; + answer_opts.recv_video = true; + answer_opts.crypto_options.enable_gcm_crypto_suites = gcm_answer; + f1_.set_secure(SEC_ENABLED); + f2_.set_secure(SEC_ENABLED); + std::unique_ptr offer( + f1_.CreateOffer(offer_opts, NULL)); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), answer_opts, NULL)); + const ContentInfo* ac = answer->GetContentByName("audio"); + const ContentInfo* vc = answer->GetContentByName("video"); + ASSERT_TRUE(ac != NULL); + ASSERT_TRUE(vc != NULL); + EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type); + EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type); + const AudioContentDescription* acd = + static_cast(ac->description); + const VideoContentDescription* vcd = + static_cast(vc->description); + EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); + EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); + EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw + EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux + if (gcm_offer && gcm_answer) { + ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM); + } else { + ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32); + } + EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type()); + EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs()); + EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc + EXPECT_TRUE(vcd->rtcp_mux()); // negotiated rtcp-mux + if (gcm_offer && gcm_answer) { + ASSERT_CRYPTO(vcd, 1U, CS_AEAD_AES_256_GCM); + } else { + ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80); + } + EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol()); + } + protected: MediaSessionDescriptionFactory f1_; MediaSessionDescriptionFactory f2_; @@ -766,6 +814,34 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) { EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol()); } +// Create a typical audio answer with GCM ciphers enabled, and ensure it +// matches what we expect. +TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) { + f1_.set_secure(SEC_ENABLED); + f2_.set_secure(SEC_ENABLED); + MediaSessionOptions options; + options.crypto_options.enable_gcm_crypto_suites = true; + std::unique_ptr offer( + f1_.CreateOffer(options, NULL)); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), options, NULL)); + const ContentInfo* ac = answer->GetContentByName("audio"); + const ContentInfo* vc = answer->GetContentByName("video"); + ASSERT_TRUE(ac != NULL); + ASSERT_TRUE(vc == NULL); + EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type); + const AudioContentDescription* acd = + static_cast(ac->description); + EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); + EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); + EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw + EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux + ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM); + EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol()); +} + // Create a typical video answer, and ensure it matches what we expect. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) { MediaSessionOptions opts; @@ -800,6 +876,24 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) { EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol()); } +// Create a typical video answer with GCM ciphers enabled, and ensure it +// matches what we expect. +TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerGcm) { + TestVideoGcmCipher(true, true); +} + +// Create a typical video answer with GCM ciphers enabled for the offer only, +// and ensure it matches what we expect. +TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerGcmOffer) { + TestVideoGcmCipher(true, false); +} + +// Create a typical video answer with GCM ciphers enabled for the answer only, +// and ensure it matches what we expect. +TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerGcmAnswer) { + TestVideoGcmCipher(false, true); +} + TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswer) { MediaSessionOptions opts; opts.data_channel_type = cricket::DCT_RTP; @@ -833,6 +927,40 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswer) { EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol()); } +TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerGcm) { + MediaSessionOptions opts; + opts.data_channel_type = cricket::DCT_RTP; + opts.crypto_options.enable_gcm_crypto_suites = true; + f1_.set_secure(SEC_ENABLED); + f2_.set_secure(SEC_ENABLED); + std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), opts, NULL)); + const ContentInfo* ac = answer->GetContentByName("audio"); + const ContentInfo* vc = answer->GetContentByName("data"); + ASSERT_TRUE(ac != NULL); + ASSERT_TRUE(vc != NULL); + EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type); + EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type); + const AudioContentDescription* acd = + static_cast(ac->description); + const DataContentDescription* vcd = + static_cast(vc->description); + EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); + EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); + EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw + EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux + ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM); + EXPECT_EQ(MEDIA_TYPE_DATA, vcd->type()); + EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), vcd->codecs()); + EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc + EXPECT_TRUE(vcd->rtcp_mux()); // negotiated rtcp-mux + ASSERT_CRYPTO(vcd, 1U, CS_AEAD_AES_256_GCM); + EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol()); +} + // Verifies that the order of the media contents in the offer is preserved in // the answer. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerContentOrder) { diff --git a/webrtc/pc/srtpfilter.cc b/webrtc/pc/srtpfilter.cc index 60dd4f1c60..9e7cc665f2 100644 --- a/webrtc/pc/srtpfilter.cc +++ b/webrtc/pc/srtpfilter.cc @@ -15,6 +15,7 @@ #include #include "webrtc/base/base64.h" +#include "webrtc/base/buffer.h" #include "webrtc/base/byteorder.h" #include "webrtc/base/checks.h" #include "webrtc/base/common.h" @@ -48,17 +49,10 @@ extern "C" debug_module_t mod_alloc; extern "C" debug_module_t mod_aes_icm; extern "C" debug_module_t mod_aes_hmac; #endif -#else -// SrtpFilter needs that constant. -#define SRTP_MASTER_KEY_LEN 30 #endif // HAVE_SRTP namespace cricket { -const int SRTP_MASTER_KEY_BASE64_LEN = SRTP_MASTER_KEY_LEN * 4 / 3; -const int SRTP_MASTER_KEY_KEY_LEN = 16; -const int SRTP_MASTER_KEY_SALT_LEN = 14; - #ifndef HAVE_SRTP // This helper function is used on systems that don't (yet) have SRTP, @@ -403,19 +397,45 @@ bool SrtpFilter::ApplyParams(const CryptoParams& send_params, // We do not want to reset the ROC if the keys are the same. So just return. return true; } + + int send_suite = rtc::SrtpCryptoSuiteFromName(send_params.cipher_suite); + int recv_suite = rtc::SrtpCryptoSuiteFromName(recv_params.cipher_suite); + if (send_suite == rtc::SRTP_INVALID_CRYPTO_SUITE || + recv_suite == rtc::SRTP_INVALID_CRYPTO_SUITE) { + LOG(LS_WARNING) << "Unknown crypto suite(s) received:" + << " send cipher_suite " << send_params.cipher_suite + << " recv cipher_suite " << recv_params.cipher_suite; + return false; + } + + int send_key_len, send_salt_len; + int recv_key_len, recv_salt_len; + if (!rtc::GetSrtpKeyAndSaltLengths(send_suite, &send_key_len, + &send_salt_len) || + !rtc::GetSrtpKeyAndSaltLengths(recv_suite, &recv_key_len, + &recv_salt_len)) { + LOG(LS_WARNING) << "Could not get lengths for crypto suite(s):" + << " send cipher_suite " << send_params.cipher_suite + << " recv cipher_suite " << recv_params.cipher_suite; + return false; + } + // TODO(juberti): Zero these buffers after use. bool ret; - uint8_t send_key[SRTP_MASTER_KEY_LEN], recv_key[SRTP_MASTER_KEY_LEN]; - ret = (ParseKeyParams(send_params.key_params, send_key, sizeof(send_key)) && - ParseKeyParams(recv_params.key_params, recv_key, sizeof(recv_key))); + rtc::Buffer send_key(send_key_len + send_salt_len); + rtc::Buffer recv_key(recv_key_len + recv_salt_len); + ret = (ParseKeyParams(send_params.key_params, send_key.data(), + send_key.size()) && + ParseKeyParams(recv_params.key_params, recv_key.data(), + recv_key.size())); if (ret) { CreateSrtpSessions(); ret = (send_session_->SetSend( - rtc::SrtpCryptoSuiteFromName(send_params.cipher_suite), send_key, - sizeof(send_key)) && + rtc::SrtpCryptoSuiteFromName(send_params.cipher_suite), + send_key.data(), send_key.size()) && recv_session_->SetRecv( - rtc::SrtpCryptoSuiteFromName(recv_params.cipher_suite), recv_key, - sizeof(recv_key))); + rtc::SrtpCryptoSuiteFromName(recv_params.cipher_suite), + recv_key.data(), recv_key.size())); } if (ret) { LOG(LS_INFO) << "SRTP activated with negotiated parameters:" @@ -442,7 +462,7 @@ bool SrtpFilter::ResetParams() { bool SrtpFilter::ParseKeyParams(const std::string& key_params, uint8_t* key, - int len) { + size_t len) { // example key_params: "inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2" // Fail if key-method is wrong. @@ -453,8 +473,7 @@ bool SrtpFilter::ParseKeyParams(const std::string& key_params, // Fail if base64 decode fails, or the key is the wrong size. std::string key_b64(key_params.substr(7)), key_str; if (!rtc::Base64::Decode(key_b64, rtc::Base64::DO_STRICT, - &key_str, nullptr) || - static_cast(key_str.size()) != len) { + &key_str, nullptr) || key_str.size() != len) { return false; } @@ -488,11 +507,11 @@ SrtpSession::~SrtpSession() { } } -bool SrtpSession::SetSend(int cs, const uint8_t* key, int len) { +bool SrtpSession::SetSend(int cs, const uint8_t* key, size_t len) { return SetKey(ssrc_any_outbound, cs, key, len); } -bool SrtpSession::SetRecv(int cs, const uint8_t* key, int len) { +bool SrtpSession::SetRecv(int cs, const uint8_t* key, size_t len) { return SetKey(ssrc_any_inbound, cs, key, len); } @@ -646,7 +665,7 @@ void SrtpSession::set_signal_silent_time(int signal_silent_time_in_ms) { srtp_stat_->set_signal_silent_time(signal_silent_time_in_ms); } -bool SrtpSession::SetKey(int type, int cs, const uint8_t* key, int len) { +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: " @@ -660,20 +679,39 @@ bool SrtpSession::SetKey(int type, int cs, const uint8_t* key, int len) { srtp_policy_t policy; memset(&policy, 0, sizeof(policy)); - if (cs == rtc::SRTP_AES128_CM_SHA1_80) { crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp); crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); } else if (cs == rtc::SRTP_AES128_CM_SHA1_32) { crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp); // rtp is 32, crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); // rtcp still 80 +#if !defined(ENABLE_EXTERNAL_AUTH) + // TODO(jbauch): Re-enable once https://crbug.com/628400 is resolved. + } else if (cs == rtc::SRTP_AEAD_AES_128_GCM) { + crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp); + crypto_policy_set_aes_gcm_128_16_auth(&policy.rtcp); + } else if (cs == rtc::SRTP_AEAD_AES_256_GCM) { + crypto_policy_set_aes_gcm_256_16_auth(&policy.rtp); + crypto_policy_set_aes_gcm_256_16_auth(&policy.rtcp); +#endif // ENABLE_EXTERNAL_AUTH } else { LOG(LS_WARNING) << "Failed to create SRTP session: unsupported" << " cipher_suite " << cs; return false; } - if (!key || len != SRTP_MASTER_KEY_LEN) { + int expected_key_len; + int expected_salt_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; + return false; + } + + if (!key || + len != static_cast(expected_key_len + expected_salt_len)) { LOG(LS_WARNING) << "Failed to create SRTP session: invalid key"; return false; } diff --git a/webrtc/pc/srtpfilter.h b/webrtc/pc/srtpfilter.h index cde9ad7e09..26a335fbae 100644 --- a/webrtc/pc/srtpfilter.h +++ b/webrtc/pc/srtpfilter.h @@ -33,13 +33,6 @@ struct srtp_policy_t; namespace cricket { -// Key is 128 bits and salt is 112 bits == 30 bytes. B64 bloat => 40 bytes. -extern const int SRTP_MASTER_KEY_BASE64_LEN; - -// Needed for DTLS-SRTP -extern const int SRTP_MASTER_KEY_KEY_LEN; -extern const int SRTP_MASTER_KEY_SALT_LEN; - class SrtpSession; class SrtpStat; @@ -140,7 +133,9 @@ class SrtpFilter { CryptoParams* selected_params); bool ApplyParams(const CryptoParams& send_params, const CryptoParams& recv_params); - static bool ParseKeyParams(const std::string& params, uint8_t* key, int len); + static bool ParseKeyParams(const std::string& params, + uint8_t* key, + size_t len); private: enum State { @@ -185,10 +180,10 @@ 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, int len); + bool SetSend(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, int len); + bool SetRecv(int cs, const uint8_t* key, size_t len); // Encrypts/signs an individual RTP/RTCP packet, in-place. // If an HMAC is used, this will increase the packet size. @@ -218,7 +213,7 @@ class SrtpSession { SignalSrtpError; private: - bool SetKey(int type, int cs, const uint8_t* key, int len); + bool SetKey(int type, int cs, const uint8_t* key, size_t len); // Returns send stream current packet index from srtp db. bool GetSendStreamPacketIndex(void* data, int in_len, int64_t* index); diff --git a/webrtc/pc/srtpfilter_unittest.cc b/webrtc/pc/srtpfilter_unittest.cc index cc5b3e5fb3..cf80bdf2a5 100644 --- a/webrtc/pc/srtpfilter_unittest.cc +++ b/webrtc/pc/srtpfilter_unittest.cc @@ -26,6 +26,8 @@ extern "C" { using rtc::CS_AES_CM_128_HMAC_SHA1_80; using rtc::CS_AES_CM_128_HMAC_SHA1_32; +using rtc::CS_AEAD_AES_128_GCM; +using rtc::CS_AEAD_AES_256_GCM; using cricket::CryptoParams; using cricket::CS_LOCAL; using cricket::CS_REMOTE; @@ -41,10 +43,26 @@ static const std::string kTestKeyParams3 = "inline:1234X19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz"; static const std::string kTestKeyParams4 = "inline:4567QCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR"; +static const std::string kTestKeyParamsGcm1 = + "inline:e166KFlKzJsGW0d5apX+rrI05vxbrvMJEzFI14aTDCa63IRTlLK4iH66uOI="; +static const std::string kTestKeyParamsGcm2 = + "inline:6X0oCd55zfz4VgtOwsuqcFq61275PDYN5uwuu3p7ZUHbfUY2FMpdP4m2PEo="; +static const std::string kTestKeyParamsGcm3 = + "inline:YKlABGZWMgX32xuMotrG0v0T7G83veegaVzubQ=="; +static const std::string kTestKeyParamsGcm4 = + "inline:gJ6tWoUym2v+/F6xjr7xaxiS3QbJJozl3ZD/0A=="; static const cricket::CryptoParams kTestCryptoParams1( 1, "AES_CM_128_HMAC_SHA1_80", kTestKeyParams1, ""); static const cricket::CryptoParams kTestCryptoParams2( 1, "AES_CM_128_HMAC_SHA1_80", kTestKeyParams2, ""); +static const cricket::CryptoParams kTestCryptoParamsGcm1( + 1, "AEAD_AES_256_GCM", kTestKeyParamsGcm1, ""); +static const cricket::CryptoParams kTestCryptoParamsGcm2( + 1, "AEAD_AES_256_GCM", kTestKeyParamsGcm2, ""); +static const cricket::CryptoParams kTestCryptoParamsGcm3( + 1, "AEAD_AES_128_GCM", kTestKeyParamsGcm3, ""); +static const cricket::CryptoParams kTestCryptoParamsGcm4( + 1, "AEAD_AES_128_GCM", kTestKeyParamsGcm4, ""); static int rtp_auth_tag_len(const std::string& cs) { return (cs == CS_AES_CM_128_HMAC_SHA1_32) ? 4 : 10; @@ -133,6 +151,13 @@ TEST_F(SrtpFilterTest, TestGoodSetupOneCipherSuite) { EXPECT_TRUE(f1_.IsActive()); } +TEST_F(SrtpFilterTest, TestGoodSetupOneCipherSuiteGcm) { + EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParamsGcm1), CS_LOCAL)); + EXPECT_FALSE(f1_.IsActive()); + EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParamsGcm2), CS_REMOTE)); + EXPECT_TRUE(f1_.IsActive()); +} + // Test that we can set up things with multiple params. TEST_F(SrtpFilterTest, TestGoodSetupMultipleCipherSuites) { std::vector offer(MakeVector(kTestCryptoParams1)); @@ -148,6 +173,18 @@ TEST_F(SrtpFilterTest, TestGoodSetupMultipleCipherSuites) { EXPECT_TRUE(f1_.IsActive()); } +TEST_F(SrtpFilterTest, TestGoodSetupMultipleCipherSuitesGcm) { + std::vector offer(MakeVector(kTestCryptoParamsGcm1)); + std::vector answer(MakeVector(kTestCryptoParamsGcm3)); + offer.push_back(kTestCryptoParamsGcm4); + offer[1].tag = 2; + answer[0].tag = 2; + EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL)); + EXPECT_FALSE(f1_.IsActive()); + EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE)); + EXPECT_TRUE(f1_.IsActive()); +} + // Test that we handle the cases where crypto is not desired. TEST_F(SrtpFilterTest, TestGoodSetupNoCipherSuites) { std::vector offer, answer;