Commit deb337b8 authored by John Rummell's avatar John Rummell Committed by Commit Bot

[eme] Support 'cbcs' decryption in AesDecryptor

Add code so that media encrypted in 'cbcs' encryption scheme can be
handled by AesDecryptor.

This adds 2 new classes:
  aes_cbc_crypto.* handles AES-CBC-128 decryption using the BoringSSL
    code to allow for decryption without padding and to avoid extra copies
    of the data.
  cbcs_decryption.* supports pattern decryption.
Both classes have unit tests to verify their basic functionality.

BUG=657957,658026
TEST=content_browsertests pass

Change-Id: Ia0a8db2859ff4f815a0cfcc705f7ea50d77a115c
Reviewed-on: https://chromium-review.googlesource.com/1038725
Commit-Queue: John Rummell <jrummell@chromium.org>
Reviewed-by: default avatarKongqun Yang <kqyang@chromium.org>
Reviewed-by: default avatarXiaohan Wang <xhwang@chromium.org>
Reviewed-by: default avatarRyan Sleevi <rsleevi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557127}
parent 8e15d6eb
......@@ -317,8 +317,10 @@ IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_Encryption_CENS) {
}
IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_Encryption_CBCS) {
std::string expected_result =
BUILDFLAG(ENABLE_CBCS_ENCRYPTION_SCHEME) ? media::kEnded : media::kError;
TestMp4EncryptionPlayback("bear-640x360-v_frag-cbcs.mp4", kMp4Avc1VideoOnly,
media::kError);
expected_result);
}
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
......
......@@ -231,6 +231,7 @@ source_set("base") {
"stream_parser.h",
"stream_parser_buffer.cc",
"stream_parser_buffer.h",
"subsample_entry.cc",
"subsample_entry.h",
"surface_manager.h",
"text_cue.cc",
......@@ -488,6 +489,7 @@ source_set("unit_tests") {
"silent_sink_suspender_unittest.cc",
"sinc_resampler_unittest.cc",
"stream_parser_unittest.cc",
"subsample_entry_unittest.cc",
"text_ranges_unittest.cc",
"text_renderer_unittest.cc",
"time_delta_interpolator_unittest.cc",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/subsample_entry.h"
#include "base/logging.h"
#include "base/numerics/safe_math.h"
namespace media {
bool VerifySubsamplesMatchSize(const std::vector<SubsampleEntry>& subsamples,
size_t input_size) {
base::CheckedNumeric<size_t> total_size = 0;
for (const auto& subsample : subsamples) {
// Add each entry separately to avoid the compiler doing the wrong thing.
total_size += subsample.clear_bytes;
total_size += subsample.cypher_bytes;
}
if (!total_size.IsValid() || total_size.ValueOrDie() != input_size) {
DVLOG(1) << "Subsample sizes do not equal input size";
return false;
}
return true;
}
} // namespace media
......@@ -7,6 +7,10 @@
#include <stdint.h>
#include <vector>
#include "media/base/media_export.h"
namespace media {
// The Common Encryption spec provides for subsample encryption, where portions
......@@ -26,6 +30,13 @@ struct SubsampleEntry {
uint32_t cypher_bytes;
};
// Verifies that |subsamples| correctly specifies a buffer of length
// |input_size|. Returns false if the total of bytes specified in |subsamples|
// does not match |input_size|.
MEDIA_EXPORT bool VerifySubsamplesMatchSize(
const std::vector<SubsampleEntry>& subsamples,
size_t input_size);
} // namespace media
#endif // MEDIA_BASE_SUBSAMPLE_ENTRY_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/subsample_entry.h"
#include <limits>
#include "base/numerics/safe_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
constexpr uint32_t kMax = std::numeric_limits<uint32_t>::max();
TEST(SubsampleEntryTest, NoEntries) {
EXPECT_TRUE(VerifySubsamplesMatchSize({}, 0));
EXPECT_FALSE(VerifySubsamplesMatchSize({}, 100));
}
TEST(SubsampleEntryTest, OneEntry) {
EXPECT_TRUE(VerifySubsamplesMatchSize({{0, 50}}, 50));
EXPECT_TRUE(VerifySubsamplesMatchSize({{100, 00}}, 100));
EXPECT_TRUE(VerifySubsamplesMatchSize({{150, 200}}, 350));
}
TEST(SubsampleEntryTest, MultipleEntries) {
EXPECT_TRUE(VerifySubsamplesMatchSize({{0, 50}, {100, 00}, {150, 200}}, 500));
}
TEST(SubsampleEntryTest, NoOverflow) {
EXPECT_TRUE(
VerifySubsamplesMatchSize({{kMax, 0}}, base::strict_cast<size_t>(kMax)));
EXPECT_TRUE(
VerifySubsamplesMatchSize({{0, kMax}}, base::strict_cast<size_t>(kMax)));
}
} // namespace media
......@@ -26,8 +26,12 @@ source_set("cdm") {
]
sources = [
"aes_cbc_crypto.cc",
"aes_cbc_crypto.h",
"aes_decryptor.cc",
"aes_decryptor.h",
"cbcs_decryptor.cc",
"cbcs_decryptor.h",
"cdm_context_ref_impl.cc",
"cdm_context_ref_impl.h",
"cenc_decryptor.cc",
......@@ -54,6 +58,7 @@ source_set("cdm") {
"//crypto",
"//media/base",
"//media/formats",
"//third_party/boringssl",
"//ui/gfx/geometry",
"//url",
]
......@@ -141,7 +146,9 @@ static_library("cdm_paths") {
source_set("unit_tests") {
testonly = true
sources = [
"aes_cbc_crypto_unittest.cc",
"aes_decryptor_unittest.cc",
"cbcs_decryptor_unittest.cc",
"cenc_decryptor_unittest.cc",
"json_web_key_unittest.cc",
]
......@@ -152,6 +159,7 @@ source_set("unit_tests") {
"//media:test_support",
"//testing/gmock",
"//testing/gtest",
"//third_party/boringssl",
"//ui/gfx:test_support",
"//url",
]
......
include_rules = [
"+components/crash/core/common/crash_key.h",
"+crypto",
"+third_party/boringssl/src/include",
]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/cdm/aes_cbc_crypto.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "crypto/openssl_util.h"
#include "crypto/symmetric_key.h"
#include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/crypto.h"
#include "third_party/boringssl/src/include/openssl/err.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
// Notes on using OpenSSL:
// https://www.openssl.org/docs/man1.1.0/crypto/EVP_DecryptUpdate.html
// The documentation for EVP_DecryptUpdate() only states
// "EVP_DecryptInit_ex(), EVP_DecryptUpdate() and EVP_DecryptFinal_ex()
// are the corresponding decryption operations. EVP_DecryptFinal() will
// return an error code if padding is enabled and the final block is not
// correctly formatted. The parameters and restrictions are identical
// to the encryption operations except that if padding is enabled ..."
// As this implementation does not use padding, the last part should not be
// an issue. However, there is no mention whether data can be decrypted
// block-by-block or if all the data must be unencrypted at once.
//
// The documentation for EVP_EncryptUpdate() (same page as above) states
// "EVP_EncryptUpdate() encrypts inl bytes from the buffer in and writes
// the encrypted version to out. This function can be called multiple times
// to encrypt successive blocks of data."
// Given that the EVP_Decrypt* methods have the same restrictions, the code
// below assumes that EVP_DecryptUpdate() can be called on a block-by-block
// basis. A test in aes_cbc_crypto_unittest.cc verifies this.
namespace media {
AesCbcCrypto::AesCbcCrypto() {
// Ensure the crypto library is initialized. CRYPTO_library_init may be
// safely called concurrently.
CRYPTO_library_init();
EVP_CIPHER_CTX_init(&ctx_);
}
AesCbcCrypto::~AesCbcCrypto() {
EVP_CIPHER_CTX_cleanup(&ctx_);
}
bool AesCbcCrypto::Initialize(const crypto::SymmetricKey& key,
base::span<const uint8_t> iv) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
// This uses AES-CBC-128, so the key must be 128 bits.
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
const uint8_t* key_data = reinterpret_cast<const uint8_t*>(key.key().data());
if (key.key().length() != EVP_CIPHER_key_length(cipher)) {
DVLOG(1) << "Key length is incorrect.";
return false;
}
// |iv| must also be 128 bits.
if (iv.size_bytes() != EVP_CIPHER_iv_length(cipher)) {
DVLOG(1) << "IV length is incorrect.";
return false;
}
if (!EVP_DecryptInit_ex(&ctx_, cipher, nullptr, key_data, iv.data())) {
DVLOG(1) << "EVP_DecryptInit_ex() failed.";
return false;
}
if (!EVP_CIPHER_CTX_set_padding(&ctx_, 0)) {
DVLOG(1) << "EVP_CIPHER_CTX_set_padding() failed.";
return false;
}
return true;
}
bool AesCbcCrypto::Decrypt(base::span<const uint8_t> encrypted_data,
uint8_t* decrypted_data) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
if (encrypted_data.size_bytes() % EVP_CIPHER_CTX_block_size(&ctx_) != 0) {
DVLOG(1) << "Encrypted bytes not a multiple of block size.";
return false;
}
int out_length;
if (!EVP_DecryptUpdate(&ctx_, decrypted_data, &out_length,
encrypted_data.data(), encrypted_data.size_bytes())) {
DVLOG(1) << "EVP_DecryptUpdate() failed.";
return false;
}
return encrypted_data.size_bytes() == base::checked_cast<size_t>(out_length);
}
} // namespace media
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_CDM_AES_CBC_CRYPTO_H_
#define MEDIA_CDM_AES_CBC_CRYPTO_H_
#include <stdint.h>
#include <string>
#include "base/containers/span.h"
#include "base/macros.h"
#include "media/base/media_export.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
namespace crypto {
class SymmetricKey;
}
namespace media {
// This class implements AES-CBC-128 decryption as described in the Advanced
// Encryption Standard specified by AES [FIPS-197, https://www.nist.gov]
// using 128-bit keys in Cipher Block Chaining mode, as specified in Block
// Cipher Modes [NIST 800-38A, https://www.nist.gov].
class MEDIA_EXPORT AesCbcCrypto {
public:
AesCbcCrypto();
~AesCbcCrypto();
// Initializes the encryptor using |key| and |iv|. Returns false if either
// the key or the initialization vector cannot be used.
bool Initialize(const crypto::SymmetricKey& key,
base::span<const uint8_t> iv);
// Decrypts |encrypted_data| into |decrypted_data|. |encrypted_data| must be
// a multiple of the blocksize (128 bits), and |decrypted_data| must have
// enough space for |encrypted_data|.size(). Returns false if the decryption
// fails.
bool Decrypt(base::span<const uint8_t> encrypted_data,
uint8_t* decrypted_data);
private:
EVP_CIPHER_CTX ctx_;
DISALLOW_COPY_AND_ASSIGN(AesCbcCrypto);
};
} // namespace media
#endif // MEDIA_CDM_AES_CBC_CRYPTO_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/cdm/aes_cbc_crypto.h"
#include <memory>
#include "base/containers/span.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "crypto/encryptor.h"
#include "crypto/symmetric_key.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/crypto.h"
#include "third_party/boringssl/src/include/openssl/err.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
namespace media {
namespace {
// Pattern decryption uses 16-byte blocks.
constexpr size_t kBlockSize = 16;
// Keys and IV have to be 128 bits.
const uint8_t kKey1[] = {0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13};
static_assert(base::size(kKey1) == 128 / 8, "kKey1 must be 128 bits");
const uint8_t kKey2[] = {0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b};
static_assert(base::size(kKey2) == 128 / 8, "kKey2 must be 128 bits");
const uint8_t kIv[] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static_assert(base::size(kIv) == 128 / 8, "kIv must be 128 bits");
const uint8_t kOneBlock[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'};
static_assert(base::size(kOneBlock) == kBlockSize, "kOneBlock not block sized");
std::string MakeString(const std::vector<uint8_t>& chars) {
return std::string(chars.begin(), chars.end());
}
// Returns a std::vector<uint8_t> containing |count| copies of |input|.
std::vector<uint8_t> Repeat(const std::vector<uint8_t>& input, size_t count) {
std::vector<uint8_t> result;
for (size_t i = 0; i < count; ++i)
result.insert(result.end(), input.begin(), input.end());
return result;
}
} // namespace
class AesCbcCryptoTest : public testing::Test {
public:
AesCbcCryptoTest()
: key1_(crypto::SymmetricKey::Import(
crypto::SymmetricKey::AES,
std::string(std::begin(kKey1), std::end(kKey1)))),
key2_(crypto::SymmetricKey::Import(
crypto::SymmetricKey::AES,
std::string(std::begin(kKey2), std::end(kKey2)))),
iv_(std::begin(kIv), std::end(kIv)),
one_block_(std::begin(kOneBlock), std::end(kOneBlock)) {}
// Encrypt |original| using AES-CBC encryption with |key| and |iv|.
std::vector<uint8_t> Encrypt(const std::vector<uint8_t>& original,
const crypto::SymmetricKey& key,
base::span<const uint8_t> iv) {
// This code uses crypto::Encryptor to encrypt |original| rather than
// calling EVP_EncryptInit_ex() / EVP_EncryptUpdate() / etc. This is done
// for simplicity, as the crypto:: code wraps all the calls up nicely.
// However, for AES-CBC encryption, the crypto:: code does add padding to
// the output, which is simply stripped off.
crypto::Encryptor encryptor;
std::string iv_as_string(std::begin(iv), std::end(iv));
EXPECT_TRUE(encryptor.Init(&key, crypto::Encryptor::CBC, iv_as_string));
std::string ciphertext;
EXPECT_TRUE(encryptor.Encrypt(MakeString(original), &ciphertext));
// CBC encyption adds a block of padding at the end, so discard it.
EXPECT_GT(ciphertext.size(), original.size());
ciphertext.resize(original.size());
return std::vector<uint8_t>(ciphertext.begin(), ciphertext.end());
}
// Constants for testing.
std::unique_ptr<crypto::SymmetricKey> key1_;
std::unique_ptr<crypto::SymmetricKey> key2_;
base::span<const uint8_t> iv_;
const std::vector<uint8_t> one_block_;
};
TEST_F(AesCbcCryptoTest, OneBlock) {
auto encrypted_block = Encrypt(one_block_, *key1_, iv_);
EXPECT_EQ(kBlockSize, encrypted_block.size());
AesCbcCrypto crypto;
EXPECT_TRUE(crypto.Initialize(*key1_, iv_));
std::vector<uint8_t> output(encrypted_block.size());
EXPECT_TRUE(crypto.Decrypt(encrypted_block, output.data()));
EXPECT_EQ(output, one_block_);
}
TEST_F(AesCbcCryptoTest, WrongKey) {
auto encrypted_block = Encrypt(one_block_, *key1_, iv_);
EXPECT_EQ(kBlockSize, encrypted_block.size());
// Use |key2_| when trying to decrypt.
AesCbcCrypto crypto;
EXPECT_TRUE(crypto.Initialize(*key2_, iv_));
std::vector<uint8_t> output(encrypted_block.size());
EXPECT_TRUE(crypto.Decrypt(encrypted_block, output.data()));
EXPECT_NE(output, one_block_);
}
TEST_F(AesCbcCryptoTest, WrongIV) {
auto encrypted_block = Encrypt(one_block_, *key1_, iv_);
EXPECT_EQ(kBlockSize, encrypted_block.size());
// Use a different IV when trying to decrypt.
AesCbcCrypto crypto;
std::vector<uint8_t> alternate_iv(iv_.size(), 'a');
EXPECT_TRUE(crypto.Initialize(*key1_, alternate_iv));
std::vector<uint8_t> output(encrypted_block.size());
EXPECT_TRUE(crypto.Decrypt(encrypted_block, output.data()));
EXPECT_NE(output, one_block_);
}
TEST_F(AesCbcCryptoTest, PartialBlock) {
auto encrypted_block = Encrypt(one_block_, *key1_, iv_);
EXPECT_EQ(kBlockSize, encrypted_block.size());
AesCbcCrypto crypto;
EXPECT_TRUE(crypto.Initialize(*key2_, iv_));
// Try to decrypt less than a full block.
std::vector<uint8_t> output(encrypted_block.size());
EXPECT_FALSE(crypto.Decrypt(
base::make_span(encrypted_block).first(encrypted_block.size() - 5),
output.data()));
}
TEST_F(AesCbcCryptoTest, MultipleBlocks) {
// Encrypt 10 copies of |one_block_| together.
constexpr size_t kNumBlocksInData = 10;
auto encrypted_block =
Encrypt(Repeat(one_block_, kNumBlocksInData), *key2_, iv_);
ASSERT_EQ(kNumBlocksInData * kBlockSize, encrypted_block.size());
AesCbcCrypto crypto;
EXPECT_TRUE(crypto.Initialize(*key2_, iv_));
std::vector<uint8_t> output(encrypted_block.size());
EXPECT_TRUE(crypto.Decrypt(encrypted_block, output.data()));
EXPECT_EQ(output, Repeat(one_block_, kNumBlocksInData));
}
// As the code in aes_cbc_crypto.cc relies on decrypting the data block by
// block, ensure that the crypto routines work the same way whether it
// decrypts one block at a time or all the blocks in one call.
TEST_F(AesCbcCryptoTest, BlockDecryptionWorks) {
constexpr size_t kNumBlocksInData = 5;
std::vector<uint8_t> data = {1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0};
ASSERT_EQ(data.size(), kNumBlocksInData * kBlockSize);
auto encrypted_data = Encrypt(data, *key1_, iv_);
// Decrypt |encrypted_data| in one pass.
{
AesCbcCrypto crypto;
EXPECT_TRUE(crypto.Initialize(*key1_, iv_));
std::vector<uint8_t> output(kNumBlocksInData * kBlockSize);
EXPECT_TRUE(crypto.Decrypt(encrypted_data, output.data()));
EXPECT_EQ(output, data);
}
// Repeat but call Decrypt() once for each block.
{
AesCbcCrypto crypto;
EXPECT_TRUE(crypto.Initialize(*key1_, iv_));
std::vector<uint8_t> output(kNumBlocksInData * kBlockSize);
auto input = base::make_span(encrypted_data);
for (size_t offset = 0; offset < output.size(); offset += kBlockSize) {
EXPECT_TRUE(
crypto.Decrypt(input.subspan(offset, kBlockSize), &output[offset]));
}
EXPECT_EQ(output, data);
}
}
} // namespace media
......@@ -23,6 +23,7 @@
#include "media/base/limits.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
#include "media/cdm/cbcs_decryptor.h"
#include "media/cdm/cenc_decryptor.h"
#include "media/cdm/cenc_utils.h"
#include "media/cdm/json_web_key.h"
......@@ -157,8 +158,10 @@ static scoped_refptr<DecoderBuffer> DecryptData(
if (input.decrypt_config()->encryption_mode() == EncryptionMode::kCenc)
return DecryptCencBuffer(input, key);
// TODO(crbug.com/658026): Add support for 'cbcs'.
DVLOG(1) << "Only 'cenc' mode supported.";
if (input.decrypt_config()->encryption_mode() == EncryptionMode::kCbcs)
return DecryptCbcsBuffer(input, key);
DVLOG(1) << "Only 'cenc' and 'cbcs' modes supported.";
return nullptr;
}
......@@ -495,7 +498,7 @@ void AesDecryptor::Decrypt(StreamType stream_type,
DecryptionKey* key = GetKey_Locked(key_id);
if (!key) {
DVLOG(1) << "Could not find a matching key for the given key ID.";
decrypt_cb.Run(kNoKey, NULL);
decrypt_cb.Run(kNoKey, nullptr);
return;
}
......@@ -503,12 +506,12 @@ void AesDecryptor::Decrypt(StreamType stream_type,
DecryptData(*encrypted.get(), *key->decryption_key());
if (!decrypted) {
DVLOG(1) << "Decryption failed.";
decrypt_cb.Run(kError, NULL);
decrypt_cb.Run(kError, nullptr);
return;
}
decrypted->set_timestamp(encrypted->timestamp());
decrypted->set_duration(encrypted->duration());
DCHECK_EQ(decrypted->timestamp(), encrypted->timestamp());
DCHECK_EQ(decrypted->duration(), encrypted->duration());
decrypt_cb.Run(kSuccess, std::move(decrypted));
}
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/cdm/cbcs_decryptor.h"
#include <stdint.h>
#include <algorithm>
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/checked_math.h"
#include "crypto/symmetric_key.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/encryption_pattern.h"
#include "media/base/subsample_entry.h"
#include "media/cdm/aes_cbc_crypto.h"
namespace media {
namespace {
constexpr size_t kAesBlockSizeInBytes = 16;
// Decrypts |input_data| into |output_data|, using the pattern specified in
// |pattern|. |pattern| only applies to full blocks. Any partial block at
// the end is considered unencrypted. |output_data| must have enough room to
// hold |input_data|.size() bytes.
bool DecryptWithPattern(const crypto::SymmetricKey& key,
base::span<const uint8_t> iv,
const EncryptionPattern& pattern,
base::span<const uint8_t> input_data,
uint8_t* output_data) {
// The AES_CBC decryption is reset for each subsample.
AesCbcCrypto aes_cbc_crypto;
if (!aes_cbc_crypto.Initialize(key, iv))
return false;
// |total_blocks| is the number of blocks in the buffer, ignoring any
// partial block at the end. |remaining_bytes| is the number of bytes
// in the partial block at the end of the buffer, if any.
size_t total_blocks = input_data.size_bytes() / kAesBlockSizeInBytes;
size_t remaining_bytes = input_data.size_bytes() % kAesBlockSizeInBytes;
size_t crypt_byte_block =
base::strict_cast<size_t>(pattern.crypt_byte_block());
size_t skip_byte_block = base::strict_cast<size_t>(pattern.skip_byte_block());
// |crypt_byte_block| and |skip_byte_block| come from 4 bit values, so fail
// if these are too large.
if (crypt_byte_block >= 16 || skip_byte_block >= 16)
return false;
if (crypt_byte_block == 0 && skip_byte_block == 0) {
// From ISO/IEC 23001-7:2016(E), section 9.6.1:
// "When the fields default_crypt_byte_block and default_skip_byte_block
// in a version 1 Track Encryption Box ('tenc') are non-zero numbers,
// pattern encryption SHALL be applied."
// So for the pattern 0:0, assume that all blocks are encrypted.
crypt_byte_block = total_blocks;
}
// Apply the pattern to |input_data|.
// Example (using Pattern(2,3), Ex is encrypted, Ux unencrypted)
// input_data: |E1|E2|U3|U4|U5|E6|E7|U8|U9|U10|E11|
// We must decrypt 2 blocks, then simply copy the next 3 blocks, and
// repeat until the end. Note that the input does not have to contain
// a full pattern at the end (although see the comment below).
size_t blocks_processed = 0;
const uint8_t* src = input_data.data();
uint8_t* dest = output_data;
bool is_encrypted_blocks = false;
while (blocks_processed < total_blocks) {
is_encrypted_blocks = !is_encrypted_blocks;
size_t blocks_to_process =
std::min(is_encrypted_blocks ? crypt_byte_block : skip_byte_block,
total_blocks - blocks_processed);
if (blocks_to_process == 0)
continue;
size_t bytes_to_process = blocks_to_process * kAesBlockSizeInBytes;
// From ISO/IEC 23001-7:2016(E), section 9.6.1:
// "If the last Block pattern in a Subsample is incomplete, the partial
// pattern SHALL be followed until truncated by the BytesOfProtectedData
// size and any partial crypt_byte_block SHALL remain unencrypted."
// So if the last Block pattern is incomplete, it needs to have at least
// |crypt_byte_block| blocks to be considered encrypted. If it doesn't,
// it is treated as unencrypted and simply copied over.
if (is_encrypted_blocks && blocks_to_process == crypt_byte_block) {
if (!aes_cbc_crypto.Decrypt(base::make_span(src, bytes_to_process),
dest)) {
return false;
}
} else {
memcpy(dest, src, bytes_to_process);
}
blocks_processed += blocks_to_process;
src += bytes_to_process;
dest += bytes_to_process;
}
// Any partial block data remaining in this subsample is considered
// unencrypted so simply copy it into |dest|.
if (remaining_bytes > 0)
memcpy(dest, src, remaining_bytes);
return true;
}
} // namespace
scoped_refptr<DecoderBuffer> DecryptCbcsBuffer(
const DecoderBuffer& input,
const crypto::SymmetricKey& key) {
size_t sample_size = input.data_size();
DCHECK(sample_size) << "No data to decrypt.";
const DecryptConfig* decrypt_config = input.decrypt_config();
DCHECK(decrypt_config) << "No need to call Decrypt() on unencrypted buffer.";
DCHECK_EQ(EncryptionMode::kCbcs, decrypt_config->encryption_mode());
DCHECK(decrypt_config->HasPattern());
const EncryptionPattern pattern =
decrypt_config->encryption_pattern().value();
// Decrypted data will be the same size as |input| size.
auto buffer = base::MakeRefCounted<DecoderBuffer>(sample_size);
uint8_t* output_data = buffer->writable_data();
buffer->set_timestamp(input.timestamp());
buffer->set_duration(input.duration());
buffer->set_is_key_frame(input.is_key_frame());
buffer->CopySideDataFrom(input.side_data(), input.side_data_size());
const std::vector<SubsampleEntry>& subsamples = decrypt_config->subsamples();
if (subsamples.empty()) {
// Assume the whole buffer is encrypted.
return DecryptWithPattern(
key, base::as_bytes(base::make_span(decrypt_config->iv())),
pattern, base::make_span(input.data(), sample_size), output_data)
? buffer
: nullptr;
}
if (!VerifySubsamplesMatchSize(subsamples, sample_size)) {
DVLOG(1) << "Subsample sizes do not equal input size";
return nullptr;
}
const uint8_t* src = input.data();
uint8_t* dest = output_data;
for (const auto& subsample : subsamples) {
if (subsample.clear_bytes) {
DVLOG(4) << "Copying clear_bytes: " << subsample.clear_bytes;
memcpy(dest, src, subsample.clear_bytes);
src += subsample.clear_bytes;
dest += subsample.clear_bytes;
}
if (subsample.cypher_bytes) {
DVLOG(4) << "Processing cypher_bytes: " << subsample.cypher_bytes
<< ", pattern(" << pattern.crypt_byte_block() << ","
<< pattern.skip_byte_block() << ")";
if (!DecryptWithPattern(
key, base::as_bytes(base::make_span(decrypt_config->iv())),
pattern, base::make_span(src, subsample.cypher_bytes), dest)) {
return nullptr;
}
src += subsample.cypher_bytes;
dest += subsample.cypher_bytes;
}
}
return buffer;
}
} // namespace media
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_CDM_CBCS_DECRYPTOR_H_
#define MEDIA_CDM_CBCS_DECRYPTOR_H_
#include "base/memory/ref_counted.h"
#include "media/base/media_export.h"
namespace crypto {
class SymmetricKey;
}
namespace media {
class DecoderBuffer;
// This class implements pattern decryption as specified by
// ISO/IEC 23001-7:2016, section 10.4 (https://www.iso.org),
// using AES-CBC-128 decryption.
//
// Subsample encryption divides each input buffer into one or more contiguous
// subsamples. Each subsample has an unprotected part (unencrypted) followed
// by a protected part (encrypted), only one of which may be zero bytes in
// length. For example:
// | DecoderBuffer.data() |
// | Subsample#1 | Subsample#2 | Subsample#3 |
// |uuuuu|eeeeeeeeee|uuuu|eeeeeeeeeeee|uu|eeeeeeeeeeee|
// Within the protected part of each subsample, the data is treated as a
// chain of 16 byte cipher blocks, starting with the initialization vector
// associated with the sample. The IV is applied to the first encrypted
// cipher block of each subsample.
//
// A partial block at the end of a subsample (if any) is unencrypted.
//
// This supports pattern decryption, where a pattern of encrypted and clear
// (skipped) blocks is used. The Pattern is specified with each DecoderBuffer
// (in the DecryptConfig). Typically encrypted video tracks use a pattern of
// (1,9) which indicates that one 16 byte block is encrypted followed by 9
// blocks unencrypted, and then the pattern repeats through all the blocks in
// the protected part. Tracks other than video usually use full-sample
// encryption.
//
// If a pattern is not specified, the protected part will use full-sample
// encryption.
// Decrypts the encrypted buffer |input| using |key| and values found in
// |input|->DecryptConfig. The key size must be 128 bits.
MEDIA_EXPORT scoped_refptr<DecoderBuffer> DecryptCbcsBuffer(
const DecoderBuffer& input,
const crypto::SymmetricKey& key);
} // namespace media
#endif // MEDIA_CDM_CBCS_DECRYPTOR_H_
This diff is collapsed.
......@@ -48,6 +48,15 @@ void CopySubsamples(const std::vector<SubsampleEntry>& subsamples,
}
}
// TODO(crbug.com/840983): This should be done in DecoderBuffer so that
// additional fields are more easily handled.
void CopyExtraSettings(const DecoderBuffer& input, DecoderBuffer* output) {
output->set_timestamp(input.timestamp());
output->set_duration(input.duration());
output->set_is_key_frame(input.is_key_frame());
output->CopySideDataFrom(input.side_data(), input.side_data_size());
}
} // namespace
scoped_refptr<DecoderBuffer> DecryptCencBuffer(
......@@ -85,31 +94,30 @@ scoped_refptr<DecoderBuffer> DecryptCencBuffer(
}
// TODO(xhwang): Find a way to avoid this data copy.
return DecoderBuffer::CopyFrom(
auto output = DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(decrypted_text.data()),
decrypted_text.size());
CopyExtraSettings(input, output.get());
return output;
}
// Verify the entries in |subsamples|.
size_t total_clear_size = 0;
size_t total_encrypted_size = 0;
for (size_t i = 0; i < subsamples.size(); i++) {
total_clear_size += subsamples[i].clear_bytes;
total_encrypted_size += subsamples[i].cypher_bytes;
// Check for overflow. This check is valid because *_size is unsigned.
DCHECK(total_clear_size >= subsamples[i].clear_bytes);
if (total_encrypted_size < subsamples[i].cypher_bytes)
return nullptr;
}
size_t total_size = total_clear_size + total_encrypted_size;
if (total_size < total_clear_size || total_size != sample_size) {
if (!VerifySubsamplesMatchSize(subsamples, sample_size)) {
DVLOG(1) << "Subsample sizes do not equal input size";
return nullptr;
}
// Compute the size of the encrypted portion. Overflow, etc. checked by
// the call to VerifySubsamplesMatchSize().
size_t total_encrypted_size = 0;
for (const auto& subsample : subsamples)
total_encrypted_size += subsample.cypher_bytes;
// No need to decrypt if there is no encrypted data.
if (total_encrypted_size == 0)
return DecoderBuffer::CopyFrom(input.data(), sample_size);
if (total_encrypted_size == 0) {
auto output = DecoderBuffer::CopyFrom(input.data(), sample_size);
CopyExtraSettings(input, output.get());
return output;
}
// The encrypted portions of all subsamples must form a contiguous block,
// such that an encrypted subsample that ends away from a block boundary is
......@@ -137,6 +145,7 @@ scoped_refptr<DecoderBuffer> DecryptCencBuffer(
CopySubsamples(subsamples, kDstContainsClearBytes,
reinterpret_cast<const uint8_t*>(decrypted_text.data()),
output->writable_data());
CopyExtraSettings(input, output.get());
return output;
}
......
......@@ -9,6 +9,8 @@
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/time/time.h"
#include "crypto/encryptor.h"
#include "crypto/symmetric_key.h"
#include "media/base/decoder_buffer.h"
......@@ -134,6 +136,34 @@ TEST_F(CencDecryptorTest, OneBlock) {
EXPECT_EQ(one_block_, DecryptWithKey(encrypted_buffer, *key_));
}
TEST_F(CencDecryptorTest, ExtraData) {
auto encrypted_block = Encrypt(one_block_, *key_, iv_);
// Only 1 subsample, all encrypted data.
std::vector<SubsampleEntry> subsamples = {{0, encrypted_block.size()}};
auto encrypted_buffer =
CreateEncryptedBuffer(encrypted_block, iv_, subsamples);
encrypted_buffer->set_timestamp(base::TimeDelta::FromDays(2));
encrypted_buffer->set_duration(base::TimeDelta::FromMinutes(5));
encrypted_buffer->set_is_key_frame(true);
encrypted_buffer->CopySideDataFrom(encrypted_block.data(),
encrypted_block.size());
auto decrypted_buffer = DecryptCencBuffer(*encrypted_buffer, *key_);
EXPECT_EQ(encrypted_buffer->timestamp(), decrypted_buffer->timestamp());
EXPECT_EQ(encrypted_buffer->duration(), decrypted_buffer->duration());
EXPECT_EQ(encrypted_buffer->end_of_stream(),
decrypted_buffer->end_of_stream());
EXPECT_EQ(encrypted_buffer->is_key_frame(), decrypted_buffer->is_key_frame());
EXPECT_EQ(encrypted_buffer->side_data_size(),
decrypted_buffer->side_data_size());
EXPECT_EQ(base::make_span(encrypted_buffer->side_data(),
encrypted_buffer->side_data_size()),
base::make_span(decrypted_buffer->side_data(),
decrypted_buffer->side_data_size()));
}
TEST_F(CencDecryptorTest, NoSubsamples) {
auto encrypted_block = Encrypt(one_block_, *key_, iv_);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment