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;

View File

@@ -127,6 +127,11 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
bool validate_key_id,
const CdmDecryptionParameters& parameters);
// Accept encrypted sample and decrypt data.
virtual CdmResponseType DecryptV16(
const CdmSessionId& session_id, bool validate_key_id,
const CdmDecryptionParametersV16& parameters);
virtual void NotifyResolution(const CdmSessionId& session_id, uint32_t width,
uint32_t height);

View File

@@ -4,6 +4,9 @@
#include "wv_content_decryption_module.h"
#include <algorithm>
#include <iterator>
#include "cdm_client_property_set.h"
#include "cdm_engine.h"
#include "cdm_engine_factory.h"
@@ -286,11 +289,18 @@ CdmResponseType WvContentDecryptionModule::GetSecureStopIds(
CdmResponseType WvContentDecryptionModule::Decrypt(
const CdmSessionId& session_id, bool validate_key_id,
const CdmDecryptionParameters& parameters) {
return DecryptV16(session_id, validate_key_id,
CdmDecryptionParametersV16::from_v15(parameters));
}
CdmResponseType WvContentDecryptionModule::DecryptV16(
const CdmSessionId& session_id, bool validate_key_id,
const CdmDecryptionParametersV16& parameters) {
// First find the CdmEngine that has the given session_id. If we are using
// key sharing, the shared session will still be in the same CdmEngine.
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
if (!cdm_engine) {
LOGE("WvContentDecryptionModule::Decrypt: session not found: %s",
LOGE("WvContentDecryptionModule::DecryptV16: session not found: %s",
session_id.c_str());
return SESSION_NOT_FOUND_18;
}
@@ -298,18 +308,26 @@ CdmResponseType WvContentDecryptionModule::Decrypt(
CdmSessionId local_session_id = session_id;
if (validate_key_id && Properties::GetSessionSharingId(session_id) != 0) {
bool status =
cdm_engine->FindSessionForKey(*parameters.key_id, &local_session_id);
cdm_engine->FindSessionForKey(parameters.key_id, &local_session_id);
if (!status) {
// key does not need to be loaded if clear lead/frame has a
// single subsample. It does in all other cases.
if (parameters.is_encrypted ||
!(parameters.subsample_flags & OEMCrypto_FirstSubsample) ||
!(parameters.subsample_flags & OEMCrypto_LastSubsample)) {
// A key does not need to be loaded if the content consists entirely of
// clear data.
bool is_any_protected = std::any_of(
std::begin(parameters.samples), std::end(parameters.samples),
[](const CdmDecryptionSample& sample) -> bool {
return std::any_of(
std::begin(sample.subsamples), std::end(sample.subsamples),
[](const CdmDecryptionSubsample& subsample) -> bool {
return subsample.protected_bytes > 0;
});
});
if (is_any_protected) {
return KEY_NOT_FOUND_IN_SESSION;
}
}
}
return cdm_engine->Decrypt(local_session_id, parameters);
return cdm_engine->DecryptV16(local_session_id, parameters);
}
void WvContentDecryptionModule::NotifyResolution(const CdmSessionId& session_id,

View File

@@ -0,0 +1,46 @@
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WVCDM_UTIL_ADVANCE_IV_CTR_H_
#define WVCDM_UTIL_ADVANCE_IV_CTR_H_
#include <stdint.h>
#include <string.h>
#include "string_conversions.h"
namespace wvcdm {
// Advance an IV according to ISO-CENC's CTR modes. The lower half of the IV is
// split off and treated as an unsigned 64-bit integer, then incremented by the
// number of complete crypto blocks decrypted. The resulting value is then
// copied back into the IV over the previous lower half.
inline void AdvanceIvCtr(uint8_t (*subsample_iv)[16], size_t bytes) {
constexpr size_t kAesBlockSize = 16;
constexpr size_t kIvSize = kAesBlockSize;
constexpr size_t kCounterIndex = kIvSize / 2;
constexpr size_t kCounterSize = kIvSize / 2;
uint64_t counter;
static_assert(
sizeof(*subsample_iv) == kIvSize,
"The subsample_iv field is no longer the length of an AES-128 IV.");
static_assert(sizeof(counter) == kCounterSize,
"A uint64_t failed to be half the size of an AES-128 IV.");
// Defensive copy because the elements of the array may not be properly
// aligned
memcpy(&counter, &(*subsample_iv)[kCounterIndex], kCounterSize);
const size_t increment =
bytes / kAesBlockSize; // The truncation here is intentional
counter = htonll64(ntohll64(counter) + increment);
memcpy(&(*subsample_iv)[kCounterIndex], &counter, kCounterSize);
}
} // namespace wvcdm
#endif // WVCDM_UTIL_ADVANCE_IV_CTR_H_

View File

@@ -285,10 +285,14 @@ enum {
kLoadProvisioningError = ERROR_DRM_VENDOR_MIN + 302,
kLoadLicenseError = ERROR_DRM_VENDOR_MIN + 303,
kLoadRenewalError = ERROR_DRM_VENDOR_MIN + 304,
kCannotDecryptZeroSamples = ERROR_DRM_VENDOR_MIN + 305,
kCannotDecryptZeroSubsamples = ERROR_DRM_VENDOR_MIN + 306,
kSampleAndSubsampleSizeMismatch = ERROR_DRM_VENDOR_MIN + 307,
kInvalidIvSize = ERROR_DRM_VENDOR_MIN + 308,
// This should always follow the last error code.
// The offset value should be updated each time a new error code is added.
kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 304,
kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 308,
// Used by crypto test mode
kErrorTestMode = ERROR_DRM_VENDOR_MAX,

View File

@@ -72,6 +72,10 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) {
// Alphabetically ordered based on the case condition.
case wvcdm::ADD_KEY_ERROR:
return kAddKeyError;
case wvcdm::CANNOT_DECRYPT_ZERO_SAMPLES:
return kCannotDecryptZeroSamples;
case wvcdm::CANNOT_DECRYPT_ZERO_SUBSAMPLES:
return kCannotDecryptZeroSubsamples;
case wvcdm::CENC_INIT_DATA_UNAVAILABLE:
return kCencInitDataUnavailable;
case wvcdm::CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE:
@@ -230,6 +234,8 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) {
return kInvalidDecryptParametersEng4;
case wvcdm::INVALID_DEVICE_CERTIFICATE_TYPE:
return kInvalidDeviceCertificateType;
case wvcdm::INVALID_IV_SIZE:
return kInvalidIvSize;
case wvcdm::INVALID_KEY_SYSTEM:
return kInvalidKeySystem;
case wvcdm::INVALID_LICENSE_REQUEST_TYPE_1:
@@ -464,6 +470,8 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) {
return kRenewKeyError2;
case wvcdm::RESTORE_OFFLINE_LICENSE_ERROR_2:
return kRestoreOfflineLicenseError2;
case wvcdm::SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH:
return kSampleAndSubsampleSizeMismatch;
case wvcdm::SESSION_FILE_HANDLE_INIT_ERROR:
return kSessionFileHandleInitError;
case wvcdm::SESSION_KEYS_NOT_FOUND:

View File

@@ -351,6 +351,10 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) {
case wvcdm::LOAD_PROVISIONING_ERROR:
case wvcdm::LOAD_LICENSE_ERROR:
case wvcdm::LOAD_RENEWAL_ERROR:
case wvcdm::CANNOT_DECRYPT_ZERO_SAMPLES:
case wvcdm::CANNOT_DECRYPT_ZERO_SUBSAMPLES:
case wvcdm::SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH:
case wvcdm::INVALID_IV_SIZE:
ALOGW("Returns UNKNOWN error for legacy status: %d", res);
return Status::ERROR_DRM_UNKNOWN;

View File

@@ -48,9 +48,8 @@ class WVCryptoPlugin : public android::CryptoPlugin {
wvcdm::CdmSessionId configureTestMode(const void* data, size_t size);
android::status_t attemptDecrypt(
const wvcdm::CdmDecryptionParameters& params,
const wvcdm::CdmDecryptionParametersV16& params,
bool haveEncryptedSubsamples, android::AString* errorDetailMsg);
static void incrementIV(uint64_t increaseBy, std::vector<uint8_t>* ivPtr);
};
} // namespace wvdrm

View File

@@ -69,10 +69,9 @@ struct WVCryptoPlugin : public ICryptoPlugin {
sp<wvcdm::WvContentDecryptionModule> const mCDM;
Status_V1_2 attemptDecrypt(
const wvcdm::CdmDecryptionParameters& params,
Status_V1_2 attemptDecrypt(
const wvcdm::CdmDecryptionParametersV16& params,
bool haveEncryptedSubsamples, std::string* errorDetailMsg);
static void incrementIV(uint64_t increaseBy, std::vector<uint8_t>* ivPtr);
};
} // namespace widevine

View File

@@ -12,6 +12,7 @@
#include <algorithm>
#include <endian.h>
#include <iterator>
#include <string.h>
#include <string>
#include <vector>
@@ -25,12 +26,6 @@
#include "wv_cdm_constants.h"
#include "WVErrors.h"
namespace {
static const size_t kAESBlockSize = 16;
} // namespace
namespace wvdrm {
using namespace android;
@@ -108,7 +103,18 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
if (mode != kMode_Unencrypted &&
mode != kMode_AES_CTR &&
mode != kMode_AES_CBC) {
errorDetailMsg->setTo("Encryption mode is not supported by Widevine CDM.");
errorDetailMsg->setTo(
"The requested encryption mode is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto;
} else if (mode == kMode_AES_CTR &&
(pattern.mEncryptBlocks != 0 || pattern.mSkipBlocks != 0)) {
errorDetailMsg->setTo(
"The 'cens' schema is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto;
} else if (mode == kMode_AES_CBC &&
(pattern.mEncryptBlocks == 0 && pattern.mSkipBlocks == 0)) {
errorDetailMsg->setTo(
"The 'cbc1' schema is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto;
}
@@ -118,158 +124,55 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
const uint8_t* const source = static_cast<const uint8_t*>(srcPtr);
uint8_t* const dest = static_cast<uint8_t*>(dstPtr);
// Calculate the output buffer size and determine if any subsamples are
// encrypted.
size_t destSize = 0;
bool haveEncryptedSubsamples = false;
for (size_t i = 0; i < numSubSamples; i++) {
const SubSample &subSample = subSamples[i];
destSize += subSample.mNumBytesOfClearData;
destSize += subSample.mNumBytesOfEncryptedData;
if (subSample.mNumBytesOfEncryptedData > 0) {
haveEncryptedSubsamples = true;
}
}
// Set up the decrypt params that do not vary.
CdmDecryptionParameters params = CdmDecryptionParameters();
// Set up the decrypt params
CdmDecryptionParametersV16 params;
params.key_id = keyId;
params.is_secure = secure;
params.key_id = &keyId;
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;
}
params.pattern.encrypt_blocks = pattern.mEncryptBlocks;
params.pattern.skip_blocks = pattern.mSkipBlocks;
// Iterate through subsamples, sending them to the CDM serially.
size_t offset = 0;
size_t blockOffset = 0;
const size_t patternLengthInBytes =
(pattern.mEncryptBlocks + pattern.mSkipBlocks) * kAESBlockSize;
// Set up the sample
// Android's API only supports one at a time
params.samples.emplace_back();
CdmDecryptionSample& sample = params.samples.back();
sample.encrypt_buffer = source;
sample.decrypt_buffer = dest;
sample.decrypt_buffer_offset = 0;
sample.iv = ivVector;
for (size_t i = 0; i < numSubSamples; ++i) {
const SubSample& subSample = subSamples[i];
// Set up the subsamples
// We abuse std::transform() here to also do some side-effects: Tallying the
// total size of the sample and checking if any of the data is protected.
size_t totalSize = 0;
bool hasProtectedData = false;
sample.subsamples.reserve(numSubSamples);
std::transform(subSamples, subSamples + numSubSamples,
std::back_inserter(sample.subsamples),
[&](const SubSample& subSample) -> CdmDecryptionSubsample {
totalSize +=
subSample.mNumBytesOfClearData + subSample.mNumBytesOfEncryptedData;
hasProtectedData |= subSample.mNumBytesOfEncryptedData > 0;
return CdmDecryptionSubsample(subSample.mNumBytesOfClearData,
subSample.mNumBytesOfEncryptedData);
});
if (mode == kMode_Unencrypted && subSample.mNumBytesOfEncryptedData != 0) {
errorDetailMsg->setTo("Encrypted subsamples found in allegedly "
"unencrypted data.");
return kErrorExpectedUnencrypted;
}
sample.encrypt_buffer_length = totalSize;
sample.decrypt_buffer_size = totalSize;
// Calculate any flags that apply to this subsample's parts.
uint8_t clearFlags = 0;
uint8_t encryptedFlags = 0;
if (mode == kMode_Unencrypted && hasProtectedData) {
errorDetailMsg->setTo("Protected ranges found in allegedly clear data.");
return kErrorExpectedUnencrypted;
}
// If this is the first subsample…
if (i == 0) {
// …add OEMCrypto_FirstSubsample to the first part that is present.
if (subSample.mNumBytesOfClearData != 0) {
clearFlags = clearFlags | OEMCrypto_FirstSubsample;
} else {
encryptedFlags = encryptedFlags | OEMCrypto_FirstSubsample;
}
}
// If this is the last subsample…
if (i == numSubSamples - 1) {
// …add OEMCrypto_LastSubsample to the last part that is present
if (subSample.mNumBytesOfEncryptedData != 0) {
encryptedFlags = encryptedFlags | OEMCrypto_LastSubsample;
} else {
clearFlags = clearFlags | OEMCrypto_LastSubsample;
}
}
// "Decrypt" any unencrypted data. Per the ISO-CENC standard, clear data
// comes before encrypted data.
if (subSample.mNumBytesOfClearData != 0) {
params.is_encrypted = false;
params.encrypt_buffer = source + offset;
params.encrypt_length = subSample.mNumBytesOfClearData;
params.block_offset = 0;
params.decrypt_buffer_offset = offset;
params.subsample_flags = clearFlags;
status_t res = attemptDecrypt(params, haveEncryptedSubsamples,
errorDetailMsg);
if (res != android::OK) {
return res;
}
offset += subSample.mNumBytesOfClearData;
}
// Decrypt any encrypted data. Per the ISO-CENC standard, encrypted data
// comes after clear data.
if (subSample.mNumBytesOfEncryptedData != 0) {
params.is_encrypted = true;
params.encrypt_buffer = source + offset;
params.encrypt_length = subSample.mNumBytesOfEncryptedData;
params.block_offset = blockOffset;
params.decrypt_buffer_offset = offset;
params.subsample_flags = encryptedFlags;
status_t res = attemptDecrypt(params, haveEncryptedSubsamples,
errorDetailMsg);
if (res != android::OK) {
return res;
}
offset += subSample.mNumBytesOfEncryptedData;
// 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 {
// The truncation in the integer divisions in this block is
// intentional.
const uint64_t numBlocks =
subSample.mNumBytesOfEncryptedData / kAESBlockSize;
const uint64_t patternLengthInBlocks =
pattern.mEncryptBlocks + pattern.mSkipBlocks;
const uint64_t numFullPatternRepetitions =
numBlocks / patternLengthInBlocks;
const uint64_t numDanglingBlocks =
numBlocks % patternLengthInBlocks;
const uint64_t numDanglingEncryptedBlocks =
std::min(static_cast<uint64_t>(pattern.mEncryptBlocks),
numDanglingBlocks);
increment =
numFullPatternRepetitions * pattern.mEncryptBlocks +
numDanglingEncryptedBlocks;
}
incrementIV(increment, &ivVector);
// Update the block offset
blockOffset = (blockOffset + subSample.mNumBytesOfEncryptedData) %
kAESBlockSize;
} 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.
}
// Decrypt
status_t res = attemptDecrypt(params, hasProtectedData, errorDetailMsg);
if (res != android::OK) {
return res;
}
// In test mode, we return an error that causes a detailed error
@@ -285,7 +188,7 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
SHA256_CTX ctx;
uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256_Init(&ctx);
SHA256_Update(&ctx, dstPtr, offset);
SHA256_Update(&ctx, dstPtr, totalSize);
SHA256_Final(digest, &ctx);
String8 buf;
for (size_t i = 0; i < sizeof(digest); i++) {
@@ -297,21 +200,21 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
return kErrorTestMode;
}
return static_cast<ssize_t>(offset);
return static_cast<ssize_t>(totalSize);
}
status_t WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params,
bool haveEncryptedSubsamples,
AString* errorDetailMsg) {
CdmResponseType res = mCDM->Decrypt(mSessionId, haveEncryptedSubsamples,
params);
status_t WVCryptoPlugin::attemptDecrypt(
const CdmDecryptionParametersV16& params, bool hasProtectedData,
AString* errorDetailMsg) {
CdmResponseType res = mCDM->DecryptV16(mSessionId, hasProtectedData,
params);
if (isCdmResponseTypeSuccess(res)) {
return android::OK;
} else {
ALOGE("Decrypt error result in session %s during %s block: %d",
ALOGE("Decrypt error in session %s during a sample %s protected data: %d",
mSessionId.c_str(),
params.is_encrypted ? "encrypted" : "unencrypted",
hasProtectedData ? "with" : "without",
res);
bool actionableError = true;
switch (res) {
@@ -360,10 +263,4 @@ status_t WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params,
}
}
void WVCryptoPlugin::incrementIV(uint64_t increaseBy, vector<uint8_t>* ivPtr) {
vector<uint8_t>& iv = *ivPtr;
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);
(*counterBuffer) = htonq(ntohq(*counterBuffer) + increaseBy);
}
} // namespace wvdrm

View File

@@ -12,6 +12,7 @@
#include <algorithm>
#include <hidlmemory/mapping.h>
#include <iterator>
#include "HidlTypes.h"
#include "mapErrors-inl.h"
@@ -23,8 +24,6 @@
namespace {
static const size_t kAESBlockSize = 16;
inline Status toStatus_1_0(Status_V1_2 status) {
switch (status) {
case Status_V1_2::ERROR_DRM_INSUFFICIENT_SECURITY:
@@ -45,7 +44,9 @@ namespace V1_2 {
namespace widevine {
using android::hardware::drm::V1_2::widevine::toVector;
using wvcdm::CdmDecryptionParameters;
using wvcdm::CdmDecryptionParametersV16;
using wvcdm::CdmDecryptionSample;
using wvcdm::CdmDecryptionSubsample;
using wvcdm::CdmQueryMap;
using wvcdm::CdmResponseType;
using wvcdm::CdmSessionId;
@@ -158,26 +159,36 @@ Return<void> WVCryptoPlugin::decrypt_1_2(
const DestinationBuffer& destination,
decrypt_1_2_cb _hidl_cb) {
if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) {
if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"source decrypt buffer base not set");
return Void();
}
if (destination.type == BufferType::SHARED_MEMORY) {
const SharedBuffer& dest = destination.nonsecureMemory;
if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"source decrypt buffer base not set");
"destination decrypt buffer base not set");
return Void();
}
if (destination.type == BufferType::SHARED_MEMORY) {
const SharedBuffer& dest = destination.nonsecureMemory;
if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"destination decrypt buffer base not set");
return Void();
}
}
}
if (mode != Mode::UNENCRYPTED &&
mode != Mode::AES_CTR &&
mode != Mode::AES_CBC) {
_hidl_cb(Status_V1_2::BAD_VALUE,
0, "Encryption mode is not supported by Widevine CDM.");
mode != Mode::AES_CTR &&
mode != Mode::AES_CBC) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The requested encryption mode is not supported by Widevine CDM.");
return Void();
} else if (mode == Mode::AES_CTR &&
(pattern.encryptBlocks != 0 || pattern.skipBlocks != 0)) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The 'cens' schema is not supported by Widevine CDM.");
return Void();
} else if (mode == Mode::AES_CBC &&
(pattern.encryptBlocks == 0 && pattern.skipBlocks == 0)) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The 'cbc1' schema is not supported by Widevine CDM.");
return Void();
}
@@ -221,176 +232,75 @@ Return<void> WVCryptoPlugin::decrypt_1_2(
destPtr = static_cast<void *>(handle);
}
// Calculate the output buffer size and determine if any subsamples are
// encrypted.
size_t destSize = 0;
bool haveEncryptedSubsamples = false;
for (size_t i = 0; i < subSamples.size(); i++) {
const SubSample &subSample = subSamples[i];
destSize += subSample.numBytesOfClearData;
destSize += subSample.numBytesOfEncryptedData;
if (subSample.numBytesOfEncryptedData > 0) {
haveEncryptedSubsamples = true;
}
}
// Set up the decrypt params that do not vary.
CdmDecryptionParameters params = CdmDecryptionParameters();
// Set up the decrypt params
CdmDecryptionParametersV16 params;
params.key_id = cryptoKey;
params.is_secure = secure;
params.key_id = &cryptoKey;
params.iv = &ivVector;
params.decrypt_buffer = destPtr;
params.decrypt_buffer_length = destSize;
params.pattern_descriptor.encrypt_blocks = pattern.encryptBlocks;
params.pattern_descriptor.skip_blocks = pattern.skipBlocks;
if (mode == Mode::AES_CTR) {
params.cipher_mode = wvcdm::kCipherModeCtr;
} else if (mode == Mode::AES_CBC) {
params.cipher_mode = wvcdm::kCipherModeCbc;
}
params.pattern.encrypt_blocks = pattern.encryptBlocks;
params.pattern.skip_blocks = pattern.skipBlocks;
// Iterate through subsamples, sending them to the CDM serially.
size_t bufferOffset = 0;
size_t blockOffset = 0;
const size_t patternLengthInBytes =
(pattern.encryptBlocks + pattern.skipBlocks) * kAESBlockSize;
// Set up the sample
// Android's API only supports one at a time
params.samples.emplace_back();
CdmDecryptionSample& sample = params.samples.back();
sample.encrypt_buffer = srcPtr;
sample.decrypt_buffer = destPtr;
sample.decrypt_buffer_offset = 0;
sample.iv = ivVector;
for (size_t i = 0; i < subSamples.size(); ++i) {
const SubSample& subSample = subSamples[i];
// Set up the subsamples
// We abuse std::transform() here to also do some side-effects: Tallying the
// total size of the sample and checking if any of the data is protected.
size_t totalSize = 0;
bool hasProtectedData = false;
sample.subsamples.reserve(subSamples.size());
std::transform(subSamples.data(), subSamples.data() + subSamples.size(),
std::back_inserter(sample.subsamples),
[&](const SubSample& subSample) -> CdmDecryptionSubsample {
totalSize +=
subSample.numBytesOfClearData + subSample.numBytesOfEncryptedData;
hasProtectedData |= subSample.numBytesOfEncryptedData > 0;
return CdmDecryptionSubsample(subSample.numBytesOfClearData,
subSample.numBytesOfEncryptedData);
});
if (mode == Mode::UNENCRYPTED && subSample.numBytesOfEncryptedData != 0) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"Encrypted subsamples found in allegedly unencrypted data.");
return Void();
}
sample.encrypt_buffer_length = totalSize;
sample.decrypt_buffer_size = totalSize;
// Calculate any flags that apply to this subsample's parts.
uint8_t clearFlags = 0;
uint8_t encryptedFlags = 0;
// If this is the first subsample…
if (i == 0) {
// …add OEMCrypto_FirstSubsample to the first part that is present.
if (subSample.numBytesOfClearData != 0) {
clearFlags = clearFlags | OEMCrypto_FirstSubsample;
} else {
encryptedFlags = encryptedFlags | OEMCrypto_FirstSubsample;
}
}
// If this is the last subsample…
if (i == subSamples.size() - 1) {
// …add OEMCrypto_LastSubsample to the last part that is present
if (subSample.numBytesOfEncryptedData != 0) {
encryptedFlags = encryptedFlags | OEMCrypto_LastSubsample;
} else {
clearFlags = clearFlags | OEMCrypto_LastSubsample;
}
}
// "Decrypt" any unencrypted data. Per the ISO-CENC standard, clear data
// comes before encrypted data.
if (subSample.numBytesOfClearData != 0) {
params.is_encrypted = false;
params.encrypt_buffer = srcPtr + bufferOffset;
params.encrypt_length = subSample.numBytesOfClearData;
params.block_offset = 0;
params.decrypt_buffer_offset = bufferOffset;
params.subsample_flags = clearFlags;
Status_V1_2 res = attemptDecrypt(params, haveEncryptedSubsamples,
&errorDetailMsg);
if (res != Status_V1_2::OK) {
_hidl_cb(res, 0, errorDetailMsg.c_str());
return Void();
}
bufferOffset += subSample.numBytesOfClearData;
}
// Decrypt any encrypted data. Per the ISO-CENC standard, encrypted data
// comes after clear data.
if (subSample.numBytesOfEncryptedData != 0) {
params.is_encrypted = true;
params.encrypt_buffer = srcPtr + bufferOffset;
params.encrypt_length = subSample.numBytesOfEncryptedData;
params.block_offset = blockOffset;
params.decrypt_buffer_offset = bufferOffset;
params.subsample_flags = encryptedFlags;
Status_V1_2 res = attemptDecrypt(params, haveEncryptedSubsamples,
&errorDetailMsg);
if (res != Status_V1_2::OK) {
_hidl_cb(res, 0, errorDetailMsg.c_str());
return Void();
}
bufferOffset += subSample.numBytesOfEncryptedData;
// 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 == Mode::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.numBytesOfEncryptedData) /
kAESBlockSize;
} else {
// The truncation in the integer divisions in this block is
// intentional.
const uint64_t numBlocks =
subSample.numBytesOfEncryptedData / kAESBlockSize;
const uint64_t patternLengthInBlocks =
pattern.encryptBlocks + pattern.skipBlocks;
const uint64_t numFullPatternRepetitions =
numBlocks / patternLengthInBlocks;
const uint64_t numDanglingBlocks =
numBlocks % patternLengthInBlocks;
const uint64_t numDanglingEncryptedBlocks =
std::min(static_cast<uint64_t>(pattern.encryptBlocks),
numDanglingBlocks);
increment =
numFullPatternRepetitions * pattern.encryptBlocks +
numDanglingEncryptedBlocks;
}
incrementIV(increment, &ivVector);
// Update the block offset
blockOffset = (blockOffset + subSample.numBytesOfEncryptedData) %
kAESBlockSize;
} else if (mode == Mode::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 = srcPtr + bufferOffset +
subSample.numBytesOfEncryptedData;
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.
}
if (mode == Mode::UNENCRYPTED && hasProtectedData) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"Protected ranges found in allegedly clear data.");
return Void();
}
_hidl_cb(Status_V1_2::OK, bufferOffset, errorDetailMsg.c_str());
// Decrypt
Status_V1_2 res = attemptDecrypt(params, hasProtectedData, &errorDetailMsg);
if (res != Status_V1_2::OK) {
_hidl_cb(res, 0, errorDetailMsg.c_str());
return Void();
}
_hidl_cb(Status_V1_2::OK, totalSize, errorDetailMsg.c_str());
return Void();
}
Status_V1_2 WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params,
bool haveEncryptedSubsamples, std::string* errorDetailMsg) {
CdmResponseType res = mCDM->Decrypt(mSessionId, haveEncryptedSubsamples,
params);
Status_V1_2 WVCryptoPlugin::attemptDecrypt(
const CdmDecryptionParametersV16& params, bool hasProtectedData,
std::string* errorDetailMsg) {
CdmResponseType res = mCDM->DecryptV16(mSessionId, hasProtectedData,
params);
if (isCdmResponseTypeSuccess(res)) {
return Status_V1_2::OK;
} else {
ALOGE("Decrypt error result in session %s during %s block: %d",
ALOGE("Decrypt error in session %s during a sample %s protected data: %d",
mSessionId.c_str(),
params.is_encrypted ? "encrypted" : "unencrypted",
hasProtectedData ? "with" : "without",
res);
bool actionableError = true;
switch (res) {
@@ -439,13 +349,6 @@ Status_V1_2 WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params
}
}
void WVCryptoPlugin::incrementIV(uint64_t increaseBy,
std::vector<uint8_t>* ivPtr) {
std::vector<uint8_t>& iv = *ivPtr;
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);
(*counterBuffer) = htonq(ntohq(*counterBuffer) + increaseBy);
}
} // namespace widevine
} // namespace V1_2
} // namespace drm

