HLS, CBC, and Pattern for Android Glue Layer

[This is a merge of http://go/wvgerrit/16522 ]

This commit adds support for CBC and Pattern Mode to the MediaCrypto
implementation. These are the only changes needed to support HLS. (No
change is needed for MediaDrm, as it already passes HLS initialization
data along to the core without closely inspecting it, as it should.)

Following this change, the glue layer also supports the CENC, CBC1,
CENS, and CBCS modes from the forthcoming update to the ISO-CENC spec.

Note that, in order to differentiate CBC1 and CBCS, we have to cue on
the presence or absence of a pattern, which may not continue to be
sufficient in the future if a third CBC mode using patterns is ever
added.

Note that the unit tests for this code remain disabled for now. New
unit tests are forthcoming in a separate commit.

Bug: 25666017
Change-Id: I5942a8b70393e63b4de9d7dab985c4c2a98a20b3
This commit is contained in:
John "Juce" Bruce
2016-01-21 18:44:21 -08:00
parent 280a9e47a0
commit a61e0e01b1
2 changed files with 117 additions and 13 deletions

View File

@@ -45,6 +45,9 @@ class WVCryptoPlugin : public android::CryptoPlugin {
wvcdm::CdmSessionId mSessionId;
wvcdm::CdmSessionId configureTestMode(const void* data, size_t size);
static wvcdm::CdmResponseType countEncryptedBlocksInPatternedRange(
size_t range, size_t startingOffset, const Pattern& pattern,
size_t startingPatternOffset, uint64_t* result);
static void incrementIV(uint64_t increaseBy, std::vector<uint8_t>* ivPtr);
};

View File

@@ -22,6 +22,12 @@
#include "wv_cdm_constants.h"
#include "WVErrors.h"
namespace {
static const size_t kAESBlockSize = 16;
} // namespace
namespace wvdrm {
using namespace android;
@@ -89,11 +95,13 @@ status_t WVCryptoPlugin::setMediaDrmSession(const Vector<uint8_t>& sessionId) {
// size, but in practice this should never happen for AES-CTR.
ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
const uint8_t iv[KEY_IV_SIZE], Mode mode,
const Pattern &pattern,
const void* srcPtr, const SubSample* subSamples,
const Pattern& pattern, const void* srcPtr,
const SubSample* subSamples,
size_t numSubSamples, void* dstPtr,
AString* errorDetailMsg) {
if (mode != kMode_Unencrypted && mode != kMode_AES_CTR) {
if (mode != kMode_Unencrypted &&
mode != kMode_AES_CTR &&
mode != kMode_AES_CBC) {
errorDetailMsg->setTo("Encryption mode is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto;
}
@@ -124,14 +132,24 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
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;
}
// Iterate through subsamples, sending them to the CDM serially.
size_t offset = 0;
static const size_t kAESBlockSize = 16;
size_t blockOffset = 0;
const size_t patternLengthInBytes =
(pattern.mEncryptBlocks + pattern.mSkipBlocks) * kAESBlockSize;
size_t patternOffsetInBytes = 0;
for (size_t i = 0; i < numSubSamples; ++i) {
const SubSample &subSample = subSamples[i];
const SubSample& subSample = subSamples[i];
if (mode == kMode_Unencrypted && subSample.mNumBytesOfEncryptedData != 0) {
errorDetailMsg->setTo("Encrypted subsamples found in allegedly "
@@ -169,6 +187,7 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
params.encrypt_buffer = source + offset;
params.encrypt_length = subSample.mNumBytesOfClearData;
params.block_offset = 0;
params.pattern_descriptor.offset_blocks = 0;
params.decrypt_buffer_offset = offset;
params.subsample_flags = clearFlags;
@@ -179,15 +198,18 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
ALOGE("Decrypt error result in session %s during unencrypted block: %d",
mSessionId.c_str(), res);
if (res == wvcdm::INSUFFICIENT_CRYPTO_RESOURCES) {
errorDetailMsg->setTo("Error decrypting data: insufficient crypto resources");
errorDetailMsg->setTo(
"Error decrypting data: insufficient crypto resources");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else if (res == wvcdm::NEED_KEY) {
errorDetailMsg->setTo("Error decrypting data: requested key has not been loaded");
errorDetailMsg->setTo(
"Error decrypting data: requested key has not been loaded");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else if (res == wvcdm::SESSION_NOT_FOUND_FOR_DECRYPT) {
errorDetailMsg->setTo("Error decrypting data: session not found, possibly reclaimed");
errorDetailMsg->setTo(
"Error decrypting data: session not found, possibly reclaimed");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else {
@@ -206,6 +228,12 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
params.encrypt_buffer = source + offset;
params.encrypt_length = subSample.mNumBytesOfEncryptedData;
params.block_offset = blockOffset;
// Note that the pattern offset calculation relies intentionally on
// integer division's flooring behavior. If we are partway through a
// crypto block, we should return the offset of the pattern block we are
// partway through.
size_t patternOffsetInBlocks = patternOffsetInBytes / kAESBlockSize;
params.pattern_descriptor.offset_blocks = patternOffsetInBlocks;
params.decrypt_buffer_offset = offset;
params.subsample_flags = encryptedFlags;
@@ -216,11 +244,13 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
ALOGE("Decrypt error result in session %s during encrypted block: %d",
mSessionId.c_str(), res);
if (res == wvcdm::INSUFFICIENT_CRYPTO_RESOURCES) {
errorDetailMsg->setTo("Error decrypting data: insufficient crypto resources");
errorDetailMsg->setTo(
"Error decrypting data: insufficient crypto resources");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else if (res == wvcdm::NEED_KEY) {
errorDetailMsg->setTo("Error decrypting data: requested key has not been loaded");
errorDetailMsg->setTo(
"Error decrypting data: requested key has not been loaded");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else {
@@ -231,9 +261,49 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
offset += subSample.mNumBytesOfEncryptedData;
blockOffset += subSample.mNumBytesOfEncryptedData;
incrementIV(blockOffset / kAESBlockSize, &ivVector);
blockOffset %= kAESBlockSize;
// 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 {
res = countEncryptedBlocksInPatternedRange(
subSample.mNumBytesOfEncryptedData, blockOffset, pattern,
patternOffsetInBlocks, &increment);
if (!isCdmResponseTypeSuccess(res)) {
// Swallow the specifics of the error to obscure decrypt internals.
return kErrorCDMGeneric;
}
}
incrementIV(increment, &ivVector);
// Update the block offset
blockOffset = (blockOffset + subSample.mNumBytesOfEncryptedData) %
kAESBlockSize;
if (patternLengthInBytes > 0) {
patternOffsetInBytes =
(patternOffsetInBytes + subSample.mNumBytesOfEncryptedData) %
patternLengthInBytes;
}
} 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.
}
}
@@ -266,6 +336,37 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
return static_cast<ssize_t>(offset);
}
CdmResponseType WVCryptoPlugin::countEncryptedBlocksInPatternedRange(
size_t range, size_t startingOffset, const Pattern& pattern,
size_t startingPatternOffset, uint64_t* result) {
uint64_t blocksPassed = 0;
size_t bytesRemaining = range;
size_t patternOffset = startingPatternOffset;
size_t patternLength = pattern.mEncryptBlocks + pattern.mSkipBlocks;
if (result == NULL || startingOffset >= kAESBlockSize ||
startingPatternOffset >= patternLength) {
return wvcdm::UNKNOWN_ERROR;
}
// We may already be partway into a block, so reduce the number of bytes
// that must be passed to complete a block if so.
size_t bytesNeededToCompleteABlock = kAESBlockSize - startingOffset;
while (bytesRemaining >= bytesNeededToCompleteABlock) {
bytesRemaining -= bytesNeededToCompleteABlock;
if (patternOffset < pattern.mEncryptBlocks) {
++blocksPassed;
}
patternOffset = (patternOffset + 1) % patternLength;
// After the first block, we only concern ourselves with complete blocks.
bytesNeededToCompleteABlock = kAESBlockSize;
}
*result = blocksPassed;
return wvcdm::NO_ERROR;
}
void WVCryptoPlugin::incrementIV(uint64_t increaseBy, vector<uint8_t>* ivPtr) {
vector<uint8_t>& iv = *ivPtr;
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);