Merge "HLS, CBC, and Pattern for Android Glue Layer"
This commit is contained in:
@@ -45,6 +45,9 @@ class WVCryptoPlugin : public android::CryptoPlugin {
|
|||||||
wvcdm::CdmSessionId mSessionId;
|
wvcdm::CdmSessionId mSessionId;
|
||||||
|
|
||||||
wvcdm::CdmSessionId configureTestMode(const void* data, size_t size);
|
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);
|
static void incrementIV(uint64_t increaseBy, std::vector<uint8_t>* ivPtr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,12 @@
|
|||||||
#include "wv_cdm_constants.h"
|
#include "wv_cdm_constants.h"
|
||||||
#include "WVErrors.h"
|
#include "WVErrors.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static const size_t kAESBlockSize = 16;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace wvdrm {
|
namespace wvdrm {
|
||||||
|
|
||||||
using namespace android;
|
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.
|
// size, but in practice this should never happen for AES-CTR.
|
||||||
ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
|
ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
|
||||||
const uint8_t iv[KEY_IV_SIZE], Mode mode,
|
const uint8_t iv[KEY_IV_SIZE], Mode mode,
|
||||||
const Pattern &pattern,
|
const Pattern& pattern, const void* srcPtr,
|
||||||
const void* srcPtr, const SubSample* subSamples,
|
const SubSample* subSamples,
|
||||||
size_t numSubSamples, void* dstPtr,
|
size_t numSubSamples, void* dstPtr,
|
||||||
AString* errorDetailMsg) {
|
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.");
|
errorDetailMsg->setTo("Encryption mode is not supported by Widevine CDM.");
|
||||||
return kErrorUnsupportedCrypto;
|
return kErrorUnsupportedCrypto;
|
||||||
}
|
}
|
||||||
@@ -124,14 +132,24 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
|
|||||||
params.iv = &ivVector;
|
params.iv = &ivVector;
|
||||||
params.decrypt_buffer = dest;
|
params.decrypt_buffer = dest;
|
||||||
params.decrypt_buffer_length = destSize;
|
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.
|
// Iterate through subsamples, sending them to the CDM serially.
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
static const size_t kAESBlockSize = 16;
|
|
||||||
size_t blockOffset = 0;
|
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) {
|
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) {
|
if (mode == kMode_Unencrypted && subSample.mNumBytesOfEncryptedData != 0) {
|
||||||
errorDetailMsg->setTo("Encrypted subsamples found in allegedly "
|
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_buffer = source + offset;
|
||||||
params.encrypt_length = subSample.mNumBytesOfClearData;
|
params.encrypt_length = subSample.mNumBytesOfClearData;
|
||||||
params.block_offset = 0;
|
params.block_offset = 0;
|
||||||
|
params.pattern_descriptor.offset_blocks = 0;
|
||||||
params.decrypt_buffer_offset = offset;
|
params.decrypt_buffer_offset = offset;
|
||||||
params.subsample_flags = clearFlags;
|
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",
|
ALOGE("Decrypt error result in session %s during unencrypted block: %d",
|
||||||
mSessionId.c_str(), res);
|
mSessionId.c_str(), res);
|
||||||
if (res == wvcdm::INSUFFICIENT_CRYPTO_RESOURCES) {
|
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.
|
// This error is actionable by the app and should be passed up.
|
||||||
return mapCdmResponseType(res);
|
return mapCdmResponseType(res);
|
||||||
} else if (res == wvcdm::NEED_KEY) {
|
} 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.
|
// This error is actionable by the app and should be passed up.
|
||||||
return mapCdmResponseType(res);
|
return mapCdmResponseType(res);
|
||||||
} else if (res == wvcdm::SESSION_NOT_FOUND_FOR_DECRYPT) {
|
} 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.
|
// This error is actionable by the app and should be passed up.
|
||||||
return mapCdmResponseType(res);
|
return mapCdmResponseType(res);
|
||||||
} else {
|
} 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_buffer = source + offset;
|
||||||
params.encrypt_length = subSample.mNumBytesOfEncryptedData;
|
params.encrypt_length = subSample.mNumBytesOfEncryptedData;
|
||||||
params.block_offset = blockOffset;
|
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.decrypt_buffer_offset = offset;
|
||||||
params.subsample_flags = encryptedFlags;
|
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",
|
ALOGE("Decrypt error result in session %s during encrypted block: %d",
|
||||||
mSessionId.c_str(), res);
|
mSessionId.c_str(), res);
|
||||||
if (res == wvcdm::INSUFFICIENT_CRYPTO_RESOURCES) {
|
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.
|
// This error is actionable by the app and should be passed up.
|
||||||
return mapCdmResponseType(res);
|
return mapCdmResponseType(res);
|
||||||
} else if (res == wvcdm::NEED_KEY) {
|
} 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.
|
// This error is actionable by the app and should be passed up.
|
||||||
return mapCdmResponseType(res);
|
return mapCdmResponseType(res);
|
||||||
} else {
|
} else {
|
||||||
@@ -231,9 +261,49 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
|
|||||||
|
|
||||||
offset += subSample.mNumBytesOfEncryptedData;
|
offset += subSample.mNumBytesOfEncryptedData;
|
||||||
|
|
||||||
blockOffset += subSample.mNumBytesOfEncryptedData;
|
// Update the block offset, pattern offset, and IV as needed by the
|
||||||
incrementIV(blockOffset / kAESBlockSize, &ivVector);
|
// various crypto modes. Possible combinations are cenc (AES-CTR), cens
|
||||||
blockOffset %= kAESBlockSize;
|
// (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);
|
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) {
|
void WVCryptoPlugin::incrementIV(uint64_t increaseBy, vector<uint8_t>* ivPtr) {
|
||||||
vector<uint8_t>& iv = *ivPtr;
|
vector<uint8_t>& iv = *ivPtr;
|
||||||
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);
|
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);
|
||||||
|
|||||||
Reference in New Issue
Block a user