View File

@@ -45,16 +45,20 @@ using ::testing::Matcher;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
using ::testing::Test;
using ::testing::Truly;
using ::testing::Value;
using ::testing::internal::ElementsAreArrayMatcher;
using wvcdm::kCipherModeCtr;
using wvcdm::CdmCencPatternEncryptionDescriptor;
using wvcdm::kCipherModeCbc;
using wvcdm::CdmCipherMode;
using wvcdm::CdmDecryptionParameters;
using wvcdm::CdmDecryptionParametersV16;
using wvcdm::CdmDecryptionSample;
using wvcdm::CdmDecryptionSubsample;
using wvcdm::CdmQueryMap;
using wvcdm::CdmResponseType;
using wvcdm::CdmSessionId;
using wvcdm::KeyId;
using wvcdm::KEY_ID_SIZE;
using wvcdm::KEY_IV_SIZE;
using wvcdm::QUERY_KEY_SECURITY_LEVEL;
@@ -65,8 +69,8 @@ class MockCDM : public wvcdm::WvContentDecryptionModule {
public:
MOCK_METHOD1(IsOpenSession, bool(const CdmSessionId&));
MOCK_METHOD3(Decrypt, CdmResponseType(const CdmSessionId&, bool,
const CdmDecryptionParameters&));
MOCK_METHOD3(DecryptV16, CdmResponseType(const CdmSessionId&, bool,
const CdmDecryptionParametersV16&));
MOCK_METHOD2(QuerySessionStatus, CdmResponseType(const CdmSessionId&,
CdmQueryMap*));
@@ -161,107 +165,95 @@ TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
"WVCryptoPlugin incorrectly expects a secure audio decoder";
}
// Factory for matchers that perform deep matching of values against a
// CdmDecryptionParameters struct. For use in the test AttemptsToDecrypt.
class CDPMatcherFactory {
public:
// Some values do not change over the course of the test. To avoid having
// to re-specify them at every call site, we pass them into the factory
// constructor.
CDPMatcherFactory(bool isSecure, CdmCipherMode cipherMode, uint8_t* keyId,
void* out, size_t outLen, bool isVideo)
: mIsSecure(isSecure), mCipherMode(cipherMode), mKeyId(keyId),
mOut(out), mOutLen(outLen), mIsVideo(isVideo) {}
// TODO(b/28295739):
// Add New MediaCrypto Unit Tests for CBC & Pattern Mode in cdmPatternDesc.
testing::Matcher<const CdmDecryptionParameters&> operator()(
bool isEncrypted,
uint8_t* in,
size_t inLen,
uint8_t* iv,
size_t blockOffset,
size_t outOffset,
uint8_t flags,
CdmCencPatternEncryptionDescriptor& cdmPatternDesc) const {
// TODO b/28295739
// Add New MediaCrypto Unit Tests for CBC & Pattern Mode
// in cdmPatternDesc.
return testing::Truly(CDPMatcher(mIsSecure, mCipherMode, mKeyId, mOut,
mOutLen, isEncrypted, in, inLen, iv,
blockOffset, outOffset, flags, mIsVideo,
cdmPatternDesc));
// Predicate that validates that the fields of a passed-in
// CdmDecryptionParametersV16 match the values it was given at construction
// time.
//
// This could be done with a huge pile of gMock matchers, but it is ugly and
// unmaintainable, particularly once you get into validating the subsamples. The
// logic here is complex enough to warrant a custom matcher for this one test.
class CDPMatcher {
public:
// TODO(b/35259313): Uncomment the removed parameters once the matcher can
// convert them from HIDL accesses to physical addresses.
CDPMatcher(const uint8_t* keyId, bool isSecure, Mode cipherMode,
const Pattern& pattern,
const uint8_t* /* input */, size_t inputLength,
const uint8_t* /* output */, size_t outputLength, const uint8_t* iv,
const SubSample* subsamples, size_t subsamplesLength)
: mKeyId(keyId, keyId + KEY_ID_SIZE), mIsSecure(isSecure),
mCipherMode(cipherMode), mPattern(pattern), /* mInput(input), */
mInputLength(inputLength), /* mOutput(output), */
mOutputLength(outputLength), mIv(iv, iv + KEY_IV_SIZE),
mSubsamples(subsamples, subsamples + subsamplesLength) {}
bool operator()(const CdmDecryptionParametersV16& params) const {
if (mCipherMode == Mode::AES_CTR &&
params.cipher_mode != kCipherModeCtr) {
return false;
} else if (mCipherMode == Mode::AES_CBC &&
params.cipher_mode != kCipherModeCbc) {
return false;
}
private:
// Predicate that validates that the fields of a passed-in
// CdmDecryptionParameters match the values it was given at construction
// time.
class CDPMatcher {
public:
// TODO b/35259313: Uncomment out parameters when addressed
CDPMatcher(bool isSecure, CdmCipherMode cipherMode, uint8_t* keyId,
void* /* out */, size_t outLen, bool isEncrypted,
uint8_t* /* in */, size_t inLen, uint8_t* iv,
size_t blockOffset, size_t outOffset, uint8_t flags,
bool isVideo,
CdmCencPatternEncryptionDescriptor& cdmPatternDesc)
: mIsSecure(isSecure), mCipherMode(cipherMode), mKeyId(keyId),
/* mOut(out), */ mOutLen(outLen), mIsEncrypted(isEncrypted),
/* mIn(in), */ mInLen(inLen), mIv(iv), mBlockOffset(blockOffset),
mOutOffset(outOffset), mFlags(flags), mIsVideo(isVideo),
mCdmPatternDesc(cdmPatternDesc) {}
if (params.key_id != mKeyId ||
params.is_secure != mIsSecure ||
params.pattern.encrypt_blocks != mPattern.encryptBlocks ||
params.pattern.skip_blocks != mPattern.skipBlocks ||
params.samples.size() != 1) {
return false;
}
bool operator()(const CdmDecryptionParameters& params) const {
return params.is_secure == mIsSecure &&
params.cipher_mode == mCipherMode &&
Value(*params.key_id, ElementsAreArray(mKeyId, KEY_ID_SIZE)) &&
// TODO b/35259313
// Converts mOut from hidl address to physical address
// params.decrypt_buffer == mOut &&
params.decrypt_buffer_length == mOutLen &&
params.is_encrypted == mIsEncrypted &&
// TODO b/35259313
// Converts mIn from hidl address to physical address
// params.encrypt_buffer == mIn &&
params.encrypt_length == mInLen &&
Value(*params.iv, ElementsAreArray(mIv, KEY_IV_SIZE)) &&
params.block_offset == mBlockOffset &&
params.decrypt_buffer_offset == mOutOffset &&
params.subsample_flags == mFlags &&
params.is_video == mIsVideo;
}
const CdmDecryptionSample& sample = params.samples[0];
if (// TODO(b/35259313): Convert from a HIDL access to a physical address.
// sample.encrypt_buffer != mInput ||
sample.encrypt_buffer_length != mInputLength ||
// TODO(b/35259313): Convert from a HIDL access to a physical address.
// sample.decrypt_buffer != mOutput ||
sample.decrypt_buffer_size != mOutputLength ||
sample.decrypt_buffer_offset != 0 ||
sample.iv != mIv ||
sample.subsamples.size() != mSubsamples.size()) {
return false;
}
private:
bool mIsSecure;
CdmCipherMode mCipherMode;
uint8_t* mKeyId;
// TODO b/35259313
//void* mOut;
size_t mOutLen;
bool mIsEncrypted;
// TODO b/35259313
//uint8_t* mIn;
size_t mInLen;
uint8_t* mIv;
size_t mBlockOffset;
size_t mOutOffset;
uint8_t mFlags;
bool mIsVideo;
CdmCencPatternEncryptionDescriptor mCdmPatternDesc;
};
for (size_t i = 0; i < mSubsamples.size(); ++i) {
const SubSample& androidSubsample = mSubsamples[i];
const CdmDecryptionSubsample& cdmSubsample = sample.subsamples[i];
bool mIsSecure;
CdmCipherMode mCipherMode;
uint8_t* mKeyId;
void* mOut;
size_t mOutLen;
bool mIsVideo;
if (cdmSubsample.clear_bytes != androidSubsample.numBytesOfClearData ||
cdmSubsample.protected_bytes != androidSubsample.numBytesOfEncryptedData) {
return false;
}
}
return true;
}
private:
const KeyId mKeyId;
const bool mIsSecure;
const Mode mCipherMode;
const Pattern mPattern;
// TODO(b/35259313): Uncomment this field once the matcher can convert this
// from a HIDL access to a physical address.
// const uint8_t* const mInput;
const size_t mInputLength;
// TODO(b/35259313): Uncomment this field once the matcher can convert this
// from a HIDL access to a physical address.
// const uint8_t* const mOutput;
const size_t mOutputLength;
const std::vector<uint8_t> mIv;
const std::vector<SubSample> mSubsamples;
};
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
static const size_t kSubSampleCount = 6;
constexpr size_t kSubSampleCount = 6;
SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].numBytesOfEncryptedData = 16;
@@ -277,16 +269,16 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0]));
auto hSubSamples = hidl_vec<SubSample>(subSamplesVector);
uint8_t baseIv[KEY_IV_SIZE];
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
static const size_t kDataSize = 185;
uint8_t in[kDataSize];
uint8_t inputData[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(baseIv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(in, sizeof(uint8_t), kDataSize, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(inputData, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
sp<MemoryDealer> memDealer = new MemoryDealer(
@@ -296,7 +288,7 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
pSrc = static_cast<uint8_t*>(
static_cast<void*>(source->unsecurePointer()));
ASSERT_NE(pSrc, nullptr);
memcpy(pSrc, in, source->size());
memcpy(pSrc, inputData, source->size());
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
ASSERT_NE(destination, nullptr);
@@ -304,84 +296,21 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
static_cast<void*>(destination->unsecurePointer()));
ASSERT_NE(pDest, nullptr);
uint8_t iv[5][KEY_IV_SIZE];
memcpy(iv[0], baseIv, sizeof(baseIv));
iv[0][15] = 0;
memcpy(iv[1], baseIv, sizeof(baseIv));
iv[1][15] = 1;
memcpy(iv[2], baseIv, sizeof(baseIv));
iv[2][15] = 2;
memcpy(iv[3], baseIv, sizeof(baseIv));
iv[3][15] = 4;
memcpy(iv[4], baseIv, sizeof(baseIv));
iv[4][15] = 7;
CdmCencPatternEncryptionDescriptor cdmPattern;
CDPMatcherFactory ParamsAre =
CDPMatcherFactory(false, kCipherModeCtr, keyId, pDest, kDataSize, true);
Pattern noPattern = { 0, 0 };
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(testing::Return(true));
// Specify the expected calls to Decrypt
{
InSequence calls;
CDPMatcher paramsMatcher(keyId, false, Mode::AES_CTR, noPattern, pSrc,
kDataSize, pDest, kDataSize, iv, subSamples,
kSubSampleCount);
// SubSample 0
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, pSrc, 16, iv[0], 0, 0,
OEMCrypto_FirstSubsample, cdmPattern)))
.Times(1);
// SubSample 1
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(false, pSrc + 16, 16, iv[1], 0, 16, 0,
cdmPattern)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, pSrc + 32, 16, iv[1], 0, 32, 0,
cdmPattern)))
.Times(1);
// SubSample 2
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, pSrc + 48, 8, iv[2], 0, 48, 0,
cdmPattern)))
.Times(1);
// SubSample 3
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(false, pSrc + 56, 29, iv[2], 0, 56, 0,
cdmPattern)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, pSrc + 85, 24, iv[2], 8, 85, 0,
cdmPattern)))
.Times(1);
// SubSample 4
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, pSrc + 109, 60, iv[3], 0, 109, 0,
cdmPattern)))
.Times(1);
// SubSample 5
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, pSrc + 169, 16, iv[4], 12, 169,
OEMCrypto_LastSubsample, cdmPattern)))
.Times(1);
}
EXPECT_CALL(*cdm, DecryptV16(ElementsAreArray(sessionId, kSessionIdSize),
true,
Truly(paramsMatcher)))
.Times(1);
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
@@ -390,13 +319,12 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
DestinationBuffer hDestination;
hDestination.type = BufferType::SHARED_MEMORY;
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
Pattern noPattern = { 0, 0 };
SharedBuffer sourceBuffer;
toSharedBuffer(plugin, source, &sourceBuffer);
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv[0]),
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
@@ -411,6 +339,87 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
constexpr size_t kSubSampleCount = 2;
SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].numBytesOfEncryptedData = 16;
subSamples[1].numBytesOfClearData = 16;
subSamples[1].numBytesOfEncryptedData = 16;
std::vector<SubSample> subSamplesVector(
subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0]));
auto hSubSamples = hidl_vec<SubSample>(subSamplesVector);
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
static const size_t kDataSize = 48;
uint8_t inputData[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(inputData, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
sp<MemoryDealer> memDealer = new MemoryDealer(
kDataSize * 2, "WVCryptoPlugin_test");
sp<android::IMemory> source = memDealer->allocate(kDataSize);
ASSERT_NE(source, nullptr);
pSrc = static_cast<uint8_t*>(
static_cast<void*>(source->unsecurePointer()));
ASSERT_NE(pSrc, nullptr);
memcpy(pSrc, inputData, source->size());
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
ASSERT_NE(destination, nullptr);
pDest = static_cast<uint8_t*>(
static_cast<void*>(destination->unsecurePointer()));
ASSERT_NE(pDest, nullptr);
Pattern noPattern = { 0, 0 };
Pattern recommendedPattern = { 1, 9 };
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(testing::Return(true));
// Refuse calls to Decrypt
EXPECT_CALL(*cdm, DecryptV16(_, _, _))
.Times(0);
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
DestinationBuffer hDestination;
hDestination.type = BufferType::SHARED_MEMORY;
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
SharedBuffer sourceBuffer;
toSharedBuffer(plugin, source, &sourceBuffer);
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, recommendedPattern, hSubSamples, sourceBuffer, 0,
hDestination,
[&](Status status, uint32_t bytesWritten,
hidl_string /* errorDetailMessage */) {
EXPECT_EQ(status, Status::BAD_VALUE);
EXPECT_EQ(bytesWritten, 0);
});
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CBC, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t bytesWritten,
hidl_string /* errorDetailMessage */) {
EXPECT_EQ(status, Status::BAD_VALUE);
EXPECT_EQ(bytesWritten, 0);
});
}
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
@@ -441,13 +450,13 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
{
InSequence calls;
typedef CdmDecryptionParameters CDP;
typedef CdmDecryptionParametersV16 CDP;
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::is_secure, false)))
.Times(2);
EXPECT_CALL(*cdm, DecryptV16(_, _, Field(&CDP::is_secure, false)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::is_secure, true)))
.Times(2);
EXPECT_CALL(*cdm, DecryptV16(_, _, Field(&CDP::is_secure, true)))
.Times(1);
}
sp<MemoryDealer> memDealer = new MemoryDealer(
@@ -504,134 +513,6 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) {
android::sp<MockCDM> cdm = new MockCDM();
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
static const size_t kDataSize = 16;
uint8_t in[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
SubSample clearSubSample;
clearSubSample.numBytesOfClearData = 16;
clearSubSample.numBytesOfEncryptedData = 0;
std::vector<SubSample> clearSubSampleVector;
clearSubSampleVector.push_back(clearSubSample);
auto hClearSubSamples = hidl_vec<SubSample>(clearSubSampleVector);
SubSample encryptedSubSample;
encryptedSubSample.numBytesOfClearData = 0;
encryptedSubSample.numBytesOfEncryptedData = 16;
std::vector<SubSample> encryptedSubSampleVector;
encryptedSubSampleVector.push_back(encryptedSubSample);
auto hEncryptedSubSamples = hidl_vec<SubSample>(encryptedSubSampleVector);
SubSample mixedSubSample;
mixedSubSample.numBytesOfClearData = 8;
mixedSubSample.numBytesOfEncryptedData = 8;
std::vector<SubSample> mixedSubSampleVector;
mixedSubSampleVector.push_back(mixedSubSample);
auto hMixedSubSamples = hidl_vec<SubSample>(mixedSubSampleVector);
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(testing::Return(true));
// Specify the expected calls to Decrypt
{
InSequence calls;
typedef CdmDecryptionParameters CDP;
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_FirstSubsample |
OEMCrypto_LastSubsample)))
.Times(2);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_FirstSubsample)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_LastSubsample)))
.Times(1);
}
sp<MemoryDealer> memDealer = new MemoryDealer(
kDataSize * 2, "WVCryptoPlugin_test");
sp<android::IMemory> source = memDealer->allocate(kDataSize);
ASSERT_NE(source, nullptr);
pSrc = static_cast<uint8_t*>(
static_cast<void*>(source->unsecurePointer()));
ASSERT_NE(pSrc, nullptr);
memcpy(pSrc, in, source->size());
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
ASSERT_NE(destination, nullptr);
pDest = static_cast<uint8_t*>(
static_cast<void*>(destination->unsecurePointer()));
ASSERT_NE(pDest, nullptr);
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
uint32_t bytesWritten = 0;
std::string errorDetailMessage;
DestinationBuffer hDestination;
hDestination.type = BufferType::SHARED_MEMORY;
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
Pattern noPattern = { 0, 0 };
SharedBuffer sourceBuffer;
toSharedBuffer(plugin, source, &sourceBuffer);
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hClearSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hEncryptedSubSamples, sourceBuffer, 0,
hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hMixedSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
@@ -669,12 +550,12 @@ TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
InSequence calls;
EXPECT_CALL(*cdm,
Decrypt(ElementsAreArray(sessionId, kSessionIdSize), _, _))
.Times(2);
DecryptV16(ElementsAreArray(sessionId, kSessionIdSize), _, _))
.Times(1);
EXPECT_CALL(*cdm,
Decrypt(ElementsAreArray(sessionId2, kSessionIdSize), _, _))
.Times(2);
DecryptV16(ElementsAreArray(sessionId2, kSessionIdSize), _, _))
.Times(1);
}
sp<MemoryDealer> memDealer = new MemoryDealer(

View File

@@ -26,12 +26,16 @@ using namespace testing;
using namespace wvcdm;
using namespace wvdrm;
namespace {
constexpr ssize_t kErrorUnsupportedCrypto = ERROR_DRM_VENDOR_MIN + 2;
}
class MockCDM : public WvContentDecryptionModule {
public:
MOCK_METHOD1(IsOpenSession, bool(const CdmSessionId&));
MOCK_METHOD3(Decrypt, CdmResponseType(const CdmSessionId&, bool,
const CdmDecryptionParameters&));
MOCK_METHOD3(DecryptV16, CdmResponseType(const CdmSessionId&, bool,
const CdmDecryptionParametersV16&));
MOCK_METHOD2(QuerySessionStatus, CdmResponseType(const CdmSessionId&,
CdmQueryMap*));
@@ -81,99 +85,88 @@ TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
"WVCryptoPlugin incorrectly expects a secure audio decoder";
}
// Factory for matchers that perform deep matching of values against a
// CdmDecryptionParameters struct. For use in the test AttemptsToDecrypt.
class CDPMatcherFactory {
public:
// Some values do not change over the course of the test. To avoid having
// to re-specify them at every call site, we pass them into the factory
// constructor.
CDPMatcherFactory(bool isSecure, CdmCipherMode cipherMode, uint8_t* keyId,
void* out, size_t outLen)
: mIsSecure(isSecure), mCipherMode(cipherMode), mKeyId(keyId),
mOut(out), mOutLen(outLen) {}
// TODO(b/28295739):
// Add New MediaCrypto Unit Tests for CBC & Pattern Mode in cdmPatternDesc.
Matcher<const CdmDecryptionParameters&> operator()(bool isEncrypted,
uint8_t* in,
size_t inLen,
uint8_t* iv,
size_t blockOffset,
size_t outOffset,
uint8_t flags) const {
return Truly(CDPMatcher(mIsSecure, mCipherMode, mKeyId, mOut, mOutLen,
isEncrypted, in, inLen, iv, blockOffset,
outOffset, flags));
// Predicate that validates that the fields of a passed-in
// CdmDecryptionParametersV16 match the values it was given at construction
// time.
//
// This could be done with a huge pile of gMock matchers, but it is ugly and
// unmaintainable, particularly once you get into validating the subsamples. The
// logic here is complex enough to warrant a custom matcher for this one test.
class CDPMatcher {
public:
CDPMatcher(const uint8_t* keyId, bool isSecure, CryptoPlugin::Mode cipherMode,
const CryptoPlugin::Pattern& pattern,
const uint8_t* input, size_t inputLength,
const uint8_t* output, size_t outputLength, const uint8_t* iv,
const CryptoPlugin::SubSample* subsamples,
size_t subsamplesLength)
: mKeyId(keyId, keyId + KEY_ID_SIZE), mIsSecure(isSecure),
mCipherMode(cipherMode), mPattern(pattern), mInput(input),
mInputLength(inputLength), mOutput(output), mOutputLength(outputLength),
mIv(iv, iv + KEY_IV_SIZE),
mSubsamples(subsamples, subsamples + subsamplesLength) {}
bool operator()(const CdmDecryptionParametersV16& params) const {
if (mCipherMode == CryptoPlugin::kMode_AES_CTR &&
params.cipher_mode != kCipherModeCtr) {
return false;
} else if (mCipherMode == CryptoPlugin::kMode_AES_CBC &&
params.cipher_mode != kCipherModeCbc) {
return false;
}
private:
// Predicate that validates that the fields of a passed-in
// CdmDecryptionParameters match the values it was given at construction
// time.
class CDPMatcher {
public:
CDPMatcher(bool isSecure, CdmCipherMode cipherMode, uint8_t* keyId,
void* out, size_t outLen, bool isEncrypted, uint8_t* in,
size_t inLen, uint8_t* iv, size_t blockOffset,
size_t outOffset, uint8_t flags)
: mIsSecure(isSecure), mCipherMode(cipherMode), mKeyId(keyId),
mOut(out), mOutLen(outLen), mIsEncrypted(isEncrypted), mIn(in),
mInLen(inLen), mIv(iv), mBlockOffset(blockOffset),
mOutOffset(outOffset), mFlags(flags) {}
if (params.key_id != mKeyId ||
params.is_secure != mIsSecure ||
params.pattern.encrypt_blocks != mPattern.mEncryptBlocks ||
params.pattern.skip_blocks != mPattern.mSkipBlocks ||
params.samples.size() != 1) {
return false;
}
bool operator()(const CdmDecryptionParameters& params) const {
return params.is_secure == mIsSecure &&
params.cipher_mode == mCipherMode &&
Value(*params.key_id, ElementsAreArray(mKeyId, KEY_ID_SIZE)) &&
params.decrypt_buffer == mOut &&
params.decrypt_buffer_length == mOutLen &&
params.is_encrypted == mIsEncrypted &&
params.encrypt_buffer == mIn &&
params.encrypt_length == mInLen &&
Value(*params.iv, ElementsAreArray(mIv, KEY_IV_SIZE)) &&
params.block_offset == mBlockOffset &&
params.decrypt_buffer_offset == mOutOffset &&
params.subsample_flags == mFlags;
}
const CdmDecryptionSample& sample = params.samples[0];
if (sample.encrypt_buffer != mInput ||
sample.encrypt_buffer_length != mInputLength ||
sample.decrypt_buffer != mOutput ||
sample.decrypt_buffer_size != mOutputLength ||
sample.decrypt_buffer_offset != 0 ||
sample.iv != mIv ||
sample.subsamples.size() != mSubsamples.size()) {
return false;
}
private:
bool mIsSecure;
CdmCipherMode mCipherMode;
uint8_t* mKeyId;
void* mOut;
size_t mOutLen;
bool mIsEncrypted;
uint8_t* mIn;
size_t mInLen;
uint8_t* mIv;
size_t mBlockOffset;
size_t mOutOffset;
uint8_t mFlags;
};
for (size_t i = 0; i < mSubsamples.size(); ++i) {
const CryptoPlugin::SubSample& androidSubsample = mSubsamples[i];
const CdmDecryptionSubsample& cdmSubsample = sample.subsamples[i];
bool mIsSecure;
CdmCipherMode mCipherMode;
uint8_t* mKeyId;
void* mOut;
size_t mOutLen;
if (cdmSubsample.clear_bytes != androidSubsample.mNumBytesOfClearData||
cdmSubsample.protected_bytes != androidSubsample.mNumBytesOfEncryptedData) {
return false;
}
}
return true;
}
private:
const KeyId mKeyId;
const bool mIsSecure;
const CryptoPlugin::Mode mCipherMode;
const CryptoPlugin::Pattern mPattern;
const uint8_t* const mInput;
const size_t mInputLength;
const uint8_t* const mOutput;
const size_t mOutputLength;
const std::vector<uint8_t> mIv;
const std::vector<CryptoPlugin::SubSample> mSubsamples;
};
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
uint8_t keyId[KEY_ID_SIZE];
uint8_t baseIv[KEY_IV_SIZE];
static const size_t kDataSize = 185;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(baseIv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const size_t kSubSampleCount = 6;
constexpr size_t kSubSampleCount = 6;
CryptoPlugin::SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].mNumBytesOfEncryptedData = 16;
@@ -185,85 +178,42 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
subSamples[4].mNumBytesOfEncryptedData = 60;
subSamples[5].mNumBytesOfEncryptedData = 16;
uint8_t iv[5][KEY_IV_SIZE];
memcpy(iv[0], baseIv, sizeof(baseIv));
iv[0][15] = 0;
memcpy(iv[1], baseIv, sizeof(baseIv));
iv[1][15] = 1;
memcpy(iv[2], baseIv, sizeof(baseIv));
iv[2][15] = 2;
memcpy(iv[3], baseIv, sizeof(baseIv));
iv[3][15] = 4;
memcpy(iv[4], baseIv, sizeof(baseIv));
iv[4][15] = 7;
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
CDPMatcherFactory ParamsAre =
CDPMatcherFactory(false, kCipherModeCtr, keyId, out, kDataSize);
constexpr size_t kDataSize = 185;
uint8_t inputData[kDataSize];
uint8_t outputData[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(inputData, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
android::CryptoPlugin::Pattern noPattern = { 0, 0 };
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
// Specify the expected calls to Decrypt
{
InSequence calls;
CDPMatcher paramsMatcher(keyId, false, CryptoPlugin::kMode_AES_CTR, noPattern,
inputData, kDataSize, outputData, kDataSize, iv,
subSamples, kSubSampleCount);
// SubSample 0
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in, 16, iv[0], 0, 0,
OEMCrypto_FirstSubsample)))
.Times(1);
// SubSample 1
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(false, in + 16, 16, iv[1], 0, 16, 0)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 32, 16, iv[1], 0, 32, 0)))
.Times(1);
// SubSample 2
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 48, 8, iv[2], 0, 48, 0)))
.Times(1);
// SubSample 3
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(false, in + 56, 29, iv[2], 0, 56, 0)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 85, 24, iv[2], 8, 85, 0)))
.Times(1);
// SubSample 4
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 109, 60, iv[3], 0, 109, 0)))
.Times(1);
// SubSample 5
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 169, 16, iv[4], 12, 169,
OEMCrypto_LastSubsample)))
.Times(1);
}
EXPECT_CALL(*cdm, DecryptV16(ElementsAreArray(sessionId, kSessionIdSize),
true,
Truly(paramsMatcher)))
.Times(1);
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
android::CryptoPlugin::Pattern noPattern = { 0, 0 };
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv[0], CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount,
out, &errorDetailMessage);
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, inputData, subSamples,
kSubSampleCount, outputData,
&errorDetailMessage);
EXPECT_EQ(static_cast<ssize_t>(kDataSize), res) <<
"WVCryptoPlugin decrypted the wrong number of bytes";
@@ -271,6 +221,62 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
constexpr size_t kSubSampleCount = 2;
CryptoPlugin::SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].mNumBytesOfEncryptedData = 16;
subSamples[1].mNumBytesOfClearData = 16;
subSamples[1].mNumBytesOfEncryptedData = 16;
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
constexpr size_t kDataSize = 48;
uint8_t inputData[kDataSize];
uint8_t outputData[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(inputData, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
android::CryptoPlugin::Pattern noPattern = { 0, 0 };
android::CryptoPlugin::Pattern recommendedPattern = { 1, 9 };
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
// Refuse calls to Decrypt
EXPECT_CALL(*cdm, DecryptV16(_, _, _))
.Times(0);
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
recommendedPattern, inputData, subSamples,
kSubSampleCount, outputData,
&errorDetailMessage);
EXPECT_EQ(res, kErrorUnsupportedCrypto) <<
"WVCryptoPlugin did not return an error for 'cens'.";
EXPECT_NE(errorDetailMessage.size(), 0u) <<
"WVCryptoPlugin did not report a detailed error message for 'cens'.";
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CBC,
noPattern, inputData, subSamples, kSubSampleCount,
outputData, &errorDetailMessage);
EXPECT_EQ(res, kErrorUnsupportedCrypto) <<
"WVCryptoPlugin did not return an error for 'cbc1'.";
EXPECT_NE(errorDetailMessage.size(), 0u) <<
"WVCryptoPlugin did not report a detailed error message for 'cbc1'.";
}
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
@@ -302,13 +308,13 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
{
InSequence calls;
typedef CdmDecryptionParameters CDP;
typedef CdmDecryptionParametersV16 CDP;
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::is_secure, false)))
.Times(2);
EXPECT_CALL(*cdm, DecryptV16(_, _, Field(&CDP::is_secure, false)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::is_secure, true)))
.Times(2);
EXPECT_CALL(*cdm, DecryptV16(_, _, Field(&CDP::is_secure, true)))
.Times(1);
}
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
@@ -332,89 +338,6 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) {
android::sp<MockCDM> cdm = new MockCDM();
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
static const size_t kDataSize = 16;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const uint32_t kSubSampleCount = 1;
CryptoPlugin::SubSample clearSubSamples[kSubSampleCount];
memset(clearSubSamples, 0, sizeof(clearSubSamples));
clearSubSamples[0].mNumBytesOfClearData = 16;
CryptoPlugin::SubSample encryptedSubSamples[kSubSampleCount];
memset(encryptedSubSamples, 0, sizeof(encryptedSubSamples));
encryptedSubSamples[0].mNumBytesOfEncryptedData = 16;
CryptoPlugin::SubSample mixedSubSamples[kSubSampleCount];
memset(mixedSubSamples, 0, sizeof(mixedSubSamples));
mixedSubSamples[0].mNumBytesOfClearData = 8;
mixedSubSamples[0].mNumBytesOfEncryptedData = 8;
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
// Specify the expected calls to Decrypt
{
InSequence calls;
typedef CdmDecryptionParameters CDP;
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_FirstSubsample |
OEMCrypto_LastSubsample)))
.Times(2);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_FirstSubsample)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_LastSubsample)))
.Times(1);
}
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
android::CryptoPlugin::Pattern noPattern = { 0, 0 };
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, clearSubSamples,
kSubSampleCount, out, &errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, encryptedSubSamples, kSubSampleCount,
out, &errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, mixedSubSamples, kSubSampleCount, out,
&errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
@@ -453,12 +376,12 @@ TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
InSequence calls;
EXPECT_CALL(*cdm,
Decrypt(ElementsAreArray(sessionId, kSessionIdSize), _, _))
.Times(2);
DecryptV16(ElementsAreArray(sessionId, kSessionIdSize), _, _))
.Times(1);
EXPECT_CALL(*cdm,
Decrypt(ElementsAreArray(sessionId2, kSessionIdSize), _, _))
.Times(2);
DecryptV16(ElementsAreArray(sessionId2, kSessionIdSize), _, _))
.Times(1);
}
uint8_t blank[1]; // Some compilers will not accept 0.

