// Copyright 2017 Google Inc. All Rights Reserved. #include "usage_table_header.h" #include "crypto_session.h" #include "license.h" #include "log.h" #include "wv_cdm_constants.h" namespace { std::string kEmptyString; uint64_t kOldUsageEntryTimeSinceLicenseReceived = 0; uint64_t kOldUsageEntryTimeSinceFirstDecrypt = 0; uint64_t kOldUsageEntryTimeSinceLastDecrypt = 0; std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryPoviderSessionToken = "nahZ6achSheiqua3TohQuei0ahwohv"; } namespace wvcdm { UsageTableHeader::UsageTableHeader() : security_level_(kSecurityLevelUninitialized), requested_security_level_(kLevelDefault), is_inited_(false) { file_handle_.reset(new DeviceFiles(file_system_.get())); } bool UsageTableHeader::Init(CdmSecurityLevel security_level, CryptoSession* crypto_session) { LOGV("UsageTableHeader::Init: security level: %d", security_level); if (crypto_session == NULL) { LOGE("UsageTableHeader::Init: no crypto session provided"); return false; } switch (security_level) { case kSecurityLevelL1: case kSecurityLevelL3: break; default: LOGE("UsageTableHeader::Init: invalid security level provided: %d", security_level); return false; } security_level_ = security_level; requested_security_level_ = security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault; if (!file_handle_->Init(security_level)) { LOGE("UsageTableHeader::Init: device files initialization failed"); return false; } CdmResponseType status = USAGE_INFO_NOT_FOUND; if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_, &usage_entry_info_)) { status = crypto_session->LoadUsageTableHeader(usage_table_header_); if (status != NO_ERROR) { LOGE( "UsageTableHeader::Init: load usage table failed, security level: %d", security_level); file_handle_->DeleteAllLicenses(); usage_entry_info_.clear(); usage_table_header_.clear(); status = crypto_session->CreateUsageTableHeader(&usage_table_header_); if (status != NO_ERROR) return false; file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); } } else { status = crypto_session->CreateUsageTableHeader(&usage_table_header_); if (status != NO_ERROR) return false; file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); metrics::CryptoMetrics alternate_metrics; metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics() != NULL ? crypto_session->GetCryptoMetrics() : &alternate_metrics; UpgradeFromUsageTable(file_handle_.get(), metrics); file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); } is_inited_ = true; return true; } CdmResponseType UsageTableHeader::AddEntry( CryptoSession* crypto_session, bool persistent_license, const CdmKeySetId& key_set_id, const std::string& usage_info_file_name, uint32_t* usage_entry_number) { LOGV("UsageTableHeader::AddEntry: Lock"); AutoLock auto_lock(usage_table_header_lock_); CdmResponseType status = crypto_session->CreateUsageEntry(usage_entry_number); if (status != NO_ERROR) return status; if (*usage_entry_number < usage_entry_info_.size()) { LOGE("UsageTableHeader::AddEntry: new entry %d smaller than table size: %d", *usage_entry_number, usage_entry_info_.size()); return USAGE_INVALID_NEW_ENTRY; } if (*usage_entry_number > usage_entry_info_.size()) { LOGW("UsageTableHeader::AddEntry: new entry %d larger than table size: %d", *usage_entry_number, usage_entry_info_.size()); size_t number_of_entries = usage_entry_info_.size(); usage_entry_info_.resize(*usage_entry_number + 1); for (size_t i = number_of_entries; i < usage_entry_info_.size() - 1; ++i) { usage_entry_info_[i].storage_type = kStorageTypeUnknown; usage_entry_info_[i].key_set_id.clear(); usage_entry_info_[i].usage_info_file_name.clear(); } } else /* *usage_entry_number == usage_entry_info_.size() */ { usage_entry_info_.resize(*usage_entry_number + 1); } usage_entry_info_[*usage_entry_number].storage_type = persistent_license ? kStorageLicense : kStorageUsageInfo; usage_entry_info_[*usage_entry_number].key_set_id = key_set_id; if (!persistent_license) usage_entry_info_[*usage_entry_number].usage_info_file_name = usage_info_file_name; file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); return NO_ERROR; } CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session, const CdmUsageEntry& usage_entry, uint32_t usage_entry_number) { LOGV("UsageTableHeader::LoadEntry: Lock"); AutoLock auto_lock(usage_table_header_lock_); if (usage_entry_number >= usage_entry_info_.size()) { LOGE( "UsageTableHeader::LoadEntry: usage entry number %d larger than table " "size: %d", usage_entry_number, usage_entry_info_.size()); return USAGE_INVALID_LOAD_ENTRY; } return crypto_session->LoadUsageEntry(usage_entry_number, usage_entry); } CdmResponseType UsageTableHeader::UpdateEntry(CryptoSession* crypto_session, CdmUsageEntry* usage_entry) { LOGV("UsageTableHeader::UpdateEntryL: Lock"); AutoLock auto_lock(usage_table_header_lock_); CdmResponseType status = crypto_session->UpdateUsageEntry(&usage_table_header_, usage_entry); if (status != NO_ERROR) return status; file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); return NO_ERROR; } CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number, DeviceFiles* handle, metrics::CryptoMetrics* metrics) { LOGV("UsageTableHeader::DeleteEntry: Lock"); AutoLock auto_lock(usage_table_header_lock_); if (usage_entry_number >= usage_entry_info_.size()) { LOGE("UsageTableHeader::DeleteEntry: usage entry number %d larger than " "usage entry size %d", usage_entry_number, usage_entry_info_.size()); return USAGE_INVALID_PARAMETERS_1; } // Find the last valid entry number, in order to swap size_t swap_entry_number = usage_entry_info_.size() - 1; CdmUsageEntry swap_usage_entry; bool swap_usage_entry_valid = false; while (!swap_usage_entry_valid && swap_entry_number > usage_entry_number) { switch (usage_entry_info_[swap_entry_number].storage_type) { case kStorageLicense: case kStorageUsageInfo: { CdmResponseType status = GetEntry(swap_entry_number, handle, &swap_usage_entry); if (status == NO_ERROR) swap_usage_entry_valid = true; break; } case kStorageTypeUnknown: default: break; } if (!swap_usage_entry_valid) --swap_entry_number; } uint32_t number_of_entries_to_be_deleted = usage_entry_info_.size() - usage_entry_number; if (swap_usage_entry_valid) { CdmResponseType status = MoveEntry(swap_entry_number, swap_usage_entry, usage_entry_number, handle, metrics); // If unable to move entry, unset storage type of entry to be deleted and // resize |usage_entry_info_| so that swap usage entry is the last entry. if (status != NO_ERROR) { usage_entry_info_[usage_entry_number].storage_type = kStorageTypeUnknown; usage_entry_info_[usage_entry_number].key_set_id.clear(); if (usage_entry_info_.size() - 1 == swap_entry_number) { file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); } else { Shrink(metrics, usage_entry_info_.size() - swap_entry_number - 1); } return NO_ERROR; } number_of_entries_to_be_deleted = usage_entry_info_.size() - swap_entry_number; } return Shrink(metrics, number_of_entries_to_be_deleted); } CdmResponseType UsageTableHeader::MoveEntry( uint32_t from_usage_entry_number, const CdmUsageEntry& from_usage_entry, uint32_t to_usage_entry_number, DeviceFiles* handle, metrics::CryptoMetrics* metrics) { LOGV("UsageTableHeader::MoveEntry"); // crypto_session points to an object whose scope is this method or a test // object whose scope is the lifetime of this class scoped_ptr scoped_crypto_session; CryptoSession* crypto_session = test_crypto_session_.get(); if (crypto_session == NULL) { scoped_crypto_session.reset((new CryptoSession(metrics))); crypto_session = scoped_crypto_session.get(); } crypto_session->Open(requested_security_level_); CdmResponseType status = crypto_session->LoadUsageEntry(from_usage_entry_number, from_usage_entry); if (status != NO_ERROR) { LOGE("UsageTableHeader::MoveEntry: Failed to load usage entry: %d", from_usage_entry_number); return status; } status = crypto_session->MoveUsageEntry(to_usage_entry_number); if (status != NO_ERROR) { LOGE("UsageTableHeader::MoveEntry: Failed to move usage entry: %d->%d", from_usage_entry_number, to_usage_entry_number); return status; } usage_entry_info_[to_usage_entry_number] = usage_entry_info_[from_usage_entry_number]; CdmUsageEntry usage_entry; status = crypto_session->UpdateUsageEntry(&usage_table_header_, &usage_entry); if (status != NO_ERROR) { LOGE("UsageTableHeader::MoveEntry: Failed to update usage entry: %d", to_usage_entry_number); return status; } file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); StoreEntry(to_usage_entry_number, handle, usage_entry); return NO_ERROR; } CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, DeviceFiles* handle, CdmUsageEntry* usage_entry) { uint32_t entry_number; switch (usage_entry_info_[usage_entry_number].storage_type) { case kStorageLicense: { DeviceFiles::LicenseState license_state; std::string init_data, key_request, key_response, key_renewal_request; std::string key_renewal_response, release_server_url; int64_t playback_start_time, last_playback_time, grace_period_end_time; CdmAppParameterMap app_parameters; if (!handle->RetrieveLicense( usage_entry_info_[usage_entry_number].key_set_id, &license_state, &init_data, &key_request, &key_response, &key_renewal_request, &key_renewal_response, &release_server_url, &playback_start_time, &last_playback_time, &grace_period_end_time, &app_parameters, usage_entry, &entry_number)) { LOGE("UsageTableHeader::GetEntry: Failed to retrieve license"); return USAGE_GET_ENTRY_RETRIEVE_LICENSE_FAILED; } break; } case kStorageUsageInfo: { std::string provider_session_token; CdmKeyMessage license_request; CdmKeyResponse license_response; if (!handle->RetrieveUsageInfoByKeySetId( usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, &provider_session_token, &license_request, &license_response, usage_entry, &entry_number)) { LOGE( "UsageTableHeader::GetEntry: Failed to retrieve usage information"); return USAGE_GET_ENTRY_RETRIEVE_USAGE_INFO_FAILED; } break; } case kStorageTypeUnknown: default: LOGE( "UsageTableHeader::GetEntry: Attempting to retrieve usage " "information from unknown storage type: %d", usage_entry_info_[usage_entry_number].storage_type); return USAGE_GET_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE; } if (usage_entry_number != entry_number) { LOGE("UsageTableHeader::GetEntry: entry number mismatch: (%d, %d)", usage_entry_number, entry_number); return USAGE_ENTRY_NUMBER_MISMATCH; } return NO_ERROR; } CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, DeviceFiles* handle, const CdmUsageEntry& usage_entry) { uint32_t entry_number; switch (usage_entry_info_[usage_entry_number].storage_type) { case kStorageLicense: { DeviceFiles::LicenseState license_state; std::string init_data, key_request, key_response, key_renewal_request; std::string key_renewal_response, release_server_url; int64_t playback_start_time, last_playback_time, grace_period_end_time; CdmAppParameterMap app_parameters; CdmUsageEntry entry; if (!handle->RetrieveLicense( usage_entry_info_[usage_entry_number].key_set_id, &license_state, &init_data, &key_request, &key_response, &key_renewal_request, &key_renewal_response, &release_server_url, &playback_start_time, &last_playback_time, &grace_period_end_time, &app_parameters, &entry, &entry_number)) { LOGE("UsageTableHeader::StoreEntry: Failed to retrieve license"); return USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED; } if (!handle->StoreLicense( usage_entry_info_[usage_entry_number].key_set_id, license_state, init_data, key_request, key_response, key_renewal_request, key_renewal_response, release_server_url, playback_start_time, last_playback_time, grace_period_end_time, app_parameters, usage_entry, usage_entry_number)) { LOGE("UsageTableHeader::StoreEntry: Failed to store license"); return USAGE_STORE_LICENSE_FAILED; } break; } case kStorageUsageInfo: { CdmUsageEntry entry; std::string provider_session_token, init_data, key_request, key_response, key_renewal_request; if (!handle->RetrieveUsageInfoByKeySetId( usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, &provider_session_token, &key_request, &key_response, &entry, &entry_number)) { LOGE( "UsageTableHeader::StoreEntry: Failed to retrieve usage " "information"); return USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED; } handle->DeleteUsageInfo( usage_entry_info_[usage_entry_number].usage_info_file_name, provider_session_token); if (!handle->StoreUsageInfo( provider_session_token, key_request, key_response, usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, usage_entry, usage_entry_number)) { LOGE("UsageTableHeader::StoreEntry: Failed to store usage information"); return USAGE_STORE_USAGE_INFO_FAILED; } break; } case kStorageTypeUnknown: default: LOGE( "UsageTableHeader::GetUsageEntry: Attempting to retrieve usage " "information from unknown storage type: %d", usage_entry_info_[usage_entry_number].storage_type); return USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE; } return NO_ERROR; } CdmResponseType UsageTableHeader::Shrink( metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete) { if (usage_entry_info_.empty()) { LOGE("UsageTableHeader::Shrink: usage entry info table unexpectedly empty"); return NO_USAGE_ENTRIES; } if (usage_entry_info_.size() < number_of_usage_entries_to_delete) { LOGW( "UsageTableHeader::Shrink: cannot delete %d entries when usage entry " "table size is %d", number_of_usage_entries_to_delete, usage_entry_info_.size()); return NO_ERROR; } if (number_of_usage_entries_to_delete == 0) return NO_ERROR; usage_entry_info_.resize(usage_entry_info_.size() - number_of_usage_entries_to_delete); // crypto_session points to an object whose scope is this method or a test // object whose scope is the lifetime of this class scoped_ptr scoped_crypto_session; CryptoSession* crypto_session = test_crypto_session_.get(); if (crypto_session == NULL) { scoped_crypto_session.reset((new CryptoSession(metrics))); crypto_session = scoped_crypto_session.get(); } CdmResponseType status = crypto_session->Open(requested_security_level_); if (status != NO_ERROR) return status; status = crypto_session->ShrinkUsageTableHeader(usage_entry_info_.size(), &usage_table_header_); if (status != NO_ERROR) return status; file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); return NO_ERROR; } CdmResponseType UsageTableHeader::UpgradeFromUsageTable( DeviceFiles* handle, metrics::CryptoMetrics* metrics) { UpgradeLicensesFromUsageTable(handle, metrics); UpgradeUsageInfoFromUsageTable(handle, metrics); return NO_ERROR; } bool UsageTableHeader::UpgradeLicensesFromUsageTable( DeviceFiles* handle, metrics::CryptoMetrics* metrics) { // Fetch the key set IDs for each offline license. For each license // * retrieve the provider session token, // * create a new usage entry // * copy over the entry from the usage table // * update the usage header table and entry numbers // * save the usage table header and store the usage entry number and // usage entry along with the license to persistent memory std::vector key_set_ids; if (!handle->ListLicenses(&key_set_ids)) { LOGW( "UpgradeUsageTableHeader::UpgradeLicensesFromUsageTable: unable to " "retrieve list of licenses"); return false; } for (size_t i = 0; i < key_set_ids.size(); ++i) { DeviceFiles::LicenseState license_state; std::string init_data, key_request, key_response, key_renewal_request; std::string key_renewal_response, release_server_url; int64_t playback_start_time, last_playback_time, grace_period_end_time; CdmAppParameterMap app_parameters; CdmUsageEntry usage_entry; uint32_t usage_entry_number; if (!handle->RetrieveLicense( key_set_ids[i], &license_state, &init_data, &key_request, &key_response, &key_renewal_request, &key_renewal_response, &release_server_url, &playback_start_time, &last_playback_time, &grace_period_end_time, &app_parameters, &usage_entry, &usage_entry_number)) { LOGW( "UsageTableHeader::UpgradeLicensesFromUsageTable: Failed to " "retrieve license"); continue; } std::string provider_session_token; if (!CdmLicense::ExtractProviderSessionToken(key_response, &provider_session_token)) { LOGW( "UsageTableHeader::UpgradeLicensesFromUsageTable: Failed to " "retrieve provider session token"); continue; } if (provider_session_token.empty()) continue; CryptoSession crypto_session(metrics); CdmResponseType status = crypto_session.Open(requested_security_level_); if (status != NO_ERROR) continue; // TODO(fredgc): remove when b/65730828 is addressed if (!CreateDummyOldUsageEntry(&crypto_session)) continue; status = AddEntry(&crypto_session, true /* persistent license */, key_set_ids[i], kEmptyString, &usage_entry_number); if (status != NO_ERROR) continue; status = crypto_session.CopyOldUsageEntry(provider_session_token); if (status != NO_ERROR) { crypto_session.Close(); Shrink(metrics, 1); continue; } status = UpdateEntry(&crypto_session, &usage_entry); if (status != NO_ERROR) { crypto_session.Close(); Shrink(metrics, 1); continue; } if (!handle->StoreLicense( key_set_ids[i], license_state, init_data, key_request, key_response, key_renewal_request, key_renewal_response, release_server_url, playback_start_time, last_playback_time, grace_period_end_time, app_parameters, usage_entry, usage_entry_number)) { LOGE( "UsageTableHeader::UpgradeLicensesFromUsageTable: Failed to store " "license"); continue; } } return NO_ERROR; } bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( DeviceFiles* handle, metrics::CryptoMetrics* metrics) { // Fetch all usage files. For each file retrieve all the usage info records // within the file. For each piece of usage information // * create a new usage entry // * copy over the entry from the usage table and // * update the usage header table and entry numbers // * save the usage table header // * once done processing all the usage records from a file, save the usage // information to persistent memory along with usage entry number and usage // entry. std::vector usage_info_file_names; if (!handle->ListUsageInfoFiles(&usage_info_file_names)) { LOGW( "UpgradeUsageTableHeader::UpgradeUsageInfoFromUsageTable: Unable to " "retrieve list of usage info file names"); return false; } for (size_t i = 0; i < usage_info_file_names.size(); ++i) { std::vector usage_data; if (!handle->RetrieveUsageInfo(usage_info_file_names[i], &usage_data)) { LOGW( "UsageTableHeader::UpgradeUsageInfoFromUsageTable: Failed to " "retrieve usage records from %s", usage_info_file_names[i].c_str()); continue; } for (size_t j = 0; j < usage_data.size(); ++j) { if (usage_data[j].provider_session_token.empty()) { LOGW( "UsageTableHeader::UpgradeUsageInfoFromUsageTable: Provider " "session id empty"); continue; } CryptoSession crypto_session(metrics); CdmResponseType status = crypto_session.Open(requested_security_level_); if (status != NO_ERROR) continue; // TODO(fredgc): remove when b/65730828 is addressed if (!CreateDummyOldUsageEntry(&crypto_session)) continue; // TODO(rfrias): We need to fill in the app id, but it is hashed // and we have no way to extract. Use the hased filename instead? status = AddEntry(&crypto_session, false /* usage info */, usage_data[j].key_set_id, usage_info_file_names[i], &(usage_data[j].usage_entry_number)); if (status != NO_ERROR) continue; status = crypto_session.CopyOldUsageEntry( usage_data[j].provider_session_token); if (status != NO_ERROR) { crypto_session.Close(); Shrink(metrics, 1); continue; } status = UpdateEntry(&crypto_session, &(usage_data[j].usage_entry)); if (status != NO_ERROR) { crypto_session.Close(); Shrink(metrics, 1); continue; } } if (!handle->StoreUsageInfo(usage_info_file_names[i], usage_data)) { LOGE( "UsageTableHeader::StoreUsageInfo: Failed to store usage records to " "%s", usage_info_file_names[i].c_str()); continue; } } return NO_ERROR; } // TODO(fredgc): remove when b/65730828 is addressed bool UsageTableHeader::CreateDummyOldUsageEntry(CryptoSession* crypto_session) { return crypto_session->CreateOldUsageEntry( kOldUsageEntryTimeSinceLicenseReceived, kOldUsageEntryTimeSinceFirstDecrypt, kOldUsageEntryTimeSinceLastDecrypt, CryptoSession::kUsageDurationsInvalid, kOldUsageEntryServerMacKey, kOldUsageEntryClientMacKey, kOldUsageEntryPoviderSessionToken); } } // namespace wvcdm