// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. #include "cdm_engine.h" #include #include #include #include #include #include #include #include #include "cdm_random.h" #include "cdm_session.h" #include "cdm_session_map.h" #include "clock.h" #include "device_files.h" #include "file_store.h" #include "log.h" #include "okp_fallback_policy.h" #include "ota_keybox_provisioner.h" #include "properties.h" #include "string_conversions.h" #include "system_id_extractor.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" namespace wvcdm { namespace { const uint64_t kReleaseSessionTimeToLive = 60; // seconds const uint32_t kUpdateUsageInformationPeriod = 60; // seconds } // namespace class UsagePropertySet : public CdmClientPropertySet { public: UsagePropertySet() {} ~UsagePropertySet() override {} void set_security_level(RequestedSecurityLevel security_level) { if (kLevel3 == security_level) security_level_ = QUERY_VALUE_SECURITY_LEVEL_L3; else security_level_.clear(); } const std::string& security_level() const override { return security_level_; } bool use_privacy_mode() const override { return false; } const std::string& service_certificate() const override { return empty_; } void set_service_certificate(const std::string&) override {} bool is_session_sharing_enabled() const override { return false; } uint32_t session_sharing_id() const override { return 0; } void set_session_sharing_id(uint32_t /* id */) override {} const std::string& app_id() const override { return app_id_; } void set_app_id(const std::string& appId) { app_id_ = appId; } bool use_atsc_mode() const override { return false; } private: std::string app_id_; std::string security_level_; const std::string empty_; }; CdmEngine::CdmEngine(wvutil::FileSystem* file_system, std::shared_ptr metrics) : metrics_(metrics), file_system_(file_system), 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(); } CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, CdmClientPropertySet* property_set, const CdmSessionId& forced_session_id, WvCdmEventListener* event_listener) { return OpenSession(key_system, property_set, event_listener, &forced_session_id, nullptr); } CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, CdmClientPropertySet* property_set, WvCdmEventListener* event_listener, CdmSessionId* session_id) { return OpenSession(key_system, property_set, event_listener, nullptr, session_id); } CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, CdmClientPropertySet* property_set, WvCdmEventListener* event_listener, const CdmSessionId* forced_session_id, CdmSessionId* session_id) { if (!ValidateKeySystem(key_system)) { LOGI("Invalid key system: %s", IdToString(key_system)); return CdmResponseType(INVALID_KEY_SYSTEM); } if (session_id == nullptr && forced_session_id == nullptr) { LOGE("Input |forced_session_id| and output |session_id| are both null"); return CdmResponseType(PARAMETER_NULL); } if (forced_session_id != nullptr) { if (forced_session_id->empty()) { // This should be enforce by the CE CDM code. return CdmResponseType(EMPTY_SESSION_ID); } if (session_map_.Exists(*forced_session_id)) { return CdmResponseType(DUPLICATE_SESSION_ID_SPECIFIED); } LOGD("forced_session_id = %s", IdPtrToString(forced_session_id)); } RequestedSecurityLevel requested_security_level = kLevelDefault; if (property_set && property_set->security_level() == QUERY_VALUE_SECURITY_LEVEL_L3) { requested_security_level = kLevel3; } bool forced_level3 = false; if (requested_security_level == kLevelDefault) { if (OkpCheck()) { bool okp_provisioned = false; bool fallback = false; { std::unique_lock lock(okp_mutex_); if (!okp_provisioner_) { // Very rare race condition. Possible if two calls to OpenSession // occur the same time. Cleanup would have been performed. if (okp_fallback_) { fallback = true; } else { okp_provisioned = true; } } else if (okp_provisioner_->IsProvisioned()) { okp_provisioned = true; } else if (okp_provisioner_->IsInFallbackMode()) { fallback = true; } } if (okp_provisioned) { // OKP not required, engine may assume normal operations. OkpCleanUp(); } else if (fallback) { LOGD("Engine is falling back to L3"); OkpTriggerFallback(); forced_level3 = true; } else { // OKP is required. return CdmResponseType(NEED_PROVISIONING); } } else { std::unique_lock lock(okp_mutex_); // |okp_fallback_| would have been set previously if required. if (okp_fallback_) forced_level3 = true; } } CloseExpiredReleaseSessions(); std::unique_ptr new_session( new CdmSession(file_system_, metrics_->AddSession())); const CdmResponseType sts = new_session->Init(property_set, forced_session_id, event_listener, forced_level3); if (sts != NO_ERROR) { if (sts == NEED_PROVISIONING) { // Reserve a session ID so the CDM can return success. if (session_id) *session_id = new_session->GenerateSessionId(); } else { LOGE("Bad session init: status = %d", static_cast(sts)); } return sts; } const CdmSessionId id = new_session->session_id(); LOGI("New session: session_id = %s", IdToString(id)); std::unique_lock lock(session_map_lock_); session_map_.Add(id, new_session.release()); if (session_id) *session_id = id; return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::OpenKeySetSession( const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set, WvCdmEventListener* event_listener) { LOGI("key_set_id = %s", IdToString(key_set_id)); if (key_set_id.empty()) { LOGE("Invalid key set ID"); return CdmResponseType(EMPTY_KEYSET_ID_ENG_1); } // If in-use, release key set before re-opening, to avoid leaking // resources (CryptoSession etc). bool key_set_in_use = false; { std::unique_lock lock(release_key_sets_lock_); key_set_in_use = release_key_sets_.find(key_set_id) != release_key_sets_.end(); } if (key_set_in_use) { LOGD("Reopening existing key session"); CloseKeySetSession(key_set_id); } CdmSessionId session_id; const CdmResponseType sts = OpenSession(KEY_SYSTEM, property_set, event_listener, nullptr /* forced_session_id */, &session_id); if (sts != NO_ERROR) return sts; std::unique_lock lock(release_key_sets_lock_); release_key_sets_[key_set_id] = std::make_pair( session_id, clock_.GetCurrentTime() + kReleaseSessionTimeToLive); return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) { LOGI("session_id = %s", IdToString(session_id)); std::unique_lock lock(session_map_lock_); if (!session_map_.CloseSession(session_id)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_1); } metrics_->ConsolidateSessions(); return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) { LOGI("key_set_id = %s", IdToString(key_set_id)); CdmSessionId session_id; { std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id)); return CdmResponseType(KEYSET_ID_NOT_FOUND_1); } session_id = iter->second.first; } const CdmResponseType sts = CloseSession(session_id); std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter != release_key_sets_.end()) { release_key_sets_.erase(iter); } return sts; } bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) { std::unique_lock lock(session_map_lock_); return session_map_.Exists(session_id); } CdmResponseType CdmEngine::GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, const InitializationData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) { LOGI("session_id = %s, key_set_id = %s, license_type = %s", IdToString(session_id), IdToString(key_set_id), CdmLicenseTypeToString(license_type)); if (key_request == nullptr) { LOGE("Output |key_request| is null"); return CdmResponseType(PARAMETER_NULL); } CdmSessionId id = session_id; // NOTE: If AlwaysUseKeySetIds() is true, there is no need to consult the // |release_key_sets_| map for release licenses. if (license_type == kLicenseTypeRelease && !Properties::AlwaysUseKeySetIds()) { if (key_set_id.empty()) { LOGE("Invalid key set ID"); return CdmResponseType(EMPTY_KEYSET_ID_ENG_2); } if (!session_id.empty()) { LOGE("Session ID should be empty: session_id = %s", IdToString(session_id)); return CdmResponseType(INVALID_SESSION_ID); } std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id)); return CdmResponseType(KEYSET_ID_NOT_FOUND_2); } id = iter->second.first; } std::shared_ptr session; if (!session_map_.FindSession(id, &session)) { LOGE("Session not found: session_id = %s", IdToString(id)); return CdmResponseType(SESSION_NOT_FOUND_2); } key_request->message.clear(); if (license_type == kLicenseTypeRelease && !session->license_received()) { int error_detail = NO_ERROR; const CdmResponseType restore_status = session->RestoreOfflineSession( key_set_id, kLicenseTypeRelease, &error_detail); session->GetMetrics()->cdm_session_restore_offline_session_.Increment( restore_status, error_detail); if (restore_status != KEY_ADDED) { LOGE("Key release restoration failed: session_id = %s, status = %d", IdToString(id), static_cast(restore_status)); return restore_status; } } const CdmResponseType sts = session->GenerateKeyRequest( init_data, license_type, app_parameters, key_request); if (KEY_ADDED == sts) { return sts; } if (KEY_MESSAGE != sts) { LOGE("CdmSession::GenerateKeyRequest failed: session_id = %s, status = %d", IdToString(id), static_cast(sts)); return sts; } if (license_type == kLicenseTypeRelease) { OnKeyReleaseEvent(key_set_id); } LOGD("key_request = (%zu) %s", key_request->message.size(), wvutil::Base64SafeEncode(key_request->message).c_str()); return CdmResponseType(KEY_MESSAGE); } CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, const CdmKeyResponse& key_data, CdmLicenseType* license_type, CdmKeySetId* key_set_id) { LOGI("session_id = %s, key_set_id = %s", IdToString(session_id), IdPtrToString(key_set_id)); if (license_type == nullptr) { LOGE("Output |license_type| is null"); return CdmResponseType(PARAMETER_NULL); } CdmSessionId id = session_id; const bool license_type_release = session_id.empty(); if (license_type_release) { if (key_set_id == nullptr) { LOGE("Input/output |key_set_id| is null"); return CdmResponseType(PARAMETER_NULL); } if (key_set_id->empty()) { LOGE("Invalid key set ID"); return CdmResponseType(EMPTY_KEYSET_ID_ENG_3); } std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(*key_set_id); if (iter == release_key_sets_.end()) { LOGE("Key set not found: key_set_id = %s", IdPtrToString(key_set_id)); return CdmResponseType(KEYSET_ID_NOT_FOUND_3); } id = iter->second.first; } else { LOGD("key_data = (%zu) %s", key_data.size(), wvutil::Base64SafeEncode(key_data).c_str()); } std::shared_ptr session; if (!session_map_.FindSession(id, &session)) { LOGE("Session not found: session_id = %s", IdToString(id)); return CdmResponseType(SESSION_NOT_FOUND_3); } if (key_data.empty()) { LOGE("No key data"); return CdmResponseType(EMPTY_KEY_DATA_1); } CdmResponseType sts(KEY_ADDED); { // TODO(rfrias): Refactor. For now lock while adding keys to prevent // a race condition between this and the decryption thread. This may // occur if |policy_timers_| is reset when PolicyEngine::SetLicense // is called. std::unique_lock lock(session_map_lock_); sts = session->AddKey(key_data); } if (sts == KEY_ADDED) { if (session->is_release()) { *license_type = kLicenseTypeRelease; } else if (session->is_temporary()) { *license_type = kLicenseTypeTemporary; } else if (session->is_offline()) { *license_type = kLicenseTypeOffline; } else { *license_type = kLicenseTypeStreaming; } } if (key_set_id != nullptr) { 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 { key_set_id->clear(); } } switch (sts.code()) { case KEY_ADDED: break; case NEED_KEY: LOGI("Service certificate loaded, no key added: session_id = %s", IdToString(id)); break; default: LOGE("CdmSession::AddKey failed: session_id = %s, status = %d", IdToString(id), static_cast(sts)); break; } return sts; } CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id, const CdmKeySetId& key_set_id) { LOGI("session_id = %s, key_set_id = %s", IdToString(session_id), IdToString(key_set_id)); if (key_set_id.empty()) { LOGI("Invalid key set ID"); return CdmResponseType(EMPTY_KEYSET_ID_ENG_4); } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_4); } int error_detail = NO_ERROR; const CdmResponseType sts = session->RestoreOfflineSession( key_set_id, kLicenseTypeOffline, &error_detail); session->GetMetrics()->cdm_session_restore_offline_session_.Increment( sts, error_detail); if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) { LOGE("Restore offline session failed: session_id = %s, status = %d", IdToString(session_id), static_cast(sts)); } return sts; } CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; std::unique_lock lock(session_map_lock_); if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_5); } session->RemoveKeys(); return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::RemoveLicense(const CdmSessionId& session_id) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; std::unique_lock lock(session_map_lock_); if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_19); } return session->RemoveLicense(); } CdmResponseType CdmEngine::GenerateRenewalRequest( const CdmSessionId& session_id, CdmKeyRequest* key_request) { LOGI("session_id = %s", IdToString(session_id)); if (key_request == nullptr) { LOGE("Output |key_request| is null"); return CdmResponseType(PARAMETER_NULL); } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_6); } key_request->message.clear(); const CdmResponseType sts = session->GenerateRenewalRequest(key_request); if (KEY_MESSAGE != sts) { LOGE("Failed: session_id = %s, status = %d", IdToString(session_id), static_cast(sts)); return sts; } return CdmResponseType(KEY_MESSAGE); } CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id, const CdmKeyResponse& key_data) { LOGI("session_id = %s", IdToString(session_id)); if (key_data.empty()) { LOGE("No key data"); return CdmResponseType(EMPTY_KEY_DATA_2); } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_7); } CdmResponseType sts; M_TIME(sts = session->RenewKey(key_data), session->GetMetrics(), cdm_session_renew_key_, sts); if (KEY_ADDED != sts) { LOGE("Failed: session_id = %s, status = %d", IdToString(session_id), static_cast(sts)); return sts; } return CdmResponseType(KEY_ADDED); } CdmResponseType CdmEngine::SetSessionServiceCertificate( const CdmSessionId& session_id, const std::string& service_certificate) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_22); } return session->SetServiceCertificate(service_certificate); } CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level, const std::string& query_token, std::string* query_response) { LOGD("security_level = %s, query_token = %s", RequestedSecurityLevelToString(security_level), IdToString(query_token)); if (query_response == nullptr) { LOGE("Output |query_response| is null"); return CdmResponseType(PARAMETER_NULL); } std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); // Force OKP check on CryptoSession. Only concerned if engine // has fallen back to L3. if (security_level == kLevelDefault && OkpIsInFallbackMode()) { LOGD("Engine is falling back to L3 for query: token = %s", query_token.c_str()); security_level = kLevel3; } // Add queries here, that can be answered before a session is opened if (query_token == QUERY_KEY_SECURITY_LEVEL) { const CdmSecurityLevel found_security_level = crypto_session->GetSecurityLevel(security_level); switch (found_security_level) { case kSecurityLevelL1: *query_response = QUERY_VALUE_SECURITY_LEVEL_L1; break; case kSecurityLevelL2: *query_response = QUERY_VALUE_SECURITY_LEVEL_L2; break; case kSecurityLevelL3: *query_response = QUERY_VALUE_SECURITY_LEVEL_L3; break; case kSecurityLevelUninitialized: case kSecurityLevelUnknown: *query_response = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; break; default: LOGW("Unknown security level: %d", static_cast(found_security_level)); return CdmResponseType(UNKNOWN_ERROR); } return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL || query_token == QUERY_KEY_MAX_HDCP_LEVEL) { CryptoSession::HdcpCapability current_hdcp; CryptoSession::HdcpCapability max_hdcp; const CdmResponseType status = crypto_session->GetHdcpCapabilities( security_level, ¤t_hdcp, &max_hdcp); if (status != NO_ERROR) { LOGW("GetHdcpCapabilities failed: status = %d", static_cast(status)); return status; } *query_response = MapHdcpVersion( query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp : max_hdcp); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_USAGE_SUPPORT) { bool supports_usage_reporting; const bool got_info = crypto_session->HasUsageTableSupport( security_level, &supports_usage_reporting); if (!got_info) { LOGW("HasUsageTableSupport failed"); metrics_->GetCryptoMetrics() ->crypto_session_usage_information_support_.SetError(got_info); return CdmResponseType(UNKNOWN_ERROR); } metrics_->GetCryptoMetrics() ->crypto_session_usage_information_support_.Record( supports_usage_reporting); *query_response = supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) { size_t number_of_open_sessions; const CdmResponseType status = crypto_session->GetNumberOfOpenSessions( security_level, &number_of_open_sessions); if (status != NO_ERROR) { LOGW("GetNumberOfOpenSessions failed: status = %d", static_cast(status)); return status; } *query_response = std::to_string(number_of_open_sessions); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) { size_t maximum_number_of_sessions = 0; const CdmResponseType status = crypto_session->GetMaxNumberOfSessions( security_level, &maximum_number_of_sessions); if (status != NO_ERROR) { LOGW("GetMaxNumberOfOpenSessions failed: status = %d", static_cast(status)); return status; } *query_response = std::to_string(maximum_number_of_sessions); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) { uint32_t api_version; if (!crypto_session->GetApiVersion(security_level, &api_version)) { LOGW("GetApiVersion failed"); return CdmResponseType(UNKNOWN_ERROR); } *query_response = std::to_string(api_version); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_CURRENT_SRM_VERSION) { uint16_t current_srm_version; const CdmResponseType status = crypto_session->GetSrmVersion(¤t_srm_version); if (status == NO_ERROR) { *query_response = std::to_string(current_srm_version); return CdmResponseType(NO_ERROR); } if (status == NO_SRM_VERSION) { // SRM is not supported or not applicable (ex. local display only). *query_response = QUERY_VALUE_NONE; return CdmResponseType(NO_ERROR); } LOGW("GetCurrentSRMVersion failed: status = %d", static_cast(status)); return status; } if (query_token == QUERY_KEY_SRM_UPDATE_SUPPORT) { *query_response = QUERY_VALUE_FALSE; return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_WVCDM_VERSION) { std::string cdm_version; if (!Properties::GetWVCdmVersion(&cdm_version)) { LOGW("GetWVCdmVersion failed"); return CdmResponseType(UNKNOWN_ERROR); } *query_response = cdm_version; return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_RESOURCE_RATING_TIER) { uint32_t tier; if (!crypto_session->GetResourceRatingTier(security_level, &tier)) { LOGW("GetResourceRatingTier failed"); return CdmResponseType(UNKNOWN_ERROR); } *query_response = std::to_string(tier); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION) { if (!crypto_session->GetBuildInformation(security_level, query_response)) { LOGW("GetBuildInformation failed"); query_response->clear(); return CdmResponseType(UNKNOWN_ERROR); } return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_DECRYPT_HASH_SUPPORT) { uint32_t hash_support = 0; if (!crypto_session->GetDecryptHashSupport(security_level, &hash_support)) { LOGW("GetDecryptHashSupport failed"); return CdmResponseType(UNKNOWN_ERROR); } *query_response = std::to_string(hash_support); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_PROVISIONING_MODEL) { CdmClientTokenType token_type = kClientTokenUninitialized; const CdmResponseType status = crypto_session->GetProvisioningMethod(security_level, &token_type); if (status != NO_ERROR) { LOGW("GetProvisioningMethod failed: status = %d", static_cast(status)); return status; } switch (token_type) { case kClientTokenDrmCert: *query_response = QUERY_VALUE_DRM_CERTIFICATE; break; case kClientTokenKeybox: *query_response = QUERY_VALUE_KEYBOX; break; case kClientTokenOemCert: *query_response = QUERY_VALUE_OEM_CERTIFICATE; break; case kClientTokenBootCertChain: *query_response = QUERY_VALUE_BOOT_CERTIFICATE_CHAIN; break; case kClientTokenUninitialized: default: LOGW("GetProvisioningMethod returned invalid method: token_type = %d", static_cast(token_type)); return CdmResponseType(GET_PROVISIONING_METHOD_ERROR); } return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_MAX_USAGE_TABLE_ENTRIES) { size_t max_number_of_usage_entries; if (!crypto_session->GetMaximumUsageTableEntries( security_level, &max_number_of_usage_entries)) { LOGW("GetMaxUsageTableEntries failed"); return CdmResponseType(UNKNOWN_ERROR); } if (max_number_of_usage_entries == 0) { // Zero indicates that the table is dynamically allocated and does // not have a defined limit. Setting to max value of int32_t to // be able to fit into a Java int. max_number_of_usage_entries = std::numeric_limits::max(); } *query_response = std::to_string(max_number_of_usage_entries); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION) { uint32_t api_minor_version; if (!crypto_session->GetApiMinorVersion(security_level, &api_minor_version)) { LOGW("GetApiMinorVersion failed"); return CdmResponseType(UNKNOWN_ERROR); } *query_response = std::to_string(api_minor_version); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES) { bool supported = false, can_disable = false, cgms_a = false; if (crypto_session->GetAnalogOutputCapabilities(&supported, &can_disable, &cgms_a)) { if (supported) { if (cgms_a) { *query_response = QUERY_VALUE_CGMS_A; } else { *query_response = QUERY_VALUE_SUPPORTED; } } else { *query_response = QUERY_VALUE_NONE; } } else { *query_response = QUERY_VALUE_UNKNOWN; } return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT) { bool supported = false, can_disable = false, cgms_a = false; if (crypto_session->GetAnalogOutputCapabilities(&supported, &can_disable, &cgms_a)) { *query_response = can_disable ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; } else { *query_response = QUERY_VALUE_UNKNOWN; } return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_WATERMARKING_SUPPORT) { CdmWatermarkingSupport support; if (!crypto_session->GetWatermarkingSupport(security_level, &support)) { // Assume not supported. support = kWatermarkingNotSupported; } switch (support) { case kWatermarkingNotSupported: *query_response = QUERY_VALUE_NOT_SUPPORTED; break; case kWatermarkingConfigurable: *query_response = QUERY_VALUE_CONFIGURABLE; break; case kWatermarkingAlwaysOn: *query_response = QUERY_VALUE_ALWAYS_ON; break; default: LOGW("Unknown watermarking support: %d", static_cast(support)); return CdmResponseType(UNKNOWN_ERROR); } return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_PRODUCTION_READY) { CdmProductionReadiness readiness; if (!crypto_session->GetProductionReadiness(security_level, &readiness)) { LOGW("GetProductionReadiness failed"); return CdmResponseType(UNKNOWN_ERROR); } switch (readiness) { case kProductionReadinessUnknown: *query_response = QUERY_VALUE_UNKNOWN; break; case kProductionReadinessTrue: *query_response = QUERY_VALUE_TRUE; break; case kProductionReadinessFalse: *query_response = QUERY_VALUE_FALSE; break; default: LOGW("Unknown readiness: %d", static_cast(readiness)); return CdmResponseType(UNKNOWN_ERROR); } return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_SYSTEM_ID) { wvutil::FileSystem global_file_system; SystemIdExtractor extractor(security_level, crypto_session.get(), &global_file_system); uint32_t system_id; if (!extractor.ExtractSystemId(&system_id)) { LOGW("ExtractSystemId failed"); return CdmResponseType(UNKNOWN_ERROR); } *query_response = std::to_string(system_id); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN) { std::string bcc; std::string signature_unused; const CdmResponseType status = crypto_session->GetBootCertificateChain( security_level, &bcc, &signature_unused); if (status == NO_ERROR) { LOGD("BCC length: %zu", bcc.size()); *query_response = std::move(bcc); return CdmResponseType(NO_ERROR); } if (status == NOT_IMPLEMENTED_ERROR || status == PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR) { LOGD("BCC not available: %d", status.ToInt()); *query_response = QUERY_VALUE_NONE; return CdmResponseType(NO_ERROR); } LOGE("Failed to extract BCC: status = %d", status.ToInt()); return status; } CdmResponseType status; M_TIME(status = crypto_session->Open(security_level), metrics_->GetCryptoMetrics(), crypto_session_open_, status, security_level); if (status != NO_ERROR) return status; // Add queries here, that need an open session before they can be answered if (query_token == QUERY_KEY_DEVICE_ID) { std::string device_id; status = crypto_session->GetExternalDeviceUniqueId(&device_id); metrics_->GetCryptoMetrics() ->crypto_session_get_device_unique_id_.Increment(status); if (status != NO_ERROR) return status; *query_response = device_id; return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_PROVISIONING_ID) { std::string provisioning_id; status = crypto_session->GetProvisioningId(&provisioning_id); if (status != NO_ERROR) { LOGW("GetProvisioningId failed: status = %d", static_cast(status)); return status; } *query_response = provisioning_id; return CdmResponseType(NO_ERROR); } LOGW("Unknown status requested: query_token = %s", IdToString(query_token)); return CdmResponseType(INVALID_QUERY_KEY); } CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id, CdmQueryMap* query_response) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_8); } return session->QueryStatus(query_response); } bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return false; } return session->is_release(); } bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return false; } return session->is_offline(); } CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id, CdmQueryMap* query_response) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_9); } return session->QueryKeyStatus(query_response); } CdmResponseType CdmEngine::QueryKeyAllowedUsage(const CdmSessionId& session_id, const std::string& key_id, CdmKeyAllowedUsage* key_usage) { LOGI("session_id = %s, key_id = %s", IdToString(session_id), IdToString(key_id)); if (key_usage == nullptr) { LOGE("Output |key_usage| is null"); return CdmResponseType(PARAMETER_NULL); } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_12); } return session->QueryKeyAllowedUsage(key_id, key_usage); } CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id, CdmKeyAllowedUsage* key_usage) { LOGI("key_id = %s", IdToString(key_id)); if (!key_usage) { LOGE("Output |key_usage| is null"); return CdmResponseType(PARAMETER_NULL); } key_usage->Clear(); CdmSessionList sessions; session_map_.GetSessionList(sessions); bool found = false; for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { CdmKeyAllowedUsage found_in_this_session; const CdmResponseType sts = (*iter)->QueryKeyAllowedUsage(key_id, &found_in_this_session); if (sts == NO_ERROR) { if (found) { // Found another key. If usage settings do not match, fail. if (!key_usage->Equals(found_in_this_session)) { key_usage->Clear(); return CdmResponseType(KEY_CONFLICT_1); } } else { *key_usage = found_in_this_session; found = true; } } else if (sts != KEY_NOT_FOUND_1) { LOGE("QueryKeyAllowedUsage failed: status = %d", static_cast(sts)); key_usage->Clear(); return sts; } } return (found) ? CdmResponseType(NO_ERROR) : CdmResponseType(KEY_NOT_FOUND_2); } CdmResponseType CdmEngine::QueryOemCryptoSessionId( const CdmSessionId& session_id, CdmQueryMap* query_response) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_10); } return session->QueryOemCryptoSessionId(query_response); } // static bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) { LOGI("level = %s", CdmSecurityLevelToString(level)); metrics::CryptoMetrics alternate_crypto_metrics; std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(&alternate_crypto_metrics)); if (level == kSecurityLevelL1) { return crypto_session->GetSecurityLevel(kLevelDefault) == kSecurityLevelL1; } if (level == kSecurityLevelL3) { return crypto_session->GetSecurityLevel(kLevel3) == kSecurityLevelL3; } LOGE("Unsupported value: level = %d", static_cast(level)); return false; } /* * Composes a device provisioning request and output the request in JSON format * in *request. It also returns the default url for the provisioning server * in *default_url. * * Returns NO_ERROR for success and CdmResponseType error code if fails. */ CdmResponseType CdmEngine::GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, const std::string& service_certificate, RequestedSecurityLevel requested_security_level, CdmProvisioningRequest* request, std::string* default_url) { LOGI("cert_type = %s", CdmCertificateTypeToString(cert_type)); if (!request) { LOGE("Output |request| is null"); return CdmResponseType(INVALID_PROVISIONING_REQUEST_PARAM_1); } if (!default_url) { LOGE("Output |default_url| is null"); return CdmResponseType(INVALID_PROVISIONING_REQUEST_PARAM_2); } if (requested_security_level == kLevelDefault) { if (OkpCheck()) { if (okp_provisioner_->IsProvisioned()) { // OKP not required, engine may assume normal operations. OkpCleanUp(); } else if (okp_provisioner_->IsInFallbackMode()) { LOGD("Engine is falling back to L3"); OkpTriggerFallback(); requested_security_level = kLevel3; } else { // OKP is required. const CdmResponseType status = okp_provisioner_->GetProvisioningRequest(request, default_url); if (status == NO_ERROR) return CdmResponseType(NO_ERROR); if (status == NOT_IMPLEMENTED_ERROR) { LOGW("OKP not supoprted, falling back to L3"); OkpTriggerFallback(); requested_security_level = kLevel3; } else if (status == OKP_ALREADY_PROVISIONED) { LOGD("OKP already completed, continuing in normal operation"); OkpCleanUp(); // Continue with normal provisioning request. } else { LOGE("Failed to generate OKP request: status = %d", static_cast(status)); return status; } } } else { std::unique_lock lock(okp_mutex_); if (okp_fallback_) { requested_security_level = kLevel3; } } } // TODO(b/141705730): Remove usage entries on provisioning. std::unique_lock cert_lock(cert_provisioning_mutex_); if (!cert_provisioning_) { cert_provisioning_.reset( new CertificateProvisioning(metrics_->GetCryptoMetrics())); const CdmResponseType status = cert_provisioning_->Init(service_certificate); if (status != NO_ERROR) return status; } const CdmResponseType status = cert_provisioning_->GetProvisioningRequest( file_system_, requested_security_level, cert_type, cert_authority, file_system_->origin(), spoid_, request, default_url); if (status != NO_ERROR) { cert_provisioning_.reset(); // Release resources. } return status; } /* * The response message consists of a device certificate and the device RSA key. * The device RSA key is stored in the T.E.E. The device certificate is stored * in the device. * * Returns NO_ERROR for success and CdmResponseType error code if fails. */ CdmResponseType CdmEngine::HandleProvisioningResponse( const CdmProvisioningResponse& response, RequestedSecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key) { LOGI("response_size = %zu, security_level = %s", response.size(), RequestedSecurityLevelToString(requested_security_level)); std::unique_lock cert_lock(cert_provisioning_mutex_); if (response.empty()) { LOGE("Empty provisioning response"); cert_provisioning_.reset(); return CdmResponseType(EMPTY_PROVISIONING_RESPONSE); } if (cert == nullptr) { LOGE("Output |cert| is null"); cert_provisioning_.reset(); return CdmResponseType(INVALID_PROVISIONING_PARAMETERS_1); } if (wrapped_key == nullptr) { LOGE("Output |wrapped_key| is null"); cert_provisioning_.reset(); return CdmResponseType(INVALID_PROVISIONING_PARAMETERS_2); } if (requested_security_level == kLevelDefault) { bool use_okp = false; CdmResponseType okp_res(UNKNOWN_ERROR); { std::unique_lock lock(okp_mutex_); if (okp_provisioner_) { use_okp = true; // If the engine initiated OKP previously, it must complete it // regardless of whether the device has fallen back to L3. okp_res = okp_provisioner_->HandleProvisioningResponse(response); } else if (okp_fallback_) { requested_security_level = kLevel3; } } if (use_okp) { // Cannot hold lock when calling OkpCleanUp() or OkpTriggerFallback(). if (okp_res == NO_ERROR) { OkpCleanUp(); } else { OkpTriggerFallback(); } return okp_res; } } if (!cert_provisioning_) { // Certificate provisioning object has been released. Check if a concurrent // provisioning attempt has succeeded before declaring failure. std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); CdmResponseType status; M_TIME(status = crypto_session->Open(requested_security_level), metrics_->GetCryptoMetrics(), crypto_session_open_, status, requested_security_level); if (NO_ERROR != status) { LOGE("Provisioning object missing and crypto session open failed"); return CdmResponseType(EMPTY_PROVISIONING_CERTIFICATE_2); } CdmSecurityLevel security_level = crypto_session->GetSecurityLevel(); if (!IsProvisioned(security_level)) { LOGE("Provisioning object missing"); return CdmResponseType(EMPTY_PROVISIONING_CERTIFICATE_1); } return CdmResponseType(NO_ERROR); } const CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse( file_system_, response, cert, wrapped_key); // Release resources only on success. It is possible that a provisioning // attempt was made after this one was requested but before the response was // received, which will cause this attempt to fail. Not releasing will // allow for the possibility that the later attempt succeeds. if (NO_ERROR == ret) cert_provisioning_.reset(); return ret; } bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) { LOGI("security_level = %d", static_cast(security_level)); if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { LOGD("OKP fallback to L3"); security_level = kSecurityLevelL3; } // To validate whether the given security level is provisioned, we attempt to // initialize a CdmSession. This verifies the existence of a certificate and // attempts to load it. If this fails, initialization will return an error. UsagePropertySet property_set; property_set.set_security_level( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); return GetProvisioningStatus(security_level) == kProvisioned; } CdmProvisioningStatus CdmEngine::GetProvisioningStatus( CdmSecurityLevel security_level) { std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); CdmResponseType status = crypto_session->Open( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); if (status != NO_ERROR) { LOGE("Failed to open crypto session: status = %d", static_cast(status)); return kUnknownProvisionStatus; } const CdmSecurityLevel cdm_security_level = crypto_session->GetSecurityLevel(); DeviceFiles handle(file_system_); if (!handle.Init(cdm_security_level)) { LOGE("Failed to initialize device files."); return kUnknownProvisionStatus; } UsagePropertySet property_set; if (handle.HasCertificate(property_set.use_atsc_mode())) { return kProvisioned; } if (crypto_session->GetPreProvisionTokenType() == kClientTokenBootCertChain) { wvutil::FileSystem global_file_system; DeviceFiles global_handle(&global_file_system); if (!global_handle.Init(cdm_security_level)) { LOGE("Failed to initialize global device files."); return kUnknownProvisionStatus; } if (!global_handle.HasOemCertificate()) { return kNeedsOemCertProvisioning; } } return kNeedsDrmCertProvisioning; } CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { LOGI("security_level = %s", CdmSecurityLevelToString(security_level)); if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { LOGD("OKP fallback to L3"); security_level = kSecurityLevelL3; } // Devices with baked-in DRM certs cannot be reprovisioned and therefore must // not be unprovisioned. std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); CdmClientTokenType token_type = kClientTokenUninitialized; const CdmResponseType res = crypto_session->GetProvisioningMethod( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault, &token_type); if (res != NO_ERROR) { return res; } if (token_type == kClientTokenDrmCert) { return CdmResponseType(DEVICE_CANNOT_REPROVISION); } DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return CdmResponseType(UNPROVISION_ERROR_1); } // TODO(b/141705730): Remove usage entries during unprovisioning. if (!file_system_->IsGlobal()) { if (!handle.RemoveCertificate() || !handle.RemoveOemCertificate()) { LOGE("Unable to delete certificate"); return CdmResponseType(UNPROVISION_ERROR_2); } return CdmResponseType(NO_ERROR); } if (!handle.DeleteAllFiles()) { LOGE("Unable to delete files"); return CdmResponseType(UNPROVISION_ERROR_3); } return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::ListStoredLicenses( CdmSecurityLevel security_level, std::vector* key_set_ids) { if (!key_set_ids) { LOGE("Output |key_set_ids| is null"); return CdmResponseType(INVALID_PARAMETERS_ENG_22); } 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 CdmResponseType(LIST_LICENSE_ERROR_1); } if (!handle.ListLicenses(key_set_ids)) { LOGE("ListLicenses call failed"); return CdmResponseType(LIST_LICENSE_ERROR_2); } return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::ListUsageIds(const std::string& app_id, CdmSecurityLevel security_level, std::vector* ksids, std::vector* psts) { if (!ksids && !psts) { LOGE("Outputs |ksids| and |psts| are null"); return CdmResponseType(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 CdmResponseType(LIST_USAGE_ERROR_1); } if (!handle.ListUsageIds(app_id, ksids, psts)) { LOGE("Failed: app_id = %s, security_level = %s", IdToString(app_id), CdmSecurityLevelToString(security_level)); return CdmResponseType(LIST_USAGE_ERROR_2); } return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id, CdmSecurityLevel security_level, const CdmKeySetId& key_set_id) { 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 CdmResponseType(DELETE_USAGE_ERROR_1); } std::string provider_session_token; if (!handle.GetProviderSessionToken(app_id, key_set_id, &provider_session_token)) { LOGE("GetProviderSessionToken failed"); return CdmResponseType(DELETE_USAGE_ERROR_2); } return RemoveUsageInfo(app_id, provider_session_token); } CdmResponseType CdmEngine::GetOfflineLicenseState( const CdmKeySetId& key_set_id, CdmSecurityLevel security_level, CdmOfflineLicenseState* license_state) { if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { LOGD("OKP fallback to L3"); security_level = kSecurityLevelL3; } DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Cannot initialize device files"); return CdmResponseType(GET_OFFLINE_LICENSE_STATE_ERROR_1); } DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; if (!handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) { LOGE("Failed to retrieve license state: key_set_id = %s", IdToString(key_set_id)); return CdmResponseType(GET_OFFLINE_LICENSE_STATE_ERROR_2); } *license_state = license_data.state; return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::RemoveOfflineLicense( const CdmKeySetId& key_set_id, CdmSecurityLevel security_level) { if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { LOGD("OKP fallback to L3"); security_level = kSecurityLevelL3; } UsagePropertySet property_set; property_set.set_security_level( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Cannot initialize device files: security_level = %s", security_level == kSecurityLevelL3 ? "L3" : "Default"); return CdmResponseType(REMOVE_OFFLINE_LICENSE_ERROR_1); } CdmResponseType sts = OpenKeySetSession(key_set_id, &property_set, nullptr /* event listener */); if (sts != NO_ERROR) { LOGE("OpenKeySetSession failed: status = %d", static_cast(sts)); handle.DeleteLicense(key_set_id); return sts; } CdmSessionId session_id; CdmAppParameterMap dummy_app_params; const InitializationData dummy_init_data("", "", ""); CdmKeyRequest key_request; // Calling with no session_id is okay sts = GenerateKeyRequest(session_id, key_set_id, dummy_init_data, kLicenseTypeRelease, dummy_app_params, &key_request); if (sts == KEY_MESSAGE) { std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id)); sts = CdmResponseType(REMOVE_OFFLINE_LICENSE_ERROR_2); } else { session_id = iter->second.first; sts = RemoveLicense(session_id); } } else if (sts == LICENSE_USAGE_ENTRY_MISSING) { // It is possible that the CDM is tracking a key set ID, but has // 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"); handle.DeleteLicense(key_set_id); sts = CdmResponseType(NO_ERROR); } if (sts != NO_ERROR) { LOGE("GenerateKeyRequest failed: status = %d", static_cast(sts)); handle.DeleteLicense(key_set_id); } CloseKeySetSession(key_set_id); return sts; } CdmResponseType CdmEngine::StoreAtscLicense( RequestedSecurityLevel requested_security_level, const CdmKeySetId& key_set_id, const std::string& serialized_license_data) { std::string security_level_string; CdmResponseType status = QueryStatus(requested_security_level, QUERY_KEY_SECURITY_LEVEL, &security_level_string); if (status != NO_ERROR) return status; DeviceFiles handle(file_system_); CdmSecurityLevel security_level = security_level_string == QUERY_VALUE_SECURITY_LEVEL_L1 ? kSecurityLevelL1 : kSecurityLevelL3; if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return CdmResponseType(STORE_ATSC_LICENSE_DEVICE_FILES_INIT_ERROR); } DeviceFiles::ResponseType response_type = handle.StoreAtscLicense(key_set_id, serialized_license_data); if (response_type != DeviceFiles::kNoError) { LOGE("Unable to store ATSC license: response = %s", DeviceFiles::ResponseTypeToString(response_type)); return CdmResponseType(STORE_ATSC_LICENSE_ERROR); } return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, const CdmSecureStopId& ssid, int* error_detail, CdmUsageReport* usage_report) { // 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_report); switch (status.code()) { 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_report); return status; default: return status; } } CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, const CdmSecureStopId& ssid, RequestedSecurityLevel security_level, int* error_detail, CdmUsageReport* usage_report) { LOGI("app_id = %s, ssid = %s", IdToString(app_id), IdToString(ssid)); if (!usage_property_set_) { usage_property_set_.reset(new UsagePropertySet()); } if (usage_report == nullptr) { LOGE("Output |usage_report| is null"); return CdmResponseType(PARAMETER_NULL); } usage_report->clear(); 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 CdmResponseType(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 CdmResponseType(GET_USAGE_INFO_ERROR_2); } if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), ssid, &usage_data)) { // No entry found for that ssid. return CdmResponseType(USAGE_INFO_NOT_FOUND); } } status = usage_session_->RestoreUsageSession(usage_data, error_detail); if (KEY_ADDED != status) { LOGE("RestoreUsageSession failed: status = %d", static_cast(status)); return status; } CdmKeyRequest request; status = usage_session_->GenerateReleaseRequest(&request); if (KEY_MESSAGE != status) { LOGE("GenerateReleaseRequest failed: status = %d", static_cast(status)); return status; } *usage_report = std::move(request.message); return CdmResponseType(KEY_MESSAGE); } CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, int* error_detail, CdmUsageReport* usage_report) { LOGI("app_id = %s", IdToString(app_id)); if (usage_report == nullptr) { LOGE("Output |usage_report| is null"); return CdmResponseType(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_report); if (KEY_MESSAGE == status && !usage_report->empty()) { return status; } } while (KEY_CANCELED == status); security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3; do { status = GetUsageInfo(app_id, security_level, error_detail, usage_report); if (NEED_PROVISIONING == status) return CdmResponseType( 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, CdmUsageReport* usage_report) { LOGI("app_id = %s, security_level = %s", IdToString(app_id), RequestedSecurityLevelToString(requested_security_level)); if (usage_report == nullptr) { LOGE("Output |usage_report| is null"); return CdmResponseType(PARAMETER_NULL); } usage_report->clear(); 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 CdmResponseType(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 CdmResponseType(GET_USAGE_INFO_ERROR_4); } if (usage_data.empty()) { return CdmResponseType(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)); return status; } CdmKeyRequest request; status = usage_session_->GenerateReleaseRequest(&request); switch (status.code()) { case KEY_MESSAGE: *usage_report = std::move(request.message); break; case KEY_CANCELED: // usage information not present in usage_session_->DeleteLicenseFile(); // OEMCrypto, delete and try again break; default: LOGE("GenerateReleaseRequest failed: status = %d", static_cast(status)); 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)); 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_->SupportsUsageTable()) { 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_index); 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].key_set_id)) { 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 = CdmResponseType(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)); 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)); 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()); DeviceFiles::CdmUsageData usage_data; if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), provider_session_token, &usage_data)) { // Try other security level continue; } if (usage_session_->SupportsUsageTable()) { status = usage_session_->DeleteUsageEntry(usage_data.usage_entry_index); if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), usage_data.key_set_id)) { status = CdmResponseType(REMOVE_USAGE_INFO_ERROR_1); } usage_session_.reset(); return status; } } else { LOGE("Failed to initialize L%d device files", j); status = CdmResponseType(REMOVE_USAGE_INFO_ERROR_2); } } usage_session_.reset(); return CdmResponseType(REMOVE_USAGE_INFO_ERROR_3); } CdmResponseType CdmEngine::ReleaseUsageInfo(const CdmKeyResponse& message) { LOGI("message_size = %zu", message.size()); if (!usage_session_) { LOGE("Usage session not initialized"); return CdmResponseType(RELEASE_USAGE_INFO_ERROR); } const CdmResponseType status = usage_session_->ReleaseKey(message); usage_session_.reset(); if (NO_ERROR != status) { LOGE("ReleaseKey failed: status = %d", static_cast(status)); } return status; } CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, CdmKeyMessage* release_message) { LOGI("key_set_id = %s", IdToString(key_set_id)); // 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 CdmResponseType(EMPTY_KEYSET_ID_ENG_5); } if (release_message == nullptr) { LOGE("Output |release_message| is null"); return CdmResponseType(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 CdmResponseType(SESSION_NOT_FOUND_11); } DeviceFiles handle(file_system_); if (!handle.Init(session->GetSecurityLevel())) { LOGE("Unable to initialize device files"); return CdmResponseType(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_index), &(usage_data.drm_certificate), &(usage_data.wrapped_private_key))) { LOGE("Unable to find usage information"); return CdmResponseType(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.code()) { 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( const CdmSessionId& session_id, const CdmDecryptionParametersV16& parameters) { for (const CdmDecryptionSample& sample : parameters.samples) { if (sample.encrypt_buffer == nullptr) { LOGE("No src encrypt buffer"); return CdmResponseType(INVALID_DECRYPT_PARAMETERS_ENG_2); } if (sample.decrypt_buffer == nullptr) { if (!parameters.is_secure && !Properties::Properties::oem_crypto_use_fifo()) { LOGE("No dest decrypt buffer"); return CdmResponseType(INVALID_DECRYPT_PARAMETERS_ENG_4); } // else we must be level 1 direct and we don't need to return a buffer. } } std::unique_lock lock(session_map_lock_); std::shared_ptr session; if (session_id.empty()) { CdmSessionList sessions; session_map_.GetSessionList(sessions); // Loop through the sessions to find the session containing the key_id // with the longest remaining license validity. int64_t seconds_remaining = 0; for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { if ((*iter)->IsKeyLoaded(parameters.key_id)) { int64_t duration = (*iter)->GetDurationRemaining(); if (duration > seconds_remaining) { session = *iter; seconds_remaining = duration; } } } if (!session) { LOGE("Session not found: session_id = "); return CdmResponseType(SESSION_NOT_FOUND_FOR_DECRYPT); } } else { if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_FOR_DECRYPT); } } return session->Decrypt(parameters); } CdmResponseType CdmEngine::GenericEncrypt(const std::string& session_id, const std::string& in_buffer, const std::string& key_id, const std::string& iv, CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { if (out_buffer == nullptr) { LOGE("Output |out_buffer| is null"); return CdmResponseType(PARAMETER_NULL); } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_13); } return session->GenericEncrypt(in_buffer, key_id, iv, algorithm, out_buffer); } CdmResponseType CdmEngine::GenericDecrypt(const std::string& session_id, const std::string& in_buffer, const std::string& key_id, const std::string& iv, CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { if (out_buffer == nullptr) { LOGE("Output |out_buffer| is null"); return CdmResponseType(PARAMETER_NULL); } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_14); } return session->GenericDecrypt(in_buffer, key_id, iv, algorithm, out_buffer); } CdmResponseType CdmEngine::GenericSign(const std::string& session_id, const std::string& message, const std::string& key_id, CdmSigningAlgorithm algorithm, std::string* signature) { if (signature == nullptr) { LOGE("Output |signature| is null"); return CdmResponseType(PARAMETER_NULL); } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_15); } return session->GenericSign(message, key_id, algorithm, signature); } CdmResponseType CdmEngine::GenericVerify(const std::string& session_id, const std::string& message, const std::string& key_id, CdmSigningAlgorithm algorithm, const std::string& signature) { std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_16); } return session->GenericVerify(message, key_id, algorithm, signature); } CdmResponseType CdmEngine::ParseDecryptHashString( const std::string& hash_string, CdmSessionId* session_id, uint32_t* frame_number, std::string* hash) { if (session_id == nullptr) { LOGE("Output |session_id| is null"); return CdmResponseType(PARAMETER_NULL); } if (frame_number == nullptr) { LOGE("Output |frame_number| is null"); return CdmResponseType(PARAMETER_NULL); } if (hash == nullptr) { LOGE("Output |hash| is null"); return CdmResponseType(PARAMETER_NULL); } std::stringstream ss; std::string token; std::vector tokens; ss.str(hash_string); while (getline(ss, token, ',')) { tokens.push_back(token); } if (tokens.size() != 3) { LOGE( "Hash string has invalid format: " "Unexpected number of tokens: %zu (hash_string = %s)", tokens.size(), hash_string.c_str()); return CdmResponseType(INVALID_DECRYPT_HASH_FORMAT); } for (size_t i = 0; i < tokens.size(); ++i) { if (tokens[i].empty()) { LOGE( "Hash string has invalid format: token %zu of length 0: " "hash_string = %s", i, hash_string.c_str()); return CdmResponseType(INVALID_DECRYPT_HASH_FORMAT); } } *session_id = tokens[0]; std::istringstream iss(tokens[1]); if (!(iss >> *frame_number)) { LOGE("Error while trying to convert frame number to a numeric format: %s", hash_string.c_str()); return CdmResponseType(INVALID_DECRYPT_HASH_FORMAT); } *hash = wvutil::a2bs_hex(tokens[2]); if (hash->empty()) { LOGE("Malformed hash: %s", hash_string.c_str()); return CdmResponseType(INVALID_DECRYPT_HASH_FORMAT); } return CdmResponseType(NO_ERROR); } CdmResponseType CdmEngine::SetDecryptHash(const CdmSessionId& session_id, uint32_t frame_number, const std::string& hash) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { return CdmResponseType(SESSION_NOT_FOUND_20); } return session->SetDecryptHash(frame_number, hash); } CdmResponseType CdmEngine::GetDecryptHashError(const CdmSessionId& session_id, std::string* error_string) { LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { return CdmResponseType(SESSION_NOT_FOUND_20); } return session->GetDecryptHashError(error_string); } // TODO(rfrias) Used? Delete if unused. bool CdmEngine::IsKeyLoaded(const KeyId& key_id) { CdmSessionList sessions; session_map_.GetSessionList(sessions); for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { if ((*iter)->IsKeyLoaded(key_id)) { return true; } } return false; } bool CdmEngine::FindSessionForKey(const KeyId& key_id, CdmSessionId* session_id) { if (session_id == nullptr) { LOGE("Output |session_id| is null"); return false; } const uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id); std::unique_lock lock(session_map_lock_); CdmSessionList sessions; session_map_.GetSessionList(sessions); CdmSessionList::iterator session_iter = sessions.end(); int64_t seconds_remaining = 0; for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { CdmSessionId id = (*iter)->session_id(); if (Properties::GetSessionSharingId(id) == session_sharing_id) { if ((*iter)->IsKeyLoaded(key_id)) { int64_t duration = (*iter)->GetDurationRemaining(); if (duration > seconds_remaining) { session_iter = iter; seconds_remaining = duration; } } } } if (session_iter != sessions.end()) { *session_id = (*session_iter)->session_id(); return true; } return false; } bool CdmEngine::NotifyResolution(const CdmSessionId& session_id, uint32_t width, uint32_t height) { std::shared_ptr session; if (session_map_.FindSession(session_id, &session)) { session->NotifyResolution(width, height); return true; } return false; } bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) { return (key_system.find("widevine") != std::string::npos); } void CdmEngine::OnTimerEvent() { wvutil::Clock clock; const uint64_t current_time = clock.GetCurrentTime(); bool usage_update_period_expired = false; if (current_time - last_usage_information_update_time_ > kUpdateUsageInformationPeriod) { usage_update_period_expired = true; last_usage_information_update_time_ = current_time; } bool is_initial_usage_update = false; bool is_usage_update_needed = false; { std::unique_lock lock(session_map_lock_); CdmSessionList sessions; session_map_.GetSessionList(sessions); 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 || 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. session_map_.GetSessionList(sessions); for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { (*iter)->reset_usage_flags(); if ((*iter)->SupportsUsageTable() && (*iter)->has_provider_session_token()) { (*iter)->UpdateUsageEntryInformation(); } } } } CloseExpiredReleaseSessions(); } void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) { CdmSessionList sessions; session_map_.GetSessionList(sessions); while (!sessions.empty()) { sessions.front()->OnKeyReleaseEvent(key_set_id); sessions.pop_front(); } } CdmResponseType CdmEngine::ValidateServiceCertificate(const std::string& cert) { ServiceCertificate certificate; return certificate.Init(cert); } CdmResponseType CdmEngine::SetPlaybackId(const CdmSessionId& session_id, const std::string& playback_id) { LOGI("session_id = %s, playback_id = %s", IdToString(session_id), IdToString(playback_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { LOGE("Session not found: session_id = %s", IdToString(session_id)); return CdmResponseType(SESSION_NOT_FOUND_23); } session->GetMetrics()->playback_id_.Record(playback_id); return CdmResponseType(NO_ERROR); } std::string CdmEngine::MapHdcpVersion(CryptoSession::HdcpCapability version) { switch (version) { case HDCP_NONE: return QUERY_VALUE_HDCP_NONE; case HDCP_V1: return QUERY_VALUE_HDCP_V1; case HDCP_V2: return QUERY_VALUE_HDCP_V2_0; case HDCP_V2_1: return QUERY_VALUE_HDCP_V2_1; case HDCP_V2_2: return QUERY_VALUE_HDCP_V2_2; case HDCP_V2_3: return QUERY_VALUE_HDCP_V2_3; case HDCP_NO_DIGITAL_OUTPUT: return QUERY_VALUE_HDCP_NO_DIGITAL_OUTPUT; default: return QUERY_VALUE_HDCP_LEVEL_UNKNOWN; } } void CdmEngine::CloseExpiredReleaseSessions() { const int64_t current_time = clock_.GetCurrentTime(); std::set close_session_set; { std::unique_lock lock(release_key_sets_lock_); for (CdmReleaseKeySetMap::iterator iter = release_key_sets_.begin(); iter != release_key_sets_.end();) { if (iter->second.second < current_time) { close_session_set.insert(iter->second.first); release_key_sets_.erase(iter++); } else { ++iter; } } } for (std::set::iterator iter = close_session_set.begin(); iter != close_session_set.end(); ++iter) { CloseSession(*iter); } } bool CdmEngine::OkpCheck() { std::unique_lock lock(okp_mutex_); if (okp_initialized_) { return static_cast(okp_provisioner_); } okp_initialized_ = true; // Creating a CryptoSession will initialize OEMCrypto and flag the need // for OKP. std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); if (!crypto_session->needs_keybox_provisioning()) { // System does not require OKP provisioning. return false; } okp_provisioner_ = OtaKeyboxProvisioner::Create(metrics_->GetCryptoMetrics()); if (!okp_provisioner_) { LOGE("Failed to create engine OKP handler, falling back to L3"); okp_fallback_ = true; return false; } if (okp_provisioner_->IsProvisioned()) { // This should have been caught by call to needs_keybox_provisioning(), // but possible with simultaneous apps. okp_provisioner_.reset(); return false; } if (okp_provisioner_->IsInFallbackMode()) { LOGD("Engine is in OKP fallback mode"); okp_fallback_ = true; okp_provisioner_.reset(); return false; } return true; } bool CdmEngine::OkpIsInFallbackMode() { const bool check = OkpCheck(); std::unique_lock lock(okp_mutex_); if (!check || !okp_provisioner_ || okp_fallback_) { return okp_fallback_; } if (!okp_provisioner_->IsInFallbackMode()) { return false; } // Trigger fallback. LOGD("Engine is entering OKP fallback mode"); okp_provisioner_.reset(); okp_fallback_ = true; return true; } void CdmEngine::OkpTriggerFallback() { std::unique_lock lock(okp_mutex_); if (!okp_initialized_) { LOGD("Call to OKP fallback before OKP setup"); return; } if (okp_fallback_) return; LOGD("Engine is entering OKP fallback mode"); okp_provisioner_.reset(); okp_fallback_ = true; } void CdmEngine::OkpCleanUp() { std::unique_lock lock(okp_mutex_); if (!okp_initialized_) { LOGD("Call to OKP fallback before OKP setup"); return; } okp_provisioner_.reset(); } void CdmEngine::SetDefaultOtaKeyboxFallbackDurationRules() { OkpCheck(); std::unique_lock lock(okp_mutex_); auto* system_fallback_policy = CryptoSession::GetOkpFallbackPolicy(); if (!system_fallback_policy) { LOGW("No system fallback policy available"); return; } system_fallback_policy->SetDefaultBackoffDurationRules(); } void CdmEngine::SetFastOtaKeyboxFallbackDurationRules() { OkpCheck(); std::unique_lock lock(okp_mutex_); auto* system_fallback_policy = CryptoSession::GetOkpFallbackPolicy(); if (!system_fallback_policy) { LOGW("No system fallback policy available"); return; } system_fallback_policy->SetFastBackoffDurationRules(); } } // namespace wvcdm