diff --git a/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h b/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h index 5fd5703b..9ba78b5c 100644 --- a/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h +++ b/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h @@ -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* ivPtr); }; diff --git a/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp b/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp index 7789b43f..60aba8e1 100644 --- a/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp +++ b/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp @@ -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& 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(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* ivPtr) { vector& iv = *ivPtr; uint64_t* counterBuffer = reinterpret_cast(&iv[8]);