diff --git a/api/test/fake_frame_decryptor.cc b/api/test/fake_frame_decryptor.cc index 432664a030..b77017fdb4 100644 --- a/api/test/fake_frame_decryptor.cc +++ b/api/test/fake_frame_decryptor.cc @@ -25,19 +25,20 @@ int FakeFrameDecryptor::Decrypt(cricket::MediaType media_type, rtc::ArrayView frame, size_t* bytes_written) { if (fail_decryption_) { - return 1; + return static_cast(FakeDecryptStatus::FORCED_FAILURE); } RTC_CHECK_EQ(frame.size() + 1, encrypted_frame.size()); for (size_t i = 0; i < frame.size(); i++) { - frame[i] ^= fake_key_; + frame[i] = encrypted_frame[i] ^ fake_key_; } if (encrypted_frame[frame.size()] != expected_postfix_byte_) { - return 1; + return static_cast(FakeDecryptStatus::INVALID_POSTFIX); } - return 0; + *bytes_written = frame.size(); + return static_cast(FakeDecryptStatus::OK); } size_t FakeFrameDecryptor::GetMaxPlaintextByteSize( diff --git a/api/test/fake_frame_decryptor.h b/api/test/fake_frame_decryptor.h index b945def59a..bcb8080fd4 100644 --- a/api/test/fake_frame_decryptor.h +++ b/api/test/fake_frame_decryptor.h @@ -22,34 +22,40 @@ namespace webrtc { // 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 +class FakeFrameDecryptor final : 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, + explicit FakeFrameDecryptor(uint8_t fake_key = 0xAA, uint8_t expected_postfix_byte = 255); - - // FrameDecryptorInterface implementation + // Fake decryption that just xors the payload with the 1 byte key and checks + // the postfix byte. This will always fail if fail_decryption_ is set to true. 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; - + // Always returns 1 less than the size of the encrypted frame. size_t GetMaxPlaintextByteSize(cricket::MediaType media_type, size_t encrypted_frame_size) override; - + // Sets the fake key to use for encryption. void SetFakeKey(uint8_t fake_key); - + // Returns the fake key used for encryption. uint8_t GetFakeKey() const; - + // Set the Postfix byte that is expected in the encrypted payload. void SetExpectedPostfixByte(uint8_t expected_postfix_byte); - + // Returns the postfix byte that will be checked for in the encrypted payload. uint8_t GetExpectedPostfixByte() const; - + // If set to true will force all encryption to fail. void SetFailDecryption(bool fail_decryption); + // Simple error codes for tests to validate against. + enum class FakeDecryptStatus : int { + OK = 0, + FORCED_FAILURE = 1, + INVALID_POSTFIX = 2 + }; private: uint8_t fake_key_ = 0; diff --git a/api/test/fake_frame_encryptor.cc b/api/test/fake_frame_encryptor.cc index 013058f96a..edf3cc2136 100644 --- a/api/test/fake_frame_encryptor.cc +++ b/api/test/fake_frame_encryptor.cc @@ -22,18 +22,18 @@ int FakeFrameEncryptor::Encrypt(cricket::MediaType media_type, rtc::ArrayView frame, rtc::ArrayView encrypted_frame, size_t* bytes_written) { - // Useful if you want to test failure cases. if (fail_encryption_) { - return 1; + return static_cast(FakeEncryptionStatus::FORCED_FAILURE); } 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[i] = frame[i] ^ fake_key_; } + encrypted_frame[frame.size()] = postfix_byte_; *bytes_written = encrypted_frame.size(); - return 0; + return static_cast(FakeEncryptionStatus::OK); } size_t FakeFrameEncryptor::GetMaxCiphertextByteSize( diff --git a/api/test/fake_frame_encryptor.h b/api/test/fake_frame_encryptor.h index 61ae9383f6..0bec967dc2 100644 --- a/api/test/fake_frame_encryptor.h +++ b/api/test/fake_frame_encryptor.h @@ -24,29 +24,35 @@ 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 + explicit FakeFrameEncryptor(uint8_t fake_key = 0xAA, + uint8_t postfix_byte = 255); + // Simply xors each payload with the provided fake key and adds the postfix + // bit to the end. This will always fail if fail_encryption_ is set to true. 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; - + // Always returns 1 more than the size of the frame. size_t GetMaxCiphertextByteSize(cricket::MediaType media_type, size_t frame_size) override; - + // Sets the fake key to use during encryption. void SetFakeKey(uint8_t fake_key); - + // Returns the fake key used during encryption. uint8_t GetFakeKey() const; - + // Set the postfix byte to use. void SetPostfixByte(uint8_t expected_postfix_byte); - + // Return a postfix byte added to each outgoing payload. uint8_t GetPostfixByte() const; - + // Force all encryptions to fail. void SetFailEncryption(bool fail_encryption); + enum class FakeEncryptionStatus : int { + OK = 0, + FORCED_FAILURE = 1, + }; + private: uint8_t fake_key_ = 0; uint8_t postfix_byte_ = 0; diff --git a/video/BUILD.gn b/video/BUILD.gn index dc715d1928..33dcbe6485 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -394,6 +394,7 @@ if (rtc_include_tests) { "end_to_end_tests/config_tests.cc", "end_to_end_tests/extended_reports_tests.cc", "end_to_end_tests/fec_tests.cc", + "end_to_end_tests/frame_encryption_tests.cc", "end_to_end_tests/histogram_tests.cc", "end_to_end_tests/multi_codec_receive_tests.cc", "end_to_end_tests/multi_stream_tester.cc", @@ -426,6 +427,8 @@ if (rtc_include_tests) { ":video", ":video_mocks", ":video_stream_encoder_impl", + "../api:fake_frame_decryptor", + "../api:fake_frame_encryptor", "../api:simulated_network_api", "../api/test/video:function_video_factory", "../api/video:encoded_image", diff --git a/video/end_to_end_tests/frame_encryption_tests.cc b/video/end_to_end_tests/frame_encryption_tests.cc new file mode 100644 index 0000000000..8cd7b47849 --- /dev/null +++ b/video/end_to_end_tests/frame_encryption_tests.cc @@ -0,0 +1,81 @@ +/* + * 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 "api/test/fake_frame_encryptor.h" +#include "media/engine/internaldecoderfactory.h" +#include "modules/video_coding/codecs/vp8/include/vp8.h" +#include "test/call_test.h" +#include "test/field_trial.h" +#include "test/gtest.h" + +namespace webrtc { + +class FrameEncryptionEndToEndTest : public test::CallTest { + public: + FrameEncryptionEndToEndTest() = default; + + private: + // GenericDescriptor is required for FrameEncryption to work. + test::ScopedFieldTrials field_trials_{"WebRTC-GenericDescriptor/Enabled/"}; +}; + +// Validates that payloads cannot be sent without a frame encryptor and frame +// decryptor attached. +TEST_F(FrameEncryptionEndToEndTest, RequireFrameEncryptionEnforced) { + class DecryptedFrameObserver : public test::EndToEndTest, + public rtc::VideoSinkInterface { + public: + DecryptedFrameObserver() + : EndToEndTest(kDefaultTimeoutMs), + encoder_factory_([]() { return VP8Encoder::Create(); }) {} + + private: + void ModifyVideoConfigs( + VideoSendStream::Config* send_config, + std::vector* receive_configs, + VideoEncoderConfig* encoder_config) override { + // Use VP8 instead of FAKE. + send_config->encoder_settings.encoder_factory = &encoder_factory_; + send_config->rtp.payload_name = "VP8"; + send_config->rtp.payload_type = kVideoSendPayloadType; + send_config->frame_encryptor = new FakeFrameEncryptor(); + send_config->crypto_options.sframe.require_frame_encryption = true; + encoder_config->codec_type = kVideoCodecVP8; + VideoReceiveStream::Decoder decoder = + test::CreateMatchingDecoder(*send_config); + decoder.decoder_factory = &decoder_factory_; + for (auto& recv_config : *receive_configs) { + recv_config.decoders.clear(); + recv_config.decoders.push_back(decoder); + recv_config.renderer = this; + recv_config.frame_decryptor = new FakeFrameDecryptor(); + recv_config.crypto_options.sframe.require_frame_encryption = true; + } + } + + // Validate that rotation is preserved. + void OnFrame(const VideoFrame& video_frame) override { + observation_complete_.Set(); + } + + void PerformTest() override { + EXPECT_TRUE(Wait()) + << "Timed out waiting for decrypted frames to be rendered."; + } + + std::unique_ptr encoder_; + test::FunctionVideoEncoderFactory encoder_factory_; + InternalDecoderFactory decoder_factory_; + } test; + + RunBaseTest(&test); +} +} // namespace webrtc