From 16a4c2690a0fda0564cace10f279baed21fb1607 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Sat, 3 Dec 2022 00:46:22 +0000 Subject: [PATCH] Revert "Core CDM: Removed secure stop support." This reverts commit b039f31b27be07614a26d3951abe8d12ada9a043. Reason for revert: Feature rejected by Android Bug: 242289743 Change-Id: I8cd6014b4e2de93b3c574d407d6c8885863fed4f --- libwvdrmengine/cdm/core/include/cdm_engine.h | 43 +- libwvdrmengine/cdm/core/include/cdm_session.h | 34 +- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 496 ++++++++++++++++-- libwvdrmengine/cdm/core/src/cdm_session.cpp | 217 ++++++-- libwvdrmengine/cdm/core/src/license.cpp | 4 +- .../cdm/core/test/cdm_session_unittest.cpp | 5 +- 6 files changed, 660 insertions(+), 139 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index bd355d68..2bc0a7a8 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -127,10 +127,10 @@ class CdmEngine { // stored offline data associated with the session. virtual CdmResponseType RemoveKeys(const CdmSessionId& session_id); - // This method removes all offline data associated with the session. It - // should be used with care, as it deletes the info immediately and - // without using a release message, so the server is not able to - // receive usage info or track releases for offline licenses. + // This method removes all offline data associated with the session, such as + // offline keys and usage info. It should be used with care, as it deletes the + // info immediately and without using a release message, so the server is not + // able to receive usage info or track releases for offline licenses. virtual CdmResponseType RemoveLicense(const CdmSessionId& session_id); // Construct valid renewal request for the current session keys. @@ -235,32 +235,38 @@ class CdmEngine { virtual CdmResponseType RemoveOfflineLicense(const std::string& key_set_id, CdmSecurityLevel security_level); - // Usage related methods for streaming licenses with persistent usage - // information (deprecated). + // Usage related methods for streaming licenses + // Retrieve a random usage info from the list of all usage infos for this app + // id. If |error_detail| is not null, an additional error code may be provided + // in the event of an error. virtual CdmResponseType GetUsageInfo(const std::string& app_id, int* error_detail, CdmUsageInfo* usage_info); - // Retrieve usage info whose PST is specified by |ssid| (deprecated). + // Retrieve usage info whose PST is specified by |ssid| + // If |error_detail| is not null, an additional error code may be provided + // in the event of an error. virtual CdmResponseType GetUsageInfo(const std::string& app_id, const CdmSecureStopId& ssid, int* error_detail, CdmUsageInfo* usage_info); - // Retrieve usage info for a given security level and whose PST is - // specified by |ssid| (deprecated). + // Retrieve usage info for a given security level and whose + // PST is specified by |ssid|. + // If |error_detail| is not null, an additional error code may be provided + // in the event of an error. virtual CdmResponseType GetUsageInfo(const std::string& app_id, const CdmSecureStopId& ssid, RequestedSecurityLevel security_level, int* error_detail, CdmUsageInfo* usage_info); - // Remove all usage records for the current origin (deprecated). + // Remove all usage records for the current origin. virtual CdmResponseType RemoveAllUsageInfo(const std::string& app_id, CdmSecurityLevel security_level); // Remove all usage records for the current origin. Span all - // security levels (deprecated). + // security levels. virtual CdmResponseType RemoveAllUsageInfo(const std::string& app_id); virtual CdmResponseType RemoveUsageInfo( @@ -400,6 +406,9 @@ class CdmEngine { CdmSessionId* session_id); bool ValidateKeySystem(const CdmKeySystem& key_system); + CdmResponseType GetUsageInfo(const std::string& app_id, + RequestedSecurityLevel requested_security_level, + int* error_detail, CdmUsageInfo* usage_info); void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); @@ -434,11 +443,13 @@ class CdmEngine { std::string spoid_; uint32_t user_id_; - // Tracks the time of the last update to usage info across all - // actively managed CDM sessions with loaded usage entries. Used - // to periodically update a usage entry associated with an active - // license. - int64_t last_usage_info_update_time_ = 0; + // Usage related variables + // Used to isolate a single active usage information license. Loading, + // creating or releasing a different usage licenses through the engine + // API will release the handle to previously active secure stop license. + std::unique_ptr usage_session_; + std::unique_ptr usage_property_set_; + int64_t last_usage_information_update_time_; // Protect release_key_sets_ from non-thread-safe operations. std::mutex release_key_sets_lock_; diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index d551dbd2..c98c8030 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -75,6 +75,12 @@ class CdmSession { virtual CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id, CdmLicenseType license_type, int* error_detail); + // Restores an usage session from the provided |usage_data|. + // The |error_detail| will be filled with an internal error code. The + // |error_detail| may be a CdmResponseType or other error code type. It is + // only suitable for additional logging or debugging. + virtual CdmResponseType RestoreUsageSession( + const DeviceFiles::CdmUsageData& usage_data, int* error_detail); virtual const CdmSessionId& session_id() { return session_id_; } virtual const CdmKeySetId& key_set_id() { return key_set_id_; } @@ -144,7 +150,7 @@ class CdmSession { virtual bool is_initial_usage_update() { return is_initial_usage_update_; } virtual bool is_usage_update_needed() { return is_usage_update_needed_; } - virtual void ResetUsageFlags() { + virtual void reset_usage_flags() { is_initial_usage_update_ = false; is_usage_update_needed_ = false; } @@ -153,19 +159,12 @@ class CdmSession { virtual bool is_offline() { return is_offline_; } virtual bool is_temporary() { return is_temporary_; } virtual bool license_received() { return license_received_; } - - virtual bool HasUsageEntry() { - // The PST is only set if a usage entry has been loaded. - return provider_session_token_.size() > 0; - } - virtual const std::string& provider_session_token() { - return provider_session_token_; - } - void SetProviderSessionTokenForTest(const std::string& pst) { - provider_session_token_ = pst; + virtual bool has_provider_session_token() { + return (license_parser_ && + license_parser_->provider_session_token().size() > 0); } - virtual bool SupportsUsageEntries() const { + virtual bool supports_usage_info() const { return usage_table_header_ != nullptr; } @@ -244,6 +243,8 @@ class CdmSession { bool StoreLicense(CdmOfflineLicenseState state, int* error_detail); + bool UpdateUsageInfo(); + CdmResponseType GenerateKeyRequestInternal( const InitializationData& init_data, CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request); @@ -304,15 +305,14 @@ class CdmSession { bool last_decrypt_failed_ = false; // Usage related flags and data - bool is_initial_usage_update_ = true; - bool is_usage_update_needed_ = false; + bool is_initial_usage_update_; + bool is_usage_update_needed_; // Only assign |usage_table_header_| if capable of supporting usage // information. UsageTableHeader* usage_table_header_ = nullptr; - uint32_t usage_entry_number_ = 0; + uint32_t usage_entry_number_; CdmUsageEntry usage_entry_; - // This PST should only be set if the session has a usage entry. - std::string provider_session_token_; + std::string usage_provider_session_token_; // information useful for offline and usage scenarios CdmKeyMessage key_request_; diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index a5a8c451..726763c8 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -31,8 +31,8 @@ namespace wvcdm { namespace { -constexpr uint64_t kReleaseSessionTimeToLive = 60; // seconds -constexpr uint32_t kUpdateUsageInfoPeriod = 60; // seconds +const uint64_t kReleaseSessionTimeToLive = 60; // seconds +const uint32_t kUpdateUsageInformationPeriod = 60; // seconds } // namespace class UsagePropertySet : public CdmClientPropertySet { @@ -67,12 +67,16 @@ CdmEngine::CdmEngine(wvutil::FileSystem* file_system, : metrics_(metrics), cert_provisioning_(), file_system_(file_system), - spoid_(EMPTY_SPOID) { + spoid_(EMPTY_SPOID), + usage_session_(), + usage_property_set_(), + last_usage_information_update_time_(0) { assert(file_system); Properties::Init(); } CdmEngine::~CdmEngine() { + usage_session_.reset(); std::unique_lock lock(session_map_lock_); session_map_.Terminate(); } @@ -411,7 +415,8 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, } if (key_set_id != nullptr) { - if ((session->is_offline()) && !license_type_release) { + if ((session->is_offline() || session->has_provider_session_token()) && + !license_type_release) { *key_set_id = session->key_set_id(); LOGI("key_set_id = %s", IdPtrToString(key_set_id)); } else { @@ -1279,21 +1284,50 @@ CdmResponseType CdmEngine::ListStoredLicenses( CdmResponseType CdmEngine::ListUsageIds( const std::string& app_id, CdmSecurityLevel security_level, - std::vector* /* ksids */, - std::vector* /* provider_session_tokens */) { - LOGI("app_id = %s, security_level = %s", IdToString(app_id), - CdmSecurityLevelToString(security_level)); - LOGW("API not supported"); - return NOT_IMPLEMENTED_ERROR; + std::vector* ksids, + std::vector* provider_session_tokens) { + if (!ksids && !provider_session_tokens) { + LOGE("Outputs |ksids| and |provider_session_tokens| are null"); + return INVALID_PARAMETERS_ENG_23; + } + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } + DeviceFiles handle(file_system_); + if (!handle.Init(security_level)) { + LOGE("Unable to initialize device files"); + return LIST_USAGE_ERROR_1; + } + if (!handle.ListUsageIds(app_id, ksids, provider_session_tokens)) { + LOGE("Failed: app_id = %s, security_level = %s", IdToString(app_id), + CdmSecurityLevelToString(security_level)); + return LIST_USAGE_ERROR_2; + } + return NO_ERROR; } CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id, CdmSecurityLevel security_level, const std::string& key_set_id) { - LOGI("app_id = %s, security_level = %s, key_set_id = %s", IdToString(app_id), - CdmSecurityLevelToString(security_level), IdToString(key_set_id)); - LOGW("API not supported"); - return NOT_IMPLEMENTED_ERROR; + LOGI("app_id = %s, key_set_id = %s", IdToString(app_id), + IdToString(key_set_id)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } + DeviceFiles handle(file_system_); + if (!handle.Init(security_level)) { + LOGE("Unable to initialize device files"); + return DELETE_USAGE_ERROR_1; + } + std::string provider_session_token; + if (!handle.GetProviderSessionToken(app_id, key_set_id, + &provider_session_token)) { + LOGE("GetProviderSessionToken failed"); + return DELETE_USAGE_ERROR_2; + } + return RemoveUsageInfo(app_id, provider_session_token); } CdmResponseType CdmEngine::GetOfflineLicenseState( @@ -1363,7 +1397,7 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( } } else if (sts == LICENSE_USAGE_ENTRY_MISSING) { // It is possible that the CDM is tracking a key set ID, but has - // removed the usage entry associated with it. In this case, + // removed the usage information associated with it. In this case, // it will no longer be possible to load the license for release; // and the file should simply be deleted. LOGW("License usage entry is missing, deleting license file"); @@ -1381,66 +1415,415 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, const CdmSecureStopId& ssid, - int* /* error_detail */, - CdmUsageInfo* /* usage_info */) { - LOGI("app_id = %s, ssid = %s", IdToString(app_id), IdToString(ssid)); - LOGE("API not supported"); - return NOT_IMPLEMENTED_ERROR; + int* error_detail, + CdmUsageInfo* usage_info) { + // Try to find usage info at the default security level. If the + // security level is unprovisioned or we are unable to find it, + // try L3. + CdmResponseType status = + GetUsageInfo(app_id, ssid, kLevelDefault, error_detail, usage_info); + switch (status) { + case NEED_PROVISIONING: + case GET_USAGE_INFO_ERROR_1: + case GET_USAGE_INFO_ERROR_2: + case USAGE_INFO_NOT_FOUND: + status = GetUsageInfo(app_id, ssid, kLevel3, error_detail, usage_info); + return status; + default: + return status; + } } CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, const CdmSecureStopId& ssid, RequestedSecurityLevel security_level, - int* /* error_detail */, - CdmUsageInfo* /* usage_info */) { - LOGI("app_id = %s, ssid = %s, security_level = %s", IdToString(app_id), - IdToString(ssid), RequestedSecurityLevelToString(security_level)); - LOGE("API not supported"); - return NOT_IMPLEMENTED_ERROR; + int* error_detail, + CdmUsageInfo* usage_info) { + LOGI("app_id = %s, ssid = %s", IdToString(app_id), IdToString(ssid)); + if (!usage_property_set_) { + usage_property_set_.reset(new UsagePropertySet()); + } + if (usage_info == nullptr) { + LOGE("Output |usage_info| is null"); + return PARAMETER_NULL; + } + usage_property_set_->set_security_level(security_level); + usage_property_set_->set_app_id(app_id); + usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession())); + CdmResponseType status = usage_session_->Init(usage_property_set_.get()); + if (NO_ERROR != status) { + LOGE("Session init error: status = %d", static_cast(status)); + return status; + } + DeviceFiles handle(file_system_); + if (!handle.Init(usage_session_->GetSecurityLevel())) { + LOGE("Device file init error"); + return GET_USAGE_INFO_ERROR_1; + } + + DeviceFiles::CdmUsageData usage_data; + if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), ssid, + &usage_data)) { + usage_property_set_->set_security_level(kLevel3); + usage_property_set_->set_app_id(app_id); + usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession())); + status = usage_session_->Init(usage_property_set_.get()); + if (NO_ERROR != status) { + LOGE("Session init error: status = %d", static_cast(status)); + return status; + } + if (!handle.Reset(usage_session_->GetSecurityLevel())) { + LOGE("Device file init error"); + return GET_USAGE_INFO_ERROR_2; + } + if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), + ssid, &usage_data)) { + // No entry found for that ssid. + return USAGE_INFO_NOT_FOUND; + } + } + + status = usage_session_->RestoreUsageSession(usage_data, error_detail); + + if (KEY_ADDED != status) { + LOGE("RestoreUsageSession failed: status = %d", static_cast(status)); + usage_info->clear(); + return status; + } + + CdmKeyRequest request; + status = usage_session_->GenerateReleaseRequest(&request); + + usage_info->clear(); + usage_info->push_back(request.message); + + if (KEY_MESSAGE != status) { + LOGE("GenerateReleaseRequest failed: status = %d", + static_cast(status)); + usage_info->clear(); + return status; + } + + return KEY_MESSAGE; } CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, - int* /* error_detail */, - CdmUsageInfo* /* usage_info */) { + int* error_detail, + CdmUsageInfo* usage_info) { LOGI("app_id = %s", IdToString(app_id)); - LOGE("API not supported"); - return NOT_IMPLEMENTED_ERROR; + if (usage_info == nullptr) { + LOGE("Output |usage_info| is null"); + return PARAMETER_NULL; + } + // Return a random usage report from a random security level + RequestedSecurityLevel security_level = + wvutil::CdmRandom::RandomBool() ? kLevelDefault : kLevel3; + CdmResponseType status = UNKNOWN_ERROR; + do { + status = GetUsageInfo(app_id, security_level, error_detail, usage_info); + if (KEY_MESSAGE == status && !usage_info->empty()) { + return status; + } + } while (KEY_CANCELED == status); + + security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3; + do { + status = GetUsageInfo(app_id, security_level, error_detail, usage_info); + if (NEED_PROVISIONING == status) + return NO_ERROR; // Valid scenario that one of the security + // levels has not been provisioned + } while (KEY_CANCELED == status); + return status; +} + +CdmResponseType CdmEngine::GetUsageInfo( + const std::string& app_id, RequestedSecurityLevel requested_security_level, + int* error_detail, CdmUsageInfo* usage_info) { + LOGI("app_id = %s, security_level = %s", IdToString(app_id), + RequestedSecurityLevelToString(requested_security_level)); + if (usage_info == nullptr) { + LOGE("Output |usage_info| is null"); + return PARAMETER_NULL; + } + if (requested_security_level == kLevelDefault && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + requested_security_level = kLevel3; + } + if (!usage_property_set_) { + usage_property_set_.reset(new UsagePropertySet()); + } + usage_property_set_->set_security_level(requested_security_level); + usage_property_set_->set_app_id(app_id); + + usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession())); + + CdmResponseType status = usage_session_->Init(usage_property_set_.get()); + if (NO_ERROR != status) { + LOGE("Session init error"); + return status; + } + + DeviceFiles handle(file_system_); + if (!handle.Init(usage_session_->GetSecurityLevel())) { + LOGE("Unable to initialize device files"); + return GET_USAGE_INFO_ERROR_3; + } + + std::vector usage_data; + if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), + &usage_data)) { + LOGE("Unable to read usage information"); + return GET_USAGE_INFO_ERROR_4; + } + + if (usage_data.empty()) { + usage_info->clear(); + return NO_ERROR; + } + + const size_t index = wvutil::CdmRandom::RandomInRange(usage_data.size() - 1); + status = usage_session_->RestoreUsageSession(usage_data[index], error_detail); + if (KEY_ADDED != status) { + // TODO(b/141704872): Make multiple attempts. + LOGE("RestoreUsageSession failed: index = %zu, status = %d", index, + static_cast(status)); + usage_info->clear(); + return status; + } + + CdmKeyRequest request; + status = usage_session_->GenerateReleaseRequest(&request); + + usage_info->clear(); + usage_info->push_back(request.message); + + switch (status) { + case KEY_MESSAGE: + break; + case KEY_CANCELED: // usage information not present in + usage_session_->DeleteLicenseFile(); // OEMCrypto, delete and try again + usage_info->clear(); + break; + default: + LOGE("GenerateReleaseRequest failed: status = %d", + static_cast(status)); + usage_info->clear(); + break; + } + return status; } CdmResponseType CdmEngine::RemoveAllUsageInfo( const std::string& app_id, CdmSecurityLevel cdm_security_level) { LOGI("app_id = %s, security_level = %s", IdToString(app_id), CdmSecurityLevelToString(cdm_security_level)); - LOGE("API not supported"); - return NOT_IMPLEMENTED_ERROR; + if (!usage_property_set_) { + usage_property_set_.reset(new UsagePropertySet()); + } + usage_property_set_->set_app_id(app_id); + + CdmResponseType status = NO_ERROR; + DeviceFiles handle(file_system_); + if (handle.Init(cdm_security_level)) { + const RequestedSecurityLevel security_level = + cdm_security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault; + usage_property_set_->set_security_level(security_level); + usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession())); + usage_session_->Init(usage_property_set_.get()); + + if (usage_session_->supports_usage_info()) { + std::vector usage_data; + // 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)) { + LOGW("Failed to retrieve usage info"); + break; + } + + if (usage_data.empty()) break; + + CdmResponseType res = + usage_session_->DeleteUsageEntry(usage_data[0].usage_entry_number); + + if (res != NO_ERROR) { + LOGW("Failed to delete usage entry: status = %d", + static_cast(res)); + break; + } + + if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), + usage_data[0].provider_session_token)) { + LOGW("Failed to delete usage info"); + break; + } + } while (!usage_data.empty()); + + std::vector provider_session_tokens; + if (!handle.DeleteAllUsageInfoForApp( + DeviceFiles::GetUsageInfoFileName(app_id), + &provider_session_tokens)) { + status = REMOVE_ALL_USAGE_INFO_ERROR_5; + } + } + } + usage_session_.reset(); + return status; } CdmResponseType CdmEngine::RemoveAllUsageInfo(const std::string& app_id) { LOGI("app_id = %s", IdToString(app_id)); - LOGE("API not supported"); - return NOT_IMPLEMENTED_ERROR; + const CdmResponseType status_l1 = + RemoveAllUsageInfo(app_id, kSecurityLevelL1); + const CdmResponseType status_l3 = + RemoveAllUsageInfo(app_id, kSecurityLevelL3); + // Prioritizing L1 status. + if (status_l1 != NO_ERROR) { + return status_l1; + } + return status_l3; } CdmResponseType CdmEngine::RemoveUsageInfo( const std::string& app_id, const CdmSecureStopId& provider_session_token) { LOGI("app_id = %s, pst = %s", IdToString(app_id), IdToString(provider_session_token)); - LOGE("API not supported"); - return NOT_IMPLEMENTED_ERROR; + if (!usage_property_set_) { + usage_property_set_.reset(new UsagePropertySet()); + } + usage_property_set_->set_app_id(app_id); + + CdmResponseType status = NO_ERROR; + for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) { + DeviceFiles handle(file_system_); + if (handle.Init(static_cast(j))) { + RequestedSecurityLevel security_level = + static_cast(j) == kSecurityLevelL3 ? kLevel3 + : kLevelDefault; + usage_property_set_->set_security_level(security_level); + usage_session_.reset( + new CdmSession(file_system_, metrics_->AddSession())); + usage_session_->Init(usage_property_set_.get()); + + CdmKeyMessage license_request; + CdmKeyResponse license_response; + CdmUsageEntry usage_entry; + uint32_t usage_entry_number; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; + + if (!handle.RetrieveUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), provider_session_token, + &license_request, &license_response, &usage_entry, + &usage_entry_number, &drm_certificate, &wrapped_private_key)) { + // Try other security level + continue; + } + + if (usage_session_->supports_usage_info()) { + status = usage_session_->DeleteUsageEntry(usage_entry_number); + if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), + provider_session_token)) { + status = REMOVE_USAGE_INFO_ERROR_1; + } + usage_session_.reset(); + return status; + } + } else { + LOGE("Failed to initialize L%d device files", j); + status = REMOVE_USAGE_INFO_ERROR_2; + } + } + usage_session_.reset(); + return REMOVE_USAGE_INFO_ERROR_3; } CdmResponseType CdmEngine::ReleaseUsageInfo( const CdmUsageInfoReleaseMessage& message) { LOGI("message_size = %zu", message.size()); - LOGE("API not supported"); - return NOT_IMPLEMENTED_ERROR; + if (!usage_session_) { + LOGE("Usage session not initialized"); + return RELEASE_USAGE_INFO_ERROR; + } + const CdmResponseType status = usage_session_->ReleaseKey(message); + usage_session_.reset(); + if (NO_ERROR != status) { + LOGE("ReleaseKey failed: status = %d", status); + } + return status; } -CdmResponseType CdmEngine::LoadUsageSession( - const CdmKeySetId& key_set_id, CdmKeyMessage* /* release_message */) { +CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, + CdmKeyMessage* release_message) { LOGI("key_set_id = %s", IdToString(key_set_id)); - LOGE("API not supported"); - return NOT_IMPLEMENTED_ERROR; + // This method is currently only used by the CE CDM, in which all session IDs + // are key set IDs. + assert(Properties::AlwaysUseKeySetIds()); + if (key_set_id.empty()) { + LOGE("Invalid key set ID"); + return EMPTY_KEYSET_ID_ENG_5; + } + if (release_message == nullptr) { + LOGE("Output |release_message| is null"); + return PARAMETER_NULL; + } + + std::shared_ptr session; + if (!session_map_.FindSession(key_set_id, &session)) { + LOGE("Session not found: key_set_id = %s", IdToString(key_set_id)); + return SESSION_NOT_FOUND_11; + } + + DeviceFiles handle(file_system_); + if (!handle.Init(session->GetSecurityLevel())) { + LOGE("Unable to initialize device files"); + return LOAD_USAGE_INFO_FILE_ERROR; + } + + std::string app_id; + session->GetApplicationId(&app_id); + + DeviceFiles::CdmUsageData usage_data; + if (!handle.RetrieveUsageInfoByKeySetId( + DeviceFiles::GetUsageInfoFileName(app_id), key_set_id, + &(usage_data.provider_session_token), &(usage_data.license_request), + &(usage_data.license), &(usage_data.usage_entry), + &(usage_data.usage_entry_number), &(usage_data.drm_certificate), + &(usage_data.wrapped_private_key))) { + LOGE("Unable to find usage information"); + return LOAD_USAGE_INFO_MISSING; + } + + int error_detail = NO_ERROR; + usage_data.key_set_id = key_set_id; + CdmResponseType status = + session->RestoreUsageSession(usage_data, &error_detail); + session->GetMetrics()->cdm_session_restore_usage_session_.Increment( + status, error_detail); + if (KEY_ADDED != status) { + LOGE("Restore failed: key_set_id = %s, status = %d", IdToString(key_set_id), + static_cast(status)); + return status; + } + + CdmKeyRequest request; + status = session->GenerateReleaseRequest(&request); + *release_message = std::move(request.message); + switch (status) { + case KEY_MESSAGE: + break; + case KEY_CANCELED: + // usage information not present in OEMCrypto, delete and try again + session->DeleteLicenseFile(); + break; + default: + LOGE("GenerateReleaseRequest failed: status = %d", + static_cast(status)); + break; + } + return status; } CdmResponseType CdmEngine::DecryptV16( @@ -1689,7 +2072,6 @@ bool CdmEngine::FindSessionForKey(const KeyId& key_id, bool CdmEngine::NotifyResolution(const CdmSessionId& session_id, uint32_t width, uint32_t height) { - std::unique_lock lock(session_map_lock_); std::shared_ptr session; if (session_map_.FindSession(session_id, &session)) { session->NotifyResolution(width, height); @@ -1706,9 +2088,10 @@ void CdmEngine::OnTimerEvent() { wvutil::Clock clock; const uint64_t current_time = clock.GetCurrentTime(); bool usage_update_period_expired = false; - if (current_time - last_usage_info_update_time_ > kUpdateUsageInfoPeriod) { + if (current_time - last_usage_information_update_time_ > + kUpdateUsageInformationPeriod) { usage_update_period_expired = true; - last_usage_info_update_time_ = current_time; + last_usage_information_update_time_ = current_time; } bool is_initial_usage_update = false; @@ -1718,24 +2101,27 @@ void CdmEngine::OnTimerEvent() { CdmSessionList sessions; session_map_.GetSessionList(sessions); - for (auto& session : sessions) { - is_initial_usage_update = - is_initial_usage_update || session->is_initial_usage_update(); + while (!sessions.empty()) { + is_initial_usage_update = is_initial_usage_update || + sessions.front()->is_initial_usage_update(); is_usage_update_needed = - is_usage_update_needed || session->is_usage_update_needed(); - session->OnTimerEvent(usage_update_period_expired); + is_usage_update_needed || sessions.front()->is_usage_update_needed(); + + sessions.front()->OnTimerEvent(usage_update_period_expired); + sessions.pop_front(); } if (is_usage_update_needed && (usage_update_period_expired || is_initial_usage_update)) { // Session list may have changed. Rebuild. - sessions.clear(); session_map_.GetSessionList(sessions); - for (auto& session : sessions) { - session->ResetUsageFlags(); - if (session->SupportsUsageEntries() && session->HasUsageEntry()) { - session->UpdateUsageEntryInformation(); + for (CdmSessionList::iterator iter = sessions.begin(); + iter != sessions.end(); ++iter) { + (*iter)->reset_usage_flags(); + if ((*iter)->supports_usage_info() && + (*iter)->has_provider_session_token()) { + (*iter)->UpdateUsageEntryInformation(); } } } diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index a1bc5203..87659180 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -78,6 +78,10 @@ CdmSession::CdmSession(wvutil::FileSystem* file_system, is_temporary_(false), security_level_(kSecurityLevelUninitialized), requested_security_level_(kLevelDefault), + is_initial_usage_update_(true), + is_usage_update_needed_(false), + usage_table_header_(nullptr), + usage_entry_number_(0), mock_license_parser_in_use_(false), mock_policy_engine_in_use_(false) { assert(metrics_); // metrics_ must not be null. @@ -87,7 +91,7 @@ CdmSession::CdmSession(wvutil::FileSystem* file_system, } CdmSession::~CdmSession() { - if (HasUsageEntry() && !is_release_) { + if (has_provider_session_token() && supports_usage_info() && !is_release_) { UpdateUsageEntryInformation(); } @@ -265,9 +269,9 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, return GET_RELEASED_LICENSE_ERROR; } + std::string provider_session_token; bool sign_fake_request = false; // TODO(b/169483174): remove this variable. - if (SupportsUsageEntries()) { - std::string provider_session_token; + if (supports_usage_info()) { if (!license_parser_->ExtractProviderSessionToken( key_response_, &provider_session_token)) { provider_session_token.clear(); @@ -284,7 +288,6 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, IdToString(key_set_id)); return USAGE_ENTRY_ALREADY_LOADED; } - provider_session_token_ = std::move(provider_session_token); if (sts != NO_ERROR) { LOGE("Failed to load usage entry: status = %d", static_cast(sts)); return sts; @@ -293,10 +296,6 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, } else { sign_fake_request = true; // TODO(b/169483174): remove this block. } - // Must be set before updating the usage entry. CdmLicense will attempt - // to update the usage info when restoring. - is_offline_ = true; - // TODO(b/169483174): remove this code in v17. For OEMCrypto v16, an offline // license would not work because the rental clock in OEMCrypto is only // started when the license request is signed. We will sign a fake license @@ -333,7 +332,7 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, } } - if (HasUsageEntry()) { + if (!provider_session_token.empty() && supports_usage_info()) { CdmResponseType sts = usage_table_header_->UpdateEntry( usage_entry_number_, crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { @@ -341,16 +340,74 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, return sts; } if (!StoreLicense(license_data.state, error_detail)) { - LOGW("Unable to save updated license"); + LOGW("Unable to save updated usage info"); } } license_received_ = true; + is_offline_ = true; is_release_ = license_type == kLicenseTypeRelease; has_license_been_restored_ = true; return KEY_ADDED; } +CdmResponseType CdmSession::RestoreUsageSession( + const DeviceFiles::CdmUsageData& usage_data, int* error_detail) { + if (!initialized_) { + LOGE("CDM session not initialized"); + return NOT_INITIALIZED_ERROR; + } + if (!key_set_id_.empty()) { + file_handle_->UnreserveLicenseId(key_set_id_); + } + key_set_id_ = usage_data.key_set_id; + key_request_ = usage_data.license_request; + key_response_ = usage_data.license; + usage_entry_ = usage_data.usage_entry; + usage_entry_number_ = usage_data.usage_entry_number; + usage_provider_session_token_ = usage_data.provider_session_token; + + CdmResponseType status = LoadPrivateOrLegacyKey( + usage_data.drm_certificate, usage_data.wrapped_private_key); + if (status != NO_ERROR) return status; + + CdmResponseType sts = NO_ERROR; + if (supports_usage_info()) { + sts = usage_table_header_->LoadEntry(crypto_session_.get(), usage_entry_, + usage_entry_number_); + crypto_metrics_->usage_table_header_load_entry_.Increment(sts); + if (sts != NO_ERROR) { + LOGE("Failed to load usage entry: status = %d", static_cast(sts)); + return sts; + } + } + + sts = license_parser_->RestoreLicenseForRelease(usage_data.drm_certificate, + key_request_, key_response_); + + if (sts != NO_ERROR) { + SetErrorDetail(error_detail, sts); + return RELEASE_LICENSE_ERROR_2; + } + + if (supports_usage_info()) { + sts = usage_table_header_->UpdateEntry( + usage_entry_number_, crypto_session_.get(), &usage_entry_); + if (sts != NO_ERROR) { + LOGE("Failed to update usage entry: status = %d", static_cast(sts)); + return sts; + } + if (!UpdateUsageInfo()) { + LOGW("Unable to save updated usage info"); + } + } + + license_received_ = true; + is_offline_ = false; + is_release_ = true; + return KEY_ADDED; +} + // This is a thin wrapper that initiates the latency metric. CdmResponseType CdmSession::GenerateKeyRequest( const InitializationData& init_data, CdmLicenseType license_type, @@ -475,21 +532,19 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { // to see if it has a provider session token. If so a new entry needs // to be created. CdmResponseType sts; - if (SupportsUsageEntries()) { - std::string provider_session_token; + std::string provider_session_token; + if (supports_usage_info()) { if (license_parser_->ExtractProviderSessionToken(key_response, &provider_session_token) && !provider_session_token.empty()) { - if (!is_offline_) { - LOGE("CDM does not support secure stop licenses"); - return ADD_KEY_ERROR; - } + std::string app_id; + GetApplicationId(&app_id); sts = usage_table_header_->AddEntry( - crypto_session_.get(), /* is_persistent */ true, key_set_id_, - /* usage_info_filename */ "", key_response, &usage_entry_number_); + crypto_session_.get(), is_offline_, key_set_id_, + DeviceFiles::GetUsageInfoFileName(app_id), key_response, + &usage_entry_number_); crypto_metrics_->usage_table_header_add_entry_.Increment(sts); if (sts != NO_ERROR) return sts; - provider_session_token_ = std::move(provider_session_token); } } sts = @@ -502,14 +557,13 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { version_info.license_service_version()); // Update or invalidate entry if usage table header+entries are supported - if (HasUsageEntry()) { + if (!provider_session_token.empty() && supports_usage_info()) { if (sts != KEY_ADDED) { const CdmResponseType invalidate_sts = usage_table_header_->InvalidateEntry( usage_entry_number_, true, file_handle_.get(), crypto_metrics_); crypto_metrics_->usage_table_header_delete_entry_.Increment( invalidate_sts); - provider_session_token_.clear(); if (invalidate_sts != NO_ERROR) { LOGW("Invalidate usage entry failed: status = %d", static_cast(invalidate_sts)); @@ -526,11 +580,15 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { IdToString(license_parser_->provider_session_token()), license_parser_->provider_session_token().size()); - if (is_offline_ && !is_temporary_) { - if (HasUsageEntry()) { + if ((is_offline_ || has_provider_session_token()) && !is_temporary_) { + if (has_provider_session_token() && supports_usage_info()) { usage_table_header_->UpdateEntry(usage_entry_number_, crypto_session_.get(), &usage_entry_); } + + if (!is_offline_) + usage_provider_session_token_ = license_parser_->provider_session_token(); + sts = StoreLicense(); if (sts != NO_ERROR) return sts; } @@ -642,7 +700,7 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParametersV16& params) { } has_decrypted_since_last_report_ = true; if (!is_usage_update_needed_) { - is_usage_update_needed_ = HasUsageEntry(); + is_usage_update_needed_ = has_provider_session_token(); } last_decrypt_failed_ = false; } else { @@ -728,7 +786,7 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) { if (KEY_MESSAGE != status) return status; - if (HasUsageEntry()) { + if (has_provider_session_token() && supports_usage_info()) { status = usage_table_header_->UpdateEntry( usage_entry_number_, crypto_session_.get(), &usage_entry_); @@ -741,6 +799,10 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) { if (is_offline_) { // Mark license as being released if (!StoreLicense(kLicenseStateReleasing, nullptr)) return RELEASE_KEY_REQUEST_ERROR; + } else if (!usage_provider_session_token_.empty()) { + if (supports_usage_info()) { + if (!UpdateUsageInfo()) return RELEASE_USAGE_INFO_FAILED; + } } key_request_type_ = key_request->type; @@ -771,7 +833,7 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { LOGE("CDM session not initialized"); return NOT_INITIALIZED_ERROR; } - if (!SupportsUsageEntries()) { + if (!supports_usage_info()) { LOGE("Cannot delete entry, usage table not supported"); return INCORRECT_USAGE_SUPPORT_TYPE_1; } @@ -800,7 +862,6 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { sts = usage_table_header_->InvalidateEntry( usage_entry_number, true, file_handle_.get(), crypto_metrics_); crypto_metrics_->usage_table_header_delete_entry_.Increment(sts); - provider_session_token_.clear(); return sts; } @@ -847,23 +908,54 @@ bool CdmSession::GenerateKeySetId(bool atsc_mode_enabled, } CdmResponseType CdmSession::StoreLicense() { - if (is_temporary_ || !is_offline_) { + if (is_temporary_) { LOGE("Session type prohibits storage"); return STORAGE_PROHIBITED; } - if (key_set_id_.empty()) { - LOGE("No key set ID"); - return EMPTY_KEYSET_ID; + + if (is_offline_) { + if (key_set_id_.empty()) { + LOGE("No key set ID"); + return EMPTY_KEYSET_ID; + } + + if (!license_parser_->is_offline()) { + LOGE("License policy prohibits storage"); + return OFFLINE_LICENSE_PROHIBITED; + } + + if (!StoreLicense(kLicenseStateActive, nullptr)) { + LOGE("Unable to store license"); + return STORE_LICENSE_ERROR_1; + } + return NO_ERROR; + } // if (is_offline_) + + std::string provider_session_token = + license_parser_->provider_session_token(); + if (provider_session_token.empty()) { + LOGE("No provider session token and not offline"); + return STORE_LICENSE_ERROR_2; } - if (!license_parser_->is_offline()) { - LOGE("License policy prohibits storage"); - return OFFLINE_LICENSE_PROHIBITED; - } + std::string app_id; + GetApplicationId(&app_id); + if (!file_handle_->StoreUsageInfo( + provider_session_token, key_request_, key_response_, + DeviceFiles::GetUsageInfoFileName(app_id), key_set_id_, usage_entry_, + usage_entry_number_, drm_certificate_, wrapped_private_key_)) { + LOGE("Unable to store usage info"); + // Usage info file is corrupt. Delete current usage entry and file. + if (supports_usage_info()) { + DeleteUsageEntry(usage_entry_number_); + } else { + LOGW("Cannot store, usage table not supported"); + } + std::vector provider_session_tokens; + file_handle_->DeleteAllUsageInfoForApp( + DeviceFiles::GetUsageInfoFileName(app_id), &provider_session_tokens); - if (!StoreLicense(kLicenseStateActive, nullptr)) { - LOGE("Unable to store license"); - return STORE_LICENSE_ERROR_1; + return STORE_USAGE_INFO_ERROR; } return NO_ERROR; } @@ -907,8 +999,8 @@ CdmResponseType CdmSession::RemoveKeys() { } CdmResponseType CdmSession::RemoveLicense() { - if (is_offline_) { - if (HasUsageEntry()) { + if (is_offline_ || has_provider_session_token()) { + if (has_provider_session_token() && supports_usage_info()) { DeleteUsageEntry(usage_entry_number_); } DeleteLicenseFile(); @@ -917,8 +1009,17 @@ CdmResponseType CdmSession::RemoveLicense() { } bool CdmSession::DeleteLicenseFile() { - if (!is_offline_) return false; - return file_handle_->DeleteLicense(key_set_id_); + if (!is_offline_ && !has_provider_session_token()) return false; + + if (is_offline_) { + return file_handle_->DeleteLicense(key_set_id_); + } else { + std::string app_id; + GetApplicationId(&app_id); + return file_handle_->DeleteUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), + license_parser_->provider_session_token()); + } } void CdmSession::NotifyResolution(uint32_t width, uint32_t height) { @@ -949,14 +1050,16 @@ void CdmSession::GetApplicationId(std::string* app_id) { } CdmResponseType CdmSession::UpdateUsageEntryInformation() { - if (!HasUsageEntry()) { - LOGE("Session does not have a usage entry"); + if (!has_provider_session_token() || !supports_usage_info()) { + LOGE("Unexpected state: usage_support = %s, PST present = %s, ", + supports_usage_info() ? "true" : "false", + has_provider_session_token() ? "yes" : "no"); return INCORRECT_USAGE_SUPPORT_TYPE_2; } CdmResponseType sts = NO_ERROR; // TODO(blueeyes): Add measurements to all UpdateEntry calls in a way that - // allows us to isolate this particular use case within + // allos us to isolate this particular use case within // UpdateUsageEntryInformation. M_TIME(sts = usage_table_header_->UpdateEntry( usage_entry_number_, crypto_session_.get(), &usage_entry_), @@ -964,8 +1067,11 @@ CdmResponseType CdmSession::UpdateUsageEntryInformation() { if (sts != NO_ERROR) return sts; - StoreLicense(is_release_ ? kLicenseStateReleasing : kLicenseStateActive, - nullptr); + if (is_offline_) + StoreLicense(is_release_ ? kLicenseStateReleasing : kLicenseStateActive, + nullptr); + else if (!usage_provider_session_token_.empty()) + UpdateUsageInfo(); return NO_ERROR; } @@ -1032,6 +1138,23 @@ CdmResponseType CdmSession::GetDecryptHashError(std::string* error_string) { return crypto_session_->GetDecryptHashError(error_string); } +bool CdmSession::UpdateUsageInfo() { + std::string app_id; + GetApplicationId(&app_id); + + DeviceFiles::CdmUsageData usage_data; + usage_data.provider_session_token = usage_provider_session_token_; + usage_data.license_request = key_request_; + usage_data.license = key_response_; + usage_data.key_set_id = key_set_id_; + usage_data.usage_entry = usage_entry_; + usage_data.usage_entry_number = usage_entry_number_; + + return file_handle_->UpdateUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), usage_provider_session_token_, + usage_data); +} + void CdmSession::UpdateRequestLatencyTiming(CdmResponseType sts) { if (sts == KEY_ADDED && license_request_latency_.IsStarted()) { metrics_->cdm_session_license_request_latency_ms_.Record( diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index c43d670f..d701fa5a 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -452,7 +452,7 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( } // TODO(rfrias): Refactor to avoid needing to call CdmSession - if (cdm_session && cdm_session->SupportsUsageEntries()) { + if (cdm_session && cdm_session->supports_usage_info()) { const CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); if (NO_ERROR != status) return status; } @@ -796,7 +796,7 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( } if (!provider_session_token_.empty()) { - if (cdm_session && cdm_session->SupportsUsageEntries()) { + if (cdm_session && cdm_session->supports_usage_info()) { const CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); if (NO_ERROR != status) return sts; } diff --git a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp index d22f9c83..5ec877dc 100644 --- a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp @@ -303,12 +303,13 @@ TEST_F(CdmSessionTest, UpdateUsageEntry) { // Set up mocks and expectations for the UpdateUsageEntryInformation call. EXPECT_CALL(*crypto_session_, HasUsageInfoSupport(_)) .WillRepeatedly(DoAll(SetArgPointee<0>(true), Return(true))); + EXPECT_CALL(*license_parser_, provider_session_token()) + .WillRepeatedly(Return("Mock provider session token")); EXPECT_CALL(usage_table_header_, UpdateEntry(_, NotNull(), NotNull())) .WillRepeatedly(Return(NO_ERROR)); EXPECT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); - cdm_session_->SetProviderSessionTokenForTest("Mock provider session token"); - EXPECT_TRUE(cdm_session_->SupportsUsageEntries()); + EXPECT_TRUE(cdm_session_->supports_usage_info()); EXPECT_EQ(NO_ERROR, cdm_session_->UpdateUsageEntryInformation()); // Verify the UsageEntry metric is set.