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