From a62886b925bc40beb5cfc73467224fda6054c197 Mon Sep 17 00:00:00 2001 From: "John W. Bruce" Date: Tue, 18 Feb 2020 14:46:31 -0800 Subject: [PATCH] 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 --- libwvdrmengine/cdm/core/include/cdm_engine.h | 5 +- .../include/cdm_engine_metrics_decorator.h | 15 +- libwvdrmengine/cdm/core/include/cdm_session.h | 2 +- .../cdm/core/include/content_key_session.h | 8 +- .../cdm/core/include/crypto_session.h | 33 +- .../core/include/entitlement_key_session.h | 1 - libwvdrmengine/cdm/core/include/key_session.h | 6 +- .../cdm/core/include/wv_cdm_types.h | 129 +++- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 41 +- libwvdrmengine/cdm/core/src/cdm_session.cpp | 24 +- .../cdm/core/src/content_key_session.cpp | 46 +- .../cdm/core/src/crypto_session.cpp | 656 ++++++++++++------ .../cdm/core/src/entitlement_key_session.cpp | 4 +- .../cdm_engine_metrics_decorator_unittest.cpp | 12 +- .../cdm/core/test/test_printers.cpp | 12 + .../include/wv_content_decryption_module.h | 5 + .../cdm/src/wv_content_decryption_module.cpp | 34 +- .../cdm/util/include/advance_iv_ctr.h | 46 ++ libwvdrmengine/include/WVErrors.h | 6 +- libwvdrmengine/include/mapErrors-inl.h | 8 + libwvdrmengine/include_hidl/mapErrors-inl.h | 4 + .../mediacrypto/include/WVCryptoPlugin.h | 3 +- .../mediacrypto/include_hidl/WVCryptoPlugin.h | 5 +- .../mediacrypto/src/WVCryptoPlugin.cpp | 223 ++---- .../mediacrypto/src_hidl/WVCryptoPlugin.cpp | 253 +++---- .../mediacrypto/test/WVCryptoPlugin_test.cpp | 499 +++++-------- .../test/legacy_src/WVCryptoPlugin_test.cpp | 409 +++++------ .../oemcrypto/ref/src/oemcrypto_session.cpp | 24 +- 28 files changed, 1253 insertions(+), 1260 deletions(-) create mode 100644 libwvdrmengine/cdm/util/include/advance_iv_ctr.h diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 28949cc7..963fa74a 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -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 diff --git a/libwvdrmengine/cdm/core/include/cdm_engine_metrics_decorator.h b/libwvdrmengine/cdm/core/include/cdm_engine_metrics_decorator.h index 0189a75d..befbd9f6 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine_metrics_decorator.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine_metrics_decorator.h @@ -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; } diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 1211e474..8859093a 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -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 diff --git a/libwvdrmengine/cdm/core/include/content_key_session.h b/libwvdrmengine/cdm/core/include/content_key_session.h index 9961c3b3..9e784547 100644 --- a/libwvdrmengine/cdm/core/include/content_key_session.h +++ b/libwvdrmengine/cdm/core/include/content_key_session.h @@ -36,7 +36,6 @@ class ContentKeySession : public KeySession { const std::string& mac_key, const std::vector& 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& 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_; diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index ba9c4d81..f6fc2ccd 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -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* 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& 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 request_id_index_source_; - CdmCipherMode cipher_mode_; uint32_t api_version_; // In order to avoid creating a deadlock if instantiation needs to take any diff --git a/libwvdrmengine/cdm/core/include/entitlement_key_session.h b/libwvdrmengine/cdm/core/include/entitlement_key_session.h index d8fab403..358ec92d 100644 --- a/libwvdrmengine/cdm/core/include/entitlement_key_session.h +++ b/libwvdrmengine/cdm/core/include/entitlement_key_session.h @@ -30,7 +30,6 @@ class EntitlementKeySession : public ContentKeySession { const std::string& mac_key, const std::vector& keys, const std::string& provider_session_token, - CdmCipherMode* cipher_mode, const std::string& srm_requirement) override; OEMCryptoResult LoadEntitledContentKeys( const std::vector& keys) override; diff --git a/libwvdrmengine/cdm/core/include/key_session.h b/libwvdrmengine/cdm/core/include/key_session.h index 92cb140b..c823ee49 100644 --- a/libwvdrmengine/cdm/core/include/key_session.h +++ b/libwvdrmengine/cdm/core/include/key_session.h @@ -32,16 +32,14 @@ class KeySession { const std::string& mac_key, const std::vector& keys, const std::string& provider_session_token, - CdmCipherMode* cipher_mode, const std::string& srm_requirement) = 0; virtual OEMCryptoResult LoadEntitledContentKeys( const std::vector& 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_; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index a5ef69a4..a6ce404e 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -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 subsamples; + std::vector 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& 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 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; diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 98a52c16..0317f6d5 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -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 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; diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 6aebc2c0..33b066b4 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -7,6 +7,8 @@ #include #include +#include +#include #include #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; } } diff --git a/libwvdrmengine/cdm/core/src/content_key_session.cpp b/libwvdrmengine/cdm/core/src/content_key_session.cpp index 84411a9e..83339740 100644 --- a/libwvdrmengine/cdm/core/src/content_key_session.cpp +++ b/libwvdrmengine/cdm/core/src/content_key_session.cpp @@ -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& 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& 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(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); diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 4f1b13b7..24c4cc7f 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -13,6 +13,8 @@ #include #include +#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(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 oec_samples; + oec_samples.reserve(params.samples.size()); + std::vector> + 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(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& 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& 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 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* iv_out) { - std::vector& iv = *iv_out; - uint64_t* counter_buffer = reinterpret_cast(&iv[8]); - (*counter_buffer) = htonll64(ntohll64(*counter_buffer) + increase_by); + return sts; } template diff --git a/libwvdrmengine/cdm/core/src/entitlement_key_session.cpp b/libwvdrmengine/cdm/core/src/entitlement_key_session.cpp index d48e3573..baf99d72 100644 --- a/libwvdrmengine/cdm/core/src/entitlement_key_session.cpp +++ b/libwvdrmengine/cdm/core/src/entitlement_key_session.cpp @@ -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& 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( diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_metrics_decorator_unittest.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_metrics_decorator_unittest.cpp index 7d9add47..2ca27d89 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_metrics_decorator_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_metrics_decorator_unittest.cpp @@ -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); diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index ae4cc820..fb6b7fb0 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -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; diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 457c415e..d74751bd 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -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); diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 3eb0697b..713fd503 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -4,6 +4,9 @@ #include "wv_content_decryption_module.h" +#include +#include + #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, diff --git a/libwvdrmengine/cdm/util/include/advance_iv_ctr.h b/libwvdrmengine/cdm/util/include/advance_iv_ctr.h new file mode 100644 index 00000000..b7b9ff4a --- /dev/null +++ b/libwvdrmengine/cdm/util/include/advance_iv_ctr.h @@ -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 +#include + +#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_ diff --git a/libwvdrmengine/include/WVErrors.h b/libwvdrmengine/include/WVErrors.h index 62a5276a..096ab9f6 100644 --- a/libwvdrmengine/include/WVErrors.h +++ b/libwvdrmengine/include/WVErrors.h @@ -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, diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index 32a6b388..090a21f9 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -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: diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index 88ed84dd..a89dfd92 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -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; diff --git a/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h b/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h index ee035864..b9e859eb 100644 --- a/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h +++ b/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h @@ -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* ivPtr); }; } // namespace wvdrm diff --git a/libwvdrmengine/mediacrypto/include_hidl/WVCryptoPlugin.h b/libwvdrmengine/mediacrypto/include_hidl/WVCryptoPlugin.h index a7570731..1824d93a 100644 --- a/libwvdrmengine/mediacrypto/include_hidl/WVCryptoPlugin.h +++ b/libwvdrmengine/mediacrypto/include_hidl/WVCryptoPlugin.h @@ -69,10 +69,9 @@ struct WVCryptoPlugin : public ICryptoPlugin { sp 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* ivPtr); }; } // namespace widevine diff --git a/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp b/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp index bd23fb61..2fd3ecd8 100644 --- a/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp +++ b/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -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(srcPtr); uint8_t* const dest = static_cast(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(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(offset); + return static_cast(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* ivPtr) { - vector& iv = *ivPtr; - uint64_t* counterBuffer = reinterpret_cast(&iv[8]); - (*counterBuffer) = htonq(ntohq(*counterBuffer) + increaseBy); -} - } // namespace wvdrm diff --git a/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp b/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp index 239f786c..57357c98 100644 --- a/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp +++ b/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp @@ -12,6 +12,7 @@ #include #include +#include #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 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 WVCryptoPlugin::decrypt_1_2( destPtr = static_cast(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(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* ivPtr) { - std::vector& iv = *ivPtr; - uint64_t* counterBuffer = reinterpret_cast(&iv[8]); - (*counterBuffer) = htonq(ntohq(*counterBuffer) + increaseBy); -} - } // namespace widevine } // namespace V1_2 } // namespace drm diff --git a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp index 997507b4..b74627a4 100644 --- a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp +++ b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp @@ -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 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 mIv; + const std::vector mSubsamples; }; TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { android::sp> cdm = new StrictMock(); - 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(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 memDealer = new MemoryDealer( @@ -296,7 +288,7 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { pSrc = static_cast( static_cast(source->unsecurePointer())); ASSERT_NE(pSrc, nullptr); - memcpy(pSrc, in, source->size()); + memcpy(pSrc, inputData, source->size()); sp destination = memDealer->allocate(kDataSize); ASSERT_NE(destination, nullptr); @@ -304,84 +296,21 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { static_cast(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(keyId), hidl_array(iv[0]), + false, hidl_array(keyId), hidl_array(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> cdm = new StrictMock(); + + 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 subSamplesVector( + subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0])); + auto hSubSamples = hidl_vec(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 memDealer = new MemoryDealer( + kDataSize * 2, "WVCryptoPlugin_test"); + sp source = memDealer->allocate(kDataSize); + ASSERT_NE(source, nullptr); + pSrc = static_cast( + static_cast(source->unsecurePointer())); + ASSERT_NE(pSrc, nullptr); + memcpy(pSrc, inputData, source->size()); + + sp destination = memDealer->allocate(kDataSize); + ASSERT_NE(destination, nullptr); + pDest = static_cast( + static_cast(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(keyId), hidl_array(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(keyId), hidl_array(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> cdm = new StrictMock(); @@ -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 memDealer = new MemoryDealer( @@ -504,134 +513,6 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { "WVCryptoPlugin reported a detailed error message."; } -TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) { - android::sp 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 clearSubSampleVector; - clearSubSampleVector.push_back(clearSubSample); - auto hClearSubSamples = hidl_vec(clearSubSampleVector); - - SubSample encryptedSubSample; - encryptedSubSample.numBytesOfClearData = 0; - encryptedSubSample.numBytesOfEncryptedData = 16; - std::vector encryptedSubSampleVector; - encryptedSubSampleVector.push_back(encryptedSubSample); - auto hEncryptedSubSamples = hidl_vec(encryptedSubSampleVector); - - SubSample mixedSubSample; - mixedSubSample.numBytesOfClearData = 8; - mixedSubSample.numBytesOfEncryptedData = 8; - std::vector mixedSubSampleVector; - mixedSubSampleVector.push_back(mixedSubSample); - auto hMixedSubSamples = hidl_vec(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 memDealer = new MemoryDealer( - kDataSize * 2, "WVCryptoPlugin_test"); - sp source = memDealer->allocate(kDataSize); - ASSERT_NE(source, nullptr); - pSrc = static_cast( - static_cast(source->unsecurePointer())); - ASSERT_NE(pSrc, nullptr); - memcpy(pSrc, in, source->size()); - - sp destination = memDealer->allocate(kDataSize); - ASSERT_NE(destination, nullptr); - pDest = static_cast( - static_cast(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(keyId), hidl_array(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(keyId), hidl_array(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(keyId), hidl_array(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> cdm = new StrictMock(); @@ -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 memDealer = new MemoryDealer( diff --git a/libwvdrmengine/mediacrypto/test/legacy_src/WVCryptoPlugin_test.cpp b/libwvdrmengine/mediacrypto/test/legacy_src/WVCryptoPlugin_test.cpp index 9ea44f3f..04f7de34 100644 --- a/libwvdrmengine/mediacrypto/test/legacy_src/WVCryptoPlugin_test.cpp +++ b/libwvdrmengine/mediacrypto/test/legacy_src/WVCryptoPlugin_test.cpp @@ -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 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 mIv; + const std::vector mSubsamples; }; TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { android::sp> cdm = new StrictMock(); - 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(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> cdm = new StrictMock(); + + 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> cdm = new StrictMock(); @@ -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 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> cdm = new StrictMock(); @@ -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. diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp index 4e74cf6e..5b7ee644 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp @@ -24,6 +24,7 @@ #include #include +#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