Merge "Break Decrypt Buffers Into 100KiB Blocks As Needed"
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<uint8_t>* 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_;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <arpa/inet.h> // needed for ntoh()
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#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<uint8_t*>(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<uint8_t> 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<uint8_t>* iv_out) {
|
||||
std::vector<uint8_t>& iv = *iv_out;
|
||||
uint64_t* counter_buffer = reinterpret_cast<uint64_t*>(&iv[8]);
|
||||
(*counter_buffer) = htonll64(ntohll64(*counter_buffer) + increase_by);
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user