From 2feec02df245b7c3713ccca80a7f6b88ab6b150d Mon Sep 17 00:00:00 2001 From: huihli Date: Wed, 21 Oct 2020 11:16:23 -0700 Subject: [PATCH] Regular sync. Changes include: 1. Fix refreshkeys when handling renewal response. 2. Change ECM start detect method. 3. Fix signing key truncation. 4. Reformat C++ code. 5. Return license_id in LICENSE_CAS_READY payload. 6. Expose OEMCrypto API version in the license request. 7. Add support for newly added widevine cas ids. 8. Store content iv and encryption mode info to entitled key. 9. Upgrade ODK library to 16.4. --- oemcrypto/odk/include/odk_structs.h | 4 +- oemcrypto/odk/src/odk.c | 37 +-- oemcrypto/ref/src/oemcrypto_key_ref.h | 13 +- oemcrypto/ref/src/oemcrypto_session.cpp | 70 ++++-- oemcrypto/test/oemcrypto_test.cpp | 11 +- plugin/include/cas_media_id.h | 2 - plugin/include/cas_status.h | 1 + plugin/include/cas_util.h | 8 +- plugin/include/crypto_key.h | 6 +- plugin/include/crypto_session.h | 31 +-- plugin/include/oemcrypto_interface.h | 14 +- plugin/include/widevine_cas_api.h | 9 +- plugin/include/widevine_media_cas_plugin.h | 13 +- plugin/src/cas_license.cpp | 57 +++-- plugin/src/crypto_session.cpp | 41 +++- plugin/src/ecm_parser.cpp | 92 ++++--- plugin/src/license_key_status.cpp | 8 +- plugin/src/oemcrypto_interface.cpp | 6 + plugin/src/policy_engine.cpp | 5 +- plugin/src/widevine_cas_api.cpp | 125 ++++------ plugin/src/widevine_media_cas.cpp | 21 +- plugin/src/widevine_media_cas_plugin.cpp | 55 ++--- protos/ca_descriptor.proto | 3 - protos/device_files.proto | 6 +- protos/license_protocol.proto | 20 +- tests/src/cas_license_test.cpp | 10 +- tests/src/cas_session_map_test.cpp | 1 + tests/src/crypto_session_test.cpp | 35 ++- tests/src/ecm_parser_test.cpp | 271 ++++++++++++--------- tests/src/license_key_status_test.cpp | 5 +- tests/src/mediacas_integration_test.cpp | 60 ++--- tests/src/mock_crypto_session.h | 1 + tests/src/policy_engine_test.cpp | 6 +- tests/src/timer_test.cpp | 3 +- tests/src/widevine_cas_api_test.cpp | 171 ++++++++----- tests/src/widevine_cas_session_test.cpp | 14 +- tests/src/wv_cas_test_main.cpp | 1 + wvutil/include/timer.h | 6 +- wvutil/src/android_properties.cpp | 7 +- 39 files changed, 703 insertions(+), 546 deletions(-) diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h index 5c9ff30..4cc0b6f 100644 --- a/oemcrypto/odk/include/odk_structs.h +++ b/oemcrypto/odk/include/odk_structs.h @@ -12,10 +12,10 @@ /* The version of this library. */ #define ODK_MAJOR_VERSION 16 -#define ODK_MINOR_VERSION 3 +#define ODK_MINOR_VERSION 4 /* ODK Version string. Date changed automatically on each release. */ -#define ODK_RELEASE_DATE "ODK v16.3 2020-06-02" +#define ODK_RELEASE_DATE "ODK v16.4 2020-10-07" /* The lowest version number for an ODK message. */ #define ODK_FIRST_VERSION 16 diff --git a/oemcrypto/odk/src/odk.c b/oemcrypto/odk/src/odk.c index 42e82d5..d9cb12d 100644 --- a/oemcrypto/odk/src/odk.c +++ b/oemcrypto/odk/src/odk.c @@ -299,20 +299,24 @@ OEMCryptoResult ODK_ParseLicense( return ODK_ERROR_CORE_MESSAGE; } - if (parsed_license->nonce_required) { - if (initial_license_load) { - if (nonce_values->nonce != - license_response.request.core_message.nonce_values.nonce || - nonce_values->session_id != - license_response.request.core_message.nonce_values.session_id) { - return OEMCrypto_ERROR_INVALID_NONCE; - } - } else { /* !initial_license_load */ - nonce_values->nonce = - license_response.request.core_message.nonce_values.nonce; - nonce_values->session_id = - license_response.request.core_message.nonce_values.session_id; +/* If this is the first time we load this license, then we verify that the + * nonce values are the correct, otherwise we copy the nonce values. If the + * nonce values are not required to be correct, then we don't know if this is + * an initial load or not. In that case, we also copy the values so that we + * can use the nonce values later for a renewal. + */ + if (parsed_license->nonce_required && initial_license_load) { + if (nonce_values->nonce != + license_response.request.core_message.nonce_values.nonce || + nonce_values->session_id != + license_response.request.core_message.nonce_values.session_id) { + return OEMCrypto_ERROR_INVALID_NONCE; } + } else { /* !initial_license_load, or can't tell if initial. */ + nonce_values->nonce = + license_response.request.core_message.nonce_values.nonce; + nonce_values->session_id = + license_response.request.core_message.nonce_values.session_id; } /* For v16, in order to be backwards compatible with a v15 license server, * OEMCrypto stores a hash of the core license request and only signs the @@ -359,9 +363,12 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, */ /* If a renewal request is lost in transit, we should throw it out and create * a new one. We use the timestamp to make sure we have the latest request. + * We only do this if playback has already started. This allows us to reload + * an offline license and also reload a renewal before starting playback. */ - if (clock_values->time_of_renewal_request < - renewal_response.request.playback_time) { + if (clock_values->timer_status != ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED && + clock_values->time_of_renewal_request < + renewal_response.request.playback_time) { return ODK_STALE_RENEWAL; } return ODK_ComputeRenewalDuration(timer_limits, clock_values, system_time, diff --git a/oemcrypto/ref/src/oemcrypto_key_ref.h b/oemcrypto/ref/src/oemcrypto_key_ref.h index c2fc17e..67b1da3 100644 --- a/oemcrypto/ref/src/oemcrypto_key_ref.h +++ b/oemcrypto/ref/src/oemcrypto_key_ref.h @@ -13,6 +13,8 @@ #include #include +#include "OEMCryptoCAS.h" + namespace wvoec_ref { class KeyControlBlock { @@ -66,18 +68,21 @@ class Key { class EntitledKey { public: explicit EntitledKey(std::vector key_string) - : value_(std::move(key_string)), ctr_mode_(true){}; + : value_(std::move(key_string)), cipher_mode_(OEMCrypto_CipherMode_CTR){}; EntitledKey(const EntitledKey&) = default; EntitledKey(EntitledKey&&) = default; ~EntitledKey() = default; const std::vector& value() const { return value_; } - bool ctr_mode() const { return ctr_mode_; } - void set_ctr_mode(bool ctr_mode) { ctr_mode_ = ctr_mode; } + OEMCryptoCipherMode cipher_mode() const { return cipher_mode_; } + void set_cipher_mode(OEMCryptoCipherMode mode) { cipher_mode_ = mode; } + void set_content_iv(const std::vector& iv) { content_iv_ = iv; } + const std::vector& content_iv() const { return content_iv_; } private: std::vector value_; - bool ctr_mode_; + OEMCryptoCipherMode cipher_mode_; + std::vector content_iv_; }; } // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_session.cpp b/oemcrypto/ref/src/oemcrypto_session.cpp index a46d56f..084bd88 100644 --- a/oemcrypto/ref/src/oemcrypto_session.cpp +++ b/oemcrypto/ref/src/oemcrypto_session.cpp @@ -7,11 +7,6 @@ #include "oemcrypto_session.h" #include -#include -#include -#include -#include - #include #include #include @@ -23,6 +18,11 @@ #include #include #include +#include + +#include +#include +#include #include "advance_iv_ctr.h" #include "keys.h" @@ -97,8 +97,7 @@ SessionContext::SessionContext(CryptoEngine* ce, SessionId sid, CryptoEngine::kApiVersion, sid); } -SessionContext::~SessionContext() { -} +SessionContext::~SessionContext() {} // Internal utility function to derive key using CMAC-128 bool SessionContext::DeriveKey(const std::vector& key, @@ -562,11 +561,13 @@ OEMCryptoResult SessionContext::CheckStatusOffline(uint32_t nonce, OEMCryptoResult SessionContext::CheckNonceOrEntry( const KeyControlBlock& key_control_block) { switch (key_control_block.control_bits() & wvoec::kControlReplayMask) { - case wvoec::kControlNonceRequired: // Online license. Nonce always required. + case wvoec::kControlNonceRequired: // Online license. Nonce always + // required. return CheckStatusOnline(key_control_block.nonce(), key_control_block.control_bits()); break; - case wvoec::kControlNonceOrEntry: // Offline license. Nonce required on first use. + case wvoec::kControlNonceOrEntry: // Offline license. Nonce required on + // first use. return CheckStatusOffline(key_control_block.nonce(), key_control_block.control_bits()); break; @@ -725,9 +726,8 @@ OEMCryptoResult SessionContext::LoadKeysNoSignature( message + key_array[i].key_control_iv.offset, message + key_array[i].key_control_iv.offset + wvoec::KEY_IV_SIZE); - OEMCryptoResult result = - InstallKey(key_id, enc_key_data, key_data_iv, key_control, - key_control_iv); + OEMCryptoResult result = InstallKey(key_id, enc_key_data, key_data_iv, + key_control, key_control_iv); if (result != OEMCrypto_SUCCESS) { status = result; break; @@ -882,9 +882,20 @@ OEMCryptoResult SessionContext::LoadEntitledCasKeys( return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (!key_session->AddOrUpdateContentKey( - entitlement_key, content_key_id, - std::unique_ptr(new EntitledKey(content_key)))) { + auto content_key_obj = + std::unique_ptr(new EntitledKey(content_key)); + // Store content iv and encryption info. + if (key_data->content_iv.length > 0) { + std::vector content_iv; + content_iv.assign( + message + key_data->content_iv.offset, + message + key_data->content_iv.offset + key_data->content_iv.length); + content_key_obj->set_content_iv(content_iv); + } + content_key_obj->set_cipher_mode(key_data->cipher_mode); + + if (!key_session->AddOrUpdateContentKey(entitlement_key, content_key_id, + std::move(content_key_obj))) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } } @@ -1172,7 +1183,7 @@ OEMCryptoResult SessionContext::CheckKeyControlBlockUse( LOGE("[%s(): CGMS required, but buffer is clear", log_string.c_str()); return OEMCrypto_ERROR_ANALOG_OUTPUT; } - if ( ce_->analog_display_active() && !ce_->cgms_a_active()) { + if (ce_->analog_display_active() && !ce_->cgms_a_active()) { LOGE("[%s(): control bit says CGMS required", log_string.c_str()); return OEMCrypto_ERROR_ANALOG_OUTPUT; } @@ -1200,8 +1211,9 @@ OEMCryptoResult SessionContext::Generic_Encrypt( LOGE("[Generic_Encrypt(): CONTENT_KEY has wrong size: %d", key.size()); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - OEMCryptoResult result = CheckKeyUse("Generic_Encrypt", wvoec::kControlAllowEncrypt, - OEMCrypto_BufferType_Clear); + OEMCryptoResult result = + CheckKeyUse("Generic_Encrypt", wvoec::kControlAllowEncrypt, + OEMCrypto_BufferType_Clear); if (result != OEMCrypto_SUCCESS) return result; if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { LOGE("[Generic_Encrypt(): algorithm bad"); @@ -1242,8 +1254,9 @@ OEMCryptoResult SessionContext::Generic_Decrypt( LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - OEMCryptoResult result = CheckKeyUse("Generic_Decrypt", wvoec::kControlAllowDecrypt, - OEMCrypto_BufferType_Clear); + OEMCryptoResult result = + CheckKeyUse("Generic_Decrypt", wvoec::kControlAllowDecrypt, + OEMCrypto_BufferType_Clear); if (result != OEMCrypto_SUCCESS) return result; if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { @@ -1327,8 +1340,8 @@ OEMCryptoResult SessionContext::Generic_Verify( LOGE("[Generic_Verify(): CONTENT_KEY has wrong size: %d", key.size()); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - OEMCryptoResult result = CheckKeyUse("Generic_Verify", wvoec::kControlAllowVerify, - OEMCrypto_BufferType_Clear); + OEMCryptoResult result = CheckKeyUse( + "Generic_Verify", wvoec::kControlAllowVerify, OEMCrypto_BufferType_Clear); if (result != OEMCrypto_SUCCESS) return result; if (algorithm != OEMCrypto_HMAC_SHA256) { LOGE("[Generic_Verify(): bad algorithm"); @@ -1411,7 +1424,7 @@ OEMCryptoResult SessionContext::SelectEntitledContentKey( LOGE("No key matches key id"); return OEMCrypto_ERROR_NO_CONTENT_KEY; } - content_key->set_ctr_mode(cipher_mode == OEMCrypto_CipherMode_CTR); + content_key->set_cipher_mode(cipher_mode); // Update current content key selection. key_session->SetCurrentKey(key_id); @@ -1579,9 +1592,11 @@ OEMCryptoResult SessionContext::DecryptSamples( subsample_source += subsample_length; advance_dest_buffer(&subsample_dest, subsample_length); if (subsample.num_bytes_encrypted > 0) { - bool is_ctr_mode = key_session == nullptr - ? current_content_key()->ctr_mode() - : key_session->CurrentContentKey()->ctr_mode(); + bool is_ctr_mode = + key_session == nullptr + ? current_content_key()->ctr_mode() + : key_session->CurrentContentKey()->cipher_mode() == + OEMCrypto_CipherMode_CTR; if (is_ctr_mode) { wvutil::AdvanceIvCtr( &subsample_iv, @@ -1702,7 +1717,8 @@ OEMCryptoResult SessionContext::ChooseDecrypt( } bool is_ctr_mode = key_session == nullptr ? current_content_key()->ctr_mode() - : key_session->CurrentContentKey()->ctr_mode(); + : key_session->CurrentContentKey()->cipher_mode() == + OEMCrypto_CipherMode_CTR; if (!is_ctr_mode) { if (block_offset > 0 || pattern->encrypt == 0) { return OEMCrypto_ERROR_INVALID_CONTEXT; diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 1ad58ad..3f62e08 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -154,13 +154,14 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { // tests are failing when the device has the wrong keybox installed. TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = - "OEMCrypto unit tests for API 16.3. Tests last updated 2020-06-01"; + "OEMCrypto unit tests for API 16.4. Tests last updated 2020-10-07"; cout << " " << log_message << "\n"; LOGI("%s", log_message.c_str()); - // If any of the following fail, then it is time to update the log message - // above. - EXPECT_EQ(ODK_MAJOR_VERSION, 16); - EXPECT_EQ(ODK_MINOR_VERSION, 3); + // Note on minor versions. Widevine requires version 16.3 or greater for CE + // CDM and Android devices. For CAS devices that do not support usage + // tables, we strongly recommend 16.4. + EXPECT_GE(ODK_MINOR_VERSION, 3); + EXPECT_LE(ODK_MINOR_VERSION, 4); EXPECT_EQ(kCurrentAPI, 16u); const char* level = OEMCrypto_SecurityLevel(); ASSERT_NE(nullptr, level); diff --git a/plugin/include/cas_media_id.h b/plugin/include/cas_media_id.h index 23b557f..c993d93 100644 --- a/plugin/include/cas_media_id.h +++ b/plugin/include/cas_media_id.h @@ -15,8 +15,6 @@ class CasMediaId { virtual CasStatus initialize(const std::string& init_data) = 0; virtual const std::string content_id() = 0; virtual const std::string provider_id() = 0; - virtual const int group_ids_size() = 0; - virtual const std::string group_id() = 0; }; } // namespace wvcas diff --git a/plugin/include/cas_status.h b/plugin/include/cas_status.h index fc68dc2..be8ec1a 100644 --- a/plugin/include/cas_status.h +++ b/plugin/include/cas_status.h @@ -28,6 +28,7 @@ enum class CasStatusCode : int32_t { kDeferedEcmProcessing = 14, kAccessDeniedByParentalControl = 15, kUnknownEvent = 16, + kOEMCryptoVersionMismatch = 17, }; class CasStatus { diff --git a/plugin/include/cas_util.h b/plugin/include/cas_util.h index 72bcf7c..bc956d1 100644 --- a/plugin/include/cas_util.h +++ b/plugin/include/cas_util.h @@ -7,9 +7,9 @@ #include -#if __cplusplus >= 201402L || \ - (defined __cpp_lib_make_unique && __cpp_lib_make_unique >= 201304L) || \ - (defined(_MSC_VER) && _MSC_VER >= 1900) +#if __cplusplus >= 201402L || \ + (defined __cpp_lib_make_unique && __cpp_lib_make_unique >= 201304L) || \ + (defined(_MSC_VER) && _MSC_VER >= 1900) using std::make_unique; #else template @@ -18,4 +18,4 @@ std::unique_ptr make_unique(Args&&... args) { } #endif -#endif //CAS_UTIL_H_ +#endif // CAS_UTIL_H_ diff --git a/plugin/include/crypto_key.h b/plugin/include/crypto_key.h index d266ff0..9341557 100644 --- a/plugin/include/crypto_key.h +++ b/plugin/include/crypto_key.h @@ -9,15 +9,15 @@ namespace wvcas { class CryptoKey { public: - CryptoKey() {}; - ~CryptoKey() {}; + CryptoKey(){}; + ~CryptoKey(){}; const std::string& key_id() const { return key_id_; } const std::string& key_data() const { return key_data_; } const std::string& key_data_iv() const { return key_data_iv_; } const std::string& key_control() const { return key_control_; } const std::string& key_control_iv() const { return key_control_iv_; } - const std::string& entitlement_key_id() const {return entitlement_key_id_;} + const std::string& entitlement_key_id() const { return entitlement_key_id_; } const std::string& track_label() const { return track_label_; } void set_key_id(const std::string& key_id) { key_id_ = key_id; } void set_key_data(const std::string& key_data) { key_data_ = key_data; } diff --git a/plugin/include/crypto_session.h b/plugin/include/crypto_session.h index 9caaed6..f262ab8 100644 --- a/plugin/include/crypto_session.h +++ b/plugin/include/crypto_session.h @@ -37,7 +37,7 @@ typedef OEMCrypto_HDCP_Capability HdcpCapability; class CryptoLock { public: - CryptoLock() {}; + CryptoLock(){}; // These methods should be used to take the various CryptoSession mutexes in // preference to taking the mutexes directly. // @@ -75,18 +75,19 @@ class CryptoLock { // of a single call into OEMCrypto) unless there is a compelling argument // otherwise, such as making two calls into OEMCrypto immediately after each // other. - template + template static auto WithStaticFieldWriteLock(const char* tag, Func body) - -> decltype(body()); - template + -> decltype(body()); + template static auto WithStaticFieldReadLock(const char* tag, Func body) - -> decltype(body()); - template + -> decltype(body()); + template static auto WithOecWriteLock(const char* tag, Func body) -> decltype(body()); - template + template static auto WithOecReadLock(const char* tag, Func body) -> decltype(body()); - template + template auto WithOecSessionLock(const char* tag, Func body) -> decltype(body()); + private: // The locking methods above should be used in preference to taking these // mutexes directly. If code takes these manually and needs to take more @@ -161,9 +162,10 @@ class CryptoInterface { OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const OEMCrypto_EntitledCasKeyObject* even_key, const OEMCrypto_EntitledCasKeyObject* odd_key); - virtual OEMCryptoResult OEMCrypto_SelectKey( - OEMCrypto_SESSION session, const uint8_t* content_key_id, - size_t content_key_id_length, OEMCryptoCipherMode cipher_mode); + virtual OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, + const uint8_t* content_key_id, + size_t content_key_id_length, + OEMCryptoCipherMode cipher_mode); virtual OEMCryptoResult OEMCrypto_GetHDCPCapability( OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max); virtual OEMCryptoResult OEMCrypto_RefreshKeys( @@ -174,10 +176,10 @@ class CryptoInterface { size_t* idLength); virtual const char* OEMCrypto_SecurityLevel(); virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession( - OEMCrypto_SESSION session, - OEMCrypto_SESSION* entitled_key_session_id); + OEMCrypto_SESSION session, OEMCrypto_SESSION* entitled_key_session_id); virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession( OEMCrypto_SESSION entitled_key_session_id); + virtual uint32_t OEMCrypto_APIVersion(); // This is the factory method used to enable the oemcrypto interface. static OEMCryptoResult create(std::unique_ptr* init) { @@ -190,7 +192,7 @@ class CryptoInterface { // This initializer factory method is templated to allow tests to pass in // a mocked OEMCryptoInterface. The caller retains ownership of // |oemcrypto_interface|. - template + template static OEMCryptoResult create(std::unique_ptr* init, CI* oemcrypto_interface) { return create_internal(oemcrypto_interface, init); @@ -299,6 +301,7 @@ class CryptoSession { OEMCrypto_SESSION* entitled_key_session_id); virtual CasStatus RemoveEntitledKeySession( OEMCrypto_SESSION entitled_key_session_id); + virtual CasStatus APIVersion(uint32_t* api_version); CryptoSession(const CryptoSession&) = delete; CryptoSession& operator=(const CryptoSession&) = delete; diff --git a/plugin/include/oemcrypto_interface.h b/plugin/include/oemcrypto_interface.h index 37919c6..1d28529 100644 --- a/plugin/include/oemcrypto_interface.h +++ b/plugin/include/oemcrypto_interface.h @@ -41,12 +41,13 @@ struct InputStreamParams { bool is_encrypted; InputStreamParams(){}; - InputStreamParams(const uint8_t* data_addr, size_t data_length, - bool is_encrypted):data_addr(data_addr), data_length(data_length), - is_encrypted(is_encrypted){} + InputStreamParams(const uint8_t* data_addr, size_t data_length, + bool is_encrypted) + : data_addr(data_addr), + data_length(data_length), + is_encrypted(is_encrypted) {} }; - // Calls to oemcrypto are called via this object. The purpose of this object is // to allow OEMCrypto to be mocked. The implementation of this object only wraps // OEMCrypto methods adding limited additional functionality. Added @@ -95,8 +96,8 @@ class OEMCryptoInterface { OEMCrypto_SESSION session, uint8_t* public_cert, size_t* public_cert_length) const; virtual OEMCryptoResult OEMCrypto_LoadDRMPrivateKey( - OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, - const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) const; + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) const; virtual OEMCryptoResult OEMCrypto_GenerateRSASignature( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, uint8_t* signature, size_t* signature_length, @@ -142,6 +143,7 @@ class OEMCryptoInterface { OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session); virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession( OEMCrypto_SESSION key_session); + virtual uint32_t OEMCrypto_APIVersion() const; OEMCryptoInterface(const OEMCryptoInterface&) = delete; OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete; diff --git a/plugin/include/widevine_cas_api.h b/plugin/include/widevine_cas_api.h index d17d358..2db8d27 100644 --- a/plugin/include/widevine_cas_api.h +++ b/plugin/include/widevine_cas_api.h @@ -49,12 +49,13 @@ class WidevineCas : public wvutil::TimerHandler { // Generates an entitlement license request in |entitlement_request| for the // media described in |init_data|. - virtual CasStatus generateEntitlementRequest( - const std::string& init_data, std::string* entitlement_request); + virtual CasStatus generateEntitlementRequest(const std::string& init_data, + std::string* entitlement_request, + std::string& license_id); // Processes the entitlement |response| to a entitlement license request. - virtual CasStatus handleEntitlementResponse( - const std::string& response, std::string& license_id); + virtual CasStatus handleEntitlementResponse(const std::string& response, + std::string& license_id); // Generates an entitlement license request in |entitlement_request| for the // media described in |init_data|. diff --git a/plugin/include/widevine_media_cas_plugin.h b/plugin/include/widevine_media_cas_plugin.h index 86cab4d..d28fae5 100644 --- a/plugin/include/widevine_media_cas_plugin.h +++ b/plugin/include/widevine_media_cas_plugin.h @@ -14,8 +14,8 @@ using android::CasPlugin; using android::CasPluginCallback; using android::CasPluginCallbackExt; using android::CasPluginStatusCallback; -using android::status_t; using android::CasSessionId; +using android::status_t; using android::String8; namespace wvcas { @@ -91,8 +91,7 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener { private: virtual std::shared_ptr getCryptoSession(); // |sessionId| is nullptr if the event is not a session event. - virtual CasStatus processEvent(int32_t event, - int32_t arg, + virtual CasStatus processEvent(int32_t event, int32_t arg, const CasData& eventData, const CasSessionId* sessionId); virtual CasStatus HandleIndividualizationResponse(const CasData& response); @@ -116,12 +115,8 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener { // Choose to use |callback_| or |callback_ext_| to send back information. // |sessionId| is ignored if |callback_ext_| is null, - void CallBack(void* appData, - int32_t event, - int32_t arg, - uint8_t* data, - size_t size, - const CasSessionId* sessionId) const; + void CallBack(void* appData, int32_t event, int32_t arg, uint8_t* data, + size_t size, const CasSessionId* sessionId) const; void* app_data_; CasPluginCallback callback_; diff --git a/plugin/src/cas_license.cpp b/plugin/src/cas_license.cpp index 8bc9df0..bfc82a4 100644 --- a/plugin/src/cas_license.cpp +++ b/plugin/src/cas_license.cpp @@ -2,13 +2,14 @@ // source code may only be used and distributed under the Widevine Master // License Agreement. +#include "cas_license.h" + #include #include #include #include -#include "cas_license.h" #include "cas_properties.h" #include "cas_util.h" #include "crypto_key.h" @@ -41,6 +42,7 @@ using video_widevine::SignedMessage; using video_widevine::SignedProvisioningMessage; static constexpr size_t kMacKeySizeBytes = 32; +static constexpr size_t kMacKeyPaddingSizeBytes = 16; static constexpr size_t kIvSizeBytes = 16; static constexpr size_t kCertificateDataSizeBytes = 4 * 1024; static constexpr uint32_t kRsaSignatureSizeBytes = 256; @@ -52,11 +54,11 @@ namespace wvcas { // module. namespace { -static constexpr char kKeyCompanyName[] = "company_name"; -static constexpr char kKeyModelName[] = "model_name"; -static constexpr char kKeyArchitectureName[] = "architecture_name"; -static constexpr char kKeyDeviceName[] = "device_name"; -static constexpr char kKeyProductName[] = "product_name"; +constexpr char kKeyCompanyName[] = "company_name"; +constexpr char kKeyModelName[] = "model_name"; +constexpr char kKeyArchitectureName[] = "architecture_name"; +constexpr char kKeyDeviceName[] = "device_name"; +constexpr char kKeyProductName[] = "product_name"; // TODO(jfore): These variables are currently unused and are flagged as build // errors in android. These values will be used in a future cl. @@ -412,8 +414,12 @@ CasStatus CasLicense::GenerateEntitlementRequest( // TODO(jfore): Anti rollback support? - // TODO(jfore): Read from oemcrypto and fail if the version is < 14. - client_capabilities->set_oem_crypto_api_version(14); + uint32_t api_version = 0; + status = crypto_session_->APIVersion(&api_version); + if (!status.ok()) { + return status; + } + client_capabilities->set_oem_crypto_api_version(api_version); // TODO(jfore): Handle hdcp capabilities. @@ -532,7 +538,8 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key, return CasStatus(CasStatusCode::kInvalidLicenseFile, "unable to parse license renewal response"); } - // Disable the expiration callback when renewal file is a part of license file. + // Disable the expiration callback when renewal file is a part of license + // file. is_renewal_in_license_file_ = true; } @@ -741,21 +748,24 @@ CasStatus CasLicense::InstallLicense(const std::string& session_key, for (int i = 0; i < license.key_size(); ++i) { if (license.key(i).type() == License_KeyContainer::SIGNING) { mac_key_iv_str.assign(license.key(i).iv()); - // Strip off PKCS#5 padding - mac_key_str.assign(license.key(i).key().data(), kMacKeySizeBytes); + mac_key_str.assign(license.key(i).key()); } } if (license.policy().can_renew() || (mac_key_iv_str.size() != 0 || mac_key_str.size() != 0)) { + // In V2.1 license protocol, the expected mac key size is 80 bytes: 32 bytes + // server key; 32 bytes client key; 16 bytes of padding. if (mac_key_iv_str.size() != kIvSizeBytes || - mac_key_str.size() != kMacKeySizeBytes) { + mac_key_str.size() != 2 * kMacKeySizeBytes + kMacKeyPaddingSizeBytes) { std::ostringstream err_string; err_string << "mac key/iv size error (key/iv size expected: " - << kMacKeySizeBytes << "/" << kIvSizeBytes - << " actual: " << mac_key_str.size() << "/" + << 2 * kMacKeySizeBytes + kMacKeyPaddingSizeBytes << "/" + << kIvSizeBytes << " actual: " << mac_key_str.size() << "/" << mac_key_iv_str.size(); return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); } + // Strip off the padding. + mac_key_str.resize(2 * kMacKeySizeBytes); } std::vector key_array = ExtractEntitlementKeys(license); @@ -972,40 +982,39 @@ void CasLicense::UpdateLicenseForLicenseRemove() { } void CasLicense::OnLicenseExpiration() { - if(event_listener_ && IsExpired() && !is_renewal_in_license_file_) { - event_listener_->OnLicenseExpiration(); + if (event_listener_ && IsExpired() && !is_renewal_in_license_file_) { + event_listener_->OnLicenseExpiration(); } } -void CasLicense::OnNewRenewalServerUrl( - const std::string& renewal_server_url) { - if(event_listener_) { +void CasLicense::OnNewRenewalServerUrl(const std::string& renewal_server_url) { + if (event_listener_) { event_listener_->OnNewRenewalServerUrl(renewal_server_url); } } void CasLicense::OnSessionRenewalNeeded() { - if(event_listener_) { + if (event_listener_) { event_listener_->OnSessionRenewalNeeded(); } } void CasLicense::OnSessionKeysChange(const wvcas::KeyStatusMap& keys_status, bool has_new_usable_key) { - if(event_listener_) { + if (event_listener_) { event_listener_->OnSessionKeysChange(keys_status, has_new_usable_key); } } void CasLicense::OnExpirationUpdate(int64_t new_expiry_time_seconds) { - if(event_listener_) { + if (event_listener_) { event_listener_->OnExpirationUpdate(new_expiry_time_seconds); } } void CasLicense::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId, - uint8_t ecm_age_restriction) { - if(event_listener_) { + uint8_t ecm_age_restriction) { + if (event_listener_) { event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_restriction); } } diff --git a/plugin/src/crypto_session.cpp b/plugin/src/crypto_session.cpp index 65afda8..b53de5b 100644 --- a/plugin/src/crypto_session.cpp +++ b/plugin/src/crypto_session.cpp @@ -11,6 +11,7 @@ #include "cas_util.h" #include "log.h" +static const uint32_t kExpectedOEMCryptoVersion = 16; namespace wvcas { @@ -53,11 +54,11 @@ void FillEntitledContentKeyObjectFromKeyData( if (nullptr == dest) { return; } - std::string entitlement_key_id - (src.entitlement_key_id.begin(), src.entitlement_key_id.end()); + std::string entitlement_key_id(src.entitlement_key_id.begin(), + src.entitlement_key_id.end()); std::string content_key_id(src.key_id.begin(), src.key_id.end()); - std::string - content_key_data_iv(src.wrapped_key_iv.begin(), src.wrapped_key_iv.end()); + std::string content_key_data_iv(src.wrapped_key_iv.begin(), + src.wrapped_key_iv.end()); std::string content_key_data(src.wrapped_key.begin(), src.wrapped_key.end()); std::string content_iv(src.content_iv.begin(), src.content_iv.end()); @@ -392,6 +393,12 @@ OEMCryptoResult CryptoInterface::OEMCrypto_RemoveEntitledKeySession( }); } +uint32_t CryptoInterface::OEMCrypto_APIVersion() { + return lock_->WithOecReadLock("APIVersion", [&] { + return oemcrypto_interface_->OEMCrypto_APIVersion(); + }); +} + OEMCryptoResult CryptoInterface::create_internal( OEMCryptoInterface* oemcrypto_interface, std::unique_ptr* init) { @@ -438,9 +445,8 @@ CryptoInterface::~CryptoInterface() { lock_->WithStaticFieldWriteLock("Terminate", [&] { if (session_count_ > 0) { if (--session_count_ == 0) { - lock_->WithOecWriteLock("Terminate", [&] { - oemcrypto_interface_->OEMCrypto_Terminate(); - }); + lock_->WithOecWriteLock( + "Terminate", [&] { oemcrypto_interface_->OEMCrypto_Terminate(); }); } } }); @@ -1087,6 +1093,8 @@ CasStatus CryptoSession::RefreshKeys(const std::string& message, if (!key.key_control().empty()) { key_object.key_control_iv.offset = GetOffset(message, key.key_control_iv()); + key_object.key_control_iv.length = key.key_control_iv().length(); + key_object.key_control.offset = GetOffset(message, key.key_control()); key_object.key_control.length = key.key_control().length(); } } @@ -1180,4 +1188,23 @@ CasStatus CryptoSession::RemoveEntitledKeySession( return CasStatus::OkStatus(); } +CasStatus CryptoSession::APIVersion(uint32_t* api_version) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + uint32_t oemcrypto_api_version = crypto_interface_->OEMCrypto_APIVersion(); + + if (oemcrypto_api_version != kExpectedOEMCryptoVersion) { + std::ostringstream err_string; + err_string << "OEMCrypto_APIVersion returned: " << oemcrypto_api_version + << ". While the correct API version should be: " + << kExpectedOEMCryptoVersion; + return CasStatus(CasStatusCode::kOEMCryptoVersionMismatch, + err_string.str()); + } + *api_version = oemcrypto_api_version; + return CasStatus::OkStatus(); +} + } // namespace wvcas diff --git a/plugin/src/ecm_parser.cpp b/plugin/src/ecm_parser.cpp index d99b538..f89d124 100644 --- a/plugin/src/ecm_parser.cpp +++ b/plugin/src/ecm_parser.cpp @@ -5,35 +5,63 @@ #include "ecm_parser.h" #include + #include +#include "log.h" + namespace wvcas { namespace { - // ECM constants -static constexpr uint8_t kSequenceCountMask = 0xF8; // Mode bits 3..7 -static constexpr uint8_t kCryptoModeFlags = (0x3 << 1); // Mode bits 1..2 -static constexpr uint8_t kCryptoModeFlagsV2 = (0xF << 1); // Mode bits 1..4 -static constexpr uint8_t kRotationFlag = (0x1 << 0); // Mode bit 0 -static constexpr uint8_t kContentIVSizeFlag = (0x1 << 6); -static constexpr uint8_t kAgeRestrictionMask = (0x1F << 1); +constexpr uint8_t kSequenceCountMask = 0xF8; // Mode bits 3..7 +constexpr uint8_t kCryptoModeFlags = (0x3 << 1); // Mode bits 1..2 +constexpr uint8_t kCryptoModeFlagsV2 = (0xF << 1); // Mode bits 1..4 +constexpr uint8_t kRotationFlag = (0x1 << 0); // Mode bit 0 +constexpr uint8_t kContentIVSizeFlag = (0x1 << 6); +constexpr uint8_t kAgeRestrictionMask = (0x1F << 1); -static constexpr size_t kEntitlementKeyIDSizeBytes = 16; -static constexpr size_t kContentKeyIDSizeBytes = 16; -static constexpr size_t kContentKeyDataSizeBytes = 16; -static constexpr size_t kWrappedKeyIVSizeBytes = 16; +constexpr size_t kEntitlementKeyIDSizeBytes = 16; +constexpr size_t kContentKeyIDSizeBytes = 16; +constexpr size_t kContentKeyDataSizeBytes = 16; +constexpr size_t kWrappedKeyIVSizeBytes = 16; // Size is either 8 or 16 bytes, depending on ContentIVSize flag. -static constexpr size_t kContentKeyMaxIVSizeBytes = 16; -static constexpr uint16_t kWidevineCaId = 0x4AD4; +constexpr size_t kContentKeyMaxIVSizeBytes = 16; + +// Legacy Widevine CAS ID +constexpr uint16_t kWidevineCasId = 0x4AD4; +// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive). +constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0; +constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9; // Two values of the table_id field (0x80 and 0x81) are reserved for // transmission of ECM data. A change of these two table_id values signals // that a change of ECM contents has occurred. -static constexpr uint16_t kSectionHeader1 = 0x80; -static constexpr uint16_t kSectionHeader2 = 0x81; +constexpr uint8_t kSectionHeader1 = 0x80; +constexpr uint8_t kSectionHeader2 = 0x81; +constexpr size_t kSectionHeaderSize = 3; +constexpr size_t kSectionHeaderWithPointerSize = 4; +constexpr uint8_t kPointerFieldZero = 0x00; -static constexpr size_t kMaxTsPayloadSizeBytes = 184; +constexpr size_t kMaxTsPayloadSizeBytes = 184; + +// Returns the possible starting index of ECM. It assumes the pointer field will +// always set to 0, if present. +int find_ecm_start_index(const CasEcm& cas_ecm) { + if (cas_ecm.empty()) { + return 0; + } + // Case 1: Pointer field (always set to 0); section header; ECM. + if (cas_ecm[0] == kPointerFieldZero) { + return kSectionHeaderWithPointerSize; + } + // Case 2: Section header (3 bytes), ECM. + if (cas_ecm[0] == kSectionHeader1 || cas_ecm[0] == kSectionHeader2) { + return kSectionHeaderSize; + } + // Case 3: ECM. + return 0; +} } // namespace @@ -92,29 +120,27 @@ const EcmKeyData* EcmParser::key_slot_data(KeySlotId id) const { bool EcmParser::create(const CasEcm& cas_ecm, std::unique_ptr* parser) { - if (nullptr == parser || (cas_ecm.size() < sizeof(EcmDescriptor))) { + if (parser == nullptr) { return false; } // Detect and strip optional section header. - // Use the index of kWidevineCaId to identify the header size. - const CasEcm::const_iterator loc_WVCaId_firstByte = - std::find(cas_ecm.begin(), cas_ecm.end(), kWidevineCaId >> 8); - const CasEcm::const_iterator loc_WVCaId_secondByte = - std::find(cas_ecm.begin(), cas_ecm.end(), kWidevineCaId & 0xFF); + int offset = find_ecm_start_index(cas_ecm); + if (offset < 0 || (static_cast(cas_ecm.size()) - offset < + static_cast(sizeof(EcmDescriptor)))) { + return false; + } - if (loc_WVCaId_firstByte >= cas_ecm.end() - 1 || - loc_WVCaId_firstByte + 1 != loc_WVCaId_secondByte) { - return false; - } - const CasEcm& ecm = std::vector(loc_WVCaId_firstByte, cas_ecm.end()); - if (ecm.size() < sizeof(EcmDescriptor)) { - return false; - } - //reconfirm ecm data should start with kWidevineCaId - uint16_t ca_id_val = ntohs(*reinterpret_cast(ecm.data())); - if (ca_id_val != kWidevineCaId) { + const CasEcm ecm(cas_ecm.begin() + offset, cas_ecm.end()); + // Reconfirm ecm data should start with valid Widevine CAS ID. + uint16_t cas_id_val = ntohs(*reinterpret_cast(ecm.data())); + if (cas_id_val != kWidevineCasId && + (cas_id_val < kWidevineNewCasIdLowerBound || + cas_id_val > kWidevineNewCasIdUpperBound)) { + LOGE("Supported Widevine CAS IDs not found at the start of ECM. Found: %u", + cas_id_val); return false; } + // Using 'new' to access a non-public constructor. std::unique_ptr new_parser = std::unique_ptr(new EcmParser(ecm)); diff --git a/plugin/src/license_key_status.cpp b/plugin/src/license_key_status.cpp index 701fcfa..d183ece 100644 --- a/plugin/src/license_key_status.cpp +++ b/plugin/src/license_key_status.cpp @@ -151,8 +151,8 @@ bool LicenseKeys::MeetsConstraints(const KeyId& key_id) { } } -void LicenseKeys::ApplyConstraints( - uint32_t new_resolution, HdcpCapability new_hdcp_level) { +void LicenseKeys::ApplyConstraints(uint32_t new_resolution, + HdcpCapability new_hdcp_level) { for (LicenseKeyStatusIterator i = key_statuses_.begin(); i != key_statuses_.end(); ++i) { i->second->ApplyConstraints(new_resolution, new_hdcp_level); @@ -321,8 +321,8 @@ bool LicenseKeyStatus::ApplyStatusChange(KeyStatus new_status, // If the key has no constraints, or if the constraint has no HDCP // requirement, use the key's default HDCP setting to check against the // device's current HDCP level. -void LicenseKeyStatus::ApplyConstraints( - uint32_t video_pixels, HdcpCapability new_hdcp_level) { +void LicenseKeyStatus::ApplyConstraints(uint32_t video_pixels, + HdcpCapability new_hdcp_level) { VideoResolutionConstraint* current_constraint = NULL; if (HasConstraints() && video_pixels != HDCP_UNSPECIFIED_VIDEO_RESOLUTION) { current_constraint = GetConstraintForRes(video_pixels, constraints_); diff --git a/plugin/src/oemcrypto_interface.cpp b/plugin/src/oemcrypto_interface.cpp index 3ba92cd..c09e972 100644 --- a/plugin/src/oemcrypto_interface.cpp +++ b/plugin/src/oemcrypto_interface.cpp @@ -118,6 +118,7 @@ class OEMCryptoInterface::Impl { OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session); typedef OEMCryptoResult (*RemoveEntitledKeySession_t)( OEMCrypto_SESSION key_session); + typedef uint32_t (*APIVersion_t)(); Initialize_t Initialize = nullptr; Terminate_t Terminate = nullptr; @@ -148,6 +149,7 @@ class OEMCryptoInterface::Impl { SecurityLevel_t SecurityLevel = nullptr; CreateEntitledKeySession_t CreateEntitledKeySession = nullptr; RemoveEntitledKeySession_t RemoveEntitledKeySession = nullptr; + APIVersion_t APIVersion = nullptr; private: bool initialize() { @@ -191,6 +193,7 @@ class OEMCryptoInterface::Impl { LOAD_SYM(SecurityLevel); LOAD_SYM(CreateEntitledKeySession); LOAD_SYM(RemoveEntitledKeySession); + LOAD_SYM(APIVersion); // Optional methods that may be available. TRY_LOAD_SYM(LoadTestKeybox); @@ -408,4 +411,7 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_RemoveEntitledKeySession( return impl_->RemoveEntitledKeySession(key_session); } +uint32_t OEMCryptoInterface::OEMCrypto_APIVersion() const { + return impl_->APIVersion(); +} } // namespace wvcas diff --git a/plugin/src/policy_engine.cpp b/plugin/src/policy_engine.cpp index 8f69bba..89b3ebc 100644 --- a/plugin/src/policy_engine.cpp +++ b/plugin/src/policy_engine.cpp @@ -5,6 +5,7 @@ #include "policy_engine.h" #include + #include #include "log.h" @@ -111,8 +112,8 @@ void PolicyEngine::NotifyRenewalServerUpdate() { } } -void PolicyEngine::NotifyLicenseExpired(LicenseState license_state){ - if(event_listener_ && license_state == kLicenseStateExpired) { +void PolicyEngine::NotifyLicenseExpired(LicenseState license_state) { + if (event_listener_ && license_state == kLicenseStateExpired) { event_listener_->OnLicenseExpiration(); } } diff --git a/plugin/src/widevine_cas_api.cpp b/plugin/src/widevine_cas_api.cpp index 3a5d5a0..483737f 100644 --- a/plugin/src/widevine_cas_api.cpp +++ b/plugin/src/widevine_cas_api.cpp @@ -1,3 +1,5 @@ +#include "widevine_cas_api.h" + #include #include "ca_descriptor.pb.h" @@ -6,11 +8,11 @@ #include "license_protocol.pb.h" #include "log.h" #include "string_conversions.h" -#include "widevine_cas_api.h" #include "widevine_cas_session_map.h" -static constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/"; -static constexpr char kCertFileBase[] = "cert.bin"; +constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/"; +constexpr char kCertFileBase[] = "cert.bin"; +constexpr char kLicenseFileNameSuffix[] = ".lic"; namespace { bool ReadFileFromStorage(wvutil::FileSystem& file_system, @@ -42,7 +44,7 @@ bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) { if (!file_system.Exists(filename)) { return false; } - if(!file_system.Remove(filename)){ + if (!file_system.Remove(filename)) { return false; } return true; @@ -62,9 +64,9 @@ bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename, return true; } -std::string GenerateLicenseFilename(const std::string& id_data_from_media_id, +std::string GenerateLicenseFilename(const std::string& content_id, const std::string& provider_id) { - std::string data(id_data_from_media_id + provider_id); + std::string data(content_id + provider_id); std::string hash; hash.resize(SHA256_DIGEST_LENGTH); const unsigned char* input = @@ -72,7 +74,7 @@ std::string GenerateLicenseFilename(const std::string& id_data_from_media_id, unsigned char* output = reinterpret_cast(&hash[0]); SHA256(input, data.size(), output); return std::string(std::string(kBasePathPrefix) + wvutil::b2a_hex(hash) + - std::string(".lic")); + std::string(kLicenseFileNameSuffix)); } } // namespace @@ -87,13 +89,6 @@ class MediaContext : public CasMediaId { const std::string content_id() override { return pssh_.content_id(); } const std::string provider_id() override { return pssh_.provider(); } - const int group_ids_size() override { return pssh_.group_ids_size(); } - const std::string group_id() override { - if (group_ids_size() > 0) { - return pssh_.group_ids(0); - } - return ""; - } CasStatus initialize(const std::string& init_data) override { if (!pssh_.ParseFromString(init_data)) { @@ -134,21 +129,15 @@ void WidevineCas::OnTimerEvent() { // Delete expired license after firing expired event in policy_engine if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) { - std::string filename; - if (media_id_->group_ids_size() > 0) { - filename = GenerateLicenseFilename(media_id_->group_id(), - media_id_->provider_id()); - } else { - filename = GenerateLicenseFilename(media_id_->content_id(), - media_id_->provider_id()); - } - if (!file_system_->Exists(filename)) { - LOGI("No expired license file stored in disk"); - }else{ - if(RemoveFile(*file_system_, filename)) { - LOGI("Remove expired license file from disk successfully."); - } + std::string filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); + if (!file_system_->Exists(filename)) { + LOGI("No expired license file stored in disk"); + } else { + if (RemoveFile(*file_system_, filename)) { + LOGI("Remove expired license file from disk successfully."); } + } } } } @@ -306,21 +295,16 @@ CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) { } CasStatus WidevineCas::generateEntitlementRequest( - const std::string& init_data, std::string* entitlement_request) { + const std::string& init_data, std::string* entitlement_request, + std::string& license_id) { media_id_ = CasMediaId::create(); CasStatus status = media_id_->initialize(init_data); if (!status.ok()) { return status; } std::string license_file; - std::string filename; - if (media_id_->group_ids_size() > 0) { - filename = GenerateLicenseFilename(media_id_->group_id(), - media_id_->provider_id()); - } else { - filename = GenerateLicenseFilename(media_id_->content_id(), - media_id_->provider_id()); - } + std::string filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); if (ReadFileFromStorage(*file_system_, filename, &license_file)) { CasStatus status = cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file); @@ -329,14 +313,16 @@ CasStatus WidevineCas::generateEntitlementRequest( // delete the stored license file. std::unique_lock locker(lock_); if (cas_license_->IsExpired()) { - if(!RemoveFile(*file_system_, filename)) { + if (!RemoveFile(*file_system_, filename)) { return CasStatus(CasStatusCode::kInvalidLicenseFile, "unable to remove expired license file from disk"); } LOGI("Remove expired license file from disk successfully."); return CasStatus(CasStatusCode::kCasLicenseError, - "license is expired, unable to process emm"); + "license is expired, unable to process emm"); } + license_id = + filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); policy_timer_.Start(this, 1); has_license_ = true; return HandleDeferredECMs(); @@ -355,7 +341,7 @@ CasStatus WidevineCas::generateEntitlementRequest( } CasStatus WidevineCas::handleEntitlementResponse(const std::string& response, - std::string& license_id) { + std::string& license_id) { if (response.empty()) { return CasStatus(CasStatusCode::kCasLicenseError, "empty entitlement response"); @@ -380,18 +366,12 @@ CasStatus WidevineCas::handleEntitlementResponse(const std::string& response, } if (!device_file.empty()) { - std::string filename; - if (media_id_->group_ids_size() > 0) { - filename = GenerateLicenseFilename(media_id_->group_id(), - media_id_->provider_id()); - } else { - filename = GenerateLicenseFilename(media_id_->content_id(), - media_id_->provider_id()); - } + std::string filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); StoreFile(*file_system_, filename, device_file); // license_id will be the filename without ".lic" extension. - license_id = filename.substr(0, - filename.size() - std::string(".lic").size()); + license_id = + filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); } } return status; @@ -421,47 +401,34 @@ CasStatus WidevineCas::handleEntitlementRenewalResponse( return status; } if (!device_file.empty()) { - std::string filename; - if (media_id_->group_ids_size() > 0) { - filename = GenerateLicenseFilename(media_id_->group_id(), - media_id_->provider_id()); - } else { - filename = GenerateLicenseFilename(media_id_->content_id(), - media_id_->provider_id()); - } - StoreFile(*file_system_, filename, device_file); - // license_id will be the filename without ".lic" extension. - license_id = filename.substr(0, - filename.size() - std::string(".lic").size()); + std::string filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); + StoreFile(*file_system_, filename, device_file); + // license_id will be the filename without ".lic" extension. + license_id = + filename.substr(0, filename.size() - std::string(".lic").size()); } return CasStatusCode::kNoError; } - CasStatus WidevineCas::RemoveLicense(const std::string file_name) { // Check if the license is in use. If it is, besides removing the license, // update policy in current license. Else, we just directly remove it. - if (nullptr == media_id_.get() ) { - return CasStatus(CasStatusCode::kCasLicenseError, "No media id"); + if (nullptr == media_id_.get()) { + return CasStatus(CasStatusCode::kCasLicenseError, "No media id"); } // Remove the license file given the file_name user provides. if (!RemoveFile(*file_system_, file_name)) { - return CasStatus(CasStatusCode::kInvalidLicenseFile, + return CasStatus(CasStatusCode::kInvalidLicenseFile, "unable to remove license file from disk"); } LOGI("Remove license file from disk successfully."); - - std::string used_license_filename; - if (media_id_->group_ids_size() > 0) { - used_license_filename = GenerateLicenseFilename(media_id_->group_id(), - media_id_->provider_id()); - } else { - used_license_filename = GenerateLicenseFilename(media_id_->content_id(), - media_id_->provider_id()); - } + std::string used_license_filename = GenerateLicenseFilename( + media_id_->content_id(), media_id_->provider_id()); if (file_name.compare(used_license_filename) == 0) { // Update license policy for the in-used license. Plugin will not allowed to - // play stream, store and renew license unless a new plugin instance is created. + // play stream, store and renew license unless a new plugin instance is + // created. std::unique_lock locker(lock_); cas_license_->UpdateLicenseForLicenseRemove(); } @@ -479,7 +446,7 @@ CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data, return CasStatus(CasStatusCode::kInvalidParameter, "missing output buffer for init_data"); } - // Parse provider, content id and group id from CA descriptor. + // Parse provider and content id from CA descriptor. video_widevine::CaDescriptorPrivateData descriptor; descriptor.ParseFromArray(private_data.data(), private_data.size()); if (!descriptor.has_content_id() || !descriptor.has_provider()) { @@ -491,10 +458,6 @@ CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data, video_widevine::WidevinePsshData pssh; pssh.set_provider(descriptor.provider()); pssh.set_content_id(descriptor.content_id()); - // group id is optional in ca_descriptor. - if (descriptor.has_group_id()) { - pssh.add_group_ids(descriptor.group_id()); - } pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT); pssh.SerializeToString(init_data); return CasStatusCode::kNoError; diff --git a/plugin/src/widevine_media_cas.cpp b/plugin/src/widevine_media_cas.cpp index 98e1598..5c63339 100644 --- a/plugin/src/widevine_media_cas.cpp +++ b/plugin/src/widevine_media_cas.cpp @@ -15,8 +15,13 @@ using android::BAD_VALUE; using android::OK; // Widevine Technologies CA system ID. -static const int32_t kWidevineCAID = 0x4AD4; -static const char kName[] = "WidevineCas"; +static constexpr int32_t kWidevineCAID = 0x4AD4; +// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive). +static constexpr int32_t kWidevineNewCasIdLowerBound = 0x56C0; +static constexpr int32_t kWidevineNewCasIdUpperBound = 0x56C9; +// Total number of supported Widevine CAS ids. +static constexpr size_t kWidevineCasIdCount = 11; +static constexpr char kName[] = "WidevineCas"; // Implements extern android::CasFactory *createCasFactory() entry point. CasFactory* createCasFactory() { @@ -33,10 +38,9 @@ WidevineCasFactory* WidevineCasFactory::createCasFactory() { } bool WidevineCasFactory::isSystemIdSupported(int32_t CA_system_id) const { - if (kWidevineCAID != CA_system_id) { - return false; - } - return true; + return (CA_system_id == kWidevineCAID) || + (CA_system_id >= kWidevineNewCasIdLowerBound && + CA_system_id <= kWidevineNewCasIdUpperBound); } status_t WidevineCasFactory::queryPlugins( @@ -45,7 +49,12 @@ status_t WidevineCasFactory::queryPlugins( return BAD_VALUE; } descriptors->clear(); + descriptors->reserve(kWidevineCasIdCount); descriptors->push_back({kWidevineCAID, String8(kName)}); + for (int32_t new_id = kWidevineNewCasIdLowerBound; + new_id <= kWidevineNewCasIdUpperBound; ++new_id) { + descriptors->push_back({new_id, String8(kName)}); + } return OK; } diff --git a/plugin/src/widevine_media_cas_plugin.cpp b/plugin/src/widevine_media_cas_plugin.cpp index 5c9086b..fceb421 100644 --- a/plugin/src/widevine_media_cas_plugin.cpp +++ b/plugin/src/widevine_media_cas_plugin.cpp @@ -42,11 +42,10 @@ WvCasSessionId androidSessionIdToWidevine( return wv_session_id; } -CasSessionId widevineSessionIdToAndroid( - const WvCasSessionId& wv_session_id) { +CasSessionId widevineSessionIdToAndroid(const WvCasSessionId& wv_session_id) { auto wv_session_id_begin = reinterpret_cast(&wv_session_id); - CasSessionId android_session_id - (wv_session_id_begin, wv_session_id_begin + sizeof(wv_session_id)); + CasSessionId android_session_id(wv_session_id_begin, + wv_session_id_begin + sizeof(wv_session_id)); return android_session_id; } @@ -157,8 +156,8 @@ status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId, return android::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED; case CasStatusCode::kAccessDeniedByParentalControl: CallBack(reinterpret_cast(app_data_), - ACCESS_DENIED_BY_PARENTAL_CONTROL, wv_session_id, - &error[0], error.size(), &sessionId); + ACCESS_DENIED_BY_PARENTAL_CONTROL, wv_session_id, &error[0], + error.size(), &sessionId); return android::ERROR_CAS_DECRYPT; default: CallBack(reinterpret_cast(app_data_), CAS_ERROR, @@ -191,8 +190,7 @@ status_t WidevineCasPlugin::sendEvent(int32_t event, int32_t arg, } status_t WidevineCasPlugin::sendSessionEvent(const CasSessionId& sessionId, - int32_t event, - int32_t arg, + int32_t event, int32_t arg, const CasData& eventData) { CasStatus status = processEvent(event, arg, eventData, &sessionId); if (status.status_code() != CasStatusCode::kNoError) { @@ -240,8 +238,9 @@ status_t WidevineCasPlugin::provision(const String8& provisionString) { status_t WidevineCasPlugin::requestLicense(const std::string& init_data) { std::string signed_license_request; + std::string license_id; CasStatus status = widevine_cas_.generateEntitlementRequest( - init_data, &signed_license_request); + init_data, &signed_license_request, license_id); if (!status.ok()) { return INVALID_OPERATION; } @@ -255,7 +254,8 @@ status_t WidevineCasPlugin::requestLicense(const std::string& init_data) { signed_license_request.size(), nullptr); } else { CallBack(reinterpret_cast(app_data_), LICENSE_CAS_READY, - LICENSE_CAS_READY, nullptr, 0, nullptr); + LICENSE_CAS_READY, reinterpret_cast(&license_id[0]), + license_id.size(), nullptr); } is_emm_request_sent_ = true; return OK; @@ -270,8 +270,7 @@ std::shared_ptr WidevineCasPlugin::getCryptoSession() { return std::make_shared(); } -CasStatus WidevineCasPlugin::processEvent(int32_t event, - int32_t arg, +CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg, const CasData& eventData, const CasSessionId* sessionId) { switch (event) { @@ -329,8 +328,8 @@ CasStatus WidevineCasPlugin::HandleEntitlementResponse( } std::string resp_string(response.begin(), response.end()); std::string license_id; - CasStatus - status = widevine_cas_.handleEntitlementResponse(resp_string, license_id); + CasStatus status = + widevine_cas_.handleEntitlementResponse(resp_string, license_id); if (!status.ok()) { return status; } @@ -352,11 +351,9 @@ CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse( if (!status.ok()) { return status; } - CallBack(reinterpret_cast(app_data_), + CallBack(reinterpret_cast(app_data_), LICENSE_CAS_RENEWAL_READY, LICENSE_CAS_RENEWAL_READY, - LICENSE_CAS_RENEWAL_READY, - reinterpret_cast(&license_id[0]), - license_id.size(), + reinterpret_cast(&license_id[0]), license_id.size(), nullptr); return CasStatusCode::kNoError; } @@ -384,8 +381,7 @@ CasStatus WidevineCasPlugin::HandleSetParentalControlAge(const CasData& data) { CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) { if (license_id.empty()) { - return CasStatus(CasStatusCode::kInvalidParameter, - "empty license id"); + return CasStatus(CasStatusCode::kInvalidParameter, "empty license id"); } std::string license_id_str(license_id.begin(), license_id.end()); @@ -398,12 +394,9 @@ CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) { error.size(), nullptr); return status; } - CallBack(reinterpret_cast(app_data_), - LICENSE_REMOVED, - LICENSE_REMOVED, + CallBack(reinterpret_cast(app_data_), LICENSE_REMOVED, LICENSE_REMOVED, reinterpret_cast(&license_id_str[0]), - license_id_str.size(), - nullptr); + license_id_str.size(), nullptr); return CasStatusCode::kNoError; } @@ -450,10 +443,7 @@ void WidevineCasPlugin::OnLicenseExpiration() { LOGI("OnLicenseExpiration"); CallBack(reinterpret_cast(app_data_), android::ERROR_CAS_LICENSE_EXPIRED, - android::ERROR_CAS_LICENSE_EXPIRED, - nullptr, - 0, - nullptr); + android::ERROR_CAS_LICENSE_EXPIRED, nullptr, 0, nullptr); } void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId, @@ -464,11 +454,8 @@ void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId, sessionId, &ecm_age_restriction, 1, &android_session_id); } -void WidevineCasPlugin::CallBack(void* appData, - int32_t event, - int32_t arg, - uint8_t* data, - size_t size, +void WidevineCasPlugin::CallBack(void* appData, int32_t event, int32_t arg, + uint8_t* data, size_t size, const CasSessionId* sessionId) const { if (callback_ext_ != nullptr) { callback_ext_(appData, event, arg, data, size, sessionId); diff --git a/protos/ca_descriptor.proto b/protos/ca_descriptor.proto index 20ebf43..72b81d1 100644 --- a/protos/ca_descriptor.proto +++ b/protos/ca_descriptor.proto @@ -15,7 +15,4 @@ message CaDescriptorPrivateData { // Content ID. optional bytes content_id = 2; - - // Group ID indicates which group the content comes from. - optional bytes group_id = 3; } diff --git a/protos/device_files.proto b/protos/device_files.proto index ed82219..9ca0b99 100644 --- a/protos/device_files.proto +++ b/protos/device_files.proto @@ -81,7 +81,7 @@ message UsageTableInfo { optional UsageEntryStorage storage = 1; optional bytes key_set_id = 2; - optional bytes usage_info_file_name = 3; // hash of the app_id + optional bytes usage_info_file_name = 3; // hash of the app_id } optional bytes usage_table_header = 1; @@ -97,9 +97,7 @@ message File { USAGE_TABLE_INFO = 5; } - enum FileVersion { - VERSION_1 = 1; - } + enum FileVersion { VERSION_1 = 1; } optional FileType type = 1; optional FileVersion version = 2 [default = VERSION_1]; diff --git a/protos/license_protocol.proto b/protos/license_protocol.proto index eb2ae73..07d6bd1 100644 --- a/protos/license_protocol.proto +++ b/protos/license_protocol.proto @@ -315,12 +315,12 @@ message LicenseRequest { optional bytes request_id = 4; } - //oneof content_id_variant { - // Exactly one of these must be present. - optional CencDeprecated cenc_id_deprecated = 1; - optional WebmDeprecated webm_id_deprecated = 2; - optional ExistingLicense existing_license = 3; - optional InitData init_data = 4; + // oneof content_id_variant { + // Exactly one of these must be present. + optional CencDeprecated cenc_id_deprecated = 1; + optional WebmDeprecated webm_id_deprecated = 2; + optional ExistingLicense existing_license = 3; + optional InitData init_data = 4; //} } @@ -513,8 +513,8 @@ message GroupKeys { // in the case of X509 certificates, the certificate authority to use. message ProvisioningOptions { enum CertificateType { - WIDEVINE_DRM = 0; // Default. The original certificate type. - X509 = 1; // X.509 certificate. + WIDEVINE_DRM = 0; // Default. The original certificate type. + X509 = 1; // X.509 certificate. WIDEVINE_KEYBOX = 2; } @@ -968,8 +968,8 @@ message WidevinePsshData { // Group identifiers for all groups to which the content belongs. This can // be used to deliver licenses to unlock multiple titles / channels. - // Optional, and may only be present in ENTITLEMENT and ENTITLED_KEY PSSHs, and - // not in conjunction with key_ids. + // Optional, and may only be present in ENTITLEMENT and ENTITLED_KEY PSSHs, + // and not in conjunction with key_ids. repeated bytes group_ids = 13; // Copy/copies of the content key used to decrypt the media stream in which diff --git a/tests/src/cas_license_test.cpp b/tests/src/cas_license_test.cpp index fb95f91..7a3b93e 100644 --- a/tests/src/cas_license_test.cpp +++ b/tests/src/cas_license_test.cpp @@ -2,13 +2,14 @@ // source code may only be used and distributed under the Widevine Master // License Agreement. +#include "cas_license.h" + #include #include #include #include -#include "cas_license.h" #include "cas_status.h" #include "cas_util.h" #include "crypto_key.h" @@ -348,6 +349,10 @@ TEST_F(CasLicenseTest, GenerateEntitlementRequest) { EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) .WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); + + EXPECT_CALL(*strict_mock_, APIVersion(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, GenerateNonce(_)) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); EXPECT_CALL(*strict_mock_, @@ -373,6 +378,9 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) { EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) .WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); + EXPECT_CALL(*strict_mock_, APIVersion(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, GenerateNonce(_)) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); EXPECT_CALL(*strict_mock_, diff --git a/tests/src/cas_session_map_test.cpp b/tests/src/cas_session_map_test.cpp index cf296d6..33afae5 100644 --- a/tests/src/cas_session_map_test.cpp +++ b/tests/src/cas_session_map_test.cpp @@ -1,5 +1,6 @@ #include #include + #include #include "widevine_cas_session.h" diff --git a/tests/src/crypto_session_test.cpp b/tests/src/crypto_session_test.cpp index d12dd18..81f1549 100644 --- a/tests/src/crypto_session_test.cpp +++ b/tests/src/crypto_session_test.cpp @@ -90,10 +90,10 @@ MATCHER_P2(IsValidInputStreamParamsWithCollectSizeT, container, expected, "") { } MATCHER_P(IsValidOutBytes, expected, "") { - if(nullptr == arg) { + if (nullptr == arg) { return false; } - if (*arg != *expected){ + if (*arg != *expected) { return false; } return true; @@ -189,6 +189,7 @@ class MockedOEMCrypto : public wvcas::OEMCryptoInterface { OEMCryptoResult(OEMCrypto_SESSION)); MOCK_CONST_METHOD0(OEMCrypto_GetProvisioningMethod, OEMCrypto_ProvisioningMethod()); + MOCK_CONST_METHOD0(OEMCrypto_APIVersion, uint32_t()); MOCK_CONST_METHOD2(OEMCrypto_GetKeyData, OEMCryptoResult(uint8_t* keyData, size_t* keyDataLength)); MOCK_CONST_METHOD0(OEMCrypto_SupportedCertificates, uint32_t()); @@ -458,6 +459,36 @@ TEST_F(CryptoSessionTest, GetKeyData) { EXPECT_EQ(size_t(0x4321), size); } +TEST_F(CryptoSessionTest, GetApiVersion) { + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + uint32_t api_version = 0; + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_APIVersion()) + .WillOnce(Return(16)) + .WillOnce(Return(15)) + .WillOnce(Return(14)); + + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.APIVersion(&api_version).status_code()); + EXPECT_EQ(api_version, 16); + + api_version = 0; + EXPECT_EQ(wvcas::CasStatusCode::kOEMCryptoVersionMismatch, + crypto_session.APIVersion(&api_version).status_code()); + EXPECT_EQ(api_version, 0); + + EXPECT_EQ(wvcas::CasStatusCode::kOEMCryptoVersionMismatch, + crypto_session.APIVersion(&api_version).status_code()); + EXPECT_EQ(api_version, 0); +} + TEST_F(CryptoSessionTest, GetSupportedCertificates) { EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) .WillOnce( diff --git a/tests/src/ecm_parser_test.cpp b/tests/src/ecm_parser_test.cpp index 186b716..4f5259e 100644 --- a/tests/src/ecm_parser_test.cpp +++ b/tests/src/ecm_parser_test.cpp @@ -7,59 +7,68 @@ #include #include #include + #include +#include namespace { -static constexpr int kCaIdSizeBytes = 2; -static constexpr int kModeSizeBytes = 1; -static constexpr int kVersionSizeBytes = 1; -static constexpr int kIVFlagsSizeBytes = 1; -static constexpr int kEntitlementKeyIDSizeBytes = 16; -static constexpr int kContentKeyIDSizeBytes = 16; -static constexpr int kContentKeyDataSize = 16; -static constexpr int kWrappedKeyIVSizeBytes = 16; +constexpr int kCasIdSizeBytes = 2; +constexpr int kModeSizeBytes = 1; +constexpr int kVersionSizeBytes = 1; +constexpr int kIVFlagsSizeBytes = 1; +constexpr int kEntitlementKeyIDSizeBytes = 16; +constexpr int kContentKeyIDSizeBytes = 16; +constexpr int kContentKeyDataSize = 16; +constexpr int kWrappedKeyIVSizeBytes = 16; -static constexpr int kEcmDescriptorSizeBytes = - kCaIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes; +constexpr int kEcmDescriptorSizeBytes = + kCasIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes; -static constexpr int kECMVersion = 2; +constexpr int kECMVersion = 2; // The cipher mode flags field in the ECM V2 is 4 bits. -static constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1); -static constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1); -static constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1); -static constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1); -static constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1); -static constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1); -static constexpr uint8_t kRotationFlag = (0x1 << 0); -static constexpr uint8_t kContentIVSizeFlag = (0x1 << 6); +constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1); +constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1); +constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1); +constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1); +constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1); +constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1); +constexpr uint8_t kRotationFlag = (0x1 << 0); +constexpr uint8_t kContentIVSizeFlag = (0x1 << 6); -static constexpr uint8_t kEntitlementKeyIDFill = '1'; -static constexpr uint8_t kEvenContentKeyIDFill = '2'; -static constexpr uint8_t kEvenContentKeyDataFill = '3'; -static constexpr uint8_t kEvenWrappedKeyIVFill = '4'; -static constexpr uint8_t kEvenContentKeyIVFill = '5'; -static constexpr uint8_t kOddContentKeyIDFill = '6'; -static constexpr uint8_t kOddContentKeyDataFill = '7'; -static constexpr uint8_t kOddWrappedKeyIVFill = '8'; -static constexpr uint8_t kOddContentKeyIVFill = '9'; +constexpr uint8_t kEntitlementKeyIDFill = '1'; +constexpr uint8_t kEvenContentKeyIDFill = '2'; +constexpr uint8_t kEvenContentKeyDataFill = '3'; +constexpr uint8_t kEvenWrappedKeyIVFill = '4'; +constexpr uint8_t kEvenContentKeyIVFill = '5'; +constexpr uint8_t kOddContentKeyIDFill = '6'; +constexpr uint8_t kOddContentKeyDataFill = '7'; +constexpr uint8_t kOddWrappedKeyIVFill = '8'; +constexpr uint8_t kOddContentKeyIVFill = '9'; -static constexpr size_t kMaxEcmSizeBytes = 184; +constexpr size_t kMaxEcmSizeBytes = 184; -static constexpr uint16_t kSectionHeader1 = 0x80; -static constexpr uint16_t kSectionHeader2 = 0x81; -static constexpr size_t kSectionHeaderSize = 4; +constexpr uint16_t kSectionHeader1 = 0x80; +constexpr uint16_t kSectionHeader2 = 0x81; +constexpr uint8_t kPointerFieldZero = 0x00; + +constexpr uint16_t kWidevineCasId = 0x4AD4; +// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive). +constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0; +constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9; } // namespace class EcmParserTest : public testing::Test { - public: - void SetUp() { BuildEcm(); } + protected: + void SetUp() { + BuildEcm(kWidevineCasId, /*with_rotation=*/true, /*content_iv_flag=*/false); + } size_t ContentKeyIVSize(bool content_iv_flag); size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false); - std::vector ecm_data_; + void BuildEcm(uint16_t cas_id, bool with_rotation, bool content_iv_flag); - private: - void BuildEcm(bool with_rotation = true, bool content_iv_flag = false); + std::vector ecm_data_; + std::unique_ptr parser_; }; size_t EcmParserTest::ContentKeyIVSize(bool content_iv_flag) { @@ -76,12 +85,13 @@ size_t EcmParserTest::CalculateEcmSize(bool with_rotation, return kEcmDescriptorSizeBytes + ecm_key_data_size * (with_rotation ? 2 : 1); } -void EcmParserTest::BuildEcm(bool with_rotation, bool content_iv_flag) { +void EcmParserTest::BuildEcm(uint16_t cas_id, bool with_rotation, + bool content_iv_flag) { ecm_data_.clear(); ecm_data_.reserve(CalculateEcmSize(with_rotation, content_iv_flag)); - ecm_data_.resize(kCaIdSizeBytes, 0); - ecm_data_[0] = 0x4A; - ecm_data_[1] = 0xD4; + ecm_data_.resize(kCasIdSizeBytes, 0); + ecm_data_[0] = cas_id >> 8; + ecm_data_[1] = cas_id & 0xff; ecm_data_.resize(ecm_data_.size() + kVersionSizeBytes, kECMVersion); ecm_data_.resize(ecm_data_.size() + kModeSizeBytes, kAESCBCCryptoModeFlagsVal); @@ -121,172 +131,207 @@ void EcmParserTest::BuildEcm(bool with_rotation, bool content_iv_flag) { TEST_F(EcmParserTest, FieldsWithoutKeyRotation) { bool content_key_iv_16b = false; ecm_data_.resize(CalculateEcmSize(false, content_key_iv_16b)); - std::unique_ptr parser; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - ASSERT_FALSE(parser->rotation_enabled()); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + ASSERT_FALSE(parser_->rotation_enabled()); std::vector test_data; test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill); EXPECT_EQ(test_data, - parser->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot)); + parser_->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill); - EXPECT_EQ(test_data, parser->content_key_id(wvcas::KeySlotId::kEvenKeySlot)); + EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill); EXPECT_EQ(test_data, - parser->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot)); + parser_->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill); - EXPECT_EQ(test_data, parser->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot)); + EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill); - EXPECT_EQ(test_data, parser->content_iv(wvcas::KeySlotId::kEvenKeySlot)); + EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kEvenKeySlot)); - EXPECT_TRUE(parser->content_key_id(wvcas::KeySlotId::kOddKeySlot).empty()); - EXPECT_TRUE(parser->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot).empty()); - EXPECT_TRUE(parser->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot).empty()); - EXPECT_TRUE(parser->content_iv(wvcas::KeySlotId::kOddKeySlot).empty()); + EXPECT_TRUE(parser_->content_key_id(wvcas::KeySlotId::kOddKeySlot).empty()); + EXPECT_TRUE(parser_->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot).empty()); + EXPECT_TRUE(parser_->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot).empty()); + EXPECT_TRUE(parser_->content_iv(wvcas::KeySlotId::kOddKeySlot).empty()); } TEST_F(EcmParserTest, FieldsWithKeyRotation) { ecm_data_[3] |= kRotationFlag; - std::unique_ptr parser; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - ASSERT_TRUE(parser->rotation_enabled()); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + ASSERT_TRUE(parser_->rotation_enabled()); std::vector test_data; test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill); EXPECT_EQ(test_data, - parser->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot)); + parser_->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill); - EXPECT_EQ(test_data, parser->content_key_id(wvcas::KeySlotId::kEvenKeySlot)); + EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill); EXPECT_EQ(test_data, - parser->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot)); + parser_->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill); - EXPECT_EQ(test_data, parser->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot)); + EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); bool content_key_iv_16b = false; test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill); - EXPECT_EQ(test_data, parser->content_iv(wvcas::KeySlotId::kEvenKeySlot)); + EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kEvenKeySlot)); test_data.clear(); test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyIDFill); - EXPECT_EQ(test_data, parser->content_key_id(wvcas::KeySlotId::kOddKeySlot)); + EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kOddKeySlot)); test_data.clear(); test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyDataFill); - EXPECT_EQ(test_data, parser->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot)); + EXPECT_EQ(test_data, + parser_->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot)); test_data.clear(); test_data.resize(kWrappedKeyIVSizeBytes, kOddWrappedKeyIVFill); - EXPECT_EQ(test_data, parser->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot)); + EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot)); test_data.clear(); test_data.resize(ContentKeyIVSize(content_key_iv_16b), kOddContentKeyIVFill); - EXPECT_EQ(test_data, parser->content_iv(wvcas::KeySlotId::kOddKeySlot)); + EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kOddKeySlot)); } TEST_F(EcmParserTest, create) { - std::unique_ptr parser; - EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); ecm_data_.resize(4); - EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, &parser_)); ecm_data_.resize(4 + CalculateEcmSize(false)); - EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); ecm_data_.resize(kMaxEcmSizeBytes); - EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); ecm_data_.resize(CalculateEcmSize(true)); - EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, nullptr)); ecm_data_.resize(CalculateEcmSize(true)); - EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, nullptr)); } TEST_F(EcmParserTest, crypto_mode) { - std::unique_ptr parser; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesCBC); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCBC); ecm_data_[3] = kAESCTRCryptoModeFlagsVal; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesCTR); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCTR); ecm_data_[3] = kDvbCsa2CryptoModeFlagsVal; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kDvbCsa2); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa2); ecm_data_[3] = kDvbCsa3CryptoModeFlagsVal; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kDvbCsa3); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa3); ecm_data_[3] = kDvbOFBCryptoModeFlagsVal; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesOFB); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesOFB); ecm_data_[3] = kDvbSCTECryptoModeFlagsVal; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesSCTE); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesSCTE); } TEST_F(EcmParserTest, ContentKeyIVSizes) { - std::unique_ptr parser; bool with_rotation = true; bool iv_flag = false; ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag)); - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(parser->content_iv_size(), ContentKeyIVSize(iv_flag)); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag)); iv_flag = true; ecm_data_[4] = kContentIVSizeFlag; ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag)); - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(parser->content_iv_size(), ContentKeyIVSize(iv_flag)); -} - -TEST_F(EcmParserTest, EcmWithSectionHeader) { - std::unique_ptr parser; - std::vector section_header; - section_header.resize(kSectionHeaderSize); - *reinterpret_cast(section_header.data()) = htons(kSectionHeader1); - // If ECM is prepended with section header, parsing must still work. - ecm_data_.insert(ecm_data_.begin(), section_header.begin(), - section_header.end()); - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - - *((uint16_t*)ecm_data_.data()) = htons(kSectionHeader2); - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - - // Change header size, parsing should still work - ecm_data_.erase(ecm_data_.begin()); - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag)); } TEST_F(EcmParserTest, AgeRestriction) { - std::unique_ptr parser; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(0, parser->age_restriction()); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(0, parser_->age_restriction()); uint8_t age_restriction = 16; ecm_data_[4] |= age_restriction << 1; - ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); - EXPECT_EQ(age_restriction, parser->age_restriction()); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); + EXPECT_EQ(age_restriction, parser_->age_restriction()); } + +// Verifies CAS ID returned by the parser must be expected ones. +class EcmParserCasIdTest + : public EcmParserTest, + public ::testing::WithParamInterface<::testing::tuple> { + protected: + void SetUp() override { + const uint16_t cas_id = ::testing::get<0>(GetParam()); + BuildEcm(cas_id, /*with_rotation=*/true, /*content_iv_flag=*/false); + } +}; + +TEST_P(EcmParserCasIdTest, ValidateCasIds) { + bool expected_result = ::testing::get<1>(GetParam()); + ASSERT_EQ(wvcas::EcmParser::create(ecm_data_, &parser_), expected_result); +} + +INSTANTIATE_TEST_SUITE_P(EcmWithLegacyCasId, EcmParserCasIdTest, + ::testing::Values(std::make_tuple(kWidevineCasId, + true))); +INSTANTIATE_TEST_SUITE_P( + EcmWithNewCasId, EcmParserCasIdTest, + ::testing::Combine( + ::testing::Range(static_cast(kWidevineNewCasIdLowerBound), + static_cast(kWidevineNewCasIdUpperBound + + 1)), + ::testing::Values(true))); + +INSTANTIATE_TEST_SUITE_P( + EcmWithInvalidCasId, EcmParserCasIdTest, + ::testing::Combine(::testing::Values(0, kWidevineCasId - 1, + kWidevineCasId + 1, + kWidevineNewCasIdLowerBound - 1, + kWidevineNewCasIdUpperBound + 1), + ::testing::Values(false))); + +// Verifies Section header and pointer field may be prepended to ECM. +class EcmParserSectionHeaderTest + : public EcmParserTest, + public ::testing::WithParamInterface {}; + +TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderOnly) { + const std::vector section_header = {GetParam(), 0, 0}; + ecm_data_.insert(ecm_data_.begin(), section_header.begin(), + section_header.end()); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); +} + +TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderAndPointerField) { + const std::vector section_header = {kPointerFieldZero, GetParam(), 0, + 0}; + ecm_data_.insert(ecm_data_.begin(), section_header.begin(), + section_header.end()); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_)); +} + +INSTANTIATE_TEST_SUITE_P(EcmWithSectionHeader, EcmParserSectionHeaderTest, + ::testing::Values(kSectionHeader1, kSectionHeader2)); diff --git a/tests/src/license_key_status_test.cpp b/tests/src/license_key_status_test.cpp index b742a1d..8a5423e 100644 --- a/tests/src/license_key_status_test.cpp +++ b/tests/src/license_key_status_test.cpp @@ -2,11 +2,14 @@ // source code may only be used and distributed under the Widevine Master // License Agreement. +#include "license_key_status.h" + #include #include + #include + #include "cas_types.h" -#include "license_key_status.h" namespace wvcas { diff --git a/tests/src/mediacas_integration_test.cpp b/tests/src/mediacas_integration_test.cpp index 4374e36..4a1da8f 100644 --- a/tests/src/mediacas_integration_test.cpp +++ b/tests/src/mediacas_integration_test.cpp @@ -27,20 +27,30 @@ TEST(IntegrationTests, TestCasFactoryCreation) { EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasFactoryCreation")); } -TEST(IntegrationTests, TestDescramblerFactoryCreation) { - EXPECT_EQ(kIntegrationTestPassed, - RunNamedTest("TestDescramblerFactoryCreation")); -} - TEST(IntegrationTests, TestCreateCasPlugin) { EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCreateCasPlugin")); } +TEST(IntegrationTests, TestCreateCasPluginWithNewCasIds) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCreateCasPluginWithNewCasIds")); +} + +TEST(IntegrationTests, TestCreateCasPluginWithInvalidCasIds) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCreateCasPluginWithInvalidCasIds")); +} + TEST(IntegrationTests, TestCreateCasPluginWithSessionEvent) { EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCreateCasPluginWithSessionEvent")); } +TEST(IntegrationTests, TestCreateCasPluginWithSessionEventWithNewCasIds) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCreateCasPluginWithSessionEventWithNewCasIds")); +} + TEST(IntegrationTests, TestCasPluginEventPassing) { EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing")); } @@ -63,11 +73,6 @@ TEST(IntegrationTests, TestCasEmmRequestWithPrivateData) { RunNamedTest("TestCasEmmRequestWithPrivateData")); } -TEST(IntegrationTests, TestCasPrivateDataWithGroupLicense) { - EXPECT_EQ(kIntegrationTestPassed, - RunNamedTest("TestCasPrivateDataWithGroupLicense")); -} - TEST(IntegrationTests, TestCasWithOfflineEMM) { EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasWithOfflineEMM")); } @@ -81,41 +86,17 @@ TEST(IntegrationTests, TestCasCanNotStoreOfflineEMM) { RunNamedTest("TestCasCanNotStoreOfflineEMM")); } -TEST(IntegrationTests, TestDescrambler) { - EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestDescrambler")); -} - TEST(IntegrationTests, TestSession) { EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSession")); } -TEST(IntegrationTests, TestPESDecrypt) { - EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPESDecrypt")); -} - -TEST(IntegrationTests, TestConcatentatedPESDecrypt) { - EXPECT_EQ(kIntegrationTestPassed, - RunNamedTest("TestConcatentatedPESDecrypt")); -} - -TEST(IntegrationTests, TestPlaybackDecrypt) { - EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPlaybackDecrypt")); -} - TEST(IntegrationTests, TestCasRenewal) { EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasRenewal")); } TEST(IntegrationTests, TestRestoreRenewalAndExpiredLicense) { - EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestRestoreRenewalAndExpiredLicense")); -} - -TEST(IntegrationTests, TestPesHeaderDecrypt) { - EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPesHeaderDecrypt")); -} - -TEST(IntegrationTests, TestPesHeaderDecryptInTSPacket) { - EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPesHeaderDecryptInTSPacket")); + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestRestoreRenewalAndExpiredLicense")); } TEST(IntegrationTests, TestLicenseExpiration) { @@ -134,10 +115,3 @@ TEST(IntegrationTests, TestSessionEventPassing) { EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSessionEventPassing")); } -TEST(IntegrationTests, TestTSDecrypt) { - EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestTSDecrypt")); -} - -TEST(IntegrationTests, TestTSDecryptWithKeyRotation) { - EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestTSDecryptWithKeyRotation")); -} \ No newline at end of file diff --git a/tests/src/mock_crypto_session.h b/tests/src/mock_crypto_session.h index 428c1ff..c2db725 100644 --- a/tests/src/mock_crypto_session.h +++ b/tests/src/mock_crypto_session.h @@ -22,6 +22,7 @@ class MockCryptoSession : public wvcas::CryptoSession { MOCK_METHOD2(GetKeyData, wvcas::CasStatus(uint8_t* keyData, size_t* keyDataLength)); MOCK_METHOD0(supported_certificates, wvcas::SupportedCertificates()); + MOCK_METHOD1(APIVersion, wvcas::CasStatus(uint32_t* api_version)); MOCK_METHOD1(GenerateNonce, wvcas::CasStatus(uint32_t* nonce)); MOCK_METHOD4(GenerateDerivedKeys, wvcas::CasStatus(const uint8_t* mac_key_context, diff --git a/tests/src/policy_engine_test.cpp b/tests/src/policy_engine_test.cpp index 5103906..1d7b677 100644 --- a/tests/src/policy_engine_test.cpp +++ b/tests/src/policy_engine_test.cpp @@ -296,7 +296,8 @@ TEST_F(PolicyEngineTest, RenewalEvents) { video_widevine::License_Policy* policy = license_.mutable_policy(); policy->set_license_duration_seconds(kLowDuration); policy->set_can_renew(true); - policy->set_renewal_delay_seconds(kLicenseRenewalPeriod);; + policy->set_renewal_delay_seconds(kLicenseRenewalPeriod); + ; policy->set_renewal_retry_interval_seconds(kLicenseRenewalRetryInterval); { @@ -344,8 +345,7 @@ TEST_F(PolicyEngineTest, RenewalEvents) { TEST_F(PolicyEngineTest, RenewalUrl) { EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_)); EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()); - EXPECT_CALL(*policy_engine_.license_keys_, - ApplyStatusChange(_, NotNull())); + EXPECT_CALL(*policy_engine_.license_keys_, ApplyStatusChange(_, NotNull())); EXPECT_CALL(event_listener_, OnExpirationUpdate(_)); EXPECT_CALL(event_listener_, OnNewRenewalServerUrl(::testing::StrEq(kRenewalServerUrl))); diff --git a/tests/src/timer_test.cpp b/tests/src/timer_test.cpp index 43e9a5a..0ea6266 100644 --- a/tests/src/timer_test.cpp +++ b/tests/src/timer_test.cpp @@ -2,11 +2,12 @@ // source code may only be used and distributed under the Widevine Master // License Agreement. +#include "timer.h" + #include #include #include "clock.h" -#include "timer.h" class TimerTest : public wvutil::TimerHandler, public ::testing::Test { public: diff --git a/tests/src/widevine_cas_api_test.cpp b/tests/src/widevine_cas_api_test.cpp index 73a9ea5..00ea9e0 100644 --- a/tests/src/widevine_cas_api_test.cpp +++ b/tests/src/widevine_cas_api_test.cpp @@ -2,17 +2,19 @@ // source code may only be used and distributed under the Widevine Master // License Agreement. +#include "widevine_cas_api.h" + #include #include -#include #include -#include "cas_util.h" +#include + #include "cas_license.h" +#include "cas_util.h" #include "ecm_parser.h" #include "mock_crypto_session.h" #include "string_conversions.h" -#include "widevine_cas_api.h" #include "widevine_cas_session_map.h" using ::testing::_; @@ -22,6 +24,22 @@ using ::testing::Return; using ::testing::SetArgPointee; using ::testing::StrictMock; +namespace { +constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/"; +constexpr char kLicenseFileNameSuffix[] = ".lic"; + +// Generate mocked license filename. +std::string GenerateTestLicenseFileName(const std::string& mocked_file_name) { + std::string hash; + hash.resize(SHA256_DIGEST_LENGTH); + const auto* input = + reinterpret_cast(mocked_file_name.data()); + auto* output = reinterpret_cast(&hash[0]); + SHA256(input, mocked_file_name.size(), output); + return kBasePathPrefix + wvutil::b2a_hex(hash) + kLicenseFileNameSuffix; +} +} // namespace + typedef StrictMock StrictMockCryptoSession; class MockLicense : public wvcas::CasLicense { @@ -224,18 +242,20 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) { cas_api.initialize(nullptr).status_code()); // Invalid parameter. - std::string request, init_data; - EXPECT_NE( - wvcas::CasStatusCode::kNoError, - cas_api.generateEntitlementRequest(init_data, nullptr).status_code()); + std::string request, init_data, license_id; + EXPECT_EQ(wvcas::CasStatusCode::kInvalidParameter, + cas_api.generateEntitlementRequest(init_data, nullptr, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); // GenerateEntitlementRequest returns an error. EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) .WillRepeatedly(Return(wvcas::CasStatus( wvcas::CasStatusCode::kCasLicenseError, "forced failure"))); - EXPECT_NE( - wvcas::CasStatusCode::kNoError, - cas_api.generateEntitlementRequest(init_data, &request).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); // HandleStoredLicense returns an error. // Call to Open will return a unique_ptr, freeing this mock_file object. @@ -252,13 +272,14 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) { EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _)) .WillRepeatedly(Return(wvcas::CasStatus( wvcas::CasStatusCode::kCasLicenseError, "forced failure"))); - EXPECT_NE( - wvcas::CasStatusCode::kNoError, - cas_api.generateEntitlementRequest(init_data, &request).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); mock_file = new MockFile(); - EXPECT_CALL(*cas_api.file_system_, - DoOpen(_, _)).WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); // For expired license file, remove it successfully // and return CasLicenseError. @@ -267,43 +288,54 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) { EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(true)); EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true)); - EXPECT_EQ( - wvcas::CasStatusCode::kCasLicenseError, - cas_api.generateEntitlementRequest(init_data, &request).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); // Unable to remove the expired license file, return InvalidLicenseFile error. mock_file = new MockFile(); - EXPECT_CALL(*cas_api.file_system_, - DoOpen(_, _)).WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); // Unable to remove the expired license file, return InvalidLicenseFile error EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillOnce(Return(false)); - EXPECT_EQ( - wvcas::CasStatusCode::kInvalidLicenseFile, - cas_api.generateEntitlementRequest(init_data, &request).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kInvalidLicenseFile, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); // For stored license file not expired, return valid response. mock_file = new MockFile(); - EXPECT_CALL(*cas_api.file_system_, - DoOpen(_, _)).WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); // For stored license file not expired, return valid response EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); - EXPECT_EQ( - wvcas::CasStatusCode::kNoError, - cas_api.generateEntitlementRequest(init_data, &request).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + // For existing license, license_id should be returned. + std::string full_filename = + GenerateTestLicenseFileName(/*mocked_file_name=*/""); + EXPECT_EQ(full_filename.substr( + 0, full_filename.size() - strlen(kLicenseFileNameSuffix)), + license_id); // Valid response for new request. mock_file = new MockFile(); - EXPECT_CALL(*cas_api.file_system_, - DoOpen(_, _)).WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); // Valid response for new request + license_id.clear(); EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(false)); EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); - EXPECT_EQ( - wvcas::CasStatusCode::kNoError, - cas_api.generateEntitlementRequest(init_data, &request).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + // For the first time license request, the license_id will be returned empty. + EXPECT_TRUE(license_id.empty()); } TEST_F(WidevineCasTest, GenerateLicenseRenewal) { @@ -343,13 +375,15 @@ TEST_F(WidevineCasTest, EntitlementRenewalResponse) { // Empty response. std::string init_data; - EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, - cas_api.handleEntitlementRenewalResponse("", init_data).status_code()); + EXPECT_EQ( + wvcas::CasStatusCode::kCasLicenseError, + cas_api.handleEntitlementRenewalResponse("", init_data).status_code()); // Valid. EXPECT_CALL(*cas_api.license_, HandleEntitlementRenewalResponse(_, _)); EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_api.handleEntitlementRenewalResponse("response", init_data).status_code()); + cas_api.handleEntitlementRenewalResponse("response", init_data) + .status_code()); } TEST_F(WidevineCasTest, RemoveExpiredLicenseInTimerEvent) { @@ -370,18 +404,20 @@ TEST_F(WidevineCasTest, RemoveExpiredLicenseInTimerEvent) { EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); - std::string request, init_data; - EXPECT_EQ( - wvcas::CasStatusCode::kNoError, - cas_api.generateEntitlementRequest(init_data, &request).status_code()); + std::string request, init_data, license_id; + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); // Valid: License is expired then start to remove. + EXPECT_TRUE(license_id.empty()); EXPECT_CALL(*cas_api.license_, IsExpired()).WillOnce(Return(true)); EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true)); EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillOnce(Return(true)); cas_api.OnTimerEvent(); // Valid: License is expired but now it does not exist on file system. + EXPECT_TRUE(license_id.empty()); EXPECT_CALL(*cas_api.license_, IsExpired()).WillOnce(Return(true)); EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false)); EXPECT_CALL(*cas_api.file_system_, Remove(_)).Times(0); @@ -462,29 +498,36 @@ TEST_P(WidevineCasTest, ECMProcessing) { .Times(expected_begin_decryption_calls); if (test_stored_license) { - std::string request; - std::string file("license_file"); + std::string request, license_id; + std::string file_data("license_file"); EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(true)); EXPECT_CALL(*cas_api.file_system_, FileSize(_)) - .WillOnce(Return(file.size())); + .WillOnce(Return(file_data.size())); auto* file_handle = new NiceMock; EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) .WillOnce(Return(file_handle)); - EXPECT_CALL(*file_handle, Read(_, _)).WillOnce(Return(file.size())); + EXPECT_CALL(*file_handle, Read(_, _)).WillOnce(Return(file_data.size())); EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _)); - EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_api.generateEntitlementRequest("init_data", &request) - .status_code()); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest("init_data", &request, license_id) + .status_code()); + // For existing license, license_id should be returned. + std::string full_filename = + GenerateTestLicenseFileName(/*mocked_file_name=*/""); + EXPECT_EQ(full_filename.substr( + 0, full_filename.size() - strlen(kLicenseFileNameSuffix)), + license_id); } else { // Empty response. std::string init_data; EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _)) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); - EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_api.handleEntitlementResponse("response", init_data) - .status_code()); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + cas_api.handleEntitlementResponse("response", init_data).status_code()); } EXPECT_EQ(wvcas::CasStatusCode::kNoError, @@ -506,23 +549,25 @@ TEST_F(WidevineCasTest, RemoveLicense) { TestWidevineCas cas_api; EXPECT_CALL(*cas_api.crypto_session_, initialize()) - .WillOnce(Return(wvcas::CasStatus::OkStatus())); + .WillOnce(Return(wvcas::CasStatus::OkStatus())); EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_api.initialize(nullptr).status_code()); + cas_api.initialize(nullptr).status_code()); // mediaId is not initialized. std::string mocked_file_name; EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, - cas_api.RemoveLicense(mocked_file_name).status_code()); + cas_api.RemoveLicense(mocked_file_name).status_code()); // CasMediaId has been initialized. EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false)); EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); - std::string request, init_data; + std::string request, init_data, license_id; EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_api.generateEntitlementRequest(init_data, &request) - .status_code()); + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + // For the first time license request, the license_id will be returned empty. + EXPECT_TRUE(license_id.empty()); // Unable to remove license file, return InvalidLicenseFile error. EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(true)); @@ -535,15 +580,17 @@ TEST_F(WidevineCasTest, RemoveLicense) { MockFile mock_file; size_t mock_filesize = 10; EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true)); - EXPECT_CALL(*cas_api.file_system_, FileSize(_)).WillRepeatedly(Return(mock_filesize)); - EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)).WillRepeatedly(Return(&mock_file)); + EXPECT_CALL(*cas_api.file_system_, FileSize(_)) + .WillRepeatedly(Return(mock_filesize)); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(&mock_file)); EXPECT_CALL(mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); EXPECT_CALL(mock_file, Close()).WillRepeatedly(Return()); EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true)); EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(0); EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_api.RemoveLicense(mocked_file_name).status_code()); + cas_api.RemoveLicense(mocked_file_name).status_code()); // Happy case: remove the in used license file std::string hash; @@ -553,10 +600,8 @@ TEST_F(WidevineCasTest, RemoveLicense) { reinterpret_cast(mocked_file_name.data()); auto* output = reinterpret_cast(&hash[0]); SHA256(input, mocked_file_name.size(), output); - std::string full_file_name = kBasePathPrefix + wvutil::b2a_hex(hash) - + std::string(".lic"); - + std::string full_filename = GenerateTestLicenseFileName(mocked_file_name); EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(1); EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_api.RemoveLicense(full_file_name).status_code()); + cas_api.RemoveLicense(full_filename).status_code()); } \ No newline at end of file diff --git a/tests/src/widevine_cas_session_test.cpp b/tests/src/widevine_cas_session_test.cpp index 31ae621..babded9 100644 --- a/tests/src/widevine_cas_session_test.cpp +++ b/tests/src/widevine_cas_session_test.cpp @@ -2,6 +2,8 @@ // source code may only be used and distributed under the Widevine Master // License Agreement. +#include "widevine_cas_session.h" + #include #include @@ -12,7 +14,6 @@ #include "cas_util.h" #include "mock_crypto_session.h" #include "string_conversions.h" -#include "widevine_cas_session.h" using ::testing::_; using ::testing::DoAll; @@ -202,8 +203,8 @@ std::unique_ptr TestCasSession::getEcmParser( std::unique_ptr> mock_ecm_parser( new NiceMock); ON_CALL(*mock_ecm_parser, sequence_count()).WillByDefault(Return(0)); - ON_CALL(*mock_ecm_parser, age_restriction()).WillByDefault(Return( - age_restriction_)); + ON_CALL(*mock_ecm_parser, age_restriction()) + .WillByDefault(Return(age_restriction_)); ON_CALL(*mock_ecm_parser, crypto_mode()) .WillByDefault(Return(wvcas::CryptoMode::kAesCTR)); ON_CALL(*mock_ecm_parser, rotation_enabled()).WillByDefault(Return(true)); @@ -234,9 +235,8 @@ TEST_F(CasSessionTest, processEcm) { EXPECT_EQ(session_id, kEntitledKeySessionId); wvcas::CasEcm ecm(184); - EXPECT_CALL(*mock, - LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(), - IsValidKeyOddSlotData())); + EXPECT_CALL(*mock, LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(), + IsValidKeyOddSlotData())); session.processEcm(ecm, 0); EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id)); } @@ -256,7 +256,7 @@ TEST_F(CasSessionTest, parentalControl) { EXPECT_CALL(*mock, LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(), IsValidKeyOddSlotData())); wvcas::CasEcm ecm(184); - session.set_age_restriction(0); // No restriction. + session.set_age_restriction(0); // No restriction. // Different Ecm to make sure processEcm() processes this ecm. std::generate(ecm.begin(), ecm.end(), std::rand); ASSERT_EQ(wvcas::CasStatusCode::kNoError, diff --git a/tests/src/wv_cas_test_main.cpp b/tests/src/wv_cas_test_main.cpp index e1597f6..976aa21 100644 --- a/tests/src/wv_cas_test_main.cpp +++ b/tests/src/wv_cas_test_main.cpp @@ -3,6 +3,7 @@ // License Agreement. #include + #include #include "OEMCryptoCAS.h" diff --git a/wvutil/include/timer.h b/wvutil/include/timer.h index f414455..a885d70 100644 --- a/wvutil/include/timer.h +++ b/wvutil/include/timer.h @@ -20,8 +20,8 @@ namespace wvutil { class TimerHandler { public: - TimerHandler() {}; - virtual ~TimerHandler() {}; + TimerHandler(){}; + virtual ~TimerHandler(){}; virtual void OnTimerEvent() = 0; }; @@ -50,6 +50,6 @@ class Timer { CORE_DISALLOW_COPY_AND_ASSIGN(Timer); }; -} // namespace wvcas +} // namespace wvutil #endif // TIMER_H_ diff --git a/wvutil/src/android_properties.cpp b/wvutil/src/android_properties.cpp index b78c8dc..f8d86d8 100644 --- a/wvutil/src/android_properties.cpp +++ b/wvutil/src/android_properties.cpp @@ -73,12 +73,7 @@ bool Properties::GetOEMCryptoPath(std::string* path) { LOGW("Properties::GetOEMCryptoPath: Invalid parameter"); return false; } - char value[PROPERTY_VALUE_MAX]; - constexpr char key[] = "cas.widevine.oemcrypto.path"; - if (property_get(key, value, "libcasoemcrypto.so") <= 0) { - return false; - } - *path = value; + *path = "libcasoemcrypto.so"; return true; }