diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index 9f1893c8..c790fd25 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -5,6 +5,7 @@ #include #include +#include #include "lock.h" #include "oemcrypto_adapter.h" @@ -143,6 +144,18 @@ class CryptoSession { CdmEncryptionAlgorithm algorithm); size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm); + // These methods are used when a subsample exceeds the maximum buffer size + // that the device can handle. + OEMCryptoResult CopyBufferInChunks( + const CdmDecryptionParameters& params, + OEMCrypto_DestBufferDesc buffer_descriptor); + OEMCryptoResult DecryptInChunks( + const CdmDecryptionParameters& params, + const OEMCrypto_DestBufferDesc& full_buffer_descriptor, + const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor, + size_t max_chunk_size); + static void IncrementIV(uint64_t increase_by, std::vector* iv_out); + static const size_t kAes128BlockSize = 16; // Block size for AES_CBC_128 static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature static Lock crypto_lock_; diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 2d20fb89..22b72d03 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -8,6 +8,7 @@ #include // needed for ntoh() #include #include +#include #include "crypto_key.h" #include "log.h" @@ -26,6 +27,7 @@ std::string EncodeUint32(unsigned int u) { return s; } const uint32_t kRsaSignatureLength = 256; +const size_t kMaximumChunkSize = 100 * 1024; // 100 KiB } namespace wvcdm { @@ -690,6 +692,13 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { sts = OEMCrypto_CopyBuffer(requested_security_level_, params.encrypt_buffer, params.encrypt_length, &buffer_descriptor, params.subsample_flags); + + if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE && + params.encrypt_length > kMaximumChunkSize) { + // OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up + // into 100 KiB sections. + sts = CopyBufferInChunks(params, buffer_descriptor); + } } if (params.is_encrypted && params.cipher_mode != cipher_mode_) { return INCORRECT_CRYPTO_MODE; @@ -710,6 +719,26 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { oec_session_id_, params.encrypt_buffer, params.encrypt_length, params.is_encrypted, &(*params.iv).front(), params.block_offset, &buffer_descriptor, &pattern_descriptor, params.subsample_flags); + + + if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) { + // OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it up + // into sections no more than 100 KiB. The exact chunk size needs to be + // an even number of pattern repetitions long or else the pattern will get + // out of sync. + const size_t pattern_length = + (pattern_descriptor.encrypt + pattern_descriptor.skip) * + kAes128BlockSize; + const size_t chunk_size = + pattern_length > 0 ? + kMaximumChunkSize - (kMaximumChunkSize % pattern_length) : + kMaximumChunkSize; + + if (params.encrypt_length > chunk_size) { + sts = DecryptInChunks(params, buffer_descriptor, pattern_descriptor, + chunk_size); + } + } } switch (sts) { @@ -1302,4 +1331,182 @@ size_t CryptoSession::GenericEncryptionBlockSize( } } +OEMCryptoResult CryptoSession::CopyBufferInChunks( + const CdmDecryptionParameters& params, + OEMCrypto_DestBufferDesc buffer_descriptor) { + size_t remaining_encrypt_length = params.encrypt_length; + uint8_t subsample_flags = OEMCrypto_FirstSubsample; + + while (remaining_encrypt_length > 0) { + // Calculate the size of the next chunk and its offset into the original + // buffer. + const size_t chunk_size = std::min(remaining_encrypt_length, + kMaximumChunkSize); + const size_t additional_offset = + params.encrypt_length - remaining_encrypt_length; + + // Update the remaining length of the original buffer only after calculating + // the new values. + remaining_encrypt_length -= chunk_size; + + // Update the destination buffer with the new offset. + switch (buffer_descriptor.type) { + case OEMCrypto_BufferType_Clear: + buffer_descriptor.buffer.clear.address = + static_cast(params.decrypt_buffer) + + params.decrypt_buffer_offset + additional_offset; + buffer_descriptor.buffer.clear.max_length = + params.decrypt_buffer_length - + (params.decrypt_buffer_offset + additional_offset); + break; + case OEMCrypto_BufferType_Secure: + buffer_descriptor.buffer.secure.offset = + params.decrypt_buffer_offset + additional_offset; + break; + case OEMCrypto_BufferType_Direct: + // OEMCrypto_BufferType_Direct does not need modification. + break; + } + + // Re-add "last subsample" flag if this is the last subsample. + if (remaining_encrypt_length == 0) { + subsample_flags |= OEMCrypto_LastSubsample; + } + + OEMCryptoResult sts = OEMCrypto_CopyBuffer( + requested_security_level_, params.encrypt_buffer + additional_offset, + chunk_size, &buffer_descriptor, subsample_flags); + if (sts != OEMCrypto_SUCCESS) { + return sts; + } + + // Clear any subsample flags before the next loop iteration. + subsample_flags = 0; + } + + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult CryptoSession::DecryptInChunks( + const CdmDecryptionParameters& params, + const OEMCrypto_DestBufferDesc& full_buffer_descriptor, + const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor, + size_t max_chunk_size) { + size_t remaining_encrypt_length = params.encrypt_length; + uint8_t subsample_flags = + (params.subsample_flags & OEMCrypto_FirstSubsample) ? + OEMCrypto_FirstSubsample : 0; + std::vector iv = *params.iv; + + const size_t pattern_length_in_bytes = + (pattern_descriptor.encrypt + pattern_descriptor.skip) * + kAes128BlockSize; + + while (remaining_encrypt_length > 0) { + // Calculate the size of the next chunk and its offset into the + // original buffer. + const size_t chunk_size = std::min(remaining_encrypt_length, + max_chunk_size); + const size_t additional_offset = + params.encrypt_length - remaining_encrypt_length; + + // Update the remaining length of the original buffer only after + // calculating the new values. + remaining_encrypt_length -= chunk_size; + + // Update the destination buffer with the new offset. Because OEMCrypto can + // modify the OEMCrypto_DestBufferDesc during the call to + // OEMCrypto_DecryptCENC, (and is known to do so on some platforms) a new + // OEMCrypto_DestBufferDesc must be allocated for each call. + OEMCrypto_DestBufferDesc buffer_descriptor = full_buffer_descriptor; + switch (buffer_descriptor.type) { + case OEMCrypto_BufferType_Clear: + buffer_descriptor.buffer.clear.address += additional_offset; + buffer_descriptor.buffer.clear.max_length -= additional_offset; + break; + case OEMCrypto_BufferType_Secure: + buffer_descriptor.buffer.secure.offset += additional_offset; + break; + case OEMCrypto_BufferType_Direct: + // OEMCrypto_BufferType_Direct does not need modification. + break; + } + + // Re-add "last subsample" flag if this is the last subsample. + if (remaining_encrypt_length == 0 && + params.subsample_flags & OEMCrypto_LastSubsample) { + subsample_flags |= OEMCrypto_LastSubsample; + } + + // block_offset and pattern_descriptor do not need to change because + // max_chunk_size is guaranteed to be an even multiple of the + // pattern length long, which is also guaranteed to be an exact number + // of AES blocks long. + OEMCryptoResult sts = OEMCrypto_DecryptCENC( + oec_session_id_, params.encrypt_buffer + additional_offset, + chunk_size, params.is_encrypted, &iv.front(), params.block_offset, + &buffer_descriptor, &pattern_descriptor, subsample_flags); + if (sts != OEMCrypto_SUCCESS) { + return sts; + } + + // If we are not yet done, update the IV so that it is valid for the next + // iteration. + if (remaining_encrypt_length != 0) { + if (cipher_mode_ == kCipherModeCtr) { + // For CTR modes, update the IV depending on how many encrypted blocks + // we passed. Since we calculated the chunk size to be an even number + // of crypto blocks and pattern repetitions in size, we can do a + // simplified calculation for this. + uint64_t encrypted_blocks_passed = 0; + if (pattern_length_in_bytes == 0) { + encrypted_blocks_passed = chunk_size / kAes128BlockSize; + } else { + const size_t pattern_repetitions_passed = + chunk_size / pattern_length_in_bytes; + encrypted_blocks_passed = + pattern_repetitions_passed * pattern_descriptor.encrypt; + } + IncrementIV(encrypted_blocks_passed, &iv); + } else if (cipher_mode_ == kCipherModeCbc) { + // For CBC modes, use the previous ciphertext block. + + // Stash the last crypto block in the IV. We don't have to handle + // partial crypto blocks here because we know we broke the buffer into + // chunks along even crypto block boundaries. + const uint8_t* const buffer_end = + params.encrypt_buffer + additional_offset + chunk_size; + + const uint8_t* block_end = NULL; + if (pattern_length_in_bytes == 0) { + // For cbc1, the last encrypted block is the last block of the + // subsample. + block_end = buffer_end; + } else { + // For cbcs, we must look for the last encrypted block, which is + // probably not the last block of the subsample. Luckily, since the + // buffer size is guaranteed to be an even number of pattern + // repetitions long, we can use the pattern to know how many blocks to + // look back. + block_end = buffer_end - kAes128BlockSize * pattern_descriptor.skip; + } + + iv.assign(block_end - kAes128BlockSize, block_end); + } + } + + // Clear any subsample flags before the next loop iteration. + subsample_flags = 0; + } + + return OEMCrypto_SUCCESS; +} + +void CryptoSession::IncrementIV(uint64_t increase_by, + std::vector* iv_out) { + std::vector& iv = *iv_out; + uint64_t* counter_buffer = reinterpret_cast(&iv[8]); + (*counter_buffer) = htonll64(ntohll64(*counter_buffer) + increase_by); +} + } // namespace wvcdm diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp index d3d91032..3ebf1f36 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp @@ -27,6 +27,7 @@ namespace { const uint8_t kBakedInCertificateMagicBytes[] = { 0xDE, 0xAD, 0xBE, 0xEF }; +const size_t kMaxBufferSize = 1024 * 100; // 100KiB } // namespace namespace wvoec_mock { @@ -605,18 +606,24 @@ OEMCryptoResult OEMCrypto_DecryptCENC(OEMCrypto_SESSION session, const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags) { if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) { - LOGI("-- OEMCryptoResult OEMCrypto_DecryptCTR" + LOGI("-- OEMCryptoResult OEMCrypto_DecryptCENC" "(OEMCrypto_SESSION session,\n"); } if (!crypto_engine) { - LOGE("OEMCrypto_DecryptCTR: OEMCrypto Not Initialized."); + LOGE("OEMCrypto_DecryptCENC: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (data_addr == NULL || data_length == 0 || iv == NULL || out_buffer == NULL) { - LOGE("[OEMCrypto_DecryptCTR(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + LOGE("[OEMCrypto_DecryptCENC(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } + if (data_length > kMaxBufferSize) { + // For testing reasons only, pretend that this integration only supports + // the minimum possible buffer size. + LOGE("[OEMCrypto_DecryptCENC(): OEMCrypto_ERROR_BUFFER_TOO_LARGE]"); + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } uint8_t* destination = NULL; size_t max_length = 0; OEMCryptoResult sts = SetDestination(out_buffer, data_length, &destination, @@ -625,14 +632,14 @@ OEMCryptoResult OEMCrypto_DecryptCENC(OEMCrypto_SESSION session, #ifndef NDEBUG if (NO_ERROR != crypto_engine->ValidateKeybox()) { - LOGE("[OEMCrypto_DecryptCTR(): ERROR_KEYBOX_INVALID]"); + LOGE("[OEMCrypto_DecryptCENC(): ERROR_KEYBOX_INVALID]"); return OEMCrypto_ERROR_KEYBOX_INVALID; } #endif SessionContext* session_ctx = crypto_engine->FindSession(session); if (!session_ctx || !session_ctx->isValid()) { - LOGE("[OEMCrypto_DecryptCTR(): ERROR_INVALID_SESSION]"); + LOGE("[OEMCrypto_DecryptCENC(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -658,6 +665,12 @@ OEMCryptoResult OEMCrypto_CopyBuffer(const uint8_t *data_addr, LOGE("[OEMCrypto_CopyBuffer(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } + if (data_length > kMaxBufferSize) { + // For testing reasons only, pretend that this integration only supports + // the minimum possible buffer size. + LOGE("[OEMCrypto_CopyBuffer(): OEMCrypto_ERROR_BUFFER_TOO_LARGE]"); + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } uint8_t* destination = NULL; size_t max_length = 0; OEMCryptoResult sts = SetDestination(out_buffer, data_length, &destination,