diff --git a/libwvdrmengine/Android.mk b/libwvdrmengine/Android.mk index c5279bd6..09a983c0 100644 --- a/libwvdrmengine/Android.mk +++ b/libwvdrmengine/Android.mk @@ -125,30 +125,6 @@ LOCAL_EXPORT_C_INCLUDE_DIRS := \ include $(BUILD_STATIC_LIBRARY) -# ----------------------------------------------------------------------------- -# Builds libwidevinehidl_utils.a -# -include $(CLEAR_VARS) - -LOCAL_MODULE := libwidevinehidl_utils -LOCAL_PROPRIETARY_MODULE := true - -LOCAL_MODULE_CLASS := STATIC_LIBRARIES - -LOCAL_C_INCLUDES := \ - vendor/widevine/libwvdrmengine/include_hidl - -LOCAL_SRC_FILES := \ - src_hidl/TypeConvert.cpp - -LOCAL_HEADER_LIBRARIES := \ - libstagefright_headers - -LOCAL_SHARED_LIBRARIES := \ - android.hardware.drm@1.0 - -include $(BUILD_STATIC_LIBRARY) - # ----------------------------------------------------------------------------- # Builds libwvdrmengine.so # @@ -242,11 +218,6 @@ LOCAL_STATIC_LIBRARIES := \ libwvdrmdrmplugin_hidl \ libwvlevel3 \ -# When the GNU linker sees a library, it discards all symbols that it doesn't -# need. libhidl_utils must come after both libwvdrmcryptoplugin and -# libwvdrmdrmplugin. -LOCAL_STATIC_LIBRARIES += libhidl_utils - LOCAL_SHARED_LIBRARIES := \ android.hardware.drm@1.0 \ android.hidl.memory@1.0 \ diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index f57132be..6e687769 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -19,7 +19,8 @@ namespace wvcdm { class CryptoKey; class UsageTableHeader; -typedef std::map CryptoKeyMap; +typedef std::map CryptoKeyMap; +typedef std::map SubLicenseSessionMap; class CryptoSession { public: @@ -167,8 +168,19 @@ class CryptoSession { virtual CdmResponseType ShrinkUsageTableHeader( uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header); virtual CdmResponseType MoveUsageEntry(uint32_t new_entry_number); + virtual bool CreateOldUsageEntry( + uint64_t time_since_license_received, + uint64_t time_since_first_decrypt, + uint64_t time_since_last_decrypt, + UsageDurationStatus status, + const std::string& server_mac_key, + const std::string& client_mac_key, + const std::string& provider_session_token); virtual CdmResponseType CopyOldUsageEntry( const std::string& provider_session_token); + virtual metrics::CryptoMetrics* GetCryptoMetrics() { return metrics_; } + + virtual CdmResponseType AddSubSession(const std::string& sub_session_key_id); private: bool GetProvisioningMethod(CdmClientTokenType* token_type); @@ -231,6 +243,7 @@ class CryptoSession { std::string oem_token_; // Cached OEMCrypto Public Key bool update_usage_table_after_close_session_; CryptoSessionId oec_session_id_; + SubLicenseSessionMap sub_license_oec_sessions_; OEMCryptoBufferType destination_buffer_type_; bool is_destination_buffer_type_valid_; diff --git a/libwvdrmengine/cdm/core/include/initialization_data.h b/libwvdrmengine/cdm/core/include/initialization_data.h index c4edbd06..8a9b42af 100644 --- a/libwvdrmengine/cdm/core/include/initialization_data.h +++ b/libwvdrmengine/cdm/core/include/initialization_data.h @@ -5,6 +5,7 @@ #include +#include "license_protocol.pb.h" #include "wv_cdm_types.h" namespace wvcdm { @@ -27,6 +28,7 @@ class InitializationData { const CdmInitData& data() const { return data_; } std::vector hls_iv() const { return hls_iv_; } CdmHlsMethod hls_method() const { return hls_method_; } + std::vector ExtractEmbeddedKeys() const; private: // Parse a blob of multiple concatenated PSSH atoms to extract the first diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index 5bae53e4..fc7e88e1 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -51,7 +51,7 @@ class CdmLicense { const CdmKeyResponse& license_response, const CdmKeyResponse& license_renewal_response, int64_t playback_start_time, int64_t last_playback_time, - int64_t grace_period_end_time); + int64_t grace_period_end_time, CdmSession* cdm_session); virtual bool RestoreLicenseForRelease(const CdmKeyMessage& license_request, const CdmKeyResponse& license_response); virtual bool IsKeyLoaded(const KeyId& key_id); diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index 37c1e1dc..f5f5296e 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -154,6 +154,9 @@ class PolicyEngine { // expiry time changes. void NotifyExpirationUpdate(int64_t current_time); + // Guard against clock rollbacks + int64_t GetCurrentTime(); + // set_clock() is for testing only. It alters ownership of the // passed-in pointer. void set_clock(Clock* clock); @@ -185,6 +188,9 @@ class PolicyEngine { // calculate the time where renewal retries should occur. int64_t next_renewal_time_; + // to assist in clock rollback checks + int64_t last_recorded_current_time_; + // Used to dispatch CDM events. CdmSessionId session_id_; WvCdmEventListener* event_listener_; diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 0fb8f748..78f3b9ec 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -92,6 +92,8 @@ class UsageTableHeader { virtual bool is_inited() { return is_inited_; } + virtual bool CreateDummyOldUsageEntry(CryptoSession* crypto_session); + // This handle and file system is only to be used when accessing // usage_table_header. Usage entries should use the file system provided // by CdmSession. diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 60923a1b..27997e60 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -21,7 +21,6 @@ typedef std::string CdmKeySetId; typedef std::string RequestId; typedef uint32_t CryptoResult; typedef uint32_t CryptoSessionId; -typedef std::string CryptoKeyId; typedef std::map CdmAppParameterMap; typedef std::map CdmQueryMap; typedef std::vector CdmUsageInfo; diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 20b7deaa..bd2f131c 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -68,8 +68,8 @@ CdmEngine::CdmEngine(FileSystem* file_system, const std::string& spoid) usage_session_(NULL), last_usage_information_update_time_(0) { assert(file_system); - Properties::Init(); if (!seeded_) { + Properties::Init(); srand(clock_.GetCurrentTime()); seeded_ = true; } @@ -1296,17 +1296,31 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) { switch (usage_session_->get_usage_support_type()) { case kUsageEntrySupport: { std::vector usage_data; - if (!handle.RetrieveUsageInfo( - DeviceFiles::GetUsageInfoFileName(app_id), &usage_data)) { - status = RELEASE_ALL_USAGE_INFO_ERROR_4; - } else { - for (size_t k = 0; k < usage_data.size(); ++k) { - CdmResponseType status2 = usage_session_->DeleteUsageEntry( - usage_data[k].usage_entry_number); - if (status == NO_ERROR && status2 != NO_ERROR) - status = status2; + // Retrieve all usage information but delete only one before + // refetching. This is because deleting the usage entry + // might cause other entries to be shifted and information updated. + do { + if (!handle.RetrieveUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), + &usage_data)) { + status = RELEASE_ALL_USAGE_INFO_ERROR_4; + break; } - } + + if (usage_data.empty()) break; + + status = usage_session_->DeleteUsageEntry( + usage_data[0].usage_entry_number); + + if (status != NO_ERROR) break; + + if (!handle.DeleteUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), + usage_data[0].provider_session_token)) { + status = RELEASE_ALL_USAGE_INFO_ERROR_6; + break; + } + } while (status == NO_ERROR && !usage_data.empty()); std::vector provider_session_tokens; if (!handle.DeleteAllUsageInfoForApp( diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index ad9ea150..2da42ab3 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -28,6 +28,7 @@ CdmSession::CdmSession(FileSystem* file_system, metrics::SessionMetrics* metrics) : metrics_(metrics), initialized_(false), + closed_(true), file_handle_(new DeviceFiles(file_system)), license_received_(false), is_offline_(false), @@ -193,6 +194,7 @@ CdmResponseType CdmSession::Init( license_received_ = false; is_initial_decryption_ = true; initialized_ = true; + closed_ = false; return NO_ERROR; } @@ -230,7 +232,8 @@ CdmResponseType CdmSession::RestoreOfflineSession( std::string provider_session_token; if (usage_support_type_ == kUsageEntrySupport) { if (!license_parser_->ExtractProviderSessionToken( - key_response_, &provider_session_token)) { + key_response_, &provider_session_token) || + usage_table_header_ == nullptr) { provider_session_token.clear(); } else { CdmResponseType sts = @@ -252,13 +255,14 @@ CdmResponseType CdmSession::RestoreOfflineSession( } else { if (!license_parser_->RestoreOfflineLicense( key_request_, key_response_, offline_key_renewal_response_, - playback_start_time, last_playback_time, grace_period_end_time)) { + playback_start_time, last_playback_time, grace_period_end_time, + this)) { return RESTORE_OFFLINE_LICENSE_ERROR_2; } } if (usage_support_type_ == kUsageEntrySupport && - !provider_session_token.empty()) { + !provider_session_token.empty() && usage_table_header_ != nullptr) { CdmResponseType sts = usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { @@ -290,9 +294,10 @@ CdmResponseType CdmSession::RestoreUsageSession( usage_entry_number_ = usage_data.usage_entry_number; usage_provider_session_token_ = usage_data.provider_session_token; - if (usage_support_type_ == kUsageEntrySupport) { + if (usage_support_type_ == kUsageEntrySupport && + usage_table_header_ != nullptr) { CdmResponseType sts = usage_table_header_->LoadEntry( - crypto_session_.get(), usage_entry_, usage_entry_number_); + crypto_session_.get(), usage_entry_, usage_entry_number_); if (sts != NO_ERROR) { LOGE("CdmSession::RestoreUsageSession: failed to load usage entry = %d", sts); @@ -304,7 +309,8 @@ CdmResponseType CdmSession::RestoreUsageSession( return RELEASE_LICENSE_ERROR_2; } - if (usage_support_type_ == kUsageEntrySupport) { + if (usage_support_type_ == kUsageEntrySupport && + usage_table_header_ != nullptr) { CdmResponseType sts = usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { @@ -424,7 +430,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { // to be created. CdmResponseType sts; std::string provider_session_token; - if (usage_support_type_ == kUsageEntrySupport) { + if (usage_support_type_ == kUsageEntrySupport && + usage_table_header_ != nullptr) { if (license_parser_->ExtractProviderSessionToken( key_response, &provider_session_token) && !provider_session_token.empty()) { @@ -440,7 +447,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { // Update or delete entry if usage table header+entries are supported if (usage_support_type_ == kUsageEntrySupport && - !provider_session_token.empty()) { + !provider_session_token.empty() && + usage_table_header_ != nullptr) { if (sts != KEY_ADDED) { CdmResponseType sts = usage_table_header_->DeleteEntry(usage_entry_number_, @@ -463,7 +471,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { if (is_offline_ || has_provider_session_token()) { if (has_provider_session_token() && - usage_support_type_ == kUsageEntrySupport) { + usage_support_type_ == kUsageEntrySupport && + usage_table_header_ != nullptr) { usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); } @@ -671,8 +680,7 @@ CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { } CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { - if (usage_support_type_ != kUsageEntrySupport || - !has_provider_session_token()) { + if (usage_support_type_ != kUsageEntrySupport) { LOGE("CdmSession::DeleteUsageEntry: Unexpected usage type supported: %d", usage_support_type_); return INCORRECT_USAGE_SUPPORT_TYPE_1; @@ -881,7 +889,8 @@ CdmResponseType CdmSession::UpdateUsageTableInformation() { CdmResponseType CdmSession::UpdateUsageEntryInformation() { if (usage_support_type_ != kUsageEntrySupport || - !has_provider_session_token()) { + !has_provider_session_token() || + usage_table_header_ == nullptr) { LOGE("CdmSession::UpdateUsageEntryInformation: Unexpected usage type " "supported: %d", usage_support_type_); return INCORRECT_USAGE_SUPPORT_TYPE_2; diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp index 17894a94..f98e9454 100644 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp +++ b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp @@ -280,29 +280,33 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( * Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails. */ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( - FileSystem* file_system, const CdmProvisioningResponse& response, + FileSystem* file_system, const CdmProvisioningResponse& response_message, std::string* cert, std::string* wrapped_key) { - std::string raw_string; - if (!wvcdm::Properties::provisioning_messages_are_binary()) { - // The response is base64 encoded in a JSON wrapper. - // Extract it and decode it. If errors, return an empty string. - ExtractAndDecodeSignedMessage(response, &raw_string); - } else { - raw_string.assign(response); + if (response_message.empty()) { + LOGE("HandleProvisioningResponse: response message is empty."); + return CERT_PROVISIONING_RESPONSE_ERROR_1; } - if (raw_string.empty()) { - LOGE("HandleProvisioningResponse: response message is empty or " - "an invalid JSON/base64 string."); - return CERT_PROVISIONING_RESPONSE_ERROR_1; + std::string response; + if (wvcdm::Properties::provisioning_messages_are_binary()) { + response.assign(response_message); + } else { + // The response is base64 encoded in a JSON wrapper. + // Extract it and decode it. On error return an empty string. + ExtractAndDecodeSignedMessage(response_message, &response); + if (response.empty()) { + LOGE("HandleProvisioningResponse: response message is " + "an invalid JSON/base64 string."); + return CERT_PROVISIONING_RESPONSE_ERROR_1; + } } // Authenticates provisioning response using D1s (server key derived from // the provisioing request's input). Validate provisioning response and // stores private device RSA key and certificate. SignedProvisioningMessage signed_response; - if (!signed_response.ParseFromString(raw_string)) { + if (!signed_response.ParseFromString(response)) { LOGE("HandleProvisioningResponse: fails to parse signed response"); return CERT_PROVISIONING_RESPONSE_ERROR_2; } diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index a580cb96..f0f89afd 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -502,24 +502,25 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { CdmSecurityLevel security_level = GetSecurityLevel(); if (security_level == kSecurityLevelL1 || security_level == kSecurityLevelL3) { - UsageTableHeader* header = security_level == kSecurityLevelL1 ? - usage_table_header_l1_ : usage_table_header_l3_; - if (header == NULL) { - header = new UsageTableHeader(); + UsageTableHeader** header = security_level == kSecurityLevelL1 ? + &usage_table_header_l1_ : &usage_table_header_l3_; + if (*header == NULL) { + *header = new UsageTableHeader(); // Ignore errors since we do not know when a session is opened, // if it is intended to be used for offline/usage session related // or otherwise. - if (!header->Init(security_level, this)) { - delete header; + crypto_lock_.Release(); + bool is_usage_table_header_inited = + (*header)->Init(security_level, this); + crypto_lock_.Acquire(); + if (!is_usage_table_header_inited) { + delete *header; + *header = NULL; usage_table_header_ = NULL; return NO_ERROR; } - if (security_level == kSecurityLevelL1) - usage_table_header_l1_ = header; - else - usage_table_header_l3_ = header; } - usage_table_header_ = header; + usage_table_header_ = *header; } } } @@ -2258,6 +2259,55 @@ CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) { return NO_ERROR; } +bool CryptoSession::CreateOldUsageEntry( + uint64_t time_since_license_received, + uint64_t time_since_first_decrypt, + uint64_t time_since_last_decrypt, + UsageDurationStatus usage_duration_status, + const std::string& server_mac_key, + const std::string& client_mac_key, + const std::string& provider_session_token) { + LOGV("CreateOldUsageEntry: Lock"); + AutoLock auto_lock(crypto_lock_); + + if (server_mac_key.size() < MAC_KEY_SIZE || + client_mac_key.size() < MAC_KEY_SIZE) { + LOGE("CreateOldUsageEntry: Invalid mac key size: server mac key size %d, " + "client mac key size: %d", server_mac_key.size(), + client_mac_key.size()); + return false; + } + + OEMCrypto_Usage_Entry_Status status; + switch (usage_duration_status) { + case kUsageDurationsInvalid: status = kUnused; break; + case kUsageDurationPlaybackNotBegun: status = kInactiveUnused; break; + case kUsageDurationsValid: status = kActive; break; + default: + LOGE("CreateOldUsageEntry: Unrecognized usage entry status: %d", status); + status = kUnused; + return false; + } + + OEMCryptoResult result = + OEMCrypto_CreateOldUsageEntry( + requested_security_level_, time_since_license_received, + time_since_first_decrypt, time_since_last_decrypt, status, + reinterpret_cast( + const_cast(server_mac_key.data())), + reinterpret_cast( + const_cast(client_mac_key.data())), + reinterpret_cast(provider_session_token.data()), + provider_session_token.size()); + + if (result != OEMCrypto_SUCCESS) { + LOGE("CreateOldUsageEntry: OEMCrypto_CreateOldUsageEntry error: %d", + result); + return false; + } + return true; +} + CdmResponseType CryptoSession::CopyOldUsageEntry( const std::string& provider_session_token) { LOGV("CopyOldUsageEntry: id=%ld", (uint32_t)oec_session_id_); @@ -2275,6 +2325,36 @@ CdmResponseType CryptoSession::CopyOldUsageEntry( return NO_ERROR; } +CdmResponseType CryptoSession::AddSubSession( + const std::string& sub_session_key_id) { + size_t exists = sub_license_oec_sessions_.count(sub_session_key_id); + if (exists > 0) { + // TODO(jfore): Should this be an error if the key exists? If so add a new + // error. If not, perhaps this should just print info message. + LOGE("AddSubSession: SubSession already exists for id: %s", + sub_session_key_id.c_str()); + return UNKNOWN_ERROR; + } + + OEMCryptoResult sts; + CryptoSessionId sid; + sts = OEMCrypto_OpenSession(&sid,requested_security_level_); + if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) { + LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", + sts, session_count_, (int)initialized_); + return INSUFFICIENT_CRYPTO_RESOURCES; + } else if (OEMCrypto_SUCCESS != sts) { + LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", + sts, session_count_, (int)initialized_); + return UNKNOWN_ERROR; + } + + // TODO(jfore): Is there some session count metrics that should be add or + // update? + sub_license_oec_sessions_[sub_session_key_id] = sid; + return NO_ERROR; +} + OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm( CdmSigningAlgorithm algorithm) { if (kSigningAlgorithmHmacSha256 == algorithm) { diff --git a/libwvdrmengine/cdm/core/src/initialization_data.cpp b/libwvdrmengine/cdm/core/src/initialization_data.cpp index 5fa64d84..112a2e6b 100644 --- a/libwvdrmengine/cdm/core/src/initialization_data.cpp +++ b/libwvdrmengine/cdm/core/src/initialization_data.cpp @@ -7,7 +7,6 @@ #include "buffer_reader.h" #include "jsmn.h" -#include "license_protocol.pb.h" #include "log.h" #include "properties.h" #include "string_conversions.h" @@ -35,9 +34,9 @@ const int kDefaultNumJsonTokens = 128; namespace wvcdm { // Protobuf generated classes. -using video_widevine::WidevineCencHeader; -using video_widevine::WidevineCencHeader_Algorithm; -using video_widevine::WidevineCencHeader_Algorithm_AESCTR; +using video_widevine::WidevinePsshData; +using video_widevine::WidevinePsshData_Algorithm; +using video_widevine::WidevinePsshData_Algorithm_AESCTR; InitializationData::InitializationData(const std::string& type, const CdmInitData& data) @@ -70,6 +69,22 @@ InitializationData::InitializationData(const std::string& type, } } +// Parse the pssh data and return the embedded key data if it exists. +std::vector +InitializationData::ExtractEmbeddedKeys() const { + std::vector keys; + WidevinePsshData cenc_header; + if (!is_cenc_ || !cenc_header.ParseFromString(data_) || + cenc_header.sub_licenses().size() == 0) + return keys; + + keys.reserve(cenc_header.sub_licenses().size()); + for (int i = 0; i < cenc_header.sub_licenses().size(); ++i) { + keys.push_back(cenc_header.sub_licenses(i)); + } + return keys; +} + // Parse a blob of multiple concatenated PSSH atoms to extract the first // Widevine PSSH. bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, @@ -339,8 +354,9 @@ bool InitializationData::ConstructWidevineInitData( return false; } if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) { - LOGV("InitializationData::ConstructWidevineInitData: Invalid method" - " parameter"); + LOGV( + "InitializationData::ConstructWidevineInitData: Invalid method" + " parameter"); return false; } @@ -466,10 +482,10 @@ bool InitializationData::ConstructWidevineInitData( } // Now format as Widevine init data protobuf - WidevineCencHeader cenc_header; + WidevinePsshData cenc_header; // TODO(rfrias): The algorithm is a deprecated field, but proto changes // have not yet been pushed to production. Set until then. - cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR); + cenc_header.set_algorithm(WidevinePsshData_Algorithm_AESCTR); for (size_t i = 0; i < key_ids.size(); ++i) { cenc_header.add_key_id(key_ids[i]); } diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index 36fcd9c1..3a0d2cd8 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -599,7 +599,8 @@ bool CdmLicense::RestoreOfflineLicense( const CdmKeyMessage& license_request, const CdmKeyResponse& license_response, const CdmKeyResponse& license_renewal_response, int64_t playback_start_time, - int64_t last_playback_time, int64_t grace_period_end_time) { + int64_t last_playback_time, int64_t grace_period_end_time, + CdmSession* cdm_session) { if (license_request.empty() || license_response.empty()) { LOGE( "CdmLicense::RestoreOfflineLicense: key_request or response empty: " @@ -641,9 +642,15 @@ bool CdmLicense::RestoreOfflineLicense( } if (!provider_session_token_.empty()) { + if (cdm_session) { + CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); + if (NO_ERROR != status) return false; + } + std::string usage_report; CryptoSession::UsageDurationStatus usage_duration_status = CryptoSession::kUsageDurationsInvalid; + int64_t seconds_since_started, seconds_since_last_played; sts = crypto_session_->GenerateUsageReport( provider_session_token_, &usage_report, &usage_duration_status, diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index cebe09ff..ba340d4e 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -101,6 +101,7 @@ message License { CONTENT = 2; KEY_CONTROL = 3; OPERATOR_SESSION = 4; + SUB_SESSION = 5; } // The SecurityLevel enumeration allows the server to communicate the level @@ -199,6 +200,9 @@ message License { // supports anti rollback of the user table. Content provider can query the // client capabilities to determine if the client support this feature. optional bool anti_rollback_usage_table = 11 [default = false]; + // Optional not limited to commonly known track types such as SD, HD. + // It can be some provider defined label to identify the track. + optional string track_label = 12; } optional LicenseIdentification id = 1; @@ -270,6 +274,19 @@ message LicenseRequest { //} } + message SubSessionData { + // Required. The key ID for the corresponding SUB_SESSION_KEY. The + // value must match the sub_session_key_id field for a + // corresponding SubLicense message from the PSSH. + optional string sub_session_key_id = 1; + // Required. The nonce for the track. + optional uint32 nonce = 2; + // Required for initial license request used for each CONTENT key_container + // to know which nonce to use for building its key control block. + // Not needed for renewal license request. + optional string track_label = 3; + } + enum RequestType { NEW = 1; RENEWAL = 2; @@ -293,6 +310,9 @@ message LicenseRequest { optional uint32 key_control_nonce = 7; // Encrypted ClientIdentification message, used for privacy purposes. optional EncryptedClientIdentification encrypted_client_id = 8; + // Optional sub session context information. Required for using + // SubLicenses from the PSSH. + repeated SubSessionData sub_session_data = 9; } message LicenseError { @@ -306,6 +326,7 @@ message LicenseError { // or similar circumstances. SERVICE_UNAVAILABLE = 3; } + optional Error error_code = 1; } @@ -410,7 +431,7 @@ message ProvisioningOptions { optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM]; // Contains the application-specific name used to identify the certificate - // authority for signing the generated certificate. This is required iff the + // authority for signing the generated certificate. This is required if the // certificate type is X509. optional string certificate_authority = 2; } @@ -680,19 +701,33 @@ message ProvisionedDeviceInfo { } // ---------------------------------------------------------------------------- -// widevine_header.proto +// widevine_pssh.proto // ---------------------------------------------------------------------------- // Description: // Public protocol buffer definitions for Widevine Cenc Header // protocol. -message WidevineCencHeader { +// Each SubLicense message represents a single content key. These keys can be +// added to Widevine CENC initialization data to support both content grouping +// and key rotation. +message SubLicense { + // Required. The key_id of a SUB_SESSION_KEY received in the master license. + // SUB_SESSION_KEY is defined in the Widevine License Protocol. + optional string sub_session_key_id = 1; + + // Required. The key_msg contains the bytes of a serialized SignedMessage + // proto. Internally the message field will contain a serialized KeyContainer + // holding a single content key. + optional bytes key_msg = 2; +} + +message WidevinePsshData { enum Algorithm { UNENCRYPTED = 0; AESCTR = 1; }; // Replaced with protection_scheme. - optional Algorithm algorithm = 1 [deprecated=true]; + optional Algorithm algorithm = 1; repeated bytes key_id = 2; // Content provider name. @@ -717,10 +752,22 @@ message WidevineCencHeader { // serialized SignedMessage. optional bytes grouped_license = 8; - // Protection scheme identifying the encryption algorithm. Represented as one - // of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC), - // 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample). + // Protection scheme identifying the encryption algorithm. The protection + // scheme is represented as a uint32 value. The uint32 contains 4 bytes each + // representing a single ascii character in one of the 4CC protection scheme + // values. + // 'cenc' (AES-CTR) protection_scheme = 0x63656E63, + // 'cbc1' (AES-CBC) protection_scheme = 0x63626331, + // 'cens' (AES-CTR subsample) protection_scheme = 0x63656E73, + // 'cbcs' (AES-CBC subsample) protection_scheme = 0x63626373. optional uint32 protection_scheme = 9; + + // Optional. For media using key rotation, this represents the duration + // of each crypto period in seconds. + optional uint32 crypto_period_seconds = 10; + + // Required when using content keys that are embedded in content. + repeated SubLicense sub_licenses = 11; } // Signed device certificate definition. diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index 3ec8429a..03b3b87f 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -506,6 +506,10 @@ class WatchDog { status_ = OEMCrypto_ERROR_INIT_FAILED; LOGE("XXX WATCH DOG ERROR XXX"); SaveFailureInformation(); + // This tells the worker thread to clean up after itself. It is not + // really needed since we are going to abort. However, if somebody + // removes the "abort()" below, then this is needed. + pthread_detach(thread_); // This is controversial. The argument for an abort here is that if we // do not abort, we will suck all the life out of the user's battery. By // saving information to the file system, above, we can still track @@ -517,7 +521,10 @@ class WatchDog { bool should_delete = !gave_up_; OEMCryptoResult status = status_; pthread_mutex_unlock(&mutex_); - if (should_delete) delete this; + if (should_delete) { + pthread_join(thread_, NULL); + delete this; + } return status; } diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index 062b6619..1d503d2b 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -14,6 +14,14 @@ using video_widevine::License; +namespace { + +const int kCdmPolicyTimerDurationSeconds = 1; +const int kClockSkewDelta = 5; // seconds +const int64_t kHdcpCheckInterval = 10; + +} // namespace + namespace wvcdm { PolicyEngine::PolicyEngine(CdmSessionId session_id, @@ -28,6 +36,7 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id, last_expiry_time_set_(false), was_expired_on_load_(false), next_renewal_time_(0), + last_recorded_current_time_(0), session_id_(session_id), event_listener_(event_listener), license_keys_(new LicenseKeys), @@ -81,7 +90,8 @@ void PolicyEngine::CheckDeviceHdcpStatus() { } void PolicyEngine::OnTimerEvent() { - int64_t current_time = clock_->GetCurrentTime(); + last_recorded_current_time_ += kCdmPolicyTimerDurationSeconds; + int64_t current_time = GetCurrentTime(); // If we have passed the grace period, the expiration will update. if (grace_period_end_time_ == 0 && HasPlaybackStarted(current_time)) { @@ -194,7 +204,7 @@ void PolicyEngine::UpdateLicense(const License& license) { license_start_time_ = license.license_start_time(); next_renewal_time_ = license_start_time_ + policy_.renewal_delay_seconds(); - int64_t current_time = clock_->GetCurrentTime(); + int64_t current_time = GetCurrentTime(); if (!policy_.can_play() || HasLicenseOrPlaybackDurationExpired(current_time)) { license_state_ = kLicenseStateExpired; @@ -219,7 +229,7 @@ void PolicyEngine::BeginDecryption() { case kLicenseStateCanPlay: case kLicenseStateNeedRenewal: case kLicenseStateWaitingLicenseUpdate: - playback_start_time_ = clock_->GetCurrentTime(); + playback_start_time_ = GetCurrentTime(); last_playback_time_ = playback_start_time_; if (policy_.play_start_grace_period_seconds() == 0) grace_period_end_time_ = playback_start_time_; @@ -239,7 +249,7 @@ void PolicyEngine::BeginDecryption() { } void PolicyEngine::DecryptionEvent() { - last_playback_time_ = clock_->GetCurrentTime(); + last_playback_time_ = GetCurrentTime(); } void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) { @@ -253,7 +263,7 @@ void PolicyEngine::NotifySessionExpiration() { CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) { std::stringstream ss; - int64_t current_time = clock_->GetCurrentTime(); + int64_t current_time = GetCurrentTime(); if (license_state_ == kLicenseStateInitial) { query_response->clear(); @@ -294,7 +304,7 @@ CdmResponseType PolicyEngine::QueryKeyAllowedUsage( bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) { if (playback_start_time_ == 0) return false; - *seconds_since_started = clock_->GetCurrentTime() - playback_start_time_; + *seconds_since_started = GetCurrentTime() - playback_start_time_; return (*seconds_since_started >= 0) ? true : false; } @@ -302,12 +312,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed( int64_t* seconds_since_last_played) { if (last_playback_time_ == 0) return false; - *seconds_since_last_played = clock_->GetCurrentTime() - last_playback_time_; + *seconds_since_last_played = GetCurrentTime() - last_playback_time_; return (*seconds_since_last_played >= 0) ? true : false; } int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() { - const int64_t current_time = clock_->GetCurrentTime(); + const int64_t current_time = GetCurrentTime(); const int64_t expiry_time = GetExpiryTime(current_time, /* ignore_soft_enforce_playback_duration */ false); @@ -331,7 +341,7 @@ void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time, playback_start_time_ = grace_period_end_time; } - const int64_t current_time = clock_->GetCurrentTime(); + const int64_t current_time = GetCurrentTime(); const int64_t expiry_time = GetExpiryTime(current_time, /* ignore_soft_enforce_playback_duration */ true); @@ -402,6 +412,8 @@ int64_t PolicyEngine::GetLicenseOrRentalDurationRemaining( if (license_expiry_time == NEVER_EXPIRES) return LLONG_MAX; if (license_expiry_time < current_time) return 0; const int64_t policy_license_duration = policy_.license_duration_seconds(); + if (policy_license_duration == NEVER_EXPIRES) + return license_expiry_time - current_time; return std::min(license_expiry_time - current_time, policy_license_duration); } @@ -469,6 +481,15 @@ void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) { last_expiry_time_set_ = true; } +int64_t PolicyEngine::GetCurrentTime() { + int64_t current_time = clock_->GetCurrentTime(); + if (current_time + kClockSkewDelta < last_recorded_current_time_) + current_time = last_recorded_current_time_; + else + last_recorded_current_time_ = current_time; + return current_time; +} + void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); } } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index 2b7d38d1..8b5abbe2 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -5,9 +5,17 @@ #include "crypto_session.h" #include "license.h" #include "log.h" +#include "wv_cdm_constants.h" namespace { std::string kEmptyString; +uint64_t kOldUsageEntryTimeSinceLicenseReceived = 0; +uint64_t kOldUsageEntryTimeSinceFirstDecrypt = 0; +uint64_t kOldUsageEntryTimeSinceLastDecrypt = 0; +std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0); +std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0); +std::string kOldUsageEntryPoviderSessionToken = + "nahZ6achSheiqua3TohQuei0ahwohv"; } namespace wvcdm { @@ -38,30 +46,43 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, } security_level_ = security_level; + requested_security_level_ = + security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault; if (!file_handle_->Init(security_level)) { LOGE("UsageTableHeader::Init: device files initialization failed"); return false; } - if (!file_handle_->RetrieveUsageTableInfo(&usage_table_header_, + CdmResponseType status = USAGE_INFO_NOT_FOUND; + if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_, &usage_entry_info_)) { - CdmResponseType status = - crypto_session->CreateUsageTableHeader(&usage_table_header_); - if (status != NO_ERROR) return false; - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); - } else { - CdmResponseType status = - crypto_session->LoadUsageTableHeader(usage_table_header_); + status = crypto_session->LoadUsageTableHeader(usage_table_header_); if (status != NO_ERROR) { LOGE( "UsageTableHeader::Init: load usage table failed, security level: %d", security_level); - return false; + file_handle_->DeleteAllLicenses(); + usage_entry_info_.clear(); + usage_table_header_.clear(); + status = crypto_session->CreateUsageTableHeader(&usage_table_header_); + if (status != NO_ERROR) return false; + file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); } + } else { + status = crypto_session->CreateUsageTableHeader(&usage_table_header_); + if (status != NO_ERROR) return false; + file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + + metrics::CryptoMetrics alternate_metrics; + metrics::CryptoMetrics* metrics = + crypto_session->GetCryptoMetrics() != nullptr ? + crypto_session->GetCryptoMetrics() : &alternate_metrics; + + UpgradeFromUsageTable(file_handle_.get(), metrics); + file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); } - requested_security_level_ = - security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault; + is_inited_ = true; return true; } @@ -141,8 +162,11 @@ CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number, metrics::CryptoMetrics* metrics) { LOGV("UsageTableHeader::DeleteEntry: Lock"); AutoLock auto_lock(usage_table_header_lock_); - if (usage_entry_number >= usage_entry_info_.size()) + if (usage_entry_number >= usage_entry_info_.size()) { + LOGE("UsageTableHeader::DeleteEntry: usage entry number %d larger than " + "usage entry size %d", usage_entry_number, usage_entry_info_.size()); return USAGE_INVALID_PARAMETERS_1; + } // Find the last valid entry number, in order to swap size_t swap_entry_number = usage_entry_info_.size() - 1; @@ -427,7 +451,7 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable( // * save the usage table header and store the usage entry number and // usage entry along with the license to persistent memory std::vector key_set_ids; - if (handle->ListLicenses(&key_set_ids)) { + if (!handle->ListLicenses(&key_set_ids)) { LOGW( "UpgradeUsageTableHeader::UpgradeLicensesFromUsageTable: unable to " "retrieve list of licenses"); @@ -470,6 +494,9 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable( if (status != NO_ERROR) continue; + // TODO(fredgc): remove when b/65730828 is addressed + if (!CreateDummyOldUsageEntry(&crypto_session)) continue; + status = AddEntry(&crypto_session, true /* persistent license */, key_set_ids[i], kEmptyString, &usage_entry_number); @@ -518,7 +545,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( // information to persistent memory along with usage entry number and usage // entry. std::vector usage_info_file_names; - if (handle->ListUsageInfoFiles(&usage_info_file_names)) { + if (!handle->ListUsageInfoFiles(&usage_info_file_names)) { LOGW( "UpgradeUsageTableHeader::UpgradeUsageInfoFromUsageTable: Unable to " "retrieve list of usage info file names"); @@ -535,7 +562,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( continue; } - for (size_t j = 0; j < usage_data.size(); --j) { + for (size_t j = 0; j < usage_data.size(); ++j) { if (usage_data[j].provider_session_token.empty()) { LOGW( "UsageTableHeader::UpgradeUsageInfoFromUsageTable: Provider " @@ -548,16 +575,19 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( if (status != NO_ERROR) continue; + // TODO(fredgc): remove when b/65730828 is addressed + if (!CreateDummyOldUsageEntry(&crypto_session)) continue; + // TODO(rfrias): We need to fill in the app id, but it is hashed // and we have no way to extract. Use the hased filename instead? status = AddEntry(&crypto_session, false /* usage info */, - usage_data[j].key_set_id, kEmptyString, - &(usage_data[i].usage_entry_number)); + usage_data[j].key_set_id, usage_info_file_names[i], + &(usage_data[j].usage_entry_number)); if (status != NO_ERROR) continue; status = crypto_session.CopyOldUsageEntry( - usage_data[i].provider_session_token); + usage_data[j].provider_session_token); if (status != NO_ERROR) { crypto_session.Close(); @@ -565,7 +595,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( continue; } - status = UpdateEntry(&crypto_session, &(usage_data[i].usage_entry)); + status = UpdateEntry(&crypto_session, &(usage_data[j].usage_entry)); if (status != NO_ERROR) { crypto_session.Close(); @@ -586,4 +616,16 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( return NO_ERROR; } +// TODO(fredgc): remove when b/65730828 is addressed +bool UsageTableHeader::CreateDummyOldUsageEntry(CryptoSession* crypto_session) { + return crypto_session->CreateOldUsageEntry( + kOldUsageEntryTimeSinceLicenseReceived, + kOldUsageEntryTimeSinceFirstDecrypt, + kOldUsageEntryTimeSinceLastDecrypt, + CryptoSession::kUsageDurationsInvalid, + kOldUsageEntryServerMacKey, + kOldUsageEntryClientMacKey, + kOldUsageEntryPoviderSessionToken); +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp b/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp index 270a5570..254b3d40 100644 --- a/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp @@ -30,48 +30,6 @@ std::string g_provisioning_server; std::string g_license_service_certificate; std::string g_provisioning_service_certificate; -/* - * Locate the portion of the server's response message that is between - * the strings jason_start_substr and json_end_substr. Returns the string - * through *result. If the start substring match fails, assume the entire - * string represents a serialized protobuf mesaage and return true with - * the entire string. If the end_substring match fails, return false with - * an empty *result. - */ -bool ExtractSignedMessage(const std::string& response, - const std::string& json_start_substr, - const std::string& json_end_substr, - std::string* result) { - std::string b64_string; - size_t start = response.find(json_start_substr); - - if (start == response.npos) { - // Assume web safe protobuf - b64_string.assign(response); - } else { - // Assume JSON-wrapped protobuf - size_t end = response.find(json_end_substr, - start + json_start_substr.length()); - if (end == response.npos) { - LOGE("ExtractSignedMessage cannot locate end substring"); - result->clear(); - return false; - } - size_t b64_string_size = end - start - json_start_substr.length(); - b64_string.assign(response, start + json_start_substr.length(), - b64_string_size); - } - - if (b64_string.empty()) { - LOGE("Response message is empty"); - result->clear(); - return false; - } - - result->swap(b64_string); - return true; -} - } // namespace namespace wvcdm { @@ -219,7 +177,7 @@ class WvGenericOperationsTest : public testing::Test { protected: virtual void Provision() { - LOGE("WvCdmEnginePreProvTest::Provision: url=%s", + LOGE("WvGenericOperationsTest::Provision: url=%s", g_provisioning_server.c_str()); CdmProvisioningRequest prov_request; std::string provisioning_server_url; @@ -232,7 +190,7 @@ class WvGenericOperationsTest : public testing::Test { cert_type, cert_authority, &prov_request, &provisioning_server_url)); - LOGV("WvCdmEnginePreProvTest::Provision: req=%s", prov_request.c_str()); + LOGV("WvGenericOperationsTest::Provision: req=%s", prov_request.c_str()); // Ignore URL provided by CdmEngine. Use ours, as configured // for test vs. production server. @@ -244,13 +202,12 @@ class WvGenericOperationsTest : public testing::Test { bool ok = url_request.GetResponse(&http_message); EXPECT_TRUE(ok); - LOGV("WvCdmEnginePreProvTest::Provision: http_message: \n%s\n", + LOGV("WvGenericOperationsTest::Provision: http_message: \n%s\n", http_message.c_str()); ASSERT_EQ(NO_ERROR, cdm_engine_->HandleProvisioningResponse(http_message, &cert, &wrapped_key)); - ASSERT_EQ(NO_ERROR, cdm_engine_->SetServiceCertificate(g_license_service_certificate)); } diff --git a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp index d62e300b..d718568b 100644 --- a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp @@ -17,7 +17,7 @@ namespace wvcdm { // Protobuf generated classes. -using video_widevine::WidevineCencHeader; +using video_widevine::WidevinePsshData; namespace { @@ -164,6 +164,19 @@ const std::string kZeroSizedPsshBox = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); +const std::string kSubLicensePsshBox = a2bs_hex( + // Widevine PSSH box + "0000009f" // atom size (whole buffer) + "70737368" // atom type="pssh" + "00000000" // v0, flags=0 + "edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine) + "0000007f" // data size + // data: + "0801120d746573745f6b65795f69645f30120d746573745f6b65795f69645f31220f" + "746573745f636f6e74656e745f69645a250a147375625f73657373696f6e5f6b6579" + "5f69645f30120d7375625f6c6963656e73655f305a250a147375625f73657373696f" + "6e5f6b65795f69645f31120d7375625f6c6963656e73655f31"); + // HLS test attribute key and values const std::string kHlsIvHexValue = "6DF49213A781E338628D0E9C812D328E"; const std::string kHlsIvValue = "0x" + kHlsIvHexValue; @@ -353,7 +366,9 @@ struct HlsInitDataVariant { HlsInitDataVariant(CdmHlsMethod method, const std::string& provider, const std::string& content_id, const std::string& key_id, bool success) - : method_(method), provider_(provider), content_id_(content_id), + : method_(method), + provider_(provider), + content_id_(content_id), success_(success) { if (key_id.size() > 0) key_ids_.push_back(key_id); } @@ -405,18 +420,42 @@ class HlsParseTest : public ::testing::TestWithParam {}; class HlsTest : public ::testing::Test {}; } // namespace +TEST_F(InitializationDataTest, BadType) { + InitializationData init_data("bad", kSubLicensePsshBox); + EXPECT_TRUE(init_data.IsEmpty()); +} + TEST_P(InitializationDataTest, Parse) { InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, GetParam()); EXPECT_FALSE(init_data.IsEmpty()); } -INSTANTIATE_TEST_CASE_P(ParsePssh, InitializationDataTest, - ::testing::Values(kWidevinePssh, kWidevinePsshFirst, - kWidevinePsshAfterV0Pssh, - kWidevinePsshAfterNonZeroFlags, - kWidevinePsshAfterV1Pssh, - kWidevineV1Pssh, kOtherBoxFirst, - kZeroSizedPsshBox)); +INSTANTIATE_TEST_CASE_P( + ParsePssh, InitializationDataTest, + ::testing::Values(kWidevinePssh, kWidevinePsshFirst, + kWidevinePsshAfterV0Pssh, kWidevinePsshAfterNonZeroFlags, + kWidevinePsshAfterV1Pssh, kWidevineV1Pssh, kOtherBoxFirst, + kZeroSizedPsshBox, kSubLicensePsshBox)); + +TEST_F(InitializationDataTest, ExtractSubLicense) { + InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, kSubLicensePsshBox); + ASSERT_FALSE(init_data.IsEmpty()); + std::vector keys = + init_data.ExtractEmbeddedKeys(); + ASSERT_EQ(keys.size(), 2UL); + EXPECT_EQ(keys[0].sub_session_key_id(), "sub_session_key_id_0"); + EXPECT_EQ(keys[1].sub_session_key_id(), "sub_session_key_id_1"); + EXPECT_EQ(keys[0].key_msg(), "sub_license_0"); + EXPECT_EQ(keys[1].key_msg(), "sub_license_1"); +} + +TEST_F(InitializationDataTest, ExtractEmptySubLicense) { + InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, kWidevinePssh); + ASSERT_FALSE(init_data.IsEmpty()); + std::vector keys = + init_data.ExtractEmbeddedKeys(); + ASSERT_TRUE(keys.empty()); +} TEST_P(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions) { std::vector versions = GetParam(); @@ -598,9 +637,9 @@ TEST_P(HlsConstructionTest, InitData) { EXPECT_EQ(param.success_, InitializationData::ConstructWidevineInitData( param.method_, uri, &value)); if (param.success_) { - WidevineCencHeader cenc_header; + WidevinePsshData cenc_header; EXPECT_TRUE(cenc_header.ParseFromString(value)); - EXPECT_EQ(video_widevine::WidevineCencHeader_Algorithm_AESCTR, + EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR, cenc_header.algorithm()); for (size_t i = 0; i < param.key_ids_.size(); ++i) { bool key_id_found = false; @@ -620,9 +659,14 @@ TEST_P(HlsConstructionTest, InitData) { cenc_header.content_id()); uint32_t protection_scheme = 0; switch (param.method_) { - case kHlsMethodAes128: protection_scheme = kFourCcCbc1; break; - case kHlsMethodSampleAes: protection_scheme = kFourCcCbcs; break; - default: break; + case kHlsMethodAes128: + protection_scheme = kFourCcCbc1; + break; + case kHlsMethodSampleAes: + protection_scheme = kFourCcCbcs; + break; + default: + break; } EXPECT_EQ(protection_scheme, cenc_header.protection_scheme()); } @@ -633,8 +677,8 @@ INSTANTIATE_TEST_CASE_P( ::testing::Values( HlsInitDataVariant(kHlsMethodAes128, "", kHlsTestContentId, kHlsTestKeyId1, false), - HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, - "", kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, "", + kHlsTestKeyId1, false), HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, kHlsTestContentId, "", false), HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, @@ -699,9 +743,9 @@ TEST_P(HlsParseTest, Parse) { EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method()); } - WidevineCencHeader cenc_header; + WidevinePsshData cenc_header; EXPECT_TRUE(cenc_header.ParseFromString(init_data.data())); - EXPECT_EQ(video_widevine::WidevineCencHeader_Algorithm_AESCTR, + EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR, cenc_header.algorithm()); if (param.key_.compare(kJsonProvider) == 0) { EXPECT_EQ(param.value_, cenc_header.provider()); diff --git a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp index 91302e7e..d15ca05c 100644 --- a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp @@ -599,6 +599,128 @@ TEST_F(PolicyEngineTest, PlaybackOk_PlaybackAndRental0) { EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); } +TEST_F(PolicyEngineTest, PlaybackOk_PlaybackAndLicense0_WithoutPlayback) { + License_Policy* policy = license_.mutable_policy(); + policy->clear_license_duration_seconds(); + policy->clear_playback_duration_seconds(); + // Only |rental_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kLicenseStartTime + 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration - 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration + 10)); + + ExpectSessionKeysChange(kKeyStatusExpired, false); + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kLicenseStartTime + kRentalDuration)); + + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + policy_engine_->OnTimerEvent(); + + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); +} + +TEST_F(PolicyEngineTest, PlaybackOk_PlaybackAndLicense0_WithPlayback) { + License_Policy* policy = license_.mutable_policy(); + policy->clear_license_duration_seconds(); + policy->clear_playback_duration_seconds(); + // Only |rental_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kLicenseStartTime + 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration + 10)); + + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, 0)); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kLicenseStartTime + kRentalDuration)); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); +} + +TEST_F(PolicyEngineTest, PlaybackOk_RentalAndLicense0_WithoutPlayback) { + License_Policy* policy = license_.mutable_policy(); + policy->clear_license_duration_seconds(); + policy->clear_rental_duration_seconds(); + // Only |playback_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kLicenseStartTime + 10)) + .WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10)); + + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, 0)); + + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); +} + +TEST_F(PolicyEngineTest, PlaybackOk_RentalAndLicense0_WithPlayback) { + License_Policy* policy = license_.mutable_policy(); + policy->clear_license_duration_seconds(); + policy->clear_rental_duration_seconds(); + // Only |playback_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration - 10)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10)); + + ExpectSessionKeysChange(kKeyStatusExpired, false); + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, 0)); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kPlaybackStartTime + kPlaybackDuration)); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + policy_engine_->OnTimerEvent(); + + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); +} + TEST_F(PolicyEngineTest, PlaybackOk_Durations0) { License_Policy* policy = license_.mutable_policy(); policy->set_rental_duration_seconds(kDurationUnlimited); @@ -2188,6 +2310,303 @@ TEST_F(PolicyEngineQueryTest, QuerySuccess_LicenseDuration0) { EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); } +TEST_F(PolicyEngineQueryTest, QuerySuccess_PlaybackAndRental0) { + License_Policy* policy = license_.mutable_policy(); + policy->set_rental_duration_seconds(kDurationUnlimited); + policy->set_playback_duration_seconds(kDurationUnlimited); + policy->set_license_duration_seconds(kLowDuration); + // Only |license_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kLicenseStartTime + 10)) + .WillOnce(Return(kLicenseStartTime + kLowDuration - 10)) + .WillOnce(Return(kLicenseStartTime + kLowDuration + 10)) + .WillOnce(Return(kLicenseStartTime + kLowDuration + 10)); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + CdmQueryMap query_info; + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(10, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); + + policy_engine_->OnTimerEvent(); + + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(0, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); +} + +TEST_F(PolicyEngineQueryTest, QuerySuccess_PlaybackAndLicense0_WithoutPlayback) { + License_Policy* policy = license_.mutable_policy(); + policy->set_rental_duration_seconds(kRentalDuration); + policy->set_playback_duration_seconds(kDurationUnlimited); + policy->set_license_duration_seconds(kDurationUnlimited); + // Only |rental_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kLicenseStartTime + 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration - 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration - 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration + 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration + 10)); + + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + CdmQueryMap query_info; + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(10, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); + + policy_engine_->OnTimerEvent(); + + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(0, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); +} + +TEST_F(PolicyEngineQueryTest, QuerySuccess_PlaybackAndLicense0_WithPlayback) { + License_Policy* policy = license_.mutable_policy(); + policy->set_rental_duration_seconds(kRentalDuration); + policy->set_playback_duration_seconds(kDurationUnlimited); + policy->set_license_duration_seconds(kDurationUnlimited); + // Only |rental_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kLicenseStartTime + kPlaybackDuration - 10)) + .WillOnce(Return(kLicenseStartTime + kPlaybackDuration - 10)) + .WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10)) + .WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration + 10)) + .WillOnce(Return(kLicenseStartTime + kRentalDuration + 10)); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + CdmQueryMap query_info; + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(kRentalDuration - kPlaybackDuration + 10, + ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); + + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(kRentalDuration - kPlaybackDuration - 10, + ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); + + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(0, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); +} + +TEST_F(PolicyEngineQueryTest, QuerySuccess_RentalAndLicense0_WithoutPlayback) { + License_Policy* policy = license_.mutable_policy(); + policy->set_rental_duration_seconds(kDurationUnlimited); + policy->set_playback_duration_seconds(kPlaybackDuration); + policy->set_license_duration_seconds(kDurationUnlimited); + // Only |playback_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kLicenseStartTime + 10)) + .WillOnce(Return(kLicenseStartTime + 10)) + .WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10)) + .WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10)); + + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + CdmQueryMap query_info; + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(kPlaybackDuration, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); + + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(kPlaybackDuration, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); +} + +TEST_F(PolicyEngineQueryTest, QuerySuccess_RentalAndLicense0_WithPlayback) { + License_Policy* policy = license_.mutable_policy(); + policy->set_rental_duration_seconds(kDurationUnlimited); + policy->set_playback_duration_seconds(kPlaybackDuration); + policy->set_license_duration_seconds(kDurationUnlimited); + // Only |playback_duration_seconds| set. + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kPlaybackStartTime + 10)) + .WillOnce(Return(kPlaybackStartTime + 10)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration - 10)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration - 10)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10)); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + CdmQueryMap query_info; + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(kPlaybackDuration - 10, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); + + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(LLONG_MAX, + ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(10, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); + + policy_engine_->OnTimerEvent(); + + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + EXPECT_EQ(0, + ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING])); + EXPECT_EQ(0, + ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING])); + EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); +} + TEST_F(PolicyEngineQueryTest, QuerySuccess_Durations0) { License_Policy* policy = license_.mutable_policy(); policy->set_rental_duration_seconds(kDurationUnlimited); diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index 6e7a545a..fed2f314 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -50,6 +50,46 @@ const CdmUsageEntryInfo kUsageEntryInfoStorageTypeUnknown = { .storage_type = kStorageTypeUnknown, .key_set_id = "", .usage_info_file_name = ""}; +const std::vector kEmptyLicenseList; +const std::vector kLicenseList = { + kUsageEntryInfoOfflineLicense1.key_set_id, + kUsageEntryInfoOfflineLicense2.key_set_id, + kUsageEntryInfoOfflineLicense3.key_set_id, +}; +const std::vector kEmptyUsageInfoFilesList; +const std::vector kUsageInfoFileList = { + kUsageEntryInfoSecureStop1.usage_info_file_name, + kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop3.usage_info_file_name, +}; +const DeviceFiles::CdmUsageData kCdmUsageData1 = { + .provider_session_token = "provider_session_token_1", + .license_request = "license_request_1", + .license = "license_1", + .key_set_id = "key_set_id_1", + .usage_entry = "usage_entry_1", + .usage_entry_number = 0, +}; +const DeviceFiles::CdmUsageData kCdmUsageData2 = { + .provider_session_token = "provider_session_token_2", + .license_request = "license_request_2", + .license = "license_2", + .key_set_id = "key_set_id_2", + .usage_entry = "usage_entry_2", + .usage_entry_number = 0, +}; +const DeviceFiles::CdmUsageData kCdmUsageData3 = { + .provider_session_token = "provider_session_token_3", + .license_request = "license_request_3", + .license = "license_3", + .key_set_id = "key_set_id_3", + .usage_entry = "usage_entry_3", + .usage_entry_number = 0, +}; +const std::vector kEmptyUsageInfoUsageDataList; +const std::vector kUsageInfoUsageDataList = { + kCdmUsageData1, kCdmUsageData2, kCdmUsageData3, +}; const std::vector kEmptyUsageEntryInfoVector; const std::vector kUsageEntryInfoVector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, @@ -82,12 +122,17 @@ class MockDeviceFiles : public DeviceFiles { bool(const std::string&, const std::string&, std::string*, CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*, uint32_t*)); + MOCK_METHOD0(DeleteAllLicenses, bool()); MOCK_METHOD7(StoreUsageInfo, bool(const std::string&, const CdmKeyMessage&, const CdmKeyResponse&, const std::string&, const std::string&, const CdmUsageEntry&, uint32_t)); MOCK_METHOD2(RetrieveUsageInfo, bool(const std::string&, std::vector*)); + MOCK_METHOD1(ListLicenses, + bool(std::vector* key_set_ids)); + MOCK_METHOD1(ListUsageInfoFiles, + bool(std::vector* usage_info_files)); private: FileSystem file_system_; @@ -170,18 +215,76 @@ class UsageTableHeaderInitializationTest public ::testing::WithParamInterface {}; TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) { - std::vector empty_usage_entry_info; - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), Return(false))); + EXPECT_CALL(*device_files_, ListLicenses(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kEmptyLicenseList), + Return(false))); + EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageInfoFilesList), + Return(false))); EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) - .WillOnce(Return(true)); + .Times(2) + .WillRepeatedly(Return(true)); + + EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); +} + +TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveLicenses) { + EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), + SetArgPointee<1>(kEmptyUsageEntryInfoVector), + Return(false))); + EXPECT_CALL(*device_files_, ListLicenses(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kLicenseList), + Return(true))); + EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageInfoFilesList), + Return(false))); + EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, + kEmptyUsageEntryInfoVector)) + .Times(2) + .WillRepeatedly(Return(true)); + + for (size_t i = 0; i < kLicenseList.size(); ++i) + device_files_->DeleteLicense(kLicenseList[i]); + EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); +} + +TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) { + EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), + SetArgPointee<1>(kEmptyUsageEntryInfoVector), + Return(false))); + EXPECT_CALL(*device_files_, ListLicenses(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kEmptyLicenseList), + Return(false))); + EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUsageInfoFileList), + Return(true))); + EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, + kEmptyUsageEntryInfoVector)) + .Times(2) + .WillRepeatedly(Return(true)); + for (size_t i = 0; i < kUsageInfoFileList.size(); ++i) { + EXPECT_CALL(*device_files_, + RetrieveUsageInfo(kUsageInfoFileList[i], NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kEmptyUsageInfoUsageDataList), + Return(false))); + } EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } @@ -560,7 +663,6 @@ TEST_F(UsageTableHeaderTest, usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - device_files_->DeleteAllLicenses(); EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); EXPECT_CALL( *crypto_session_, @@ -1558,4 +1660,27 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreSecureStopAndUnknknown) { device_files_, &metrics)); } +// If the crypto session says the usage table header is stale, init should fail. +TEST_F(UsageTableHeaderTest, StaleHeader) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2}; + + EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), + SetArgPointee<1>(usage_entry_info_vector), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + .WillOnce(Return(LOAD_USAGE_HEADER_GENERATION_SKEW)); + EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, + kEmptyUsageEntryInfoVector)) + .WillOnce(Return(true)); + + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/src/file_utils.cpp b/libwvdrmengine/cdm/src/file_utils.cpp index 05fd7782..fa00760c 100644 --- a/libwvdrmengine/cdm/src/file_utils.cpp +++ b/libwvdrmengine/cdm/src/file_utils.cpp @@ -15,14 +15,6 @@ #include "log.h" #include "properties.h" -namespace { -const char* kSecurityLevelPathCompatibilityExclusionList[] = { - "ay64.dat", "ay64.dat2", "ay64.dat3"}; -size_t kSecurityLevelPathCompatibilityExclusionListSize = - sizeof(kSecurityLevelPathCompatibilityExclusionList) / - sizeof(*kSecurityLevelPathCompatibilityExclusionList); -} // namespace - namespace wvcdm { bool IsCurrentOrParentDirectory(char* dir) { @@ -258,15 +250,6 @@ void FileUtils::SecurityLevelPathBackwardCompatibility( for (size_t i = 0; i < files.size(); ++i) { std::string from = from_dir + files[i]; - bool exclude = false; - for (size_t j = 0; j < kSecurityLevelPathCompatibilityExclusionListSize; - ++j) { - if (files[i] == kSecurityLevelPathCompatibilityExclusionList[j]) { - exclude = true; - break; - } - } - if (exclude) continue; if (!FileUtils::IsRegularFile(from)) continue; for (size_t j = 0; j < security_dirs.size(); ++j) { diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index a5880336..187dd20c 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -2021,55 +2021,6 @@ TEST_F(WvCdmRequestLicenseTest, VerifyKeyRequestResponse(g_license_server, client_auth); } -TEST_F(WvCdmRequestLicenseTest, ExpiryOnReleaseOfflineKeyTest) { - Unprovision(); - Provision(kLevelDefault); - - // override default settings unless configured through the command line - std::string key_id; - std::string client_auth; - GetOfflineConfiguration(&key_id, &client_auth); - - decryptor_.OpenSession(g_key_system, NULL, kDefaultCdmIdentifier, NULL, - &session_id_); - GenerateKeyRequest(key_id, kLicenseTypeOffline); - VerifyKeyRequestResponse(g_license_server, client_auth); - - CdmKeySetId key_set_id = key_set_id_; - EXPECT_FALSE(key_set_id_.empty()); - decryptor_.CloseSession(session_id_); - - session_id_.clear(); - key_set_id_.clear(); - StrictMock listener; - decryptor_.OpenSession(g_key_system, NULL, kDefaultCdmIdentifier, &listener, - &session_id_); - CdmSessionId restore_session_id = session_id_; - EXPECT_CALL( - listener, - OnSessionKeysChange( - restore_session_id, - AllOf(Each(Pair(_, kKeyStatusUsable)), Not(IsEmpty())), true)); - EXPECT_CALL(listener, OnExpirationUpdate(restore_session_id, _)); - EXPECT_EQ(wvcdm::KEY_ADDED, - decryptor_.RestoreKey(restore_session_id, key_set_id)); - - session_id_.clear(); - key_set_id_.clear(); - // Maybe called since VerifyKeyRequestResponse could take some time. - EXPECT_CALL(listener, OnSessionRenewalNeeded(restore_session_id)) - .Times(AtLeast(0)); - EXPECT_CALL( - listener, - OnSessionKeysChange( - restore_session_id, - AllOf(Each(Pair(_, kKeyStatusExpired)), Not(IsEmpty())), false)); - GenerateKeyRelease(key_set_id); - key_set_id_ = key_set_id; - VerifyKeyRequestResponse(g_license_server, client_auth); - decryptor_.CloseSession(restore_session_id); -} - // This test verifies that repeated generation of the key release message // for the same key_set_id results in the previous session being // deallocated (rather than leaked) and a new one allocated. diff --git a/libwvdrmengine/include_hidl/TypeConvert.h b/libwvdrmengine/include_hidl/TypeConvert.h index c3cac776..dada0b26 100644 --- a/libwvdrmengine/include_hidl/TypeConvert.h +++ b/libwvdrmengine/include_hidl/TypeConvert.h @@ -70,8 +70,6 @@ template std::vector toVector( return vec; } -Status toStatus(status_t mediaError); - } // namespace widevine } // namespace V1_0 } // namespace drm diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index 4e77701a..b1b7dc34 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -272,20 +272,29 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::USAGE_INVALID_NEW_ENTRY: case wvcdm::USAGE_INVALID_PARAMETERS_1: case wvcdm::USAGE_ENTRY_NUMBER_MISMATCH: + case wvcdm::USAGE_GET_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE: + case wvcdm::USAGE_GET_ENTRY_RETRIEVE_LICENSE_FAILED: + case wvcdm::USAGE_GET_ENTRY_RETRIEVE_USAGE_INFO_FAILED: + case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE: + case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED: + case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED: case wvcdm::USAGE_STORE_LICENSE_FAILED: case wvcdm::USAGE_STORE_USAGE_INFO_FAILED: case wvcdm::USAGE_INVALID_LOAD_ENTRY: case wvcdm::RELEASE_ALL_USAGE_INFO_ERROR_4: + case wvcdm::RELEASE_ALL_USAGE_INFO_ERROR_5: + case wvcdm::RELEASE_ALL_USAGE_INFO_ERROR_6: + case wvcdm::RELEASE_ALL_USAGE_INFO_ERROR_7: case wvcdm::INCORRECT_USAGE_SUPPORT_TYPE_1: case wvcdm::INCORRECT_USAGE_SUPPORT_TYPE_2: case wvcdm::NO_USAGE_ENTRIES: + case wvcdm::LIST_LICENSE_ERROR_1: + case wvcdm::LIST_LICENSE_ERROR_2: case wvcdm::LIST_USAGE_ERROR_1: case wvcdm::LIST_USAGE_ERROR_2: case wvcdm::DELETE_USAGE_ERROR_1: case wvcdm::DELETE_USAGE_ERROR_2: case wvcdm::DELETE_USAGE_ERROR_3: - case wvcdm::LIST_LICENSE_ERROR_1: - case wvcdm::LIST_LICENSE_ERROR_2: case wvcdm::PRIVACY_MODE_ERROR_1: case wvcdm::PRIVACY_MODE_ERROR_2: case wvcdm::PRIVACY_MODE_ERROR_3: @@ -295,15 +304,6 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::PARSE_RESPONSE_ERROR_2: case wvcdm::PARSE_RESPONSE_ERROR_3: case wvcdm::PARSE_RESPONSE_ERROR_4: - case wvcdm::USAGE_GET_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE: - case wvcdm::USAGE_GET_ENTRY_RETRIEVE_LICENSE_FAILED: - case wvcdm::USAGE_GET_ENTRY_RETRIEVE_USAGE_INFO_FAILED: - case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED: - case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED: - case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE: - case wvcdm::RELEASE_ALL_USAGE_INFO_ERROR_5: - case wvcdm::RELEASE_ALL_USAGE_INFO_ERROR_6: - case wvcdm::RELEASE_ALL_USAGE_INFO_ERROR_7: ALOGW("Returns UNKNOWN error for legacy status: %d", res); return Status::ERROR_DRM_UNKNOWN; diff --git a/libwvdrmengine/mediacrypto/Android.mk b/libwvdrmengine/mediacrypto/Android.mk index 31490297..bf68f649 100644 --- a/libwvdrmengine/mediacrypto/Android.mk +++ b/libwvdrmengine/mediacrypto/Android.mk @@ -58,8 +58,8 @@ LOCAL_HEADER_LIBRARIES := \ libutils_headers LOCAL_STATIC_LIBRARIES := \ - libcrypto_static \ - libhidl_utils \ + libcdm_protos \ + libcrypto LOCAL_SHARED_LIBRARIES := \ android.hardware.drm@1.0 \ diff --git a/libwvdrmengine/mediacrypto/include_hidl/WVCryptoPlugin.h b/libwvdrmengine/mediacrypto/include_hidl/WVCryptoPlugin.h index d3ff9914..1ea1fd4a 100644 --- a/libwvdrmengine/mediacrypto/include_hidl/WVCryptoPlugin.h +++ b/libwvdrmengine/mediacrypto/include_hidl/WVCryptoPlugin.h @@ -68,7 +68,7 @@ struct WVCryptoPlugin : public ICryptoPlugin { sp const mCDM; - android::status_t attemptDecrypt( + Status attemptDecrypt( const wvcdm::CdmDecryptionParameters& params, bool haveEncryptedSubsamples, std::string* errorDetailMsg); static wvcdm::CdmResponseType countEncryptedBlocksInPatternedRange( diff --git a/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp b/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp index 8b8d10c8..075f6d88 100644 --- a/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp +++ b/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp @@ -30,12 +30,9 @@ namespace V1_0 { namespace widevine { using ::android::hardware::drm::V1_0::BufferType; -using ::android::hardware::drm::V1_0::widevine::toStatus; using ::android::hardware::drm::V1_0::widevine::toVector; using ::android::hardware::Void; -using android::status_t; - using wvcdm::CdmDecryptionParameters; using wvcdm::CdmQueryMap; using wvcdm::CdmResponseType; @@ -98,7 +95,11 @@ Return WVCryptoPlugin::setMediaDrmSession( Return WVCryptoPlugin::setSharedBufferBase( const hidl_memory& base, uint32_t bufferId) { - mSharedBufferMap[bufferId] = mapMemory(base); + sp hidlMemory = mapMemory(base); + ALOGE_IF(hidlMemory == nullptr, "mapMemory returns nullptr"); + + // allow mapMemory to return nullptr + mSharedBufferMap[bufferId] = hidlMemory; return Void(); } @@ -144,6 +145,10 @@ Return WVCryptoPlugin::decrypt( std::string errorDetailMsg; sp sourceBase = mSharedBufferMap[source.bufferId]; + if (sourceBase == nullptr) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "source is a nullptr"); + return Void(); + } if (source.offset + offset + source.size > sourceBase->getSize()) { _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size"); @@ -157,6 +162,11 @@ Return WVCryptoPlugin::decrypt( if (destination.type == BufferType::SHARED_MEMORY) { const SharedBuffer& destBuffer = destination.nonsecureMemory; sp destBase = mSharedBufferMap[destBuffer.bufferId]; + if (destBase == nullptr) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "destination is a nullptr"); + return Void(); + } + if (destBuffer.offset + destBuffer.size > destBase->getSize()) { _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size"); return Void(); @@ -245,10 +255,10 @@ Return WVCryptoPlugin::decrypt( params.decrypt_buffer_offset = bufferOffset; params.subsample_flags = clearFlags; - status_t res = attemptDecrypt(params, haveEncryptedSubsamples, + Status res = attemptDecrypt(params, haveEncryptedSubsamples, &errorDetailMsg); - if (res != android::OK) { - _hidl_cb(toStatus(res), 0, errorDetailMsg.c_str()); + if (res != Status::OK) { + _hidl_cb(res, 0, errorDetailMsg.c_str()); return Void(); } bufferOffset += subSample.numBytesOfClearData; @@ -264,10 +274,10 @@ Return WVCryptoPlugin::decrypt( params.decrypt_buffer_offset = bufferOffset; params.subsample_flags = encryptedFlags; - status_t res = attemptDecrypt(params, haveEncryptedSubsamples, + Status res = attemptDecrypt(params, haveEncryptedSubsamples, &errorDetailMsg); - if (res != android::OK) { - _hidl_cb(toStatus(res), 0, errorDetailMsg.c_str()); + if (res != Status::OK) { + _hidl_cb(res, 0, errorDetailMsg.c_str()); return Void(); } @@ -318,14 +328,14 @@ Return WVCryptoPlugin::decrypt( return Void(); } -status_t WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params, +Status WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params, bool haveEncryptedSubsamples, std::string* errorDetailMsg) { CdmResponseType res = mCDM->Decrypt(mSessionId, haveEncryptedSubsamples, params); if (isCdmResponseTypeSuccess(res)) { - return android::OK; + return Status::OK; } else { ALOGE("Decrypt error result in session %s during %s block: %d", mSessionId.c_str(), @@ -365,10 +375,10 @@ status_t WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params, if (actionableError) { // This error is actionable by the app and should be passed up. - return (status_t)mapCdmResponseType(res); + return mapCdmResponseType(res); } else { // Swallow the specifics of other errors to obscure decrypt internals. - return kErrorCDMGeneric; + return Status::ERROR_DRM_UNKNOWN; } } } diff --git a/libwvdrmengine/mediacrypto/test/Android.mk b/libwvdrmengine/mediacrypto/test/Android.mk index 7cd6a1c5..bc50cb6a 100644 --- a/libwvdrmengine/mediacrypto/test/Android.mk +++ b/libwvdrmengine/mediacrypto/test/Android.mk @@ -87,10 +87,6 @@ LOCAL_STATIC_LIBRARIES := \ libwvlevel3 \ libwvdrmcryptoplugin_hidl \ -# When the GNU linker sees a library, it discards all symbols that it doesn't -# need. libhidl_utils must come after libwvdrmcryptoplugin. -LOCAL_STATIC_LIBRARIES += libhidl_utils - LOCAL_SHARED_LIBRARIES := \ android.hardware.drm@1.0 \ android.hidl.memory@1.0 \ diff --git a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp index 1cf18879..2bbca746 100644 --- a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp @@ -642,7 +642,7 @@ Return WVDrmPlugin::getPropertyString(const hidl_string& propertyName, if (name == "vendor") { value = "Google"; } else if (name == "version") { - value = "1.0"; + status = queryProperty(wvcdm::QUERY_KEY_WVCDM_VERSION, value); } else if (name == "description") { value = "Widevine CDM"; } else if (name == "algorithms") { @@ -685,6 +685,10 @@ Return WVDrmPlugin::getPropertyString(const hidl_string& propertyName, value = mPropertySet.app_id().c_str(); } else if (name == "origin") { value = mCdmIdentifierBuilder.origin().c_str(); + } else if (name == "CurrentSRMVersion") { + status = queryProperty(wvcdm::QUERY_KEY_CURRENT_SRM_VERSION, value); + } else if (name == "SRMUpdateSupport") { + status = queryProperty(wvcdm::QUERY_KEY_SRM_UPDATE_SUPPORT, value); } else { ALOGE("App requested unknown string property %s", name.c_str()); status = Status::ERROR_DRM_CANNOT_HANDLE; @@ -716,6 +720,10 @@ Return WVDrmPlugin::getPropertyByteArray( } } else if (name == "serviceCertificate") { value = StrToVector(mPropertySet.service_certificate()); + } else if (name == "metrics") { + std::string metrics_value; + mCDM->GetSerializedMetrics(&metrics_value); + value = StrToVector(metrics_value); } else { ALOGE("App requested unknown byte array property %s", name.c_str()); status = Status::ERROR_DRM_CANNOT_HANDLE; diff --git a/libwvdrmengine/mediadrm/src_hidl/WVGenericCryptoInterface.cpp b/libwvdrmengine/mediadrm/src_hidl/WVGenericCryptoInterface.cpp index 16100c67..e8d7b6f4 100644 --- a/libwvdrmengine/mediadrm/src_hidl/WVGenericCryptoInterface.cpp +++ b/libwvdrmengine/mediadrm/src_hidl/WVGenericCryptoInterface.cpp @@ -4,7 +4,7 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "WVCdm" -#include +#include #include "WVGenericCryptoInterface.h" diff --git a/libwvdrmengine/mediadrm/test/Android.mk b/libwvdrmengine/mediadrm/test/Android.mk index 746c6dc7..f6d62730 100644 --- a/libwvdrmengine/mediadrm/test/Android.mk +++ b/libwvdrmengine/mediadrm/test/Android.mk @@ -88,10 +88,6 @@ LOCAL_STATIC_LIBRARIES := \ libwvlevel3 \ libwvdrmdrmplugin_hidl \ -# When the GNU linker sees a library, it discards all symbols that it doesn't -# need. libhidl_utils must come after libwvdrmdrmplugin. -LOCAL_STATIC_LIBRARIES += libhidl_utils - LOCAL_SHARED_LIBRARIES := \ android.hardware.drm@1.0 \ android.hidl.memory@1.0 \ diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index a5d0465d..93ba3388 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "cdm_client_property_set.h" #include "gmock/gmock.h" @@ -117,16 +118,19 @@ const std::string kAppId("com.unittest.mock.app.id"); const uint8_t* const kUnprovisionResponse = reinterpret_cast("unprovision"); const size_t kUnprovisionResponseSize = 11; -const std::string kDeviceId = "0123456789ABCDEF"; +const std::string kDeviceId("0123456789\0ABCDEF", 17); // This is a serialized MetricsGroup message containing a small amount of // sample data. This ensures we're able to extract it via a property. const char kSerializedMetrics[] = { - 0x0a, 0x0a, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x02, 0x08, 0x00, - 0x0a, 0x12, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x32, 0x12, 0x09, 0x11, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x15, 0x0a, 0x05, - 0x74, 0x65, 0x73, 0x74, 0x33, 0x12, 0x0c, 0x1a, 0x0a, 0x74, 0x65, 0x73, - 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65}; + 0x0a, 0x0a, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x02, 0x08, 0x00, + 0x0a, 0x12, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x32, 0x12, 0x09, 0x11, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x15, 0x0a, 0x05, + 0x74, 0x65, 0x73, 0x74, 0x33, 0x12, 0x0c, 0x1a, 0x0a, 0x74, 0x65, 0x73, + 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65 +}; + +#define N_ELEM(a) (sizeof(a)/sizeof(a[0])) } // anonymous namespace class MockCDM : public WvContentDecryptionModule { @@ -434,7 +438,7 @@ TEST_F(WVDrmPluginTest, DISABLED_GeneratesKeyRequests) { {kIsoBmffMimeType, initData, cdmPsshBox}, // ISO-BMFF, old passing style {kWebmMimeType, initData, cdmInitData} // WebM }; - size_t testSetCount = sizeof(testSets) / sizeof(TestSet); + size_t testSetCount = N_ELEM(testSets); // Set up the expected calls. Per gMock rules, this must be done for all test // sets prior to testing any of them. @@ -809,8 +813,8 @@ TEST_F(WVDrmPluginTest, UnprovisionsDevice) { .Times(1); WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false); - status_t res = plugin.unprovisionDevice(); - ASSERT_EQ(android::OK, res); + Status res = plugin.unprovisionDevice(); + ASSERT_EQ(Status::OK, res); } TEST_F(WVDrmPluginTest, MuxesUnprovisioningErrors) { @@ -830,12 +834,12 @@ TEST_F(WVDrmPluginTest, MuxesUnprovisioningErrors) { .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)); WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false); - status_t res = plugin.unprovisionDevice(); - ASSERT_NE(android::OK, res); + Status res = plugin.unprovisionDevice(); + ASSERT_NE(Status::OK, res); res = plugin.unprovisionDevice(); - ASSERT_NE(android::OK, res); + ASSERT_NE(Status::OK, res); res = plugin.unprovisionDevice(); - ASSERT_NE(android::OK, res); + ASSERT_NE(Status::OK, res); } TEST_F(WVDrmPluginTest, UnprovisionsOrigin) { @@ -843,8 +847,6 @@ TEST_F(WVDrmPluginTest, UnprovisionsOrigin) { StrictMock crypto; std::string appPackageName; - std::vector cert; - std::vector key; std::vector specialResponse; specialResponse.assign( kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize); @@ -868,16 +870,38 @@ TEST_F(WVDrmPluginTest, UnprovisionsOrigin) { }); } -TEST_F(WVDrmPluginTest, WillNotUnprovisionWithoutOrigin) { - // This test is only valid on SPOID-free devices. SPOID devices can - // unprovision without an origin because the empty-origin provisioning is - // not global. +TEST_F(WVDrmPluginTest, UnprovisionsGloballyWithSpoid) { + android::sp> cdm = new StrictMock(); + StrictMock crypto; + std::string appPackageName; + + std::vector specialResponse; + specialResponse.assign( + kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize); + + EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) + .WillRepeatedly(DoAll(SetArgPointee<2>(kDeviceId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) + .Times(1); + EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN))) + .Times(1); + + WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, true); + plugin.provideProvisionResponse( + toHidlVec(specialResponse), + [&](Status status, hidl_vec /* cert */, + hidl_vec /* key */) { + EXPECT_EQ(Status::OK, status); + }); +} + +TEST_F(WVDrmPluginTest, WillNotUnprovisionWithoutOriginOrSpoid) { android::sp> cdm = new StrictMock(); StrictMock crypto; std::string appPackageName; - std::vector cert; - std::vector key; std::vector specialResponse; specialResponse.assign( kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize); @@ -899,8 +923,6 @@ TEST_F(WVDrmPluginTest, MuxesOriginUnprovisioningErrors) { StrictMock crypto; std::string appPackageName; - std::vector cert; - std::vector key; std::vector specialResponse; specialResponse.assign( kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize); @@ -1053,7 +1075,6 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { CdmQueryMap l3Map; l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3; - static const std::string deviceId("0123456789\0ABCDEF", 17); static const std::string systemId = "The Universe"; static const std::string provisioningId("Life\0&Everything", 16); static const std::string openSessions = "42"; @@ -1074,7 +1095,7 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { testing::Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) - .WillOnce(DoAll(SetArgPointee<2>(deviceId), + .WillOnce(DoAll(SetArgPointee<2>(kDeviceId), testing::Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SYSTEM_ID, _)) @@ -1142,36 +1163,36 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { plugin.getPropertyString( hidl_string("securityLevel"), - [&](Status status, hidl_string stringResult) { + [&](Status status, hidl_string stringResult) { ASSERT_EQ(Status::OK, status); EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L1.c_str(), stringResult.c_str()); }); plugin.getPropertyString( hidl_string("securityLevel"), - [&](Status status, hidl_string stringResult) { + [&](Status status, hidl_string stringResult) { ASSERT_EQ(Status::OK, status); EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L3.c_str(), stringResult.c_str()); }); plugin.getPropertyByteArray( hidl_string("deviceUniqueId"), - [&](Status status, hidl_vec vectorResult) { + [&](Status status, hidl_vec vectorResult) { ASSERT_EQ(Status::OK, status); std::vector id(vectorResult); - EXPECT_THAT(id, ElementsAreArray(deviceId.data(), deviceId.size())); + EXPECT_THAT(id, ElementsAreArray(kDeviceId.data(), kDeviceId.size())); }); plugin.getPropertyString( hidl_string("systemId"), - [&](Status status, hidl_string stringResult) { + [&](Status status, hidl_string stringResult) { ASSERT_EQ(Status::OK, status); EXPECT_STREQ(systemId.c_str(), stringResult.c_str()); }); plugin.getPropertyByteArray( hidl_string("provisioningUniqueId"), - [&](Status status, hidl_vec vectorResult) { + [&](Status status, hidl_vec vectorResult) { ASSERT_EQ(Status::OK, status); std::vector id(vectorResult); EXPECT_THAT(id, ElementsAreArray(provisioningId.data(), @@ -1180,21 +1201,21 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { plugin.getPropertyString( hidl_string("numberOfOpenSessions"), - [&](Status status, hidl_string stringResult) { + [&](Status status, hidl_string stringResult) { ASSERT_EQ(Status::OK, status); EXPECT_EQ(openSessions, stringResult.c_str()); }); plugin.getPropertyString( hidl_string("maxNumberOfSessions"), - [&](Status status, hidl_string stringResult) { + [&](Status status, hidl_string stringResult) { ASSERT_EQ(Status::OK, status); EXPECT_EQ(maxSessions, stringResult.c_str()); }); plugin.getPropertyString( hidl_string("oemCryptoApiVersion"), - [&](Status status, hidl_string stringResult) { + [&](Status status, hidl_string stringResult) { ASSERT_EQ(Status::OK, status); EXPECT_STREQ(oemCryptoApiVersion.c_str(), stringResult.c_str()); }); @@ -1215,7 +1236,7 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { plugin.getPropertyByteArray( hidl_string("metrics"), - [&](Status status, hidl_vec vectorResult) { + [&](Status status, hidl_vec vectorResult) { ASSERT_EQ(Status::OK, status); std::vector id(vectorResult); EXPECT_THAT(id, ElementsAreArray(serializedMetrics.data(), @@ -1271,6 +1292,117 @@ TEST_F(WVDrmPluginTest, DoesNotSetUnknownProperties) { ASSERT_NE(Status::OK, status); } +TEST_F(WVDrmPluginTest, CompliesWithSpoidVariability) { + StrictMock crypto; + + const std::string kDeviceIds[] = { + kDeviceId, + kDeviceId + " the Second", + }; + const size_t kDeviceCount = N_ELEM(kDeviceIds); + + const std::string kAppNames[] = { + std::string("com.google.widevine"), + std::string("com.youtube"), + }; + const size_t kAppCount = N_ELEM(kAppNames); + + const std::string kOrigins[] = { + kOrigin, + kOrigin + " but not that one, the other one.", + std::string(/* Intentionally Empty */), + }; + const size_t kOriginCount = N_ELEM(kOrigins); + + const size_t kPluginCount = 2; + + const size_t kPluginsPerCdm = kAppCount * kOriginCount * kPluginCount; + + // We will get kPluginCount SPOIDs for every app package name + device id + + // origin combination. + std::vector + spoids[kDeviceCount][kAppCount][kOriginCount][kPluginCount]; + + for (size_t deviceIndex = 0; deviceIndex < kDeviceCount; ++deviceIndex) { + const std::string& deviceId = kDeviceIds[deviceIndex]; + + android::sp> cdm = new StrictMock(); + + EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) + .Times(AtLeast(kPluginsPerCdm)) + .WillRepeatedly(DoAll(SetArgPointee<2>(deviceId), + testing::Return(wvcdm::NO_ERROR))); + + for (size_t appIndex = 0; appIndex < kAppCount; ++appIndex) { + const std::string& appPackageName = kAppNames[appIndex]; + + for (size_t originIndex = 0; originIndex < kOriginCount; ++originIndex) { + const std::string& origin = kOrigins[originIndex]; + + for (size_t pluginIndex = 0; + pluginIndex < kPluginCount; + ++pluginIndex) { + WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, true); + + if (!origin.empty()) { + ASSERT_EQ(Status::OK, + plugin.setPropertyString(hidl_string("origin"), + hidl_string(origin))); + } + + plugin.getPropertyByteArray( + hidl_string("deviceUniqueId"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + spoids[deviceIndex][appIndex][originIndex][pluginIndex] = + vectorResult; + }); + } + } + } + } + + // This nest of loops makes sure all the SPOIDs we retrieved above are + // identical if their parameters were identical and dissimilar otherwise. + for (size_t deviceIndex = 0; deviceIndex < kDeviceCount; ++deviceIndex) { + for (size_t appIndex = 0; appIndex < kAppCount; ++appIndex) { + for (size_t originIndex = 0; originIndex < kOriginCount; ++originIndex) { + for (size_t pluginIndex = 0; + pluginIndex < kPluginCount; + ++pluginIndex) { + const std::vector& firstSpoid = + spoids[deviceIndex][appIndex][originIndex][pluginIndex]; + + for (size_t deviceIndex2 = 0; + deviceIndex2 < kDeviceCount; + ++deviceIndex2) { + for (size_t appIndex2 = 0; appIndex2 < kAppCount; ++appIndex2) { + for (size_t originIndex2 = 0; + originIndex2 < kOriginCount; + ++originIndex2) { + for (size_t pluginIndex2 = 0; + pluginIndex2 < kPluginCount; + ++pluginIndex2) { + const std::vector& secondSpoid = + spoids[deviceIndex2][appIndex2][originIndex2][pluginIndex2]; + + if (deviceIndex == deviceIndex2 && + appIndex == appIndex2 && + originIndex == originIndex2) { + EXPECT_EQ(firstSpoid, secondSpoid); + } else { + EXPECT_NE(firstSpoid, secondSpoid); + } + } + } + } + } + } + } + } + } +} + TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) { android::sp> cdm = new StrictMock(); StrictMock crypto; @@ -2235,7 +2367,7 @@ TEST_F(WVDrmPluginTest, CanSetSessionSharing) { } WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false); - status_t res; + Status res; // Test turning on session sharing Status status = plugin.setPropertyString(hidl_string("sessionSharing"), diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index 8fca7c9b..525e93ff 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -1709,7 +1709,7 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, size_t dataLength); * * Parameters: * session (in) - crypto session identifier. - * nonce (in) - The nonce provided in the provisioning response. + * unaligned_nonce (in) - The nonce provided in the provisioning response. * encrypted_message_key (in) - message_key encrypted by private key * - from OEM cert. * encrypted_message_key_length (in) - length of encrypted_message_key in @@ -1752,7 +1752,7 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, size_t dataLength); * This method is new in API version 12. */ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( - OEMCrypto_SESSION session, const uint32_t* nonce, + OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, @@ -1842,7 +1842,7 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( * - signature for message, received from the * - provisioning server. * signature_length (in) - length of the signature, in bytes. - * nonce (in) - The nonce provided in the provisioning response. + * unaligned_nonce (in) - The nonce provided in the provisioning response. * enc_rsa_key (in) - Encrypted device private RSA key received from * - the provisioning server. Format is PKCS#8 * - binary DER encoded, encrypted with the derived @@ -1883,10 +1883,10 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - const uint8_t* signature, size_t signature_length, const uint32_t* nonce, - const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, - size_t* wrapped_rsa_key_length); + const uint8_t* signature, size_t signature_length, + const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, + uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length); /* * OEMCrypto_LoadDeviceRSAKey diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_keybox_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_keybox_mock.cpp index 0577967a..318d0499 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_keybox_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_keybox_mock.cpp @@ -64,7 +64,9 @@ KeyboxError WvKeybox::Validate() { return BAD_MAGIC; } uint32_t crc_computed; - uint32_t* crc_stored = (uint32_t*)crc_; + uint32_t crc_stored; + uint8_t* crc_stored_bytes = (uint8_t*) &crc_stored; + memcpy(crc_stored_bytes, crc_, sizeof(crc_)); WidevineKeybox keybox; memset(&keybox, 0, sizeof(keybox)); memcpy(keybox.device_id_, &device_id_[0], device_id_.size()); @@ -74,9 +76,9 @@ KeyboxError WvKeybox::Validate() { crc_computed = ntohl(wvcrc32(reinterpret_cast(&keybox), sizeof(keybox) - 4)); // Drop last 4 bytes. - if (crc_computed != *crc_stored) { + if (crc_computed != crc_stored) { LOGE("[KEYBOX CRC problem: computed = %08x, stored = %08x]\n", - crc_computed, *crc_stored); + crc_computed, crc_stored); return BAD_CRC; } return NO_ERROR; diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp index b46f49e7..66381d87 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp @@ -28,6 +28,20 @@ namespace { const uint8_t kBakedInCertificateMagicBytes[] = {0xDE, 0xAD, 0xBE, 0xEF}; + +// Return uint32 referenced through a potentially unaligned pointer. +// If the pointer is NULL, return 0. +uint32_t unaligned_dereference_uint32(const uint32_t* unaligned_ptr) { + if (unaligned_ptr == NULL) return 0; + uint32_t value; + const uint8_t* src = reinterpret_cast(unaligned_ptr); + uint8_t* dest = reinterpret_cast(&value); + for (unsigned long i=0; i < sizeof(value); i++) { + dest[i] = src[i]; + } + return value; +} + } // namespace namespace wvoec_mock { @@ -812,14 +826,20 @@ extern "C" OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, } extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( - OEMCrypto_SESSION session, const uint32_t* nonce, + OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length) { + uint32_t nonce = unaligned_dereference_uint32(unaligned_nonce); if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls | kLoggingTraceNonce)) { LOGI("-- OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(%d)\n", session); - LOGI("nonce = %08X;\n", *nonce); + if (unaligned_nonce != NULL) { + LOGI("nonce = %08X;\n", nonce); + } else { + LOGI("nonce = NULL;\n"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } if (wvcdm::g_cutoff >= wvcdm::LOG_VERBOSE) { dump_hex("encrypted_message_key", encrypted_message_key, encrypted_message_key_length); @@ -859,13 +879,14 @@ extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( return OEMCrypto_ERROR_INVALID_SESSION; } if (encrypted_message_key == NULL || encrypted_message_key_length == 0 || - enc_rsa_key == NULL || enc_rsa_key_iv == NULL) { + enc_rsa_key == NULL || enc_rsa_key_iv == NULL || + unaligned_nonce == NULL) { LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } // Validate nonce - if (!session_ctx->CheckNonce(*nonce)) { + if (!session_ctx->CheckNonce(nonce)) { return OEMCrypto_ERROR_INVALID_NONCE; } session_ctx->FlushNonces(); @@ -943,17 +964,23 @@ extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - const uint8_t* signature, size_t signature_length, const uint32_t* nonce, - const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, - size_t* wrapped_rsa_key_length) { + const uint8_t* signature, size_t signature_length, + const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, + uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length) { + uint32_t nonce = unaligned_dereference_uint32(unaligned_nonce); if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls | kLoggingTraceNonce)) { LOGI("-- OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(%d)\n", session); if (wvcdm::g_cutoff >= wvcdm::LOG_VERBOSE) { dump_hex("message", message, message_length); dump_hex("signature", signature, signature_length); } - LOGI("nonce = %08X;\n", *nonce); + if (unaligned_nonce != NULL) { + LOGI("nonce = %08X;\n", nonce); + } else { + LOGI("nonce = NULL;\n"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } if (wvcdm::g_cutoff >= wvcdm::LOG_VERBOSE) { dump_hex("enc_rsa_key", enc_rsa_key, enc_rsa_key_length); dump_hex("enc_rsa_key_iv", enc_rsa_key_iv, wvcdm::KEY_IV_SIZE); @@ -994,15 +1021,15 @@ extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( return OEMCrypto_ERROR_INVALID_SESSION; } if (message == NULL || message_length == 0 || signature == NULL || - signature_length == 0 || nonce == NULL || enc_rsa_key == NULL) { + signature_length == 0 || unaligned_nonce == NULL || enc_rsa_key == NULL) { LOGE("[OEMCrypto_RewrapDeviceRSAKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } // Range check if (!RangeCheck(message, message_length, - reinterpret_cast(nonce), sizeof(uint32_t), - true) || + reinterpret_cast(unaligned_nonce), + sizeof(uint32_t), true) || !RangeCheck(message, message_length, enc_rsa_key, enc_rsa_key_length, true) || !RangeCheck(message, message_length, enc_rsa_key_iv, wvcdm::KEY_IV_SIZE, @@ -1012,7 +1039,7 @@ extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( } // Validate nonce - if (!session_ctx->CheckNonce(*nonce)) { + if (!session_ctx->CheckNonce(nonce)) { return OEMCrypto_ERROR_INVALID_NONCE; } session_ctx->FlushNonces(); diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.cpp index fc8c1aba..72225ab0 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.cpp @@ -746,8 +746,9 @@ bool SessionContext::LoadRSAKey(const uint8_t* pkcs8_rsa_key, return false; } if ((memcmp(pkcs8_rsa_key, "SIGN", 4) == 0)) { - const uint32_t* schemes_n = (const uint32_t*)(pkcs8_rsa_key + 4); - allowed_schemes_ = htonl(*schemes_n); + uint32_t schemes_n; + memcpy((uint8_t*)&schemes_n, pkcs8_rsa_key + 4, sizeof(uint32_t)); + allowed_schemes_ = htonl(schemes_n); pkcs8_rsa_key += 8; rsa_key_length -= 8; } else { diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 13ad3818..54179b29 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -4375,7 +4375,7 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { } } - void TestWithKey(int key_index) { + void TestWithKey(unsigned int key_index) { ASSERT_LT(key_index, session_.num_keys()); EncryptAndLoadKeys(); vector encrypted;