Combined Decrypt Calls

(This is a merge of http://go/wvgerrit/93829,
http://go/wvgerrit/93830, http://go/wvgerrit/93832,
http://go/wvgerrit/93833, and http://go/wvgerrit/93834 from the
Widevine repo.)

This implements the CDM code changes necessary to take advantage of
Combined Decrypt Calls on OEMCrypto v16. The result of this is that
WVCryptoPlugin is much lighter now because it can pass the full sample
down to the core in one call, but CryptoSession is heavier, as it now
has to handle more complex fallback logic when devices can't handle
multiple subsamples at once.

This patch also removes support for the 'cens' and 'cbc1' schema, which
are being dropped in OEMCrypto v16. This fixes an overflow in the code
for handling those schemas by removing it entirely.

This patch also fixes the "in chunks" legacy decrypt path to use larger
chunk sizes on devices with higher resource rating tiers.

Bug: 135285640
Bug: 123435824
Bug: 138584971
Bug: 139257871
Bug: 78289910
Bug: 149361893
Test: no new CE CDM Unit Test failures
Test: Google Play plays
Test: Netflix plays
Test: no new GTS failures
Change-Id: Ic4952c9fa3bc7fd5ed08698e88254380a7a18514
This commit is contained in:
John W. Bruce
2020-02-18 14:46:31 -08:00
parent 3708c4d53f
commit a62886b925
28 changed files with 1253 additions and 1260 deletions

View File

