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 <endian.h>
#include <iterator>
#include <string.h>
#include <string>
#include <vector>
@@ -25,12 +26,6 @@
#include "wv_cdm_constants.h"
#include "WVErrors.h"
namespace {
static const size_t kAESBlockSize = 16;
} // namespace
namespace wvdrm {
using namespace android;
@@ -108,7 +103,18 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
if (mode != kMode_Unencrypted &&
mode != kMode_AES_CTR &&
mode != kMode_AES_CBC) {
errorDetailMsg->setTo("Encryption mode is not supported by Widevine CDM.");
errorDetailMsg->setTo(
"The requested encryption mode is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto;
} else if (mode == kMode_AES_CTR &&
(pattern.mEncryptBlocks != 0 || pattern.mSkipBlocks != 0)) {
errorDetailMsg->setTo(
"The 'cens' schema is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto;
} else if (mode == kMode_AES_CBC &&
(pattern.mEncryptBlocks == 0 && pattern.mSkipBlocks == 0)) {
errorDetailMsg->setTo(
"The 'cbc1' schema is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto;
}
@@ -118,158 +124,55 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
const uint8_t* const source = static_cast<const uint8_t*>(srcPtr);
uint8_t* const dest = static_cast<uint8_t*>(dstPtr);
// 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 < numSubSamples; i++) {
const SubSample &subSample = subSamples[i];
destSize += subSample.mNumBytesOfClearData;
destSize += subSample.mNumBytesOfEncryptedData;
if (subSample.mNumBytesOfEncryptedData > 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 = keyId;
params.is_secure = secure;
params.key_id = &keyId;
params.iv = &ivVector;
params.decrypt_buffer = dest;
params.decrypt_buffer_length = destSize;
params.pattern_descriptor.encrypt_blocks = pattern.mEncryptBlocks;
params.pattern_descriptor.skip_blocks = pattern.mSkipBlocks;
if (mode == kMode_AES_CTR) {
params.cipher_mode = kCipherModeCtr;
} else if (mode == kMode_AES_CBC) {
params.cipher_mode = kCipherModeCbc;
}
params.pattern.encrypt_blocks = pattern.mEncryptBlocks;
params.pattern.skip_blocks = pattern.mSkipBlocks;
// Iterate through subsamples, sending them to the CDM serially.
size_t offset = 0;
size_t blockOffset = 0;
const size_t patternLengthInBytes =
(pattern.mEncryptBlocks + pattern.mSkipBlocks) * 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 = source;
sample.decrypt_buffer = dest;
sample.decrypt_buffer_offset = 0;
sample.iv = ivVector;
for (size_t i = 0; i < numSubSamples; ++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(numSubSamples);
std::transform(subSamples, subSamples + numSubSamples,
std::back_inserter(sample.subsamples),
[&](const SubSample& subSample) -> CdmDecryptionSubsample {
totalSize +=
subSample.mNumBytesOfClearData + subSample.mNumBytesOfEncryptedData;
hasProtectedData |= subSample.mNumBytesOfEncryptedData > 0;
return CdmDecryptionSubsample(subSample.mNumBytesOfClearData,
subSample.mNumBytesOfEncryptedData);
});
if (mode == kMode_Unencrypted && subSample.mNumBytesOfEncryptedData != 0) {
errorDetailMsg->setTo("Encrypted subsamples found in allegedly "
"unencrypted data.");
return kErrorExpectedUnencrypted;
}
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 (mode == kMode_Unencrypted && hasProtectedData) {
errorDetailMsg->setTo("Protected ranges found in allegedly clear data.");
return kErrorExpectedUnencrypted;
}
// If this is the first subsample…
if (i == 0) {
// …add OEMCrypto_FirstSubsample to the first part that is present.
if (subSample.mNumBytesOfClearData != 0) {
clearFlags = clearFlags | OEMCrypto_FirstSubsample;
} else {
encryptedFlags = encryptedFlags | OEMCrypto_FirstSubsample;
}
}
// If this is the last subsample…
if (i == numSubSamples - 1) {
// …add OEMCrypto_LastSubsample to the last part that is present
if (subSample.mNumBytesOfEncryptedData != 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.mNumBytesOfClearData != 0) {
params.is_encrypted = false;
params.encrypt_buffer = source + offset;
params.encrypt_length = subSample.mNumBytesOfClearData;
params.block_offset = 0;
params.decrypt_buffer_offset = offset;
params.subsample_flags = clearFlags;
status_t res = attemptDecrypt(params, haveEncryptedSubsamples,
errorDetailMsg);
if (res != android::OK) {
return res;
}
offset += subSample.mNumBytesOfClearData;
}
// Decrypt any encrypted data. Per the ISO-CENC standard, encrypted data
// comes after clear data.
if (subSample.mNumBytesOfEncryptedData != 0) {
params.is_encrypted = true;
params.encrypt_buffer = source + offset;
params.encrypt_length = subSample.mNumBytesOfEncryptedData;
params.block_offset = blockOffset;
params.decrypt_buffer_offset = offset;
params.subsample_flags = encryptedFlags;
status_t res = attemptDecrypt(params, haveEncryptedSubsamples,
errorDetailMsg);
if (res != android::OK) {
return res;
}
offset += subSample.mNumBytesOfEncryptedData;
// 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 == kMode_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.mNumBytesOfEncryptedData) /
kAESBlockSize;
} else {
// The truncation in the integer divisions in this block is
// intentional.
const uint64_t numBlocks =
subSample.mNumBytesOfEncryptedData / kAESBlockSize;
const uint64_t patternLengthInBlocks =
pattern.mEncryptBlocks + pattern.mSkipBlocks;
const uint64_t numFullPatternRepetitions =
numBlocks / patternLengthInBlocks;
const uint64_t numDanglingBlocks =
numBlocks % patternLengthInBlocks;
const uint64_t numDanglingEncryptedBlocks =
std::min(static_cast<uint64_t>(pattern.mEncryptBlocks),
numDanglingBlocks);
increment =
numFullPatternRepetitions * pattern.mEncryptBlocks +
numDanglingEncryptedBlocks;
}
incrementIV(increment, &ivVector);
// Update the block offset
blockOffset = (blockOffset + subSample.mNumBytesOfEncryptedData) %
kAESBlockSize;
} else if (mode == kMode_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 = source + offset +
subSample.mNumBytesOfEncryptedData;
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.
}
// Decrypt
status_t res = attemptDecrypt(params, hasProtectedData, errorDetailMsg);
if (res != android::OK) {
return res;
}
// In test mode, we return an error that causes a detailed error
@@ -285,7 +188,7 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
SHA256_CTX ctx;
uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256_Init(&ctx);
SHA256_Update(&ctx, dstPtr, offset);
SHA256_Update(&ctx, dstPtr, totalSize);
SHA256_Final(digest, &ctx);
String8 buf;
for (size_t i = 0; i < sizeof(digest); i++) {
@@ -297,21 +200,21 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
return kErrorTestMode;
}
return static_cast<ssize_t>(offset);
return static_cast<ssize_t>(totalSize);
}
status_t WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params,
bool haveEncryptedSubsamples,
AString* errorDetailMsg) {
CdmResponseType res = mCDM->Decrypt(mSessionId, haveEncryptedSubsamples,
params);
status_t WVCryptoPlugin::attemptDecrypt(
const CdmDecryptionParametersV16& params, bool hasProtectedData,
AString* errorDetailMsg) {
CdmResponseType res = mCDM->DecryptV16(mSessionId, hasProtectedData,
params);
if (isCdmResponseTypeSuccess(res)) {
return android::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) {
@@ -360,10 +263,4 @@ status_t WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params,
}
}
void WVCryptoPlugin::incrementIV(uint64_t increaseBy, vector<uint8_t>* ivPtr) {
vector<uint8_t>& iv = *ivPtr;
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);
(*counterBuffer) = htonq(ntohq(*counterBuffer) + increaseBy);
}
} // namespace wvdrm