From 1d74988159b05031e26a0b4cdef794ef0b351401 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Tue, 5 Apr 2016 17:26:20 -0700 Subject: [PATCH] Implement AesPatternCryptor for pattern encryption/decryption Issue #78 Change-Id: If0fadf6f83ef67dd39af29080bab6ed71fb35290 --- packager/media/base/aes_pattern_cryptor.cc | 84 +++++++++ packager/media/base/aes_pattern_cryptor.h | 66 +++++++ .../base/aes_pattern_cryptor_unittest.cc | 178 ++++++++++++++++++ packager/media/base/media_base.gyp | 3 + 4 files changed, 331 insertions(+) create mode 100644 packager/media/base/aes_pattern_cryptor.cc create mode 100644 packager/media/base/aes_pattern_cryptor.h create mode 100644 packager/media/base/aes_pattern_cryptor_unittest.cc diff --git a/packager/media/base/aes_pattern_cryptor.cc b/packager/media/base/aes_pattern_cryptor.cc new file mode 100644 index 0000000000..3ad69a863d --- /dev/null +++ b/packager/media/base/aes_pattern_cryptor.cc @@ -0,0 +1,84 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/base/aes_pattern_cryptor.h" + +#include +#include +#include "packager/base/logging.h" + +namespace edash_packager { +namespace media { + +AesPatternCryptor::AesPatternCryptor(uint8_t crypt_byte_block, + uint8_t skip_byte_block, + ConstantIvFlag constant_iv_flag, + scoped_ptr cryptor) + : crypt_byte_block_(crypt_byte_block), + skip_byte_block_(skip_byte_block), + constant_iv_flag_(constant_iv_flag), + cryptor_(cryptor.Pass()) { + DCHECK(cryptor_); +} + +AesPatternCryptor::~AesPatternCryptor() {} + +bool AesPatternCryptor::InitializeWithIv(const std::vector& key, + const std::vector& iv) { + iv_ = iv; + return cryptor_->InitializeWithIv(key, iv); +} + +bool AesPatternCryptor::SetIv(const std::vector& iv) { + iv_ = iv; + return cryptor_->SetIv(iv); +} + +void AesPatternCryptor::UpdateIv() { + cryptor_->UpdateIv(); +} + +bool AesPatternCryptor::CryptInternal(const uint8_t* text, + size_t text_size, + uint8_t* crypt_text, + size_t* crypt_text_size) { + if (constant_iv_flag_ == AesPatternCryptor::kUseConstantIv) + CHECK(SetIv(iv_)); + + // |crypt_text_size| is always the same as |text_size| for pattern encryption. + if (*crypt_text_size < text_size) { + LOG(ERROR) << "Expecting output size of at least " << text_size + << " bytes."; + return false; + } + *crypt_text_size = text_size; + + while (text_size > 0) { + const size_t crypt_byte_size = crypt_byte_block_ * AES_BLOCK_SIZE; + if (text_size >= crypt_byte_size) { + if (!cryptor_->Crypt(text, crypt_byte_size, crypt_text)) + return false; + } else { + // If there is not enough data, just keep it in clear. + memcpy(crypt_text, text, text_size); + return true; + } + text += crypt_byte_size; + text_size -= crypt_byte_size; + crypt_text += crypt_byte_size; + + const size_t skip_byte_size = std::min( + static_cast(skip_byte_block_ * AES_BLOCK_SIZE), text_size); + memcpy(crypt_text, text, skip_byte_size); + text += skip_byte_size; + text_size -= skip_byte_size; + crypt_text += skip_byte_size; + } + return true; +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/base/aes_pattern_cryptor.h b/packager/media/base/aes_pattern_cryptor.h new file mode 100644 index 0000000000..3eb6c2d8b4 --- /dev/null +++ b/packager/media/base/aes_pattern_cryptor.h @@ -0,0 +1,66 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/base/aes_cryptor.h" + +#include "packager/base/macros.h" +#include "packager/base/memory/scoped_ptr.h" + +namespace edash_packager { +namespace media { + +/// Implements pattern-based encryption/decryption. +class AesPatternCryptor : public AesCryptor { + public: + enum ConstantIvFlag { + kUseConstantIv, + kDontUseConstantIv, + }; + + /// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in + /// pattern based encryption. + /// @param skip_byte_block indicates number of unencrypted blocks (16-byte) + /// in pattern based encryption. + /// @param constant_iv_flag indicates whether a constant iv is used, + /// kUseConstantIv means that the same iv is used for all Crypt calls + /// until iv is changed via SetIv; otherwise, iv can be incremented + /// (for counter mode) or chained (for cipher block chaining mode) + /// internally inside Crypt call, i.e. iv will be updated across Crypt + /// calls. + /// @param cryptor points to an AesCryptor instance which performs the actual + /// encryption/decryption. + AesPatternCryptor(uint8_t crypt_byte_block, + uint8_t skip_byte_block, + ConstantIvFlag constant_iv_flag, + scoped_ptr cryptor); + ~AesPatternCryptor() override; + + /// @name AesCryptor implementation overrides. + /// @{ + bool InitializeWithIv(const std::vector& key, + const std::vector& iv) override; + bool SetIv(const std::vector& iv) override; + void UpdateIv() override; + /// @} + + protected: + bool CryptInternal(const uint8_t* text, + size_t text_size, + uint8_t* crypt_text, + size_t* crypt_text_size) override; + + private: + const uint8_t crypt_byte_block_; + const uint8_t skip_byte_block_; + const ConstantIvFlag constant_iv_flag_; + scoped_ptr cryptor_; + std::vector iv_; + + DISALLOW_COPY_AND_ASSIGN(AesPatternCryptor); +}; + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/base/aes_pattern_cryptor_unittest.cc b/packager/media/base/aes_pattern_cryptor_unittest.cc new file mode 100644 index 0000000000..8ac1402019 --- /dev/null +++ b/packager/media/base/aes_pattern_cryptor_unittest.cc @@ -0,0 +1,178 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include +#include + +#include "packager/base/strings/string_number_conversions.h" +#include "packager/media/base/aes_pattern_cryptor.h" + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; + +namespace { +const uint8_t kCryptByteBlock = 2u; +const uint8_t kSkipByteBlock = 1u; +} // namespace + +namespace edash_packager { +namespace media { + +class MockAesCryptor : public AesCryptor { + public: + MOCK_METHOD2(InitializeWithIv, + bool(const std::vector& key, + const std::vector& iv)); + MOCK_METHOD1(SetIv, bool(const std::vector& iv)); + MOCK_METHOD0(UpdateIv, void()); + MOCK_METHOD4(CryptInternal, + bool(const uint8_t* text, + size_t text_size, + uint8_t* crypt_text, + size_t* crypt_text_size)); +}; + +class AesPatternCryptorTest : public ::testing::Test { + public: + AesPatternCryptorTest() + : mock_cryptor_(new MockAesCryptor), + pattern_cryptor_(kCryptByteBlock, + kSkipByteBlock, + AesPatternCryptor::kDontUseConstantIv, + scoped_ptr(mock_cryptor_)) {} + + protected: + MockAesCryptor* mock_cryptor_; + AesPatternCryptor pattern_cryptor_; +}; + +TEST_F(AesPatternCryptorTest, InitializeWithIv) { + std::vector key(16, 'k'); + std::vector iv(8, 'i'); + EXPECT_CALL(*mock_cryptor_, InitializeWithIv(key, iv)).WillOnce(Return(true)); + EXPECT_TRUE(pattern_cryptor_.InitializeWithIv(key, iv)); +} + +TEST_F(AesPatternCryptorTest, UpdateIv) { + EXPECT_CALL(*mock_cryptor_, UpdateIv()); + pattern_cryptor_.UpdateIv(); +} + +namespace { + +struct PatternTestCase { + const char* text_hex; + const char* expected_crypt_text_hex; +}; + +const PatternTestCase kPatternTestCases[] = { + // Empty. + {"", ""}, + // One partial block (not encrypted). + {"010203", "010203"}, + // One block (not encrypted). + {"01020304050607080910111213141516", "01020304050607080910111213141516"}, + // One block + partial block (not encrypted). + {"010203040506070809101112131415161718", + "010203040506070809101112131415161718"}, + // Two blocks (encrypted) - the mock encryptor adds every byte by 0x10. + {"0102030405060708091011121314151617181920212223242526272829303132", + "1112131415161718192021222324252627282930313233343536373839404142"}, + // Two blocks + partial block (only the first two blocks are encrypted). + {"0102030405060708091011121314151617181920212223242526272829303132" + "333435363738", + "1112131415161718192021222324252627282930313233343536373839404142" + "333435363738"}, + // Seven blocks. + {// kCryptByteBlock (2) blocks encrypted. + "0102030405060708091011121314151617181920212223242526272829303132" + // kSkipByteBlock (1) block not encrypted. + "33343536373839404142434445464748" + // kCryptByteBlock (2) blocks encrypted. + "4950515253545556575859606162636465666768697071727374757677787980" + // kSkipByteBlock (1) block not encrypted. + "81828384858687888990919293949596" + // Less than kCryptByteBlock blocks remaining, so not encrypted. + "97989900010203040506070809101112", + "1112131415161718192021222324252627282930313233343536373839404142" + "33343536373839404142434445464748" + "5960616263646566676869707172737475767778798081828384858687888990" + "81828384858687888990919293949596" + "97989900010203040506070809101112"}, +}; + +} // namespace + +class AesPatternCryptorVerificationTest + : public AesPatternCryptorTest, + public ::testing::WithParamInterface {}; + +TEST_P(AesPatternCryptorVerificationTest, PatternTest) { + std::vector text; + std::string text_hex(GetParam().text_hex); + if (!text_hex.empty()) + ASSERT_TRUE(base::HexStringToBytes(text_hex, &text)); + std::vector expected_crypt_text; + std::string expected_crypt_text_hex(GetParam().expected_crypt_text_hex); + if (!expected_crypt_text_hex.empty()) { + ASSERT_TRUE( + base::HexStringToBytes(expected_crypt_text_hex, &expected_crypt_text)); + } + + ON_CALL(*mock_cryptor_, CryptInternal(_, _, _, _)) + .WillByDefault(Invoke([](const uint8_t* text, size_t text_size, + uint8_t* crypt_text, size_t* crypt_text_size) { + *crypt_text_size = text_size; + for (size_t i = 0; i < text_size; ++i) { + *crypt_text++ = *text++ + 0x10; + } + return true; + })); + + std::vector crypt_text; + ASSERT_TRUE(pattern_cryptor_.Crypt(text, &crypt_text)); + EXPECT_EQ(expected_crypt_text, crypt_text); +} + +INSTANTIATE_TEST_CASE_P(PatternTestCases, + AesPatternCryptorVerificationTest, + ::testing::ValuesIn(kPatternTestCases)); + +TEST(AesPatternCryptorConstIvTest, UseConstantIv) { + MockAesCryptor* mock_cryptor = new MockAesCryptor; + AesPatternCryptor pattern_cryptor(kCryptByteBlock, kSkipByteBlock, + AesPatternCryptor::kUseConstantIv, + scoped_ptr(mock_cryptor)); + + std::vector iv(8, 'i'); + // SetIv will be called twice: + // once by AesPatternCryptor::SetIv, + // once by AesPatternCryptor::Crypt, to make sure the same iv is used. + EXPECT_CALL(*mock_cryptor, SetIv(iv)).Times(2).WillRepeatedly(Return(true)); + EXPECT_TRUE(pattern_cryptor.SetIv(iv)); + + std::string crypt_text; + ASSERT_TRUE(pattern_cryptor.Crypt("010203", &crypt_text)); +} + +TEST(AesPatternCryptorConstIvTest, DontUseConstantIv) { + MockAesCryptor* mock_cryptor = new MockAesCryptor; + AesPatternCryptor pattern_cryptor(kCryptByteBlock, kSkipByteBlock, + AesPatternCryptor::kDontUseConstantIv, + scoped_ptr(mock_cryptor)); + + std::vector iv(8, 'i'); + // SetIv will be called only once by AesPatternCryptor::SetIv. + EXPECT_CALL(*mock_cryptor, SetIv(iv)).WillOnce(Return(true)); + EXPECT_TRUE(pattern_cryptor.SetIv(iv)); + + std::string crypt_text; + ASSERT_TRUE(pattern_cryptor.Crypt("010203", &crypt_text)); +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/base/media_base.gyp b/packager/media/base/media_base.gyp index 521b3d6b6b..01331bd3c2 100644 --- a/packager/media/base/media_base.gyp +++ b/packager/media/base/media_base.gyp @@ -19,6 +19,8 @@ 'aes_decryptor.h', 'aes_encryptor.cc', 'aes_encryptor.h', + 'aes_pattern_cryptor.cc', + 'aes_pattern_cryptor.h', 'audio_stream_info.cc', 'audio_stream_info.h', 'audio_timestamp_helper.cc', @@ -116,6 +118,7 @@ 'type': '<(gtest_target_type)', 'sources': [ 'aes_cryptor_unittest.cc', + 'aes_pattern_cryptor_unittest.cc', 'audio_timestamp_helper_unittest.cc', 'bit_reader_unittest.cc', 'buffer_writer_unittest.cc',