Combined Decrypt Calls
(This is a merge of http://go/wvgerrit/93829, http://go/wvgerrit/93830, http://go/wvgerrit/93832, http://go/wvgerrit/93833, and http://go/wvgerrit/93834 from the Widevine repo.) This implements the CDM code changes necessary to take advantage of Combined Decrypt Calls on OEMCrypto v16. The result of this is that WVCryptoPlugin is much lighter now because it can pass the full sample down to the core in one call, but CryptoSession is heavier, as it now has to handle more complex fallback logic when devices can't handle multiple subsamples at once. This patch also removes support for the 'cens' and 'cbc1' schema, which are being dropped in OEMCrypto v16. This fixes an overflow in the code for handling those schemas by removing it entirely. This patch also fixes the "in chunks" legacy decrypt path to use larger chunk sizes on devices with higher resource rating tiers. Bug: 135285640 Bug: 123435824 Bug: 138584971 Bug: 139257871 Bug: 78289910 Bug: 149361893 Test: no new CE CDM Unit Test failures Test: Google Play plays Test: Netflix plays Test: no new GTS failures Change-Id: Ic4952c9fa3bc7fd5ed08698e88254380a7a18514
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "advance_iv_ctr.h"
|
||||
#include "arraysize.h"
|
||||
#include "content_key_session.h"
|
||||
#include "crypto_key.h"
|
||||
#include "entitlement_key_session.h"
|
||||
@@ -50,20 +52,36 @@
|
||||
namespace wvcdm {
|
||||
|
||||
namespace {
|
||||
const uint32_t kRsaSignatureLength = 256;
|
||||
// TODO(b/117112392): adjust chunk size based on resource rating.
|
||||
const size_t kMaximumChunkSize = 100 * 1024; // 100 KiB
|
||||
const size_t kEstimatedInitialUsageTableHeader = 40;
|
||||
// Ability to switch cipher modes in SelectKey() was introduced in this
|
||||
// OEMCrypto version
|
||||
const size_t kOemCryptoApiVersionSupportsSwitchingCipherMode = 14;
|
||||
constexpr size_t KiB = 1024;
|
||||
constexpr size_t MiB = 1024 * 1024;
|
||||
|
||||
constexpr uint32_t kRsaSignatureLength = 256;
|
||||
constexpr size_t kEstimatedInitialUsageTableHeader = 40;
|
||||
|
||||
// Constants and utility objects relating to OEM Certificates
|
||||
const char* const kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
|
||||
constexpr const char* kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
|
||||
constexpr int kMaxTerminateCountDown = 5;
|
||||
|
||||
const std::string kStringNotAvailable = "NA";
|
||||
|
||||
// Constants relating to OEMCrypto resource rating tiers. These tables are
|
||||
// ordered by resource rating tier from lowest to highest. These should be
|
||||
// updated whenever the supported range of resource rating tiers changes.
|
||||
constexpr size_t kMaxSubsampleRegionSizes[] = {
|
||||
100 * KiB, // Tier 1 - Low
|
||||
500 * KiB, // Tier 2 - Medium
|
||||
1 * MiB, // Tier 3 - High
|
||||
4 * MiB, // Tier 4 - Very High
|
||||
};
|
||||
// The +1 in this calculation is because the bounds RESOURCE_RATING_TIER_MAX and
|
||||
// RESOURCE_RATING_TIER_MIN are inclusive.
|
||||
static_assert(ArraySize(kMaxSubsampleRegionSizes) ==
|
||||
RESOURCE_RATING_TIER_MAX - RESOURCE_RATING_TIER_MIN + 1,
|
||||
"The kMaxSubsampleRegionSizes table needs to be updated to "
|
||||
"reflect the supported range of resource rating tiers.");
|
||||
|
||||
constexpr size_t kDefaultMaximumChunkSize = 100 * KiB;
|
||||
|
||||
// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings
|
||||
// are not universal but are OEMCrypto method specific. Those will be
|
||||
// specified in the CryptoSession method rather than here.
|
||||
@@ -91,6 +109,25 @@ CdmResponseType MapOEMCryptoResult(OEMCryptoResult result,
|
||||
return default_status;
|
||||
}
|
||||
}
|
||||
|
||||
void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
|
||||
switch (dest_buffer->type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
dest_buffer->buffer.clear.address += bytes;
|
||||
dest_buffer->buffer.clear.address_length -= bytes;
|
||||
return;
|
||||
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
dest_buffer->buffer.secure.offset += bytes;
|
||||
return;
|
||||
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
// Nothing to do for this buffer type.
|
||||
return;
|
||||
}
|
||||
LOGE("Unrecognized OEMCryptoBufferType %u - doing nothing",
|
||||
dest_buffer->type);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
shared_mutex CryptoSession::static_field_mutex_;
|
||||
@@ -180,7 +217,6 @@ CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
|
||||
requested_security_level_(kLevelDefault),
|
||||
usage_support_type_(kUnknownUsageSupport),
|
||||
usage_table_header_(nullptr),
|
||||
cipher_mode_(kCipherModeCtr),
|
||||
api_version_(0) {
|
||||
assert(metrics);
|
||||
Init();
|
||||
@@ -901,8 +937,7 @@ CdmResponseType CryptoSession::LoadKeys(
|
||||
|
||||
LOGV("Loading key: id = %u", oec_session_id_);
|
||||
sts = key_session_->LoadKeys(message, signature, mac_key_iv, mac_key, keys,
|
||||
provider_session_token, &cipher_mode_,
|
||||
srm_requirement);
|
||||
provider_session_token, srm_requirement);
|
||||
});
|
||||
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
@@ -1295,99 +1330,153 @@ CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
|
||||
"OEMCrypto_GenerateRSASignature");
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
bool CryptoSession::GetMaxSubsampleRegionSize(size_t* max) {
|
||||
uint32_t tier = 0;
|
||||
if (!GetResourceRatingTier(&tier)) return false;
|
||||
// Subtract RESOURCE_RATING_TIER_MIN to get a 0-based index into the table.
|
||||
const uint32_t index = tier - RESOURCE_RATING_TIER_MIN;
|
||||
if (index >= ArraySize(kMaxSubsampleRegionSizes)) return false;
|
||||
*max = kMaxSubsampleRegionSizes[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::Decrypt(
|
||||
const CdmDecryptionParametersV16& params) {
|
||||
if (!is_destination_buffer_type_valid_) {
|
||||
if (!SetDestinationBufferType()) return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
OEMCrypto_DestBufferDesc buffer_descriptor;
|
||||
buffer_descriptor.type =
|
||||
OEMCryptoBufferType output_descriptor_type =
|
||||
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
|
||||
|
||||
if (params.is_secure &&
|
||||
buffer_descriptor.type == OEMCrypto_BufferType_Clear) {
|
||||
output_descriptor_type == OEMCrypto_BufferType_Clear) {
|
||||
return SECURE_BUFFER_REQUIRED;
|
||||
}
|
||||
|
||||
switch (buffer_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
buffer_descriptor.buffer.clear.address =
|
||||
static_cast<uint8_t*>(params.decrypt_buffer) +
|
||||
params.decrypt_buffer_offset;
|
||||
buffer_descriptor.buffer.clear.address_length =
|
||||
params.decrypt_buffer_length - params.decrypt_buffer_offset;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
buffer_descriptor.buffer.secure.handle = params.decrypt_buffer;
|
||||
buffer_descriptor.buffer.secure.offset = params.decrypt_buffer_offset;
|
||||
buffer_descriptor.buffer.secure.handle_length =
|
||||
params.decrypt_buffer_length;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
buffer_descriptor.type = OEMCrypto_BufferType_Direct;
|
||||
buffer_descriptor.buffer.direct.is_video = params.is_video;
|
||||
break;
|
||||
if (params.samples.size() == 0) return CANNOT_DECRYPT_ZERO_SAMPLES;
|
||||
if (std::any_of(std::begin(params.samples), std::end(params.samples),
|
||||
[](const CdmDecryptionSample& sample) -> bool {
|
||||
return sample.subsamples.size() == 0;
|
||||
})) {
|
||||
return CANNOT_DECRYPT_ZERO_SUBSAMPLES;
|
||||
}
|
||||
|
||||
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
if (!params.is_encrypted &&
|
||||
params.subsample_flags ==
|
||||
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)) {
|
||||
WithOecSessionLock("Decrypt() calling CopyBuffer", [&] {
|
||||
M_TIME(sts = OEMCrypto_CopyBuffer(
|
||||
oec_session_id_, params.encrypt_buffer, params.encrypt_length,
|
||||
&buffer_descriptor, params.subsample_flags),
|
||||
metrics_, oemcrypto_copy_buffer_, sts,
|
||||
metrics::Pow2Bucket(params.encrypt_length));
|
||||
});
|
||||
// Convert all the sample and subsample definitions to OEMCrypto structs.
|
||||
// This code also caches whether any of the data is protected, to save later
|
||||
// code the trouble of iterating over all the subsamples to check.
|
||||
bool is_any_sample_protected = false;
|
||||
std::vector<OEMCrypto_SampleDescription> oec_samples;
|
||||
oec_samples.reserve(params.samples.size());
|
||||
std::vector<std::vector<OEMCrypto_SubSampleDescription>>
|
||||
oec_subsample_vectors;
|
||||
oec_subsample_vectors.reserve(params.samples.size());
|
||||
|
||||
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE &&
|
||||
params.encrypt_length > kMaximumChunkSize) {
|
||||
// OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up
|
||||
// into 100 KiB sections.
|
||||
sts = CopyBufferInChunks(params, buffer_descriptor);
|
||||
}
|
||||
}
|
||||
if (api_version_ < kOemCryptoApiVersionSupportsSwitchingCipherMode) {
|
||||
if (params.is_encrypted && params.cipher_mode != cipher_mode_) {
|
||||
return INCORRECT_CRYPTO_MODE;
|
||||
}
|
||||
}
|
||||
if (params.is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern_descriptor;
|
||||
pattern_descriptor.encrypt = params.pattern_descriptor.encrypt_blocks;
|
||||
pattern_descriptor.skip = params.pattern_descriptor.skip_blocks;
|
||||
// Check if key needs to be selected
|
||||
if (params.is_encrypted) {
|
||||
CdmResponseType result = SelectKey(*params.key_id, params.cipher_mode);
|
||||
if (result != NO_ERROR) return result;
|
||||
for (const CdmDecryptionSample& sample : params.samples) {
|
||||
oec_samples.emplace_back();
|
||||
OEMCrypto_SampleDescription& oec_sample = oec_samples.back();
|
||||
|
||||
// Set up the sample's input buffer
|
||||
oec_sample.buffers.input_data = sample.encrypt_buffer;
|
||||
oec_sample.buffers.input_data_length = sample.encrypt_buffer_length;
|
||||
|
||||
// Set up the sample's output buffer
|
||||
OEMCrypto_DestBufferDesc& output_descriptor =
|
||||
oec_sample.buffers.output_descriptor;
|
||||
output_descriptor.type = output_descriptor_type;
|
||||
switch (output_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
output_descriptor.buffer.clear.address =
|
||||
static_cast<uint8_t*>(sample.decrypt_buffer) +
|
||||
sample.decrypt_buffer_offset;
|
||||
output_descriptor.buffer.clear.address_length =
|
||||
sample.decrypt_buffer_size - sample.decrypt_buffer_offset;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
output_descriptor.buffer.secure.handle = sample.decrypt_buffer;
|
||||
output_descriptor.buffer.secure.offset = sample.decrypt_buffer_offset;
|
||||
output_descriptor.buffer.secure.handle_length =
|
||||
sample.decrypt_buffer_size;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
output_descriptor.buffer.direct.is_video = params.is_video;
|
||||
break;
|
||||
}
|
||||
|
||||
WithOecSessionLock("Decrypt() calling key_session_->Decrypt()", [&] {
|
||||
sts = key_session_->Decrypt(params, buffer_descriptor, 0,
|
||||
pattern_descriptor);
|
||||
});
|
||||
// Convert all the sample's subsamples. This code also tallies the total
|
||||
// size as a sanity check.
|
||||
oec_subsample_vectors.emplace_back();
|
||||
std::vector<OEMCrypto_SubSampleDescription>& oec_subsamples =
|
||||
oec_subsample_vectors.back();
|
||||
oec_subsamples.reserve(sample.subsamples.size());
|
||||
size_t sample_size = 0;
|
||||
bool is_any_subsample_protected = false;
|
||||
size_t current_block_offset = 0;
|
||||
for (const CdmDecryptionSubsample& subsample : sample.subsamples) {
|
||||
oec_subsamples.push_back(OEMCrypto_SubSampleDescription{
|
||||
subsample.clear_bytes, subsample.protected_bytes,
|
||||
0, // subsample_flags
|
||||
current_block_offset});
|
||||
|
||||
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
||||
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it
|
||||
// up into sections no more than 100 KiB. The exact chunk size needs to
|
||||
// be an even number of pattern repetitions long or else the pattern
|
||||
// will get out of sync.
|
||||
const size_t pattern_length =
|
||||
(pattern_descriptor.encrypt + pattern_descriptor.skip) *
|
||||
kAes128BlockSize;
|
||||
const size_t chunk_size =
|
||||
pattern_length > 0
|
||||
? kMaximumChunkSize - (kMaximumChunkSize % pattern_length)
|
||||
: kMaximumChunkSize;
|
||||
is_any_subsample_protected |= (subsample.protected_bytes > 0);
|
||||
sample_size += subsample.clear_bytes + subsample.protected_bytes;
|
||||
if (params.cipher_mode == kCipherModeCtr) {
|
||||
current_block_offset =
|
||||
(current_block_offset + subsample.protected_bytes) %
|
||||
kAes128BlockSize;
|
||||
}
|
||||
|
||||
if (params.encrypt_length > chunk_size) {
|
||||
sts = DecryptInChunks(params, buffer_descriptor, pattern_descriptor,
|
||||
chunk_size);
|
||||
// TODO(b/149524614): This block is not necessary except for
|
||||
// backwards-compatibility while we are transitioning from the v15 API to
|
||||
// the v16 API.
|
||||
if (params.observe_legacy_fields) {
|
||||
OEMCrypto_SubSampleDescription& oec_subsample = oec_subsamples.back();
|
||||
oec_subsample.subsample_flags = subsample.flags;
|
||||
oec_subsample.block_offset = subsample.block_offset;
|
||||
}
|
||||
}
|
||||
|
||||
is_any_sample_protected |= is_any_subsample_protected;
|
||||
|
||||
// TODO(b/149524614): This check is not necessary except for
|
||||
// backwards-compatibility while we are transitioning from the v15 API to
|
||||
// the v16 API.
|
||||
if (!params.observe_legacy_fields) {
|
||||
// Set the actual subsample_flags now that all the subsamples are
|
||||
// converted.
|
||||
oec_subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample;
|
||||
oec_subsamples.back().subsample_flags |= OEMCrypto_LastSubsample;
|
||||
}
|
||||
|
||||
// Check that the total size is valid
|
||||
if (sample_size != oec_sample.buffers.input_data_length)
|
||||
return SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH;
|
||||
|
||||
// Set up the sample's IV
|
||||
if (is_any_subsample_protected) {
|
||||
if (sizeof(oec_sample.iv) != sample.iv.size()) return INVALID_IV_SIZE;
|
||||
memcpy(oec_sample.iv, sample.iv.data(), sizeof(oec_sample.iv));
|
||||
} else {
|
||||
memset(oec_sample.iv, 0, sizeof(oec_sample.iv));
|
||||
}
|
||||
|
||||
// Attach the subsamples to the sample description
|
||||
oec_sample.subsamples = oec_subsamples.data();
|
||||
oec_sample.subsamples_length = oec_subsamples.size();
|
||||
}
|
||||
|
||||
// Convert the pattern descriptor
|
||||
OEMCrypto_CENCEncryptPatternDesc oec_pattern{params.pattern.encrypt_blocks,
|
||||
params.pattern.skip_blocks};
|
||||
|
||||
// Check if a key needs to be selected
|
||||
if (is_any_sample_protected) {
|
||||
CdmResponseType result = SelectKey(params.key_id, params.cipher_mode);
|
||||
if (result != NO_ERROR) return result;
|
||||
}
|
||||
|
||||
// Perform decrypt
|
||||
OEMCryptoResult sts =
|
||||
DecryptMultipleSamples(oec_samples, params.cipher_mode, oec_pattern);
|
||||
switch (sts) {
|
||||
case OEMCrypto_SUCCESS:
|
||||
return NO_ERROR;
|
||||
@@ -2494,190 +2583,301 @@ size_t CryptoSession::GenericEncryptionBlockSize(
|
||||
}
|
||||
}
|
||||
|
||||
OEMCryptoResult CryptoSession::CopyBufferInChunks(
|
||||
const CdmDecryptionParameters& params,
|
||||
OEMCrypto_DestBufferDesc full_buffer_descriptor) {
|
||||
size_t remaining_encrypt_length = params.encrypt_length;
|
||||
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
|
||||
// OEMCryptoResult OEMCrypto_DecryptCENC(
|
||||
// OEMCrypto_SESSION session,
|
||||
// const OEMCrypto_SampleDescription* samples, // an array of samples.
|
||||
// size_t samples_length, // the number of samples.
|
||||
// const OEMCrypto_CENCEncryptPatternDesc* pattern);
|
||||
|
||||
while (remaining_encrypt_length > 0) {
|
||||
// Calculate the size of the next chunk and its offset into the original
|
||||
// buffer.
|
||||
const size_t chunk_size =
|
||||
std::min(remaining_encrypt_length, kMaximumChunkSize);
|
||||
const size_t additional_offset =
|
||||
params.encrypt_length - remaining_encrypt_length;
|
||||
OEMCryptoResult CryptoSession::DecryptMultipleSamples(
|
||||
const std::vector<OEMCrypto_SampleDescription>& samples,
|
||||
CdmCipherMode cipher_mode,
|
||||
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
|
||||
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
||||
|
||||
// Update the remaining length of the original buffer only after
|
||||
// calculating the new values.
|
||||
remaining_encrypt_length -= chunk_size;
|
||||
// If there's only one sample, automatically fall through to avoid a redundant
|
||||
// roundtrip through OEMCrypto_DecryptCENC()
|
||||
if (samples.size() > 1) {
|
||||
WithOecSessionLock("DecryptMultipleSamples", [&] {
|
||||
sts = key_session_->Decrypt(samples.data(), samples.size(), pattern);
|
||||
});
|
||||
}
|
||||
|
||||
// Update the destination buffer with the new offset. Because OEMCrypto
|
||||
// can modify the OEMCrypto_DestBufferDesc during the call to
|
||||
// OEMCrypto_CopyBuffer, (and is known to do so on some platforms) a new
|
||||
// OEMCrypto_DestBufferDesc must be allocated for each call.
|
||||
OEMCrypto_DestBufferDesc buffer_descriptor = full_buffer_descriptor;
|
||||
switch (buffer_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
buffer_descriptor.buffer.clear.address += additional_offset;
|
||||
buffer_descriptor.buffer.clear.address_length -= additional_offset;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
buffer_descriptor.buffer.secure.offset += additional_offset;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
// OEMCrypto_BufferType_Direct does not need modification.
|
||||
break;
|
||||
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
||||
// Fall back to sending each sample individually
|
||||
for (const OEMCrypto_SampleDescription& sample : samples) {
|
||||
sts = DecryptSample(sample, cipher_mode, pattern);
|
||||
if (sts != OEMCrypto_SUCCESS) break;
|
||||
}
|
||||
}
|
||||
|
||||
return sts;
|
||||
}
|
||||
|
||||
OEMCryptoResult CryptoSession::DecryptSample(
|
||||
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
||||
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
|
||||
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
||||
|
||||
// If there's only one subsample and it contains only one type of region,
|
||||
// automatically fall through to avoid a redundant roundtrip through
|
||||
// OEMCrypto_DecryptCENC()
|
||||
if (sample.subsamples_length > 1 ||
|
||||
(sample.subsamples[0].num_bytes_clear > 0 &&
|
||||
sample.subsamples[0].num_bytes_encrypted > 0)) {
|
||||
WithOecSessionLock("DecryptSample", [&] {
|
||||
sts = key_session_->Decrypt(&sample, 1, pattern);
|
||||
});
|
||||
}
|
||||
|
||||
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
||||
// Fall back to sending each subsample region individually
|
||||
OEMCrypto_SampleDescription fake_sample = sample;
|
||||
for (size_t i = 0; i < sample.subsamples_length; ++i) {
|
||||
const OEMCrypto_SubSampleDescription& original_subsample =
|
||||
sample.subsamples[i];
|
||||
|
||||
if (original_subsample.num_bytes_clear > 0) {
|
||||
const size_t length = original_subsample.num_bytes_clear;
|
||||
OEMCrypto_SubSampleDescription clear_subsample{
|
||||
length,
|
||||
0, // num_bytes_encrypted
|
||||
0, // subsample_flags
|
||||
0 // block_offset, not relevant for clear data
|
||||
};
|
||||
if (original_subsample.subsample_flags & OEMCrypto_FirstSubsample) {
|
||||
clear_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
|
||||
}
|
||||
if ((original_subsample.subsample_flags & OEMCrypto_LastSubsample) &&
|
||||
original_subsample.num_bytes_encrypted == 0) {
|
||||
clear_subsample.subsample_flags |= OEMCrypto_LastSubsample;
|
||||
}
|
||||
|
||||
fake_sample.buffers.input_data_length = length;
|
||||
fake_sample.subsamples = &clear_subsample;
|
||||
fake_sample.subsamples_length = 1;
|
||||
|
||||
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
|
||||
if (sts != OEMCrypto_SUCCESS) break;
|
||||
|
||||
fake_sample.buffers.input_data += length;
|
||||
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
|
||||
}
|
||||
|
||||
if (original_subsample.num_bytes_encrypted > 0) {
|
||||
const size_t length = original_subsample.num_bytes_encrypted;
|
||||
OEMCrypto_SubSampleDescription encrypted_subsample{
|
||||
0, // num_bytes_clear
|
||||
length,
|
||||
0, // subsample_flags
|
||||
original_subsample.block_offset};
|
||||
if ((original_subsample.subsample_flags & OEMCrypto_FirstSubsample) &&
|
||||
original_subsample.num_bytes_clear == 0) {
|
||||
encrypted_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
|
||||
}
|
||||
if (original_subsample.subsample_flags & OEMCrypto_LastSubsample) {
|
||||
encrypted_subsample.subsample_flags |= OEMCrypto_LastSubsample;
|
||||
}
|
||||
|
||||
fake_sample.buffers.input_data_length = length;
|
||||
fake_sample.subsamples = &encrypted_subsample;
|
||||
fake_sample.subsamples_length = 1;
|
||||
|
||||
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
|
||||
if (sts != OEMCrypto_SUCCESS) break;
|
||||
|
||||
fake_sample.buffers.input_data += length;
|
||||
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
|
||||
if (cipher_mode == kCipherModeCtr) {
|
||||
AdvanceIvCtr(&fake_sample.iv,
|
||||
original_subsample.block_offset +
|
||||
original_subsample.num_bytes_encrypted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sts;
|
||||
}
|
||||
|
||||
OEMCryptoResult CryptoSession::LegacyDecrypt(
|
||||
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
||||
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
|
||||
size_t max_chunk_size;
|
||||
if (!GetMaxSubsampleRegionSize(&max_chunk_size)) {
|
||||
LOGW("Unable to get maximum subsample region size. Defaulting to 100 KiB.");
|
||||
max_chunk_size = kDefaultMaximumChunkSize;
|
||||
}
|
||||
|
||||
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
|
||||
// We can be sure this is only called with one subsample containing one
|
||||
// region of data.
|
||||
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
|
||||
const bool is_encrypted = (subsample.num_bytes_encrypted > 0);
|
||||
const bool is_only_subsample =
|
||||
subsample.subsample_flags ==
|
||||
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
|
||||
if (!is_encrypted && is_only_subsample) {
|
||||
WithOecSessionLock("LegacyDecrypt() calling CopyBuffer", [&] {
|
||||
M_TIME(sts = OEMCrypto_CopyBuffer(
|
||||
oec_session_id_, sample.buffers.input_data,
|
||||
sample.buffers.input_data_length,
|
||||
&sample.buffers.output_descriptor, subsample.subsample_flags),
|
||||
metrics_, oemcrypto_copy_buffer_, sts,
|
||||
metrics::Pow2Bucket(sample.buffers.input_data_length));
|
||||
});
|
||||
|
||||
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE &&
|
||||
sample.buffers.input_data_length > max_chunk_size) {
|
||||
// OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up
|
||||
// into 100 KiB sections.
|
||||
sts = LegacyCopyBufferInChunks(sample, max_chunk_size);
|
||||
}
|
||||
}
|
||||
if (is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
WithOecSessionLock("LegacyDecrypt() calling key_session_->Decrypt()", [&] {
|
||||
sts = key_session_->Decrypt(&sample, 1, pattern);
|
||||
});
|
||||
|
||||
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
||||
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it
|
||||
// up into sections no more than 100 KiB. The exact chunk size needs to
|
||||
// be an even number of pattern repetitions long or else the pattern
|
||||
// will get out of sync.
|
||||
const size_t pattern_length =
|
||||
(pattern.encrypt + pattern.skip) * kAes128BlockSize;
|
||||
const size_t chunk_size =
|
||||
pattern_length > 0
|
||||
? max_chunk_size - (max_chunk_size % pattern_length)
|
||||
: max_chunk_size;
|
||||
|
||||
if (sample.buffers.input_data_length > chunk_size) {
|
||||
sts = LegacyDecryptInChunks(sample, cipher_mode, pattern, chunk_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sts;
|
||||
}
|
||||
|
||||
OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks(
|
||||
const OEMCrypto_SampleDescription& sample, size_t max_chunk_size) {
|
||||
const uint8_t* input_data = sample.buffers.input_data;
|
||||
size_t remaining_input_data = sample.buffers.input_data_length;
|
||||
OEMCrypto_DestBufferDesc output_descriptor = sample.buffers.output_descriptor;
|
||||
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
|
||||
OEMCryptoResult sts = OEMCrypto_SUCCESS;
|
||||
|
||||
while (remaining_input_data > 0) {
|
||||
// Calculate the size of the next chunk.
|
||||
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
||||
|
||||
// Re-add "last subsample" flag if this is the last subsample.
|
||||
if (remaining_encrypt_length == 0) {
|
||||
if (chunk_size == remaining_input_data) {
|
||||
subsample_flags |= OEMCrypto_LastSubsample;
|
||||
}
|
||||
|
||||
OEMCryptoResult sts;
|
||||
WithOecSessionLock("CopyBufferInChunks", [&] {
|
||||
M_TIME(sts = OEMCrypto_CopyBuffer(
|
||||
oec_session_id_, params.encrypt_buffer + additional_offset,
|
||||
chunk_size, &buffer_descriptor, subsample_flags),
|
||||
WithOecSessionLock("LegacyCopyBufferInChunks", [&] {
|
||||
M_TIME(sts = OEMCrypto_CopyBuffer(oec_session_id_, input_data, chunk_size,
|
||||
&output_descriptor, subsample_flags),
|
||||
metrics_, oemcrypto_copy_buffer_, sts,
|
||||
metrics::Pow2Bucket(chunk_size));
|
||||
});
|
||||
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
return sts;
|
||||
}
|
||||
if (sts != OEMCrypto_SUCCESS) break;
|
||||
|
||||
// Clear any subsample flags before the next loop iteration.
|
||||
subsample_flags = 0;
|
||||
|
||||
// Update the source and destination buffers based on the amount of data
|
||||
// copied.
|
||||
input_data += chunk_size;
|
||||
remaining_input_data -= chunk_size;
|
||||
AdvanceDestBuffer(&output_descriptor, chunk_size);
|
||||
}
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
return sts;
|
||||
}
|
||||
|
||||
OEMCryptoResult CryptoSession::DecryptInChunks(
|
||||
const CdmDecryptionParameters& params,
|
||||
const OEMCrypto_DestBufferDesc& full_buffer_descriptor,
|
||||
const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor,
|
||||
size_t max_chunk_size) {
|
||||
size_t remaining_encrypt_length = params.encrypt_length;
|
||||
uint8_t subsample_flags = (params.subsample_flags & OEMCrypto_FirstSubsample)
|
||||
? OEMCrypto_FirstSubsample
|
||||
: 0;
|
||||
std::vector<uint8_t> iv = *params.iv;
|
||||
OEMCryptoResult CryptoSession::LegacyDecryptInChunks(
|
||||
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
||||
const OEMCrypto_CENCEncryptPatternDesc& pattern, size_t max_chunk_size) {
|
||||
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
|
||||
|
||||
const size_t pattern_length_in_bytes =
|
||||
(pattern_descriptor.encrypt + pattern_descriptor.skip) * kAes128BlockSize;
|
||||
const bool is_protected = (subsample.num_bytes_encrypted > 0);
|
||||
OEMCrypto_SampleDescription fake_sample = sample;
|
||||
OEMCrypto_SubSampleDescription fake_subsample = subsample;
|
||||
fake_sample.subsamples = &fake_subsample;
|
||||
fake_subsample.subsample_flags =
|
||||
subsample.subsample_flags & OEMCrypto_FirstSubsample;
|
||||
|
||||
while (remaining_encrypt_length > 0) {
|
||||
// Calculate the size of the next chunk and its offset into the
|
||||
// original buffer.
|
||||
const size_t chunk_size =
|
||||
std::min(remaining_encrypt_length, max_chunk_size);
|
||||
const size_t additional_offset =
|
||||
params.encrypt_length - remaining_encrypt_length;
|
||||
size_t remaining_input_data = sample.buffers.input_data_length;
|
||||
OEMCryptoResult sts = OEMCrypto_SUCCESS;
|
||||
|
||||
// Update the remaining length of the original buffer only after
|
||||
// calculating the new values.
|
||||
remaining_encrypt_length -= chunk_size;
|
||||
|
||||
// Update the destination buffer with the new offset. Because OEMCrypto
|
||||
// can modify the OEMCrypto_DestBufferDesc during the call to
|
||||
// OEMCrypto_DecryptCENC, (and is known to do so on some platforms) a new
|
||||
// OEMCrypto_DestBufferDesc must be allocated for each call.
|
||||
OEMCrypto_DestBufferDesc buffer_descriptor = full_buffer_descriptor;
|
||||
switch (buffer_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
buffer_descriptor.buffer.clear.address += additional_offset;
|
||||
buffer_descriptor.buffer.clear.address_length -= additional_offset;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
buffer_descriptor.buffer.secure.offset += additional_offset;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
// OEMCrypto_BufferType_Direct does not need modification.
|
||||
break;
|
||||
while (remaining_input_data > 0) {
|
||||
// Calculate the size of the next chunk.
|
||||
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
||||
fake_sample.buffers.input_data_length = chunk_size;
|
||||
if (is_protected) {
|
||||
fake_subsample.num_bytes_encrypted = chunk_size;
|
||||
} else {
|
||||
fake_subsample.num_bytes_clear = chunk_size;
|
||||
}
|
||||
|
||||
// Re-add "last subsample" flag if this is the last subsample.
|
||||
if (remaining_encrypt_length == 0 &&
|
||||
params.subsample_flags & OEMCrypto_LastSubsample) {
|
||||
subsample_flags |= OEMCrypto_LastSubsample;
|
||||
if (chunk_size == remaining_input_data) {
|
||||
fake_subsample.subsample_flags |=
|
||||
subsample.subsample_flags & OEMCrypto_LastSubsample;
|
||||
}
|
||||
|
||||
// block_offset and pattern_descriptor do not need to change because
|
||||
// max_chunk_size is guaranteed to be an even multiple of the
|
||||
// |pattern| and |fake_subsample.block_offset| do not need to change because
|
||||
// |max_chunk_size| is guaranteed to be an even multiple of the
|
||||
// pattern length long, which is also guaranteed to be an exact number
|
||||
// of AES blocks long.
|
||||
OEMCryptoResult sts;
|
||||
sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
WithOecSessionLock("DecryptInChunks", [&] {
|
||||
M_TIME(sts = key_session_->Decrypt(params, buffer_descriptor,
|
||||
additional_offset, pattern_descriptor),
|
||||
metrics_, oemcrypto_decrypt_cenc_, sts,
|
||||
metrics::Pow2Bucket(chunk_size));
|
||||
WithOecSessionLock("LegacyDecryptInChunks", [&] {
|
||||
sts = key_session_->Decrypt(&fake_sample, 1, pattern);
|
||||
});
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
return sts;
|
||||
}
|
||||
|
||||
// If we are not yet done, update the IV so that it is valid for the next
|
||||
// iteration.
|
||||
if (remaining_encrypt_length != 0) {
|
||||
if (cipher_mode_ == kCipherModeCtr) {
|
||||
// For CTR modes, update the IV depending on how many encrypted blocks
|
||||
// we passed. Since we calculated the chunk size to be an even number
|
||||
// of crypto blocks and pattern repetitions in size, we can do a
|
||||
// simplified calculation for this.
|
||||
uint64_t encrypted_blocks_passed = 0;
|
||||
if (pattern_length_in_bytes == 0) {
|
||||
encrypted_blocks_passed = chunk_size / kAes128BlockSize;
|
||||
} else {
|
||||
const size_t pattern_repetitions_passed =
|
||||
chunk_size / pattern_length_in_bytes;
|
||||
encrypted_blocks_passed =
|
||||
pattern_repetitions_passed * pattern_descriptor.encrypt;
|
||||
}
|
||||
IncrementIV(encrypted_blocks_passed, &iv);
|
||||
} else if (cipher_mode_ == kCipherModeCbc) {
|
||||
// For CBC modes, use the previous ciphertext block.
|
||||
if (sts != OEMCrypto_SUCCESS) break;
|
||||
|
||||
// Stash the last crypto block in the IV. We don't have to handle
|
||||
// partial crypto blocks here because we know we broke the buffer into
|
||||
// chunks along even crypto block boundaries.
|
||||
// Clear any subsample flags before the next loop iteration.
|
||||
fake_subsample.subsample_flags = 0;
|
||||
|
||||
// Update the IV so that it is valid for the next iteration. This should not
|
||||
// be done on the last iteration both to save time and because the 'cbcs'
|
||||
// calculation can underflow if the chunk is less than the max chunk size.
|
||||
if (remaining_input_data > chunk_size) {
|
||||
if (cipher_mode == kCipherModeCtr) {
|
||||
// For 'cenc', update the IV depending on how many encrypted blocks
|
||||
// we passed.
|
||||
AdvanceIvCtr(&fake_sample.iv, chunk_size + fake_subsample.block_offset);
|
||||
} else if (cipher_mode == kCipherModeCbc) {
|
||||
// For 'cbcs', use the last ciphertext block as the next IV. The last
|
||||
// block that was encrypted is probably not the last block of the
|
||||
// subsample. Since the max buffer size is guaranteed to be an even
|
||||
// number of pattern repetitions long, we can use the pattern to know
|
||||
// how many blocks to look back.
|
||||
const uint8_t* const buffer_end =
|
||||
params.encrypt_buffer + additional_offset + chunk_size;
|
||||
fake_sample.buffers.input_data + chunk_size;
|
||||
const uint8_t* const block_end =
|
||||
buffer_end - kAes128BlockSize * pattern.skip;
|
||||
|
||||
const uint8_t* block_end = nullptr;
|
||||
if (pattern_length_in_bytes == 0) {
|
||||
// For cbc1, the last encrypted block is the last block of the
|
||||
// subsample.
|
||||
block_end = buffer_end;
|
||||
} else {
|
||||
// For cbcs, we must look for the last encrypted block, which is
|
||||
// probably not the last block of the subsample. Luckily, since the
|
||||
// buffer size is guaranteed to be an even number of pattern
|
||||
// repetitions long, we can use the pattern to know how many blocks
|
||||
// to look back.
|
||||
block_end = buffer_end - kAes128BlockSize * pattern_descriptor.skip;
|
||||
}
|
||||
|
||||
iv.assign(block_end - kAes128BlockSize, block_end);
|
||||
static_assert(sizeof(fake_sample.iv) == kAes128BlockSize,
|
||||
"The size of an AES-128 block and the size of an AES-128 "
|
||||
"IV have become misaligned.");
|
||||
memcpy(fake_sample.iv, block_end - kAes128BlockSize, kAes128BlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear any subsample flags before the next loop iteration.
|
||||
subsample_flags = 0;
|
||||
// Update the source and destination buffers based on the amount of data
|
||||
// copied.
|
||||
fake_sample.buffers.input_data += chunk_size;
|
||||
remaining_input_data -= chunk_size;
|
||||
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size);
|
||||
}
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
void CryptoSession::IncrementIV(uint64_t increase_by,
|
||||
std::vector<uint8_t>* iv_out) {
|
||||
std::vector<uint8_t>& iv = *iv_out;
|
||||
uint64_t* counter_buffer = reinterpret_cast<uint64_t*>(&iv[8]);
|
||||
(*counter_buffer) = htonll64(ntohll64(*counter_buffer) + increase_by);
|
||||
return sts;
|
||||
}
|
||||
|
||||
template <class Func>
|
||||
|
||||
Reference in New Issue
Block a user