diff --git a/api/BUILD.gn b/api/BUILD.gn index 007879b066..5b21334fe9 100644 --- a/api/BUILD.gn +++ b/api/BUILD.gn @@ -446,6 +446,23 @@ if (rtc_include_tests) { ] } + rtc_source_set("fake_frame_crypto") { + testonly = true + sources = [ + "test/fake_frame_decryptor.cc", + "test/fake_frame_decryptor.h", + "test/fake_frame_encryptor.cc", + "test/fake_frame_encryptor.h", + ] + deps = [ + ":array_view", + ":libjingle_peerconnection_api", + "..:webrtc_common", + "../rtc_base:checks", + "../rtc_base:rtc_base_approved", + ] + } + rtc_source_set("mock_peerconnectioninterface") { testonly = true sources = [ diff --git a/api/test/fake_frame_decryptor.cc b/api/test/fake_frame_decryptor.cc new file mode 100644 index 0000000000..432664a030 --- /dev/null +++ b/api/test/fake_frame_decryptor.cc @@ -0,0 +1,69 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "api/test/fake_frame_decryptor.h" +#include +#include "rtc_base/checks.h" + +namespace webrtc { + +FakeFrameDecryptor::FakeFrameDecryptor(uint8_t fake_key, + uint8_t expected_postfix_byte) + : fake_key_(fake_key), expected_postfix_byte_(expected_postfix_byte) {} + +int FakeFrameDecryptor::Decrypt(cricket::MediaType media_type, + const std::vector& csrcs, + rtc::ArrayView additional_data, + rtc::ArrayView encrypted_frame, + rtc::ArrayView frame, + size_t* bytes_written) { + if (fail_decryption_) { + return 1; + } + + RTC_CHECK_EQ(frame.size() + 1, encrypted_frame.size()); + for (size_t i = 0; i < frame.size(); i++) { + frame[i] ^= fake_key_; + } + + if (encrypted_frame[frame.size()] != expected_postfix_byte_) { + return 1; + } + + return 0; +} + +size_t FakeFrameDecryptor::GetMaxPlaintextByteSize( + cricket::MediaType media_type, + size_t encrypted_frame_size) { + return encrypted_frame_size - 1; +} + +void FakeFrameDecryptor::SetFakeKey(uint8_t fake_key) { + fake_key_ = fake_key; +} + +uint8_t FakeFrameDecryptor::GetFakeKey() const { + return fake_key_; +} + +void FakeFrameDecryptor::SetExpectedPostfixByte(uint8_t expected_postfix_byte) { + expected_postfix_byte_ = expected_postfix_byte; +} + +uint8_t FakeFrameDecryptor::GetExpectedPostfixByte() const { + return expected_postfix_byte_; +} + +void FakeFrameDecryptor::SetFailDecryption(bool fail_decryption) { + fail_decryption_ = fail_decryption; +} + +} // namespace webrtc diff --git a/api/test/fake_frame_decryptor.h b/api/test/fake_frame_decryptor.h new file mode 100644 index 0000000000..b945def59a --- /dev/null +++ b/api/test/fake_frame_decryptor.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef API_TEST_FAKE_FRAME_DECRYPTOR_H_ +#define API_TEST_FAKE_FRAME_DECRYPTOR_H_ + +#include + +#include "api/crypto/framedecryptorinterface.h" +#include "rtc_base/refcountedobject.h" + +namespace webrtc { + +// The FakeFrameDecryptor is a TEST ONLY fake implementation of the +// FrameDecryptorInterface. It is constructed with a simple single digit key and +// a fixed postfix byte. This is just to validate that the core code works +// as expected. +class FakeFrameDecryptor + : public rtc::RefCountedObject { + public: + // Provide a key (0,255) and some postfix byte (0,255) this should match the + // byte you expect from the FakeFrameEncryptor. + explicit FakeFrameDecryptor(uint8_t fake_key = 1, + uint8_t expected_postfix_byte = 255); + + // FrameDecryptorInterface implementation + int Decrypt(cricket::MediaType media_type, + const std::vector& csrcs, + rtc::ArrayView additional_data, + rtc::ArrayView encrypted_frame, + rtc::ArrayView frame, + size_t* bytes_written) override; + + size_t GetMaxPlaintextByteSize(cricket::MediaType media_type, + size_t encrypted_frame_size) override; + + void SetFakeKey(uint8_t fake_key); + + uint8_t GetFakeKey() const; + + void SetExpectedPostfixByte(uint8_t expected_postfix_byte); + + uint8_t GetExpectedPostfixByte() const; + + void SetFailDecryption(bool fail_decryption); + + private: + uint8_t fake_key_ = 0; + uint8_t expected_postfix_byte_ = 0; + bool fail_decryption_ = false; +}; + +} // namespace webrtc + +#endif // API_TEST_FAKE_FRAME_DECRYPTOR_H_ diff --git a/api/test/fake_frame_encryptor.cc b/api/test/fake_frame_encryptor.cc new file mode 100644 index 0000000000..013058f96a --- /dev/null +++ b/api/test/fake_frame_encryptor.cc @@ -0,0 +1,65 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "api/test/fake_frame_encryptor.h" +#include "rtc_base/checks.h" + +namespace webrtc { +FakeFrameEncryptor::FakeFrameEncryptor(uint8_t fake_key, uint8_t postfix_byte) + : fake_key_(fake_key), postfix_byte_(postfix_byte) {} + +// FrameEncryptorInterface implementation +int FakeFrameEncryptor::Encrypt(cricket::MediaType media_type, + uint32_t ssrc, + rtc::ArrayView additional_data, + rtc::ArrayView frame, + rtc::ArrayView encrypted_frame, + size_t* bytes_written) { + // Useful if you want to test failure cases. + if (fail_encryption_) { + return 1; + } + + RTC_CHECK_EQ(frame.size() + 1, encrypted_frame.size()); + for (size_t i = 0; i < frame.size(); i++) { + encrypted_frame[i] ^= fake_key_; + } + encrypted_frame[frame.size()] = postfix_byte_; + *bytes_written = encrypted_frame.size(); + return 0; +} + +size_t FakeFrameEncryptor::GetMaxCiphertextByteSize( + cricket::MediaType media_type, + size_t frame_size) { + return frame_size + 1; +} + +void FakeFrameEncryptor::SetFakeKey(uint8_t fake_key) { + fake_key_ = fake_key; +} + +uint8_t FakeFrameEncryptor::GetFakeKey() const { + return fake_key_; +} + +void FakeFrameEncryptor::SetPostfixByte(uint8_t postfix_byte) { + postfix_byte_ = postfix_byte; +} + +uint8_t FakeFrameEncryptor::GetPostfixByte() const { + return postfix_byte_; +} + +void FakeFrameEncryptor::SetFailEncryption(bool fail_encryption) { + fail_encryption_ = fail_encryption; +} + +} // namespace webrtc diff --git a/api/test/fake_frame_encryptor.h b/api/test/fake_frame_encryptor.h new file mode 100644 index 0000000000..61ae9383f6 --- /dev/null +++ b/api/test/fake_frame_encryptor.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef API_TEST_FAKE_FRAME_ENCRYPTOR_H_ +#define API_TEST_FAKE_FRAME_ENCRYPTOR_H_ + +#include "api/crypto/frameencryptorinterface.h" +#include "rtc_base/refcountedobject.h" + +namespace webrtc { + +// The FakeFrameEncryptor is a TEST ONLY fake implementation of the +// FrameEncryptorInterface. It is constructed with a simple single digit key and +// a fixed postfix byte. This is just to validate that the core code works +// as expected. +class FakeFrameEncryptor + : public rtc::RefCountedObject { + public: + // Provide a key (0,255) and some postfix byte (0,255). + explicit FakeFrameEncryptor(uint8_t fake_key = 1, uint8_t postfix_byte = 255); + + // FrameEncryptorInterface implementation + int Encrypt(cricket::MediaType media_type, + uint32_t ssrc, + rtc::ArrayView additional_data, + rtc::ArrayView frame, + rtc::ArrayView encrypted_frame, + size_t* bytes_written) override; + + size_t GetMaxCiphertextByteSize(cricket::MediaType media_type, + size_t frame_size) override; + + void SetFakeKey(uint8_t fake_key); + + uint8_t GetFakeKey() const; + + void SetPostfixByte(uint8_t expected_postfix_byte); + + uint8_t GetPostfixByte() const; + + void SetFailEncryption(bool fail_encryption); + + private: + uint8_t fake_key_ = 0; + uint8_t postfix_byte_ = 0; + bool fail_encryption_ = false; +}; + +} // namespace webrtc + +#endif // API_TEST_FAKE_FRAME_ENCRYPTOR_H_ diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc index b9faea5bf8..7e473ed23a 100644 --- a/audio/audio_receive_stream.cc +++ b/audio/audio_receive_stream.cc @@ -76,7 +76,7 @@ std::unique_ptr CreateChannelAndProxy( nullptr /* RtcpRttStats */, event_log, config.rtp.remote_ssrc, config.jitter_buffer_max_packets, config.jitter_buffer_fast_accelerate, config.decoder_factory, - config.codec_pair_id)); + config.codec_pair_id, config.frame_decryptor)); } } // namespace diff --git a/audio/audio_send_stream.cc b/audio/audio_send_stream.cc index 06be261f85..e82264a83c 100644 --- a/audio/audio_send_stream.cc +++ b/audio/audio_send_stream.cc @@ -50,10 +50,12 @@ std::unique_ptr CreateChannelAndProxy( rtc::TaskQueue* worker_queue, ProcessThread* module_process_thread, RtcpRttStats* rtcp_rtt_stats, - RtcEventLog* event_log) { + RtcEventLog* event_log, + FrameEncryptorInterface* frame_encryptor) { return absl::make_unique( absl::make_unique(worker_queue, module_process_thread, - rtcp_rtt_stats, event_log)); + rtcp_rtt_stats, event_log, + frame_encryptor)); } } // namespace @@ -103,7 +105,8 @@ AudioSendStream::AudioSendStream( CreateChannelAndProxy(worker_queue, module_process_thread, rtcp_rtt_stats, - event_log)) {} + event_log, + config.frame_encryptor)) {} AudioSendStream::AudioSendStream( const webrtc::AudioSendStream::Config& config, @@ -227,6 +230,11 @@ void AudioSendStream::ConfigureStream( stream->timed_send_transport_adapter_.get()); } + // Enable the frame encryptor if a new frame encryptor has been provided. + if (first_time || new_config.frame_encryptor != old_config.frame_encryptor) { + channel_proxy->SetFrameEncryptor(new_config.frame_encryptor); + } + const ExtensionIds old_ids = FindExtensionIds(old_config.rtp.extensions); const ExtensionIds new_ids = FindExtensionIds(new_config.rtp.extensions); // Audio level indication diff --git a/audio/audio_send_stream_unittest.cc b/audio/audio_send_stream_unittest.cc index 0ed1ac18f6..0a954f86d6 100644 --- a/audio/audio_send_stream_unittest.cc +++ b/audio/audio_send_stream_unittest.cc @@ -196,6 +196,7 @@ struct ConfigHelper { EXPECT_CALL(*channel_proxy_, SetLocalSSRC(kSsrc)).Times(1); EXPECT_CALL(*channel_proxy_, SetRTCP_CNAME(StrEq(kCName))).Times(1); EXPECT_CALL(*channel_proxy_, SetNACKStatus(true, 10)).Times(1); + EXPECT_CALL(*channel_proxy_, SetFrameEncryptor(nullptr)).Times(1); EXPECT_CALL(*channel_proxy_, SetSendAudioLevelIndicationStatus(true, kAudioLevelId)) .Times(1); diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc index 784e4a7747..26213cc5b6 100644 --- a/audio/channel_receive.cc +++ b/audio/channel_receive.cc @@ -229,7 +229,8 @@ ChannelReceive::ChannelReceive( size_t jitter_buffer_max_packets, bool jitter_buffer_fast_playout, rtc::scoped_refptr decoder_factory, - absl::optional codec_pair_id) + absl::optional codec_pair_id, + FrameDecryptorInterface* frame_decryptor) : event_log_(rtc_event_log), rtp_receive_statistics_( ReceiveStatistics::Create(Clock::GetRealTimeClock())), @@ -245,7 +246,8 @@ ChannelReceive::ChannelReceive( _audioDeviceModulePtr(audio_device_module), _transportPtr(NULL), _outputGain(1.0f), - associated_send_channel_(nullptr) { + associated_send_channel_(nullptr), + frame_decryptor_(frame_decryptor) { RTC_DCHECK(module_process_thread); RTC_DCHECK(audio_device_module); AudioCodingModule::Config acm_config; @@ -425,7 +427,38 @@ bool ChannelReceive::ReceivePacket(const uint8_t* packet, WebRtcRTPHeader webrtc_rtp_header = {}; webrtc_rtp_header.header = header; - const size_t payload_data_length = payload_length - header.paddingLength; + size_t payload_data_length = payload_length - header.paddingLength; + + // E2EE Custom Audio Frame Decryption (This is optional). + // Keep this buffer around for the lifetime of the OnReceivedPayloadData call. + rtc::Buffer decrypted_audio_payload; + if (frame_decryptor_ != nullptr) { + size_t max_plaintext_size = frame_decryptor_->GetMaxPlaintextByteSize( + cricket::MEDIA_TYPE_AUDIO, payload_length); + decrypted_audio_payload.SetSize(max_plaintext_size); + + size_t bytes_written = 0; + std::vector csrcs(header.arrOfCSRCs, + header.arrOfCSRCs + header.numCSRCs); + int decrypt_status = frame_decryptor_->Decrypt( + cricket::MEDIA_TYPE_AUDIO, csrcs, + /*additional_data=*/nullptr, + rtc::ArrayView(payload, payload_data_length), + decrypted_audio_payload, &bytes_written); + + // In this case just interpret the failure as a silent frame. + if (decrypt_status != 0) { + bytes_written = 0; + } + + // Resize the decrypted audio payload to the number of bytes actually + // written. + decrypted_audio_payload.SetSize(bytes_written); + // Update the final payload. + payload = decrypted_audio_payload.data(); + payload_data_length = decrypted_audio_payload.size(); + } + if (payload_data_length == 0) { webrtc_rtp_header.frameType = kEmptyFrame; return OnReceivedPayloadData(nullptr, 0, &webrtc_rtp_header); diff --git a/audio/channel_receive.h b/audio/channel_receive.h index 20198c47fd..2e089b7159 100644 --- a/audio/channel_receive.h +++ b/audio/channel_receive.h @@ -42,6 +42,7 @@ class TimestampWrapAroundHandler; namespace webrtc { class AudioDeviceModule; +class FrameDecryptorInterface; class PacketRouter; class ProcessThread; class RateLimiter; @@ -112,7 +113,8 @@ class ChannelReceive : public RtpData, public Transport { size_t jitter_buffer_max_packets, bool jitter_buffer_fast_playout, rtc::scoped_refptr decoder_factory, - absl::optional codec_pair_id); + absl::optional codec_pair_id, + FrameDecryptorInterface* frame_decryptor); virtual ~ChannelReceive(); void SetSink(AudioSinkInterface* sink); @@ -263,6 +265,9 @@ class ChannelReceive : public RtpData, public Transport { PacketRouter* packet_router_ = nullptr; rtc::ThreadChecker construction_thread_; + + // E2EE Audio Frame Decryption + FrameDecryptorInterface* frame_decryptor_ = nullptr; }; } // namespace voe diff --git a/audio/channel_send.cc b/audio/channel_send.cc index 0c9328f474..d3748b3990 100644 --- a/audio/channel_send.cc +++ b/audio/channel_send.cc @@ -19,6 +19,7 @@ #include "absl/memory/memory.h" #include "api/array_view.h" +#include "api/crypto/frameencryptorinterface.h" #include "audio/utility/audio_frame_operations.h" #include "call/rtp_transport_controller_send_interface.h" #include "logging/rtc_event_log/events/rtc_event_audio_playout.h" @@ -260,6 +261,35 @@ int32_t ChannelSend::SendData(FrameType frameType, _rtpRtcpModule->SetAudioLevel(rms_level_.Average()); } + // E2EE Custom Audio Frame Encryption (This is optional). + // Keep this buffer around for the lifetime of the send call. + rtc::Buffer encrypted_audio_payload; + if (frame_encryptor_ != nullptr) { + // TODO(benwright@webrtc.org) - Allocate enough to always encrypt inline. + // Allocate a buffer to hold the maximum possible encrypted payload. + size_t max_ciphertext_size = frame_encryptor_->GetMaxCiphertextByteSize( + cricket::MEDIA_TYPE_AUDIO, payloadSize); + encrypted_audio_payload.SetSize(max_ciphertext_size); + + // Encrypt the audio payload into the buffer. + size_t bytes_written = 0; + int encrypt_status = frame_encryptor_->Encrypt( + cricket::MEDIA_TYPE_AUDIO, _rtpRtcpModule->SSRC(), + /*additional_data=*/nullptr, + rtc::ArrayView(payloadData, payloadSize), + encrypted_audio_payload, &bytes_written); + if (encrypt_status != 0) { + RTC_DLOG(LS_ERROR) << "Channel::SendData() failed encrypt audio payload: " + << encrypt_status; + return -1; + } + // Resize the buffer to the exact number of bytes actually used. + encrypted_audio_payload.SetSize(bytes_written); + // Rewrite the payloadData and size to the new encrypted payload. + payloadData = encrypted_audio_payload.data(); + payloadSize = encrypted_audio_payload.size(); + } + // Push data from ACM to RTP/RTCP-module to deliver audio frame for // packetization. // This call will trigger Transport::SendPacket() from the RTP/RTCP module. @@ -322,7 +352,8 @@ int ChannelSend::PreferredSampleRate() const { ChannelSend::ChannelSend(rtc::TaskQueue* encoder_queue, ProcessThread* module_process_thread, RtcpRttStats* rtcp_rtt_stats, - RtcEventLog* rtc_event_log) + RtcEventLog* rtc_event_log, + FrameEncryptorInterface* frame_encryptor) : event_log_(rtc_event_log), _timeStamp(0), // This is just an offset, RTP module will add it's own // random offset @@ -342,7 +373,8 @@ ChannelSend::ChannelSend(rtc::TaskQueue* encoder_queue, kMaxRetransmissionWindowMs)), use_twcc_plr_for_ana_( webrtc::field_trial::FindFullName("UseTwccPlrForAna") == "Enabled"), - encoder_queue_(encoder_queue) { + encoder_queue_(encoder_queue), + frame_encryptor_(frame_encryptor) { RTC_DCHECK(module_process_thread); RTC_DCHECK(encoder_queue); audio_coding_.reset(AudioCodingModule::Create(AudioCodingModule::Config())); @@ -949,5 +981,16 @@ int64_t ChannelSend::GetRTT() const { return rtt; } +void ChannelSend::SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor) { + rtc::CritScope cs(&encoder_queue_lock_); + if (encoder_queue_is_active_) { + encoder_queue_->PostTask([this, frame_encryptor]() { + this->frame_encryptor_ = frame_encryptor; + }); + } else { + frame_encryptor_ = frame_encryptor; + } +} + } // namespace voe } // namespace webrtc diff --git a/audio/channel_send.h b/audio/channel_send.h index 4569201038..ef92f8e84e 100644 --- a/audio/channel_send.h +++ b/audio/channel_send.h @@ -37,6 +37,7 @@ class TimestampWrapAroundHandler; namespace webrtc { +class FrameEncryptorInterface; class PacketRouter; class ProcessThread; class RateLimiter; @@ -118,7 +119,8 @@ class ChannelSend ChannelSend(rtc::TaskQueue* encoder_queue, ProcessThread* module_process_thread, RtcpRttStats* rtcp_rtt_stats, - RtcEventLog* rtc_event_log); + RtcEventLog* rtc_event_log, + FrameEncryptorInterface* frame_encryptor); virtual ~ChannelSend(); @@ -222,6 +224,9 @@ class ChannelSend int64_t GetRTT() const; + // E2EE Custom Audio Frame Encryption + void SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor); + private: class ProcessAndEncodeAudioTask; @@ -290,6 +295,9 @@ class ChannelSend rtc::CriticalSection encoder_queue_lock_; bool encoder_queue_is_active_ RTC_GUARDED_BY(encoder_queue_lock_) = false; rtc::TaskQueue* encoder_queue_ = nullptr; + + // E2EE Audio Frame Encryption + FrameEncryptorInterface* frame_encryptor_ = nullptr; }; } // namespace voe diff --git a/audio/channel_send_proxy.cc b/audio/channel_send_proxy.cc index a4d8b690d4..8091bdc671 100644 --- a/audio/channel_send_proxy.cc +++ b/audio/channel_send_proxy.cc @@ -197,5 +197,11 @@ ChannelSend* ChannelSendProxy::GetChannel() const { return channel_.get(); } +void ChannelSendProxy::SetFrameEncryptor( + FrameEncryptorInterface* frame_encryptor) { + RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); + channel_->SetFrameEncryptor(frame_encryptor); +} + } // namespace voe } // namespace webrtc diff --git a/audio/channel_send_proxy.h b/audio/channel_send_proxy.h index 754f9f61fd..1b8b4a02ce 100644 --- a/audio/channel_send_proxy.h +++ b/audio/channel_send_proxy.h @@ -23,6 +23,7 @@ namespace webrtc { +class FrameEncryptorInterface; class RtcpBandwidthObserver; class RtpRtcp; class RtpTransportControllerSendInterface; @@ -84,6 +85,9 @@ class ChannelSendProxy { // Needed by ChannelReceiveProxy::AssociateSendChannel. virtual ChannelSend* GetChannel() const; + // E2EE Custom Audio Frame Encryption (Optional) + virtual void SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor); + private: // Thread checkers document and lock usage of some methods on voe::Channel to // specific threads we know about. The goal is to eventually split up diff --git a/audio/mock_voe_channel_proxy.h b/audio/mock_voe_channel_proxy.h index 0ae3cdcfa1..88a50ea7b0 100644 --- a/audio/mock_voe_channel_proxy.h +++ b/audio/mock_voe_channel_proxy.h @@ -105,6 +105,8 @@ class MockChannelSendProxy : public voe::ChannelSendProxy { void(float recoverable_packet_loss_rate)); MOCK_METHOD0(StartSend, void()); MOCK_METHOD0(StopSend, void()); + MOCK_METHOD1(SetFrameEncryptor, + void(FrameEncryptorInterface* frame_encryptor)); }; } // namespace test } // namespace webrtc diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h index 9595a293da..9c890b1cda 100644 --- a/call/audio_receive_stream.h +++ b/call/audio_receive_stream.h @@ -27,6 +27,7 @@ namespace webrtc { class AudioSinkInterface; +class FrameDecryptorInterface; class AudioReceiveStream { public: @@ -120,6 +121,11 @@ class AudioReceiveStream { rtc::scoped_refptr decoder_factory; absl::optional codec_pair_id; + + // An optional custom frame decryptor that allows the entire frame to be + // decrypted in whatever way the caller choses. This is not required by + // default. + rtc::scoped_refptr frame_decryptor; }; // Reconfigure the stream according to the Configuration. diff --git a/call/audio_send_stream.h b/call/audio_send_stream.h index 60909ae128..61e2531222 100644 --- a/call/audio_send_stream.h +++ b/call/audio_send_stream.h @@ -21,6 +21,7 @@ #include "api/audio_codecs/audio_encoder_factory.h" #include "api/audio_codecs/audio_format.h" #include "api/call/transport.h" +#include "api/crypto/frameencryptorinterface.h" #include "api/rtpparameters.h" #include "call/rtp_config.h" #include "modules/audio_processing/include/audio_processing_statistics.h" @@ -128,6 +129,11 @@ class AudioSendStream { // Track ID as specified during track creation. std::string track_id; + + // An optional custom frame encryptor that allows the entire frame to be + // encryptor in whatever way the caller choses. This is not required by + // default. + rtc::scoped_refptr frame_encryptor; }; virtual ~AudioSendStream() = default; diff --git a/media/base/mediachannel.cc b/media/base/mediachannel.cc index 304d7f8232..cba3be30ed 100644 --- a/media/base/mediachannel.cc +++ b/media/base/mediachannel.cc @@ -15,6 +15,13 @@ namespace cricket { VideoOptions::VideoOptions() = default; VideoOptions::~VideoOptions() = default; +MediaChannel::MediaChannel(const MediaConfig& config) + : enable_dscp_(config.enable_dscp), network_interface_(NULL) {} + +MediaChannel::MediaChannel() : enable_dscp_(false), network_interface_(NULL) {} + +MediaChannel::~MediaChannel() {} + void MediaChannel::SetInterface(NetworkInterface* iface) { rtc::CritScope cs(&network_interface_crit_); network_interface_ = iface; @@ -30,13 +37,15 @@ rtc::DiffServCodePoint MediaChannel::PreferredDscp() const { } void MediaChannel::SetFrameEncryptor( - webrtc::FrameEncryptorInterface* frame_encryptor) { - frame_encryptor_ = frame_encryptor; + uint32_t ssrc, + rtc::scoped_refptr frame_encryptor) { + // Placeholder should be pure virtual once internal supports it. } void MediaChannel::SetFrameDecryptor( - webrtc::FrameDecryptorInterface* frame_decryptor) { - frame_decryptor_ = frame_decryptor; + uint32_t ssrc, + rtc::scoped_refptr frame_decryptor) { + // Placeholder should be pure virtual once internal supports it. } MediaSenderInfo::MediaSenderInfo() = default; diff --git a/media/base/mediachannel.h b/media/base/mediachannel.h index 98b36ac22f..ff3368cf40 100644 --- a/media/base/mediachannel.h +++ b/media/base/mediachannel.h @@ -179,10 +179,9 @@ class MediaChannel : public sigslot::has_slots<> { virtual ~NetworkInterface() {} }; - explicit MediaChannel(const MediaConfig& config) - : enable_dscp_(config.enable_dscp), network_interface_(NULL) {} - MediaChannel() : enable_dscp_(false), network_interface_(NULL) {} - ~MediaChannel() override {} + explicit MediaChannel(const MediaConfig& config); + MediaChannel(); + ~MediaChannel() override; // Sets the abstract interface class for sending RTP/RTCP data. virtual void SetInterface(NetworkInterface* iface); @@ -219,13 +218,17 @@ class MediaChannel : public sigslot::has_slots<> { // Set the frame encryptor to use on all outgoing frames. This is optional. // This pointers lifetime is managed by the set of RtpSender it is attached // to. + // TODO(benwright) make pure virtual once internal supports it. virtual void SetFrameEncryptor( - webrtc::FrameEncryptorInterface* frame_encryptor); + uint32_t ssrc, + rtc::scoped_refptr frame_encryptor); // Set the frame decryptor to use on all incoming frames. This is optional. // This pointers lifetimes is managed by the set of RtpReceivers it is // attached to. + // TODO(benwright) make pure virtual once internal supports it. virtual void SetFrameDecryptor( - webrtc::FrameDecryptorInterface* frame_decryptor); + uint32_t ssrc, + rtc::scoped_refptr frame_decryptor); // Base method to send packet using NetworkInterface. bool SendPacket(rtc::CopyOnWriteBuffer* packet, @@ -281,10 +284,6 @@ class MediaChannel : public sigslot::has_slots<> { // of network_interface_ object. rtc::CriticalSection network_interface_crit_; NetworkInterface* network_interface_; - - protected: - webrtc::FrameEncryptorInterface* frame_encryptor_ = nullptr; - webrtc::FrameDecryptorInterface* frame_decryptor_ = nullptr; }; // The stats information is structured as follows: diff --git a/media/engine/webrtcvoiceengine.cc b/media/engine/webrtcvoiceengine.cc index bbe48a2f5a..6b14c23d01 100644 --- a/media/engine/webrtcvoiceengine.cc +++ b/media/engine/webrtcvoiceengine.cc @@ -719,7 +719,8 @@ class WebRtcVoiceMediaChannel::WebRtcAudioSendStream webrtc::Call* call, webrtc::Transport* send_transport, const rtc::scoped_refptr& encoder_factory, - const absl::optional codec_pair_id) + const absl::optional codec_pair_id, + rtc::scoped_refptr frame_encryptor) : call_(call), config_(send_transport), send_side_bwe_with_overhead_( @@ -736,6 +737,7 @@ class WebRtcVoiceMediaChannel::WebRtcAudioSendStream config_.encoder_factory = encoder_factory; config_.codec_pair_id = codec_pair_id; config_.track_id = track_id; + config_.frame_encryptor = frame_encryptor; rtp_parameters_.encodings[0].ssrc = ssrc; rtp_parameters_.rtcp.cname = c_name; rtp_parameters_.header_extensions = extensions; @@ -775,6 +777,13 @@ class WebRtcVoiceMediaChannel::WebRtcAudioSendStream ReconfigureAudioSendStream(); } + void SetFrameEncryptor( + rtc::scoped_refptr frame_encryptor) { + RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); + config_.frame_encryptor = frame_encryptor; + ReconfigureAudioSendStream(); + } + void SetAudioNetworkAdaptorConfig( const absl::optional& audio_network_adaptor_config) { RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); @@ -1072,7 +1081,8 @@ class WebRtcVoiceMediaChannel::WebRtcAudioReceiveStream { const std::map& decoder_map, absl::optional codec_pair_id, size_t jitter_buffer_max_packets, - bool jitter_buffer_fast_accelerate) + bool jitter_buffer_fast_accelerate, + rtc::scoped_refptr frame_decryptor) : call_(call), config_() { RTC_DCHECK(call); config_.rtp.remote_ssrc = remote_ssrc; @@ -1089,6 +1099,7 @@ class WebRtcVoiceMediaChannel::WebRtcAudioReceiveStream { config_.decoder_factory = decoder_factory; config_.decoder_map = decoder_map; config_.codec_pair_id = codec_pair_id; + config_.frame_decryptor = frame_decryptor; RecreateAudioReceiveStream(); } @@ -1097,6 +1108,13 @@ class WebRtcVoiceMediaChannel::WebRtcAudioReceiveStream { call_->DestroyAudioReceiveStream(stream_); } + void SetFrameDecryptor( + rtc::scoped_refptr frame_decryptor) { + RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); + config_.frame_decryptor = frame_decryptor; + RecreateAudioReceiveStream(); + } + void SetLocalSsrc(uint32_t local_ssrc) { RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); config_.rtp.local_ssrc = local_ssrc; @@ -1745,7 +1763,7 @@ bool WebRtcVoiceMediaChannel::AddSendStream(const StreamParams& sp) { WebRtcAudioSendStream* stream = new WebRtcAudioSendStream( ssrc, mid_, sp.cname, sp.id, send_codec_spec_, send_rtp_extensions_, max_send_bitrate_bps_, audio_network_adaptor_config, call_, this, - engine()->encoder_factory_, codec_pair_id_); + engine()->encoder_factory_, codec_pair_id_, nullptr); send_streams_.insert(std::make_pair(ssrc, stream)); // At this point the stream's local SSRC has been updated. If it is the first @@ -1831,7 +1849,8 @@ bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) { recv_nack_enabled_, sp.stream_ids(), recv_rtp_extensions_, call_, this, engine()->decoder_factory_, decoder_map_, codec_pair_id_, engine()->audio_jitter_buffer_max_packets_, - engine()->audio_jitter_buffer_fast_accelerate_))); + engine()->audio_jitter_buffer_fast_accelerate_, + unsignaled_frame_decryptor_))); recv_streams_[ssrc]->SetPlayout(playout_); return true; @@ -1912,6 +1931,30 @@ bool WebRtcVoiceMediaChannel::CanInsertDtmf() { return dtmf_payload_type_.has_value() && send_; } +void WebRtcVoiceMediaChannel::SetFrameDecryptor( + uint32_t ssrc, + rtc::scoped_refptr frame_decryptor) { + RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); + auto matching_stream = recv_streams_.find(ssrc); + if (matching_stream != recv_streams_.end()) { + matching_stream->second->SetFrameDecryptor(frame_decryptor); + } + // Handle unsignaled frame decryptors. + if (ssrc == 0) { + unsignaled_frame_decryptor_ = frame_decryptor; + } +} + +void WebRtcVoiceMediaChannel::SetFrameEncryptor( + uint32_t ssrc, + rtc::scoped_refptr frame_encryptor) { + RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); + auto matching_stream = send_streams_.find(ssrc); + if (matching_stream != send_streams_.end()) { + matching_stream->second->SetFrameEncryptor(frame_encryptor); + } +} + bool WebRtcVoiceMediaChannel::InsertDtmf(uint32_t ssrc, int event, int duration) { diff --git a/media/engine/webrtcvoiceengine.h b/media/engine/webrtcvoiceengine.h index db3f7c2e7c..bedede1248 100644 --- a/media/engine/webrtcvoiceengine.h +++ b/media/engine/webrtcvoiceengine.h @@ -170,6 +170,21 @@ class WebRtcVoiceMediaChannel final : public VoiceMediaChannel, bool RemoveSendStream(uint32_t ssrc) override; bool AddRecvStream(const StreamParams& sp) override; bool RemoveRecvStream(uint32_t ssrc) override; + + // E2EE Frame API + // Set a frame decryptor to a particular ssrc that will intercept all + // incoming audio payloads and attempt to decrypt them before forwarding the + // result. + void SetFrameDecryptor(uint32_t ssrc, + rtc::scoped_refptr + frame_decryptor) override; + // Set a frame encryptor to a particular ssrc that will intercept all + // outgoing audio payloads frames and attempt to encrypt them and forward the + // result to the packetizer. + void SetFrameEncryptor(uint32_t ssrc, + rtc::scoped_refptr + frame_encryptor) override; + // SSRC=0 will apply the new volume to current and future unsignaled streams. bool SetOutputVolume(uint32_t ssrc, double volume) override; @@ -287,6 +302,10 @@ class WebRtcVoiceMediaChannel final : public VoiceMediaChannel, const webrtc::AudioCodecPairId codec_pair_id_ = webrtc::AudioCodecPairId::Create(); + // Unsignaled streams have an option to have a frame decryptor set on them. + rtc::scoped_refptr + unsignaled_frame_decryptor_; + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(WebRtcVoiceMediaChannel); }; } // namespace cricket diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 3ad3340964..4e6e55810a 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -492,6 +492,7 @@ if (rtc_include_tests) { deps = [ ":peerconnection", ":rtc_pc_base", + "../api:fake_frame_crypto", "../api:libjingle_peerconnection_api", "../api:mock_rtp", "../api/units:time_delta", diff --git a/pc/rtpreceiver.cc b/pc/rtpreceiver.cc index 8ab08bf8f2..d4a1f62f83 100644 --- a/pc/rtpreceiver.cc +++ b/pc/rtpreceiver.cc @@ -43,13 +43,17 @@ std::vector> CreateStreamsFromIds( return streams; } -void AttachFrameDecryptorToMediaChannel( +// Attempt to attach the frame decryptor to the current media channel on the +// correct worker thread only if both the media channel exists and a ssrc has +// been allocated to the stream. +void MaybeAttachFrameDecryptorToMediaChannel( + const absl::optional& ssrc, rtc::Thread* worker_thread, - webrtc::FrameDecryptorInterface* frame_decryptor, + rtc::scoped_refptr frame_decryptor, cricket::MediaChannel* media_channel) { - if (media_channel) { + if (media_channel && ssrc.has_value()) { return worker_thread->Invoke(RTC_FROM_HERE, [&] { - media_channel->SetFrameDecryptor(frame_decryptor); + media_channel->SetFrameDecryptor(*ssrc, frame_decryptor); }); } } @@ -152,8 +156,9 @@ bool AudioRtpReceiver::SetParameters(const RtpParameters& parameters) { void AudioRtpReceiver::SetFrameDecryptor( rtc::scoped_refptr frame_decryptor) { frame_decryptor_ = std::move(frame_decryptor); - AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(), - media_channel_); + // Attach the frame decryptor to the media channel if it exists. + MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_, + frame_decryptor_, media_channel_); } rtc::scoped_refptr @@ -246,6 +251,9 @@ void AudioRtpReceiver::Reconfigure() { if (!SetOutputVolume(track_->enabled() ? cached_volume_ : 0)) { RTC_NOTREACHED(); } + // Reattach the frame decryptor if we were reconfigured. + MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_, + frame_decryptor_, media_channel_); } void AudioRtpReceiver::SetObserver(RtpReceiverObserverInterface* observer) { @@ -259,8 +267,6 @@ void AudioRtpReceiver::SetObserver(RtpReceiverObserverInterface* observer) { void AudioRtpReceiver::SetVoiceMediaChannel( cricket::VoiceMediaChannel* voice_media_channel) { media_channel_ = voice_media_channel; - AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(), - media_channel_); } void AudioRtpReceiver::NotifyFirstPacketReceived() { @@ -341,8 +347,9 @@ bool VideoRtpReceiver::SetParameters(const RtpParameters& parameters) { void VideoRtpReceiver::SetFrameDecryptor( rtc::scoped_refptr frame_decryptor) { frame_decryptor_ = std::move(frame_decryptor); - AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(), - media_channel_); + // Attach the new frame decryptor the media channel if it exists yet. + MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_, + frame_decryptor_, media_channel_); } rtc::scoped_refptr @@ -379,6 +386,9 @@ void VideoRtpReceiver::SetupMediaChannel(uint32_t ssrc) { } ssrc_ = ssrc; SetSink(source_->sink()); + // Attach any existing frame decryptor to the media channel. + MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_, + frame_decryptor_, media_channel_); } void VideoRtpReceiver::set_stream_ids(std::vector stream_ids) { @@ -429,8 +439,6 @@ void VideoRtpReceiver::SetObserver(RtpReceiverObserverInterface* observer) { void VideoRtpReceiver::SetVideoMediaChannel( cricket::VideoMediaChannel* video_media_channel) { media_channel_ = video_media_channel; - AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(), - media_channel_); } void VideoRtpReceiver::NotifyFirstPacketReceived() { diff --git a/pc/rtpsender.cc b/pc/rtpsender.cc index 20130a187c..3bb90f24da 100644 --- a/pc/rtpsender.cc +++ b/pc/rtpsender.cc @@ -31,20 +31,6 @@ int GenerateUniqueId() { return ++g_unique_id; } -// Attaches the frame encryptor to the media channel through an invoke on a -// worker thread. This set must be done on the corresponding worker thread that -// the media channel was created on. -void AttachFrameEncryptorToMediaChannel( - rtc::Thread* worker_thread, - webrtc::FrameEncryptorInterface* frame_encryptor, - cricket::MediaChannel* media_channel) { - if (media_channel) { - return worker_thread->Invoke(RTC_FROM_HERE, [&] { - media_channel->SetFrameEncryptor(frame_encryptor); - }); - } -} - // Returns an true if any RtpEncodingParameters member that isn't implemented // contains a value. bool UnimplementedRtpEncodingParameterHasValue( @@ -78,6 +64,21 @@ bool PerSenderRtpEncodingParameterHasValue( return false; } +// Attempt to attach the frame decryptor to the current media channel on the +// correct worker thread only if both the media channel exists and a ssrc has +// been allocated to the stream. +void MaybeAttachFrameEncryptorToMediaChannel( + const absl::optional ssrc, + rtc::Thread* worker_thread, + rtc::scoped_refptr frame_encryptor, + cricket::MediaChannel* media_channel) { + if (media_channel && ssrc.has_value()) { + return worker_thread->Invoke(RTC_FROM_HERE, [&] { + media_channel->SetFrameEncryptor(*ssrc, frame_encryptor); + }); + } +} + } // namespace // Returns true if any RtpParameters member that isn't implemented contains a @@ -304,8 +305,8 @@ rtc::scoped_refptr AudioRtpSender::GetDtmfSender() const { void AudioRtpSender::SetFrameEncryptor( rtc::scoped_refptr frame_encryptor) { frame_encryptor_ = std::move(frame_encryptor); - AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(), - media_channel_); + MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_, + frame_encryptor_, media_channel_); } rtc::scoped_refptr AudioRtpSender::GetFrameEncryptor() @@ -354,6 +355,9 @@ void AudioRtpSender::SetSsrc(uint32_t ssrc) { init_parameters_.encodings.clear(); }); } + // Each time there is an ssrc update. + MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_, + frame_encryptor_, media_channel_); } void AudioRtpSender::Stop() { @@ -379,8 +383,6 @@ void AudioRtpSender::Stop() { void AudioRtpSender::SetVoiceMediaChannel( cricket::VoiceMediaChannel* voice_media_channel) { media_channel_ = voice_media_channel; - AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(), - media_channel_); } void AudioRtpSender::SetAudioSend() { @@ -555,8 +557,8 @@ rtc::scoped_refptr VideoRtpSender::GetDtmfSender() const { void VideoRtpSender::SetFrameEncryptor( rtc::scoped_refptr frame_encryptor) { frame_encryptor_ = std::move(frame_encryptor); - AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(), - media_channel_); + MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_, + frame_encryptor_, media_channel_); } rtc::scoped_refptr VideoRtpSender::GetFrameEncryptor() @@ -599,6 +601,8 @@ void VideoRtpSender::SetSsrc(uint32_t ssrc) { init_parameters_.encodings.clear(); }); } + MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_, + frame_encryptor_, media_channel_); } void VideoRtpSender::Stop() { @@ -620,8 +624,6 @@ void VideoRtpSender::Stop() { void VideoRtpSender::SetVideoMediaChannel( cricket::VideoMediaChannel* video_media_channel) { media_channel_ = video_media_channel; - AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(), - media_channel_); } void VideoRtpSender::SetVideoSend() { diff --git a/pc/rtpsenderreceiver_unittest.cc b/pc/rtpsenderreceiver_unittest.cc index 2af2ebf066..07ff6a3865 100644 --- a/pc/rtpsenderreceiver_unittest.cc +++ b/pc/rtpsenderreceiver_unittest.cc @@ -13,6 +13,8 @@ #include #include "api/rtpparameters.h" +#include "api/test/fake_frame_decryptor.h" +#include "api/test/fake_frame_encryptor.h" #include "media/base/fakemediaengine.h" #include "media/base/rtpdataengine.h" #include "media/base/testutils.h" @@ -1411,4 +1413,26 @@ TEST_F(RtpSenderReceiverTest, TestOnDestroyedSignal) { EXPECT_TRUE(audio_sender_destroyed_signal_fired_); } +// Validate that the default FrameEncryptor setting is nullptr. +TEST_F(RtpSenderReceiverTest, AudioSenderCanSetFrameEncryptor) { + CreateAudioRtpSender(); + rtc::scoped_refptr fake_frame_encryptor( + new FakeFrameEncryptor()); + EXPECT_EQ(nullptr, audio_rtp_sender_->GetFrameEncryptor()); + audio_rtp_sender_->SetFrameEncryptor(fake_frame_encryptor); + EXPECT_EQ(fake_frame_encryptor.get(), + audio_rtp_sender_->GetFrameEncryptor().get()); +} + +// Validate that the default FrameEncryptor setting is nullptr. +TEST_F(RtpSenderReceiverTest, AudioReceiverCanSetFrameDecryptor) { + CreateAudioRtpReceiver(); + rtc::scoped_refptr fake_frame_decryptor( + new FakeFrameDecryptor()); + EXPECT_EQ(nullptr, audio_rtp_receiver_->GetFrameDecryptor()); + audio_rtp_receiver_->SetFrameDecryptor(fake_frame_decryptor); + EXPECT_EQ(fake_frame_decryptor.get(), + audio_rtp_receiver_->GetFrameDecryptor().get()); +} + } // namespace webrtc