@@ -258,8 +258,9 @@ class CdmEngine {
// Decryption and key related methods
// Accept encrypted buffer and return decrypted data.
virtual CdmResponseType Decrypt(const CdmSessionId& session_id,
const CdmDecryptionParameters& parameters);
virtual CdmResponseType DecryptV16(
const CdmSessionId& session_id,
const CdmDecryptionParametersV16& parameters);
// Generic crypto operations - provides basic crypto operations that an
// application can use outside of content stream processing

View File

@@ -251,12 +251,17 @@ class CdmEngineMetricsImpl : public T {
return status;
}
CdmResponseType Decrypt(const CdmSessionId& session_id,
const CdmDecryptionParameters& parameters) override {
CdmResponseType DecryptV16(
const CdmSessionId& session_id,
const CdmDecryptionParametersV16& parameters) override {
size_t total_size = 0;
for (const CdmDecryptionSample& sample : parameters.samples) {
total_size += sample.encrypt_buffer_length;
}
CdmResponseType sts;
M_TIME(sts = T::Decrypt(session_id, parameters), metrics_,
cdm_engine_decrypt_, sts,
metrics::Pow2Bucket(parameters.encrypt_length));
M_TIME(sts = T::DecryptV16(session_id, parameters), metrics_,
cdm_engine_decrypt_, sts, metrics::Pow2Bucket(total_size));
return sts;
}

View File

@@ -107,7 +107,7 @@ class CdmSession {
virtual CdmResponseType QueryOemCryptoSessionId(CdmQueryMap* query_response);
// Decrypt() - Accept encrypted buffer and return decrypted data.
virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
virtual CdmResponseType Decrypt(const CdmDecryptionParametersV16& parameters);
// License renewal
// GenerateRenewalRequest() - Construct valid renewal request for the current

View File

@@ -36,7 +36,6 @@ class ContentKeySession : public KeySession {
const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
CdmCipherMode* cipher_mode,
const std::string& srm_requirement) override;
OEMCryptoResult LoadEntitledContentKeys(
@@ -50,16 +49,15 @@ class ContentKeySession : public KeySession {
// Decrypt for ContentKeySession
OEMCryptoResult Decrypt(
const CdmDecryptionParameters& params,
OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset,
const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) override;
const OEMCrypto_SampleDescription* samples, size_t samples_length,
const OEMCrypto_CENCEncryptPatternDesc& pattern) override;
protected:
virtual OEMCryptoResult LoadKeysAsLicenseType(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token, CdmCipherMode* cipher_mode,
const std::string& provider_session_token,
const std::string& srm_requirement, OEMCrypto_LicenseType license_type);
CryptoSessionId oec_session_id_;

View File

@@ -156,7 +156,7 @@ class CryptoSession {
const std::string& wrapped_key);
// Media data path
virtual CdmResponseType Decrypt(const CdmDecryptionParameters& params);
virtual CdmResponseType Decrypt(const CdmDecryptionParametersV16& params);
virtual bool IsAntiRollbackHwPresent();
@@ -300,6 +300,7 @@ class CryptoSession {
CdmResponseType GetSystemIdInternal(uint32_t* system_id);
CdmResponseType GenerateRsaSignature(const std::string& message,
std::string* signature);
bool GetMaxSubsampleRegionSize(size_t* max);
bool SetDestinationBufferType();
@@ -314,17 +315,24 @@ 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);
// These methods fall back into each other in the order given, depending on
// how much data they were given and how much data OEMCrypto can accept in one
// call.
OEMCryptoResult DecryptMultipleSamples(
const std::vector<OEMCrypto_SampleDescription>& samples,
CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern);
OEMCryptoResult DecryptSample(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern);
OEMCryptoResult LegacyDecrypt(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern);
OEMCryptoResult LegacyCopyBufferInChunks(
const OEMCrypto_SampleDescription& sample, size_t max_chunk_size);
OEMCryptoResult LegacyDecryptInChunks(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern, size_t max_chunk_size);
// These methods should be used to take the various CryptoSession mutexes in
// preference to taking the mutexes directly.
@@ -420,7 +428,6 @@ class CryptoSession {
std::string request_id_;
static std::atomic<uint64_t> request_id_index_source_;
CdmCipherMode cipher_mode_;
uint32_t api_version_;
// In order to avoid creating a deadlock if instantiation needs to take any

View File

@@ -30,7 +30,6 @@ class EntitlementKeySession : public ContentKeySession {
const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
CdmCipherMode* cipher_mode,
const std::string& srm_requirement) override;
OEMCryptoResult LoadEntitledContentKeys(
const std::vector<CryptoKey>& keys) override;

View File

@@ -32,16 +32,14 @@ class KeySession {
const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
CdmCipherMode* cipher_mode,
const std::string& srm_requirement) = 0;
virtual OEMCryptoResult LoadEntitledContentKeys(
const std::vector<CryptoKey>& keys) = 0;
virtual OEMCryptoResult SelectKey(const std::string& key_id,
CdmCipherMode cipher_mode) = 0;
virtual OEMCryptoResult Decrypt(
const CdmDecryptionParameters& params,
OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset,
const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) = 0;
const OEMCrypto_SampleDescription* samples, size_t samples_length,
const OEMCrypto_CENCEncryptPatternDesc& pattern) = 0;
protected:
metrics::CryptoMetrics* metrics_;

View File

@@ -404,6 +404,10 @@ enum CdmResponseType {
CORE_MESSAGE_NOT_FOUND = 350,
LOAD_LICENSE_ERROR = 351,
LOAD_RENEWAL_ERROR = 352,
CANNOT_DECRYPT_ZERO_SAMPLES = 353,
CANNOT_DECRYPT_ZERO_SUBSAMPLES = 354,
SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH = 355,
INVALID_IV_SIZE = 356,
// Don't forget to add new values to
// * core/test/test_printers.cpp.
// * android/include/mapErrors-inl.h
@@ -581,11 +585,13 @@ class CdmKeyAllowedUsage {
bool valid_;
};
// For schemes that do not use pattern encryption (cenc and cbc1), encrypt
// and skip should be set to 0. For those that do (cens and cbcs), it is
// recommended that encrypt+skip bytes sum to 10 and for cbcs that a 1:9
// encrypt:skip ratio be used. See ISO/IEC DIS 23001-7, section 10.4.2 for
// more information.
// For schemes that do not use pattern encryption (cenc), encrypt and skip
// must be set to 0. For those that do (cbcs), it is recommended that
// encrypt+skip bytes sum to 10. See ISO/IEC DIS 23001-7, section 10.4.2 for
// more information. For Widevine, we often use the pattern (1,0) to represent
// "encrypt all blocks," but in content, the patterns (0,0) and (10,0) are more
// common. Since we are constrained to use (0,0) for "do not use pattern
// encryption" — as noted above — we commonly use (1,0) for this case instead.
struct CdmCencPatternEncryptionDescriptor {
size_t encrypt_blocks; // number of 16 byte blocks to decrypt
size_t skip_blocks; // number of 16 byte blocks to leave in clear
@@ -640,6 +646,119 @@ struct CdmDecryptionParameters {
is_video(true) {}
};
struct CdmDecryptionSubsample {
size_t clear_bytes;
size_t protected_bytes;
// TODO(b/149524614): These fields are not necessary except for
// backwards-compatibility while we are transitioning from the v15 API to the
// v16 API.
uint8_t flags;
size_t block_offset;
CdmDecryptionSubsample()
: clear_bytes(0), protected_bytes(0), flags(0), block_offset(0) {}
CdmDecryptionSubsample(size_t clear_param, size_t protected_param)
: clear_bytes(clear_param),
protected_bytes(protected_param),
flags(0),
block_offset(0) {}
};
struct CdmDecryptionSample {
const uint8_t* encrypt_buffer;
size_t encrypt_buffer_length;
void* decrypt_buffer;
size_t decrypt_buffer_size;
size_t decrypt_buffer_offset;
std::vector<CdmDecryptionSubsample> subsamples;
std::vector<uint8_t> iv;
CdmDecryptionSample()
: encrypt_buffer(nullptr),
encrypt_buffer_length(0),
decrypt_buffer(nullptr),
decrypt_buffer_size(0),
decrypt_buffer_offset(0),
subsamples(),
iv() {}
CdmDecryptionSample(const uint8_t* encrypt_buffer_param,
void* decrypt_buffer_param,
size_t decrypt_buffer_offset_param, size_t length,
const std::vector<uint8_t>& iv_param)
: encrypt_buffer(encrypt_buffer_param),
encrypt_buffer_length(length),
decrypt_buffer(decrypt_buffer_param),
decrypt_buffer_size(length),
decrypt_buffer_offset(decrypt_buffer_offset_param),
subsamples(),
iv(iv_param) {}
};
// TODO(b/149524614): This name is a temporary measure for
// backwards-compatibility while we are transitioning from the v15 API to the
// v16 API.
struct CdmDecryptionParametersV16 {
KeyId key_id;
std::vector<CdmDecryptionSample> samples;
bool is_secure;
CdmCipherMode cipher_mode;
bool is_video;
CdmCencPatternEncryptionDescriptor pattern;
// TODO(b/149524614): These field is not necessary except for
// backwards-compatibility while we are transitioning from the v15 API to the
// v16 API.
bool observe_legacy_fields;
CdmDecryptionParametersV16()
: key_id(),
samples(),
is_secure(true),
cipher_mode(kCipherModeCtr),
is_video(true),
pattern(),
observe_legacy_fields(false) {}
CdmDecryptionParametersV16(const KeyId& key_id_param)
: key_id(key_id_param),
samples(),
is_secure(true),
cipher_mode(kCipherModeCtr),
is_video(true),
pattern(),
observe_legacy_fields(false) {}
// TODO(b/149524614): This method is a temporary measure for
// backwards-compatibility while we are transitioning from the v15 API to the
// v16 API.
static CdmDecryptionParametersV16 from_v15(
const CdmDecryptionParameters& v15_params) {
CdmDecryptionParametersV16 new_params;
new_params.key_id = *(v15_params.key_id);
new_params.is_secure = v15_params.is_secure;
new_params.cipher_mode = v15_params.cipher_mode;
new_params.is_video = v15_params.is_video;
new_params.pattern = v15_params.pattern_descriptor;
new_params.observe_legacy_fields = true;
new_params.samples.emplace_back();
CdmDecryptionSample& sample = new_params.samples.back();
sample.encrypt_buffer = v15_params.encrypt_buffer;
sample.encrypt_buffer_length = v15_params.encrypt_length;
sample.decrypt_buffer = v15_params.decrypt_buffer;
sample.decrypt_buffer_size = v15_params.decrypt_buffer_length;
sample.decrypt_buffer_offset = v15_params.decrypt_buffer_offset;
sample.iv = *(v15_params.iv);
sample.subsamples.emplace_back();
CdmDecryptionSubsample& subsample = sample.subsamples.back();
if (v15_params.is_encrypted) {
subsample.protected_bytes = v15_params.encrypt_length;
} else {
subsample.clear_bytes = v15_params.encrypt_length;
}
subsample.flags = v15_params.subsample_flags;
subsample.block_offset = v15_params.block_offset;
return new_params;
}
};
struct CdmKeyRequest {
CdmKeyMessage message;
CdmKeyRequestType type;

View File

@@ -1555,30 +1555,23 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id,
return status;
}
CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id,
const CdmDecryptionParameters& parameters) {
if (parameters.key_id == nullptr) {
LOGE("No key ID");
return INVALID_DECRYPT_PARAMETERS_ENG_1;
}
if (parameters.encrypt_buffer == nullptr) {
LOGE("No src encrypt buffer");
return INVALID_DECRYPT_PARAMETERS_ENG_2;
}
if (parameters.iv == nullptr) {
LOGE("No IV");
return INVALID_DECRYPT_PARAMETERS_ENG_3;
}
if (parameters.decrypt_buffer == nullptr) {
if (!parameters.is_secure &&
!Properties::Properties::oem_crypto_use_fifo()) {
LOGE("No dest decrypt buffer");
return INVALID_DECRYPT_PARAMETERS_ENG_4;
CdmResponseType CdmEngine::DecryptV16(
const CdmSessionId& session_id,
const CdmDecryptionParametersV16& parameters) {
for (const CdmDecryptionSample& sample : parameters.samples) {
if (sample.encrypt_buffer == nullptr) {
LOGE("No src encrypt buffer");
return INVALID_DECRYPT_PARAMETERS_ENG_2;
}
if (sample.decrypt_buffer == nullptr) {
if (!parameters.is_secure &&
!Properties::Properties::oem_crypto_use_fifo()) {
LOGE("No dest decrypt buffer");
return INVALID_DECRYPT_PARAMETERS_ENG_4;
}
// else we must be level 1 direct and we don't need to return a buffer.
}
// else we must be level 1 direct and we don't need to return a buffer.
}
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
@@ -1592,7 +1585,7 @@ CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id,
int64_t seconds_remaining = 0;
for (CdmSessionList::iterator iter = sessions.begin();
iter != sessions.end(); ++iter) {
if ((*iter)->IsKeyLoaded(*parameters.key_id)) {
if ((*iter)->IsKeyLoaded(parameters.key_id)) {
int64_t duration = (*iter)->GetDurationRemaining();
if (duration > seconds_remaining) {
session = *iter;

View File

@@ -7,6 +7,8 @@
#include <assert.h>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
#include <string>
#include "cdm_engine.h"
@@ -629,23 +631,33 @@ CdmResponseType CdmSession::QueryOemCryptoSessionId(
}
// Decrypt() - Accept encrypted buffer and return decrypted data.
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParametersV16& params) {
if (!initialized_) {
return NOT_INITIALIZED_ERROR;
}
// Encrypted playback may not begin until either the start time passes or the
bool is_protected = std::any_of(
std::begin(params.samples), std::end(params.samples),
[](const CdmDecryptionSample& sample) {
return std::any_of(std::begin(sample.subsamples),
std::end(sample.subsamples),
[](const CdmDecryptionSubsample& subsample) {
return subsample.protected_bytes > 0;
});
});
// Protected playback may not begin until either the start time passes or the
// license is updated, so we treat this Decrypt call as invalid.
// For the clear lead, we allow playback even if the key_id is not found or if
// the security level is not high enough yet.
if (params.is_encrypted) {
if (!policy_engine_->CanDecryptContent(*params.key_id)) {
if (is_protected) {
if (!policy_engine_->CanDecryptContent(params.key_id)) {
if (policy_engine_->IsLicenseForFuture()) return DECRYPT_NOT_READY;
if (!policy_engine_->IsSufficientOutputProtection(*params.key_id))
if (!policy_engine_->IsSufficientOutputProtection(params.key_id))
return INSUFFICIENT_OUTPUT_PROTECTION;
return NEED_KEY;
}
if (!policy_engine_->CanUseKeyForSecurityLevel(*params.key_id)) {
if (!policy_engine_->CanUseKeyForSecurityLevel(params.key_id)) {
return KEY_PROHIBITED_FOR_SECURITY_LEVEL;
}
}

View File

@@ -69,11 +69,11 @@ OEMCryptoResult ContentKeySession::LoadKeys(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token, CdmCipherMode* cipher_mode,
const std::string& provider_session_token,
const std::string& srm_requirement) {
return LoadKeysAsLicenseType(message, signature, mac_key_iv, mac_key, keys,
provider_session_token, cipher_mode,
srm_requirement, OEMCrypto_ContentLicense);
provider_session_token, srm_requirement,
OEMCrypto_ContentLicense);
}
// Select Key for ContentKeySession
@@ -106,35 +106,18 @@ OEMCryptoResult ContentKeySession::SelectKey(const std::string& key_id,
// Decrypt for ContentKeySession
OEMCryptoResult ContentKeySession::Decrypt(
const CdmDecryptionParameters& params,
OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset,
const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) {
// TODO(b/135285640): fix this. At the moment, all this does is send one
// subsample. No checks are in place.
const OEMCrypto_SampleDescription* samples, size_t samples_length,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
size_t total_size = 0;
for (size_t i = 0; i < samples_length; ++i) {
total_size += samples[i].buffers.input_data_length;
}
OEMCryptoResult sts;
OEMCrypto_SampleDescription sample; // Just one for now.
OEMCrypto_SubSampleDescription subsample;
sample.buffers.input_data = params.encrypt_buffer + additional_offset;
sample.buffers.input_data_length = params.encrypt_length;
sample.buffers.output_descriptor = buffer_descriptor;
memcpy(sample.iv, params.iv->data(), KEY_IV_SIZE);
sample.subsamples = &subsample;
sample.subsamples_length = 1;
if (params.is_encrypted) {
subsample.num_bytes_clear = 0;
subsample.num_bytes_encrypted = params.encrypt_length;
} else {
subsample.num_bytes_clear = params.encrypt_length;
subsample.num_bytes_encrypted = 0;
}
subsample.subsample_flags = params.subsample_flags;
subsample.block_offset = params.block_offset;
M_TIME(sts = OEMCrypto_DecryptCENC(oec_session_id_, &sample, 1,
&pattern_descriptor),
M_TIME(sts = OEMCrypto_DecryptCENC(oec_session_id_, samples, samples_length,
&pattern),
metrics_, oemcrypto_decrypt_cenc_, sts,
metrics::Pow2Bucket(params.encrypt_length));
metrics::Pow2Bucket(total_size));
return sts;
}
@@ -142,7 +125,7 @@ OEMCryptoResult ContentKeySession::LoadKeysAsLicenseType(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token, CdmCipherMode* cipher_mode,
const std::string& provider_session_token,
const std::string& srm_requirement, OEMCrypto_LicenseType license_type) {
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
cached_key_id_.clear();
@@ -173,9 +156,6 @@ OEMCryptoResult ContentKeySession::LoadKeysAsLicenseType(
i, ki->key_control().length());
}
cipher_modes[i] = ToOEMCryptoCipherMode(ki->cipher_mode());
// TODO(jfore): Is returning the cipher needed. If not drop this.
*cipher_mode = ki->cipher_mode();
}
OEMCrypto_Substring pst = GetSubstring(message, provider_session_token);

View File

@@ -13,6 +13,8 @@
#include <iostream>
#include <memory>
#include "advance_iv_ctr.h"
#include "arraysize.h"
#include "content_key_session.h"
#include "crypto_key.h"
#include "entitlement_key_session.h"
@@ -50,20 +52,36 @@
namespace wvcdm {
namespace {
const uint32_t kRsaSignatureLength = 256;
// TODO(b/117112392): adjust chunk size based on resource rating.
const size_t kMaximumChunkSize = 100 * 1024; // 100 KiB
const size_t kEstimatedInitialUsageTableHeader = 40;
// Ability to switch cipher modes in SelectKey() was introduced in this
// OEMCrypto version
const size_t kOemCryptoApiVersionSupportsSwitchingCipherMode = 14;
constexpr size_t KiB = 1024;
constexpr size_t MiB = 1024 * 1024;
constexpr uint32_t kRsaSignatureLength = 256;
constexpr size_t kEstimatedInitialUsageTableHeader = 40;
// Constants and utility objects relating to OEM Certificates
const char* const kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
constexpr const char* kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
constexpr int kMaxTerminateCountDown = 5;
const std::string kStringNotAvailable = "NA";
// Constants relating to OEMCrypto resource rating tiers. These tables are
// ordered by resource rating tier from lowest to highest. These should be
// updated whenever the supported range of resource rating tiers changes.
constexpr size_t kMaxSubsampleRegionSizes[] = {
100 * KiB, // Tier 1 - Low
500 * KiB, // Tier 2 - Medium
1 * MiB, // Tier 3 - High
4 * MiB, // Tier 4 - Very High
};
// The +1 in this calculation is because the bounds RESOURCE_RATING_TIER_MAX and
// RESOURCE_RATING_TIER_MIN are inclusive.
static_assert(ArraySize(kMaxSubsampleRegionSizes) ==
RESOURCE_RATING_TIER_MAX - RESOURCE_RATING_TIER_MIN + 1,
"The kMaxSubsampleRegionSizes table needs to be updated to "
"reflect the supported range of resource rating tiers.");
constexpr size_t kDefaultMaximumChunkSize = 100 * KiB;
// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings
// are not universal but are OEMCrypto method specific. Those will be
// specified in the CryptoSession method rather than here.
@@ -91,6 +109,25 @@ CdmResponseType MapOEMCryptoResult(OEMCryptoResult result,
return default_status;
}
}
void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
switch (dest_buffer->type) {
case OEMCrypto_BufferType_Clear:
dest_buffer->buffer.clear.address += bytes;
dest_buffer->buffer.clear.address_length -= bytes;
return;
case OEMCrypto_BufferType_Secure:
dest_buffer->buffer.secure.offset += bytes;
return;
case OEMCrypto_BufferType_Direct:
// Nothing to do for this buffer type.
return;
}
LOGE("Unrecognized OEMCryptoBufferType %u - doing nothing",
dest_buffer->type);
}
} // namespace
shared_mutex CryptoSession::static_field_mutex_;
@@ -180,7 +217,6 @@ CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
requested_security_level_(kLevelDefault),
usage_support_type_(kUnknownUsageSupport),
usage_table_header_(nullptr),
cipher_mode_(kCipherModeCtr),
api_version_(0) {
assert(metrics);
Init();
@@ -901,8 +937,7 @@ CdmResponseType CryptoSession::LoadKeys(
LOGV("Loading key: id = %u", oec_session_id_);
sts = key_session_->LoadKeys(message, signature, mac_key_iv, mac_key, keys,
provider_session_token, &cipher_mode_,
srm_requirement);
provider_session_token, srm_requirement);
});
if (sts != OEMCrypto_SUCCESS) {
@@ -1295,99 +1330,153 @@ CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
"OEMCrypto_GenerateRSASignature");
}
CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
bool CryptoSession::GetMaxSubsampleRegionSize(size_t* max) {
uint32_t tier = 0;
if (!GetResourceRatingTier(&tier)) return false;
// Subtract RESOURCE_RATING_TIER_MIN to get a 0-based index into the table.
const uint32_t index = tier - RESOURCE_RATING_TIER_MIN;
if (index >= ArraySize(kMaxSubsampleRegionSizes)) return false;
*max = kMaxSubsampleRegionSizes[index];
return true;
}
CdmResponseType CryptoSession::Decrypt(
const CdmDecryptionParametersV16& params) {
if (!is_destination_buffer_type_valid_) {
if (!SetDestinationBufferType()) return UNKNOWN_ERROR;
}
OEMCrypto_DestBufferDesc buffer_descriptor;
buffer_descriptor.type =
OEMCryptoBufferType output_descriptor_type =
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
if (params.is_secure &&
buffer_descriptor.type == OEMCrypto_BufferType_Clear) {
output_descriptor_type == OEMCrypto_BufferType_Clear) {
return SECURE_BUFFER_REQUIRED;
}
switch (buffer_descriptor.type) {
case OEMCrypto_BufferType_Clear:
buffer_descriptor.buffer.clear.address =
static_cast<uint8_t*>(params.decrypt_buffer) +
params.decrypt_buffer_offset;
buffer_descriptor.buffer.clear.address_length =
params.decrypt_buffer_length - params.decrypt_buffer_offset;
break;
case OEMCrypto_BufferType_Secure:
buffer_descriptor.buffer.secure.handle = params.decrypt_buffer;
buffer_descriptor.buffer.secure.offset = params.decrypt_buffer_offset;
buffer_descriptor.buffer.secure.handle_length =
params.decrypt_buffer_length;
break;
case OEMCrypto_BufferType_Direct:
buffer_descriptor.type = OEMCrypto_BufferType_Direct;
buffer_descriptor.buffer.direct.is_video = params.is_video;
break;
if (params.samples.size() == 0) return CANNOT_DECRYPT_ZERO_SAMPLES;
if (std::any_of(std::begin(params.samples), std::end(params.samples),
[](const CdmDecryptionSample& sample) -> bool {
return sample.subsamples.size() == 0;
})) {
return CANNOT_DECRYPT_ZERO_SUBSAMPLES;
}
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (!params.is_encrypted &&
params.subsample_flags ==
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)) {
WithOecSessionLock("Decrypt() calling CopyBuffer", [&] {
M_TIME(sts = OEMCrypto_CopyBuffer(
oec_session_id_, params.encrypt_buffer, params.encrypt_length,
&buffer_descriptor, params.subsample_flags),
metrics_, oemcrypto_copy_buffer_, sts,
metrics::Pow2Bucket(params.encrypt_length));
});
// Convert all the sample and subsample definitions to OEMCrypto structs.
// This code also caches whether any of the data is protected, to save later
// code the trouble of iterating over all the subsamples to check.
bool is_any_sample_protected = false;
std::vector<OEMCrypto_SampleDescription> oec_samples;
oec_samples.reserve(params.samples.size());
std::vector<std::vector<OEMCrypto_SubSampleDescription>>
oec_subsample_vectors;
oec_subsample_vectors.reserve(params.samples.size());
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 (api_version_ < kOemCryptoApiVersionSupportsSwitchingCipherMode) {
if (params.is_encrypted && params.cipher_mode != cipher_mode_) {
return INCORRECT_CRYPTO_MODE;
}
}
if (params.is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
OEMCrypto_CENCEncryptPatternDesc pattern_descriptor;
pattern_descriptor.encrypt = params.pattern_descriptor.encrypt_blocks;
pattern_descriptor.skip = params.pattern_descriptor.skip_blocks;
// Check if key needs to be selected
if (params.is_encrypted) {
CdmResponseType result = SelectKey(*params.key_id, params.cipher_mode);
if (result != NO_ERROR) return result;
for (const CdmDecryptionSample& sample : params.samples) {
oec_samples.emplace_back();
OEMCrypto_SampleDescription& oec_sample = oec_samples.back();
// Set up the sample's input buffer
oec_sample.buffers.input_data = sample.encrypt_buffer;
oec_sample.buffers.input_data_length = sample.encrypt_buffer_length;
// Set up the sample's output buffer
OEMCrypto_DestBufferDesc& output_descriptor =
oec_sample.buffers.output_descriptor;
output_descriptor.type = output_descriptor_type;
switch (output_descriptor.type) {
case OEMCrypto_BufferType_Clear:
output_descriptor.buffer.clear.address =
static_cast<uint8_t*>(sample.decrypt_buffer) +
sample.decrypt_buffer_offset;
output_descriptor.buffer.clear.address_length =
sample.decrypt_buffer_size - sample.decrypt_buffer_offset;
break;
case OEMCrypto_BufferType_Secure:
output_descriptor.buffer.secure.handle = sample.decrypt_buffer;
output_descriptor.buffer.secure.offset = sample.decrypt_buffer_offset;
output_descriptor.buffer.secure.handle_length =
sample.decrypt_buffer_size;
break;
case OEMCrypto_BufferType_Direct:
output_descriptor.buffer.direct.is_video = params.is_video;
break;
}
WithOecSessionLock("Decrypt() calling key_session_->Decrypt()", [&] {
sts = key_session_->Decrypt(params, buffer_descriptor, 0,
pattern_descriptor);
});
// Convert all the sample's subsamples. This code also tallies the total
// size as a sanity check.
oec_subsample_vectors.emplace_back();
std::vector<OEMCrypto_SubSampleDescription>& oec_subsamples =
oec_subsample_vectors.back();
oec_subsamples.reserve(sample.subsamples.size());
size_t sample_size = 0;
bool is_any_subsample_protected = false;
size_t current_block_offset = 0;
for (const CdmDecryptionSubsample& subsample : sample.subsamples) {
oec_subsamples.push_back(OEMCrypto_SubSampleDescription{
subsample.clear_bytes, subsample.protected_bytes,
0, // subsample_flags
current_block_offset});
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;
is_any_subsample_protected |= (subsample.protected_bytes > 0);
sample_size += subsample.clear_bytes + subsample.protected_bytes;
if (params.cipher_mode == kCipherModeCtr) {
current_block_offset =
(current_block_offset + subsample.protected_bytes) %
kAes128BlockSize;
}
if (params.encrypt_length > chunk_size) {
sts = DecryptInChunks(params, buffer_descriptor, pattern_descriptor,
chunk_size);
// TODO(b/149524614): This block is not necessary except for
// backwards-compatibility while we are transitioning from the v15 API to
// the v16 API.
if (params.observe_legacy_fields) {
OEMCrypto_SubSampleDescription& oec_subsample = oec_subsamples.back();
oec_subsample.subsample_flags = subsample.flags;
oec_subsample.block_offset = subsample.block_offset;
}
}
is_any_sample_protected |= is_any_subsample_protected;
// TODO(b/149524614): This check is not necessary except for
// backwards-compatibility while we are transitioning from the v15 API to
// the v16 API.
if (!params.observe_legacy_fields) {
// Set the actual subsample_flags now that all the subsamples are
// converted.
oec_subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample;
oec_subsamples.back().subsample_flags |= OEMCrypto_LastSubsample;
}
// Check that the total size is valid
if (sample_size != oec_sample.buffers.input_data_length)
return SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH;
// Set up the sample's IV
if (is_any_subsample_protected) {
if (sizeof(oec_sample.iv) != sample.iv.size()) return INVALID_IV_SIZE;
memcpy(oec_sample.iv, sample.iv.data(), sizeof(oec_sample.iv));
} else {
memset(oec_sample.iv, 0, sizeof(oec_sample.iv));
}
// Attach the subsamples to the sample description
oec_sample.subsamples = oec_subsamples.data();
oec_sample.subsamples_length = oec_subsamples.size();
}
// Convert the pattern descriptor
OEMCrypto_CENCEncryptPatternDesc oec_pattern{params.pattern.encrypt_blocks,
params.pattern.skip_blocks};
// Check if a key needs to be selected
if (is_any_sample_protected) {
CdmResponseType result = SelectKey(params.key_id, params.cipher_mode);
if (result != NO_ERROR) return result;
}
// Perform decrypt
OEMCryptoResult sts =
DecryptMultipleSamples(oec_samples, params.cipher_mode, oec_pattern);
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
@@ -2494,190 +2583,301 @@ size_t CryptoSession::GenericEncryptionBlockSize(
}
}
OEMCryptoResult CryptoSession::CopyBufferInChunks(
const CdmDecryptionParameters& params,
OEMCrypto_DestBufferDesc full_buffer_descriptor) {
size_t remaining_encrypt_length = params.encrypt_length;
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
// OEMCryptoResult OEMCrypto_DecryptCENC(
// OEMCrypto_SESSION session,
// const OEMCrypto_SampleDescription* samples, // an array of samples.
// size_t samples_length, // the number of samples.
// const OEMCrypto_CENCEncryptPatternDesc* pattern);
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;
OEMCryptoResult CryptoSession::DecryptMultipleSamples(
const std::vector<OEMCrypto_SampleDescription>& samples,
CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
// Update the remaining length of the original buffer only after
// calculating the new values.
remaining_encrypt_length -= chunk_size;
// If there's only one sample, automatically fall through to avoid a redundant
// roundtrip through OEMCrypto_DecryptCENC()
if (samples.size() > 1) {
WithOecSessionLock("DecryptMultipleSamples", [&] {
sts = key_session_->Decrypt(samples.data(), samples.size(), pattern);
});
}
// Update the destination buffer with the new offset. Because OEMCrypto
// can modify the OEMCrypto_DestBufferDesc during the call to
// OEMCrypto_CopyBuffer, (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.address_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;
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// Fall back to sending each sample individually
for (const OEMCrypto_SampleDescription& sample : samples) {
sts = DecryptSample(sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
}
}
return sts;
}
OEMCryptoResult CryptoSession::DecryptSample(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
// If there's only one subsample and it contains only one type of region,
// automatically fall through to avoid a redundant roundtrip through
// OEMCrypto_DecryptCENC()
if (sample.subsamples_length > 1 ||
(sample.subsamples[0].num_bytes_clear > 0 &&
sample.subsamples[0].num_bytes_encrypted > 0)) {
WithOecSessionLock("DecryptSample", [&] {
sts = key_session_->Decrypt(&sample, 1, pattern);
});
}
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// Fall back to sending each subsample region individually
OEMCrypto_SampleDescription fake_sample = sample;
for (size_t i = 0; i < sample.subsamples_length; ++i) {
const OEMCrypto_SubSampleDescription& original_subsample =
sample.subsamples[i];
if (original_subsample.num_bytes_clear > 0) {
const size_t length = original_subsample.num_bytes_clear;
OEMCrypto_SubSampleDescription clear_subsample{
length,
0, // num_bytes_encrypted
0, // subsample_flags
0 // block_offset, not relevant for clear data
};
if (original_subsample.subsample_flags & OEMCrypto_FirstSubsample) {
clear_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
}
if ((original_subsample.subsample_flags & OEMCrypto_LastSubsample) &&
original_subsample.num_bytes_encrypted == 0) {
clear_subsample.subsample_flags |= OEMCrypto_LastSubsample;
}
fake_sample.buffers.input_data_length = length;
fake_sample.subsamples = &clear_subsample;
fake_sample.subsamples_length = 1;
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
fake_sample.buffers.input_data += length;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
}
if (original_subsample.num_bytes_encrypted > 0) {
const size_t length = original_subsample.num_bytes_encrypted;
OEMCrypto_SubSampleDescription encrypted_subsample{
0, // num_bytes_clear
length,
0, // subsample_flags
original_subsample.block_offset};
if ((original_subsample.subsample_flags & OEMCrypto_FirstSubsample) &&
original_subsample.num_bytes_clear == 0) {
encrypted_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
}
if (original_subsample.subsample_flags & OEMCrypto_LastSubsample) {
encrypted_subsample.subsample_flags |= OEMCrypto_LastSubsample;
}
fake_sample.buffers.input_data_length = length;
fake_sample.subsamples = &encrypted_subsample;
fake_sample.subsamples_length = 1;
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
fake_sample.buffers.input_data += length;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
if (cipher_mode == kCipherModeCtr) {
AdvanceIvCtr(&fake_sample.iv,
original_subsample.block_offset +
original_subsample.num_bytes_encrypted);
}
}
}
}
return sts;
}
OEMCryptoResult CryptoSession::LegacyDecrypt(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
size_t max_chunk_size;
if (!GetMaxSubsampleRegionSize(&max_chunk_size)) {
LOGW("Unable to get maximum subsample region size. Defaulting to 100 KiB.");
max_chunk_size = kDefaultMaximumChunkSize;
}
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
// We can be sure this is only called with one subsample containing one
// region of data.
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
const bool is_encrypted = (subsample.num_bytes_encrypted > 0);
const bool is_only_subsample =
subsample.subsample_flags ==
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
if (!is_encrypted && is_only_subsample) {
WithOecSessionLock("LegacyDecrypt() calling CopyBuffer", [&] {
M_TIME(sts = OEMCrypto_CopyBuffer(
oec_session_id_, sample.buffers.input_data,
sample.buffers.input_data_length,
&sample.buffers.output_descriptor, subsample.subsample_flags),
metrics_, oemcrypto_copy_buffer_, sts,
metrics::Pow2Bucket(sample.buffers.input_data_length));
});
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE &&
sample.buffers.input_data_length > max_chunk_size) {
// OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up
// into 100 KiB sections.
sts = LegacyCopyBufferInChunks(sample, max_chunk_size);
}
}
if (is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
WithOecSessionLock("LegacyDecrypt() calling key_session_->Decrypt()", [&] {
sts = key_session_->Decrypt(&sample, 1, pattern);
});
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.encrypt + pattern.skip) * kAes128BlockSize;
const size_t chunk_size =
pattern_length > 0
? max_chunk_size - (max_chunk_size % pattern_length)
: max_chunk_size;
if (sample.buffers.input_data_length > chunk_size) {
sts = LegacyDecryptInChunks(sample, cipher_mode, pattern, chunk_size);
}
}
}
return sts;
}
OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks(
const OEMCrypto_SampleDescription& sample, size_t max_chunk_size) {
const uint8_t* input_data = sample.buffers.input_data;
size_t remaining_input_data = sample.buffers.input_data_length;
OEMCrypto_DestBufferDesc output_descriptor = sample.buffers.output_descriptor;
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
OEMCryptoResult sts = OEMCrypto_SUCCESS;
while (remaining_input_data > 0) {
// Calculate the size of the next chunk.
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
// Re-add "last subsample" flag if this is the last subsample.
if (remaining_encrypt_length == 0) {
if (chunk_size == remaining_input_data) {
subsample_flags |= OEMCrypto_LastSubsample;
}
OEMCryptoResult sts;
WithOecSessionLock("CopyBufferInChunks", [&] {
M_TIME(sts = OEMCrypto_CopyBuffer(
oec_session_id_, params.encrypt_buffer + additional_offset,
chunk_size, &buffer_descriptor, subsample_flags),
WithOecSessionLock("LegacyCopyBufferInChunks", [&] {
M_TIME(sts = OEMCrypto_CopyBuffer(oec_session_id_, input_data, chunk_size,
&output_descriptor, subsample_flags),
metrics_, oemcrypto_copy_buffer_, sts,
metrics::Pow2Bucket(chunk_size));
});
if (sts != OEMCrypto_SUCCESS) {
return sts;
}
if (sts != OEMCrypto_SUCCESS) break;
// Clear any subsample flags before the next loop iteration.
subsample_flags = 0;
// Update the source and destination buffers based on the amount of data
// copied.
input_data += chunk_size;
remaining_input_data -= chunk_size;
AdvanceDestBuffer(&output_descriptor, chunk_size);
}
return OEMCrypto_SUCCESS;
return sts;
}
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;
OEMCryptoResult CryptoSession::LegacyDecryptInChunks(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern, size_t max_chunk_size) {
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
const size_t pattern_length_in_bytes =
(pattern_descriptor.encrypt + pattern_descriptor.skip) * kAes128BlockSize;
const bool is_protected = (subsample.num_bytes_encrypted > 0);
OEMCrypto_SampleDescription fake_sample = sample;
OEMCrypto_SubSampleDescription fake_subsample = subsample;
fake_sample.subsamples = &fake_subsample;
fake_subsample.subsample_flags =
subsample.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, max_chunk_size);
const size_t additional_offset =
params.encrypt_length - remaining_encrypt_length;
size_t remaining_input_data = sample.buffers.input_data_length;
OEMCryptoResult sts = OEMCrypto_SUCCESS;
// 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.address_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;
while (remaining_input_data > 0) {
// Calculate the size of the next chunk.
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
fake_sample.buffers.input_data_length = chunk_size;
if (is_protected) {
fake_subsample.num_bytes_encrypted = chunk_size;
} else {
fake_subsample.num_bytes_clear = chunk_size;
}
// 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;
if (chunk_size == remaining_input_data) {
fake_subsample.subsample_flags |=
subsample.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| and |fake_subsample.block_offset| 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;
sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
WithOecSessionLock("DecryptInChunks", [&] {
M_TIME(sts = key_session_->Decrypt(params, buffer_descriptor,
additional_offset, pattern_descriptor),
metrics_, oemcrypto_decrypt_cenc_, sts,
metrics::Pow2Bucket(chunk_size));
WithOecSessionLock("LegacyDecryptInChunks", [&] {
sts = key_session_->Decrypt(&fake_sample, 1, pattern);
});
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.
if (sts != OEMCrypto_SUCCESS) break;
// 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.
// Clear any subsample flags before the next loop iteration.
fake_subsample.subsample_flags = 0;
// Update the IV so that it is valid for the next iteration. This should not
// be done on the last iteration both to save time and because the 'cbcs'
// calculation can underflow if the chunk is less than the max chunk size.
if (remaining_input_data > chunk_size) {
if (cipher_mode == kCipherModeCtr) {
// For 'cenc', update the IV depending on how many encrypted blocks
// we passed.
AdvanceIvCtr(&fake_sample.iv, chunk_size + fake_subsample.block_offset);
} else if (cipher_mode == kCipherModeCbc) {
// For 'cbcs', use the last ciphertext block as the next IV. The last
// block that was encrypted is probably not the last block of the
// subsample. Since the max 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.
const uint8_t* const buffer_end =
params.encrypt_buffer + additional_offset + chunk_size;
fake_sample.buffers.input_data + chunk_size;
const uint8_t* const block_end =
buffer_end - kAes128BlockSize * pattern.skip;
const uint8_t* block_end = nullptr;
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);
static_assert(sizeof(fake_sample.iv) == kAes128BlockSize,
"The size of an AES-128 block and the size of an AES-128 "
"IV have become misaligned.");
memcpy(fake_sample.iv, block_end - kAes128BlockSize, kAes128BlockSize);
}
}
// Clear any subsample flags before the next loop iteration.
subsample_flags = 0;
// Update the source and destination buffers based on the amount of data
// copied.
fake_sample.buffers.input_data += chunk_size;
remaining_input_data -= chunk_size;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size);
}
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);
return sts;
}
template <class Func>

View File

@@ -17,13 +17,13 @@ OEMCryptoResult EntitlementKeySession::LoadKeys(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token, CdmCipherMode* cipher_mode,
const std::string& provider_session_token,
const std::string& srm_requirement) {
// Call our superclass's LoadKeysAsLicenseType(), but set the license type to
// OEMCrypto_EntitlementLicense.
return ContentKeySession::LoadKeysAsLicenseType(
message, signature, mac_key_iv, mac_key, keys, provider_session_token,
cipher_mode, srm_requirement, OEMCrypto_EntitlementLicense);
srm_requirement, OEMCrypto_EntitlementLicense);
}
OEMCryptoResult EntitlementKeySession::LoadEntitledContentKeys(

View File

@@ -95,8 +95,8 @@ class MockCdmEngineImpl : public CdmEngine {
CdmResponseType(const std::string&, const CdmSecureStopId&));
MOCK_METHOD1(ReleaseUsageInfo,
CdmResponseType(const CdmUsageInfoReleaseMessage&));
MOCK_METHOD2(Decrypt, CdmResponseType(const CdmSessionId&,
const CdmDecryptionParameters&));
MOCK_METHOD2(DecryptV16, CdmResponseType(const CdmSessionId&,
const CdmDecryptionParametersV16&));
MOCK_METHOD2(FindSessionForKey, bool(const KeyId&, CdmSessionId*));
};
@@ -491,12 +491,12 @@ TEST_F(WvCdmEngineMetricsImplTest, FindSessionForKey) {
}
TEST_F(WvCdmEngineMetricsImplTest, Decrypt) {
CdmDecryptionParameters parameters;
EXPECT_CALL(*test_cdm_metrics_engine_, Decrypt(Eq("fake session id"), _))
CdmDecryptionParametersV16 parameters;
EXPECT_CALL(*test_cdm_metrics_engine_, DecryptV16(Eq("fake session id"), _))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR));
ASSERT_EQ(wvcdm::UNKNOWN_ERROR,
test_cdm_metrics_engine_->Decrypt("fake session id", parameters));
ASSERT_EQ(wvcdm::UNKNOWN_ERROR, test_cdm_metrics_engine_->DecryptV16(
"fake session id", parameters));
drm_metrics::WvCdmMetrics metrics_proto;
test_cdm_metrics_engine_->GetMetricsSnapshot(&metrics_proto);

View File

@@ -23,6 +23,12 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case ANALOG_OUTPUT_ERROR:
*os << "ANALOG_OUTPUT_ERROR";
break;
case CANNOT_DECRYPT_ZERO_SAMPLES:
*os << "CANNOT_DECRYPT_ZERO_SAMPLES";
break;
case CANNOT_DECRYPT_ZERO_SUBSAMPLES:
*os << "CANNOT_DECRYPT_ZERO_SUBSAMPLES";
break;
case CENC_INIT_DATA_UNAVAILABLE:
*os << "CENC_INIT_DATA_UNAVAILABLE";
break;
@@ -314,6 +320,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case INVALID_DEVICE_CERTIFICATE_TYPE:
*os << "INVALID_DEVICE_CERTIFICATE_TYPE";
break;
case INVALID_IV_SIZE:
*os << "INVALID_IV_SIZE";
break;
case INVALID_KEY_SYSTEM:
*os << "INVALID_KEY_SYSTEM";
break;
@@ -725,6 +734,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case RSA_SIGNATURE_GENERATION_ERROR:
*os << "RSA_SIGNATURE_GENERATION_ERROR";
break;
case SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH:
*os << "SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH";
break;
case SERVICE_CERTIFICATE_PROVIDER_ID_EMPTY:
*os << "SERVICE_CERTIFICATE_PROVIDER_ID_EMPTY";
break;