// 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_session.h" #include #include #include #include #include #include "cdm_engine.h" #include "cdm_random.h" #include "cdm_usage_table.h" #include "clock.h" #include "crypto_wrapped_key.h" #include "file_store.h" #include "log.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" // Stringify turns macro arguments into static C strings. // Example: STRINGIFY(this_argument) -> "this_argument" #define STRINGIFY(PARAM) #PARAM #define RETURN_STATUS_IF_NULL(PARAM) \ if ((PARAM) == nullptr) { \ LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ return CdmResponseType(PARAMETER_NULL); \ } #define RETURN_FALSE_IF_NULL(PARAM) \ if ((PARAM) == nullptr) { \ LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ return false; \ } namespace wvcdm { namespace { const size_t kKeySetIdLength = 14; // Helper function for setting the error detail value. template void SetErrorDetail(int* error_detail, T error_code) { if (error_detail != nullptr) { *error_detail = static_cast(error_code); } } int DrmKeyTypeToMetricValue(CryptoWrappedKey::Type type) { constexpr int kUnknownMetricType = -1; constexpr int kRsaMetricType = 0; constexpr int kEccMetricType = 1; switch (type) { case CryptoWrappedKey::kRsa: return kRsaMetricType; case CryptoWrappedKey::kEcc: return kEccMetricType; default: LOGE("Unexpected DRM key type: %d", static_cast(type)); return kUnknownMetricType; } } } // namespace CdmSession::CdmSession(wvutil::FileSystem* file_system, std::shared_ptr metrics) : metrics_(metrics), initialized_(false), closed_(true), file_handle_(new DeviceFiles(file_system)), license_received_(false), is_offline_(false), is_release_(false), is_temporary_(false), security_level_(kSecurityLevelUninitialized), requested_security_level_(kLevelDefault), is_initial_usage_update_(true), is_usage_update_needed_(false), mock_license_parser_in_use_(false), mock_policy_engine_in_use_(false) { assert(metrics_); // metrics_ must not be null. crypto_metrics_ = metrics_->GetCryptoMetrics(); crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_)); life_span_.Start(); } CdmSession::~CdmSession() { if (has_provider_session_token() && SupportsUsageTable() && !is_release_) { UpdateUsageEntryInformation(); } if (!key_set_id_.empty()) { // Unreserve the license ID. file_handle_->UnreserveLicenseId(key_set_id_); } Properties::RemoveSessionPropertySet(session_id_); if (metrics_) { M_RECORD(metrics_.get(), cdm_session_life_span_, life_span_.AsMs()); metrics_->SetCompleted(); } } CdmResponseType CdmSession::Init( CdmClientPropertySet* cdm_client_property_set) { return Init(cdm_client_property_set, nullptr, nullptr, false); } CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, const CdmSessionId* forced_session_id, WvCdmEventListener* event_listener, bool forced_level3) { if (initialized_) { LOGE("Failed due to previous initialization"); return CdmResponseType(REINIT_ERROR); } if ((cdm_client_property_set && cdm_client_property_set->security_level() == QUERY_VALUE_SECURITY_LEVEL_L3) || forced_level3) { requested_security_level_ = kLevel3; security_level_ = kSecurityLevelL3; } CdmResponseType sts; M_TIME(sts = crypto_session_->Open(requested_security_level_), crypto_metrics_, crypto_session_open_, sts, requested_security_level_); if (NO_ERROR != sts) return sts; security_level_ = crypto_session_->GetSecurityLevel(); crypto_metrics_->crypto_session_security_level_.Record(security_level_); std::string oemcrypto_build; if (crypto_session_->GetBuildInformation(requested_security_level_, &oemcrypto_build)) { metrics_->oemcrypto_build_info_.Record(oemcrypto_build); } else { metrics_->oemcrypto_build_info_.SetError(false); } if (!file_handle_->Init(security_level_)) { LOGE("Unable to initialize file handle"); return CdmResponseType(SESSION_FILE_HANDLE_INIT_ERROR); } bool has_support = false; if (crypto_session_->HasUsageTableSupport(&has_support) && has_support) { usage_table_ = crypto_session_->GetUsageTable(); } if (cdm_client_property_set != nullptr) atsc_mode_enabled_ = cdm_client_property_set->use_atsc_mode(); // If a DRM certificate does not exist, indicate that provisioning is needed. // The actual validation and loading of a certificate will happen when // a key request is generated or an offline license is loaded. if (!file_handle_->HasCertificate(atsc_mode_enabled_)) return CdmResponseType(NEED_PROVISIONING); if (forced_session_id) { key_set_id_ = *forced_session_id; } else { const bool ok = GenerateKeySetId(atsc_mode_enabled_, &key_set_id_); assert(ok); if (!ok) { // Assertions may be disabled LOGE("Could not generate keyset ID"); } } session_id_ = Properties::AlwaysUseKeySetIds() ? key_set_id_ : GenerateSessionId(); metrics_->SetSessionId(session_id_); if (session_id_.empty()) { LOGE("Empty session ID"); return CdmResponseType(EMPTY_SESSION_ID); } if (cdm_client_property_set) Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); if (!mock_license_parser_in_use_) license_parser_.reset(new CdmLicense(session_id_)); if (!mock_policy_engine_in_use_) policy_engine_.reset( new PolicyEngine(session_id_, event_listener, crypto_session_.get())); std::string service_certificate; if (!Properties::GetServiceCertificate(session_id_, &service_certificate)) service_certificate.clear(); if (!license_parser_->Init(Properties::UsePrivacyMode(session_id_), service_certificate, crypto_session_.get(), policy_engine_.get())) return CdmResponseType(LICENSE_PARSER_INIT_ERROR); license_received_ = false; is_initial_decryption_ = true; initialized_ = true; closed_ = false; return CdmResponseType(NO_ERROR); } CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, CdmLicenseType license_type, int* error_detail) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } if (!key_set_id_.empty()) { file_handle_->UnreserveLicenseId(key_set_id_); } if (has_license_been_loaded_ || has_license_been_restored_) { LOGE( "Disallow multiple offline license restores or restoring a license if " "a license has already been loaded"); return CdmResponseType(RESTORE_OFFLINE_LICENSE_ERROR_3); } key_set_id_ = key_set_id; DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; if (!file_handle_->RetrieveLicense(key_set_id, &license_data, &sub_error_code)) { LOGE("Failed to retrieve license: sub_error_code = %s, key_set_id = %s", DeviceFiles::ResponseTypeToString(sub_error_code), IdToString(key_set_id)); SetErrorDetail(error_detail, sub_error_code); return sub_error_code == DeviceFiles::kFileNotFound ? CdmResponseType(KEYSET_ID_NOT_FOUND_4) : CdmResponseType(GET_LICENSE_ERROR); } offline_init_data_ = std::move(license_data.pssh_data); key_request_ = std::move(license_data.license_request); key_response_ = std::move(license_data.license); offline_key_renewal_request_ = std::move(license_data.license_renewal_request); offline_key_renewal_response_ = std::move(license_data.license_renewal); offline_release_server_url_ = std::move(license_data.release_server_url); app_parameters_ = std::move(license_data.app_parameters); usage_entry_ = std::move(license_data.usage_entry); usage_entry_index_ = license_data.usage_entry_index; CdmResponseType result = LoadPrivateOrLegacyKey( license_data.drm_certificate, license_data.wrapped_private_key); if (result != NO_ERROR) return result; // Attempts to restore a released offline license are treated as a release // retry. if (Properties::allow_restore_of_offline_licenses_with_release()) { if (license_data.state == kLicenseStateReleasing) { license_type = kLicenseTypeRelease; } } // Only restore offline licenses if they are active or this is a release // retry. if (!(license_type == kLicenseTypeRelease || license_data.state == kLicenseStateActive)) { LOGE("Invalid offline license state: state = %s, license_type = %s", CdmOfflineLicenseStateToString(license_data.state), CdmLicenseTypeToString(license_type)); return CdmResponseType(GET_RELEASED_LICENSE_ERROR); } std::string provider_session_token; bool sign_fake_request = false; // TODO(b/169483174): remove this variable. if (SupportsUsageTable()) { if (!license_parser_->ExtractProviderSessionToken( key_response_, &provider_session_token)) { provider_session_token.clear(); sign_fake_request = true; // TODO(b/169483174): remove this line. } else if (!VerifyOfflineUsageEntry()) { LOGE("License usage entry is invalid, cannot restore"); return CdmResponseType(LICENSE_USAGE_ENTRY_MISSING); } else { CdmResponseType sts = usage_table_->LoadEntry( crypto_session_.get(), usage_entry_, usage_entry_index_); crypto_metrics_->usage_table_header_load_entry_.Increment(sts); if (sts == LOAD_USAGE_ENTRY_INVALID_SESSION) { LOGE("License loaded in different session: key_set_id = %s", IdToString(key_set_id)); return CdmResponseType(USAGE_ENTRY_ALREADY_LOADED); } if (sts != NO_ERROR) { LOGE("Failed to load usage entry: status = %d", static_cast(sts)); return sts; } } } else { sign_fake_request = true; // TODO(b/169483174): remove this block. } // 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 // request if the device does not support usage tables, or if the license does // not have a usage entry. if (sign_fake_request) { std::string fake_message("empty message"); std::string core_message; std::string license_request_signature; bool should_specify_algorithm; OEMCrypto_SignatureHashAlgorithm algorithm = OEMCrypto_SHA1; uint32_t nonce; // Sign a fake message so that OEMCrypto will start the rental clock. The // signature and generated core message are ignored. result = crypto_session_->GenerateNonce(&nonce); if (result != NO_ERROR) return result; result = crypto_session_->PrepareAndSignLicenseRequest( fake_message, &core_message, &license_request_signature, should_specify_algorithm, algorithm); if (result != NO_ERROR) return result; } if (license_type == kLicenseTypeRelease) { result = license_parser_->RestoreLicenseForRelease( license_data.drm_certificate, key_request_, key_response_); if (result != NO_ERROR) { SetErrorDetail(error_detail, result.Enum()); return CdmResponseType(RELEASE_LICENSE_ERROR_1); } } else { result = license_parser_->RestoreOfflineLicense( license_data.drm_certificate, key_request_, key_response_, offline_key_renewal_response_, license_data.playback_start_time, license_data.last_playback_time, license_data.grace_period_end_time, this); if (result != NO_ERROR) { SetErrorDetail(error_detail, result.Enum()); return CdmResponseType(RESTORE_OFFLINE_LICENSE_ERROR_2); } } if (!provider_session_token.empty() && SupportsUsageTable()) { CdmResponseType sts = usage_table_->UpdateEntry( usage_entry_index_, crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { LOGE("Failed to update usage entry: status = %d", static_cast(sts)); return sts; } if (!StoreLicense(license_data.state, error_detail)) { LOGW("Unable to save updated usage info"); } } license_received_ = true; is_offline_ = true; is_release_ = license_type == kLicenseTypeRelease; has_license_been_restored_ = true; return CdmResponseType(KEY_ADDED); } CdmResponseType CdmSession::RestoreUsageSession( const DeviceFiles::CdmUsageData& usage_data, int* error_detail) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(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_index_ = usage_data.usage_entry_index; 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 (SupportsUsageTable()) { sts = usage_table_->LoadEntry(crypto_session_.get(), usage_entry_, usage_entry_index_); 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 CdmResponseType(RELEASE_LICENSE_ERROR_2); } if (SupportsUsageTable()) { sts = usage_table_->UpdateEntry(usage_entry_index_, 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 CdmResponseType(KEY_ADDED); } // This is a thin wrapper that initiates the latency metric. CdmResponseType CdmSession::GenerateKeyRequest( const InitializationData& init_data, CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) { CdmResponseType result = GenerateKeyRequestInternal( init_data, license_type, app_parameters, key_request); // Note that GenerateReleaseRequest and GenerateRenewalRequest will initialize // the timer themselves. This is duplicate because there are duplicate paths // for calling GenerateReleaseRequest and GenerateRenewalRequest. if (result == KEY_MESSAGE) { key_request_type_ = key_request->type; license_request_latency_.Start(); // Start or restart timer. } return result; } CdmResponseType CdmSession::GenerateKeyRequestInternal( const InitializationData& init_data, CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } RETURN_STATUS_IF_NULL(key_request); switch (license_type) { case kLicenseTypeTemporary: is_temporary_ = true; is_offline_ = false; break; case kLicenseTypeStreaming: is_offline_ = false; break; case kLicenseTypeOffline: is_offline_ = true; break; case kLicenseTypeRelease: is_release_ = true; break; // TODO(b/132071885): Once a license has been received, CdmSession assumes // that a call to this method is to generate a license renewal/release // or key rotation. Key rotation can be indicated by specifing a license // type kLicenseTypeEmbeddedKeyData or interrogating the PSSH // (See "else if (license_received_)" below). b/132071885 is to evaluate // whether both mechanisms are needed. case kLicenseTypeEmbeddedKeyData: return license_parser_->HandleEmbeddedKeyData(init_data); default: LOGE("Unrecognized license type: %d", static_cast(license_type)); return CdmResponseType(INVALID_LICENSE_TYPE); } if (is_release_) { return GenerateReleaseRequest(key_request); } if (license_received_) { // A call to GenerateKeyRequest after the initial license has been received // is either a renewal/release request or a key rotation event if (init_data.contains_entitled_keys()) { key_request->message.clear(); key_request->type = kKeyRequestTypeNone; key_request->url.clear(); return license_parser_->HandleEmbeddedKeyData(init_data); } return GenerateRenewalRequest(key_request); } // Otherwise, initialize license request. key_request->type = kKeyRequestTypeInitial; if (!init_data.is_supported()) { LOGW("Unsupported init data type: %s", init_data.type().c_str()); return CdmResponseType(UNSUPPORTED_INIT_DATA); } if (init_data.IsEmpty() && !license_parser_->HasInitData()) { LOGW("Init data absent"); return CdmResponseType(INIT_DATA_NOT_FOUND); } if (is_offline_ && key_set_id_.empty()) { LOGE("Key set ID not set"); return CdmResponseType(KEY_REQUEST_ERROR_1); } // Attempt to load provisioned private key if available. CdmResponseType status = LoadPrivateKey(); if (status != NO_ERROR) return status; app_parameters_ = app_parameters; status = license_parser_->PrepareKeyRequest( init_data, drm_certificate_, license_type, app_parameters, &key_request->message, &key_request->url); if (status != KEY_MESSAGE) return status; key_request_ = key_request->message; if (is_offline_) { offline_init_data_ = init_data.data(); offline_release_server_url_ = key_request->url; } return CdmResponseType(KEY_MESSAGE); } // This thin wrapper allows us to update metrics. CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { CdmResponseType sts = AddKeyInternal(key_response); UpdateRequestLatencyTiming(sts); return sts; } // AddKeyInternal() - Accept license response and extract key info. CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { if (!initialized_) { LOGE("Not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } if (is_release_) { const CdmResponseType sts = ReleaseKey(key_response); return (sts == NO_ERROR) ? CdmResponseType(KEY_ADDED) : sts; } if (license_received_) { // renewal return RenewKey(key_response); } // If usage table header+entries are supported, preprocess the license // to see if it has a provider session token. If so a new entry needs // to be created. CdmResponseType sts; std::string provider_session_token; if (SupportsUsageTable()) { if (license_parser_->ExtractProviderSessionToken(key_response, &provider_session_token) && !provider_session_token.empty()) { std::string app_id; GetApplicationId(&app_id); sts = usage_table_->AddEntry(crypto_session_.get(), is_offline_, key_set_id_, DeviceFiles::GetUsageInfoFileName(app_id), key_response, &usage_entry_index_); crypto_metrics_->usage_table_header_add_entry_.Increment(sts); if (sts != NO_ERROR) return sts; } } sts = license_parser_->HandleKeyResponse(/* is restore */ false, key_response); // Update the license sdk and service versions. const VersionInfo& version_info = license_parser_->GetServiceVersion(); metrics_->license_sdk_version_.Record(version_info.license_sdk_version()); metrics_->license_service_version_.Record( version_info.license_service_version()); // Update or invalidate entry if usage table header+entries are supported if (!provider_session_token.empty() && SupportsUsageTable()) { if (sts != KEY_ADDED) { const CdmResponseType invalidate_sts = usage_table_->InvalidateEntry( usage_entry_index_, true, file_handle_.get(), crypto_metrics_); crypto_metrics_->usage_table_header_delete_entry_.Increment( invalidate_sts); if (invalidate_sts != NO_ERROR) { LOGW("Invalidate usage entry failed: status = %d", static_cast(invalidate_sts)); } } } if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? CdmResponseType(ADD_KEY_ERROR) : sts; license_received_ = true; key_response_ = key_response; LOGV("Key added: provider_session_token = %s (size = %zu)", IdToString(license_parser_->provider_session_token()), license_parser_->provider_session_token().size()); if ((is_offline_ || has_provider_session_token()) && !is_temporary_) { if (has_provider_session_token() && SupportsUsageTable()) { usage_table_->UpdateEntry(usage_entry_index_, 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; } has_license_been_loaded_ = true; return CdmResponseType(KEY_ADDED); } CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } RETURN_STATUS_IF_NULL(query_response); switch (security_level_) { case kSecurityLevelL1: (*query_response)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1; break; case kSecurityLevelL2: (*query_response)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2; break; case kSecurityLevelL3: (*query_response)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3; break; case kSecurityLevelUninitialized: case kSecurityLevelUnknown: (*query_response)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; break; default: return CdmResponseType(INVALID_QUERY_KEY); } return CdmResponseType(NO_ERROR); } CdmResponseType CdmSession::SetServiceCertificate( const std::string& service_certificate) { return license_parser_->SetServiceCertificate(service_certificate); } CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* query_response) { return policy_engine_->Query(query_response); } CdmResponseType CdmSession::QueryKeyAllowedUsage( const std::string& key_id, CdmKeyAllowedUsage* key_usage) { return policy_engine_->QueryKeyAllowedUsage(key_id, key_usage); } CdmResponseType CdmSession::QueryOemCryptoSessionId( CdmQueryMap* query_response) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } RETURN_STATUS_IF_NULL(query_response); (*query_response)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = std::to_string(crypto_session_->oec_session_id()); return CdmResponseType(NO_ERROR); } // Decrypt() - Accept encrypted buffer and return decrypted data. CdmResponseType CdmSession::Decrypt(const CdmDecryptionParametersV16& params) { if (!initialized_) { return CdmResponseType(NOT_INITIALIZED_ERROR); } bool is_protected = std::any_of( std::begin(params.samples), std::end(params.samples), [](const CdmDecryptionSample& sample) { return std::any_of(std::begin(sample.subsamples), std::end(sample.subsamples), [](const CdmDecryptionSubsample& subsample) { return subsample.protected_bytes > 0; }); }); // Protected playback may not begin until either the start time passes or the // license is updated, so we treat this Decrypt call as invalid. // For the clear lead, we allow playback even if the key_id is not found or if // the security level is not high enough yet. if (is_protected) { if (!policy_engine_->CanDecryptContent(params.key_id)) { if (policy_engine_->IsLicenseForFuture()) return CdmResponseType(DECRYPT_NOT_READY); if (!policy_engine_->IsSufficientOutputProtection(params.key_id)) { LOGE("Key use prohibited as HDCP or resolution requirements not met"); return CdmResponseType(INSUFFICIENT_OUTPUT_PROTECTION); } return CdmResponseType(NEED_KEY); } if (!policy_engine_->CanUseKeyForSecurityLevel(params.key_id)) { LOGE( "Key use prohibited as security level requirements in the policy" " not met"); return CdmResponseType(KEY_PROHIBITED_FOR_SECURITY_LEVEL); } } const CdmResponseType status = crypto_session_->Decrypt(params); if (status == NO_ERROR) { if (is_initial_decryption_) { is_initial_decryption_ = !policy_engine_->BeginDecryption(); } has_decrypted_since_last_report_ = true; if (!is_usage_update_needed_) { is_usage_update_needed_ = has_provider_session_token(); } last_decrypt_failed_ = false; } else { if (!last_decrypt_failed_) { // Only log on the first failure. Certain failures are likely // to occur in succession of each other. LOGE("Decryption failed: sid = %s, status = %d", IdToString(session_id_), static_cast(status)); } last_decrypt_failed_ = true; wvutil::Clock clock; const int64_t current_time = clock.GetCurrentTime(); if (policy_engine_->HasLicenseOrRentalOrPlaybackDurationExpired( current_time)) { return CdmResponseType(NEED_KEY); } } return status; } // License renewal // GenerateRenewalRequest() - Construct valid renewal request for the current // session keys. CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyRequest* key_request) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } RETURN_STATUS_IF_NULL(key_request); CdmResponseType status = license_parser_->PrepareKeyUpdateRequest( true, app_parameters_, nullptr, &key_request->message, &key_request->url); key_request->type = kKeyRequestTypeRenewal; if (KEY_MESSAGE != status) return status; if (is_offline_) { offline_key_renewal_request_ = key_request->message; } key_request_type_ = key_request->type; license_request_latency_.Start(); // Start or restart timer. return CdmResponseType(KEY_MESSAGE); } // RenewKey() - Accept renewal response and update key info. CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } CdmResponseType sts = license_parser_->HandleKeyUpdateResponse( /* is renewal */ true, /* is restore */ false, key_response); // Record the timing on success. UpdateRequestLatencyTiming(sts); if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? CdmResponseType(RENEW_KEY_ERROR_1) : sts; if (is_offline_) { offline_key_renewal_response_ = key_response; if (!StoreLicense(kLicenseStateActive, nullptr /* error_detail */)) return CdmResponseType(RENEW_KEY_ERROR_2); } return CdmResponseType(KEY_ADDED); } CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } RETURN_STATUS_IF_NULL(key_request); is_release_ = true; license_request_latency_.Clear(); CdmResponseType status = license_parser_->PrepareKeyUpdateRequest( false, app_parameters_, usage_table_ == nullptr ? nullptr : this, &key_request->message, &key_request->url); key_request->type = kKeyRequestTypeRelease; if (KEY_MESSAGE != status) return status; if (has_provider_session_token() && SupportsUsageTable()) { status = usage_table_->UpdateEntry(usage_entry_index_, crypto_session_.get(), &usage_entry_); if (status != NO_ERROR) { LOGE("Update usage entry failed: status = %d", static_cast(status)); return status; } } if (is_offline_) { // Mark license as being released if (!StoreLicense(kLicenseStateReleasing, nullptr)) return CdmResponseType(RELEASE_KEY_REQUEST_ERROR); } else if (!usage_provider_session_token_.empty()) { if (SupportsUsageTable()) { if (!UpdateUsageInfo()) return CdmResponseType(RELEASE_USAGE_INFO_FAILED); } } key_request_type_ = key_request->type; license_request_latency_.Start(); // Start or restart timer. return CdmResponseType(KEY_MESSAGE); } // ReleaseKey() - Accept release response and release license. CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } CdmResponseType sts = license_parser_->HandleKeyUpdateResponse( /* is renewal */ false, /* is restore */ false, key_response); // Record the timing on success. UpdateRequestLatencyTiming(sts); if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? CdmResponseType(RELEASE_KEY_ERROR) : sts; return RemoveLicense(); } CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_index) { if (!initialized_) { LOGE("CDM session not initialized"); return CdmResponseType(NOT_INITIALIZED_ERROR); } if (!SupportsUsageTable()) { LOGE("Cannot delete entry, usage table not supported"); return CdmResponseType(INCORRECT_USAGE_SUPPORT_TYPE_1); } // The usage entry cannot be deleted if it has a crypto session handling // it, so close and reopen session. UpdateUsageEntryInformation(); CdmResponseType sts; crypto_session_->Close(); crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_)); M_TIME(sts = crypto_session_->Open(requested_security_level_), crypto_metrics_, crypto_session_open_, sts, requested_security_level_); if (sts != NO_ERROR) return sts; usage_table_ = nullptr; bool has_support = false; if (crypto_session_->HasUsageTableSupport(&has_support) && has_support) { usage_table_ = crypto_session_->GetUsageTable(); } if (usage_table_ == nullptr) { LOGE("Usage table header unavailable"); return CdmResponseType(INCORRECT_USAGE_SUPPORT_TYPE_1); } sts = usage_table_->InvalidateEntry(usage_entry_index, true, file_handle_.get(), crypto_metrics_); crypto_metrics_->usage_table_header_delete_entry_.Increment(sts); return sts; } bool CdmSession::IsKeyLoaded(const KeyId& key_id) { return license_parser_->IsKeyLoaded(key_id); } int64_t CdmSession::GetDurationRemaining() { if (policy_engine_->IsLicenseForFuture()) return 0; return policy_engine_->GetLicenseOrRentalOrPlaybackDurationRemaining(); } CdmSessionId CdmSession::GenerateSessionId() { static int session_num = 1; return SESSION_ID_PREFIX + std::to_string(++session_num); } bool CdmSession::GenerateKeySetId(bool atsc_mode_enabled, CdmKeySetId* key_set_id) { RETURN_FALSE_IF_NULL(key_set_id); while (key_set_id->empty()) { constexpr size_t random_size = (kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2; std::string random_data = wvutil::CdmRandom::RandomData(random_size); if (random_data.size() != random_size) { LOGE("Error generating random id."); return false; } if (atsc_mode_enabled) *key_set_id = ATSC_KEY_SET_ID_PREFIX + wvutil::b2a_hex(random_data); else *key_set_id = KEY_SET_ID_PREFIX + wvutil::b2a_hex(random_data); // key set collision if (file_handle_->LicenseExists(*key_set_id)) { key_set_id->clear(); } } // Reserve the license ID to avoid collisions. file_handle_->ReserveLicenseId(*key_set_id); return true; } CdmResponseType CdmSession::StoreLicense() { if (is_temporary_) { LOGE("Session type prohibits storage"); return CdmResponseType(STORAGE_PROHIBITED); } if (is_offline_) { if (key_set_id_.empty()) { LOGE("No key set ID"); return CdmResponseType(EMPTY_KEYSET_ID); } if (!license_parser_->is_offline()) { LOGE("License policy prohibits storage"); return CdmResponseType(OFFLINE_LICENSE_PROHIBITED); } if (!StoreLicense(kLicenseStateActive, nullptr)) { LOGE("Unable to store license"); return CdmResponseType(STORE_LICENSE_ERROR_1); } return CdmResponseType(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 CdmResponseType(STORE_LICENSE_ERROR_2); } 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_index_, drm_certificate_, wrapped_private_key_)) { LOGE("Unable to store usage info"); // Usage info file is corrupt. Delete current usage entry and file. if (SupportsUsageTable()) { DeleteUsageEntry(usage_entry_index_); } else { LOGW("Cannot store, usage table not supported"); } std::vector provider_session_tokens; file_handle_->DeleteAllUsageInfoForApp( DeviceFiles::GetUsageInfoFileName(app_id), &provider_session_tokens); return CdmResponseType(STORE_USAGE_INFO_ERROR); } return CdmResponseType(NO_ERROR); } bool CdmSession::StoreLicense(CdmOfflineLicenseState state, int* error_detail) { DeviceFiles::ResponseType error_detail_alt = DeviceFiles::kNoError; DeviceFiles::CdmLicenseData license_data{ key_set_id_, state, /* pssh_data = */ offline_init_data_, /* license_request = */ key_request_, /* license = */ key_response_, /* license_renewal_request = */ offline_key_renewal_request_, /* license_renewal = */ offline_key_renewal_response_, offline_release_server_url_, policy_engine_->GetPlaybackStartTime(), policy_engine_->GetLastPlaybackTime(), policy_engine_->GetGracePeriodEndTime(), app_parameters_, usage_entry_, usage_entry_index_, drm_certificate_, wrapped_private_key_}; bool result = file_handle_->StoreLicense(license_data, &error_detail_alt); if (error_detail != nullptr) { *error_detail = static_cast(error_detail_alt); } return result; } CdmResponseType CdmSession::RemoveKeys() { CdmResponseType sts; crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_)); // Ignore errors M_TIME(sts = crypto_session_->Open(requested_security_level_), crypto_metrics_, crypto_session_open_, sts, requested_security_level_); policy_engine_.reset( new PolicyEngine(session_id_, nullptr, crypto_session_.get())); return CdmResponseType(NO_ERROR); } CdmResponseType CdmSession::RemoveLicense() { if (is_offline_ || has_provider_session_token()) { if (has_provider_session_token() && SupportsUsageTable()) { DeleteUsageEntry(usage_entry_index_); } DeleteLicenseFile(); } return CdmResponseType(NO_ERROR); } bool CdmSession::DeleteLicenseFile() { 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), key_set_id_); } } void CdmSession::NotifyResolution(uint32_t width, uint32_t height) { policy_engine_->NotifyResolution(width, height); } void CdmSession::OnTimerEvent(bool update_usage) { if (update_usage && has_decrypted_since_last_report_) { policy_engine_->DecryptionEvent(); has_decrypted_since_last_report_ = false; if (is_offline_ && !is_release_) { StoreLicense(kLicenseStateActive, nullptr); } } policy_engine_->OnTimerEvent(); } void CdmSession::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) { if (key_set_id_ == key_set_id) { policy_engine_->NotifySessionExpiration(); } } void CdmSession::GetApplicationId(std::string* app_id) { if (app_id && !Properties::GetApplicationId(session_id_, app_id)) { *app_id = ""; } } CdmResponseType CdmSession::UpdateUsageEntryInformation() { if (!has_provider_session_token() || !SupportsUsageTable()) { LOGE("Unexpected state: usage_support = %s, PST present = %s, ", SupportsUsageTable() ? "true" : "false", has_provider_session_token() ? "yes" : "no"); return CdmResponseType(INCORRECT_USAGE_SUPPORT_TYPE_2); } CdmResponseType sts(NO_ERROR); // TODO(blueeyes): Add measurements to all UpdateEntry calls in a way that // allos us to isolate this particular use case within // UpdateUsageEntryInformation. M_TIME(sts = usage_table_->UpdateEntry(usage_entry_index_, crypto_session_.get(), &usage_entry_), crypto_metrics_, usage_table_header_update_entry_, sts); if (sts != NO_ERROR) return sts; if (is_offline_) StoreLicense(is_release_ ? kLicenseStateReleasing : kLicenseStateActive, nullptr); else if (!usage_provider_session_token_.empty()) UpdateUsageInfo(); return CdmResponseType(NO_ERROR); } CdmResponseType CdmSession::GenericEncrypt(const std::string& in_buffer, const std::string& key_id, const std::string& iv, CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { RETURN_STATUS_IF_NULL(out_buffer); CdmResponseType sts; M_TIME(sts = crypto_session_->GenericEncrypt(in_buffer, key_id, iv, algorithm, out_buffer), crypto_metrics_, crypto_session_generic_encrypt_, sts, metrics::Pow2Bucket(in_buffer.size()), algorithm); return sts; } CdmResponseType CdmSession::GenericDecrypt(const std::string& in_buffer, const std::string& key_id, const std::string& iv, CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { RETURN_STATUS_IF_NULL(out_buffer); CdmResponseType sts; M_TIME(sts = crypto_session_->GenericDecrypt(in_buffer, key_id, iv, algorithm, out_buffer), crypto_metrics_, crypto_session_generic_decrypt_, sts, metrics::Pow2Bucket(in_buffer.size()), algorithm); return sts; } CdmResponseType CdmSession::GenericSign(const std::string& message, const std::string& key_id, CdmSigningAlgorithm algorithm, std::string* signature) { RETURN_STATUS_IF_NULL(signature); CdmResponseType sts; M_TIME( sts = crypto_session_->GenericSign(message, key_id, algorithm, signature), crypto_metrics_, crypto_session_generic_sign_, sts, metrics::Pow2Bucket(message.size()), algorithm); return sts; } CdmResponseType CdmSession::GenericVerify(const std::string& message, const std::string& key_id, CdmSigningAlgorithm algorithm, const std::string& signature) { CdmResponseType sts; M_TIME(sts = crypto_session_->GenericVerify(message, key_id, algorithm, signature), crypto_metrics_, crypto_session_generic_verify_, sts, metrics::Pow2Bucket(message.size()), algorithm); return sts; } CdmResponseType CdmSession::SetDecryptHash(uint32_t frame_number, const std::string& hash) { return crypto_session_->SetDecryptHash(frame_number, hash); } 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_index = usage_entry_index_; return file_handle_->UpdateUsageInfo( DeviceFiles::GetUsageInfoFileName(app_id), usage_data); } void CdmSession::UpdateRequestLatencyTiming(CdmResponseType sts) { if (sts == KEY_ADDED && license_request_latency_.IsStarted()) { metrics_->cdm_session_license_request_latency_ms_.Record( license_request_latency_.AsMs(), key_request_type_); } license_request_latency_.Clear(); } bool CdmSession::VerifyOfflineUsageEntry() { // Check that the current license is the same as the expected // entry in the usage table. It is possible that the license has // been removed from the usage table but the license file remains. if (usage_entry_index_ >= usage_table_->size()) { LOGD("License usage entry does not exist: entry_index = %u, size = %zu", usage_entry_index_, usage_table_->size()); return false; } const CdmUsageEntryInfo& entry_info = usage_table_->entry_info_list().at(usage_entry_index_); if (entry_info.storage_type != kStorageLicense || entry_info.key_set_id != key_set_id_) { LOGD("License usage entry does not match"); return false; } return true; } CdmResponseType CdmSession::LoadPrivateKey() { std::string drm_certificate; CryptoWrappedKey private_key; uint32_t system_id; if (file_handle_->RetrieveCertificate(atsc_mode_enabled_, &drm_certificate, &private_key, nullptr, &system_id) != DeviceFiles::kCertificateValid) { return CdmResponseType(NEED_PROVISIONING); } return LoadPrivateKey(drm_certificate, private_key); } CdmResponseType CdmSession::LoadPrivateOrLegacyKey( const std::string& certificate, const CryptoWrappedKey& private_key) { // Use provided key if valid if (!certificate.empty() && private_key.IsValid()) return LoadPrivateKey(certificate, private_key); // Otherwise use key from legacy certificate std::string drm_certificate; CryptoWrappedKey wrapped_private_key; if (file_handle_->RetrieveLegacyCertificate( &drm_certificate, &wrapped_private_key, nullptr, nullptr) != DeviceFiles::kCertificateValid) return CdmResponseType(NEED_PROVISIONING); return LoadPrivateKey(drm_certificate, wrapped_private_key); } CdmResponseType CdmSession::LoadPrivateKey( const std::string& drm_certificate, const CryptoWrappedKey& private_key) { CdmResponseType load_cert_sts; M_TIME( load_cert_sts = crypto_session_->LoadCertificatePrivateKey(private_key), crypto_metrics_, crypto_session_load_certificate_private_key_, load_cert_sts); switch (load_cert_sts.Enum()) { case NO_ERROR: metrics_->drm_certificate_key_type_.Record( DrmKeyTypeToMetricValue(private_key.type())); drm_certificate_ = drm_certificate; wrapped_private_key_ = std::move(private_key); return CdmResponseType(NO_ERROR); case SESSION_LOST_STATE_ERROR: case SYSTEM_INVALIDATED_ERROR: return load_cert_sts; default: return CdmResponseType(NEED_PROVISIONING); } } // For testing only - takes ownership of pointers void CdmSession::set_license_parser(CdmLicense* license_parser) { license_parser_.reset(license_parser); mock_license_parser_in_use_ = true; } void CdmSession::set_crypto_session(CryptoSession* crypto_session) { crypto_session_.reset(crypto_session); } void CdmSession::set_policy_engine(PolicyEngine* policy_engine) { policy_engine_.reset(policy_engine); mock_policy_engine_in_use_ = true; } void CdmSession::set_file_handle(DeviceFiles* file_handle) { file_handle_.reset(file_handle); } } // namespace wvcdm