View File

@@ -24,6 +24,7 @@
#include <openssl/sha.h>
#include <openssl/x509.h>
#include "advance_iv_ctr.h"
#include "disallow_copy_and_assign.h"
#include "keys.h"
#include "log.h"
@@ -66,25 +67,6 @@ void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
}
}
// Advance an IV according to ISO-CENC's CTR modes. The lower half of the IV is
// split off and treated as an unsigned 64-bit integer, then incremented by the
// number of complete crypto blocks decrypted. The resulting value is then
// copied back into the IV over the previous lower half.
void advance_iv_ctr(uint8_t (*subsample_iv)[wvoec::KEY_IV_SIZE], size_t bytes) {
uint64_t counter;
// Per its type, sizeof(*subsample_iv) == wvoec::KEY_IV_SIZE
static_assert(sizeof(counter) * 2 == wvoec::KEY_IV_SIZE,
"A uint64_t failed to be half the size of an AES-128 IV.");
constexpr size_t half_iv_size = wvoec::KEY_IV_SIZE / 2;
memcpy(&counter, &(*subsample_iv)[half_iv_size], half_iv_size);
const size_t increment =
bytes / wvoec::AES_128_BLOCK_SIZE; // The truncation here is intentional
counter = wvcdm::htonll64(wvcdm::ntohll64(counter) + increment);
memcpy(&(*subsample_iv)[half_iv_size], &counter, half_iv_size);
}
} // namespace
namespace wvoec_ref {
@@ -1604,8 +1586,8 @@ OEMCryptoResult SessionContext::DecryptSamples(
advance_dest_buffer(&subsample_dest, subsample_length);
if (subsample.num_bytes_encrypted > 0 &&
current_content_key()->ctr_mode()) {
advance_iv_ctr(&subsample_iv,
subsample.block_offset + subsample.num_bytes_encrypted);
wvcdm::AdvanceIvCtr(&subsample_iv, subsample.block_offset +
subsample.num_bytes_encrypted);
}
} // Subsample loop
} // Sample loop