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:
John W. Bruce
2020-02-18 14:46:31 -08:00
parent 3708c4d53f
commit a62886b925
28 changed files with 1253 additions and 1260 deletions

View File

@@ -12,6 +12,7 @@
#include <algorithm>
#include <hidlmemory/mapping.h>
#include <iterator>
#include "HidlTypes.h"
#include "mapErrors-inl.h"
@@ -23,8 +24,6 @@
namespace {
static const size_t kAESBlockSize = 16;
inline Status toStatus_1_0(Status_V1_2 status) {
switch (status) {
case Status_V1_2::ERROR_DRM_INSUFFICIENT_SECURITY:
@@ -45,7 +44,9 @@ namespace V1_2 {
namespace widevine {
using android::hardware::drm::V1_2::widevine::toVector;
using wvcdm::CdmDecryptionParameters;
using wvcdm::CdmDecryptionParametersV16;
using wvcdm::CdmDecryptionSample;
using wvcdm::CdmDecryptionSubsample;
using wvcdm::CdmQueryMap;
using wvcdm::CdmResponseType;
using wvcdm::CdmSessionId;
@@ -158,26 +159,36 @@ Return<void> WVCryptoPlugin::decrypt_1_2(
const DestinationBuffer& destination,
decrypt_1_2_cb _hidl_cb) {
if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) {
if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"source decrypt buffer base not set");
return Void();
}
if (destination.type == BufferType::SHARED_MEMORY) {
const SharedBuffer& dest = destination.nonsecureMemory;
if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"source decrypt buffer base not set");
"destination decrypt buffer base not set");
return Void();
}
if (destination.type == BufferType::SHARED_MEMORY) {
const SharedBuffer& dest = destination.nonsecureMemory;
if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"destination decrypt buffer base not set");
return Void();
}
}
}
if (mode != Mode::UNENCRYPTED &&
mode != Mode::AES_CTR &&
mode != Mode::AES_CBC) {
_hidl_cb(Status_V1_2::BAD_VALUE,
0, "Encryption mode is not supported by Widevine CDM.");
mode != Mode::AES_CTR &&
mode != Mode::AES_CBC) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The requested encryption mode is not supported by Widevine CDM.");
return Void();
} else if (mode == Mode::AES_CTR &&
(pattern.encryptBlocks != 0 || pattern.skipBlocks != 0)) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The 'cens' schema is not supported by Widevine CDM.");
return Void();
} else if (mode == Mode::AES_CBC &&
(pattern.encryptBlocks == 0 && pattern.skipBlocks == 0)) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The 'cbc1' schema is not supported by Widevine CDM.");
return Void();
}
@@ -221,176 +232,75 @@ Return<void> WVCryptoPlugin::decrypt_1_2(
destPtr = static_cast<void *>(handle);
}
// Calculate the output buffer size and determine if any subsamples are
// encrypted.
size_t destSize = 0;
bool haveEncryptedSubsamples = false;
for (size_t i = 0; i < subSamples.size(); i++) {
const SubSample &subSample = subSamples[i];
destSize += subSample.numBytesOfClearData;
destSize += subSample.numBytesOfEncryptedData;
if (subSample.numBytesOfEncryptedData > 0) {
haveEncryptedSubsamples = true;
}
}
// Set up the decrypt params that do not vary.
CdmDecryptionParameters params = CdmDecryptionParameters();
// Set up the decrypt params
CdmDecryptionParametersV16 params;
params.key_id = cryptoKey;
params.is_secure = secure;
params.key_id = &cryptoKey;
params.iv = &ivVector;
params.decrypt_buffer = destPtr;
params.decrypt_buffer_length = destSize;
params.pattern_descriptor.encrypt_blocks = pattern.encryptBlocks;
params.pattern_descriptor.skip_blocks = pattern.skipBlocks;
if (mode == Mode::AES_CTR) {
params.cipher_mode = wvcdm::kCipherModeCtr;
} else if (mode == Mode::AES_CBC) {
params.cipher_mode = wvcdm::kCipherModeCbc;
}
params.pattern.encrypt_blocks = pattern.encryptBlocks;
params.pattern.skip_blocks = pattern.skipBlocks;
// Iterate through subsamples, sending them to the CDM serially.
size_t bufferOffset = 0;
size_t blockOffset = 0;
const size_t patternLengthInBytes =
(pattern.encryptBlocks + pattern.skipBlocks) * kAESBlockSize;
// Set up the sample
// Android's API only supports one at a time
params.samples.emplace_back();
CdmDecryptionSample& sample = params.samples.back();
sample.encrypt_buffer = srcPtr;
sample.decrypt_buffer = destPtr;
sample.decrypt_buffer_offset = 0;
sample.iv = ivVector;
for (size_t i = 0; i < subSamples.size(); ++i) {
const SubSample& subSample = subSamples[i];
// Set up the subsamples
// We abuse std::transform() here to also do some side-effects: Tallying the
// total size of the sample and checking if any of the data is protected.
size_t totalSize = 0;
bool hasProtectedData = false;
sample.subsamples.reserve(subSamples.size());
std::transform(subSamples.data(), subSamples.data() + subSamples.size(),
std::back_inserter(sample.subsamples),
[&](const SubSample& subSample) -> CdmDecryptionSubsample {
totalSize +=
subSample.numBytesOfClearData + subSample.numBytesOfEncryptedData;
hasProtectedData |= subSample.numBytesOfEncryptedData > 0;
return CdmDecryptionSubsample(subSample.numBytesOfClearData,
subSample.numBytesOfEncryptedData);
});
if (mode == Mode::UNENCRYPTED && subSample.numBytesOfEncryptedData != 0) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"Encrypted subsamples found in allegedly unencrypted data.");
return Void();
}
sample.encrypt_buffer_length = totalSize;
sample.decrypt_buffer_size = totalSize;
// Calculate any flags that apply to this subsample's parts.
uint8_t clearFlags = 0;
uint8_t encryptedFlags = 0;
// If this is the first subsample…
if (i == 0) {
// …add OEMCrypto_FirstSubsample to the first part that is present.
if (subSample.numBytesOfClearData != 0) {
clearFlags = clearFlags | OEMCrypto_FirstSubsample;
} else {
encryptedFlags = encryptedFlags | OEMCrypto_FirstSubsample;
}
}
// If this is the last subsample…
if (i == subSamples.size() - 1) {
// …add OEMCrypto_LastSubsample to the last part that is present
if (subSample.numBytesOfEncryptedData != 0) {
encryptedFlags = encryptedFlags | OEMCrypto_LastSubsample;
} else {
clearFlags = clearFlags | OEMCrypto_LastSubsample;
}
}
// "Decrypt" any unencrypted data. Per the ISO-CENC standard, clear data
// comes before encrypted data.
if (subSample.numBytesOfClearData != 0) {
params.is_encrypted = false;
params.encrypt_buffer = srcPtr + bufferOffset;
params.encrypt_length = subSample.numBytesOfClearData;
params.block_offset = 0;
params.decrypt_buffer_offset = bufferOffset;
params.subsample_flags = clearFlags;
Status_V1_2 res = attemptDecrypt(params, haveEncryptedSubsamples,
&errorDetailMsg);
if (res != Status_V1_2::OK) {
_hidl_cb(res, 0, errorDetailMsg.c_str());
return Void();
}
bufferOffset += subSample.numBytesOfClearData;
}
// Decrypt any encrypted data. Per the ISO-CENC standard, encrypted data
// comes after clear data.
if (subSample.numBytesOfEncryptedData != 0) {
params.is_encrypted = true;
params.encrypt_buffer = srcPtr + bufferOffset;
params.encrypt_length = subSample.numBytesOfEncryptedData;
params.block_offset = blockOffset;
params.decrypt_buffer_offset = bufferOffset;
params.subsample_flags = encryptedFlags;
Status_V1_2 res = attemptDecrypt(params, haveEncryptedSubsamples,
&errorDetailMsg);
if (res != Status_V1_2::OK) {
_hidl_cb(res, 0, errorDetailMsg.c_str());
return Void();
}
bufferOffset += subSample.numBytesOfEncryptedData;
// Update the block offset, pattern offset, and IV as needed by the
// various crypto modes. Possible combinations are cenc (AES-CTR), cens
// (AES-CTR w/ Patterns), cbc1 (AES-CBC), and cbcs (AES-CBC w/ Patterns).
if (mode == Mode::AES_CTR) {
// Update the IV depending on how many encrypted blocks we passed.
uint64_t increment = 0;
if (patternLengthInBytes == 0) {
// If there's no pattern, all the blocks are encrypted. We have to add
// in blockOffset to account for any incomplete crypto blocks from the
// preceding subsample.
increment = (blockOffset + subSample.numBytesOfEncryptedData) /
kAESBlockSize;
} else {
// The truncation in the integer divisions in this block is
// intentional.
const uint64_t numBlocks =
subSample.numBytesOfEncryptedData / kAESBlockSize;
const uint64_t patternLengthInBlocks =
pattern.encryptBlocks + pattern.skipBlocks;
const uint64_t numFullPatternRepetitions =
numBlocks / patternLengthInBlocks;
const uint64_t numDanglingBlocks =
numBlocks % patternLengthInBlocks;
const uint64_t numDanglingEncryptedBlocks =
std::min(static_cast<uint64_t>(pattern.encryptBlocks),
numDanglingBlocks);
increment =
numFullPatternRepetitions * pattern.encryptBlocks +
numDanglingEncryptedBlocks;
}
incrementIV(increment, &ivVector);
// Update the block offset
blockOffset = (blockOffset + subSample.numBytesOfEncryptedData) %
kAESBlockSize;
} else if (mode == Mode::AES_CBC && patternLengthInBytes == 0) {
// If there is no pattern, assume cbc1 mode and update the IV.
// Stash the last crypto block in the IV.
const uint8_t* bufferEnd = srcPtr + bufferOffset +
subSample.numBytesOfEncryptedData;
ivVector.assign(bufferEnd - kAESBlockSize, bufferEnd);
}
// There is no branch for cbcs mode because the IV and pattern offest
// reset at the start of each subsample, so they do not need to be
// updated.
}
if (mode == Mode::UNENCRYPTED && hasProtectedData) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"Protected ranges found in allegedly clear data.");
return Void();
}
_hidl_cb(Status_V1_2::OK, bufferOffset, errorDetailMsg.c_str());
// Decrypt
Status_V1_2 res = attemptDecrypt(params, hasProtectedData, &errorDetailMsg);
if (res != Status_V1_2::OK) {
_hidl_cb(res, 0, errorDetailMsg.c_str());
return Void();
}
_hidl_cb(Status_V1_2::OK, totalSize, errorDetailMsg.c_str());
return Void();
}
Status_V1_2 WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params,
bool haveEncryptedSubsamples, std::string* errorDetailMsg) {
CdmResponseType res = mCDM->Decrypt(mSessionId, haveEncryptedSubsamples,
params);
Status_V1_2 WVCryptoPlugin::attemptDecrypt(
const CdmDecryptionParametersV16& params, bool hasProtectedData,
std::string* errorDetailMsg) {
CdmResponseType res = mCDM->DecryptV16(mSessionId, hasProtectedData,
params);
if (isCdmResponseTypeSuccess(res)) {
return Status_V1_2::OK;
} else {
ALOGE("Decrypt error result in session %s during %s block: %d",
ALOGE("Decrypt error in session %s during a sample %s protected data: %d",
mSessionId.c_str(),
params.is_encrypted ? "encrypted" : "unencrypted",
hasProtectedData ? "with" : "without",
res);
bool actionableError = true;
switch (res) {
@@ -439,13 +349,6 @@ Status_V1_2 WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params
}
}
void WVCryptoPlugin::incrementIV(uint64_t increaseBy,
std::vector<uint8_t>* ivPtr) {
std::vector<uint8_t>& iv = *ivPtr;
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);
(*counterBuffer) = htonq(ntohq(*counterBuffer) + increaseBy);
}
} // namespace widevine
} // namespace V1_2
} // namespace drm